summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChris Hiszpanski <chris@hiszpanski.name>2019-04-18 00:55:30 -0700
committerChris Hiszpanski <chris@hiszpanski.name>2021-04-04 01:23:16 -0700
commit6d88c555019f32509f303e23dcfbba824fecd2ee (patch)
tree9d976063b4cb63e2e8ad4529c6d597b4470464c7 /src
Initial public commit.
mDNS and SDP are functional. Otherwise, library is still very much a work in progress. All tests pass.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am9
-rw-r--r--src/err.h58
-rw-r--r--src/g711.c40
-rw-r--r--src/g711.h68
-rw-r--r--src/g711_tables.c745
-rw-r--r--src/log.h83
-rw-r--r--src/mdns.c607
-rw-r--r--src/mdns.h43
-rw-r--r--src/prng.c47
-rw-r--r--src/prng.h31
-rw-r--r--src/sdp.c699
-rw-r--r--src/sdp.h150
-rw-r--r--src/urtc.c441
-rw-r--r--src/urtc.h223
-rw-r--r--src/uuid.c76
-rw-r--r--src/uuid.h54
16 files changed, 3374 insertions, 0 deletions
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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+
+#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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef URTC_G711_H
+#define URTC_G711_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+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 <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#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 <stdio.h>
+
+#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 <errno.h>
+#include <net/if.h> // IFF_LOOPBACK (include pre ifaddrs.h)
+#include <ifaddrs.h> // getifaddrs
+#include <limits.h> // HOST_NAME_MAX
+#include <stdbool.h>
+#include <string.h> // memcpy
+#include <unistd.h> // sleep
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef WITH_IPV6
+ #include <netinet6/in6.h>
+#endif
+#include <sys/socket.h> // 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 <stddef.h>
+#include <stdint.h>
+
+#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 <stdint.h>
+#include <stdlib.h>
+#include <time.h>
+
+#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 <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <mid> <mid> ...
+ *
+ * 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:
+ *
+ * <payload type> <encoding name>/<clock rate> [/<encoding parameters>]
+ *
+ * \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=<audio|video> <port> UDP/TLS/RTP/SAVPF <n> ...
+ *
+ * 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=<username> <sess-id> <sess-version> <nettype> <addrtype>
+ * <unicast-address>
+ *
+ * <nettype> <addrtype> and <unicast-address> 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=<start-time> <stop-time>
+ *
+ * \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 <stdbool.h>
+#include <stdint.h>
+
+
+// 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.h> // assert
+#include <errno.h> // errno
+#include <poll.h> // poll
+#include <pthread.h> // pthread_create
+#include <stdbool.h> // true
+#include <stdio.h> // fprintf
+#include <stdlib.h> // calloc, free
+#include <string.h> // strerror
+#include <unistd.h> // close
+
+#include <arpa/inet.h> // inet_ntoa
+#include <netinet/in.h> // IPPROTO_UDP
+#include <sys/socket.h> // socket
+#include <sys/types.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+
+#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 <stdint.h>
+
+#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