From 6d88c555019f32509f303e23dcfbba824fecd2ee Mon Sep 17 00:00:00 2001 From: Chris Hiszpanski Date: Thu, 18 Apr 2019 00:55:30 -0700 Subject: Initial public commit. mDNS and SDP are functional. Otherwise, library is still very much a work in progress. All tests pass. --- src/Makefile.am | 9 + src/err.h | 58 +++++ src/g711.c | 40 +++ src/g711.h | 68 +++++ src/g711_tables.c | 745 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/log.h | 83 ++++++ src/mdns.c | 607 ++++++++++++++++++++++++++++++++++++++++++++ src/mdns.h | 43 ++++ src/prng.c | 47 ++++ src/prng.h | 31 +++ src/sdp.c | 699 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/sdp.h | 150 +++++++++++ src/urtc.c | 441 ++++++++++++++++++++++++++++++++ src/urtc.h | 223 ++++++++++++++++ src/uuid.c | 76 ++++++ src/uuid.h | 54 ++++ 16 files changed, 3374 insertions(+) create mode 100644 src/Makefile.am create mode 100644 src/err.h create mode 100644 src/g711.c create mode 100644 src/g711.h create mode 100644 src/g711_tables.c create mode 100644 src/log.h create mode 100644 src/mdns.c create mode 100644 src/mdns.h create mode 100644 src/prng.c create mode 100644 src/prng.h create mode 100644 src/sdp.c create mode 100644 src/sdp.h create mode 100644 src/urtc.c create mode 100644 src/urtc.h create mode 100644 src/uuid.c create mode 100644 src/uuid.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..5855857 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,9 @@ +lib_LTLIBRARIES = liburtc.la +liburtc_la_SOURCES = g711.c g711_tables.c mdns.c prng.c sdp.c urtc.c \ + uuid.c +include_HEADERS = err.h g711.h mdns.h prng.h sdp.h urtc.h uuid.h + +# for pthreads support on linux +liburtc_la_CFLAGS = $(PTHREAD_CFLAGS) +liburtc_la_LDFLAGS = $(PTHREAD_LDFLAGS) +liburtc_la_LIBADD = $(PTHREAD_LIBS) diff --git a/src/err.h b/src/err.h new file mode 100644 index 0000000..cd7fd19 --- /dev/null +++ b/src/err.h @@ -0,0 +1,58 @@ +/* + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_ERR_H +#define _URTC_ERR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum err { + URTC_SUCCESS = 0, + + URTC_ERR, + + URTC_ERR_BAD_ARGUMENT, + URTC_ERR_INSUFFICIENT_MEMORY, + URTC_ERR_MALFORMED, + URTC_ERR_NOT_IMPLEMENTED, + + URTC_ERR_PEERCONN_MISSING_REMOTE_DESC, + + URTC_ERR_SDP_MALFORMED, + URTC_ERR_SDP_MALFORMED_VERSION, + URTC_ERR_SDP_MALFORMED_ORIGIN, + URTC_ERR_SDP_MALFORMED_TIMING, + URTC_ERR_SDP_MALFORMED_MEDIA, + URTC_ERR_SDP_MALFORMED_ATTRIBUTE, + URTC_ERR_SDP_UNSUPPORTED_FINGERPRINT_ALGO, + URTC_ERR_SDP_UNSUPPORTED_MEDIA_PROTOCOL, + URTC_ERR_SDP_UNSUPPORTED_MEDIA_TYPE +} err_t; + +#ifdef __cplusplus +} +#endif + +#endif /* _URTC_ERR_H */ diff --git a/src/g711.c b/src/g711.c new file mode 100644 index 0000000..47114d6 --- /dev/null +++ b/src/g711.c @@ -0,0 +1,40 @@ +/* + * liburtc + * Copyright (C) 2019 Chris Hiszpanski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "g711.h" + +void g711_encode(uint8_t *dst, const int16_t *src, size_t n) { + int i; + + for (i = 0; i < n; i++) { + dst[i] = pcm2ulaw[0x1FFF & (src[i] >> 3)]; + } +} + +void g711_decode(int16_t *dst, const uint8_t *src, size_t n) { + int i; + + for (i = 0; i < n; i++) { + fprintf(stderr, "i = %i, src[i] = %i\n", i, src[i]); + fprintf(stderr, "ulaw2pcm[i] = %i\n", ulaw2pcm[0]); +// dst[i] = ulaw2pcm[src[i]] << 3; + dst[i] = 0; + } +} diff --git a/src/g711.h b/src/g711.h new file mode 100644 index 0000000..b607734 --- /dev/null +++ b/src/g711.h @@ -0,0 +1,68 @@ +/* + * liburtc + * Copyright (C) 2019 Chris Hiszpanski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef URTC_G711_H +#define URTC_G711_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +extern const uint8_t pcm2ulaw[]; +extern const int16_t ulaw2pcm[]; + +/** + * ITU-T G.711 compand a 16-bit signed PCM audio buffer. Buffer may contain + * multiple channels, in which case \a n must be the number of frames times + * the number of channels. + * + * Only the upper 13-bits are companded. The lowest 3-bits are discarded. + * + * \a dst must be allocated and point to at least (\a n / 2) bytes. \a dst + * and \a src may overlap. + * + * @param dst Destination buffer + * @param src Signed 16-bit sample source buffer + * @param n Number of samples (frames times channels) + */ +void g711_encode(uint8_t *dst, const int16_t *src, size_t n); + +/** + * ITU-T G.711 decompand to a 16-bit signed PCM audio buffer. Buffer may + * contain multiple channels, in which case \a n must be the number of frames + * times the number of channels. + * + * The decompanded samples are normalized to 16-bits (the lowest 3-bits will be + * zero). + * + * \a dst must NOT overlap with \a src. + * + * @param dst Destination buffer + * @param src Signed 16-bit sample source buffer + * @param n Number of samples (frames times channels) + */ +void g711_decode(int16_t *dst, const uint8_t *src, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* URTC_G711_H */ diff --git a/src/g711_tables.c b/src/g711_tables.c new file mode 100644 index 0000000..0690d1a --- /dev/null +++ b/src/g711_tables.c @@ -0,0 +1,745 @@ +/* + * liburtc + * Copyright 2019 Chris Hiszpanski + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "g711.h" + +/* Table for ITU-T G.711 mu-law companding from 16-bit signed PCM */ +const uint8_t pcm2ulaw[] = { + 0xff, 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, + 0xf9, 0xf8, 0xf8, 0xf7, 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, + 0xf3, 0xf2, 0xf2, 0xf1, 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, + 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, + 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, + 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, + 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, + 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, + 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, + 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, + 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, + 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, + 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, + 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, + 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, + 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, + 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, + 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, + 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, + 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 +}; + +/* Table for ITU-T G.711 mu-law decompanding to 16-bit signed PCM */ +const int16_t ulaw2pcm[] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 +}; diff --git a/src/log.h b/src/log.h new file mode 100644 index 0000000..0798200 --- /dev/null +++ b/src/log.h @@ -0,0 +1,83 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_LOG_H +#define _URTC_LOG_H + +#include + +#ifdef _cplusplus +extern "C" { +#endif + +enum level { + TRACE = 0, + DEBUG, + INFO, + WARN, + ERROR, + FATAL, + NUM_LEVELS // must be last +}; + +static const char *logl[NUM_LEVELS] = { + "\033[0;37m", // trace (gray) + "\033[0;32m", // debug (green) + "", // info (white) + "\033[0;33m", // warn (yellow) + "\033[0;35m", // error (magenta) + "\033[0;31m" // fatal (red) +}; + +static const char *logn[NUM_LEVELS] = { + "[trace] ", // trace (gray) + "[debug] ", // debug (green) + "[info] ", // info (white) + "[warn] ", // warn (yellow) + "[error] ", // error (magenta) + "[fatal] " // fatal (red) +}; + +static const char *logr[NUM_LEVELS] = { + "\033[0m\n", // trace + "\033[0m\n", // debug + "\n", // info + "\033[0m\n", // warn + "\033[0m\n", // error + "\033[0m\n" // fatal +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define AT __FILE__ ":" TOSTRING(__LINE__) + +#define log(lvl, format, ...) \ + do { \ + fprintf(stderr, "%s" AT " %s" format "%s", logl[lvl], logn[lvl], ##__VA_ARGS__, logr[lvl]); \ + } while (0); + +#ifdef _cplusplus +} +#endif + +#endif // _URTC_LOG_H diff --git a/src/mdns.c b/src/mdns.c new file mode 100644 index 0000000..fa782e4 --- /dev/null +++ b/src/mdns.c @@ -0,0 +1,607 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include // IFF_LOOPBACK (include pre ifaddrs.h) +#include // getifaddrs +#include // HOST_NAME_MAX +#include +#include // memcpy +#include // sleep + +#include +#include +#ifdef WITH_IPV6 + #include +#endif +#include // sendto, setsocketopt, socket + +#include "err.h" +#include "log.h" +#include "mdns.h" +#include "uuid.h" + +#define MDNS_ADDR "224.0.0.251" +#define MDNS_PORT 5353 + +#define DEFAULT_TTL 120 // time-to-live recommended by RFC 6762 + +#define CLASS_INTERNET 1 +#define CACHE_FLUSH (1 << 15) + +#define FLAG_RESPONSE (1 << 15) +#define FLAG_OPCODE (0xF << 11) + +#ifndef HOST_NAME_MAX + #define HOST_NAME_MAX 255 +#endif + +struct __attribute__((__packed__)) header { + uint16_t id; + uint16_t flags; + uint16_t n_question; // number of question records + uint16_t n_answer; // number of answer records + uint16_t n_authority; // number of authority records + uint16_t n_additional; // number of additional records +}; + +struct __attribute__((__packed__)) qname { + uint8_t size; + uint8_t name[]; +}; + +struct __attribute__((__packed__)) answer { + // variable length name precedes answer + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t size; + uint8_t data[]; +}; + +/** + * Subscribe to mDNS queries + * + * Opens new multicast socket on the mDNS port. This socket receives and can + * be used to send mDNS datagrams. Close with mdns_unsubscribe(). + * + * \return Socket file descriptor on success, negative on error. + */ +int mdns_subscribe() { + // create socket + int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (-1 == sockfd) { + log(ERROR, "%s", strerror(errno)); + goto _fail_create_socket; + } + + // enable address/port reuse + { + const unsigned int opt = 1; + if (-1 == setsockopt( + sockfd, + SOL_SOCKET, +#if defined(__APPLE__) || defined(__FreeBSD__) + SO_REUSEPORT, +#else + SO_REUSEADDR, +#endif + &opt, + sizeof(opt) + )) { + log(ERROR, "%s", strerror(errno)); + goto _fail_enable_addr_reuse; + } + } + + // bind socket to mDNS query port + { + const struct sockaddr_in mgroup = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(INADDR_ANY), + .sin_port = htons(MDNS_PORT) + }; + if (-1 == bind(sockfd, (struct sockaddr *)(&mgroup), sizeof(mgroup))) { + log(ERROR, "%s", strerror(errno)); + goto _fail_bind; + } + } + + // disable loopback + { + const int opt = 0; + if (-1 == setsockopt( + sockfd, + IPPROTO_IP, + IP_MULTICAST_LOOP, + &opt, + sizeof(opt) + )) { + log(ERROR, "setsockopt: %s", strerror(errno)); + goto _fail_disable_loopback; + } + } + + // join multicast group + { + const struct ip_mreqn imr = { + .imr_multiaddr.s_addr = inet_addr(MDNS_ADDR), + .imr_address.s_addr = htonl(INADDR_ANY) + }; + if (-1 == setsockopt( + sockfd, + IPPROTO_IP, + IP_ADD_MEMBERSHIP, + &imr, + sizeof(imr) + )) { + log(ERROR, "setsockopt: %s", strerror(errno)); + goto _fail_join_multicast_group; + } + } + + return sockfd; + +_fail_join_multicast_group: +_fail_disable_loopback: +_fail_bind: +_fail_enable_addr_reuse: + close(sockfd); +_fail_create_socket: + + return -URTC_ERR; +} + +/** + * Unsubscribe from mDNS queries + * + * Close multicast socket previously opened with mdns_subscribe(). + * + * \param sockfd Socket file descriptor returned by mdns_subscribe(). + * + * \return 0 on success, negative on error. + */ +int mdns_unsubscribe(int sockfd) { + if (-1 == close(sockfd)) { + log(ERROR, "close: %s", strerror(errno)); + return -URTC_ERR; + } + + return 0; +} + +/** + * Send a multicast DNS query + * + * \param name Hostname to query. Must end with ".local". + * + * \return 0 on success, negative on error. + */ +int mdns_query(const char *name) { + // create socket + int msockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (-1 == msockfd) { + log(FATAL, "socket: %s", strerror(errno)); + } + + uint8_t q[512]; + size_t qlen = 0; + + // header + struct header hdr = { + .n_question = htons(1) + }; + memcpy(&q[qlen], &hdr, sizeof(hdr)); + qlen += sizeof(hdr); + + // qname + q[qlen] = strlen(name); + memcpy(&q[1 + qlen], name, strlen(name)); + qlen += 1 + q[qlen]; + + q[qlen] = strlen("local"); + memcpy(&q[1 + qlen], "local", strlen("local")); + qlen += 1 + q[qlen]; + + q[qlen++] = 0; // root record + + // qtype: 255 = any + q[qlen++] = 0; + q[qlen++] = 255; + + // qclass: 1 = ipv4 + q[qlen++] = 0x80; // unicast response please + q[qlen++] = 1; + + // send query + struct sockaddr_in mgroup = { + .sin_family = AF_INET, + .sin_addr.s_addr = inet_addr(MDNS_ADDR), + .sin_port = htons(MDNS_PORT) + }; + if (-1 == sendto(msockfd, q, qlen, 0, (struct sockaddr *)(&mgroup), sizeof(mgroup))) { + log(FATAL, "sendto: %s", strerror(errno)); + } + + return 0; +} + +/** + * Simple mDNS response parser (single record only) + * + * \param response mDNS response packet + * \param len Length of response, in bytes + * + * \return 0 on success, negative on error. + */ +int mdns_parse_response(const void *response, size_t len) +{ + // sanity checks + if (!response) return -URTC_ERR_BAD_ARGUMENT; + if (len < sizeof(struct header)) return -URTC_ERR_MALFORMED; + + struct header *hdr = (struct header *)(response); + response += sizeof(struct header); + len -= sizeof(struct header); + + // transaction id must be zero + if (0 != hdr->id) return -URTC_ERR_MALFORMED; + + // flags must indicate a response + if (!(ntohs(hdr->flags) & FLAG_RESPONSE)) return -URTC_ERR_MALFORMED; + + // sent single question, expecting reponse for single question + if (1 != ntohs(hdr->n_question)) return -URTC_ERR_MALFORMED; + + // parse query names + struct qname *qname; + do { + if (len < 1) return -URTC_ERR_MALFORMED; + qname = (struct qname *)(response); + if (len < 1 + qname->size) return -URTC_ERR_MALFORMED; + response += 1 + qname->size; + len -= 1 + qname->size; + } while (qname->size); + + // parse query type (ignore) + if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED; + response += sizeof(uint16_t); + len -= sizeof(uint16_t); + + // parse query class (ignore) + if (len < sizeof(uint16_t)) return -URTC_ERR_MALFORMED; + response += sizeof(uint16_t); + len -= sizeof(uint16_t); + + // answers + for (int i = 0; i < ntohs(hdr->n_answer); i++) { + uint16_t size; + uint8_t namelen; + + // parse name (assume matches) + if (len < 2) return -URTC_ERR_MALFORMED; + namelen = *(uint8_t *)response; + switch (namelen) { + // name compression + case 0xc0: + response += 2; + len -= 2; + break; + // no name compression + default: + response += 1 + namelen; + len -= 1 + namelen; + break; + } + + if (len < sizeof(struct answer)) return -URTC_ERR_MALFORMED; + struct answer *answer = (struct answer *)response; + response += sizeof(struct answer); + len -= sizeof(struct answer); + + size = ntohs(answer->size); + if (len < size) return -URTC_ERR_MALFORMED; + response += size; + len -= size; + } + + return 0; +} + +/** + * Check if run-length encoded query name resource records match hostname + * + * \param expected Expected hostname record. + * \param q Length-encoded query names. + * \param len Pointer to minimum length of q. Overwritten with actual length. + * + * \return Positive on match, 0 on no match, negative on malformed query. + */ +static int match(const char *expected, const uint8_t *q, int *len) { + struct qname *qname; + bool matches; + int n_records; + int n; + + if (!q) return -URTC_ERR_BAD_ARGUMENT; + if (!expected) return -URTC_ERR_BAD_ARGUMENT; + if (!len) return -URTC_ERR_BAD_ARGUMENT; + + n = *len; + matches = true; + + n_records = 0; + do { + // minimum size check: a single root record (1 byte) + if (n < 1) return -URTC_ERR_MALFORMED; + qname = (struct qname *)(q); + + // minimum size check: length byte plus stated length + if (n < 1 + qname->size) return -URTC_ERR_MALFORMED; + + // match records + switch (n_records) { + // match hostname record + case 0: + if (qname->size == strlen(expected)) { + if (0 == memcmp(qname->name, expected, qname->size)) { + break; // matched + } + } + matches = false; + break; + // match "local" record + case 1: + if (qname->size == sizeof("local")-1) { + if (0 == memcmp(qname->name, "local", sizeof("local")-1)) { + break; //matched + } + } + matches = false; + break; + // match root record + case 2: + if (qname->size == 0) { + break; + } + matches = false; + break; + // match ancillary records + default: + matches = false; + break; + } + + q += 1 + qname->size; + n -= 1 + qname->size; + n_records++; + } while(qname->size); + + // update with actual length of run-length encoding + *len -= n; + + return matches; +} + +/** + * Validate whether mDNS packet is a query for the specified hostname + * + * Only standard, single question queries are supported. All others + * return an error. + * + * \param q Query buffer (from network) + * \param n Size of query (in bytes) + * \param hostname Queried hostname + * + * \return 0 on success, negative on error + */ +int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname) +{ + const struct header *hdr = (struct header *)q; + const uint8_t *qi = q; + const size_t ni = n; + int found; + + found = 0; + + // sanity checks + if (!q) return -URTC_ERR_BAD_ARGUMENT; + if (n < sizeof(struct header)) return -URTC_ERR_MALFORMED; + + q += sizeof(struct header); + n -= sizeof(struct header); + + // transaction id must be zero + // note: hdr in host byte order (0 is byte order agnostic) + if (0 != hdr->id) return -URTC_ERR_MALFORMED; + + // only support standard, non-truncated, non-recursive queries + // note: hdr in host byte order (non-0 is byte order agnostic) + if (hdr->flags) return -URTC_ERR_NOT_IMPLEMENTED; + + // parse questions + for (int j = 0; j < ntohs(hdr->n_question); j++) { + bool matched; + int ret; + int len; + + // upper two bits set? "compressed" records + matched = false; + if (n > 2 && q[0] >> 6 == 3) { + // compressed (i.e. offset to previous record) + const uint16_t offset = ((q[0] & 0x3F) << 8) | q[1]; + if (offset > ni) return -URTC_ERR_MALFORMED; + len = ni - offset; + ret = match(hostname, qi + offset, &len); + q += 2; + n -= 2; + } else { + // uncompressed + len = n; + ret = match(hostname, q, &len); + q += len; + n -= len; + } + if (ret < 0) return ret; + if (ret > 0) matched = true; + + // parse query type for A and AAAA records + if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED; + if (matched) { + uint16_t type = ntohs(*(uint16_t *)(q)); + if (TYPE_A == type || TYPE_AAAA == type) { + found |= type; + } + } + q += sizeof(uint16_t); + n -= sizeof(uint16_t); + + // parse query class (ignore) + if (n < sizeof(uint16_t)) return -URTC_ERR_MALFORMED; + q += sizeof(uint16_t); + n -= sizeof(uint16_t); + } + + return found; +} + +/** + * Send mDNS response + * + * \param hostname NULL-terminated hostname (sans ".local" suffix) + * + * \return 0 on success, negative on error + */ +int mdns_send_response(int sockfd, const char *hostname) { + struct ifaddrs *ifaddrs; + struct header *hdr; + uint8_t *wr; // writer (convenience pointer) + uint8_t response[ + sizeof(struct header) + + 1 + HOST_NAME_MAX + 1 + // max size of length and host record + 1 + sizeof(".local") + // size of length and local record + 1 + // size of root record + sizeof(struct answer) + + 16 // size of AAAA record (largest record) + ]; + hdr = (struct header *)(response); + wr = response; + + // write header + *hdr = (struct header){ + .id = 0, + .flags = htons(FLAG_RESPONSE), + .n_question = 0, + .n_answer = 0, + .n_authority = 0, + .n_additional = 0 + }; + wr += sizeof(struct header); + + // get linked list of network interfaces + if (-1 == getifaddrs(&ifaddrs)) { + log(ERROR, "%s", strerror(errno)); + goto _fail_getifaddrs; + } + + // for each network interface... + for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) { + // skip loopback interfaces + if (ifaddr->ifa_flags & IFF_LOOPBACK) continue; + + // for an AF_INET* interface address, display the address + if (ifaddr->ifa_addr && ifaddr->ifa_addr->sa_family == AF_INET) { + + // write host record + { + strncpy((char *)(wr + 1), hostname, HOST_NAME_MAX); + wr[1 + HOST_NAME_MAX - 1] = '\0'; + *wr = strlen((char *)(wr + 1)); + wr += 1 + strlen((char *)(wr + 1)); + } + + // write local record + { + const char local[] = "local"; + *wr++ = sizeof(local) - 1; + memcpy(wr, local, sizeof(local) - 1); + wr += sizeof(local) - 1; + } + + // write root record + *wr++ = 0; + + // write answer + { + struct answer ans = { + .type = htons(TYPE_A), + .class = htons(CACHE_FLUSH | CLASS_INTERNET), + .ttl = htonl(DEFAULT_TTL), + .size = htons(sizeof(struct in_addr)), + }; + memcpy(wr, &ans, sizeof(ans)); + wr += sizeof(ans); + + memcpy(wr, + &(((struct sockaddr_in *)(ifaddr->ifa_addr))->sin_addr), + sizeof(struct in_addr)); + wr += sizeof(struct in_addr); + } + + // increment number of answers + hdr->n_answer++; + + if (hdr->n_answer == 1) { + break; + } + } + } + + // free linked list of network interfaces + freeifaddrs(ifaddrs); + + // convert to network byte order + hdr->n_answer = htons(hdr->n_answer); + + // send response + struct sockaddr_in mgroup = { + .sin_family = AF_INET, + .sin_addr.s_addr = inet_addr(MDNS_ADDR), + .sin_port = htons(MDNS_PORT) + }; + if (-1 == sendto( + sockfd, + response, + wr - response, + 0, + (struct sockaddr *)(&mgroup), + sizeof(mgroup) + )) { + log(FATAL, "sendto: %s", strerror(errno)); + } + + return 0; + +_fail_getifaddrs: + return -URTC_ERR; +} diff --git a/src/mdns.h b/src/mdns.h new file mode 100644 index 0000000..7e73ba6 --- /dev/null +++ b/src/mdns.h @@ -0,0 +1,43 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_MDNS_H +#define _URTC_MDNS_H + +#include +#include + +#define TYPE_A 1 // IPv4 record +#define TYPE_AAAA 28 // IPv6 record + +// server: for processing queries and generating responses +int mdns_subscribe(); +int mdns_unsubscribe(int sockfd); +int mdns_validate_query(const uint8_t *q, size_t n, const char *hostname); +int mdns_send_response(int sockfd, const char *hostname); + +// client: for sending queries and processing responses +int mdns_parse_response(const void *response, size_t len); +int mdns_query(const char *name); + +#endif // _URTC_MDNS_H diff --git a/src/prng.c b/src/prng.c new file mode 100644 index 0000000..147304b --- /dev/null +++ b/src/prng.c @@ -0,0 +1,47 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include + +#include "prng.h" + +/** + * Initialize pseudo-random number generator + */ +void prng_init() { + srand(time(NULL)); +} + +/** + * Fill specified memory with pseudo-random data + * + * \param dst Destination memory + * \param sz Size (in bytes) of destination memory + */ +void prng(void *dst, size_t sz) { + for (; sz; sz--) { + *(uint8_t *)dst++ = rand() & 0xFF; + } +} diff --git a/src/prng.h b/src/prng.h new file mode 100644 index 0000000..b1ad9b4 --- /dev/null +++ b/src/prng.h @@ -0,0 +1,31 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_PRNG_H +#define _URTC_PRNG_H + +void prng_init(); + +void prng(void *dst, size_t sz); + +#endif // _URTC_PRNG_H diff --git a/src/sdp.c b/src/sdp.c new file mode 100644 index 0000000..baa1ece --- /dev/null +++ b/src/sdp.c @@ -0,0 +1,699 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "err.h" +#include "log.h" +#include "sdp.h" + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + +/** + * Parse group attribute + * + * Format is: + * + * a=group:BUNDLE ... + * + * Only the BUNDLE semantics value is supported. + * + * \param sdp[out] SDP structure updated with bundle id tags. + * \param value[in] NULL-terminated string containing SDP attribute line value + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_attr_group(struct sdp *sdp, char *val) { + char *id = val; + + val = strsep(&id, " "); + + if (0 == strcmp("BUNDLE", val)) { + for (int i = 0; id && i < SDP_MAX_BUNDLE_IDS; i++) { + strncpy(sdp->mid[i], strsep(&id, " "), SDP_MAX_BUNDLE_ID_SIZE); + } + } + return 0; +} + +static int sdp_parse_attr_msid_semantic(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +static int sdp_parse_attr_rtcp(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +static int sdp_parse_attr_ice_ufrag(struct sdp *sdp, const char *val) { + if (sdp->ufrag != strncpy(sdp->ufrag, val, sizeof(sdp->ufrag))) { + return -1; + } + return 0; +} + +static int sdp_parse_attr_ice_password(struct sdp *sdp, const char *val) { + if (sdp->pwd != strncpy(sdp->pwd, val, sizeof(sdp->pwd))) { + return -1; + } + return 0; +} + +static int sdp_parse_attr_ice_options(struct sdp *sdp, const char *val) { + if (0 == strcmp("trickle", val)) { + sdp->ice_options.trickle = true; + } + return 0; +} + +/** + * Parse fingerprint + * + * Only SHA-256 fingerprint is supported. + * + * \param[out] sdp SDP structure updated with parsed content. + * \param[in] val NULL-terminated string. + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_attr_fingerprint(struct sdp *sdp, char *val) { + char *fingerprint = val; + + val = strsep(&fingerprint, " "); + + if (fingerprint) { + if (0 == strcmp("sha-256", val)) { + if (32 != sscanf(fingerprint, + "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:" + "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:" + "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:" + "%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX:%2hhX", + &sdp->fingerprint.sha256[0], &sdp->fingerprint.sha256[1], + &sdp->fingerprint.sha256[2], &sdp->fingerprint.sha256[3], + &sdp->fingerprint.sha256[4], &sdp->fingerprint.sha256[5], + &sdp->fingerprint.sha256[6], &sdp->fingerprint.sha256[7], + &sdp->fingerprint.sha256[8], &sdp->fingerprint.sha256[9], + &sdp->fingerprint.sha256[10], &sdp->fingerprint.sha256[11], + &sdp->fingerprint.sha256[12], &sdp->fingerprint.sha256[13], + &sdp->fingerprint.sha256[14], &sdp->fingerprint.sha256[15], + &sdp->fingerprint.sha256[16], &sdp->fingerprint.sha256[17], + &sdp->fingerprint.sha256[18], &sdp->fingerprint.sha256[19], + &sdp->fingerprint.sha256[20], &sdp->fingerprint.sha256[21], + &sdp->fingerprint.sha256[22], &sdp->fingerprint.sha256[23], + &sdp->fingerprint.sha256[24], &sdp->fingerprint.sha256[25], + &sdp->fingerprint.sha256[26], &sdp->fingerprint.sha256[27], + &sdp->fingerprint.sha256[28], &sdp->fingerprint.sha256[29], + &sdp->fingerprint.sha256[30], &sdp->fingerprint.sha256[31] + )) { + return -URTC_ERR_SDP_MALFORMED; + } + } else { + return -URTC_ERR_SDP_UNSUPPORTED_FINGERPRINT_ALGO; + } + } else { + return -URTC_ERR_SDP_MALFORMED; + } + + return 0; +} + +static int sdp_parse_attr_setup(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +static int sdp_parse_attr_mid(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +static int sdp_parse_attr_extmap(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +/** + * Parse RTP map + * + * Expected format is: + * + * / [/] + * + * \param[out] sdp SDP structure updated with parsed content. + * \param[in] val NULL-terminated string. + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_attr_rtpmap(struct sdp *sdp, char *val) { + if (!val) return -URTC_ERR_SDP_MALFORMED; + + unsigned int pt; // payload type + char en[7]; // encoding name + unsigned int cr; // clock rate + + if (3 != sscanf(val, "%3d %6[a-zA-Z0-9]%*c%5d", &pt, en, &cr)) { + return -URTC_ERR_SDP_MALFORMED_ATTRIBUTE; + } + + if (0 == strcmp("H264", en)) { + for (int i = 0; i < sdp->video.count; i++) { + if (sdp->video.params[i].type == pt) { + sdp->video.params[i].clock = cr; + sdp->video.params[i].codec = SDP_CODEC_H264; + } + } + } + + return 0; +} + +static int sdp_parse_attr_rtcp_feedback(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +static int sdp_parse_attr_fmtp(struct sdp *sdp, const char *val) { + /* not implemented */ + return 0; +} + +/** + * Parse SDP attribute line + * + * \param sdp[out] SDP structure updated with parsed content + * \param attr[in] NULL-terminated SDP attribute string (after "a=") + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_attribute(struct sdp *sdp, char *attr) { + char *value = attr; + + // Find colon separating attribute from value + attr = strsep(&value, ":"); + + if (value) { + if (0 == strcmp("group", attr)) { + return sdp_parse_attr_group(sdp, value); + } else if (0 == strcmp("msid-semantic", attr)) { + return sdp_parse_attr_msid_semantic(sdp, value); + } else if (0 == strcmp("rtcp", attr)) { + return sdp_parse_attr_rtcp(sdp, value); + } else if (0 == strcmp("ice-ufrag", attr)) { + return sdp_parse_attr_ice_ufrag(sdp, value); + } else if (0 == strcmp("ice-pwd", attr)) { + return sdp_parse_attr_ice_password(sdp, value); + } else if (0 == strcmp("ice-options", attr)) { + return sdp_parse_attr_ice_options(sdp, value); + } else if (0 == strcmp("fingerprint", attr)) { + return sdp_parse_attr_fingerprint(sdp, value); + } else if (0 == strcmp("setup", attr)) { + return sdp_parse_attr_setup(sdp, value); + } else if (0 == strcmp("mid", attr)) { + return sdp_parse_attr_mid(sdp, value); + } else if (0 == strcmp("extmap", attr)) { + return sdp_parse_attr_extmap(sdp, value); + } else if (0 == strcmp("rtpmap", attr)) { + return sdp_parse_attr_rtpmap(sdp, value); + } else if (0 == strcmp("rtcp-fb", attr)) { + return sdp_parse_attr_rtcp_feedback(sdp, value); + } else if (0 == strcmp("fmtp", attr)) { + return sdp_parse_attr_fmtp(sdp, value); + } + } else /* flag */ { + if (0 == strcmp("recvonly", attr)) { + sdp->mode = SDP_MODE_RECEIVE_ONLY; + } else if (0 == strcmp("sendonly", attr)) { + sdp->mode = SDP_MODE_SEND_ONLY; + } else if (0 == strcmp("sendrecv", attr)) { + sdp->mode = SDP_MODE_SEND_AND_RECEIVE; + } else if (0 == strcmp("rtcp-mux", attr)) { + sdp->rtcp_mux = true; + } else if (0 == strcmp("rtcp-rsize", attr)) { + sdp->rtcp_rsize = true; + } + } + + return 0; +} + +/** + * Parse media description line + * + * Format is: + * + * m= UDP/TLS/RTP/SAVPF ... + * + * Only the UDP/TLS/RTP/SAVPF transport protocol is supported. + * + * \param sdp[out] SDP structure updated with username, session id and + * session version. + * \param value[in] NULL-terminated string containing SDP line value + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_media_description(struct sdp *sdp, char *value) { + char *next = value; + + value = strsep(&next, " "); + + if (0 == strcmp("audio", value)) { + /* not implemented */ + } else if (0 == strcmp("video", value)) { + if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA; + + /* get port */ + value = strsep(&next, " "); + if (1 != sscanf(value, "%5hd", &sdp->video.port)) { + return -URTC_ERR_SDP_MALFORMED_MEDIA; + } + if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA; + + /* get protocol: only "UDP/TLS/RTP/SAVPF" supported */ + value = strsep(&next, " "); + if (0 != strcmp("UDP/TLS/RTP/SAVPF", value)) { + return -URTC_ERR_SDP_UNSUPPORTED_MEDIA_PROTOCOL; + } + if (!next) return -URTC_ERR_SDP_MALFORMED_MEDIA; + + /* get RTP payload types */ + sdp->video.count = 0; + for ( + value = strsep(&next, " "); + value && sdp->video.count < SDP_MAX_RTP_PAYLOAD_TYPES; + value = strsep(&next, " ") + ) { + unsigned int *v = &sdp->video.params[sdp->video.count].type; + if (1 != sscanf(value, "%3d", v)) { + return -URTC_ERR_SDP_MALFORMED_MEDIA; + } + sdp->video.count++; + } + + } else if (0 == strcmp("text", value)) { + /* ignore */ + } else if (0 == strcmp("message", value)) { + /* ignore */ + } else if (0 == strcmp("application", value)) { + /* ignore */ + } else { + return -URTC_ERR_SDP_UNSUPPORTED_MEDIA_TYPE; + } + + return 0; +} + +static int sdp_parse_connection_data(struct sdp *sdp, const char *value) { + return 0; +} + +/** + * Parse version line + * + * Format is: + * + * v=0 + * + * Only version zero is supported. Anything else is an error. + * + * \param value[in] NULL-terminated string containing SDP line value + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_version(struct sdp *sdp, const char *value) { + if (0 != strcmp("0", value)) { + return -URTC_ERR_SDP_MALFORMED_VERSION; + } + sdp->version = 0; + + return 0; +} + +/** + * Parse origin line + * + * Format is: + * + * o= + * + * + * and are ignored. + * + * \param sdp[out] SDP structure updated with username, session id and + * session version. + * \param value[in] NULL-terminated string containing SDP line value + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_origin(struct sdp *sdp, const char *value) { + if (3 != sscanf(value, + "%" TOSTRING(SDP_MAX_USERNAME_SIZE) "s " + "%" TOSTRING(SDP_MAX_SESSION_ID_SIZE) "[0-9] " + "%" TOSTRING(SDP_MAX_SESSION_VERSION_SIZE) "[0-9] " + "IN IP4 127.0.0.1", + sdp->username, + sdp->session_id, + sdp->session_version + )) { + return -URTC_ERR_SDP_MALFORMED_ORIGIN; + } + + return 0; +} + +static int sdp_parse_session_name(struct sdp *sdp, const char *value) { + return 0; +} + +/** + * Parse timing line + * + * Format is: + * + * t= + * + * \param sdp[out] SDP structure updated with start and stop time + * \param value[in] NULL-terminated string containing SDP line value + * + * \return 0 on success. Negative on error. + */ +static int sdp_parse_timing(struct sdp *sdp, const char *value) { + if (2 != sscanf(value, "%10lli %10lli", + (unsigned long long *)&sdp->start_time, + (unsigned long long *)&sdp->stop_time + )) { + return -URTC_ERR_SDP_MALFORMED_TIMING; + } + + return 0; +} + +/** + * Parse a line of SDP + * + * \param sdp[out] SDP structure to be updated with parsed information + * \param line[in] NULL-terminated string containing one line of SDP + * + * \return 0 on success, negative on error. + */ +static int sdp_parse_line(struct sdp *sdp, char *line) { + if (!line) return -URTC_ERR_BAD_ARGUMENT; + if (0 == strlen(line)) return 0; + if (strlen(line) < 3) return -URTC_ERR_SDP_MALFORMED; + if ('=' != line[1]) return -URTC_ERR_SDP_MALFORMED; + + char type = line[0]; + char *value = line + 2; + + switch (type) { + case 'a': + return sdp_parse_attribute(sdp, value); + case 'c': + return sdp_parse_connection_data(sdp, value); + case 'o': + return sdp_parse_origin(sdp, value); + case 'm': + return sdp_parse_media_description(sdp, value); + case 's': + return sdp_parse_session_name(sdp, value); + case 't': + return sdp_parse_timing(sdp, value); + case 'v': + return sdp_parse_version(sdp, value); + default: + /* ignore unsupported types */ + return 0; + } +} + + +int sdp_parse(struct sdp *dst, const char *str) { + if (!str) return -URTC_ERR_BAD_ARGUMENT; + if (!dst) return -URTC_ERR_BAD_ARGUMENT; + + err_t retval = 0; + + /* make a malleable copy of string for parser */ + char *copy = malloc(strlen(str)); + if (copy) { + strcpy(copy, str); + + for (char *in = copy, *line = strsep(&in, "\r\n"); in; line = strsep(&in, "\r\n")) { + retval = sdp_parse_line(dst, line); + if (retval != 0) { + log(ERROR, "sdp_parse_line returned %i for %s", retval, line); + break; + } + } + + free(copy); + } else { + return -URTC_ERR_INSUFFICIENT_MEMORY; + } + + return retval; +} + +int sdp_serialize(char *dst, size_t len, const struct sdp *src) { + int n; + + // sanity check + if (!dst) return -URTC_ERR_BAD_ARGUMENT; + if (!src) return -URTC_ERR_BAD_ARGUMENT; + + // write version + n = snprintf(dst, len, "v=%u\n", src->version); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write originator and session identifier + n = snprintf(dst, len, + "o=liburtc/0.0.0 %s %s IN IP4 127.0.0.1\n", + src->session_id, + src->session_version + ); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write session name + n = snprintf(dst, len, "s=%s\n", + 0 == strlen(src->session_name) ? " " : src->session_name); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write uri + n = snprintf(dst, len, "u=http://www.liburtc.org\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write time descriptor + n = snprintf(dst, len, "t=%" PRIu64 " %" PRIu64 "\n", + src->start_time, src->stop_time); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write session attribute + n = snprintf(dst, len, "a=group:BUNDLE"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + for (int i = 0; i < SDP_MAX_BUNDLE_IDS; i++) { + if (strlen(src->mid[i]) > 0) { + n = snprintf(dst, len, " %s", src->mid[i]); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + } + n = snprintf(dst, len, "\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write video media description + if (src->video.count > 0) { + n = snprintf( + dst, len, "m=video %hd UDP/TLS/RTP/SAVPF", src->video.port + ); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + for (int i = 0; i < src->video.count; i++) { + if (src->video.params[i].codec == SDP_CODEC_H264) { + n = snprintf(dst, len, " %d", src->video.params[i].type); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n > len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + } + n = snprintf(dst, len, "\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + + // TODO write rtcp attribute + + // write connection information (TODO IPv6) + n = snprintf(dst, len, "c=IN IP4 0.0.0.0\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write media attribute: ice ufrag + n = snprintf(dst, len, "a=ice-ufrag:%s\n", src->ufrag); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write media attribute: ice pwd + n = snprintf(dst, len, "a=ice-pwd:%s\n", src->pwd); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write media attribute: trickle ICE + if (src->ice_options.trickle) { + n = snprintf(dst, len, "a=ice-options:trickle\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + + // write media attribute: fingerprint + n = snprintf(dst, len, + "a=fingerprint:sha-256 " + "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:" + "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:" + "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:" + "%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX\n", + src->fingerprint.sha256[0], src->fingerprint.sha256[1], + src->fingerprint.sha256[2], src->fingerprint.sha256[3], + src->fingerprint.sha256[4], src->fingerprint.sha256[5], + src->fingerprint.sha256[6], src->fingerprint.sha256[7], + src->fingerprint.sha256[8], src->fingerprint.sha256[9], + src->fingerprint.sha256[10], src->fingerprint.sha256[11], + src->fingerprint.sha256[12], src->fingerprint.sha256[13], + src->fingerprint.sha256[14], src->fingerprint.sha256[15], + src->fingerprint.sha256[16], src->fingerprint.sha256[17], + src->fingerprint.sha256[18], src->fingerprint.sha256[19], + src->fingerprint.sha256[20], src->fingerprint.sha256[21], + src->fingerprint.sha256[22], src->fingerprint.sha256[23], + src->fingerprint.sha256[24], src->fingerprint.sha256[25], + src->fingerprint.sha256[26], src->fingerprint.sha256[27], + src->fingerprint.sha256[28], src->fingerprint.sha256[29], + src->fingerprint.sha256[30], src->fingerprint.sha256[31] + ); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // TODO write setup, mid + + // write media attribute: mode + switch (src->mode) { + case SDP_MODE_RECEIVE_ONLY: + n = snprintf(dst, len, "a=recvonly\n"); + break; + case SDP_MODE_SEND_ONLY: + n = snprintf(dst, len, "a=sendonly\n"); + break; + case SDP_MODE_SEND_AND_RECEIVE: + n = snprintf(dst, len, "a=sendrecv\n"); + break; + default: + return -URTC_ERR_SDP_MALFORMED; + break; + } + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + // write media attribute: rtcp mux + if (src->rtcp_mux) { + n = snprintf(dst, len, "a=rtcp-mux\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + + // write media attribute: rtcp rsize + if (src->rtcp_rsize) { + n = snprintf(dst, len, "a=rtcp-rsize\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + n -= n; + } + + // write dynamic profiles + for (int i = 0; i < src->video.count; i++) { + if (src->video.params[i].codec == SDP_CODEC_H264) { + n = snprintf(dst, len, "a=rtpmap:%d H264/%d\n", + src->video.params[i].type, + src->video.params[i].clock + ); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + } + } + + // write media attribute: media id + // TODO fix hardcoded media id + n = snprintf(dst, len, "a=mid:0\n"); + if (n < 0) return -URTC_ERR_SDP_MALFORMED; + if (n >= len) return -URTC_ERR_SDP_MALFORMED; + dst += n; + len -= n; + + return 0; +} diff --git a/src/sdp.h b/src/sdp.h new file mode 100644 index 0000000..b65b5e5 --- /dev/null +++ b/src/sdp.h @@ -0,0 +1,150 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_SDP_H +#define _URTC_SDP_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + + +// Maximum size of SDP string (in bytes) +#define SDP_MAX_SIZE 2048 + +#define SDP_MAX_USERNAME_SIZE 32 +#define SDP_MAX_SESSION_ID_SIZE 32 +#define SDP_MAX_SESSION_VERSION_SIZE 32 +#define SDP_MAX_SESSION_NAME_SIZE 32 + +#define SDP_MAX_BUNDLE_IDS 5 +#define SDP_MAX_BUNDLE_ID_SIZE 32 +#define SDP_MAX_RTP_PAYLOAD_TYPES 32 + + +typedef enum sdp_mode { + SDP_MODE_SEND_AND_RECEIVE = 0, + SDP_MODE_RECEIVE_ONLY, + SDP_MODE_SEND_ONLY +} sdp_mode_t; + +typedef enum sdp_media { + SDP_MEDIA_TYPE_NULL = 0, + SDP_MEDIA_TYPE_AUDIO, + SDP_MEDIA_TYPE_VIDEO, + SDP_MEDIA_TYPE_TEXT, + SDP_MEDIA_TYPE_APPLICATION, + SDP_MEDIA_TYPE_MESSAGE +} sdp_media_type_t; + +// Codecs recognized by SDP parser +typedef enum sdp_codec { + SDP_CODEC_NULL = 0, + SDP_CODEC_H264, + SDP_CODEC_VP9 +} sdp_codec_t; + +// Dynamic payload type +typedef struct sdp_rtpmap { + unsigned int type; // Dynamic RTP payload type (e.g. 96) + enum sdp_codec codec; // H264, VP9, etc. + unsigned int clock; // Typically 90kHz for video + unsigned int flags; +} sdp_rtpmap_t; + +typedef struct sdp { + // protocol version + uint8_t version; + + // originator and session identifier + char username[SDP_MAX_USERNAME_SIZE+1]; + char session_id[SDP_MAX_SESSION_ID_SIZE+1]; + char session_version[SDP_MAX_SESSION_VERSION_SIZE+1]; + + // session name + char session_name[SDP_MAX_SESSION_NAME_SIZE+1]; + + // time description (mandatory) + uint64_t start_time; + uint64_t stop_time; + + // session attributes + + // bundle media identification tags + char mid[SDP_MAX_BUNDLE_IDS][SDP_MAX_BUNDLE_ID_SIZE+1]; + + // ice + char ufrag[4*256]; // ice-ufrag (256 unicode chars max.) + char pwd[4*256]; // ice-pwd (256 unicode chars max.) + struct { + bool trickle; + } ice_options; + + // video media + struct { + uint16_t port; + struct sdp_rtpmap params[SDP_MAX_RTP_PAYLOAD_TYPES]; + int count; + } video; + + sdp_mode_t mode; // send, receive, and send-and-receive + + bool rtcp_mux; // is rtcp multiplexed on same socket + bool rtcp_rsize; + + union { + uint8_t sha256[32]; // certificate fingerprint + } fingerprint; + +} sdp_t; + + +/** + * Parse SDP string into structure + * + * \param dst[out] SDP structure + * \param src[in] NULL-terminated SDP string + * + * \return 0 on success, negative on error. + */ +int sdp_parse(struct sdp *dst, const char *src); + +/** + * Serialize SDP structure into string + * + * \param dst[out] NULL-terminated SDP string + * \param len[in] Capacity of dst + * \param src[in] SDP structure + * + * \return 0 on success, negative on error. + */ +int sdp_serialize(char *dst, size_t len, const struct sdp *src); + +#ifdef __cplusplus +} +#endif + +#endif /* _URTC_SDP_H */ diff --git a/src/urtc.c b/src/urtc.c new file mode 100644 index 0000000..892b632 --- /dev/null +++ b/src/urtc.c @@ -0,0 +1,441 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include // assert +#include // errno +#include // poll +#include // pthread_create +#include // true +#include // fprintf +#include // calloc, free +#include // strerror +#include // close + +#include // inet_ntoa +#include // IPPROTO_UDP +#include // socket +#include + +#include "err.h" +#include "log.h" +#include "mdns.h" // mdns_subscribe, mdns_unsubscribe +#include "prng.h" // prng_init +#include "sdp.h" +#include "urtc.h" +#include "uuid.h" // uuid_create_str + +#define RX_BUF_CAP 2048 // receive buffer capacity + +#define POLL_TIMEOUT_MS 50 // poll() timeout (in milliseconds) + +enum signaling_state { + SIGNALING_STATE_STABLE = 0, + SIGNALING_STATE_HAVE_LOCAL_OFFER, + SIGNALING_STATE_HAVE_REMOTE_OFFER, + SIGNALING_STATE_HAVE_LOCAL_PRANSWER, + SIGNALING_STATE_HAVE_REMOTE_PRANSWER, +}; + +enum rtc_state { + STATE_NEW = 0, + + STATE_ICE_NEW, + STATE_ICE_GATHERING, + STATE_ICE_COMPLETE, + + STATE_CONNECTING, + + STATE_CONNECTED, + + STATE_DISCONNECTED, + + STATE_FAILED, + + STATE_CLOSED, + + NUM_STATES // must be last +}; + +enum rtc_event { + EVENT_SOCKET = 0, + EVENT_TIMER, + EVENT_MDNS, + NUM_EVENTS, // must be last +}; + +struct peerconn { + // socket file descriptor + int sockfd; + + // execution thread + pthread_t thread; + + // poll file descriptors + struct pollfd fds[NUM_EVENTS]; + + // callbacks + urtc_on_ice_candidate *on_ice_candidate; + urtc_force_idr *force_idr; + + // stun servers + const char **stun; + + // state machine + enum signaling_state ss; + + // local and remote descriptions + struct sdp ldesc, rdesc; + + // mDNS related state + struct { + char hostname[UUID_STR_LEN]; // .local hostname + int sockfd; // port 5353 listener + } mdns; +}; + + +typedef int (*event_handler)(struct peerconn *pc); + +const enum rtc_state state_table[NUM_STATES][NUM_EVENTS] = { + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW }, + { STATE_NEW, STATE_NEW } +}; + +/** + * Handle incoming STUN packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int stun_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming DTLS packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int dtls_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming SRTP (or SRTCP) packet + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int rtp_handler( + struct peerconn *pc, + const uint8_t *pkt, + size_t n +) { + return 0; +} + +/** + * Handle incoming packet + * + * Packet may be a DTLS, SRTP, SRTCP, or STUN packet. Other packet types + * are discarded. + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int socket_event_handler(struct peerconn *pc) { + uint8_t buffer[RX_BUF_CAP]; + struct sockaddr_in ra; + socklen_t ralen; + ssize_t n; + + // read packet + ralen = sizeof(ra); + n = recvfrom( + pc->sockfd, + buffer, + sizeof(buffer), + 0, + (struct sockaddr *)(&ra), + &ralen + ); + + // rtp + if ((127 < buffer[0]) && (buffer[0] < 192)) { + log(INFO, "[rtp] %s", inet_ntoa(ra.sin_addr)); + rtp_handler(pc, buffer, n); + } else + // dtls + if ((19 < buffer[0]) && (buffer[0] < 64)) { + log(INFO, "[dtls] %s", inet_ntoa(ra.sin_addr)); + dtls_handler(pc, buffer, n); + } else + // stun + if (buffer[0] < 2) { + log(INFO, "[stun] %s", inet_ntoa(ra.sin_addr)); + stun_handler(pc, buffer, n); + } + + return 0; +} + +/** + * Handle timer event + * + * May need to: + * - resend STUN packet + * - resend DTLS packet + * - expire ICE candidate? + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int timer_event_handler(struct peerconn *pc) { + return 0; +} + +/** + * Handle incoming mDNS query + * + * \param pc Peer connection. + * + * \return 0 on success, negative on error. + */ +static int mdns_handler(struct peerconn *pc) { + uint8_t buffer[RX_BUF_CAP]; + ssize_t n; + + // read packet + if (n = recvfrom( + pc->mdns.sockfd, + buffer, + sizeof(buffer), + 0, + NULL, + NULL + ), -1 == n) { + log(ERROR, "%s", strerror(errno)); + goto _fail_recvfrom; + } + + // if valid query for peer connection's ephemeral hostname... + int type = mdns_validate_query(buffer, n, pc->mdns.hostname); + if (type > 0) { + if (type | TYPE_A) { + log(DEBUG, "[mdns] received A query"); + // ...respond to query with interface addresses + mdns_send_response(pc->mdns.sockfd, pc->mdns.hostname); + } + if (type | TYPE_AAAA) { + log(DEBUG, "[mdns] received AAAA query"); + } + } + + return 0; + +_fail_recvfrom: + return -URTC_ERR; +} + +const event_handler action_table[NUM_STATES][NUM_EVENTS] = { + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL }, + { NULL, NULL, NULL } +}; + +/** + * Runloop finite state machine (mealy) for handling timer and receive events + */ + +// send stun request +// wait for stun reply +// timeout -> resend request + +// called dir caller +// ------ --- ------ +// setRemoteDescription(offer) <-- offer = createOffer() +// setLocalDescription(offer) +// answer = createAnswer() --> setRemoteDescription(answer) +// setLocalDescription(answer) +// +// cand = onIceCandidate --> addIceCandidate(cand) +// addIceCandidate(cand) <-- cand = onIceCandidate +// ... +// +// (connection established) +// +// (dtls handshake) +// +// srtp --> +// <-- rtcp, nack + +/** + * Execution thread per peer connection + * + * \param arg Peer connection + * + * \return Unused. + */ +static void * runloop(void *arg) { + struct peerconn *pc; + enum rtc_event event; + int n; + + pc = (struct peerconn *)arg; + assert(pc); + +_loop: + // block indefinitely (-1 timeout) until i/o event + n = poll(pc->fds, NUM_EVENTS, -1); + assert(n > 0); + + // which event(s) occurred? + if (pc->fds[EVENT_SOCKET].revents & POLLIN) { + event = EVENT_SOCKET; + } else + if (pc->fds[EVENT_MDNS].revents & POLLIN) { + mdns_handler(pc); + } else + if (pc->fds[EVENT_TIMER].revents & POLLIN) { + event = EVENT_TIMER; + } + + goto _loop; + + + return NULL; +}; + +urtc_peerconn_t * urtc_peerconn_create(const char *stun[]) { + // seend pseudorandom number generator + prng_init(); + + // allocate peer connection + struct peerconn *pc = (struct peerconn *)calloc(1, sizeof(struct peerconn)); + if (!pc) return pc; + + // copy pointer to stun servers + pc->stun = stun; + + // open udp socket (for all dtls/srtp/srtcp/stun/turn communication) + pc->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (-1 == pc->sockfd) goto _fail_socket; + pc->fds[EVENT_SOCKET] = (struct pollfd){ pc->sockfd, POLLIN }; + + // generate a unique local mDNS hostname + uuid_create_str(pc->mdns.hostname); + log(INFO, "mDNS hostname is %s.local", pc->mdns.hostname); + + // open multicast udp socket for replying to mDNS queries + pc->mdns.sockfd = mdns_subscribe(); + if (-1 == pc->mdns.sockfd) goto _fail_mdns_subscribe; + pc->fds[EVENT_MDNS] = (struct pollfd){ + pc->mdns.sockfd, + POLLIN + }; + + // start new thread per peer connection + if (0 != pthread_create( + &(pc->thread), + NULL, + runloop, + pc + )) goto _fail_pthread_create; + + return pc; + +_fail_pthread_create: + mdns_unsubscribe(pc->mdns.sockfd); +_fail_mdns_subscribe: + close(pc->sockfd); +_fail_socket: + free(pc); + + return NULL; +} + +int urtc_set_on_ice_candidate(struct peerconn *pc, urtc_on_ice_candidate *cb) { + if (!pc) return -1; + if (!cb) return -1; + pc->on_ice_candidate = cb; + + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_add_ice_candidate(struct peerconn *pc, const char *cand) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_create_answer(struct peerconn *pc, char **answer) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_create_offer(struct peerconn *pc, char **offer) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_set_remote_description(struct peerconn *pc, const char *desc) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +int urtc_set_local_description(struct peerconn *pc, const char *desc) { + return -URTC_ERR_NOT_IMPLEMENTED; +} + +void urtc_peerconn_destroy(struct peerconn *pc) { + if (pc) { + pthread_cancel(pc->thread); + pthread_join(pc->thread, NULL); + mdns_unsubscribe(pc->mdns.sockfd); + shutdown(pc->sockfd, SHUT_RDWR); + close(pc->sockfd); + free(pc); + } +} diff --git a/src/urtc.h b/src/urtc.h new file mode 100644 index 0000000..3da0947 --- /dev/null +++ b/src/urtc.h @@ -0,0 +1,223 @@ +/** + * \file urtc.h + * + * \brief liburtc header file. + * + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** + * \mainpage notitle + * + * The reference WebRTC implementation, libwebrtc, was developed by Google + * for the Chrome web browser. It's codebase is large (over 10GB of source), + * uses uncommon build tools (Bazel), and produces large executables not + * well suited for constrained embedded devices. + * + * liburtc is designed with embedded devices in mind. Only functionality + * commonly found on embedded devices is implemented. For example, only + * the H.264 video codec is supported (there is no VP8/VP9 support) as + * most embedded devices with hardware-accelerated video encoders only + * support H.264. + * + * \section features_sec Features + * * Small footprint (< 100KB binary) + * * H.264 support only (no VP8/9 support) + * * ANSI C99 + * + */ +#ifndef _URTC_H +#define _URTC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Opque peer connection structure. + * + * Instantiate via urtc_peerconn_create(). Each peer connection creates its + * own single thread for processing events in a runloop. + */ +typedef struct peerconn urtc_peerconn_t; + +/** + * (callback) Called for each new local ICE candidate discovered + * + * The local candidate should be relayed to the remote peer. Note that callback + * executes on peer connection event loop -- do not block or do other expensive + * work within the callback. + * + * Akin to the `onicecandidate` event handler in `RTCPeerConnection`. + * + * \param cand Discovered local candidate string. + * \param arg User specified argument, see urtc_set_on_ice_candidate(). + * + * \return 0 on success, negative on error. + */ +typedef int (urtc_on_ice_candidate)(const char *cand, void *arg); + +/** + * (callback) Called to request that video encoder immediately generate an IDR + * + * H.264 video encoders produce I-frames and P-frames. Only I-frames can be + * decoded independently. To minimize the time-to-first-frame (TTFF), an + * I-frame should be sent as soon as a peer connection is established. In the + * event of picture loss, an I-frame should be sent as soon as possible to + * re-establish picture. + * + * This is an optional callback. If set, it improves the TTFF and picture + * recovery time from loss of picture. + */ +typedef void (urtc_force_idr)(); + + +/** + * Create a new peer connection + * + * Note that the new peer connection is not yet connected to a peer. + * + * Akin to `RTCPeerConnection()` in the WebRTC JS API. + * + * \param stun Array of STUN servers to use for discovery of server-reflexive + * ICE candidates. Each entry should be a string of the form + * `hostname[:port]` (e.g. "stun.l.google.com:19302"). The port + * is optional -- if omitted, 3478 is assumed. The final entry in + * the array must be NULL. If no STUN servers are provided, only + * local area network peers will be discoverable. + * + * \return On success, a pointer to new peer connection object is returned. + * The peer connection must be destroyed with urtc_peerconn_destroy() + * when no longer needed. On error, NULL is returned. + */ +urtc_peerconn_t * urtc_peerconn_create(const char *stun[]); + +/** + * Sets onIceCandidate callback function + * + * Callback function will be called when a new local ICE candidate is + * discovered. + */ +int urtc_set_on_ice_candidate(urtc_peerconn_t *pc, urtc_on_ice_candidate *cb); + +/** + * Adds received remote ICE candidate to peer connection + * + * For each ICE candidate received from the remote peer via some external + * signaling channel, use this function to add the candidate to the peer + * connection. Candidates are asynchronously used to check for direct + * peer-to-peer connectivity with the remote peer. + * + * Akin to `addIceCandidate` method of `RTCPeerConnection` in the WebRTC JS API. + * + * \param pc Peer connection. + * \param cand An ICE candidate string from remote peer. + * + * \return 0 on success, negative on error. + */ +int urtc_add_ice_candidate(urtc_peerconn_t *pc, const char *cand); + +/** + * Creates local description for an answering peer connection + * + * When a peer connection is the answering party, generates a local + * description (in Session Description Protocol). This description + * should be both sent to remote peer connection via some external + * signaling channel as well as added to the local peer connection via + * urtc_set_local_description(). + * + * Akin to `createAnswer` method of `RTCPeerConnection` in WebRTC JS API. + * + * \param pc Peer connection + * \param answer Pointer to string pointer pointing to generated answer. + * Memory internally managed (caller need not and must not free). + * + * \return 0 on success, negative on error. + */ +int urtc_create_answer(urtc_peerconn_t *pc, char **answer); + +/** + * Creates a local description for an offering peer connection + * + * When a peer connection is the offering party, this function generates a + * local description (in Session Description Protocol). This description + * should be both sent to the remote peer connection via some external + * signaling channel as well as added to the local peer connection via + * urtc_set_local_description(). + * + * Akin to `createOffer` method of `RTCPeerConnection` in WebRTC JS API. + * + * \param pc Peer connection + * \param offer Pointer to string pointer pointing to generated offer. + * Memory interally managed (caller need no and must not free). + * + * \return 0 on success, negative on error. + */ +int urtc_create_offer(urtc_peerconn_t *pc, char **offer); + +/** + * Sets local description for peer connection + * + * The local description is a Session Description Protocol string, typically + * generated by urtc_create_offer() or urtc_create_answer(), depending on + * whether the local peer connection is the offering or answering party, + * respectively. Setting the local description begins local ICE candidate + * discovery. + * + * Akin to `setLocalDescription` method of `RTCPeerConnection` in WebRTC JS API. + * + * \param pc Peer connection previously created via urtc_peerconn_create(). + * \param desc Local description in Session Description Protocol (SDP) + * + * \return 0 on success, negative on error. + */ +int urtc_set_local_description(urtc_peerconn_t *pc, const char *desc); + +/** + * Sets remote description + * + * The remote description is a Session Description Protocol string, + * received from the remote peer via some external signaling channel. + * Amongst other things, it defines the set of media codecs the remote + * peer supports. + * + * Akin to `setRemoteDescription` method of `RTCPeerConnection` in WebRTC JS + * API. + * + * \param pc Peer connection created by urtc_peerconn_create(). + * \param desc Session description protocol. + * + * \return 0 on success, negative on error. + */ +int urtc_set_remote_description(urtc_peerconn_t *pc, const char *desc); + +/** + * Tears down peer connection, closing and freeing all resources + * + * \param pc Peer connection. + */ +void urtc_peerconn_destroy(urtc_peerconn_t *pc); + +#ifdef __cplusplus +} +#endif + +#endif /* _URTC_H */ diff --git a/src/uuid.c b/src/uuid.c new file mode 100644 index 0000000..6dfa4c1 --- /dev/null +++ b/src/uuid.c @@ -0,0 +1,76 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** + * Pseudo-random univerally-unique identifier generator [^RFC4122]. + * + * [^RFC4122]: https://tools.ietf.org/html/rfc4122#section-4.4 + */ + +#include +#include + +#include "prng.h" +#include "uuid.h" + +/** + * Generate a pseudo-random UUID. + * + * \return Populated UUID. + */ +struct uuid uuid_create() { + struct uuid u; + + // fill with randomness + prng(&u, sizeof(u)); + + // set reserved and version bits + u.clk_seq_hi_and_reserved = (u.clk_seq_hi_and_reserved & 0x3F) | 0x80; + u.time_hi_and_version = (u.time_hi_and_version & 0x0FFF) | 0x4000; + + return u; +} + +/** + * Generate pseudorandom UUID (as a string) + * + * \param s Destination string memory. + * + */ +void uuid_create_str(char s[UUID_STR_LEN]) { + struct uuid u = uuid_create(); + + sprintf(s, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + u.time_low, + u.time_mid, + u.time_hi_and_version, + u.clk_seq_hi_and_reserved, + u.clk_seq_low, + u.node[0], + u.node[1], + u.node[2], + u.node[3], + u.node[4], + u.node[5] + ); +} diff --git a/src/uuid.h b/src/uuid.h new file mode 100644 index 0000000..464f33e --- /dev/null +++ b/src/uuid.h @@ -0,0 +1,54 @@ +/** + * liburtc + * Copyright 2020 Chris Hiszpanski + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef _URTC_UUID_H +#define _URTC_UUID_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define UUID_SIZE sizeof(struct uuid) + +#define UUID_STR_LEN (32 + 4 + 1) // 16 hex octets, 4 dashes, 1 null terminator + +struct __attribute__((__packed__)) uuid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clk_seq_hi_and_reserved; + uint8_t clk_seq_low; + uint8_t node[6]; +}; + +struct uuid uuid_create(); + +void uuid_create_str(char uuid[UUID_STR_LEN]); + +#ifdef __cplusplus +} +#endif + +#endif // _URTC_UUID_H -- cgit v1.2.3