diff --git a/.github/workflows/build-observer-firmwares-beta.yml b/.github/workflows/build-observer-firmwares-beta.yml index 1646db11..3c5e7d19 100644 --- a/.github/workflows/build-observer-firmwares-beta.yml +++ b/.github/workflows/build-observer-firmwares-beta.yml @@ -38,11 +38,17 @@ on: - 'LICENSE' - '.vscode/**' - '.claude/**' + # Throwaway build worktrees never affect the binaries. + - '.build-wt-*' + - '.build-wt-*/**' -# Shared with build-observer-firmwares.yml and sync-flasher-content.yml so the -# workflows never push to the flasher repo at the same time. +# Own group: serialize beta builds against each other only. Concurrency groups are +# repo-wide, not per-branch, so sharing one with the production workflows let an +# in-flight beta build force a production build into the pending state, where the +# next run queued into the group cancelled it outright. Concurrent flasher pushes +# are instead made safe by the rebase-retry in "Commit & Push Beta Artifacts". concurrency: - group: flasher-publish + group: observer-firmware-beta-build cancel-in-progress: false env: @@ -50,7 +56,7 @@ env: # OTA comparison treats a different base version as "always an update", so a # distinct base here would make every beta node think it is permanently behind. # Channels are separated by manifest URL, not by base version. - FIRMWARE_VERSION: v1.16.0 + FIRMWARE_VERSION: v1.17.0 # Beta-only rolling release. A separate tag is required, not cosmetic: the # publish step prunes all but the KEEP_BUILDS most recent build hashes within @@ -346,4 +352,26 @@ jobs: git config user.name "meshcore-bot" git config user.email "noreply@gessaman.com" git commit -m "Update BETA observer firmware to ${{ steps.sha.outputs.short }} (build ${FIRMWARE_VERSION}.${{ needs.enumerate.outputs.build_number }})" - git push + # The production workflows can be pushing to the flasher repo right now + # (no shared concurrency group any more), so a non-fast-forward is expected + # rather than fatal: rebase onto their commit and retry. The scoped add + # above keeps this commit inside the beta paths, so a conflict with + # production's files is not possible. + br=$(git rev-parse --abbrev-ref HEAD) + for attempt in 1 2 3; do + if git push origin "HEAD:$br"; then + exit 0 + fi + echo "push rejected (attempt $attempt); rebasing onto origin/$br" + git fetch origin "$br" + # --autostash: the scoped add above leaves unrelated edits in the + # checkout unstaged on purpose, and plain rebase refuses to run with a + # dirty tree. They stay unstaged, so they still never get published. + git rebase --autostash "origin/$br" || { + git rebase --abort || true + echo "::error::flasher rebase conflicted; re-run this workflow to republish" + exit 1 + } + done + echo "::error::could not push beta changes after 3 attempts" + exit 1 diff --git a/.github/workflows/build-observer-firmwares.yml b/.github/workflows/build-observer-firmwares.yml index 1671010c..03b2d38c 100644 --- a/.github/workflows/build-observer-firmwares.yml +++ b/.github/workflows/build-observer-firmwares.yml @@ -7,7 +7,7 @@ on: workflow_dispatch: push: branches: - - mqtt-bridge-implementation-flex + - observer-firmware # Only rebuild firmware when something that affects the binaries changes. # Docs, the changelog, the changelog generator, CI files, and repo-meta # files do not change firmware output - docs/changelog are handled by @@ -25,19 +25,29 @@ on: - 'LICENSE' - '.vscode/**' - '.claude/**' + # Throwaway build worktrees never affect the binaries. + - '.build-wt-*' + - '.build-wt-*/**' -# Serialize with sync-flasher-content.yml so the two workflows never push to the -# flasher repo at the same time (shared group name across both workflows). +# Own group: serialize builds against each other only. Do NOT share this group +# with sync-flasher-content.yml -- a mixed code+docs commit triggers both, and a +# run that is still pending when its sibling is queued gets cancelled, which +# silently skipped the firmware build. Concurrent flasher pushes are instead made +# safe by the rebase-retry in "Commit & Push Flasher Config". concurrency: - group: flasher-publish + group: observer-firmware-build cancel-in-progress: false env: # Version embedded in firmware filenames; must match the version key in the # flasher's config.json. Bump here when the observer version changes. - FIRMWARE_VERSION: v1.16.0 + FIRMWARE_VERSION: v1.17.0 # Rolling release tag that hosts the latest observer binaries. RELEASE_TAG: observer-mqtt-latest + # Download host serving RELEASE_TAG's assets (cloudflare-worker in the flasher + # repo). Baked into the slim OTA manifests' file URLs; must stay consistent + # with config.json's staticPath. + STATIC_PATH: https://observer-fw.gessaman.com jobs: @@ -185,6 +195,13 @@ jobs: # not published as release assets (and they'd break the hash-based prune). gh release upload "$RELEASE_TAG" $(find out -maxdepth 1 -type f ! -name '*.partsig') --clobber + # Keep the release body in sync with the repo's notes source: the + # /releases feed serves it as the flasher dropdown's changelog + # (config.json no longer carries per-version notes). Non-fatal -- + # stale notes beat a red build whose binaries are already live. + gh release edit "$RELEASE_TAG" --notes-file firmware-notes.html \ + || echo "WARNING: release notes sync failed" >&2 + # Prune old assets, but RETAIN the most recent KEEP_BUILDS build hashes # (not just the current one). The flasher SPA reads config.json once at # page load and the embedded git short-hash is what links it to assets; @@ -222,22 +239,23 @@ jobs: token: ${{ secrets.FLASHER_DISPATCH_TOKEN }} path: flasher - - name: Update Flasher Config - run: | - python3 flasher/scripts/update-firmware.py \ - "${{ steps.sha.outputs.short }}" \ - "$GITHUB_WORKSPACE/firmware-notes.html" + # update-firmware.py is no longer called here: config.json's observer + # entries are github release defs (no embedded filenames to rewrite), the + # flasher's versions come from the Worker's /releases feed, and the + # changelog notes ride the release body (see the publish step). - name: Generate Slim Per-Variant Manifests + Persist Build Counter env: BUILD_NUMBER: ${{ needs.enumerate.outputs.build_number }} run: | # Derive the slim per-variant manifests (flasher/v/.json) that the - # firmware fetches for `ota check`/`ota update`, from the just-updated - # config.json (single source of truth), stamping this build's number. - # Then persist the counter so the next run increments from here. + # firmware fetches for `ota check`/`ota update`, from the build output + # in out/ -- the assets actually uploaded to the release -- stamping this + # build's number. Then persist the counter so the next run increments + # from here. python3 flasher/scripts/gen-slim-manifests.py \ - --config flasher/config.json \ + --bin-dir out \ + --static-path "$STATIC_PATH" \ --out-dir flasher/v \ --base-version "$FIRMWARE_VERSION" \ --build "$BUILD_NUMBER" \ @@ -294,4 +312,24 @@ jobs: git config user.name "meshcore-bot" git config user.email "noreply@gessaman.com" git commit -m "Update observer firmware to ${{ steps.sha.outputs.short }} (build ${FIRMWARE_VERSION}.${{ needs.enumerate.outputs.build_number }})" - git push + # sync-flasher-content.yml can be pushing the same docs/changelog right + # now (a mixed code+docs commit triggers both), so a non-fast-forward is + # expected rather than fatal: rebase onto the sibling's commit and retry. + # If the rebase leaves nothing, git reports up-to-date and we exit clean. + br=$(git rev-parse --abbrev-ref HEAD) + for attempt in 1 2 3; do + if git push origin "HEAD:$br"; then + exit 0 + fi + echo "push rejected (attempt $attempt); rebasing onto origin/$br" + git fetch origin "$br" + # --autostash so a dirty tree can never make the rebase refuse to run; + # nothing unstaged gets published either way. + git rebase --autostash "origin/$br" || { + git rebase --abort || true + echo "::error::flasher rebase conflicted; re-run this workflow to republish" + exit 1 + } + done + echo "::error::could not push flasher changes after 3 attempts" + exit 1 diff --git a/.github/workflows/sync-flasher-content.yml b/.github/workflows/sync-flasher-content.yml index e2bd5033..7ae08947 100644 --- a/.github/workflows/sync-flasher-content.yml +++ b/.github/workflows/sync-flasher-content.yml @@ -14,16 +14,21 @@ on: workflow_dispatch: push: branches: - - mqtt-bridge-implementation-flex + - observer-firmware paths: - '**.md' - 'docs/**' - 'scripts/gen_changelog.py' + # /webconfig frames the real portal page against a browser simulator, so + # the page itself is a synced doc asset like the .md files above. + - 'webui/index.html' -# Shared with build-observer-firmwares.yml so the two never push to the flasher -# repo concurrently (a mixed code+docs commit can trigger both). +# Own group: serialize doc syncs against each other only. Deliberately NOT shared +# with build-observer-firmwares.yml -- a shared group let one workflow cancel the +# other's pending run (a mixed code+docs commit triggers both). Concurrent flasher +# pushes are handled by the rebase-retry in "Commit & Push Flasher Content". concurrency: - group: flasher-publish + group: flasher-docs-sync cancel-in-progress: false jobs: @@ -57,6 +62,21 @@ jobs: fi done + - name: Sync the WebConfig Portal Page into Flasher + run: | + # flasher/webconfig.html embeds the REAL portal page in a device + # surround and runs it against lib/webconfig-sim.js, which intercepts + # fetch() so no device is needed. Syncing the page is what keeps that + # demo honest -- the alternative is screenshots, which go stale without + # anyone noticing. The build script only injects a " in low): + mode = "html" + + return "\n".join(out) + "\n" + + +def check_stripped(raw, stripped): + """Guard against a stripper bug silently shipping a broken portal to the + fleet. Returns a reason string when the output looks wrong, else None.""" + for tag in ("", "", ""): + if raw.count(tag) != stripped.count(tag): + return "%s count changed" % tag + if len(stripped) < len(raw) * 0.5: + return "output shrank by more than half (%d -> %d)" % (len(raw), len(stripped)) + # Only comments and whitespace may go, so no structural token may appear + # that the source did not already have. + for token in ("{", "}", "(", ")", " raw.count(token): + return "gained a %s" % token + return None diff --git a/scripts/webconfig_mock_server.py b/scripts/webconfig_mock_server.py index c654cd7a..4d9344b9 100644 --- a/scripts/webconfig_mock_server.py +++ b/scripts/webconfig_mock_server.py @@ -9,6 +9,11 @@ pending -> done result polling, aggregate-success reboot gating, secret masking So the browser drives the actual portal JS (wizard, save/poll/reqid, effective value handling, reboot overlay, stats, scan) against realistic responses. +/api/cli is the CLI terminal's backend and has no firmware counterpart yet: it +is the proposed contract (202 + reqid, streamed per-command results) executed +against a CommonCLI-shaped interpreter, so the terminal UI can be designed +against realistic single- and multi-line replies before any of it goes on-device. + It does NOT run the C++ handlers (that's what test/ gtest covers) or the AsyncTCP transport -- it's a frontend + contract harness. @@ -28,6 +33,7 @@ import json import os import re import secrets +import sys import threading import time from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer @@ -36,6 +42,18 @@ from urllib.parse import parse_qs, urlsplit HERE = os.path.dirname(os.path.abspath(__file__)) INDEX_HTML = os.path.join(HERE, "..", "webui", "index.html") +sys.path.insert(0, HERE) +# The build-time comment stripper, shared so --minify serves byte-for-byte what +# the generator embeds rather than a second implementation that could drift. +from webconfig_minify import strip_source # noqa: E402 + +MINIFY = False +# Overridable with --fw-version to exercise the console's channel labelling: +# v1.16.0.5-observer-a1b2c3d release +# v1.16.0.5-observer-beta-dev-a1b2c3d dev +# v1.16.0 local build, no OTA +FW_VERSION = "v1.16.0.5-observer-a1b2c3d" + SENTINEL = "********" ADMIN_PASSWORD = "password" # matches the default ADMIN_PASSWORD build flag BATCH_PENDING_SECS = 0.8 # how long POST->done takes, to exercise polling @@ -47,9 +65,17 @@ LEN_LIMITS = { "name": 31, "wifi.ssid": 31, "wifi.pwd": 63, "mqtt.origin": 31, "mqtt.email": 63, "mqtt.ntp": 63, "timezone": 31, "snmp.community": 23, } +# "filter" is absent on purpose: it is a bitmask, not a text buffer, so it has +# no destination-buffer limit. It is still bounded by the shared CLI command +# budget below, like every other key. SLOT_LEN_LIMITS = {"server": 63, "username": 31, "password": 63, "token": 47, "topic": 95, "audience": 63} +# BatchEntry::cmd[160] in WebConfigServer.cpp holds "set " plus a +# NUL. Over-long values are rejected there rather than truncated, because a +# clipped value can still be valid and would persist as a different setting. +BATCH_CMD_SIZE = 160 + # Preset names + what the UI must collect (mirrors handlePresets()). PRESETS = ( [(n, "none") for n in ( @@ -92,14 +118,30 @@ def default_config(setup_mode): "interval": 5, "timezone": "MST7MDT,M3.2.0,M11.1.0", "timezone_offset": -7, "ntp": "pool.ntp.org", "owner": "", "email": "", "snmp": False, "snmp_community": "public", + "neighbors": False, "neighbors_interval": 24, "slots": [_slot() for _ in range(6)], }, + # Settings the CLI reaches but no portal form does, so they are absent + # from /api/config (see config_json) and live only here. Without them + # the terminal answers "unknown config key" for perfectly real commands. + "cli": { + "radio.watchdog": 0, "int.thresh": 0, "agc.reset.interval": 0, + "direct.txdelay": 0.0, "multi.acks": 0, "allow.read.only": False, + "path.hash.mode": 0, "owner.info": "", "guest.password": "", + "adc.multiplier": 1.0, + "alert": False, "alert.psk": "", "alert.hashtag": "", + "alert.region": "", "alert.interval": 15, + "alert.mqtt": False, "alert.wifi": False, + "bridge.enabled": False, "bridge.source": "rx", "bridge.baud": 115200, + "bridge.delay": 0, "bridge.channel": 0, "bridge.secret": "", + }, } def _slot(): return {"preset": "none", "server": "", "port": 8883, "username": "", - "password": "", "token": "", "topic": "", "audience": ""} + "password": "", "token": "", "topic": "", "audience": "", + "filter": "all"} class State: @@ -113,6 +155,8 @@ class State: self.start = time.time() self.session = None # cookie token when logged in (LAN mode) self.batch = {"state": "idle"} + self.cli = {"state": "idle"} # deferred CLI sequence, see /api/cli + self.admin_pwd_set = False # satisfies the initial-setup invariant self.scan_started = None # ---- auth ------------------------------------------------------------- @@ -128,6 +172,7 @@ class State: # ---- config serialization (masks secrets, like handleConfigGet) ------- def config_json(self): c = copy.deepcopy(self.cfg) + c.pop("cli") # CLI-only settings: not part of this contract c["wifi"]["pwd"] = SENTINEL if self.cfg["wifi"]["pwd"] else "" for s in c["mqtt"]["slots"]: s["password"] = SENTINEL if s["password"] else "" @@ -140,9 +185,14 @@ class State: "auth": authed, "needs_setup": self.cfg["wifi"]["ssid"] == "", "name": self.cfg["radio"]["name"], "node_id": "a1b2c3d4e5f60718", - "fw": "v1.7.1-mock", "role": "Repeater", "board": "Heltec V3 (mock)", + # Shaped like build.sh's EMBEDDED_VERSION_STRING + # (base[.build][-observer][-channel]-hash) so the console's channel + # labelling is exercised against a real version, not "v1.x-mock". + "fw": FW_VERSION, "build_date": "6 Jun 2026", + "role": "Repeater", "board": "Heltec V3 (mock)", "uptime_s": int(time.time() - self.start), "runtime_slots": 6, "max_slots": 6, "active_slots": self.active_slots, + "max_cmds": CLI_MAX_CMDS, } @@ -153,13 +203,15 @@ class State: BOOL_KEYS = {"cad": ("radio", "cad"), "radio.rxgain": ("radio", "rxgain"), "repeat": ("radio", "repeat"), "mqtt.status": ("mqtt", "status"), "mqtt.packets": ("mqtt", "packets"), "mqtt.raw": ("mqtt", "raw"), - "mqtt.rx": ("mqtt", "rx"), "snmp": ("mqtt", "snmp")} + "mqtt.rx": ("mqtt", "rx"), "snmp": ("mqtt", "snmp"), + "mqtt.neighbors": ("mqtt", "neighbors")} INT_KEYS = {"tx": ("radio", "tx"), "flood.max": ("radio", "flood_max"), "flood.max.advert": ("radio", "flood_max_advert"), "flood.max.unscoped": ("radio", "flood_max_unscoped"), "advert.interval": ("radio", "advert_interval"), "flood.advert.interval": ("radio", "flood_advert_interval"), "mqtt.interval": ("mqtt", "interval"), + "mqtt.neighbors.interval": ("mqtt", "neighbors_interval"), "timezone.offset": ("mqtt", "timezone_offset")} FLOAT_KEYS = {"lat": ("radio", "lat"), "lon": ("radio", "lon"), "af": ("radio", "af"), "rxdelay": ("radio", "rxdelay"), @@ -171,6 +223,16 @@ STR_KEYS = {"name": ("radio", "name"), "wifi.ssid": ("wifi", "ssid"), "snmp.community": ("mqtt", "snmp_community"), "mqtt.tx": ("mqtt", "tx")} SECRET_STR_KEYS = {"wifi.pwd": ("wifi", "pwd")} +# The CLI-only settings, typed the same way so apply_set/cli_read_key reach them +# through the existing lookups rather than a parallel code path. +for _k, _v in default_config(False)["cli"].items(): + _t = {bool: BOOL_KEYS, int: INT_KEYS, float: FLOAT_KEYS, str: STR_KEYS}[type(_v)] + _t[_k] = ("cli", _k) +SECRET_STR_KEYS.update({k: ("cli", k) for k in + ("guest.password", "alert.psk", "bridge.secret")}) +for _k in SECRET_STR_KEYS: + STR_KEYS.pop(_k, None) + def _hex64(v): return len(v) == 64 and all(c in "0123456789abcdefABCDEF" for c in v) @@ -191,6 +253,28 @@ def apply_set(cfg, key, val): ADMIN_PASSWORD = val return True, "OK" + if key == "radio.fem.rxgain": + return False, "Error: unsupported" # no FEM on the mock board, see GETTERS + + if key == "dutycycle": + try: + dc = float(val) + except ValueError: + return False, "Error: expected a number" + if not 0 < dc <= 100: + return False, "Error, must be 1-100" + cfg["radio"]["af"] = 100.0 / dc - 1 # the CLI stores it as airtime_factor + return True, "OK" + + if key in ("freq", "bw", "sf", "cr"): + # single-component radio setters, reachable from the CLI but not from + # the form batch (which always sends the whole `radio` combo) + try: + cfg["radio"][key] = int(val) if key in ("sf", "cr") else float(val) + except ValueError: + return False, "Error: expected a number" + return True, "OK - reboot to apply" + if key == "radio": try: f, bw, sf, cr = val.split(",") @@ -211,6 +295,12 @@ def apply_set(cfg, key, val): cfg["mqtt"]["iata"] = val.upper() return True, "OK" + if key == "prv.key": + # write-only by design: the identity goes in, nothing reads it back + if not _hex64(val): + return False, "Error: private key must be 64 hex characters" + return True, "OK - identity restored, reboot to apply" + if key == "mqtt.owner": if val == "": cfg["mqtt"]["owner"] = "" @@ -250,7 +340,57 @@ def apply_set(cfg, key, val): sec, f = STR_KEYS[key] cfg[sec][f] = val return True, "OK" - return True, "OK" # unknown-but-allowlisted: accept (mock is lenient here) + # Strict fallthrough: this function is the single authority on what can be + # set, for the batch and the CLI alike. Accepting unknown keys here once hid + # the fact that the CLI could not reach `dutycycle` or `radio.fem.rxgain`. + # Verbatim shape from CommonCLI::handleSetCmd's fallthrough. + return False, "unknown config: %s" % key + + +# Payload-type names accepted alongside the decimal form. Mirrors +# namedPacketTypes() in src/helpers/MQTTPacketFilter.h; 12-14 are reserved +# upstream and stay reachable by number only. +PACKET_TYPE_NAMES = { + "req": 0, "response": 1, "txt_msg": 2, "ack": 3, "advert": 4, + "grp_txt": 5, "grp_data": 6, "anon_req": 7, "path": 8, "trace": 9, + "multipart": 10, "control": 11, "raw_custom": 15, +} + + +def packet_filter_mask(text): + """Canonical filter text -> bitmask, for the stats payload.""" + if text == "all": + return 0xFFFF + if text == "none": + return 0 + mask = 0 + for token in text.split(","): + mask |= 1 << int(token) + return mask + + +def canonical_packet_filter(val): + """Mirror of MQTTPacketFilter::parse + ::format. Returns None if invalid.""" + stripped = val.strip() + if stripped == "" or stripped == "all": + return "all" + if stripped == "none": + return "none" + mask = 0 + for part in stripped.split(","): + token = part.strip() + if re.fullmatch(r"[0-9]+", token): + packet_type = int(token) + if packet_type > 15: + return None + elif token in PACKET_TYPE_NAMES: + packet_type = PACKET_TYPE_NAMES[token] + else: + return None + mask |= 1 << packet_type + if mask == 0xFFFF: + return "all" + return ",".join(str(i) for i in range(16) if mask & (1 << i)) def apply_slot_set(cfg, idx, field, val): @@ -266,6 +406,13 @@ def apply_slot_set(cfg, idx, field, val): return False, "Error: port must be between 1 and 65535" slot["port"] = p return True, "OK" + if field == "filter": + canonical = canonical_packet_filter(val) + if canonical is None: + return False, ("Error: filter must be all, none, or a CSV of " + "types 0-15 / names (advert,txt_msg,...)") + slot["filter"] = canonical + return True, "OK - slot %d packet types: %s" % (idx + 1, canonical) if field in ("preset", "server", "username", "password", "token", "topic", "audience"): slot[field] = val if field == "token": @@ -275,7 +422,270 @@ def apply_slot_set(cfg, idx, field, val): def is_secret_key(key): - return key == "wifi.pwd" or bool(re.match(r"^mqtt[1-6]\.(password|token)$", key)) + # The serial console prints these back; the portal is reachable over the + # LAN, so it masks them in `get` replies the way /api/config already does. + return key in SECRET_STR_KEYS or bool(re.match(r"^mqtt[1-6]\.(password|token)$", key)) + + +# --------------------------------------------------------------------------- +# CLI command execution (backs /api/cli), mirroring CommonCLI enough to give +# the terminal UI realistic single- and multi-line replies. +# +# The portal's `set` batch is allowlisted (WebConfigKeys.h) because it is driven +# by form fields; the CLI is deliberately NOT, since its whole point is reaching +# the same surface the serial console reaches. Auth is the boundary -- exactly +# as it is for the serial console and for remote admin over the mesh. +# --------------------------------------------------------------------------- +# MAX_BATCH in WebConfigServer.h: the CLI shares the config batch's fixed slot, +# so this is the real cap, reported to the page as status.max_cmds. +CLI_MAX_CMDS = 24 +CLI_RESULT_PAGE = 8 # WebConfigBatch::kCliResultPage +CLI_CMD_SECS = 0.25 # simulated per-command execution time +# Board::reboot() does not return, so the firmware answers `reboot` itself and +# arms the deferred reboot once results have been read (see wcIsDeferredReboot). +CLI_DEFERRED_REBOOT = "reboot" # matched as a PREFIX, like CommonCLI does + +# Commands the CLI reaches but the portal cannot honestly serve; rejected at +# POST. Mirrors wcCliUnavailable() in WebConfigServer.cpp. +CLI_UNAVAILABLE = [ + ("start ota", True, "start ota needs port 80, which this portal is using. " + "Run it from the serial console, or use `ota update`."), + ("clock sync", True, "clock sync takes its time from the caller, which a web request " + "has no way to supply. Use `time ` instead."), + ("log", False, "log writes the packet log to the serial console, not here, and " + "blocks the radio while it does. Use `log start` / `log stop`."), + ("get acl", False, "get acl writes to the serial console, not here."), +] + + +def cli_unavailable(cmd): + for token, is_prefix, why in CLI_UNAVAILABLE: + if cmd.startswith(token) if is_prefix else cmd == token: + return why + return None + + +# Failure replies CommonCLI emits that do NOT start with "Err" -- the shapes that +# made a naive prefix test call them success. Mirrors +# WebConfigBatch::cliReplyIsFailure. +def cli_reads_secret(cmd): + """Commands that READ a secret. CommonCLI gates these on the caller being + the serial console; the portal is not, so the value is masked here the way + CommonCLI masks it for remote callers. Mirrors wcCliReadsSecret().""" + if not cmd.startswith("get "): + return False + key = cmd[4:].strip() + return key in ("prv.key", "guest.password", "alert.psk", "bridge.secret") \ + or is_secret_key(key) + + +def cli_reply_is_failure(reply): + if not reply: + return False + if reply.startswith(("Err", "ERR", "err", "(ERR", "Unknown command", + "unknown config", "??", "Can't find")): + return True + return ": Err" in reply + +# Commands the device answers but that have no config-key equivalent. +GETTERS = { + "freq": lambda c: "%.3f" % c["radio"]["freq"], + "bw": lambda c: "%.2f" % c["radio"]["bw"], + "sf": lambda c: str(c["radio"]["sf"]), + "cr": lambda c: str(c["radio"]["cr"]), + "public.key": lambda c: "a1b2c3d4" * 8, + "wifi.status": lambda c: ( + "SSID: %s\nIP: 192.168.1.42\nRSSI: -58 dBm\nUptime: %dm" + % (c["wifi"]["ssid"] or "(not set)", int(time.time() - ST.start) // 60)), + "mqtt.status": lambda c: cli_mqtt_status(c), + "mqtt.presets": lambda c: "\n".join( + "%2d. %s%s" % (i + 1, n, "" if nd == "none" else " (needs %s)" % nd) + for i, (n, nd) in enumerate(PRESETS)), + "role": lambda c: "Repeater", + "acl": lambda c: "a1b2c3d4e5f60718 perms 3\n1122334455667788 perms 1", + # not its own pref: the CLI derives it from airtime_factor both ways + "dutycycle": lambda c: "%.1f" % (100.0 / (c["radio"]["af"] + 1)), + "mqtt.config.valid": lambda c: ( + "yes" if any(s["preset"] != "none" for s in c["mqtt"]["slots"]) else "no - no slot configured"), + "mqtt.ntp.diag": lambda c: "last sync: 42s ago via %s (offset +0.011s)" % (c["mqtt"]["ntp"] or "none"), + "mqtt.stats": lambda c: ("published: %d\ndropped: 0\nqueue: 0/24\nreconnects: 1" + % (100 + int(time.time() - ST.start))), + # Runtime-gated on the real device (Board::canControlLoRaFemLna), not + # compiled out -- the command exists everywhere and the board answers for + # itself. The mock board is a Heltec V3, which has no FEM. + "radio.fem.rxgain": lambda c: None, +} + + +def cli_mqtt_status(cfg): + lines = [] + for i, s in enumerate(cfg["mqtt"]["slots"][:ST.active_slots]): + if s["preset"] == "none": + lines.append("slot %d: unconfigured" % (i + 1)) + else: + lines.append("slot %d: %-16s connected tx=%d err=0" + % (i + 1, s["preset"], 100 + int(time.time() - ST.start))) + return "\n".join(lines) + + +def cli_get(cfg, key): + """Reply to `get `. + + CommonCLI::handleGetCmd answers `> value` -- the marker sets the value apart + on the serial console. Reproduced here because it is load-bearing: a reply + that starts with "> " does not start with "OK", which is what made the + firmware's first cut mark every getter a failure. + """ + ok, val = _cli_get_value(cfg, key) + return (ok, "> " + val) if ok else (ok, val) + + +def _cli_get_value(cfg, key): + if key in GETTERS: + val = GETTERS[key](cfg) + return (True, val) if val is not None else (False, "Error: unsupported") + if is_secret_key(key): + # The serial console prints these; the portal is reachable over the LAN, + # so it masks them the same way /api/config does. + return True, SENTINEL if cli_read_key(cfg, key) else "(not set)" + val = cli_read_key(cfg, key) + if val is None: + return False, "??: %s" % key # CommonCLI::handleGetCmd fallthrough + return True, str(val) + + +def cli_read_key(cfg, key): + """Current value of a `set` key, or None when the key is unknown.""" + m = re.match(r"^mqtt([1-6])\.(\w+)$", key) + if m: + slot = cfg["mqtt"]["slots"][int(m.group(1)) - 1] + return slot.get(m.group(2)) + for table in (BOOL_KEYS, INT_KEYS, FLOAT_KEYS, STR_KEYS, SECRET_STR_KEYS): + if key in table: + sec, f = table[key] + v = cfg[sec][f] + return ("on" if v else "off") if key in BOOL_KEYS else v + # keys apply_set() special-cases, so they appear in none of the tables above + r = cfg["radio"] + return { + "name": r["name"], "lat": r["lat"], "lon": r["lon"], + "radio": "%.3f,%.2f,%d,%d" % (r["freq"], r["bw"], r["sf"], r["cr"]), + "bw": r["bw"], "sf": r["sf"], "cr": r["cr"], + "mqtt.iata": cfg["mqtt"]["iata"], "mqtt.owner": cfg["mqtt"]["owner"], + }.get(key) + + +def run_cli(cfg, line): + """Execute one command line. Returns (ok, reply); reply may be multi-line.""" + cmd = line.strip() + if cmd == "": + return True, "" + if cmd == "ver": + # Same source as /api/status's fw on the device: both are + # FIRMWARE_VERSION, so they must not disagree here either. + return True, "%s (Build: 6 Jun 2026)" % FW_VERSION + if cmd == "board": + return True, "Heltec V3 (mock)" + if cmd == "clock": + return True, time.strftime("%d/%m/%Y %H:%M:%S", time.gmtime()) + " UTC" + if cmd == "advert": + return True, "OK - Advert sent (zero hop)" + if cmd == "advert.zerohop": + return True, "OK - Advert sent (zero hop)" + if cmd in ("reboot", "clkreboot"): + return True, "OK - rebooting" + if cmd in ("poweroff", "shutdown"): + return True, "OK - powering off" + if cmd == "erase": + return True, "File system erase: OK" + if cmd == "memory": + return True, ("heap free: 142000\nheap min: 118000\n" + "largest block: 96000\npsram free: 3980000") + if cmd == "neighbors": + return True, ("d4e5f60718 -71 dBm snr 9.5 2m ago\n" + "1122334455 -94 dBm snr 2.0 14m ago") + # Handled by MyMesh::handleCommand before it delegates to CommonCLI. + if cmd == "discover.neighbors": + return True, "OK - Discover sent" + if cmd == "discover.scopes": + return True, "OK - scopes queued (18s discovery remaining)" + if cmd.startswith("setperm "): + parts = cmd[8:].split() + if len(parts) != 2 or not _hex64(parts[0]): + return False, "Err - bad params" + return True, "OK" + if cmd.startswith("clock sync"): + # Rejected at POST, but modelled anyway: over the web the caller's + # timestamp is 0, so CommonCLI always takes this branch. + return False, "(ERR: clock cannot go backwards)" + if cmd == "region": + return True, "US915" + if cmd == "sensor list": + return True, "0: battery (mV)\n1: temperature (C)\n2: humidity (%)" + if cmd.startswith("sensor get "): + return True, "> 22.4" + if cmd.startswith("sensor set "): + return True, "OK" + if cmd.startswith("gps advert "): + mode = cmd[11:] + if mode not in ("none", "share", "prefs"): + return False, "Error, must be none, share or prefs" + return True, "OK - advert position: %s" % mode + if cmd in ("gps on", "gps off"): + return True, "OK - GPS %s" % cmd[4:] + if cmd == "gps sync": + return True, "OK - clock and location set from GPS" + if cmd == "gps setloc": + return True, "OK - lat/lon set from the current fix" + if cmd == "gps": + return True, "GPS: no fix (0 satellites)" + if cmd in ("powersaving on", "powersaving off"): + return True, "OK - power saving %s" % cmd[12:] + if cmd == "powersaving": + return True, "off" + if cmd.startswith("alert test"): + if not ST.cfg["cli"]["alert.psk"]: + return False, "Error: alert channel not configured (set alert.psk or set alert.hashtag)" + return True, "OK - test alert sent" + if cmd.startswith("ota "): + return True, ("v1.7.2 available (current v1.7.1-mock)" if cmd == "ota check" + else "OK - downloading v1.7.2, will reboot when flashed") + if cmd.startswith("start webconfig"): + return True, "OK - already running (you are using it)" + if cmd == "stop webconfig": + return True, "OK - portal stopping" + if cmd == "start ota": + return True, "OK - upload AP raised at 192.168.4.1" + if cmd.startswith("neighbor.remove "): + return (True, "OK") if _hex64(cmd[16:]) else (False, "ERR: bad pubkey") + if cmd.startswith("tempradio "): + return True, "OK - temporary radio params applied (not saved)" + if cmd == "clear stats": + return True, "OK - stats cleared" + if cmd.startswith("stats-"): + return True, "recv=512 sent=88 rx_err=3 airtime=41s" + if cmd == "log": + return True, "packet log: 128 entries, 14 KB" + if cmd.startswith("log "): + return True, "OK" + if cmd.startswith("password "): + global ADMIN_PASSWORD + ADMIN_PASSWORD = cmd[9:] + return True, "OK - password changed" + if cmd.startswith("time "): + return True, "OK - clock set" + if cmd.startswith("get "): + return cli_get(cfg, cmd[4:].strip()) + if cmd.startswith("set "): + rest = cmd[4:].strip() + key, _, val = rest.partition(" ") + if not key: + return False, "Error: set what?" + # apply_set owns the "is this settable" decision; gating on whether the + # key is *readable* rejected write-only and computed ones (`dutycycle`, + # `prv.key`, `radio.fem.rxgain`). + return apply_set(cfg, key, val.strip()) + return False, "Unknown command" def valid_reqid(reqid): @@ -331,6 +741,10 @@ class Handler(BaseHTTPRequestHandler): if self._need_auth(): return return self._config_result() + if path == "/api/cli/result": + if self._need_auth(): + return + return self._cli_result() if path == "/api/stats": if self._need_auth(): return @@ -353,6 +767,10 @@ class Handler(BaseHTTPRequestHandler): if self._need_auth(): return return self._config_post() + if path == "/api/cli": + if self._need_auth(): + return + return self._cli_post() if path == "/api/reboot": if self._need_auth(): return @@ -369,6 +787,12 @@ class Handler(BaseHTTPRequestHandler): except OSError: self.send_error(500, "webui/index.html not found") return + if MINIFY: + # Serve what the device actually serves. The generator strips + # comments and indentation before compressing, so --minify is how + # you exercise those bytes in a browser rather than trusting that + # stripping a 100 KB page never changes its behaviour. + html = strip_source(html.decode("utf-8")).encode("utf-8") self.send_response(200) self.send_header("Content-Type", "text/html; charset=utf-8") self.send_header("Content-Length", str(len(html))) @@ -424,6 +848,13 @@ class Handler(BaseHTTPRequestHandler): # drop unchanged secrets (sentinel), like the firmware does entries = [(k, v) for k, v in setmap.items() if not (is_secret_key(k) and v == SENTINEL)] + # Same command-budget rejection the firmware applies while building + # BatchEntry::cmd; CR/LF are stripped there and don't count. + for k, v in entries: + prefix = "password " if k == "password" else "set %s " % k + stripped = str(v).replace("\r", "").replace("\n", "") + if len(prefix) + len(stripped) > BATCH_CMD_SIZE - 1: + return self._json(400, {"error": "value too long", "key": k[:32]}) if not entries and not reboot: return self._json(400, {"error": "no changes"}) # apply now, but expose as pending->done to exercise polling @@ -457,6 +888,125 @@ class Handler(BaseHTTPRequestHandler): "reboot": b["reboot"] and b["all_ok"], "results": b["results"], }) + # ---- CLI --------------------------------------------------------------- + # Same 202 + reqid + poll shape as /api/config, for the same reason: the + # commands have to run on the main loop, not the web server's task. The + # difference is that results stream -- a pasted sequence fills the terminal + # command by command instead of appearing all at once at the end. + def _cli_post(self): + raw = self._read_body() + if len(raw) > 8192: + return self._json(413, {"error": "body too large"}) + try: + body = json.loads(raw or b"{}") + except ValueError: + return self._json(400, {"error": "bad json"}) + reqid = body.get("reqid", "") + if not valid_reqid(reqid): + return self._json(400, {"error": "bad reqid"}) + cmds = body.get("cmds") + if not isinstance(cmds, list) or not cmds: + return self._json(400, {"error": "no commands"}) + if len(cmds) > CLI_MAX_CMDS: + return self._json(413, {"error": "too many commands", "max": CLI_MAX_CMDS}) + cmds = [str(c).replace("\r", "").replace("\n", "").strip() for c in cmds] + cmds = [c for c in cmds if c] + if not cmds: + return self._json(400, {"error": "no commands"}) + for c in cmds: + if len(c) > BATCH_CMD_SIZE - 1: + return self._json(400, {"error": "command too long"}) + why = cli_unavailable(c) + if why: + return self._json(400, {"error": why}) + # Same invariant handleConfigPost enforces (see wcCliUnavailable's + # neighbour in WebConfigServer.cpp): first onboarding is committed by the + # reboot, and must not commit the factory password onto someone's LAN. + if (ST.setup_mode and ST.initial_setup and not ST.admin_pwd_set + and not any(c.startswith("password ") for c in cmds) + and (any(c.startswith(CLI_DEFERRED_REBOOT) for c in cmds) + or any(c.startswith("set wifi.ssid ") for c in cmds))): + return self._json(400, {"error": "admin password required for initial setup -- " + "run `password ` first"}) + + with ST.lock: + self._cli_advance(ST.cli) + if ST.cli.get("state") != "idle" and ST.cli.get("reqid") == reqid: + return self._json(202, {"state": ST.cli["state"], "reqid": reqid, + "total": len(ST.cli["cmds"])}) + if ST.cli.get("state") == "running": + return self._json(409, {"error": "busy", "reqid": ST.cli.get("reqid", "")}) + ST.cli = {"state": "running", "reqid": reqid, "cmds": cmds, "results": [], + "all_ok": True, + "reboot": any(c.startswith(CLI_DEFERRED_REBOOT) for c in cmds), + "next_at": time.time() + CLI_CMD_SECS} + return self._json(202, {"state": "running", "reqid": reqid, "total": len(cmds)}) + + @staticmethod + def _cli_advance(job): + """Run whichever queued commands are now due. Execution belongs to the + node's loop, not to the client's polling -- a client that walks away must + not leave the executor claimed forever.""" + now = time.time() + while (job.get("state") == "running" and len(job["results"]) < len(job["cmds"]) + and now >= job["next_at"]): + cmd = job["cmds"][len(job["results"])] + if cmd.startswith(CLI_DEFERRED_REBOOT): + reply = "OK - reboot queued" + else: + _, reply = run_cli(ST.cfg, cmd) + if cmd.startswith("password "): + reply = "OK" # never echo the new password back + ST.admin_pwd_set = True + elif cli_reads_secret(cmd): + val = reply[2:] if reply.startswith("> ") else reply + reply = ("> (not set)" if val in ("", "(not set)") + else "> ******** (serial only)") + ok = not cli_reply_is_failure(reply) + # Only writes gate the reboot, and only on the "OK" convention every + # setter keeps (WebConfigBatch::cliReplyGatesReboot). + if cmd.startswith(("set ", "password ")): + job["all_ok"] = job.get("all_ok", True) and reply.startswith("OK") + # The command is NOT echoed: it may carry a password or token, and + # the client matches results to its own sequence by index. + job["results"].append({"ok": ok, "reply": reply}) + job["next_at"] = now + CLI_CMD_SECS + if job.get("state") == "running" and len(job["results"]) == len(job["cmds"]): + job["state"] = "done" # stays readable until the next POST + + def _cli_result(self): + query = parse_qs(urlsplit(self.path).query) + reqid = query.get("reqid", [""])[0] + if not valid_reqid(reqid): + return self._json(400, {"error": "bad reqid"}) + # `from` lets the client ask only for results it has not rendered yet, + # so a long sequence isn't re-sent on every poll. + try: + frm = max(0, int(query.get("from", ["0"])[0])) + except ValueError: + frm = 0 + with ST.lock: + j = ST.cli + if j.get("state") == "idle": + return self._json(200, {"state": "idle", "reqid": reqid}) + if j.get("reqid") != reqid: + return self._json(404, {"error": "unknown request"}) + self._cli_advance(j) # one command per CLI_CMD_SECS + # Results stream, capped per read so the device's JSON document + # stays small; a longer sequence pages across reads. "done" means + # the client has been handed everything, not just that execution + # finished -- a client that stops polling at "done" must lose nothing. + page = j["results"][frm:frm + CLI_RESULT_PAGE] + final = j["state"] == "done" and frm + len(page) >= len(j["cmds"]) + body = {"state": "done" if final else "running", "reqid": reqid, + "total": len(j["cmds"]), "from": frm, "results": page} + if final: + body["all_ok"] = j["all_ok"] + body["reboot"] = j["reboot"] and j["all_ok"] + if j["reboot"] and not j["all_ok"]: + body["reboot_withheld"] = True + return self._json(200, body) + def _scan(self): rescan = "rescan=1" in self.path now = time.time() @@ -473,8 +1023,14 @@ class Handler(BaseHTTPRequestHandler): for i, s in enumerate(ST.cfg["mqtt"]["slots"]): if s["preset"] == "none": continue - slots.append({"n": i + 1, "name": s["preset"], "state": "ok", - "ok": 100 + up, "err": 0}) + row = {"n": i + 1, "name": s["preset"], "state": "ok", + "ok": 100 + up, "err": 0} + # Mirrors buildStatsJson(): "filt" carries the raw mask and is + # omitted entirely for the all-types default. + mask = packet_filter_mask(s.get("filter", "all")) + if mask != 0xFFFF: + row["filt"] = mask + slots.append(row) return { "uptime_s": up, "batt_mv": 4020, "heap_free": 142000, "heap_min": 118000, "heap_max_alloc": 96000, "noise": -98, "rssi": -71, "snr": 9.5, @@ -486,17 +1042,22 @@ class Handler(BaseHTTPRequestHandler): def main(): - global ST, PORT + global ST, PORT, MINIFY, FW_VERSION ap = argparse.ArgumentParser(description="Mock WebConfig portal backend") ap.add_argument("--port", type=int, default=8080) ap.add_argument("--setup", action="store_true", help="first-boot setup wizard mode") ap.add_argument("--active-slots", type=int, default=5, help="server slots to expose (2 or 5)") + ap.add_argument("--fw-version", default=FW_VERSION, + help="version string to report, shaped like build.sh's embedded one") + ap.add_argument("--minify", action="store_true", + help="serve the comment-stripped page the device ships, not the source") args = ap.parse_args() - ST, PORT = State(args), args.port + ST, PORT, MINIFY = State(args), args.port, args.minify + FW_VERSION = args.fw_version srv = ThreadingHTTPServer(("127.0.0.1", args.port), Handler) mode = "SETUP (wizard)" if args.setup else "LAN (login: %s)" % ADMIN_PASSWORD - print("WebConfig mock backend -- %s" % mode) + print("WebConfig mock backend -- %s%s" % (mode, " [minified]" if MINIFY else "")) print(" open http://localhost:%d/ (Ctrl-C to stop)" % args.port) try: srv.serve_forever() diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 4228e063..e8f42621 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -358,6 +358,7 @@ public: uint32_t getNumRecvDirect() const { return n_recv_direct; } uint16_t getErrFlags() const { return _err_flags; } // Get error flags bool hasOutbound() const { return outbound != NULL; } + bool isCurrentOutbound(const Packet* packet) const { return outbound == packet; } void resetStats() { n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0; _err_flags = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d31886b5..5baac3c2 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -52,6 +52,8 @@ static void resetToUf2Bootloader() { #include #include #include +#else +#include // mallinfo() for the `memory` command on nRF52/RP2040 #endif #ifdef WITH_MQTT_BRIDGE #include "bridges/MQTTBridge.h" @@ -2026,6 +2028,16 @@ void CommonCLI::loadMQTTPrefs( MESH_DEBUG_PRINTLN("MQTT: /mqtt_prefs read failed, using defaults (file preserved)"); } else { has_observer_fields = plan.observer_fields_present; + // Written by a later build with appended fields. Everything this + // binary knows loaded normally; say so, because the next `set` will + // rewrite the file at this length and drop the newer settings. + if (file_size - sizeof(MQTTPrefsHeader) > plan.payload_len) { + MESH_DEBUG_PRINTLN( + "MQTT: /mqtt_prefs written by newer firmware (%u > %u bytes); " + "config loaded, newer settings ignored and dropped on next save", + (unsigned)(file_size - sizeof(MQTTPrefsHeader)), + (unsigned)plan.payload_len); + } } if (file) file.close(); } else if (plan.rewrite_legacy) { @@ -2182,12 +2194,15 @@ bool CommonCLI::saveMQTTPrefs(FILESYSTEM* fs) { } // Write header and payload sequentially so the transaction needs no second - // full-size (2.8 KiB) staging buffer on constrained targets. - const MQTTPrefsHeader header = MQTTPrefsCodec::makeHeader(); + // full-size (2.8 KiB) staging buffer on constrained targets. The length is + // the shortest that still round-trips this config, so a node with default + // packet filters keeps writing a payload older firmware can read. + const size_t payload_len = MQTTPrefsCodec::payloadLenFor(_mqtt_prefs); + const MQTTPrefsHeader header = MQTTPrefsCodec::makeHeader(payload_len); MQTTPrefsFileStore store(fs); const MQTTPrefsAtomicStore::Result result = MQTTPrefsAtomicStore::write( store, (const uint8_t *)&header, sizeof(header), - (const uint8_t *)&_mqtt_prefs, sizeof(_mqtt_prefs)); + (const uint8_t *)&_mqtt_prefs, payload_len); if (!MQTTPrefsAtomicStore::committed(result)) { MESH_DEBUG_PRINTLN("MQTT: atomic /mqtt_prefs save failed at %s; source preserved", mqttPrefsSaveResultName(result)); @@ -2514,8 +2529,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re strcpy(reply, "ERR: webconfig not supported on this build"); } #endif -#ifdef ESP_PLATFORM } else if (memcmp(command, "memory", 6) == 0) { +#ifdef ESP_PLATFORM sprintf(reply, "Free: %d, Min: %d, Max: %d, Queue: %d, IntFree: %d, IntMax: %d, PSRAM: %d/%d", ESP.getFreeHeap(), ESP.getMinFreeHeap(), ESP.getMaxAllocHeap(), _callbacks->getQueueSize(), @@ -2523,6 +2538,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re (int)heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL), (int)heap_caps_get_free_size(MALLOC_CAP_SPIRAM), (int)heap_caps_get_total_size(MALLOC_CAP_SPIRAM)); +#else + struct mallinfo mi = mallinfo(); + sprintf(reply, "Heap: free=%d, used=%d", (int)mi.fordblks, (int)mi.uordblks); #endif } else if (memcmp(command, "start ota", 9) == 0 && (command[9] == 0 || command[9] == ' ')) { // Manual OTA: bring up the board's browser server for a hand-uploaded binary. diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b36d175f..d9003ccd 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -8,6 +8,7 @@ #include #include #include +#include #ifndef DEFAULT_CAD_ENABLED #define DEFAULT_CAD_ENABLED 0 @@ -106,90 +107,91 @@ #define TELEMETRY_ACCESS_ALL 0 #define TELEMETRY_ACCESS_ACL 1 -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - char password[16]; - float freq; - int8_t tx_power_dbm; - uint8_t disable_fwd; - uint8_t advert_interval; // minutes / 2 - uint8_t flood_advert_interval; // hours - float rx_delay_base; - float tx_delay_factor; - char guest_password[16]; - float direct_tx_delay_factor; - uint32_t guard; - uint8_t sf; - uint8_t cr; - uint8_t allow_read_only; - uint8_t multi_acks; - float bw; - uint8_t flood_max; - uint8_t flood_max_unscoped; - uint8_t flood_max_advert; - uint8_t interference_threshold; - uint8_t agc_reset_interval; // secs / 4 +class NodePrefs : public ConfigSerializer { // persisted to file +public: + float airtime_factor = 0; + char node_name[32] = {}; + double node_lat = 0, node_lon = 0; + char password[16] = {}; + float freq = 0; + int8_t tx_power_dbm = 0; + uint8_t disable_fwd = 0; + uint8_t advert_interval = 0; // minutes / 2 + uint8_t flood_advert_interval = 0; // hours + float rx_delay_base = 0; + float tx_delay_factor = 0; + char guest_password[16] = {}; + float direct_tx_delay_factor = 0; + uint32_t guard = 0; + uint8_t sf = 0; + uint8_t cr = 0; + uint8_t allow_read_only = 0; + uint8_t multi_acks = 0; + float bw = 0; + uint8_t flood_max = 0; + uint8_t flood_max_unscoped = 0; + uint8_t flood_max_advert = 0; + uint8_t interference_threshold = 0; + uint8_t agc_reset_interval = 0; // secs / 4 // Bridge settings - uint8_t bridge_enabled; // boolean - uint16_t bridge_delay; // milliseconds (default 500 ms) - uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logRx) - uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) - uint8_t bridge_channel; // 1-14 (ESP-NOW only) - char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) + uint8_t bridge_enabled = 0; // boolean + uint16_t bridge_delay = 0; // milliseconds (default 500 ms) + uint8_t bridge_pkt_src = 0; // 0 = logTx, 1 = logRx (default logRx) + uint32_t bridge_baud = 0; // 9600, 19200, 38400, 57600, 115200 (default 115200) + uint8_t bridge_channel = 0; // 1-14 (ESP-NOW only) + char bridge_secret[16] = {}; // for XOR encryption of bridge packets (ESP-NOW only) // Power setting - uint8_t powersaving_enabled; // boolean - uint8_t reboot_interval; // hours, 0-255 (default 0=disable) + uint8_t powersaving_enabled = 0; // boolean + uint8_t reboot_interval = 0; // hours, 0-255 (default 0=disable) // Gps settings - uint8_t gps_enabled; - uint32_t gps_interval; // in seconds - uint8_t advert_loc_policy; - uint32_t discovery_mod_timestamp; - float adc_multiplier; - char owner_info[120]; + uint8_t gps_enabled = 0; + uint32_t gps_interval = 0; // in seconds + uint8_t advert_loc_policy = 0; + uint32_t discovery_mod_timestamp = 0; + float adc_multiplier = 0; + char owner_info[120] = {}; // NOTE: member order below matches mcarper/keymindCascade (upstream/dev order). // It is in-memory only - /com_prefs is read/written field-by-field in loadPrefsInt/ // savePrefs, whose canonical file order (identical to the flex fleet's through // offset 294, keymind retry tail at 295+) is what devices actually persist. - uint8_t rx_boosted_gain; // power settings - uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting - uint8_t path_hash_mode; // which path mode to use when sending - uint8_t loop_detect; - uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean) + uint8_t rx_boosted_gain = 0; // power settings + uint8_t radio_fem_rxgain = 0; // LoRa FEM RX gain setting + uint8_t path_hash_mode = 0; // which path mode to use when sending + uint8_t loop_detect = 0; + uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean) // LR2021 side-detector SFs are appended at /com_prefs offset 856. Their // in-memory placement here does not shift the established binary layout. uint8_t extra_sf[4] = {}; - uint8_t radio_fem_txgain; // LoRa FEM TX gain; persisted at /com_prefs offset 860 - uint8_t retry_preset; - uint8_t direct_retry_attempts; - uint16_t direct_retry_base_ms; - uint16_t direct_retry_step_ms; - uint16_t direct_retry_snr_margin_x4; - int8_t direct_retry_cr4_snr_x4; - int8_t direct_retry_cr5_snr_x4; - int8_t direct_retry_cr7_snr_x4; - int8_t direct_retry_cr8_snr_x4; - uint8_t direct_retry_enabled; - uint8_t direct_retry_cr_enabled; - uint8_t direct_retry_prefs_magic[2]; - uint8_t flood_retry_attempts; - uint8_t flood_retry_max_path; - uint8_t flood_retry_prefixes[FLOOD_RETRY_PREFIX_SLOTS][FLOOD_RETRY_PREFIX_LEN]; - uint8_t flood_retry_bridge_enabled; - uint8_t flood_retry_bridge_buckets[FLOOD_RETRY_BRIDGE_BUCKETS][FLOOD_RETRY_BUCKET_PREFIXES][FLOOD_RETRY_PREFIX_LEN]; - uint8_t flood_retry_ignore_prefixes[FLOOD_RETRY_IGNORE_PREFIXES][FLOOD_RETRY_PREFIX_LEN]; - uint8_t flood_retry_advert_enabled; - uint8_t battery_alert_enabled; - uint8_t battery_alert_low_percent; - uint8_t battery_alert_critical_percent; - uint8_t direct_retry_recent_enabled; - uint8_t flood_channel_data_enabled; + uint8_t radio_fem_txgain = 0; // LoRa FEM TX gain; persisted at /com_prefs offset 860 + uint8_t retry_preset = 0; + uint8_t direct_retry_attempts = 0; + uint16_t direct_retry_base_ms = 0; + uint16_t direct_retry_step_ms = 0; + uint16_t direct_retry_snr_margin_x4 = 0; + int8_t direct_retry_cr4_snr_x4 = 0; + int8_t direct_retry_cr5_snr_x4 = 0; + int8_t direct_retry_cr7_snr_x4 = 0; + int8_t direct_retry_cr8_snr_x4 = 0; + uint8_t direct_retry_enabled = 0; + uint8_t direct_retry_cr_enabled = 0; + uint8_t direct_retry_prefs_magic[2] = {}; + uint8_t flood_retry_attempts = 0; + uint8_t flood_retry_max_path = 0; + uint8_t flood_retry_prefixes[FLOOD_RETRY_PREFIX_SLOTS][FLOOD_RETRY_PREFIX_LEN] = {}; + uint8_t flood_retry_bridge_enabled = 0; + uint8_t flood_retry_bridge_buckets[FLOOD_RETRY_BRIDGE_BUCKETS][FLOOD_RETRY_BUCKET_PREFIXES][FLOOD_RETRY_PREFIX_LEN] = {}; + uint8_t flood_retry_ignore_prefixes[FLOOD_RETRY_IGNORE_PREFIXES][FLOOD_RETRY_PREFIX_LEN] = {}; + uint8_t flood_retry_advert_enabled = 0; + uint8_t battery_alert_enabled = 0; + uint8_t battery_alert_low_percent = 0; + uint8_t battery_alert_critical_percent = 0; + uint8_t direct_retry_recent_enabled = 0; + uint8_t flood_channel_data_enabled = 0; // Retains the removed flood.channel.block byte at its established file // offset so every following preference remains upgrade-compatible. - uint8_t legacy_flood_channel_block_max_hops; - uint8_t flood_channel_data_max_hops; - uint8_t telemetry_access; + uint8_t legacy_flood_channel_block_max_hops = 0; + uint8_t flood_channel_data_max_hops = 0; + uint8_t telemetry_access = 0; // NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) were moved out of // NodePrefs into MQTTPrefs (persisted to /mqtt_prefs) so this struct stays @@ -198,24 +200,155 @@ struct NodePrefs { // persisted to file #if defined(ENABLE_OTA) // OTA config (persisted; synced to OtaContext on load, written on change). 0 = conservative defaults. - uint8_t ota_autofetch; // OtaManager AUTOFETCH_* (0=off, 1=any-compatible, 2=signed-only) - uint8_t ota_autoinstall; // OtaContext AUTOINSTALL_* (0=off, 1=trusted-only) - uint8_t ota_signer_count; // # of allowlisted signer pubkeys below - uint8_t ota_signers[4][32]; // trusted Ed25519 signer pubkeys (== MAX_OTA_SIGNERS) - uint16_t ota_checkpoint_blocks; // resume checkpoint cadence (blocks); 0=never. Default 4 (runtime-tunable) - uint16_t ota_advert_interval; // OTA beacon re-advertise cadence (mins); 0=off. Default 1440 (24h, tunable) - uint8_t ota_max_hops; // OTA flood reach in hops; 0=direct only. Default 3 (runtime-tunable) + uint8_t ota_autofetch = 0; // OtaManager AUTOFETCH_* (0=off, 1=any-compatible, 2=signed-only) + uint8_t ota_autoinstall = 0; // OtaContext AUTOINSTALL_* (0=off, 1=trusted-only) + uint8_t ota_signer_count = 0; // # of allowlisted signer pubkeys below + uint8_t ota_signers[4][32] = {}; // trusted Ed25519 signer pubkeys (== MAX_OTA_SIGNERS) + uint16_t ota_checkpoint_blocks = 0; // resume checkpoint cadence (blocks); 0=never. Default 4 (runtime-tunable) + uint16_t ota_advert_interval = 0; // OTA beacon re-advertise cadence (mins); 0=off. Default 1440 (24h, tunable) + uint8_t ota_max_hops = 0; // OTA flood reach in hops; 0=direct only. Default 3 (runtime-tunable) #endif - uint8_t rx_powersaving_enabled; // boolean - uint32_t rx_ps_rx_us; - uint32_t rx_ps_sleep_us; - uint8_t rx_ps_level; // 0 = manual/explicit us timings; 1..10 = level-derived (auto-retunes on SF/BW change) - uint8_t rx_ps_preamble; // 0 = auto (derive from SF); else 16 or 32 = explicit override for level calc - char battery_alert_region[31]; // named scope for low-battery floods; empty = no alert scope - uint8_t flood_retry_group_max_path; // PAYLOAD_TYPE_GRP_DATA retry path gate; 0xFF = use only the general gate - uint8_t rx_watchdog_enabled; // repeater RX-inactivity reboot watchdog (boolean) - uint8_t system_watchdog_enabled; // nRF52 main-loop hardware watchdog (boolean; default on) + uint8_t rx_powersaving_enabled = 0; // boolean + uint32_t rx_ps_rx_us = 0; + uint32_t rx_ps_sleep_us = 0; + uint8_t rx_ps_level = 0; // 0 = manual/explicit us timings; 1..10 = level-derived (auto-retunes on SF/BW change) + uint8_t rx_ps_preamble = 0; // 0 = auto (derive from SF); else 16 or 32 = explicit override for level calc + char battery_alert_region[31] = {}; // named scope for low-battery floods; empty = no alert scope + uint8_t flood_retry_group_max_path = 0; // PAYLOAD_TYPE_GRP_DATA retry path gate; 0xFF = use only the general gate + uint8_t rx_watchdog_enabled = 0; // repeater RX-inactivity reboot watchdog (boolean) + uint8_t system_watchdog_enabled = 0; // nRF52 main-loop hardware watchdog (boolean; default on) + +private: + class RadioPrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("freq", _parent->freq); + def("bw", _parent->bw); + def("sf", _parent->sf); + def("cr", _parent->cr); + def("cad", _parent->cad_enabled); + def("int_thr", _parent->interference_threshold); + def("rxgain", _parent->rx_boosted_gain); + def("fem_rxgain", _parent->radio_fem_rxgain); + def("fem_txgain", _parent->radio_fem_txgain); + def("tx", _parent->tx_power_dbm); + def("af", _parent->airtime_factor); + def("rxdelay", _parent->rx_delay_base); + def("f_txdelay", _parent->tx_delay_factor); + def("d_txdelay", _parent->direct_tx_delay_factor); + def("agc_int", _parent->agc_reset_interval); + def("hash_mode", _parent->path_hash_mode); + def("multi_ack", _parent->multi_acks); + } + + public: + explicit RadioPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RadioPrefs radio; + + class BridgePrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("en", _parent->bridge_enabled); + def("delay", _parent->bridge_delay); + def("src", _parent->bridge_pkt_src); + def("baud", _parent->bridge_baud); + def("ch", _parent->bridge_channel); + def("secret", _parent->bridge_secret, sizeof(_parent->bridge_secret)); + } + + public: + explicit BridgePrefs(NodePrefs* parent) : _parent(parent) { } + }; + BridgePrefs bridge; + + class GPSPrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("en", _parent->gps_enabled); + def("int", _parent->gps_interval); + def("adv_loc", _parent->advert_loc_policy); + } + + public: + explicit GPSPrefs(NodePrefs* parent) : _parent(parent) { } + }; + GPSPrefs gps; + + class PowerPrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("adc_mult", _parent->adc_multiplier); + def("pwr_sav_en", _parent->powersaving_enabled); + } + + public: + explicit PowerPrefs(NodePrefs* parent) : _parent(parent) { } + }; + PowerPrefs power; + + class RepeatPrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("disable", _parent->disable_fwd); + def("f_max", _parent->flood_max); + def("f_max_uns", _parent->flood_max_unscoped); + def("f_max_adv", _parent->flood_max_advert); + def("loop", _parent->loop_detect); + } + + public: + explicit RepeatPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RepeatPrefs repeat; + + class RoomPrefs : public ConfigSerializer { + NodePrefs* _parent; + + protected: + void structure() override { + def("rd_only", _parent->allow_read_only); + } + + public: + explicit RoomPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RoomPrefs room; + +protected: + void structure() override { + def("name", node_name, sizeof(node_name)); + def("pass", password, sizeof(password)); + def("guest", guest_password, sizeof(guest_password)); + def("owner", owner_info, sizeof(owner_info)); + def("adv_int", advert_interval); + def("f_adv_int", flood_advert_interval); + def("lat", node_lat); + def("lon", node_lon); + def("disc_mod", discovery_mod_timestamp); + def("radio", radio); + def("bridge", bridge); + def("gps", gps); + def("repeat", repeat); + def("room", room); + def("power", power); + } + +public: + NodePrefs() + : ConfigSerializer(), radio(this), bridge(this), gps(this), power(this), + repeat(this), room(this) { } }; #ifdef WITH_MQTT_BRIDGE @@ -518,7 +651,7 @@ class CommonCLI { // run on defaults and saveMQTTPrefs() must not overwrite the source file. bool _mqtt_prefs_hold = false; #endif - bool _com_prefs_needs_upgrade = false; // old-format /com_prefs detected; rewrite once after load + bool _com_prefs_needs_upgrade = false; // old-format legacy prefs detected; rewrite once after load mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs( diff --git a/src/helpers/CommonCLI_Observer.cpp b/src/helpers/CommonCLI_Observer.cpp index b7bf152e..f4f94eb0 100644 --- a/src/helpers/CommonCLI_Observer.cpp +++ b/src/helpers/CommonCLI_Observer.cpp @@ -31,6 +31,7 @@ #include "bridges/MQTTBridge.h" #include "MQTTConnectionPolicy.h" // classifySlotActivation() -- "will this slot connect here?" #include "MQTTDefaults.h" +#include "MQTTPacketFilter.h" #endif // Local copy of the busted-libc-safe atoi (the original in CommonCLI.cpp is static). @@ -294,7 +295,7 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf #elif defined(WITH_MQTT_BRIDGE) } else if (memcmp(config, "mqtt.neighbors.interval ", 24) == 0 || memcmp(config, "mqtt.neighbors ", 15) == 0) { - strcpy(reply, "Err - not supported (requires PSRAM)"); + strcpy(reply, "Err - neighbors not enabled in this build"); #endif } else if (memcmp(config, "mqtt.ntp ", 9) == 0) { const char* host = &config[9]; @@ -539,6 +540,32 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf saveObserverPrefs(); _callbacks->restartBridgeSlot(slot); sprintf(reply, "OK - slot %d JWT audience cleared (using username/password auth)", slot + 1); + } else if (strcmp(subcmd, "filter") == 0 || + strncmp(subcmd, "filter ", 7) == 0) { + // Empty/bare input resets to the backwards-compatible all-types default. + const char* filter_value = subcmd[6] == '\0' ? "" : &subcmd[7]; + uint16_t filter_mask = 0; + if (!MQTTPacketFilter::parse(filter_value, &filter_mask)) { + strcpy(reply, "Error: filter must be all, none, or a CSV of types 0-15 / names (advert,txt_msg,...)"); + } else { + _mqtt_prefs.mqtt_slot_packet_filter[slot] = filter_mask; + savePrefs(); + char filter_text[MQTTPacketFilter::kFilterTextSize]; + MQTTPacketFilter::format(filter_mask, filter_text, sizeof(filter_text)); + snprintf(reply, 160, "OK - slot %d packet types: %s", slot + 1, filter_text); + // A non-default filter extends /mqtt_prefs past what pre-filter + // firmware can read (see MQTTPrefsCodec::payloadLenFor), so say when + // that cost buys nothing: slots beyond the runtime array are never + // published to on this board, the same warning `preset` gives. + if (slot >= RUNTIME_MQTT_SLOTS && + filter_mask != MQTTPacketFilter::kAllPacketTypes) { + size_t used = strlen(reply); + if (used < 158) { + snprintf(reply + used, 160 - used, + " (slot inactive on this hardware; blocks firmware rollback)"); + } + } + } } else { sprintf(reply, "unknown config: %s", config); } @@ -825,7 +852,7 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf #elif defined(WITH_MQTT_BRIDGE) } else if (memcmp(config, "mqtt.neighbors.interval", 23) == 0 || memcmp(config, "mqtt.neighbors", 14) == 0) { - strcpy(reply, "Err - not supported (requires PSRAM)"); + strcpy(reply, "Err - neighbors not enabled in this build"); #endif } else if (memcmp(config, "mqtt.ntp.diag", 13) == 0 && (config[13] == '\0' || config[13] == ' ')) { #if defined(PORTABLE_MQTT_OBSERVER) @@ -888,6 +915,14 @@ bool CommonCLI::handleObserverGetCmd(uint32_t sender_timestamp, const char* conf } else { strcpy(reply, "> (not set - custom slots use username/password auth)"); } + } else if (strcmp(subcmd, "filter") == 0) { + char filter_text[MQTTPacketFilter::kFilterTextSize]; + if (MQTTPacketFilter::format(_mqtt_prefs.mqtt_slot_packet_filter[slot], + filter_text, sizeof(filter_text))) { + snprintf(reply, 160, "> %s", filter_text); + } else { + strcpy(reply, "Error: invalid stored packet filter"); + } } else if (memcmp(subcmd, "diag", 4) == 0) { MQTTBridge::formatSlotDiagReply(reply, 160, slot); } else { diff --git a/src/helpers/JWTHelper.cpp b/src/helpers/JWTHelper.cpp index b2873298..b1c1393d 100644 --- a/src/helpers/JWTHelper.cpp +++ b/src/helpers/JWTHelper.cpp @@ -1,7 +1,10 @@ -#include "JWTHelper.h" - +// MQTT-only translation unit. 22 variants re-glob helpers/*.cpp past the +// arduino_base exclusion, so the contents are guarded here rather than in +// the build filter -- same idiom as helpers/esp32/WebConfigServer.cpp. #ifdef WITH_MQTT_BRIDGE +#include "JWTHelper.h" + #include #include #include @@ -202,4 +205,4 @@ size_t JWTHelper::createPayload( return base64UrlEncode((uint8_t*)jsonBuffer, len, output, outputSize); } -#endif +#endif // WITH_MQTT_BRIDGE diff --git a/src/helpers/MQTTConnectionPolicy.h b/src/helpers/MQTTConnectionPolicy.h index 0175a1aa..766bdb3e 100644 --- a/src/helpers/MQTTConnectionPolicy.h +++ b/src/helpers/MQTTConnectionPolicy.h @@ -18,6 +18,10 @@ static const uint32_t kDefaultJwtLifetimeSecs = 86400UL; static const uint32_t kMaxJwtStaggerSecs = 300UL; static const uint32_t kMinimumValidEpoch = 1000000000UL; static const uint32_t kJwtClockThreshold = 1735689600UL; // 2025-01-01 UTC +// A wall clock at or past this instant was set from a real source (NTP or an +// admin); anything earlier is the firmware's unset-clock default (1715770351, +// 15 May 2024), so a delta spanning the sync is meaningless. +static const uint32_t kSyncedClockEpoch = kJwtClockThreshold; // Unsigned subtraction is intentionally used: it is the standard millis() // idiom and remains correct across a single 32-bit counter rollover. diff --git a/src/helpers/MQTTDefaults.h b/src/helpers/MQTTDefaults.h index 2865e6ef..6bf0b43b 100644 --- a/src/helpers/MQTTDefaults.h +++ b/src/helpers/MQTTDefaults.h @@ -5,6 +5,7 @@ #include #include #include "MQTTPrefs.h" +#include "MQTTPacketFilter.h" #include "MQTTPresets.h" // Compile-time defaults for fresh /mqtt_prefs (override via platformio build_flags). @@ -82,6 +83,9 @@ static inline void applyMQTTDefaults(MQTTPrefs* prefs) { MQTT_DEFAULT_SLOT5_PRESET); mqttDefaultSlotPreset(prefs->mqtt_slot_preset[5], sizeof(prefs->mqtt_slot_preset[5]), MQTT_DEFAULT_SLOT6_PRESET); + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + prefs->mqtt_slot_packet_filter[i] = MQTTPacketFilter::kAllPacketTypes; + } if (MQTT_DEFAULT_IATA[0] != '\0') { strncpy(prefs->mqtt_iata, MQTT_DEFAULT_IATA, sizeof(prefs->mqtt_iata) - 1); diff --git a/src/helpers/MQTTMessageBuilder.cpp b/src/helpers/MQTTMessageBuilder.cpp index 1389cfeb..8486b68f 100644 --- a/src/helpers/MQTTMessageBuilder.cpp +++ b/src/helpers/MQTTMessageBuilder.cpp @@ -1,7 +1,10 @@ -#include "MQTTMessageBuilder.h" - +// MQTT-only translation unit. 22 variants re-glob helpers/*.cpp past the +// arduino_base exclusion, so the contents are guarded here rather than in +// the build filter -- same idiom as helpers/esp32/WebConfigServer.cpp. #ifdef WITH_MQTT_BRIDGE +#include "MQTTMessageBuilder.h" + #include "MQTTPayloadBuilder.h" #include #include @@ -114,14 +117,38 @@ int MQTTMessageBuilder::buildNeighborsMessage( const char* origin_id, const char* timestamp, const char* self_scopes, + const char* self_default_scope, const NeighborsMessageEntry* neighbors, int neighbor_count, char* buffer, - size_t buffer_size + size_t buffer_size, + int total_neighbors, + int queried_neighbors, + bool truncated ) { return MQTTPayloadBuilder::buildNeighborsMessage( - doc, origin, origin_id, timestamp, self_scopes, neighbors, neighbor_count, - buffer, buffer_size); + doc, origin, origin_id, timestamp, self_scopes, self_default_scope, + neighbors, neighbor_count, buffer, buffer_size, total_neighbors, + queried_neighbors, truncated); +} + +size_t MQTTMessageBuilder::measureNeighborsMessageBase( + const char* origin, + const char* origin_id, + const char* timestamp, + const char* self_scopes, + const char* self_default_scope, + int total_neighbors +) { + return MQTTPayloadBuilder::measureNeighborsMessageBase( + origin, origin_id, timestamp, self_scopes, self_default_scope, + total_neighbors); +} + +size_t MQTTMessageBuilder::measureNeighborsMessageEntry( + const NeighborsMessageEntry& neighbor +) { + return MQTTPayloadBuilder::measureNeighborsMessageEntry(neighbor); } int MQTTMessageBuilder::buildPacketJSON( @@ -348,4 +375,4 @@ void MQTTMessageBuilder::packetToHex(mesh::Packet* packet, char* hex, size_t hex bytesToHex(raw_buf, raw_len, hex, hex_size); } -#endif +#endif // WITH_MQTT_BRIDGE diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index 69c943b3..098b2d3b 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -166,11 +166,24 @@ public: const char* origin_id, const char* timestamp, const char* self_scopes, + const char* self_default_scope, const NeighborsMessageEntry* neighbors, int neighbor_count, char* buffer, - size_t buffer_size + size_t buffer_size, + int total_neighbors = -1, + int queried_neighbors = -1, + bool truncated = false ); + static size_t measureNeighborsMessageBase( + const char* origin, + const char* origin_id, + const char* timestamp, + const char* self_scopes, + const char* self_default_scope, + int total_neighbors + ); + static size_t measureNeighborsMessageEntry(const NeighborsMessageEntry& neighbor); /** * Convert packet to JSON message diff --git a/src/helpers/MQTTPacketFilter.h b/src/helpers/MQTTPacketFilter.h new file mode 100644 index 00000000..df811828 --- /dev/null +++ b/src/helpers/MQTTPacketFilter.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include + +// Pure per-broker packet-type allowlist helpers. MeshCore payload types occupy +// the low four bits of the packet header, so a uint16_t stores the complete +// 0..15 allowlist without dynamic allocation. +namespace MQTTPacketFilter { + +static const uint8_t kMinPacketType = 0; +static const uint8_t kMaxPacketType = 15; +static const uint16_t kAllPacketTypes = 0xFFFFu; +// "0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15" plus the terminator. +static const size_t kFilterTextSize = 38; + +inline bool isAsciiSpace(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +inline bool tokenEquals(const char* begin, size_t len, const char* token) { + return token != nullptr && strlen(token) == len && memcmp(begin, token, len) == 0; +} + +// Payload-type spellings accepted alongside the decimal form, mirroring the +// PAYLOAD_TYPE_* names in src/Packet.h. Types 12-14 are reserved upstream and +// have no name, so they stay reachable only by number. +struct NamedPacketType { + const char* name; + uint8_t type; +}; + +inline const NamedPacketType* namedPacketTypes(size_t* count_out) { + static const NamedPacketType kNames[] = { + {"req", 0}, {"response", 1}, {"txt_msg", 2}, {"ack", 3}, + {"advert", 4}, {"grp_txt", 5}, {"grp_data", 6}, {"anon_req", 7}, + {"path", 8}, {"trace", 9}, {"multipart", 10}, {"control", 11}, + {"raw_custom", 15}, + }; + if (count_out != nullptr) *count_out = sizeof(kNames) / sizeof(kNames[0]); + return kNames; +} + +// Resolve one already-trimmed list entry to a payload type. Decimal and named +// spellings are interchangeable within a list; "all"/"none" are whole-value +// keywords and deliberately do not resolve here, so "all,2" stays invalid. +inline bool resolveToken(const char* begin, size_t len, uint8_t* type_out) { + if (begin == nullptr || type_out == nullptr || len == 0) return false; + + if (*begin >= '0' && *begin <= '9') { + unsigned value = 0; + for (size_t i = 0; i < len; ++i) { + if (begin[i] < '0' || begin[i] > '9') return false; + value = value * 10u + static_cast(begin[i] - '0'); + if (value > kMaxPacketType) return false; + } + *type_out = static_cast(value); + return true; + } + + size_t name_count = 0; + const NamedPacketType* names = namedPacketTypes(&name_count); + for (size_t i = 0; i < name_count; ++i) { + if (tokenEquals(begin, len, names[i].name)) { + *type_out = names[i].type; + return true; + } + } + return false; +} + +// Parse an allowlist value. Empty input means "all" so WebConfig can clear a +// field and retain the same backwards-compatible default as an older +// /mqtt_prefs file. Keywords and type names are deliberately lowercase; "all" +// and "none" cannot be mixed into a list. Entries may be decimal (0..15) or +// named, may carry surrounding ASCII whitespace, and may be repeated. +inline bool parse(const char* input, uint16_t* mask_out) { + if (input == nullptr || mask_out == nullptr) return false; + + const char* begin = input; + while (*begin && isAsciiSpace(*begin)) begin++; + const char* end = begin + strlen(begin); + while (end > begin && isAsciiSpace(end[-1])) end--; + + const size_t len = static_cast(end - begin); + if (len == 0 || tokenEquals(begin, len, "all")) { + *mask_out = kAllPacketTypes; + return true; + } + if (tokenEquals(begin, len, "none")) { + *mask_out = 0; + return true; + } + + uint16_t parsed = 0; + const char* cursor = begin; + for (;;) { + const char* comma = cursor; + while (comma < end && *comma != ',') comma++; + + const char* token_begin = cursor; + const char* token_end = comma; + while (token_begin < token_end && isAsciiSpace(*token_begin)) token_begin++; + while (token_end > token_begin && isAsciiSpace(token_end[-1])) token_end--; + + uint8_t type = 0; + if (!resolveToken(token_begin, static_cast(token_end - token_begin), &type)) { + return false; + } + parsed |= static_cast(1u << type); + + if (comma >= end) break; + cursor = comma + 1; + } + + *mask_out = parsed; + return true; +} + +// Format masks deterministically for CLI/API output. Subsets are emitted in +// ascending order; the two useful extremes use concise keywords. +inline bool format(uint16_t mask, char* output, size_t output_size) { + if (output == nullptr || output_size == 0) return false; + output[0] = '\0'; + + const char* keyword = nullptr; + if (mask == kAllPacketTypes) keyword = "all"; + else if (mask == 0) keyword = "none"; + if (keyword != nullptr) { + const size_t len = strlen(keyword); + if (output_size <= len) return false; + memcpy(output, keyword, len + 1); + return true; + } + + char formatted[kFilterTextSize]; + size_t pos = 0; + bool first = true; + for (uint8_t type = kMinPacketType; type <= kMaxPacketType; ++type) { + if ((mask & static_cast(1u << type)) == 0) continue; + if (!first) formatted[pos++] = ','; + if (type >= 10) formatted[pos++] = '1'; + formatted[pos++] = static_cast('0' + (type % 10)); + first = false; + } + formatted[pos] = '\0'; + + if (output_size <= pos) return false; + memcpy(output, formatted, pos + 1); + return true; +} + +// The keyword shortcut above means format() never actually emits the whole +// 0..15 list, but the emit loop is written for every type, so the internal +// buffer must still hold it: 22 digits + 15 separators + NUL. +static_assert(kFilterTextSize >= 38, "filter text buffer must hold the full 0..15 list"); + +inline bool allows(uint16_t mask, uint8_t packet_type) { + return packet_type <= kMaxPacketType && + (mask & static_cast(1u << packet_type)) != 0; +} + +// How many types a mask allows. Lets a length-constrained reply fall back to +// "N/16" instead of a clipped list, which would read as a different allowlist. +inline uint8_t countTypes(uint16_t mask) { + uint8_t total = 0; + for (uint8_t type = kMinPacketType; type <= kMaxPacketType; ++type) { + if ((mask & static_cast(1u << type)) != 0) total++; + } + return total; +} + +// Conservative "could any configured broker want this type?" mask, used to +// reject a packet before it is copied into the publish queue. Disabled slots +// contribute nothing; a slot whose topic style later turns out not to support +// the publication is still counted here, so the gate never drops a packet the +// per-slot pass would have published. +inline uint16_t enabledUnion(const uint16_t* masks, const bool* enabled, size_t count) { + if (masks == nullptr) return 0; + uint16_t combined = 0; + for (size_t i = 0; i < count; ++i) { + if (enabled != nullptr && !enabled[i]) continue; + combined = static_cast(combined | masks[i]); + } + return combined; +} + +// True when every slot still carries the default all-types mask, i.e. nothing +// depends on the packet-filter tail of /mqtt_prefs being written. +inline bool allMasksDefault(const uint16_t* masks, size_t count) { + if (masks == nullptr) return true; + for (size_t i = 0; i < count; ++i) { + if (masks[i] != kAllPacketTypes) return false; + } + return true; +} + +// Stage one of the publish gate: everything decidable before a packet is +// serialised or a topic is built. Cheap enough to run per packet per slot. +inline bool slotCandidate(bool slot_enabled, uint16_t mask, uint8_t packet_type) { + return slot_enabled && allows(mask, packet_type); +} + +// The complete gate, once the slot's topic support is known. Eligibility +// deliberately excludes connection state: a temporarily disconnected broker +// that is configured for this type still requires the shared queue's existing +// bounded retry policy. +// +// eligiblePacketSlots() calls this directly, using slotCandidate() first only +// to skip the topic build for slots the mask already rejects. A slot that +// passes both therefore has its topic built again in the publish loop; that +// second build is the accepted cost of not carrying six 128-byte topics on the +// MQTT task stack. +inline bool slotEligible(bool slot_enabled, bool topic_supported, + uint16_t mask, uint8_t packet_type) { + return slotCandidate(slot_enabled, mask, packet_type) && topic_supported; +} + +// A fully filtered/topic-incompatible packet is intentionally complete. Once +// any eligible target exists, at least one actual publish must succeed. +inline bool publishComplete(bool has_eligible_target, bool any_publish_succeeded) { + return any_publish_succeeded || !has_eligible_target; +} + +} // namespace MQTTPacketFilter diff --git a/src/helpers/MQTTPayloadBuilder.cpp b/src/helpers/MQTTPayloadBuilder.cpp index f68ae044..06634625 100644 --- a/src/helpers/MQTTPayloadBuilder.cpp +++ b/src/helpers/MQTTPayloadBuilder.cpp @@ -186,47 +186,110 @@ int MQTTPayloadBuilder::buildRawMessage( return serializeComplete(root, buffer, buffer_size); } +static JsonArray buildNeighborsMessageBase( + JsonDocument& doc, + const char* origin, + const char* origin_id, + const char* timestamp, + const char* self_scopes, + const char* self_default_scope, + int total_neighbors, + int queried_neighbors, + bool truncated +) { + doc.clear(); + JsonObject root = doc.to(); + root["timestamp"] = timestamp; + root["origin"] = origin; + root["origin_id"] = origin_id; + if (total_neighbors >= 0) { + root["total_neighbors"] = total_neighbors; + root["queried_neighbors"] = queried_neighbors >= 0 ? queried_neighbors : total_neighbors; + root["truncated"] = truncated; + } + + JsonObject self = root["self"].to(); + self["scopes"] = self_scopes ? self_scopes : ""; + self["default_scope"] = self_default_scope ? self_default_scope : ""; + return root["neighbors"].to(); +} + +static void addNeighborsMessageEntry( + JsonArray& arr, + const MQTTPayloadBuilder::NeighborsMessageEntry& neighbor +) { + JsonObject nb = arr.add(); + nb["pubkey"] = neighbor.pubkey_hex; + nb["snr"] = neighbor.snr; + if (neighbor.heard_unknown) { + nb["heard_secs_ago"] = nullptr; // age unknown, not zero + } else { + nb["heard_secs_ago"] = neighbor.heard_secs_ago; + } + nb["scopes"] = neighbor.scopes ? neighbor.scopes : ""; + nb["status"] = neighbor.status; +} + +size_t MQTTPayloadBuilder::measureNeighborsMessageBase( + const char* origin, + const char* origin_id, + const char* timestamp, + const char* self_scopes, + const char* self_default_scope, + int total_neighbors +) { + JsonDocument doc; + buildNeighborsMessageBase( + doc, origin, origin_id, timestamp, self_scopes, self_default_scope, + total_neighbors, total_neighbors, false); + return measureJson(doc); +} + +size_t MQTTPayloadBuilder::measureNeighborsMessageEntry( + const NeighborsMessageEntry& neighbor +) { + JsonDocument doc; + JsonArray arr = doc.to(); + addNeighborsMessageEntry(arr, neighbor); + JsonObject entry = arr[0]; + return measureJson(entry); +} + int MQTTPayloadBuilder::buildNeighborsMessage( JsonDocument& doc, const char* origin, const char* origin_id, const char* timestamp, const char* self_scopes, + const char* self_default_scope, const NeighborsMessageEntry* neighbors, int neighbor_count, char* buffer, - size_t buffer_size + size_t buffer_size, + int total_neighbors, + int queried_neighbors, + bool truncated ) { if (!buffer || buffer_size == 0) return 0; - doc.clear(); - JsonObject root = doc.to(); - root["timestamp"] = timestamp; - root["origin"] = origin; - root["origin_id"] = origin_id; - - JsonObject self = root["self"].to(); - self["scopes"] = self_scopes ? self_scopes : ""; - - JsonArray arr = root["neighbors"].to(); - if (measureJson(root) >= buffer_size) return 0; + JsonArray arr = buildNeighborsMessageBase( + doc, origin, origin_id, timestamp, self_scopes, self_default_scope, + total_neighbors, queried_neighbors, truncated); + if (doc.overflowed() || arr.isNull() || measureJson(doc) >= buffer_size) return 0; for (int i = 0; i < neighbor_count; i++) { - JsonObject nb = arr.add(); - nb["pubkey"] = neighbors[i].pubkey_hex; - nb["snr"] = neighbors[i].snr; - nb["heard_secs_ago"] = neighbors[i].heard_secs_ago; - nb["scopes"] = neighbors[i].scopes ? neighbors[i].scopes : ""; - nb["status"] = neighbors[i].status; + addNeighborsMessageEntry(arr, neighbors[i]); + if (doc.overflowed()) return 0; // Entries arrive ordered most- to least-useful. Stop as soon as the next // one would fill the fixed publish buffer, dropping the remaining tail so // document growth stays bounded. - if (measureJson(root) >= buffer_size) { + if (measureJson(doc) >= buffer_size) { arr.remove(arr.size() - 1); + if (total_neighbors >= 0) doc["truncated"] = true; break; } } - return serializeComplete(root, buffer, buffer_size); + return serializeComplete(doc.as(), buffer, buffer_size); } diff --git a/src/helpers/MQTTPayloadBuilder.h b/src/helpers/MQTTPayloadBuilder.h index 931601d5..24e56124 100644 --- a/src/helpers/MQTTPayloadBuilder.h +++ b/src/helpers/MQTTPayloadBuilder.h @@ -75,6 +75,11 @@ public: uint32_t heard_secs_ago; const char* scopes; const char* status; + // True renders heard_secs_ago as JSON null, for a neighbour whose stored + // stamp cannot yield an age. No default initializer: the struct stays an + // aggregate for the device toolchain, and a zeroed tail means "age known", + // so size-measurement callers keep reserving the widest numeric value. + bool heard_unknown; }; // Build neighbors-table JSON for the meshcore/{iata}/{device}/neighbors topic. @@ -86,9 +91,26 @@ public: const char* origin_id, const char* timestamp, const char* self_scopes, + const char* self_default_scope, const NeighborsMessageEntry* neighbors, int neighbor_count, char* buffer, - size_t buffer_size + size_t buffer_size, + int total_neighbors = -1, + int queried_neighbors = -1, + bool truncated = false ); + + // Exact serialized-size components used by paced neighbor discovery. The + // base reserves the largest progress metadata values for this snapshot; + // callers add each entry size plus one byte for commas after the first. + static size_t measureNeighborsMessageBase( + const char* origin, + const char* origin_id, + const char* timestamp, + const char* self_scopes, + const char* self_default_scope, + int total_neighbors + ); + static size_t measureNeighborsMessageEntry(const NeighborsMessageEntry& neighbor); }; diff --git a/src/helpers/MQTTPrefsCodec.h b/src/helpers/MQTTPrefsCodec.h index 715d33f2..cf259f29 100644 --- a/src/helpers/MQTTPrefsCodec.h +++ b/src/helpers/MQTTPrefsCodec.h @@ -2,6 +2,7 @@ #include +#include "MQTTPacketFilter.h" #include "MQTTPrefsStorage.h" #ifdef WITH_MQTT_BRIDGE @@ -37,23 +38,44 @@ struct DecodePlan { static const size_t kV1PreObserverPayloadSize = MQTT_PREFS_V1_PRE_OBSERVER_PAYLOAD_SIZE; static const size_t kV1PreNeighborsPayloadSize = MQTT_PREFS_V1_PRE_NEIGHBORS_PAYLOAD_SIZE; +static const size_t kV1PreFilterPayloadSize = MQTT_PREFS_V1_PRE_FILTER_PAYLOAD_SIZE; static const size_t kV1BaselinePayloadSize = MQTT_PREFS_V1_FULL_PAYLOAD_SIZE; static const size_t kEncodedSize = sizeof(MQTTPrefsHeader) + kV1BaselinePayloadSize; -inline MQTTPrefsHeader makeHeader() { +// Shortest payload length that still round-trips this configuration. +// +// The packet-filter tail is the only optional part of the current layout, and +// its default (all types) is exactly what a pre-filter decoder supplies for a +// missing tail. So a device whose filters are all default keeps writing the +// 2864-byte payload that pre-filter firmware can still read. That matters +// because /mqtt_prefs also carries the WiFi credentials: an unrecognised +// longer payload sends older firmware to defaults with no network, and it +// refuses to overwrite the file, so the node cannot be recovered over the air. +// Touching any filter opts that node into the longer payload -- a deliberate, +// operator-initiated trade rather than a side effect of upgrading. +inline size_t payloadLenFor(const MQTTPrefs& prefs) { + return MQTTPacketFilter::allMasksDefault(prefs.mqtt_slot_packet_filter, + MQTT_PREFS_SLOT_COUNT) + ? kV1PreFilterPayloadSize + : kV1BaselinePayloadSize; +} + +inline MQTTPrefsHeader makeHeader(size_t payload_len) { MQTTPrefsHeader header; memcpy(header.magic, MQTT_PREFS_MAGIC, sizeof(header.magic)); header.version = MQTT_PREFS_VERSION; - header.payload_len = static_cast(kV1BaselinePayloadSize); + header.payload_len = static_cast(payload_len); return header; } inline size_t encode(const MQTTPrefs& prefs, uint8_t* output, size_t output_size) { - if (output == nullptr || output_size < kEncodedSize) return 0; - const MQTTPrefsHeader header = makeHeader(); + const size_t payload_len = payloadLenFor(prefs); + const size_t encoded_size = sizeof(MQTTPrefsHeader) + payload_len; + if (output == nullptr || output_size < encoded_size) return 0; + const MQTTPrefsHeader header = makeHeader(payload_len); memcpy(output, &header, sizeof(header)); - memcpy(output + sizeof(header), &prefs, sizeof(prefs)); - return kEncodedSize; + memcpy(output + sizeof(header), &prefs, payload_len); + return encoded_size; } inline bool isMagicPrefix(const uint8_t* input, size_t available) { @@ -92,14 +114,35 @@ inline DecodePlan classify(const uint8_t* prefix, size_t prefix_read, size_t fil if (header.payload_len != payload_available) { return corruptPlan(); } - // A same-version append is still unknown to this binary. Holding the - // file prevents a downgrade from discarding it on the next CLI save. + // A longer same-version payload was written by a later build that + // appended fields. Within a version tag the layout is append-only, so + // every byte this binary knows is present and correctly positioned -- read + // the baseline prefix and ignore the tail. + // + // This is the downgrade contract, and it is deliberately asymmetric: + // refusing the file would cost the operator WiFi credentials and every + // broker slot (a node with no network and no portal, recoverable only + // over serial), whereas reading it costs only the settings the newer + // build added. Losing a later feature's settings is the acceptable half. + // + // The tail survives until something actually writes: saveMQTTPrefs() + // rewrites at this binary's own length, so a rollback that changes no + // observer setting and is later rolled forward keeps the newer fields + // intact. Only an explicit `set` while downgraded drops them. + // + // A layout change that is NOT a pure append must bump MQTT_PREFS_VERSION; + // the version check above is what makes this rule safe. if (header.payload_len > kV1BaselinePayloadSize) { - return {Source::UnsupportedVersion, false, true, false, 0}; + return {Source::Current, false, false, true, kV1BaselinePayloadSize}; } if (header.payload_len == kV1BaselinePayloadSize) { return {Source::Current, false, false, true, kV1BaselinePayloadSize}; } + if (header.payload_len == kV1PreFilterPayloadSize) { + // Written before the per-slot packet-filter tail. Defaults supply an + // all-types mask for every slot. + return {Source::Current, false, false, true, kV1PreFilterPayloadSize}; + } if (header.payload_len == kV1PreNeighborsPayloadSize) { // Written by observer/webconfig firmware before the neighbors tail // existed. The observer fields ARE present; only the neighbors tail is diff --git a/src/helpers/MQTTPrefsStorage.h b/src/helpers/MQTTPrefsStorage.h index 4d13e830..463a6057 100644 --- a/src/helpers/MQTTPrefsStorage.h +++ b/src/helpers/MQTTPrefsStorage.h @@ -117,6 +117,11 @@ struct MQTTPrefs { // interchangeable (see the offsetof static_asserts below). uint8_t mqtt_neighbors_enabled; uint32_t mqtt_neighbors_interval; + + // Per-slot payload-type allow masks. Bit N controls MeshCore packet type N + // for both packets and raw MQTT topics. Appended so older v1 payloads load + // with the default all-types masks intact. + uint16_t mqtt_slot_packet_filter[MQTT_PREFS_SLOT_COUNT]; }; // Neighbor discovery is scheduled with the wrap-safe millis() helpers, whose @@ -129,14 +134,28 @@ static const uint32_t MQTT_NEIGHBORS_MIN_INTERVAL_MS = MQTT_NEIGHBORS_MIN_INTERV static const uint32_t MQTT_NEIGHBORS_MAX_INTERVAL_MS = MQTT_NEIGHBORS_MAX_INTERVAL_HOURS * 3600000UL; static const uint32_t MQTT_NEIGHBORS_DEFAULT_INTERVAL_MS = MQTT_NEIGHBORS_DEFAULT_INTERVAL_HOURS * 3600000UL; -// Version-1 has three payload layouts this firmware can decode. Never infer a -// compatible payload from an arbitrary shorter size: raw prefs have no checksum. +// Version-1 has four payload layouts this firmware can decode. Never infer a +// compatible payload from an arbitrary SHORTER size: raw prefs have no +// checksum, so a short length has to match a boundary that was really shipped. +// +// A LONGER v1 payload is different and is always readable: within a version tag +// the layout is append-only, so a later build's file still starts with this +// binary's exact baseline. classify() reads that prefix and ignores the tail +// rather than rejecting the file -- see the downgrade contract there. Any change +// that is not a pure append MUST bump MQTT_PREFS_VERSION instead. // - PRE_OBSERVER (2736): stops before the observer tail (snmp_*/alert_*). // - PRE_NEIGHBORS (2860): full observer tail, no neighbors fields yet. -// - FULL (2864): current baseline, with the neighbors tail. +// - PRE_FILTER (2864): neighbors tail, no per-slot packet filters. +// - FULL (2876): current baseline, with six uint16_t filter masks. +// +// FULL is the maximum written, not the default: MQTTPrefsCodec::payloadLenFor() +// keeps emitting PRE_FILTER while every slot holds the all-types default, so a +// node that never touches a filter stays readable by pre-filter firmware. See +// the rollback note there -- /mqtt_prefs also carries the WiFi credentials. static const size_t MQTT_PREFS_V1_PRE_OBSERVER_PAYLOAD_SIZE = 2736; static const size_t MQTT_PREFS_V1_PRE_NEIGHBORS_PAYLOAD_SIZE = 2860; -static const size_t MQTT_PREFS_V1_FULL_PAYLOAD_SIZE = 2864; +static const size_t MQTT_PREFS_V1_PRE_FILTER_PAYLOAD_SIZE = 2864; +static const size_t MQTT_PREFS_V1_FULL_PAYLOAD_SIZE = 2876; // /mqtt_prefs starts with a self-describing 8-byte header. Headerless files // are deployed legacy layouts and continue to be distinguished by size. @@ -266,6 +285,8 @@ static_assert(offsetof(MQTTPrefs, mqtt_neighbors_enabled) == 2857, "neighbors enable flag must sit at the flex-compatible offset"); static_assert(offsetof(MQTTPrefs, mqtt_neighbors_interval) == MQTT_PREFS_V1_PRE_NEIGHBORS_PAYLOAD_SIZE, "neighbors interval offset must equal the pre-neighbors payload size"); +static_assert(offsetof(MQTTPrefs, mqtt_slot_packet_filter) == MQTT_PREFS_V1_PRE_FILTER_PAYLOAD_SIZE, + "packet filters must begin at the pre-filter payload boundary"); static_assert(sizeof(OldMQTTPrefs) == 472, "frozen pre-slot /mqtt_prefs layout changed"); static_assert(sizeof(PreWifiPowerOldMQTTPrefs) == 472, "frozen pre-WiFi-power /mqtt_prefs layout changed"); static_assert(offsetof(OldMQTTPrefs, wifi_power_save) == 144, diff --git a/src/helpers/MQTTPresets.h b/src/helpers/MQTTPresets.h index b0689b8c..f8362b57 100644 --- a/src/helpers/MQTTPresets.h +++ b/src/helpers/MQTTPresets.h @@ -33,7 +33,7 @@ enum MQTTAuthType : uint8_t { enum MQTTTopicStyle : uint8_t { MQTT_TOPIC_MESHCORE, // meshcore/{iata}/{device_id}/{status|packets|raw} - MQTT_TOPIC_MESHRANK, // meshrank/uplink/{token}/{device_id}/packets (packets only) + MQTT_TOPIC_MESHRANK, // meshrank/uplink/{token}/{device_id}/{type} (no raw) }; struct MQTTPresetDef { @@ -79,7 +79,7 @@ static inline bool mqttPresetNeedsSlotCredentials(const MQTTPresetDef* preset) { } // Number of built-in presets -static const int MQTT_PRESET_COUNT = 29; +static const int MQTT_PRESET_COUNT = 34; // Keep the certificate and preset tables in one translation unit. Defining // these as header-local constants created a complete flash copy in every MQTT @@ -165,13 +165,15 @@ extern const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = { { "ctmesh", "mqtt://mqtt.ctmesh.org:1883", nullptr, nullptr, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 60, "meshdev", "large4cats"}, { "chimesh", "wss://mqtt.chimesh.org:443", "mqtt.chimesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "meshat.se", "wss://meshcore-mqtt.meshat.se:443", "meshcore-mqtt.meshat.se", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, - { "eastidahomesh", "wss://broker.eastidahomesh.net:443", nullptr, ISRG_ROOT_X1, MQTT_AUTH_NONE, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + { "eastidahomesh", "mqtt://live.eastidahomesh.com:1883", nullptr, nullptr, MQTT_AUTH_NONE, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "coloradomesh", "wss://mqtt.meshcore.coloradomesh.org:443","mqtt.meshcore.coloradomesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "dutchmeshcore-1", "wss://collector1.dutchmeshcore.nl:443/mqtt", "collector1.dutchmeshcore.nl", GTS_ROOT_R4, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "dutchmeshcore-2", "wss://collector2.dutchmeshcore.nl:443/mqtt", "collector2.dutchmeshcore.nl", GTS_ROOT_R4, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "meshcore-ca-1", "wss://mqtt1.meshcore.ca:443/mqtt", "mqtt1.meshcore.ca", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "meshcore-ca-2", "wss://mqtt2.meshcore.ca:443/mqtt", "mqtt2.meshcore.ca", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "meshcore-fi", "wss://mc-mqtt.meshcore.fi:443/", "mc-mqtt.meshcore.fi", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + { "okimesh-1", "wss://mqtt1.okimesh.org:9002/mqtt", "mqtt1.okimesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + { "okimesh-2", "wss://mqtt2.okimesh.org:9002/mqtt", "mqtt2.okimesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "inwmesh", "mqtts://scope.inwmesh.org:8883", nullptr, ISRG_ROOT_X1, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "bostonmesh", "wss://mqttmc01.bostonme.sh:443/mqtt", "mqttmc01.bostonme.sh", GTS_ROOT_R4, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "rflab", "wss://mqtt.rflab.io:443", "mqtt.rflab.io", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, @@ -183,6 +185,10 @@ extern const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = { { "mesh-chaun14", "mqtt://mqtt.mesh.chaun14.fr:1884", nullptr, nullptr, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 60, MQTT_USERPASS_USERNAME_PUBKEY, nullptr }, // LetsMesh-compatible JWT; TLS is Let's Encrypt (ISRG Root X1), not GTS. { "wcmesh", "wss://mqtt.wcmesh.com:443", "mqtt.wcmesh.com", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + { "atvirastinklas","wss://mqtt-mc.atvirastinklas.lt:443", "mqtt-mc.atvirastinklas.lt", GTS_ROOT_R4, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + // JWT token auth; LE Gen-Y ECDSA chain (YE2 -> Root YE -> X2) still anchors at ISRG Root X1. + { "gomesh", "wss://mqtt.gomesh.dev:443", "mqtt.gomesh.dev", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, + { "idahomesh", "wss://mqtt.idahomesh.org:443/mqtt", "mqtt.idahomesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, }; #else diff --git a/src/helpers/MQTTTopicRouter.h b/src/helpers/MQTTTopicRouter.h index 1340ce0f..d0ab8a2b 100644 --- a/src/helpers/MQTTTopicRouter.h +++ b/src/helpers/MQTTTopicRouter.h @@ -45,11 +45,12 @@ static inline bool mqttWriteTopic(char* buf, size_t buf_size, const char* format return written > 0 && (size_t)written < buf_size; } -// Build the complete topic for one publication. MeshRank is deliberately -// packets-only; status, raw, and neighbors are unsupported by the current -// broker contract (the type != PACKETS guard below rejects them all). -// MeshCore routes require a configured IATA and device id. Custom templates may -// omit either placeholder, so their individual values are allowed to be empty. +// Build the complete topic for one publication. MeshRank takes status, packets, +// and neighbors under meshrank/uplink/{token}/{device}/, using the same type +// suffixes as the MeshCore layout, and requires a per-slot token rather than an +// IATA. MeshCore routes require a configured IATA and device id. Custom +// templates may omit either placeholder, so their individual values are allowed +// to be empty. static inline bool mqttBuildPublicationTopic(MQTTTopicRouteStyle style, int type, const char* custom_template, const char* iata, const char* device, @@ -69,7 +70,9 @@ static inline bool mqttBuildPublicationTopic(MQTTTopicRouteStyle style, int type return mqttWriteTopic(buf, buf_size, "meshcore/%s/%s/%s", iata, device, type_name); case MQTT_ROUTE_MESHRANK: - if (type != MQTT_PUBLICATION_PACKETS || !token || token[0] == '\0' || + // Raw is deliberately withheld: highest-volume topic, and the broker does + // not consume it. observer-firmware still sends it -- keep this on merge. + if (type == MQTT_PUBLICATION_RAW || !token || token[0] == '\0' || !device || device[0] == '\0') { return false; } diff --git a/src/helpers/WebConfigBatch.h b/src/helpers/WebConfigBatch.h index f5c852a0..bbc11b5a 100644 --- a/src/helpers/WebConfigBatch.h +++ b/src/helpers/WebConfigBatch.h @@ -1,5 +1,6 @@ #pragma once +#include // size_t / NULL for the reply classifiers below #include // Fork-owned, dependency-free spec for the WebConfig "config batch / reboot / @@ -193,6 +194,110 @@ static inline uint32_t confirmSetupHandoffRebootAt(uint32_t now) { return scheduleAt(now, kSetupHandoffRebootConfirmMs); } +// -------------------------------------------------------------------------- +// CLI sequences (/api/cli). The terminal shares this one deferred-command slot +// with config saves rather than owning a second MAX_BATCH array: both drain on +// the loop task, both are single-slot, and a duplicate would cost ~8 KB of +// permanently resident RAM. Sharing also makes a save and a CLI run mutually +// exclusive, which they must be. +// +// Two things differ from a config save: +// 1. results stream. A save's results appear only when the whole batch is +// Done; a CLI read hands back whatever has executed so far, so a pasted +// sequence fills the terminal command by command. +// 2. the reboot is not requested by a `reboot` flag on the request but by the +// word `reboot` appearing in the sequence. It is deferred rather than +// executed, because Board::reboot() does not return and would take the node +// down before the client could read a single result. +// -------------------------------------------------------------------------- + +// Results returned by one read. Bounds the JSON document built on the +// async_tcp task; a longer sequence pages across successive reads. +static const int kCliResultPage = 8; + +static inline int cliPageCount(int from, int produced, int page) { + const int pending = produced - from; + if (pending <= 0) return 0; + return pending > page ? page : pending; +} + +// "done" means the client has been handed every result, not merely that +// execution finished: the last page may still be unread, and a client that +// stops polling at "done" would lose it. +static inline bool cliReadIsFinal(State state, int from, int page_count, int total) { + return state == State::Done && from + page_count >= total; +} + +// A trailing `reboot` is withheld when any command in the sequence failed, +// exactly as a config save's is. The operator asked for the reboot, but +// rebooting into a half-applied config -- over a link they may not get back -- +// is the worse failure, and the result body reports the refusal. +static inline bool cliRebootAllowed(bool has_reboot, bool all_ok) { + return has_reboot && all_ok; +} + +// CommonCLI has no single failure convention. Testing only for an "Err" prefix +// let five other shapes through as success -- including "Unknown command" and +// "unknown config: x", the two an operator hits most -- which coloured them +// green AND let a queued reboot proceed after them. +// +// Every shape below is a literal from CommonCLI.cpp / CommonCLI_Observer.cpp. +// This list is the fragile part of the design: a new failure string added there +// is silently a success here. Which is exactly why it must not be what decides +// whether to reboot -- see cliReplyConfirmsWrite. +static inline bool cliReplyIsFailure(const char* r) { + if (r == NULL || r[0] == 0) return false; // empty is normalised to "OK" + static const char* const kPrefixes[] = { + "Err", "ERR", "err", // "Err - ", "ERR: ", "Error: " + "(ERR", // "(ERR: clock cannot go backwards)" + "Unknown command", + "unknown config", + "??", // "??: " from the get fallthrough + "Can't find", // "Can't find GPS" + }; + for (size_t i = 0; i < sizeof(kPrefixes) / sizeof(kPrefixes[0]); i++) { + const char* p = kPrefixes[i]; + size_t n = 0; + while (p[n]) n++; + bool match = true; + for (size_t j = 0; j < n; j++) { + if (r[j] != p[j]) { match = false; break; } + } + if (match) return true; + } + // "File system erase: Err" reports the failure at the END of the reply. + for (size_t i = 0; r[i]; i++) { + if (r[i] == ':' && r[i + 1] == ' ' && r[i + 2] == 'E' && r[i + 3] == 'r' && + r[i + 4] == 'r') return true; + } + return false; +} + +// Whether a command's reply is allowed to gate the deferred reboot. +// +// Only writes are, and only writes have a reply convention worth trusting: +// every setter answers with an "OK" prefix. Diagnostics do not -- `memory` +// answers "Free: ...", a getter answers "> value" -- so letting them gate would +// mean guessing, and guessing wrong here either strands the operator (a +// harmless `memory` blocks their reboot) or reboots into a config that did not +// apply. The question the gate exists to answer is narrower than "did anything +// fail": it is "did every setting I asked for actually take". +static inline bool cliReplyGatesReboot(const char* cmd) { + if (cmd == NULL) return false; + const char* set = "set "; + const char* pwd = "password "; + bool is_set = true, is_pwd = true; + for (int i = 0; i < 4; i++) if (cmd[i] != set[i]) { is_set = false; break; } + for (int i = 0; i < 9; i++) if (cmd[i] != pwd[i]) { is_pwd = false; break; } + return is_set || is_pwd; +} + +// A write took effect iff its reply starts with "OK" -- the one convention every +// setter in CommonCLI actually keeps. +static inline bool cliWriteSucceeded(const char* reply) { + return reply != NULL && reply[0] == 'O' && reply[1] == 'K'; +} + // -------------------------------------------------------------------------- // Reboot fire (.cpp:262-265) and isRebootPending (.cpp:70-74). // -------------------------------------------------------------------------- diff --git a/src/helpers/WebConfigKeys.h b/src/helpers/WebConfigKeys.h index a0c7a673..826c23b7 100644 --- a/src/helpers/WebConfigKeys.h +++ b/src/helpers/WebConfigKeys.h @@ -31,6 +31,7 @@ static const char* const WC_ALLOWED_SET_KEYS[] = { }; static const char* const WC_ALLOWED_SLOT_KEYS[] = { "preset", "server", "port", "username", "password", "token", "topic", "audience", + "filter", }; // True when `key` is a well-formed per-slot key ("mqttN." with N in @@ -82,6 +83,28 @@ static inline bool wcIsSecretKey(const char* key) { return false; } +// CommonCLI answers a secret getter in plaintext only for the serial console +// (sender_timestamp 0) and masks it for remote callers. The web CLI executes +// with sender_timestamp 0 -- that is what makes `erase`, `stats-*` and `set freq` +// reachable -- so it would otherwise inherit the serial console's plaintext +// answers for an HTTP request. This says which `get` commands must be masked +// back down, restoring the distinction for a caller not at the serial port. +// +// Writing these has always been possible from the portal; reading them never +// was, because handleConfigGet masks them (wcIsSecretKey). The two are different +// capabilities: replacing a WiFi password does not reveal the current one, and +// replacing an identity does not reveal the existing private key. +static inline bool wcIsSecretReadCommand(const char* cmd) { + if (strncmp(cmd, "get ", 4) != 0) return false; + const char* key = cmd + 4; + while (*key == ' ') key++; + if (strcmp(key, "prv.key") == 0) return true; // this node's identity + if (strcmp(key, "guest.password") == 0) return true; + if (strcmp(key, "alert.psk") == 0) return true; + if (strcmp(key, "bridge.secret") == 0) return true; + return wcIsSecretKey(key); // wifi.pwd, mqttN.password, mqttN.token +} + // Browser-generated request IDs are exactly eight random bytes encoded as // hexadecimal. Keeping the grammar deliberately small makes the ID safe to // echo in JSON/logs and prevents an empty or truncated ID from weakening the diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index f499982f..cd1947fb 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -351,10 +351,17 @@ void MQTTBridge::formatMqttStatsReply(char* buf, size_t bufsize) { // drops=/: outbox-cap drops vs. memory-pressure skips. int pos = 0; - replyAppendf(buf, bufsize, &pos, "> Free=%d Max=%d q:%d/%d Outbox=%u drops=%lu/%d |", + replyAppendf(buf, bufsize, &pos, "> Free=%d Max=%d q:%d/%d Outbox=%u drops=%lu/%d", (int)ESP.getFreeHeap(), (int)ESP.getMaxAllocHeap(), q, MAX_QUEUE_SIZE, (unsigned)outbox_total, outbox_drops, b->_skipped_publishes); + // filt=: packets the per-slot type filters rejected before the queue. + // Omitted while zero so an unfiltered node's reply keeps its former length -- + // the per-slot list below is what usually gets clamped away first. + if (b->_filtered_packets > 0) { + replyAppendf(buf, bufsize, &pos, " filt=%lu", b->_filtered_packets); + } + replyAppendf(buf, bufsize, &pos, " |"); for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { if (!b->_slots[i].enabled || !b->_slots[i].client) continue; replyAppendf(buf, bufsize, &pos, " s%d=%lu/%lu", i + 1, @@ -392,6 +399,9 @@ bool MQTTBridge::getSlotStatusSnapshot(int slot_index, SlotStatusSnapshot* out) out->has_publish_counts = true; out->publish_ok = slot.client ? slot.client->getPublishOk() : 0; out->publish_err = slot.client ? slot.client->getPublishErr() : 0; + // Lets the portal show why a healthy slot is quiet. + out->filter_mask = b->_obs ? b->_obs->mqtt_slot_packet_filter[slot_index] + : MQTTPacketFilter::kAllPacketTypes; return true; } @@ -498,6 +508,17 @@ void MQTTBridge::formatSlotDiagReply(char* buf, size_t bufsize, int slot_index) // replyAppendf clamps pos on every call, so the chained appends below can't // walk past the reply buffer even if the accumulated text exceeds it (A1). + // A non-default filter is the one healthy-looking reason for a slot to stop + // publishing, so it has to appear -- but appended last. replyAppendf clamps at + // the 160-byte reply, and an error tail (TLS + mbedTLS + errno + age) can + // already reach ~117 chars, so putting the filter first would push the + // operator's diagnostic detail off the end of a failing slot's line. + const uint16_t filter_mask = b->_obs ? b->_obs->mqtt_slot_packet_filter[slot_index] + : MQTTPacketFilter::kAllPacketTypes; + char filter_text[MQTTPacketFilter::kFilterTextSize]; + const bool show_filter = filter_mask != MQTTPacketFilter::kAllPacketTypes && + MQTTPacketFilter::format(filter_mask, filter_text, sizeof(filter_text)); + int pos = 0; replyAppendf(buf, bufsize, &pos, "> mqtt%d: %s", slot_index + 1, state); if (slot.disconnect_count > 0) { @@ -508,14 +529,10 @@ void MQTTBridge::formatSlotDiagReply(char* buf, size_t bufsize, int slot_index) } } - // If connected with no errors, we're done + // Connected with no errors: nothing more to say about the connection. if (slot.connected && slot.last_error_time == 0) { replyAppendf(buf, bufsize, &pos, ", no errors"); - return; - } - - // Show last error if we have one - if (slot.last_error_time > 0) { + } else if (slot.last_error_time > 0) { // TLS error with human-friendly description if (slot.last_tls_err != 0) { const char* desc = tlsErrorStr(slot.last_tls_err); @@ -545,6 +562,26 @@ void MQTTBridge::formatSlotDiagReply(char* buf, size_t bufsize, int slot_index) } else if (!slot.connected) { replyAppendf(buf, bufsize, &pos, ", no error info"); } + + // Appended last so it never displaces connection diagnostics. replyAppendf + // clamps rather than overflows, but a clipped type list is worse than no + // list: "...,13,14," parses as a real, different allowlist, and this is the + // one line an operator reads to find out why a slot is quiet. So the exact + // text is only emitted when it fits whole; otherwise fall back to a count, + // which cannot be misread. `get mqttN.filter` always has the exact value. + if (show_filter) { + static const int kFilterLabelLen = (int)sizeof(", filter:") - 1; + const int remaining = pos < (int)bufsize ? (int)bufsize - 1 - pos : 0; + const uint8_t allowed = MQTTPacketFilter::countTypes(filter_mask); + const int compact_len = kFilterLabelLen + (allowed >= 10 ? 5 : 4); // "N/16" + if (kFilterLabelLen + (int)strlen(filter_text) <= remaining) { + replyAppendf(buf, bufsize, &pos, ", filter:%s", filter_text); + } else if (compact_len <= remaining) { + replyAppendf(buf, bufsize, &pos, ", filter:%u/16", (unsigned)allowed); + } + // Neither fits: the slot is reporting so much error detail that the filter + // is the least useful field on the line. Omit it rather than mislead. + } } // Bounded cooperative-stop timeout for end() (see MQTTLifecycle::Coordinator). @@ -718,18 +755,23 @@ void MQTTBridge::allocateRuntimeBuffers() { _publish_json_buffer, PUBLISH_JSON_BUFFER_SIZE, psram_malloc)); _status_json_buffer = static_cast(MQTTRuntimeBufferLifecycle::allocateIfMissing( _status_json_buffer, STATUS_JSON_BUFFER_SIZE, psram_malloc)); -#if defined(WITH_MQTT_NEIGHBORS) - // Persistent neighbors JSON buffer. Unlike status/packet there is no stack - // fallback: the feature is PSRAM-gated, so a nullptr simply disables publishing - // (requestPublishNeighbors/publishNeighbors both no-op on nullptr). - _neighbors_json_buffer = static_cast(MQTTRuntimeBufferLifecycle::allocateIfMissing( - _neighbors_json_buffer, NEIGHBORS_JSON_BUFFER_SIZE, psram_malloc)); -#endif MQTT_DEBUG_PRINTLN("Runtime buffers: raw=%s publish=%s status=%s", _last_raw_data ? "PSRAM" : "unavailable", _publish_json_buffer ? "PSRAM" : "stack fallback", _status_json_buffer ? "PSRAM" : "stack fallback"); #endif + +#if defined(WITH_MQTT_NEIGHBORS) + // Persistent neighbors JSON buffer, heap-allocated on every board: too large to + // keep inline in the bridge object the way the non-PSRAM status/packet buffers + // are. psram_malloc() falls back to internal DRAM, so this works without PSRAM. + // Unlike status/packet there is no stack fallback -- a nullptr simply disables + // publishing (requestPublishNeighbors/publishNeighbors both no-op on nullptr). + _neighbors_json_buffer = static_cast(MQTTRuntimeBufferLifecycle::allocateIfMissing( + _neighbors_json_buffer, NEIGHBORS_JSON_BUFFER_SIZE, psram_malloc)); + MQTT_DEBUG_PRINTLN("Neighbors buffer: %s", + _neighbors_json_buffer ? "ready" : "unavailable"); +#endif } void MQTTBridge::releaseRuntimeBuffers() { @@ -740,13 +782,15 @@ void MQTTBridge::releaseRuntimeBuffers() { _publish_json_buffer, psram_free)); _status_json_buffer = static_cast(MQTTRuntimeBufferLifecycle::release( _status_json_buffer, psram_free)); + #endif + #if defined(WITH_MQTT_NEIGHBORS) + // Paired with the unconditional allocation in allocateRuntimeBuffers(). _neighbors_json_buffer = static_cast(MQTTRuntimeBufferLifecycle::release( _neighbors_json_buffer, psram_free)); _neighbors_publish_len = 0; _neighbors_publish_pending.store(false, std::memory_order_release); #endif - #endif // Never pair a newly allocated raw buffer with metadata from a prior bridge // run. This also makes non-PSRAM restarts discard their stale raw cache. @@ -2322,7 +2366,7 @@ void MQTTBridge::publishStatusToSlot(int index) { // Build per-slot topic (handles IATA check for meshcore, token check for meshrank) char status_topic[128]; if (!buildTopicForSlot(index, MSG_STATUS, status_topic, sizeof(status_topic))) { - return; // Slot doesn't support status (e.g., meshrank) or missing required config + return; // Slot is missing required topic configuration } // Reuse pre-allocated buffer to avoid heap alloc/free churn under memory pressure. @@ -2804,15 +2848,16 @@ void MQTTBridge::loop() { void MQTTBridge::onPacketReceived(mesh::Packet *packet) { if (!_initialized || !_obs->mqtt_packets_enabled || !_obs->mqtt_rx_enabled) return; - // Check if we have any enabled slots to send to - bool has_valid_slots = false; - for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { - if (_slots[i].enabled && _slots[i].client) { - has_valid_slots = true; - break; - } + // Drop before the queue copy when no configured slot allows this payload + // type. A QueuedPacket carries the packet plus up to 256 bytes of raw radio + // data and crosses to Core 0, so filtering here (not at publish time) is + // what actually saves the queue slot and the memcpy. This also subsumes the + // older "is any slot configured at all?" pre-check. + bool filtered = false; + if (!shouldQueuePacketType(packet->getPayloadType(), filtered)) { + if (filtered) _filtered_packets++; + return; } - if (!has_valid_slots) return; // Queue packet for transmission queuePacket(packet, false); @@ -2830,6 +2875,13 @@ void MQTTBridge::sendPacket(mesh::Packet *packet) { if (!_identity || memcmp(_identity->pub_key, packet->payload, PUB_KEY_SIZE) != 0) return; } + // Same pre-queue filter gate as the RX path. + bool filtered = false; + if (!shouldQueuePacketType(packet->getPayloadType(), filtered)) { + if (filtered) _filtered_packets++; + return; + } + // Queue packet for transmission queuePacket(packet, true); } @@ -2921,7 +2973,9 @@ void MQTTBridge::processPacketQueue() { _last_raw_timestamp = millis(); } + bool packet_eligible = false; bool packet_published = publishPacket(&queued.packet_copy, queued.is_tx, + packet_eligible, queued.has_raw_data ? queued.raw_data : nullptr, queued.has_raw_data ? queued.raw_len : 0, queued.snr, queued.rssi); @@ -2929,15 +2983,21 @@ void MQTTBridge::processPacketQueue() { // Publish raw if enabled (live from prefs so `set mqtt.raw` applies without // a bridge restart) + bool raw_eligible = false; bool raw_published = false; if (_obs->mqtt_raw_enabled) { - raw_published = publishRaw(&queued.packet_copy); + raw_published = publishRaw(&queued.packet_copy, raw_eligible); } - bool any_published = MQTTPacketQueuePolicy::queuedPacketPublished(packet_published, raw_published); + // Decide intentional completion once across the entire queue item. An + // ineligible raw path (for example, MeshRank, which does not take raw) + // must not hide a failed eligible structured publish. + const bool queue_complete = MQTTPacketFilter::publishComplete( + packet_eligible || raw_eligible, + MQTTPacketQueuePolicy::queuedPacketPublished(packet_published, raw_published)); const MQTTPacketQueuePolicy::RetryDecision retry = MQTTPacketQueuePolicy::retryDecision( - any_published, queued.retry_attempts, + queue_complete, queued.retry_attempts, static_cast(now_ms)); if (retry.action == MQTTPacketQueuePolicy::RetryAction::Schedule) { queued.retry_attempts = retry.retry_attempts; @@ -3048,22 +3108,30 @@ void MQTTBridge::processPacketQueue() { _last_raw_timestamp = millis(); } + bool packet_eligible = false; bool packet_published = publishPacket(&queued.packet_copy, queued.is_tx, + packet_eligible, queued.has_raw_data ? queued.raw_data : nullptr, queued.has_raw_data ? queued.raw_len : 0, queued.snr, queued.rssi); // No taskYIELD() on non-ESP32 platforms (non-FreeRTOS, cooperative scheduling not needed) // Live from prefs so `set mqtt.raw` applies without a bridge restart. + bool raw_eligible = false; bool raw_published = false; if (_obs->mqtt_raw_enabled) { - raw_published = publishRaw(&queued.packet_copy); + raw_published = publishRaw(&queued.packet_copy, raw_eligible); } - bool any_published = MQTTPacketQueuePolicy::queuedPacketPublished(packet_published, raw_published); + // Decide intentional completion once across the entire queue item. An + // ineligible raw path (for example, MeshRank, which does not take raw) + // must not hide a failed eligible structured publish. + const bool queue_complete = MQTTPacketFilter::publishComplete( + packet_eligible || raw_eligible, + MQTTPacketQueuePolicy::queuedPacketPublished(packet_published, raw_published)); const MQTTPacketQueuePolicy::RetryDecision retry = MQTTPacketQueuePolicy::retryDecision( - any_published, queued.retry_attempts, + queue_complete, queued.retry_attempts, static_cast(now_ms)); if (retry.action == MQTTPacketQueuePolicy::RetryAction::Schedule) { queued.retry_attempts = retry.retry_attempts; @@ -3110,6 +3178,69 @@ void MQTTBridge::processPacketQueue() { // Publishing // --------------------------------------------------------------------------- +// Which slots will actually take this payload type, resolved in two stages so +// the cheap test runs first: the filter/enabled check costs a bit test, while +// the topic build costs an snprintf. Both are far cheaper than the ~2 KB JSON +// serialisation they gate, so this must complete before a caller builds a +// message -- a slot that cannot form a topic (MeshRank raw, or a meshcore +// preset with no IATA set) would otherwise pay for a document nobody receives. +// +// A slot's connection state is deliberately not consulted: a configured but +// disconnected broker is still a target, so the packet stays on the queue for +// the existing bounded retry rather than being dropped as complete. +uint8_t MQTTBridge::eligiblePacketSlots(uint8_t packet_type, MQTTMessageType type) { + static_assert(RUNTIME_MQTT_SLOTS <= 8, "eligible slot mask must fit in uint8_t"); + // Per-slot filters cover packet traffic only. Status and neighbors are + // documented as never filtered, so refuse those publication types here + // rather than silently applying a packet mask if a caller is added later. + if (!_obs || (type != MSG_PACKETS && type != MSG_RAW)) return 0; + + uint8_t eligible_slots = 0; + char topic[128]; + for (int i = 0; i < RUNTIME_MQTT_SLOTS; ++i) { + const bool slot_enabled = _slots[i].enabled && _slots[i].client != nullptr; + // Load once so a live CLI/WebConfig update cannot split this packet's + // decision across two different masks. + const uint16_t filter_mask = _obs->mqtt_slot_packet_filter[i]; + if (!MQTTPacketFilter::slotCandidate(slot_enabled, filter_mask, packet_type)) continue; + + const bool topic_supported = buildTopicForSlot(i, type, topic, sizeof(topic)); + if (MQTTPacketFilter::slotEligible(slot_enabled, topic_supported, + filter_mask, packet_type)) { + eligible_slots |= static_cast(1u << i); + } + } + return eligible_slots; +} + +// Conservative pre-queue gate: the OR of every configured slot's mask. Runs on +// Core 1 in the radio callback, so it must stay cheap -- a packet no broker +// wants is rejected before it is copied into the queue at all. `filtered` +// separates "a broker is configured but none wants this type" (worth counting) +// from "no broker configured at all" (the pre-existing silent drop). +bool MQTTBridge::shouldQueuePacketType(uint8_t packet_type, bool& filtered) { + filtered = false; + if (!_obs) return false; + + uint16_t masks[RUNTIME_MQTT_SLOTS]; + bool enabled[RUNTIME_MQTT_SLOTS]; + bool any_enabled = false; + for (int i = 0; i < RUNTIME_MQTT_SLOTS; ++i) { + masks[i] = _obs->mqtt_slot_packet_filter[i]; + enabled[i] = _slots[i].enabled && _slots[i].client != nullptr; + any_enabled = any_enabled || enabled[i]; + } + if (!any_enabled) return false; + + if (MQTTPacketFilter::allows( + MQTTPacketFilter::enabledUnion(masks, enabled, RUNTIME_MQTT_SLOTS), + packet_type)) { + return true; + } + filtered = true; + return false; +} + bool MQTTBridge::publishStatus() { if (!_cached_has_connected_slots) { return false; @@ -3200,8 +3331,8 @@ bool MQTTBridge::publishStatus() { } } } - // If no connected slot accepts status topics (e.g. meshrank is packets-only), - // treat as success to avoid infinite retry loops + // If no connected slot accepts status topics, treat as success to avoid + // infinite retry loops if (published || !any_slot_wants_status) { if (published) MQTT_DEBUG_PRINTLN("Status published"); return true; @@ -3212,10 +3343,19 @@ bool MQTTBridge::publishStatus() { } bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, + bool& has_eligible_target, const uint8_t* raw_data, int raw_len, float snr, float rssi) { + has_eligible_target = false; if (!packet) return false; + const uint8_t packet_type = packet->getPayloadType(); + const uint8_t eligible_slots = eligiblePacketSlots(packet_type, MSG_PACKETS); + has_eligible_target = eligible_slots != 0; + // Filtered out everywhere, or no slot can form a packets topic: intentionally + // complete, and nothing below (JSON build included) is worth paying for. + if (!has_eligible_target) return false; + refreshOriginFromPrefs(); // Memory pressure check: Skip publishes when there's not enough contiguous @@ -3321,7 +3461,11 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, bool published = false; char topic[128]; for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { - if (_slots[i].enabled && _slots[i].client && _slots[i].connected) { + // Eligibility already proved this slot's topic builds; rebuilding it is + // one snprintf, cheaper than carrying a RUNTIME_MQTT_SLOTS x 128 topic + // cache on the MQTT task's 8 KB stack alongside the JSON buffer. + if ((eligible_slots & static_cast(1u << i)) != 0 && + _slots[i].enabled && _slots[i].client && _slots[i].connected) { if (buildTopicForSlot(i, MSG_PACKETS, topic, sizeof(topic))) { if (publishToSlot(i, topic, active_buffer, false)) { published = true; @@ -3331,7 +3475,6 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, } return published; } else { - uint8_t packet_type = packet->getPayloadType(); if (packet_type == 4 || packet_type == 9) { MQTT_DEBUG_PRINTLN("Failed to build packet JSON for type=%d (len=%d), packet not published", packet_type, len); } @@ -3339,9 +3482,17 @@ bool MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx, return false; } -bool MQTTBridge::publishRaw(mesh::Packet* packet) { +bool MQTTBridge::publishRaw(mesh::Packet* packet, bool& has_eligible_target) { + has_eligible_target = false; if (!packet) return false; + const uint8_t packet_type = packet->getPayloadType(); + const uint8_t eligible_slots = eligiblePacketSlots(packet_type, MSG_RAW); + has_eligible_target = eligible_slots != 0; + // Filtered out everywhere, or no slot has a raw topic (MeshRank does not take + // raw): intentionally complete, and no JSON is built. + if (!has_eligible_target) return false; + refreshOriginFromPrefs(); #if defined(BOARD_HAS_PSRAM) @@ -3372,7 +3523,8 @@ bool MQTTBridge::publishRaw(mesh::Packet* packet) { bool published = false; char topic[128]; for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { - if (_slots[i].enabled && _slots[i].client && _slots[i].connected) { + if ((eligible_slots & static_cast(1u << i)) != 0 && + _slots[i].enabled && _slots[i].client && _slots[i].connected) { if (buildTopicForSlot(i, MSG_RAW, topic, sizeof(topic))) { if (publishToSlot(i, topic, active_buffer, false)) { published = true; @@ -3419,11 +3571,12 @@ bool MQTTBridge::publishNeighbors() { char topic[128]; for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { if (_slots[i].enabled && _slots[i].client && _slots[i].connected) { - // MeshRank slots reject non-packets by contract, so buildTopicForSlot - // returns false for them here and the slot is skipped. + // Slots that cannot form a neighbors topic are skipped here. if (buildTopicForSlot(i, MSG_NEIGHBORS, topic, sizeof(topic))) { + // Neighbor snapshots are periodically refreshed. Publish synchronously + // at QoS 0 to avoid the QoS 1 outbox, retaining where the broker allows. bool use_retain = _slots[i].preset ? _slots[i].preset->allow_retain : false; - if (publishToSlot(i, topic, _neighbors_json_buffer, use_retain, 1)) { + if (publishToSlot(i, topic, _neighbors_json_buffer, use_retain, 0)) { published = true; } } diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index f0c5c117..40c4760a 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -10,6 +10,7 @@ #include #include #include "helpers/JWTHelper.h" +#include "helpers/MQTTPacketFilter.h" #include "helpers/MQTTPresets.h" #include "helpers/MQTTLifecycle.h" #include @@ -52,11 +53,15 @@ struct MQTTNodeInfo { bool repeat_when_nonzero = true; }; -// Periodic neighbors publication is PSRAM-only: it needs a persistent ~10 KB JSON -// buffer plus a second transient one while the mesh builds the table, and it keys -// off the mesh neighbor cache (sized by MAX_NEIGHBOURS). Every neighbors-specific -// member, method, and code block in this bridge is gated on WITH_MQTT_NEIGHBORS. -#if defined(BOARD_HAS_PSRAM) && defined(MAX_NEIGHBOURS) && MAX_NEIGHBOURS > 0 +// Periodic neighbors publication keys off the mesh neighbor cache (sized by +// MAX_NEIGHBOURS) and needs a persistent JSON buffer plus a second transient one +// while the mesh builds the table. PSRAM boards get it automatically; non-PSRAM +// boards must opt in per variant with MQTT_NEIGHBORS_WITHOUT_PSRAM, which spends +// internal DRAM the TLS stack also needs (see NEIGHBORS_JSON_BUFFER_SIZE). Every +// neighbors-specific member, method, and code block here is gated on +// WITH_MQTT_NEIGHBORS. +#if defined(MAX_NEIGHBOURS) && MAX_NEIGHBOURS > 0 && \ + (defined(BOARD_HAS_PSRAM) || defined(MQTT_NEIGHBORS_WITHOUT_PSRAM)) #define WITH_MQTT_NEIGHBORS 1 #endif @@ -349,6 +354,10 @@ private: unsigned long _last_memory_check; bool _memory_pressure = false; // Cached max-alloc verdict; re-sampled at most once per interval in publishPacket() so the heap walk isn't paid per-packet under pressure int _skipped_publishes; // Exposed via SNMP; count of publishes skipped when max_alloc is too low + // Packets rejected by the per-slot filters before reaching the queue. Written + // on Core 1 (radio callbacks), read on Core 0 for `mqtt.stats`; a torn read of + // a diagnostic counter is harmless, so no atomic is warranted. + unsigned long _filtered_packets = 0; // Status publish retry tracking unsigned long _last_status_retry; // Track last retry attempt (separate from successful publish) @@ -393,6 +402,8 @@ private: enum MQTTMessageType { MSG_STATUS, MSG_PACKETS, MSG_RAW, MSG_NEIGHBORS }; bool buildTopicForSlot(int index, MQTTMessageType type, char* topic_buf, size_t buf_size); bool substituteTopicTemplate(const char* tmpl, MQTTMessageType type, int slot_index, char* buf, size_t buf_size); + uint8_t eligiblePacketSlots(uint8_t packet_type, MQTTMessageType type); + bool shouldQueuePacketType(uint8_t packet_type, bool& filtered); // Internal methods - slot management // Lifetime model (Phase 1 of MQTT memory-defrag): @@ -428,10 +439,10 @@ private: void mqttTaskLoop(); // Main loop for MQTT task void initializeWiFiInTask(); // WiFi initialization moved to task #endif - bool publishPacket(mesh::Packet* packet, bool is_tx, + bool publishPacket(mesh::Packet* packet, bool is_tx, bool& has_eligible_target, const uint8_t* raw_data = nullptr, int raw_len = 0, float snr = 0.0f, float rssi = 0.0f); - bool publishRaw(mesh::Packet* packet); + bool publishRaw(mesh::Packet* packet, bool& has_eligible_target); #if defined(WITH_MQTT_NEIGHBORS) // Publishes the pending _neighbors_json_buffer to every connected slot's // neighbors topic. Runs on the MQTT task (Core 0) only. @@ -537,7 +548,38 @@ public: #if defined(WITH_MQTT_NEIGHBORS) // Single source of truth for the neighbors JSON size, used by both the bridge's // persistent buffer and the mesh's transient build buffer. + #if defined(BOARD_HAS_PSRAM) static const size_t NEIGHBORS_JSON_BUFFER_SIZE = 10240; + #else + // Without PSRAM all three neighbors allocations (persistent buffer, transient + // build buffer, ArduinoJson pool) come out of internal DRAM, which each TLS + // slot also needs ~40 KB of. A quarter-size buffer keeps the peak near 13 KB + // instead of ~35 KB; the builder drops the tail and sets "truncated" once the + // next entry will not fit, so the table degrades instead of failing. + static const size_t NEIGHBORS_JSON_BUFFER_SIZE = 4096; + #endif + + // The ArduinoJson pool is NOT bounded by the text buffer: v7 hands out pool + // blocks in fixed 4096-byte chunks, so a table that just fits the text buffer + // can still need well over it in pool. Budgeting the pool at the text size + // starves it, and a starved pool sets doc.overflowed() -- which drops the whole + // publish instead of truncating. Measured need is 12541 B for 50 entries and + // 4286 B for 21, so allow a spare block over each. + #if defined(BOARD_HAS_PSRAM) + static const size_t NEIGHBORS_DOC_POOL_BUDGET = 16384; + #else + static const size_t NEIGHBORS_DOC_POOL_BUDGET = 12288; + #endif + + // Entries per publish. One 4096-byte pool block holds 21, so staying under + // that keeps the non-PSRAM pool to a single block (~4.3 KB rather than + // ~8.4 KB) and the whole publish peak near 13 KB of internal DRAM. PSRAM + // boards are limited only by the text buffer and the neighbour cache. + #if defined(BOARD_HAS_PSRAM) + static const int NEIGHBORS_MAX_PUBLISH_ENTRIES = MAX_NEIGHBOURS; + #else + static const int NEIGHBORS_MAX_PUBLISH_ENTRIES = 20; + #endif // Called by the mesh (Core 1) once a neighbor-discovery pass has built the // table JSON. Copies it into the persistent PSRAM buffer and raises the @@ -629,6 +671,9 @@ public: bool has_publish_counts; unsigned long publish_ok; unsigned long publish_err; + // Raw packet-type allowlist. Kept as the mask rather than canonical text so + // the 1 KB stats JSON stays compact; the portal renders it. + uint16_t filter_mask; }; static bool getSlotStatusSnapshot(int slot_index, SlotStatusSnapshot* out); /** True when WiFi is set and at least one MQTT slot can run (preset + custom host if needed). */ diff --git a/src/helpers/esp32/WebConfigHtml.h b/src/helpers/esp32/WebConfigHtml.h index 8b99a7ea..11b3b040 100644 --- a/src/helpers/esp32/WebConfigHtml.h +++ b/src/helpers/esp32/WebConfigHtml.h @@ -1,1342 +1,1727 @@ // Auto-generated by scripts/generate_webconfig_html.py from webui/index.html // DO NOT EDIT - edit webui/index.html instead. +// build-inputs-sha256: 246f2546ffbbacdd2687de7b4e6086783c79ea6582d5d564ef174e2553d6a62d #pragma once #include #include -const uint32_t WEBCONFIG_HTML_GZ_LEN = 21308; -const char WEBCONFIG_HTML_ETAG[] = "\"b059385fe3505f65\""; +const uint32_t WEBCONFIG_HTML_GZ_LEN = 27446; +const char WEBCONFIG_HTML_ETAG[] = "\"762a49f633c7c16b\""; const uint8_t WEBCONFIG_HTML_GZ[] PROGMEM = { - 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0xd5,0x7d,0x69,0x63,0xdb,0x46, - 0xb2,0xe0,0x77,0xff,0x0a,0x18,0xc9,0x48,0x44,0x04,0x42,0x24,0x75,0x58,0x26,0x45, - 0x69,0x15,0xc7,0x9e,0x78,0xe2,0xeb,0x59,0xf2,0x4b,0xde,0x53,0x34,0x1e,0x90,0x04, - 0x45,0x44,0x20,0x80,0x00,0xa0,0x28,0x99,0xd2,0xfe,0xf6,0xad,0xa3,0xbb,0xd1,0x38, - 0x48,0x51,0x8e,0x67,0xf7,0xed,0x64,0x12,0x11,0x40,0x9f,0xd5,0xd5,0x75,0x75,0x55, - 0xf5,0xe1,0xd3,0x9f,0xde,0xbf,0x38,0xfb,0xaf,0x0f,0x2f,0x8d,0x49,0x36,0x0d,0x8e, - 0x9e,0x1c,0xe2,0x1f,0x23,0x70,0xc3,0xcb,0xbe,0xe9,0x85,0x26,0xbe,0xf0,0xdc,0x11, - 0xfc,0x99,0x7a,0x99,0x6b,0x0c,0x27,0x6e,0x92,0x7a,0x59,0xdf,0x9c,0x65,0xe3,0xe6, - 0x81,0x29,0x5f,0x87,0xee,0xd4,0xeb,0x9b,0xd7,0xbe,0x37,0x8f,0xa3,0x24,0x33,0x8d, - 0x61,0x14,0x66,0x5e,0x08,0xc5,0xe6,0xfe,0x28,0x9b,0xf4,0x47,0xde,0xb5,0x3f,0xf4, - 0x9a,0xf4,0x60,0x1b,0x7e,0xe8,0x67,0xbe,0x1b,0x34,0xd3,0xa1,0x1b,0x78,0xfd,0xb6, - 0x6d,0x4c,0xdd,0x1b,0x7f,0x3a,0x9b,0xe6,0x2f,0x64,0x43,0xcd,0xb1,0x9f,0xf5,0x87, - 0xd1,0xb5,0x97,0x60,0x4f,0x99,0x9f,0x05,0xde,0xd1,0x5b,0x2f,0x9d,0xbc,0x88,0x12, - 0xcf,0x78,0x11,0x85,0x63,0xff,0xf2,0x70,0x9b,0x5f,0x3f,0x39,0x4c,0xb3,0x5b,0xfc, - 0xdb,0x4d,0xa2,0x28,0x5b,0x3c,0x31,0x8c,0x66,0x73,0x70,0xd9,0xfd,0x6e,0xbc,0x3b, - 0xde,0x1f,0x1f,0xf4,0xe0,0x69,0xe8,0x26,0x23,0x78,0x1e,0x8f,0xf1,0xc1,0x0f,0xaf, - 0xba,0xdf,0xb5,0xdd,0xce,0xce,0x4e,0x0b,0x1f,0xa7,0xb3,0xac,0xfb,0xdd,0xfe,0xfe, - 0xb3,0x9d,0x03,0x17,0x1f,0x03,0x3f,0xf4,0xba,0xdf,0x79,0x1d,0xef,0x99,0xe7,0xf5, - 0xa8,0x29,0x77,0x38,0xec,0x7e,0xd7,0x19,0x3c,0x1b,0x79,0xcf,0x7b,0xfc,0xc8,0x4d, - 0x88,0xe6,0x22,0x6c,0x6d,0xf4,0x7c,0xb4,0x47,0x4f,0x5e,0x92,0x74,0xbf,0x1b,0xed, - 0xef,0xee,0xed,0xee,0xe1,0xe3,0xdc,0x4d,0xc2,0xee,0x77,0xc3,0x83,0x83,0x67,0x6d, - 0x97,0x5b,0x1b,0x4e,0xfc,0x18,0xda,0xf7,0xc6,0x9d,0xf1,0x33,0x1e,0x0d,0x8f,0x55, - 0x8e,0x4d,0x0c,0x60,0x78,0x30,0xea,0x8c,0x3c,0x7c,0x95,0x4e,0xdc,0x51,0x34,0xef, - 0xb6,0x8c,0x76,0x7c,0x63,0xec,0xc0,0xbf,0xc9,0xe5,0xc0,0x6d,0xb4,0xf7,0xed,0xce, - 0x81,0xbd,0xbb,0x67,0x3b,0xad,0x03,0xab,0xf7,0xe4,0xfe,0xc9,0xff,0x9a,0x7a,0x23, - 0xdf,0x35,0x1a,0x71,0xe2,0x8d,0xbd,0x24,0x6d,0x0e,0xa3,0x20,0x4a,0x00,0xac,0x13, - 0x6f,0xea,0x75,0x47,0x6e,0x72,0x65,0x2d,0x4a,0xe0,0x69,0xb7,0xda,0x7b,0xed,0x61, - 0x0e,0x1e,0x00,0x49,0xbb,0x33,0x50,0x10,0xf2,0xf6,0xbd,0xc1,0xb8,0xa3,0x20,0x74, - 0x30,0x78,0x7e,0xe0,0x0e,0x72,0x08,0x75,0xdc,0x9d,0xdd,0xdd,0x8e,0x06,0xa1,0x5d, - 0xf7,0xf9,0xee,0x98,0x20,0xca,0x53,0xec,0xec,0x74,0x46,0x3b,0xae,0x36,0xc5,0xf6, - 0x2e,0xf4,0xd0,0x29,0xcc,0x72,0xc7,0xdd,0xdd,0xdf,0xdb,0x5f,0x3e,0xcb,0x96,0x8d, - 0xff,0x38,0xbb,0x38,0xc3,0xfb,0x27,0x3f,0x2c,0x06,0xd1,0x4d,0x33,0xf5,0xbf,0xf8, - 0xe1,0x65,0x77,0x10,0x25,0x23,0x2f,0x69,0xc2,0x9b,0xde,0xd4,0x4d,0x2e,0xfd,0xb0, - 0xdb,0xba,0x7f,0x82,0xf8,0xbb,0x68,0xce,0xbd,0xc1,0x95,0x9f,0x35,0x33,0xef,0x26, - 0xc3,0xd2,0x5e,0xd3,0x1d,0xfd,0x31,0x4b,0xb3,0x6e,0xbb,0xd5,0xfa,0xdb,0xfd,0x93, - 0x41,0x34,0xba,0x5d,0x8c,0x01,0x49,0xbb,0xed,0xbd,0xf8,0x66,0xbb,0xed,0xec,0xee, - 0x19,0xe9,0x6d,0x9a,0x79,0xd3,0xe6,0xcc,0xb7,0x9b,0x6e,0x1c,0x07,0x5e,0x93,0x5f, - 0xd8,0xe6,0xa9,0x77,0x19,0x79,0xc6,0xa7,0xd7,0xa6,0xfd,0x31,0x1a,0x44,0x59,0x64, - 0xa7,0x6e,0x98,0x36,0x53,0x2f,0xf1,0xc7,0xbd,0x81,0x3b,0xbc,0xba,0x4c,0xa2,0x59, - 0x38,0xea,0x5e,0xbb,0x49,0x03,0x81,0x6a,0xf5,0x08,0xec,0xe2,0x19,0xc0,0x68,0xf5, - 0x62,0x77,0x34,0x82,0xf1,0xc2,0x40,0xb3,0x2c,0x9a,0x76,0x9f,0xb7,0xe2,0x9b,0x1e, - 0x62,0xf5,0x38,0x88,0xe6,0xcd,0x9b,0xee,0xc4,0x1f,0x8d,0xbc,0x10,0x46,0xde,0xa6, - 0x31,0xd1,0x78,0xbb,0xed,0x67,0x50,0x88,0x1e,0xe7,0x9e,0x7f,0x39,0xc9,0xba,0xfb, - 0x7b,0x38,0xb9,0x8e,0x5e,0x64,0xb7,0x5a,0xa4,0x47,0x33,0xce,0x12,0x18,0xe2,0x38, - 0x4a,0xa6,0xdd,0x59,0x1c,0x7b,0xc9,0xd0,0x4d,0xbd,0x5e,0xe0,0x65,0x19,0x00,0x2b, - 0x8d,0xdd,0x21,0xc2,0xce,0x69,0xed,0x79,0xd3,0xc2,0x50,0x61,0x89,0x2d,0x09,0xc7, - 0x4e,0x07,0xc0,0x0f,0x0b,0x01,0x23,0xc5,0x4e,0xbb,0x63,0x3f,0x49,0x33,0x5c,0xd4, - 0x60,0xb4,0xe0,0x22,0xcd,0x2c,0x8a,0x11,0xdc,0xee,0x42,0x6f,0x03,0xb0,0xc0,0x82, - 0x0a,0x40,0x31,0xbc,0x64,0x31,0xf2,0xd3,0x38,0x70,0x6f,0xbb,0xe3,0xc0,0xbb,0xe9, - 0xb9,0x81,0x7f,0x19,0x36,0x7d,0x80,0x68,0xda,0x1d,0x02,0x6d,0xf0,0x92,0xde,0xa5, - 0x1b,0x77,0xb1,0x07,0x09,0x9f,0x6e,0x1b,0x7b,0x6d,0xef,0xc3,0x9b,0x0a,0x5c,0x11, - 0x3d,0xad,0x9e,0x5a,0x70,0x82,0x23,0xa2,0x49,0x1a,0x05,0xfe,0xc8,0xe0,0x32,0x88, - 0x4f,0x00,0xec,0x28,0x05,0xfa,0x12,0x85,0xdd,0x34,0xf3,0x87,0x57,0xb7,0x3d,0x1a, - 0x67,0xef,0x0b,0xac,0xc4,0xc8,0xbb,0x81,0xfe,0xe4,0xf0,0x8c,0xf4,0xfa,0x72,0x81, - 0x43,0xeb,0x86,0x51,0xe8,0xdd,0x3f,0x71,0x26,0x48,0xca,0x16,0x53,0x98,0x1a,0x11, - 0x2a,0x9c,0x1c,0xbf,0x33,0x46,0xfe,0xb5,0x0e,0x76,0x18,0x65,0x15,0x70,0xf3,0x09, - 0xcc,0x8d,0xa0,0xeb,0x41,0x83,0xf3,0xc4,0x8d,0xd5,0x12,0x8b,0x05,0xe6,0xa5,0x51, - 0x2f,0xbd,0x20,0xf0,0xe3,0xd4,0x4f,0xa1,0x97,0x81,0x3b,0xba,0xf4,0x24,0x5c,0x03, - 0x6f,0x9c,0x75,0xdd,0x59,0x16,0xf5,0xd4,0xe0,0x7a,0x5a,0xe7,0xed,0x9a,0x35,0x97, - 0xf0,0xc3,0x3d,0xf3,0x1c,0xa1,0xc7,0x60,0x4a,0xdc,0x91,0x3f,0x4b,0xbb,0xcf,0x9f, - 0xd7,0x02,0x14,0x36,0xa8,0x55,0x99,0x86,0x1c,0x8c,0x03,0x44,0x7e,0x16,0x2f,0xb4, - 0x5a,0xdf,0x8d,0x77,0xbc,0x83,0xd1,0x8e,0xa8,0xf1,0xdd,0x81,0xbb,0x37,0x6c,0xb5, - 0xd6,0x22,0x39,0x4b,0x5b,0xdc,0x71,0x3b,0xe3,0xf6,0xae,0x6c,0xd1,0x6b,0x0d,0x76, - 0xf7,0x86,0xb0,0xc5,0xa7,0xae,0x1f,0x02,0x30,0x6e,0xc4,0x32,0xec,0xef,0x22,0x8a, - 0xc8,0xfd,0x6d,0x10,0x64,0x14,0xc2,0xec,0x23,0x7e,0x3a,0x88,0x1c,0x8b,0x95,0x28, - 0xb3,0x04,0x57,0x8a,0x80,0xa2,0x85,0xd5,0x9b,0xee,0x11,0xad,0x61,0xaa,0xc4,0xd5, - 0xf8,0x41,0x6e,0x13,0x85,0x88,0xbb,0x34,0x8c,0xcc,0x1d,0xa4,0x45,0xa4,0x47,0x0c, - 0xdf,0x5d,0x0e,0x7d,0xd9,0xd7,0x6e,0x65,0xcd,0xda,0xf9,0x9c,0x6b,0x3a,0x31,0x06, - 0x33,0x78,0x15,0x32,0xf6,0xb6,0xe5,0x14,0x5b,0x7a,0x37,0x84,0x36,0x15,0x24,0x25, - 0x92,0xe7,0x87,0x13,0xa0,0x5d,0x99,0x8e,0x54,0x3b,0xce,0x5e,0x19,0xad,0x5a,0x39, - 0x5a,0x01,0x25,0x32,0xaa,0x43,0x44,0xfa,0x34,0x9c,0x25,0x29,0xf4,0x10,0x47,0x3e, - 0x6e,0xe8,0xe2,0xe8,0x1c,0x18,0xe0,0x92,0x35,0xa9,0x10,0xc8,0x65,0x70,0x86,0x16, - 0xc7,0x8b,0x12,0x18,0x76,0x08,0x0c,0x63,0x10,0x51,0x06,0x5e,0xa0,0xc0,0x3d,0x08, - 0xa2,0xe1,0x95,0x3e,0xa5,0x4e,0xed,0x94,0x8a,0x6d,0xed,0x8a,0xa6,0x9c,0x09,0x8c, - 0x5f,0xdf,0xe1,0x6d,0xaa,0xbc,0x84,0x38,0x12,0xe5,0xa3,0x51,0xf8,0x61,0x3c,0xcb, - 0xce,0xb3,0xdb,0xd8,0xeb,0xe3,0xde,0xbe,0xb0,0xb5,0x17,0xb1,0x9b,0xa6,0x73,0x00, - 0x58,0xe1,0x65,0x38,0x9b,0x0e,0xbc,0xa4,0xf0,0xca,0x03,0x74,0x0f,0x2e,0xec,0xd4, - 0x0b,0xbc,0x21,0x31,0x66,0xc6,0x7a,0xe4,0x53,0x6a,0x01,0x0e,0x90,0x2c,0xb6,0xc4, - 0x74,0xea,0xd6,0x6f,0xbf,0x34,0x5a,0x06,0x6a,0x19,0xf8,0xc4,0x83,0x2d,0x64,0xd7, - 0x4b,0x36,0x85,0xe0,0xc9,0xe5,0x7d,0x71,0x80,0xec,0x6a,0x96,0x11,0xbb,0x26,0x5a, - 0x69,0x18,0xdb,0x3f,0x18,0x47,0x7d,0xec,0xb6,0x6b,0xa4,0x00,0x8e,0xd4,0xf0,0xdf, - 0x9f,0x1a,0x5f,0xa2,0x68,0xda,0x8c,0xc2,0xe6,0x38,0x1a,0xce,0x52,0xa3,0x91,0xfa, - 0x23,0x6f,0xee,0xde,0x02,0xab,0x1c,0x26,0x51,0x10,0x80,0xdc,0x67,0x0c,0xdd,0x38, - 0xf3,0xaf,0x3d,0x23,0x9d,0x78,0x5e,0x66,0x19,0x3f,0x6c,0x33,0x08,0xbb,0x54,0x43, - 0xc0,0x80,0x1f,0x16,0x62,0x08,0x65,0xe6,0xa2,0xa3,0x4a,0xcb,0xc0,0x7f,0x90,0x67, - 0x30,0xd5,0x99,0xfa,0x37,0x0d,0xe8,0x24,0x05,0xb1,0xc1,0xce,0x6b,0x18,0x9d,0xbd, - 0xbf,0xd9,0xc4,0x10,0x63,0x37,0x01,0xc6,0x63,0x89,0x75,0x73,0x46,0x7e,0x92,0xdd, - 0x8a,0x4e,0xf9,0xa1,0xae,0x53,0x94,0xde,0x10,0x0f,0x93,0x68,0x5e,0xdd,0xdb,0xc4, - 0x1f,0xf1,0xd3,0x11,0xe0,0xa9,0xd8,0x8d,0x45,0x06,0x92,0xce,0x1f,0x64,0x83,0x28, - 0x97,0xf8,0xe3,0xdb,0xa6,0x10,0x9a,0xbb,0xc4,0x43,0x9a,0x03,0x2f,0x9b,0x7b,0xc0, - 0x34,0x2a,0x4c,0xf2,0x39,0x72,0x66,0x6a,0xd9,0x80,0x92,0xa1,0x31,0x58,0xbe,0x07, - 0x6a,0xb7,0xb5,0x56,0xd5,0x17,0x38,0x8f,0x92,0x33,0xac,0x6c,0x32,0x75,0x83,0xde, - 0x83,0xbb,0x00,0x77,0xf9,0x65,0xb0,0x50,0x6c,0x36,0xf1,0x02,0x17,0x17,0xb5,0xc7, - 0x93,0xde,0x45,0x22,0x3a,0xe1,0xde,0x3a,0x24,0xa0,0x68,0x2c,0x16,0x2a,0x1a,0x04, - 0xfc,0xbc,0x3a,0x10,0x8c,0x28,0x98,0x65,0x5e,0x2f,0x42,0xc1,0x24,0xbb,0x05,0x32, - 0xa6,0xed,0x00,0xd1,0x10,0xfd,0x96,0x2c,0xa0,0x86,0xea,0x40,0xb3,0xb3,0x9a,0x26, - 0xfd,0x10,0x98,0x4e,0x91,0x2e,0xae,0x44,0x74,0xe2,0x94,0x84,0x2a,0xdc,0x90,0xd3, - 0xde,0x4b,0x7b,0xa2,0x9b,0xa6,0x77,0x0d,0x8b,0x93,0xea,0x33,0x99,0x75,0x07,0x1e, - 0x08,0x59,0xde,0x42,0xae,0x9c,0x69,0xf6,0xaa,0x83,0x10,0xd4,0xa2,0x47,0x7c,0x1d, - 0x7f,0x88,0xe9,0x1d,0xe4,0x70,0xa2,0xdf,0x05,0x5e,0x0b,0x9a,0x40,0x71,0x68,0x7b, - 0x00,0x80,0xd2,0xc8,0x74,0x78,0x76,0x81,0xe1,0x0e,0xaf,0xbc,0xd1,0xd6,0xac,0x4a, - 0x73,0x59,0x26,0xab,0x2b,0x2b,0xc7,0x4f,0x43,0xeb,0xb4,0x91,0xa4,0x09,0xda,0x3d, - 0xc8,0x42,0x85,0x56,0x7e,0x88,0xb0,0x6a,0xae,0x8b,0xbe,0x9a,0x70,0xb7,0xaf,0x98, - 0x06,0x2c,0xc2,0x32,0xda,0x55,0x27,0xc4,0x02,0x89,0xd2,0xd1,0xbd,0xbd,0x5f,0x15, - 0x6a,0x2a,0xdc,0xa7,0x57,0x3b,0xf1,0x5e,0x89,0x80,0x10,0x69,0xd4,0xa7,0x09,0x72, - 0xc9,0x70,0xb1,0x86,0x74,0x54,0xae,0xd7,0x05,0xf0,0xb8,0x83,0xc0,0x1b,0x2d,0x24, - 0xe2,0x3a,0x7b,0x72,0x44,0x23,0x6f,0xec,0xce,0x82,0xac,0xd8,0xcd,0x74,0x51,0xe1, - 0x4e,0x72,0x8e,0xfb,0x82,0xbe,0xa3,0x28,0x03,0x1d,0x97,0x21,0x5f,0xd9,0xd7,0xab, - 0x64,0x40,0x24,0x87,0x07,0xf5,0x32,0xa0,0x2e,0x60,0x22,0x44,0x41,0x12,0x05,0x11, - 0x19,0x34,0x71,0x5a,0xd5,0x6e,0x5b,0x0d,0xc0,0x89,0xae,0x74,0x90,0x2c,0xa3,0xae, - 0xd1,0x95,0x65,0xb4,0x4b,0xc4,0xb5,0x00,0xb3,0x08,0x41,0xc6,0x2d,0x82,0x4e,0xbc, - 0x4e,0x93,0x50,0xec,0x81,0x36,0xb1,0x04,0x92,0x30,0xf7,0xda,0x1b,0xb8,0x49,0xbe, - 0xe9,0xc7,0xfe,0x8d,0x37,0xe2,0x4d,0xd6,0xea,0x25,0x04,0x16,0xd8,0xf8,0xcc,0xe7, - 0x73,0xf1,0xbf,0xd3,0x7a,0x40,0xb5,0xc0,0xdd,0x5a,0x2f,0x2b,0x6a,0x38,0x89,0x6b, - 0x45,0x48,0x09,0x0c,0x2d,0x18,0x36,0xe8,0x71,0xcb,0xf0,0xc2,0xeb,0x46,0xea,0x8e, - 0x41,0xd5,0x4c,0x3c,0xb7,0x49,0xb4,0x47,0x08,0x1a,0x96,0xd5,0x93,0x4b,0x4a,0x72, - 0xd9,0x32,0x5d,0xa8,0x43,0x2b,0x20,0xa6,0xe6,0xa4,0x93,0x12,0xcb,0xc9,0xbf,0x01, - 0xd1,0x2f,0x93,0x79,0xed,0xa3,0x93,0xc6,0x82,0x19,0xe1,0xcb,0x00,0xf4,0xfd,0xc7, - 0x08,0xc1,0x65,0x85,0xac,0x2c,0x86,0x32,0x9e,0x62,0xb3,0xd0,0x51,0xb6,0x96,0x92, - 0x77,0xf0,0x50,0x2b,0xf5,0xf3,0x89,0xfd,0x20,0xd0,0xdf,0xb7,0x2a,0xec,0xec,0x59, - 0x6b,0x2d,0xc4,0x5f,0x53,0xf9,0x79,0x84,0xee,0xbc,0xeb,0x4d,0xc5,0x00,0xbf,0xed, - 0x5e,0xa1,0x16,0xc7,0x20,0x11,0xda,0xfc,0x13,0xa0,0x3b,0xfc,0xa6,0xfb,0x86,0x5a, - 0x9d,0xbb,0x7e,0xb6,0x4e,0xab,0x24,0xfb,0xac,0x6e,0x56,0x8a,0x47,0x99,0x1f,0x78, - 0xb9,0xf2,0x73,0x99,0xf8,0xa3,0x1e,0xfe,0xa7,0x09,0xa8,0x00,0x6f,0x40,0x33,0x86, - 0x4a,0xb3,0x69,0x98,0x82,0xb8,0x10,0x7b,0x6e,0xd6,0x40,0x35,0xae,0x39,0x86,0xc1, - 0xd8,0x20,0x2f,0x81,0xb2,0xd7,0x68,0xa3,0x9a,0x67,0xb7,0xc7,0x09,0x6c,0x16,0x25, - 0x5a,0x71,0xbb,0xcb,0xc8,0xf3,0x2a,0xc4,0xa5,0x1d,0xda,0x51,0x4d,0xd4,0x8a,0x3a, - 0x4b,0xe5,0xa6,0x3a,0xfa,0xfa,0x97,0x91,0x85,0x86,0x51,0xc0,0xf3,0x83,0x3a,0x0b, - 0x0f,0x97,0x4b,0x17,0xd4,0xfc,0xc8,0x1b,0x46,0x89,0x4b,0xf4,0xad,0x62,0x07,0x58, - 0x22,0x9c,0x0d,0xdd,0xf0,0xda,0x4d,0x17,0x55,0x19,0x6a,0x0f,0x49,0x7e,0x61,0xca, - 0xd0,0x59,0xe8,0x65,0xeb,0x9b,0x69,0xb4,0x36,0xd7,0x52,0x39,0x49,0x0b,0x29,0xb0, - 0xfd,0x32,0x0d,0xd5,0xf8,0xfa,0x4a,0x9b,0x4e,0x89,0xd3,0x13,0x6c,0x98,0x69,0x21, - 0xc1,0xe7,0x79,0x74,0x03,0x57,0x99,0xa8,0x8a,0x6d,0xb6,0xb8,0x00,0x10,0x9a,0x54, - 0x0a,0xe9,0x65,0xbd,0x70,0x3d,0x53,0x4d,0x8d,0x91,0x47,0x34,0x9d,0x02,0x3e,0x05, - 0x8b,0xea,0x6a,0x80,0x9e,0xf4,0x9a,0x98,0xb8,0x91,0xc2,0xc2,0xc0,0xe4,0x43,0x2f, - 0x30,0x1a,0xef,0xde,0x9f,0x19,0xf0,0x84,0xed,0x03,0xe0,0xad,0xae,0x91,0x4d,0x3c, - 0xd2,0xa0,0x5e,0xbc,0x3b,0x61,0xdd,0xc8,0x80,0xb9,0x81,0x64,0x96,0x1a,0x71,0x12, - 0x5d,0x26,0xee,0x74,0x0a,0x48,0x30,0x04,0xe8,0x19,0x83,0x60,0x96,0x34,0x2c,0x1b, - 0xaa,0x8f,0x8c,0xf9,0xc4,0x0b,0xa9,0xea,0x95,0x77,0x3b,0x88,0x80,0x8b,0x19,0x7e, - 0x6a,0xcc,0x62,0x6a,0x89,0x75,0xae,0xd4,0x20,0xb6,0x28,0x7b,0x4a,0x8d,0x68,0x3c, - 0x86,0x2f,0x1e,0xd6,0x8b,0xb0,0xb9,0x2b,0xcf,0x8b,0xa9,0x09,0x52,0xba,0xa0,0xe4, - 0xd8,0xf7,0x02,0x80,0xbd,0x9f,0xfa,0x20,0xdb,0x38,0xc6,0x89,0x18,0x32,0xd0,0x05, - 0xde,0x34,0x06,0x02,0xc4,0x00,0x6d,0x09,0x94,0xa6,0xe0,0xd6,0x80,0x75,0xf7,0x12, - 0x6c,0x00,0x1b,0x3b,0x3d,0x7d,0xfd,0x93,0x68,0x00,0x98,0x72,0x8a,0x95,0xb2,0x89, - 0x9b,0x19,0x97,0x33,0x17,0xb6,0x49,0xe6,0x79,0xa3,0xa6,0x68,0x18,0x54,0x45,0x00, - 0x09,0xb0,0xc8,0xcc,0x73,0x47,0x0e,0xea,0x81,0x0e,0xc2,0x87,0xfa,0x7a,0x34,0xb3, - 0x5a,0xca,0xd0,0x57,0xda,0x6e,0xba,0x4d,0x61,0xe2,0x64,0x7b,0x82,0xea,0x1e,0xb9, - 0x67,0x13,0x2d,0x84,0x6b,0xed,0x8b,0x7d,0xb9,0x85,0x73,0xcb,0xe0,0x37,0x36,0xc8, - 0x56,0x28,0x9a,0xb1,0x5b,0x1a,0xf0,0x77,0xf8,0x13,0xc0,0x03,0xcc,0x18,0xad,0x66, - 0x62,0xbb,0xef,0xee,0x5d,0x4f,0x72,0xb4,0x2e,0x58,0xcd,0x5a,0xc8,0x27,0x0d,0x36, - 0x9d,0xc1,0x0a,0xc4,0x35,0x46,0xab,0xfd,0x2a,0xc7,0xd6,0xca,0x23,0x35,0xe5,0x7d, - 0x24,0x3b,0xab,0xf0,0xdd,0x4e,0xdd,0xc2,0xd0,0x12,0xe6,0x8d,0xd4,0x5a,0x88,0xa4, - 0xb6,0x12,0xc1,0x6e,0x2e,0x4b,0x77,0x28,0x9c,0xed,0xef,0x4a,0x5d,0x4a,0xa9,0x44, - 0x04,0x57,0xfa,0x85,0x9c,0xe6,0xb7,0x46,0x13,0xbe,0x58,0x4a,0xe4,0xdb,0xad,0xd5, - 0xff,0xae,0x8a,0x6c,0x4c,0xd8,0x45,0xd6,0x36,0x89,0xad,0xd0,0x4a,0x72,0x3d,0x56, - 0xd7,0xd7,0x3a,0xf5,0x9a,0x64,0x2f,0xb7,0x73,0x3e,0x6f,0x5d,0xcf,0xe5,0xc4,0x59, - 0xf6,0x93,0x2d,0xa1,0x18,0x27,0x76,0x70,0x19,0x22,0x52,0xc3,0x95,0x93,0xdd,0x6b, - 0xd5,0x1f,0x49,0x3c,0x24,0x7e,0x2e,0x51,0xe2,0x34,0x42,0x2b,0xde,0x28,0x69,0x8b, - 0xb1,0x50,0x0c,0xab,0x5e,0x56,0x8d,0xfd,0x50,0xf0,0xa1,0x8e,0x6e,0x14,0xe8,0xe4, - 0x5a,0xe1,0xce,0xaa,0x3d,0x0e,0xab,0x5d,0x67,0xfe,0x29,0x6b,0xc5,0x6e,0xe8,0x4f, - 0x99,0x45,0x26,0x46,0x3b,0x35,0xb0,0x09,0x90,0x81,0xfd,0x70,0x8c,0xe7,0x8c,0x5e, - 0xaf,0x4e,0x97,0xba,0x7f,0xf2,0xbf,0x80,0x64,0x8e,0x81,0xae,0x02,0x85,0x4d,0x16, - 0x59,0xb4,0xc8,0xf1,0x28,0x89,0x32,0x40,0xa2,0xc6,0xce,0x7e,0x6b,0xe4,0x5d,0x5a, - 0xf7,0x30,0x0f,0x90,0x9c,0xe8,0x1c,0x69,0x51,0x16,0xa7,0x0a,0xa6,0x96,0x7c,0xc3, - 0x20,0x96,0x92,0xc4,0x0b,0xbf,0x35,0x05,0x1f,0x5a,0xba,0xba,0xae,0x88,0xb9,0xbd, - 0xc2,0xd6,0x5b,0xc3,0x14,0xd4,0x29,0xe9,0x8d,0xad,0x75,0x18,0x28,0xf5,0xbd,0x9a, - 0x41,0x5e,0x5d,0xd7,0xca,0x48,0x35,0xd6,0x1f,0x28,0x29,0xc4,0x18,0x6d,0x63,0x68, - 0x98,0x42,0xca,0xd7,0xfa,0x87,0x1f,0xf0,0xdd,0x5b,0xe8,0xf8,0xf9,0xd4,0x9f,0xe2, - 0xe1,0x2f,0x70,0x0c,0x64,0xae,0x51,0xe6,0x55,0x15,0xe7,0x0a,0x95,0x5c,0x4b,0x42, - 0x3c,0x28,0x59,0xd1,0x48,0xce,0xab,0xb5,0x2e,0x0f,0x03,0xbf,0x19,0xcd,0xb2,0x85, - 0xb6,0x84,0x9d,0x7d,0x36,0xc9,0x2b,0x02,0xbb,0xd7,0xa9,0x10,0x58,0x5d,0x3c,0x88, - 0x13,0xaf,0x59,0x38,0x05,0xa2,0xa7,0xae,0x1b,0xde,0x02,0xdb,0x4e,0xe8,0x58,0x59, - 0xb7,0xf8,0xb4,0x06,0xed,0x56,0x7b,0x5f,0x9e,0x85,0x8c,0x86,0xde,0xb3,0xf1,0xde, - 0x63,0xce,0x2e,0x9e,0xeb,0xc2,0x2f,0xce,0x4b,0x50,0x33,0x9a,0x11,0x1f,0x70,0xce, - 0xfc,0xe6,0x34,0x0a,0x23,0x1a,0x9e,0x7d,0xfa,0xea,0x2d,0xfc,0x6e,0x7e,0xf4,0x2e, - 0x67,0x81,0x9b,0xd8,0x2f,0xa2,0x10,0x7a,0x70,0x53,0xdb,0x7c,0xe3,0x0f,0x3c,0x16, - 0x39,0x0d,0x2c,0x61,0xda,0xaa,0x4e,0xbd,0x0a,0x87,0xa0,0x9a,0x46,0xda,0x12,0xe6, - 0xc6,0xd1,0x7d,0xfd,0xdc,0xa6,0x45,0x27,0x53,0xa5,0x1d,0x53,0x83,0x5d,0xb2,0xbd, - 0x92,0x7d,0xff,0x21,0x26,0x5c,0xb1,0x09,0xaa,0x76,0xd8,0xde,0x98,0x9f,0x0f,0xd3, - 0x97,0x8a,0x35,0x57,0x6f,0x1a,0x5f,0x34,0xbd,0x70,0x24,0x75,0xd7,0xbc,0x0e,0x37, - 0x66,0xab,0x47,0x44,0x6a,0xd4,0xf6,0x6b,0x6c,0xbf,0x45,0x4c,0x53,0xc6,0x7b,0xb2, - 0x1a,0xfc,0xe5,0x95,0x80,0xc5,0x5d,0xdb,0xdc,0xff,0x17,0x6d,0xfd,0x4f,0xaa,0x93, - 0xd5,0xf4,0x00,0x9d,0xc6,0xed,0xe1,0x24,0x41,0x66,0xc5,0xd5,0x95,0xa6,0xa5,0x32, - 0xe8,0x84,0xa5,0xbf,0xd2,0xe6,0xff,0x05,0xa3,0xff,0xe1,0xb6,0xf0,0x10,0x39,0xdc, - 0x16,0x1e,0x2d,0xe8,0x05,0x20,0xfc,0x5b,0xbc,0xe4,0x08,0x60,0x7a,0x98,0x5e,0x5f, - 0xf2,0xf9,0x4b,0xdf,0xec,0xec,0x9b,0x06,0xcf,0x8c,0x7f,0xa3,0x4f,0xca,0x8f,0xd1, - 0x4d,0xdf,0xa4,0xee,0x77,0xe1,0xff,0xa6,0x81,0xfa,0x6b,0xdf,0x44,0x30,0x99,0x46, - 0x9a,0x25,0xd1,0x15,0x3a,0xc1,0xa8,0x31,0xc8,0x77,0x4d,0xd9,0xa2,0x7a,0x81,0xd0, - 0x1d,0xba,0x71,0xdf,0xa4,0xe5,0x32,0xb1,0x6b,0xe8,0x7c,0xe8,0x27,0x43,0x10,0x8d, - 0x87,0xd0,0x47,0x1b,0xca,0x0e,0x6f,0xf9,0x6f,0x02,0x35,0x9d,0x8e,0xec,0xac,0xda, - 0xbc,0x18,0xc0,0xb6,0x68,0x25,0x76,0xb3,0x89,0x31,0xea,0x9b,0x6f,0x9f,0x39,0xcf, - 0x0c,0xf8,0xd7,0xdd,0x37,0xf6,0x8d,0x96,0xf8,0xe7,0xc0,0xd9,0x7f,0xdb,0xde,0x77, - 0x76,0x0a,0x1f,0xda,0xe2,0xc3,0xae,0xf3,0xdc,0x80,0x7f,0xdd,0x36,0x9e,0xfb,0xab, - 0x2a,0xed,0x5d,0xa7,0xf3,0xb6,0xfd,0xdc,0x69,0x97,0xbe,0xb5,0xc5,0x37,0xee,0x18, - 0x80,0x7b,0x7d,0x49,0x3f,0x46,0xfe,0xb5,0x31,0x04,0xfc,0x4d,0xfb,0x26,0x1d,0x9e, - 0xcb,0xd9,0x4d,0xda,0x86,0x0f,0xc3,0x9a,0x34,0xd1,0x57,0xc8,0x54,0x6e,0x3c,0xb0, - 0x18,0x6d,0x51,0x02,0x6b,0x72,0x91,0x74,0x36,0x30,0x8f,0x80,0x07,0x02,0x94,0x32, - 0xd8,0x42,0x1b,0x13,0x62,0x17,0xbd,0xc3,0x6d,0x28,0xc2,0xbd,0xc9,0x1f,0x74,0x8c, - 0x21,0xba,0xa3,0x63,0x66,0x03,0xd9,0x89,0x29,0xda,0xa1,0x37,0xe6,0x11,0x0c,0x0e, - 0x8a,0xc9,0x85,0xc7,0xa5,0x3e,0xc4,0x33,0xe6,0xa3,0x27,0x4f,0x0e,0x9f,0x36,0x9b, - 0x46,0x5f,0xfb,0x9f,0xf1,0xe6,0xfd,0xdf,0x5f,0xbf,0x2b,0xbe,0x6a,0x36,0xd1,0xbb, - 0x08,0x87,0x02,0x3b,0x11,0x1b,0xbe,0x6e,0x06,0x11,0x10,0x13,0x53,0xcd,0x13,0xbb, - 0x2c,0x4f,0x1e,0x95,0x10,0x35,0xf7,0xce,0xd1,0xc9,0x08,0xb6,0x8a,0x41,0xf5,0x60, - 0x18,0x1d,0xb9,0x58,0x06,0xe1,0x64,0xdf,0xac,0x88,0x05,0xcb,0xce,0x1e,0x25,0xf1, - 0x85,0x0d,0x60,0x1e,0xbd,0x44,0x5a,0x07,0x3a,0x16,0xa8,0x7d,0x21,0x90,0xba,0xcd, - 0xd4,0x70,0xa9,0x1b,0x79,0xf6,0x68,0x34,0x50,0xb5,0x4b,0x01,0xe0,0x06,0xea,0x5b, - 0xac,0xe0,0x45,0x89,0x91,0x78,0x53,0x60,0xab,0xc6,0x8b,0x37,0xaf,0xb9,0x82,0xe5, - 0x1c,0x6e,0xc7,0x62,0x48,0x28,0x04,0xd1,0x2c,0x69,0xac,0x4d,0x7c,0x14,0xd3,0x28, - 0xce,0x6f,0x0c,0x70,0x25,0x12,0x8d,0x2d,0xca,0xd2,0xf1,0x1c,0xe6,0xfc,0x41,0xf4, - 0x7e,0xb8,0x4d,0xdf,0x65,0x65,0xa8,0x4e,0x24,0xc0,0xa0,0x63,0x4f,0x53,0x8e,0xd1, - 0xd4,0x3a,0xc3,0xea,0x74,0xc0,0x3f,0x8c,0xa6,0x31,0xa8,0x47,0x50,0x0c,0xe8,0x3a, - 0xee,0xdf,0x66,0x5e,0x1c,0xbf,0x13,0xb5,0x38,0x52,0x68,0x40,0x8d,0xb3,0xad,0x5d, - 0x21,0x43,0x06,0xeb,0xc3,0x3d,0x01,0x36,0x4d,0xfd,0x4c,0xef,0x07,0x3f,0x1e,0x9d, - 0x02,0xc1,0x37,0x70,0x35,0xb8,0x62,0xdd,0x1c,0xa5,0xe4,0xa7,0xd7,0x85,0x77,0xa6, - 0xde,0xf3,0xe1,0x36,0x82,0x48,0xc3,0x4a,0xc0,0x36,0xc6,0x95,0x5a,0xfc,0x3a,0x7d, - 0x79,0xf6,0xe9,0x83,0xf1,0xeb,0xeb,0xff,0x3e,0xf9,0xf8,0xd3,0x83,0x68,0x36,0xf7, - 0xbf,0x20,0x16,0xad,0xc4,0x33,0x14,0x90,0x78,0x7c,0xc3,0xd0,0x6d,0xd2,0xd3,0xd1, - 0x6b,0x36,0x0b,0xfc,0xea,0xbf,0xf2,0x41,0x13,0x47,0xbe,0x06,0x38,0x11,0xc5,0xb3, - 0xf8,0xd8,0x78,0x9d,0x41,0xc5,0x28,0x05,0xb9,0xd7,0x1f,0x1b,0xb7,0xd1,0x2c,0x31, - 0xe2,0x09,0xe2,0x46,0x1a,0x78,0xa0,0x93,0x39,0x3c,0x25,0xd7,0x98,0x24,0xde,0xb8, - 0x6f,0x7e,0x67,0x02,0xde,0x00,0xa9,0x1e,0x5e,0xc1,0x7a,0x45,0xf1,0xfb,0x59,0xd6, - 0xb0,0x80,0xc2,0x67,0xb3,0x24,0x34,0xc6,0x6e,0x90,0x12,0xd9,0x23,0xfc,0xad,0x1a, - 0x46,0xcc,0xa3,0xf7,0xb1,0x87,0x00,0xe6,0x5e,0x06,0x40,0xed,0x53,0x2f,0x39,0xdc, - 0x76,0x8f,0x8c,0x2c,0x62,0xbb,0x03,0x77,0x8f,0x06,0x0e,0x2f,0x4d,0x9d,0x7c,0x53, - 0x6b,0xb3,0x23,0x4d,0x11,0xe0,0xed,0xd3,0x0c,0xd3,0x2c,0x6e,0x2b,0x68,0x44,0x21, - 0xae,0x83,0xaf,0x7d,0xeb,0x94,0x5f,0xec,0x94,0x5f,0xec,0x8a,0x17,0xdc,0xd5,0x13, - 0x8d,0xe8,0xcc,0xbf,0xb4,0x4d,0x8d,0x0e,0x55,0x37,0x31,0x6f,0xe3,0x53,0x18,0x0f, - 0x90,0xbe,0x8d,0x29,0x08,0xb5,0x51,0xd6,0x23,0x10,0xe7,0xfb,0xf9,0x2f,0xef,0x68, - 0x31,0x14,0x58,0xf8,0xb1,0x0f,0x5c,0x30,0xbe,0x35,0x8f,0x5e,0x30,0x19,0xcc,0x77, - 0x39,0x82,0x8f,0x20,0x47,0xcb,0x1b,0x82,0x6e,0x10,0x25,0x57,0xc0,0xda,0x0d,0x1f, - 0x96,0x16,0xa8,0x21,0x70,0xd3,0xe1,0x84,0xd6,0xff,0xed,0x7f,0x9c,0x9d,0x19,0x00, - 0x74,0x60,0xc6,0x69,0xbe,0xc3,0x6b,0xd0,0xe7,0xe8,0x70,0x70,0xf4,0x21,0xf1,0x80, - 0x55,0x02,0xb5,0xd5,0x29,0xc4,0xc8,0x8b,0x83,0xe8,0x76,0x0a,0x1b,0xf0,0x18,0xf6, - 0xc9,0x91,0x71,0x86,0x83,0x60,0xac,0x04,0xd6,0x9d,0x66,0x69,0x8e,0x67,0x7c,0x94, - 0x08,0x15,0x07,0x51,0x94,0x39,0xc6,0xeb,0x31,0x7d,0xca,0x1b,0x50,0x23,0xa5,0x69, - 0x64,0x86,0x7b,0xed,0xfa,0x01,0x1e,0xa0,0x19,0x28,0x0e,0xdb,0x48,0x9c,0x8c,0x4f, - 0xa7,0x3f,0xe2,0x78,0x7d,0x37,0x30,0xdc,0x0c,0x76,0x34,0xf2,0x80,0xb1,0x7f,0xe9, - 0x80,0x9a,0x36,0x19,0x42,0xeb,0x8e,0x1f,0xd1,0x30,0xd0,0xf8,0x95,0xcc,0x42,0x2c, - 0x01,0xea,0xaf,0x81,0xd0,0x72,0xd2,0x14,0x44,0x1b,0xc7,0x71,0x54,0x01,0xfd,0x23, - 0x10,0x16,0xf9,0xcd,0x81,0x49,0x78,0x0c,0x48,0x3c,0x2f,0xa1,0x19,0x4c,0xa1,0x54, - 0x36,0x01,0xb1,0xc7,0xc8,0x99,0x0e,0x77,0xe2,0x65,0x89,0x0f,0x65,0xdc,0x31,0x12, - 0xda,0x7c,0x32,0x4e,0x91,0xf4,0xd4,0x91,0xc5,0xa3,0x77,0x62,0xbe,0xc8,0xef,0x8c, - 0x06,0xda,0xc6,0xac,0x2a,0x45,0xd4,0x6a,0xc2,0x06,0x51,0xdb,0x49,0x08,0x9e,0x66, - 0x5e,0xb2,0x44,0x3d,0x99,0x2a,0x8d,0xdc,0xcc,0x6d,0x5e,0xa1,0x97,0xad,0x00,0x80, - 0x42,0x20,0x7e,0x20,0x52,0xea,0xc6,0x7e,0x06,0xd2,0xed,0x17,0xa8,0x15,0x8d,0xc7, - 0x92,0xbe,0x26,0x68,0xc0,0x13,0x6f,0x24,0xc6,0x92,0x0c,0x5b,0xec,0xb3,0x42,0x54, - 0x61,0x7d,0x86,0x92,0xb0,0xf2,0x47,0xa4,0x11,0xd3,0x08,0x79,0x4b,0x34,0x0f,0x81, - 0x66,0xa2,0x71,0xc3,0x01,0xed,0x07,0xff,0xfe,0xc4,0x07,0xa1,0x0d,0x4b,0x23,0x24, - 0x11,0x90,0x85,0x53,0x40,0xd4,0xc6,0xa6,0x18,0xe7,0xa6,0x05,0xc4,0x18,0x5e,0x94, - 0x29,0xb1,0xa4,0xa8,0x6b,0xc0,0xfa,0xb1,0x2c,0xa7,0x00,0xb8,0x1a,0xae,0x83,0x60, - 0xa9,0x5f,0x25,0xf4,0x14,0x32,0x8f,0xde,0x78,0x80,0x3a,0xc6,0x20,0x70,0xc3,0x2b, - 0xda,0x2e,0x68,0xcc,0x45,0x6a,0x27,0x70,0xdc,0x31,0x3a,0xce,0xee,0x06,0xec,0x58, - 0x10,0x3f,0x7b,0x7f,0xff,0xf9,0x8b,0x7c,0x9f,0x02,0x14,0x82,0x5b,0x67,0xed,0x69, - 0xbd,0x43,0x2c,0x45,0xfc,0x59,0x3d,0xaf,0x22,0x32,0x90,0x7c,0x85,0x3e,0xd5,0x81, - 0x17,0x5e,0xa2,0xdc,0xb9,0xd3,0x5e,0x35,0x17,0x89,0x31,0x58,0xad,0xc9,0xb3,0x3b, - 0x9d,0xc0,0x4a,0xc2,0x50,0x69,0x07,0xe3,0xde,0xa3,0xbd,0x00,0x94,0x9c,0x09,0x4a, - 0xe6,0x66,0xb3,0x14,0xdf,0xa7,0xee,0xa5,0x97,0x56,0x67,0xa3,0xff,0xac,0x61,0xca, - 0x0a,0x11,0xe6,0x5f,0xfe,0x1e,0x35,0x3a,0xb0,0xf8,0xef,0x60,0xfc,0x5d,0xe3,0x23, - 0x28,0x20,0x91,0x8e,0x03,0xb5,0x54,0xba,0x53,0xe5,0x86,0xeb,0x90,0xec,0x4e,0x4e, - 0xb2,0x45,0x3f,0xdf,0x8c,0x66,0xd3,0x2a,0xf1,0xca,0x02,0x29,0x73,0x13,0x83,0xe8, - 0x6f,0x04,0xb0,0x4b,0x0c,0xe2,0xe4,0xb0,0x0f,0x50,0x15,0x32,0x50,0xc3,0x8a,0x60, - 0xff,0x64,0x48,0x5e,0x52,0xc7,0xf8,0x00,0x50,0x20,0x10,0xc3,0x66,0x41,0x4a,0x45, - 0x44,0x3d,0x88,0xb0,0x28,0x01,0x1d,0xb6,0xd4,0x52,0xb2,0x9d,0xe3,0x08,0xe8,0x8d, - 0x28,0x30,0x70,0x1b,0x55,0x3c,0x61,0xf7,0x26,0xb9,0xc8,0x49,0x4c,0xf0,0x9f,0xb8, - 0xe1,0x25,0x32,0xed,0x2f,0x04,0x8b,0x53,0x2f,0x68,0x20,0x77,0xb1,0x48,0x52,0xa6, - 0xf2,0x6b,0xe0,0x4b,0x12,0x37,0x41,0x36,0x93,0xa2,0xd0,0x1a,0xc8,0x7c,0xf6,0x1b, - 0xc8,0x20,0x73,0x00,0x4a,0x63,0xf4,0xe3,0xd4,0x5a,0x8d,0xd1,0xec,0x27,0x97,0xe3, - 0x74,0x06,0xac,0x11,0xe4,0xd4,0xbe,0xd9,0x7c,0x4e,0xa8,0x0d,0x48,0xdd,0x52,0x74, - 0x2b,0x37,0xa1,0xb6,0x3b,0xad,0x02,0xd1,0xac,0x6e,0xdc,0xb7,0x1c,0x6a,0x60,0x04, - 0xde,0x25,0x80,0x99,0xc7,0x03,0xcb,0x8b,0x34,0x7e,0x70,0x0b,0xe4,0x9e,0x60,0xb9, - 0x31,0x1d,0xb9,0xe9,0xa4,0x67,0x90,0x9f,0x8c,0x58,0x91,0x64,0x16,0xd4,0xe1,0x7a, - 0x1d,0x33,0x7d,0xe7,0x81,0x98,0x2d,0x97,0x99,0x4f,0x40,0x50,0x07,0xdd,0xcc,0x0c, - 0xb4,0xd1,0x7b,0xa3,0x63,0xe3,0xc5,0x24,0x02,0x11,0x0c,0x39,0xd4,0x2f,0x28,0x07, - 0x09,0x19,0x57,0xd5,0x41,0x36,0x65,0x1b,0x68,0xbf,0x04,0x2c,0x20,0xb7,0x59,0x1b, - 0xf1,0x24,0xc4,0x57,0x5e,0x33,0x9b,0xd1,0xd1,0x09,0x61,0x4e,0xad,0xa4,0x46,0x86, - 0x91,0x93,0x38,0x2e,0xcb,0x6a,0xa0,0x83,0x5c,0xbb,0xe1,0x10,0x06,0xe7,0x8d,0xfc, - 0x2c,0x22,0x59,0xcc,0x59,0xb2,0x6f,0x4b,0x1b,0x4a,0x69,0x14,0xcd,0xca,0xf6,0x3a, - 0x29,0x68,0x1c,0xdf,0x72,0x5f,0x7d,0x04,0x76,0x0b,0xf2,0x24,0xcb,0x18,0x63,0x77, - 0x08,0x43,0xbe,0x55,0x1d,0x39,0x28,0xc9,0xe2,0x71,0x17,0xea,0x34,0x20,0x11,0xa5, - 0x2c,0xcd,0x93,0x30,0x61,0xe0,0x31,0x40,0x42,0x44,0x0b,0xbe,0x90,0x86,0x83,0x80, - 0xa7,0x73,0x2a,0xe6,0xfc,0x2c,0xde,0xc0,0x86,0x45,0xe3,0x9c,0x22,0x73,0x0f,0x6f, - 0xb6,0x77,0xde,0x5c,0x9b,0xea,0xe3,0x78,0x4d,0xfe,0x46,0xa3,0xcd,0xed,0xbd,0x32, - 0xeb,0x09,0xbd,0x79,0xae,0xec,0xac,0x83,0xca,0xed,0x3d,0x8a,0xca,0x01,0xf8,0xb0, - 0x98,0xb7,0xe6,0x76,0xa4,0xf0,0x19,0x50,0xf5,0xbe,0x6a,0x3a,0xc3,0xf1,0xf4,0x2b, - 0x27,0xf4,0x30,0xbe,0xa1,0x1c,0xb4,0x5c,0xa9,0x63,0xf9,0xa3,0xc8,0x43,0xda,0x40, - 0xb9,0x7e,0x74,0x87,0x57,0x15,0x55,0xae,0x86,0xfb,0x14,0x65,0x1d,0x45,0xcc,0x90, - 0x0e,0x36,0x43,0xe2,0xa3,0xc5,0xb6,0x77,0x14,0x7f,0x42,0xe6,0x57,0xec,0xa1,0x6c, - 0x8f,0x28,0x72,0xaa,0x9d,0xaf,0xe3,0x54,0x3b,0x39,0xa7,0xe2,0x1e,0xb5,0x0d,0x55, - 0xbb,0x90,0xef,0x07,0x2c,0xe2,0x0b,0x41,0x33,0x4a,0x7c,0xd8,0x49,0xd6,0x63,0x24, - 0x86,0xe9,0x9f,0x59,0xe6,0x70,0xbd,0x7a,0x99,0x71,0x15,0x1a,0xfe,0x8c,0x56,0x3c, - 0x54,0x0b,0x22,0x39,0x0e,0xe6,0x78,0x63,0xa4,0xa9,0x7e,0x06,0xfc,0x64,0x8c,0xfb, - 0x32,0x9e,0x0d,0x80,0xfa,0x4d,0x90,0x32,0x92,0xfc,0xb0,0x0d,0xaa,0xe2,0x15,0x30, - 0x3d,0x25,0x46,0x18,0xaf,0x43,0x54,0xa4,0xc9,0xde,0x09,0xf4,0x96,0xd8,0xa9,0x24, - 0xc4,0xa8,0x2f,0x28,0xe3,0x45,0x16,0xc5,0xfe,0x10,0xba,0x4b,0x70,0xa8,0x13,0x66, - 0xae,0x58,0x09,0x18,0x2a,0x90,0x42,0x3f,0x08,0xa0,0x1c,0x8a,0xf3,0xf9,0x6e,0x47, - 0xc0,0xac,0xbf,0x31,0x5e,0x9f,0x9c,0x9d,0x48,0x4e,0x30,0x84,0xea,0x8f,0x86,0xa4, - 0x0f,0x0f,0x45,0x01,0xac,0x0a,0xd5,0x7c,0xc7,0x2a,0x94,0x5c,0x7a,0x4e,0xfc,0x18, - 0xfe,0xf6,0x0e,0x04,0x10,0xd0,0xcb,0x0c,0xd7,0x4f,0xf0,0x0c,0x85,0x26,0x60,0x1b, - 0x9e,0x73,0xe9,0x20,0xb7,0xf9,0xe9,0xe5,0x3b,0x56,0x81,0x3e,0x21,0x30,0x91,0x87, - 0x20,0x2c,0x0d,0x34,0x19,0x3e,0x82,0x72,0xbc,0x9f,0x87,0xb0,0xca,0xb4,0xa0,0x43, - 0x74,0x3d,0x78,0x3c,0xaa,0x61,0x03,0x05,0x08,0xed,0xef,0xae,0xad,0xac,0x3c,0x4e, - 0x50,0x7f,0x1f,0x0b,0x7c,0x92,0xa8,0xb4,0xbf,0xdb,0x44,0xd8,0x03,0x9f,0xb8,0xd1, - 0xa6,0x60,0x44,0xc2,0x18,0x82,0x2d,0xbb,0x21,0x2e,0x3d,0x62,0x0e,0x22,0xe5,0x30, - 0x98,0x8d,0x18,0x58,0x88,0x6e,0xc6,0x3f,0x7e,0x3d,0x4b,0x51,0xd9,0x46,0x5c,0xf7, - 0x99,0x39,0x01,0x7f,0x4f,0x61,0xb5,0x10,0xdc,0x42,0x05,0x0f,0x58,0x9a,0xc3,0xa1, - 0xf8,0xd3,0x5c,0x73,0x7f,0x2c,0x88,0x29,0x34,0x60,0x35,0x74,0xa9,0x48,0x09,0xbc, - 0xe2,0xdd,0x63,0x37,0x72,0x05,0x54,0x20,0x34,0x44,0x30,0xef,0x3a,0x00,0xe0,0x3e, - 0xa4,0xc9,0xa1,0xa2,0x9c,0x5b,0x26,0x00,0x6c,0x69,0x04,0x44,0x48,0x02,0xa7,0x76, - 0xc2,0x48,0xe8,0xd8,0x26,0x51,0xa1,0x6d,0x52,0x79,0x0d,0xa2,0x2c,0xfd,0xf7,0xb0, - 0x8a,0xce,0xd7,0xb3,0x8a,0x62,0x43,0xbb,0x16,0x0a,0x28,0x78,0x28,0xf0,0x18,0x9e, - 0xb0,0xfb,0x58,0x9e,0xa0,0x18,0x14,0x75,0xd5,0xa4,0x58,0x57,0x93,0x19,0xc5,0xae, - 0xa6,0xd2,0xd0,0x57,0x63,0xc3,0x9d,0xc6,0x3d,0xb2,0x6a,0x2c,0x83,0x2c,0x37,0xb3, - 0x0e,0x68,0x59,0x94,0x3d,0x75,0xaf,0x69,0x85,0x95,0xa9,0x87,0xd5,0x03,0x40,0x07, - 0xb4,0xf9,0xd8,0x06,0x1e,0xfb,0xa7,0x8a,0xd0,0x6e,0x02,0x1a,0xc0,0x30,0xa4,0xa1, - 0xc7,0x1d,0x8d,0xd0,0xb0,0x27,0x64,0x57,0x36,0x0c,0xa1,0x53,0x10,0xc8,0x64,0xc2, - 0x97,0x08,0x74,0xe6,0x44,0xd0,0xee,0xb3,0x28,0xf7,0x45,0x8a,0x81,0x21,0x68,0xf6, - 0x21,0x92,0xe6,0xec,0x82,0xc1,0xc7,0x1b,0xcc,0x7c,0x58,0x10,0x22,0x66,0x7f,0x01, - 0x37,0x8a,0xc0,0xc5,0x03,0xb4,0x3a,0xf6,0xff,0xd7,0x44,0x0b,0x5c,0x0e,0xbd,0x55, - 0x32,0x9e,0x01,0x5c,0xbd,0x86,0x45,0xf0,0xf5,0xc4,0xb2,0x31,0x7c,0x96,0x20,0xd3, - 0x2a,0xb3,0x34,0xf4,0x51,0xb4,0x49,0x6b,0x98,0x17,0x17,0x98,0x4b,0xc1,0x91,0x43, - 0x73,0x56,0xc0,0x33,0x49,0xa8,0xff,0x18,0x5d,0xa2,0x4e,0xb2,0xc7,0x46,0x6a,0x54, - 0x0c,0x12,0xaa,0x57,0x1b,0xc5,0xdf,0x9e,0xbc,0x7e,0x67,0x9c,0x7c,0xf8,0xf0,0xa0, - 0x41,0xdc,0x8d,0xe3,0xd5,0xd6,0x70,0x8c,0x3f,0x63,0xb0,0xd0,0xaf,0xa2,0x15,0x82, - 0xa8,0x24,0xf0,0x11,0x92,0xf6,0x0a,0x76,0xe4,0x8a,0xed,0xa1,0x5a,0x09,0x49,0x2b, - 0x08,0xdd,0x55,0x21,0xb0,0x54,0x0e,0x4d,0x4a,0xe6,0x11,0xdb,0x85,0x57,0x95,0x43, - 0x31,0x28,0xc5,0xad,0x0c,0x7f,0x56,0x97,0x84,0xa5,0x30,0x8f,0x5e,0xbc,0x79,0xfd, - 0x80,0x6d,0x24,0x63,0x29,0x76,0x1d,0x41,0xf3,0x1d,0x89,0x35,0x0f,0x09,0x96,0xef, - 0x34,0xc3,0xd3,0x63,0xed,0x4d,0x4b,0x79,0x1c,0x59,0x39,0x59,0x87,0xc0,0x03,0xd0, - 0x65,0xa6,0xa9,0x7c,0x14,0x6f,0x80,0x40,0x64,0xb3,0x51,0xfd,0x48,0xca,0x76,0x02, - 0x20,0x15,0x88,0x9a,0x1e,0xb4,0xec,0x86,0xb7,0xa5,0x61,0x2c,0xed,0x21,0x0a,0x2f, - 0x1f,0xd1,0x45,0x14,0x2e,0xef,0xe2,0xdf,0xa9,0x4d,0x57,0x89,0x33,0xec,0xe6,0x54, - 0xe9,0xbc,0xa8,0xed,0x6a,0x02,0x31,0x3a,0x36,0xb1,0x0d,0x9f,0x8f,0x04,0x41,0xb0, - 0x99,0x42,0x91,0xb4,0xa4,0xf1,0x1a,0xba,0x25,0x33,0xd3,0x68,0xb0,0xb4,0x49,0xe4, - 0xfa,0xf6,0x2a,0x7d,0xf3,0x7f,0x96,0xd2,0xfc,0xff,0xb3,0xf6,0x5b,0xc1,0x88,0x37, - 0xd1,0x47,0x57,0x98,0x0e,0x0b,0xa7,0xdd,0xe8,0xe0,0x64,0x00,0xe1,0xd7,0x0e,0xbc, - 0x59,0x8b,0x45,0x8f,0x7c,0xf3,0x88,0xd9,0x09,0xd9,0x3f,0xe2,0x38,0xb8,0x15,0x47, - 0xe0,0xab,0x11,0xea,0x55,0xe2,0xfd,0x39,0xf3,0xc2,0xe1,0xad,0x6d,0x0c,0x00,0x57, - 0x44,0xba,0x8e,0xd3,0x57,0x84,0x5b,0x2f,0x3e,0x1a,0xd3,0x19,0x68,0x16,0xa0,0xa1, - 0x0d,0x27,0x2c,0x2c,0x93,0x95,0xd2,0xbb,0x71,0xc9,0x13,0x58,0x49,0x8d,0xc6,0x3c, - 0x81,0x0d,0x65,0x5c,0xbb,0xc1,0xcc,0x33,0x32,0xf7,0x8a,0x64,0x64,0x25,0x21,0x8e, - 0xf9,0x5c,0x08,0xf4,0x13,0x63,0x06,0x98,0x1a,0x68,0x0e,0xca,0xe2,0xe8,0xc7,0x59, - 0x49,0x3b,0x1e,0xda,0xcc,0x6a,0x0e,0x46,0xe3,0xed,0xcf,0x5f,0xac,0x87,0xb7,0x74, - 0x72,0x09,0xd5,0xa1,0x92,0xdc,0xd4,0x2d,0xa7,0xd5,0x6a,0x0b,0x6b,0x63,0x7b,0xaf, - 0x25,0xcc,0x8d,0x9d,0xbd,0x56,0x6b,0x4d,0x72,0xf2,0xa3,0x04,0x9d,0xd1,0xb8,0x5a, - 0x7b,0x04,0x83,0xb9,0xd6,0xbf,0xec,0xfe,0x99,0xe8,0xbc,0xda,0xf7,0x5f,0x02,0xd1, - 0x69,0x9c,0x78,0xee,0x88,0x4e,0xfa,0xc8,0xa8,0x56,0xc1,0xfd,0xdc,0x6c,0xac,0xc6, - 0x97,0x62,0xf5,0x88,0x34,0x84,0xa3,0xbd,0xc3,0x6d,0xf1,0x4b,0xbe,0xd9,0xaf,0xbc, - 0x79,0x56,0x79,0x73,0x50,0x79,0xf3,0xbc,0xf2,0xa6,0xdd,0xaa,0xbe,0x6a,0x57,0x5f, - 0x75,0xf2,0x57,0xd2,0x5e,0xbd,0xd6,0xca,0xbc,0x88,0x68,0xd6,0x20,0x72,0x7a,0xeb, - 0xcc,0x79,0x98,0x7c,0xc3,0x39,0xd7,0x0e,0xf4,0xab,0xad,0xe6,0xdf,0xd0,0x58,0xfe, - 0x15,0x24,0x49,0x0a,0x79,0xcb,0xa8,0xc9,0x2a,0x24,0xd4,0x78,0x7f,0x47,0x4d,0xf2, - 0xc4,0x4f,0x32,0x7f,0xea,0x95,0xf0,0x71,0xe5,0x24,0xdd,0x71,0xbe,0x61,0xe4,0x7e, - 0x69,0x15,0xcf,0x2e,0x6b,0x4f,0x2e,0xdc,0x71,0x73,0x34,0xac,0x3d,0xb5,0x58,0x31, - 0xd2,0xdd,0xfc,0xb4,0xe5,0x37,0x63,0xe4,0x05,0xee,0x2d,0x10,0xc8,0x74,0x3d,0x79, - 0x21,0xb9,0xa1,0x0a,0x75,0xa3,0x15,0xa4,0x65,0xed,0xcd,0x5d,0x18,0xd3,0x5e,0xbb, - 0xa3,0x23,0x09,0x8f,0xea,0x11,0x00,0xcc,0x1e,0x1c,0xd7,0x5a,0x38,0x53,0x72,0xd7, - 0x28,0x08,0x77,0x07,0x50,0x92,0xd9,0xce,0xe0,0xe8,0xc5,0xc9,0x4f,0x46,0x83,0xce, - 0x3f,0x42,0xe9,0x2a,0x40,0xb6,0xae,0xa9,0x9f,0x59,0xa8,0xc6,0x1d,0xfa,0x47,0x2f, - 0x26,0x6e,0x88,0xf1,0x0c,0x30,0x07,0xff,0xda,0xcf,0x6e,0x61,0x4e,0x19,0x4b,0xff, - 0xec,0xb3,0xc1,0x4e,0x5c,0xf9,0x76,0xd5,0x98,0x61,0x76,0x19,0x98,0xc5,0xe9,0xd2, - 0xd9,0xcd,0x20,0xba,0xc9,0x27,0x3c,0x74,0x91,0x01,0xcf,0xa0,0xa1,0xd9,0x91,0x62, - 0x87,0x6b,0xcd,0xa3,0xbd,0xaf,0x4d,0x04,0x30,0x00,0x38,0x2b,0x9e,0xe3,0x18,0x97, - 0x2e,0xb9,0x0f,0xe1,0xd8,0x4f,0x7f,0x6b,0x77,0xf6,0x6f,0x40,0xe4,0x1a,0x7a,0x3e, - 0x9a,0x36,0xc8,0x7e,0xf9,0x4d,0x86,0x4d,0x4c,0xdd,0x49,0x6e,0xb0,0xb3,0xaf,0x1c, - 0x7f,0xa7,0xb5,0xab,0x2f,0xc5,0xab,0x97,0x6f,0x0d,0x39,0x0b,0x31,0xfc,0x97,0x37, - 0x20,0xcc,0xa1,0xed,0x67,0x9c,0xa0,0x42,0xe7,0x85,0x23,0xf4,0x9d,0x9d,0x81,0xf2, - 0xfd,0xe6,0xdd,0xc9,0x37,0x9c,0xc6,0xd8,0x9b,0xfe,0xb5,0xa9,0xec,0x74,0xf4,0xa5, - 0xa0,0x50,0x42,0x31,0x85,0x57,0x51,0x32,0x47,0xd9,0x9a,0x84,0x12,0x40,0xad,0xf1, - 0xd8,0x1f,0x3a,0xc6,0x7b,0x90,0x37,0xfa,0x7c,0xee,0x16,0x36,0xc9,0xa6,0xdc,0x48, - 0x41,0xea,0x08,0xa4,0xb5,0x3a,0x25,0xd9,0x46,0x1a,0xa7,0x53,0x74,0x84,0xfb,0x16, - 0x93,0xa5,0x81,0xad,0x3d,0x45,0xdd,0xf3,0x43,0x53,0xcb,0x51,0x2b,0xd7,0xa6,0xbe, - 0xbf,0xfb,0xb0,0xae,0x44,0x47,0x95,0xee,0x08,0xbd,0x6f,0x8d,0xc6,0x54,0x3b,0x06, - 0x58,0x4d,0x4b,0xa9,0x82,0x43,0x9e,0xd3,0x20,0xb6,0x95,0xe9,0xc0,0xee,0x03,0x74, - 0xf5,0xa8,0x05,0x20,0x06,0xc1,0x6e,0x0d,0x9a,0xaa,0xa4,0xb4,0x20,0x8a,0x46,0x6a, - 0xa0,0x93,0x24,0x5d,0x6f,0xa0,0x63,0xac,0xe6,0xac,0x1e,0x6e,0x7b,0xff,0x60,0xbd, - 0xe1,0xda,0x86,0x17,0xa4,0x9e,0xb1,0xb3,0x11,0x92,0xe0,0x0a,0xf5,0xea,0xac,0x95, - 0xeb,0x71,0x47,0x4d,0xfc,0xa6,0x31,0x56,0x15,0x3b,0x9a,0x31,0x4a,0x1f,0x6c,0xed, - 0x81,0xa7,0x38,0x5d,0x2d,0x8d,0xff,0x1c,0xc5,0x80,0xb8,0x53,0x0c,0xa4,0x43,0x73, - 0x2b,0xb5,0x2b,0x11,0x5b,0x93,0xa8,0xc7,0x8c,0xf7,0x29,0xa8,0x72,0xe2,0x34,0x1b, - 0xe4,0xee,0x94,0x32,0xf0,0xb9,0x14,0xb5,0xe7,0xa7,0xb7,0x46,0x88,0x8e,0xcc,0x03, - 0x90,0xd9,0xd3,0x1e,0xa8,0x04,0x91,0x81,0x31,0x7c,0x2c,0x9a,0xc7,0x6e,0x92,0x61, - 0x58,0x60,0xee,0xeb,0x81,0x6e,0x50,0xf0,0x4c,0x6e,0x64,0xce,0xda,0x7a,0x7c,0xe7, - 0xe0,0x41,0xec,0x04,0x05,0x52,0xcc,0x62,0x42,0x93,0x5f,0x7b,0xc1,0x61,0x5d,0x4b, - 0x8b,0x5c,0xd8,0x0b,0xb5,0x6b,0x2c,0x56,0x75,0x7f,0xd7,0x31,0x84,0x13,0x92,0x01, - 0xbf,0x1f,0x81,0xa2,0x38,0x5a,0x81,0xa0,0x8f,0x1f,0xae,0xc0,0xd1,0x6f,0x31,0xea, - 0x03,0x67,0x35,0x5a,0xae,0x10,0x11,0x68,0x51,0xb4,0xe9,0xcc,0xc2,0x74,0x18,0xc5, - 0x78,0x8c,0xf6,0x95,0xab,0xe0,0xc8,0x16,0xaa,0x13,0xfb,0x1a,0x67,0x8c,0x13,0xd0, - 0x4c,0xf1,0x94,0x10,0x94,0x54,0x3e,0x0d,0x4c,0xf9,0xe0,0x2e,0x8c,0xe4,0xf9,0x1b, - 0xf5,0xe6,0x18,0x1a,0x5c,0x1e,0x3c,0x42,0x79,0x98,0x90,0x76,0xf6,0xf6,0x35,0x8a, - 0x09,0xbb,0x4c,0x93,0x33,0x96,0xb8,0xcf,0xe4,0xc6,0x9f,0x28,0x76,0xb8,0xb8,0xd2, - 0x0d,0x78,0xc7,0x89,0xf3,0x95,0xf7,0x48,0x06,0x8b,0x5a,0x80,0xfc,0x8e,0x5e,0x08, - 0x53,0x20,0x57,0x47,0x6f,0xf9,0xc7,0xd2,0x72,0xb0,0xab,0x51,0x45,0x81,0x82,0xe2, - 0xd7,0xb2,0x92,0x69,0x96,0xf8,0x38,0x90,0x53,0xfa,0x5b,0xd5,0x35,0x56,0x80,0xfe, - 0xa7,0x04,0x93,0x36,0x31,0x1e,0x48,0xd8,0xb3,0xa7,0x4a,0x80,0xca,0xe1,0xad,0x01, - 0x04,0x2c,0xb9,0x2d,0xb8,0x96,0x4f,0x5c,0xb4,0x17,0x71,0x5f,0x40,0x67,0xfa,0xc6, - 0xd8,0x43,0x7a,0xc3,0xbc,0x0e,0x17,0x31,0xa0,0xc1,0x8e,0xa4,0x78,0x37,0x82,0x2e, - 0x62,0xa0,0x78,0x2b,0x1c,0xc4,0x6a,0xad,0x98,0x64,0x68,0x7d,0xec,0xd1,0xf9,0x6b, - 0x3a,0x73,0xce,0x6e,0xff,0xea,0x71,0xf9,0xff,0x98,0x53,0x72,0x79,0x2e,0xde,0xc3, - 0x48,0xc6,0xd2,0xb9,0xb8,0x63,0xfc,0x48,0xf6,0xba,0xfe,0xb7,0x3c,0xde,0xfe,0x7f, - 0x7e,0xaa,0xfd,0x17,0x4e,0x9e,0xff,0x9d,0x07,0xce,0xab,0x56,0x75,0xf9,0x61,0x32, - 0x72,0x53,0xea,0x75,0x33,0x2d,0x9d,0x29,0x03,0xca,0x89,0x83,0x56,0xeb,0x2f,0x9d, - 0x07,0xaf,0x79,0x0c,0xfc,0x35,0xfa,0xfd,0x07,0xc6,0x42,0xd8,0xbb,0xab,0x25,0x94, - 0x33,0xed,0xe0,0x17,0x24,0x6b,0x0f,0xd1,0x1a,0xc4,0xf2,0x59,0x1c,0x44,0xee,0x88, - 0xbc,0xd8,0x90,0xb4,0x93,0xd3,0x68,0x13,0x7d,0x38,0x26,0x64,0x73,0x4a,0xa2,0xa9, - 0xe1,0x62,0x3c,0xc0,0x15,0xee,0x02,0x34,0x60,0xff,0xc1,0x47,0x8a,0x74,0x32,0xa8, - 0xf9,0x62,0x2d,0x57,0x08,0x72,0x15,0x80,0x3c,0x64,0xd9,0xa3,0x44,0xe8,0x01,0x1f, - 0xbc,0xc4,0x07,0x49,0x6b,0x88,0xbe,0x98,0x41,0x36,0xd9,0xa6,0x73,0x16,0x43,0xb4, - 0x2c,0x7c,0x57,0xc9,0xed,0xe1,0x9b,0x48,0xfa,0x04,0x69,0x6e,0xf5,0x31,0x1a,0x4d, - 0x3e,0x01,0x01,0x6b,0x49,0x84,0xc5,0x24,0xde,0xba,0xe4,0xaf,0x96,0x02,0x3b,0x1c, - 0x4e,0xd8,0xc3,0x18,0x74,0x16,0xe1,0x32,0x23,0x65,0x40,0xc0,0x82,0x68,0xfe,0xed, - 0x66,0x21,0x46,0xf0,0x75,0xd3,0x38,0x19,0x8d,0x8c,0xc4,0x9d,0x1b,0x1c,0x53,0x2d, - 0x66,0x71,0x12,0x90,0xa3,0x04,0xba,0xe1,0xa1,0x07,0xdf,0x0c,0xa6,0x80,0x3b,0x85, - 0xca,0xc8,0x15,0xc1,0x4a,0xdf,0x78,0x39,0xa0,0xc9,0xaf,0x9b,0xc4,0x47,0xd6,0xde, - 0x47,0xa5,0xc5,0x00,0x35,0x13,0x7d,0x3b,0x24,0x9b,0x44,0x1f,0xdf,0x51,0x7e,0x8c, - 0xe2,0xfa,0xc9,0x37,0x1c,0xfa,0xcd,0x5f,0xc3,0x22,0x21,0xe7,0x27,0x95,0xcd,0x20, - 0x3f,0x80,0xd4,0x8f,0x47,0xea,0xa8,0xf4,0x92,0x6c,0x95,0x1a,0x8d,0x0f,0xa7,0x1f, - 0x4f,0xde,0x1a,0x94,0x3f,0x84,0xcd,0x16,0xd6,0xb7,0x9b,0x8f,0x1a,0xcf,0xb7,0xd0, - 0x85,0x1f,0x92,0xd8,0xcf,0x84,0x3d,0x29,0xd3,0x57,0x70,0xb5,0x99,0x57,0x8e,0x33, - 0xbb,0x79,0xb4,0x38,0x87,0xc7,0xc6,0x27,0x80,0xd1,0x67,0xbf,0x2d,0x2b,0x21,0x74, - 0x00,0x24,0xde,0x42,0x93,0x90,0x66,0xa1,0xa5,0x82,0x5a,0x1d,0x93,0x29,0xa1,0x5f, - 0xae,0xf9,0xe1,0xce,0x4a,0x1f,0xa3,0xd2,0x9c,0x32,0xf5,0x93,0x8a,0xf3,0x23,0x2c, - 0x04,0x2c,0x01,0x14,0x15,0xee,0xb6,0x14,0xfe,0x1f,0x75,0x38,0xf1,0x95,0x6b,0xfb, - 0x4e,0x22,0x92,0x36,0xfa,0x09,0xea,0xb3,0x8f,0x18,0xbf,0x42,0xc6,0xca,0x4c,0x3a, - 0xd2,0x4e,0xbe,0xb3,0xff,0x80,0x86,0x86,0x72,0x5c,0x34,0xce,0x28,0x7f,0x8e,0x94, - 0xd6,0xd8,0xff,0xa5,0xb8,0xc1,0x1a,0xed,0x4e,0x13,0x5a,0xb3,0x0d,0x91,0x0a,0xd0, - 0xe8,0xec,0x5a,0xce,0xd7,0xdb,0x17,0xd6,0x75,0x9e,0x72,0xe3,0x78,0x7d,0xef,0xa9, - 0x4a,0xcb,0x67,0x68,0x83,0x67,0xfb,0xc4,0xe9,0xbb,0xb7,0x1f,0xd6,0xf0,0x10,0x38, - 0xfb,0x20,0xe2,0xcb,0xd6,0x95,0xc4,0xc2,0x2c,0x5e,0x22,0x78,0x91,0xaf,0xf7,0x24, - 0x0a,0x40,0xe5,0xc1,0x60,0xc3,0x28,0xc0,0xb2,0x20,0x6f,0x5f,0xae,0x94,0xc0,0x38, - 0x02,0x16,0x28,0x20,0xc6,0x40,0x4b,0x89,0x63,0x18,0x00,0x89,0x7e,0x48,0xb8,0x5a, - 0xe7,0xa4,0x0c,0x01,0x42,0x99,0x8a,0x1a,0x1f,0xde,0x9f,0xbe,0xfe,0x6d,0x1d,0x2d, - 0x21,0x13,0x55,0xd6,0x99,0xe5,0xdb,0xd3,0xb3,0x67,0x6f,0x7f,0x3a,0xb3,0xdf,0xee, - 0x38,0x1d,0xa7,0x65,0xbf,0x6d,0xb7,0x9d,0xb6,0xf3,0xd0,0x31,0x63,0x8d,0xa2,0xdd, - 0x66,0xf9,0x99,0xc7,0xf6,0xe9,0xec,0x05,0x65,0x79,0xca,0x83,0x41,0x56,0x1f,0x03, - 0x88,0xf1,0x3a,0x5c,0x47,0x9e,0x1c,0xa9,0x2d,0xd1,0xde,0x5d,0x77,0x77,0x17,0x18, - 0x12,0xe2,0x8f,0x01,0xfa,0x4b,0x98,0x29,0x26,0x0a,0x52,0x12,0x90,0x31,0x3c,0x9e, - 0xf5,0x13,0x3c,0x3b,0xfa,0x06,0x3c,0x26,0x0d,0xa7,0xf1,0xda,0x9c,0x45,0xa3,0x83, - 0x38,0x38,0x74,0x88,0x98,0x85,0xa4,0x32,0x3e,0xb8,0xa8,0xd8,0x8f,0xa3,0x2a,0x2c, - 0xd1,0xfe,0xd6,0x56,0x70,0xc9,0x43,0xe8,0xb1,0x0a,0x2e,0xc5,0x58,0xca,0xe8,0xc4, - 0x28,0x5c,0x2d,0x98,0xe3,0x49,0xca,0x25,0x0a,0xdb,0x54,0x2b,0x61,0xd0,0xb3,0x9f, - 0x5e,0xde,0x84,0x3a,0xb1,0x27,0x8e,0x42,0x3e,0x77,0x73,0xb4,0x8c,0xa3,0xde,0xce, - 0xb1,0x13,0x79,0xb4,0x04,0xd0,0x15,0xd8,0x52,0xa9,0x8c,0x09,0xd3,0xbc,0xfc,0x1c, - 0xe3,0x95,0x8f,0xc1,0x61,0x19,0xfb,0xfe,0xbd,0xfe,0xc0,0x02,0x3e,0xf9,0x07,0x24, - 0xd1,0x0c,0x77,0x67,0x94,0x08,0xd7,0x41,0x0a,0xec,0x1c,0x52,0x7e,0x0b,0xef,0x7f, - 0x76,0x04,0x25,0xd1,0xd1,0xff,0x1f,0x42,0x28,0xe5,0x40,0xff,0x0d,0x31,0x94,0x7f, - 0x25,0x74,0x72,0x4d,0xfb,0x64,0xab,0x93,0x9f,0xac,0x7e,0x20,0xeb,0x35,0x3b,0xaf, - 0x3e,0x60,0x85,0xe3,0xae,0xb1,0x3c,0x39,0x57,0xd6,0x18,0xda,0x84,0x91,0x6d,0x36, - 0x5d,0x26,0x9b,0x51,0xca,0x0c,0xd0,0x1e,0x91,0xb6,0x0b,0x16,0x8d,0x46,0xf9,0x39, - 0xfa,0xce,0xa3,0xbf,0x69,0x38,0x44,0x31,0x78,0x89,0x81,0xce,0xbd,0xc9,0xa3,0x6f, - 0x1a,0x03,0x17,0xb3,0xb8,0xe9,0xa5,0x6b,0x8e,0xfa,0xd7,0xe5,0xc0,0x6f,0x5d,0x14, - 0x4d,0x42,0x3c,0x5d,0x5f,0xf3,0x70,0x7d,0x09,0x76,0x2d,0xf3,0x5d,0x1e,0x45,0x1f, - 0xc9,0x31,0xa8,0x41,0xee,0xcb,0xe4,0x22,0x14,0x92,0x11,0xa8,0x82,0x36,0xeb,0x35, - 0x2c,0xf2,0x22,0xc0,0x46,0x6f,0x16,0x83,0x3b,0x47,0xd1,0x1b,0x7a,0x4d,0x5e,0xae, - 0x18,0x9b,0x05,0xbf,0x2b,0x4e,0xb4,0xeb,0x52,0x4c,0xf6,0x95,0x7c,0x2c,0xc9,0xfc, - 0x89,0x6e,0x1d,0x12,0x5c,0x85,0x13,0x00,0x40,0x33,0x4d,0x20,0x74,0xda,0x34,0x22, - 0x37,0x13,0x09,0xab,0xf4,0x8c,0x56,0x7b,0xad,0x4a,0xde,0x3e,0xca,0x9c,0x56,0x4a, - 0xd9,0xd7,0x32,0x8f,0x1e,0x70,0xac,0xa2,0xb4,0xa8,0xc2,0x21,0x95,0x7e,0x7e,0x0d, - 0x56,0xbc,0x4a,0x3c,0x8c,0x92,0x77,0x63,0xa3,0xf1,0xcb,0x8f,0x16,0x75,0x75,0xc8, - 0x69,0x3e,0x79,0x5a,0xb1,0x9b,0x5c,0x61,0xca,0xc2,0x38,0xcf,0x58,0x43,0x26,0xee, - 0x6d,0x2e,0x54,0xf4,0xfc,0xf4,0x53,0x8f,0xec,0xbe,0xca,0xa3,0xa4,0xb6,0x35,0x3c, - 0x36,0xf2,0x56,0x35,0xf7,0xe0,0x69,0x18,0x8a,0x7b,0xbc,0x70,0xd5,0xc3,0x30,0x3d, - 0x31,0x41,0xad,0x1c,0x8b,0xf5,0x84,0x20,0xbb,0x76,0x0c,0x21,0x6e,0x67,0x22,0x11, - 0xc6,0xad,0x97,0x39,0x6b,0xe3,0x16,0x7a,0xd7,0x3e,0x16,0xb3,0xc8,0xea,0x04,0x88, - 0x00,0x94,0x06,0xed,0xf8,0xab,0x58,0xf1,0x89,0x72,0x63,0x27,0x1b,0xd9,0x7c,0x82, - 0xd9,0x5d,0x05,0x13,0x06,0x3d,0x15,0x80,0xee,0x32,0x97,0x86,0x71,0x00,0xb5,0xef, - 0xd1,0xc1,0x5d,0x12,0x05,0x18,0x4a,0x42,0x87,0x21,0x7a,0x1a,0x03,0x28,0x03,0xcd, - 0xdc,0xd1,0x59,0x2b,0x86,0xf1,0x7c,0x9c,0x85,0x29,0x65,0x76,0x91,0x31,0x8e,0x89, - 0x9b,0xc1,0xc2,0x62,0x5e,0x17,0xe1,0xef,0x89,0x79,0x14,0x5c,0x03,0x85,0x3c,0xc7, - 0x78,0x21,0x5d,0x40,0xc9,0xd0,0x2f,0x1d,0x04,0x25,0x93,0x87,0x7a,0x78,0x93,0x83, - 0xf1,0xab,0x37,0xe0,0x9b,0xb5,0x28,0x92,0x85,0x72,0x85,0x68,0x12,0x82,0x74,0xdd, - 0x00,0xc4,0x20,0xd3,0x3f,0x4c,0xc9,0x4d,0x12,0xff,0xda,0x2b,0x7b,0x82,0x02,0xf7, - 0x52,0xe0,0xe3,0x64,0x6a,0x22,0x49,0x09,0x3f,0x00,0x67,0x31,0x8f,0xf2,0xae,0x70, - 0xc4,0x74,0xee,0xe0,0x3c,0x39,0xdc,0x86,0xaa,0x75,0xf0,0x94,0x09,0xbd,0x4c,0x90, - 0x2b,0x10,0x21,0xe8,0xb8,0x15,0x13,0x5e,0xa1,0x46,0x01,0xa2,0x45,0x93,0xb8,0x46, - 0xdf,0x14,0xd3,0xe4,0x7c,0x53,0x06,0xd5,0xd0,0xc8,0x5a,0x0d,0x6f,0x13,0xee,0xe0, - 0x7c,0x87,0x59,0xde,0x8b,0x3c,0x64,0x81,0x8d,0x1e,0xc0,0xa3,0x48,0x9a,0x7f,0x64, - 0x9c,0xd2,0x0b,0x09,0xe0,0x2a,0xb3,0x7a,0x74,0x0f,0x94,0x92,0xd0,0x3c,0x92,0xeb, - 0x63,0xd0,0x73,0xa9,0xdd,0x22,0x70,0x55,0x8e,0x1e,0x6c,0x8b,0x32,0xf4,0xe8,0x40, - 0x2a,0x71,0x88,0xaa,0xd0,0x23,0x6b,0x8a,0x29,0x94,0x3c,0x58,0x9f,0xd7,0x71,0x74, - 0x4d,0xa0,0x59,0x57,0x22,0x8a,0xbd,0x20,0x20,0xa0,0xc1,0xde,0xa5,0x98,0x01,0xad, - 0x8d,0x82,0x1a,0xc4,0x3a,0x9c,0x2b,0x21,0xaa,0x8f,0x5d,0xa6,0x07,0x53,0x43,0x66, - 0x58,0x15,0xf6,0x6b,0x61,0xf4,0xfb,0xad,0x56,0x0b,0x11,0x64,0x9e,0xa2,0xfb,0xd1, - 0xbf,0x7f,0xd4,0xb8,0xf9,0xe4,0x56,0x8b,0xe1,0x99,0x52,0xfd,0x36,0x66,0x31,0xaa, - 0xa1,0xed,0x56,0x4b,0xb9,0x5d,0x53,0x6c,0xbd,0x9c,0xcd,0x2a,0x2e,0x9b,0xaf,0x0e, - 0x9a,0x77,0x4a,0x29,0x8c,0x40,0xf7,0x47,0x8c,0x2b,0x73,0x51,0x99,0x80,0xa8,0x4a, - 0xea,0xb4,0xb0,0x8b,0xc3,0x6d,0x3d,0xe7,0x55,0x4a,0x5e,0xdf,0x6e,0xc2,0x41,0x16, - 0xba,0x16,0xc7,0x39,0xe4,0x79,0x18,0xf2,0x81,0x5a,0x1c,0xa8,0x57,0x4d,0xca,0xbc, - 0x33,0x38,0x2a,0xe8,0x6b,0x69,0x6c,0x6a,0x0a,0x5d,0xbd,0xf8,0x60,0xa4,0x53,0x4d, - 0x44,0x40,0xf9,0x36,0xc9,0x4e,0x82,0x80,0x85,0x11,0x7c,0x28,0x84,0x38,0xd4,0x34, - 0x31,0xcd,0xc7,0x55,0x92,0x37,0x40,0x10,0x2e,0x04,0xd5,0x70,0x8e,0x03,0x2d,0xb2, - 0x42,0x12,0x7f,0x9a,0xbe,0x5f,0xce,0xc9,0xdc,0x05,0x42,0x81,0x96,0x5f,0xce,0x67, - 0x0c,0x54,0x1a,0x70,0x00,0x2d,0xbc,0x5a,0x4a,0x63,0x72,0x2c,0x26,0xbf,0x2f,0x20, - 0xcd,0x15,0xb0,0xa9,0x6c,0xbc,0xb9,0x3b,0x36,0xa5,0xe5,0xa5,0x97,0x95,0x30,0x15, - 0x91,0x62,0x98,0xe3,0x35,0x07,0xb7,0x2a,0x3f,0x08,0x43,0xb4,0xa4,0x4c,0xe8,0x6a, - 0xf2,0x0a,0xb8,0xfe,0x15,0x85,0x82,0x14,0x44,0xd2,0x28,0xb2,0x64,0xe6,0xd1,0x72, - 0xa4,0x15,0x6d,0xe2,0xdf,0xd4,0x37,0xb1,0x18,0xea,0x1b,0xfa,0x7d,0x81,0x0f,0xaa, - 0xdb,0x62,0x4a,0x28,0x05,0x53,0x74,0xe7,0x02,0xb0,0xe0,0xbb,0xa5,0xe1,0x4d,0x2a, - 0x17,0x26,0x29,0x81,0x25,0x4c,0x25,0xf7,0x33,0xdd,0x5c,0xa0,0xb6,0x8b,0xc0,0x11, - 0x5d,0x82,0xc3,0x7c,0xbc,0x42,0x82,0xa3,0x9f,0x47,0x05,0x4c,0x92,0x37,0x1a,0xc5, - 0x51,0xdc,0x24,0xcf,0x19,0x4e,0x88,0x5b,0x41,0x10,0xf1,0x9e,0x1b,0xf2,0x6e,0x7c, - 0xcc,0x85,0x9a,0xa3,0x45,0xc5,0xa6,0xb3,0x8b,0x67,0xa2,0xe5,0x3c,0xe7,0x5a,0x2a, - 0xbd,0xa2,0x15,0x55,0x4b,0x9b,0x50,0x4d,0xd9,0x4b,0x29,0xa4,0x32,0x3f,0x9c,0x79, - 0xd5,0x64,0x5c,0x2a,0xf7,0x1e,0x08,0x1c,0x55,0x39,0x0b,0xa5,0x2c,0x4a,0x9a,0x2b, - 0x33,0x3c,0x3a,0xcf,0xd5,0x55,0x86,0x7c,0x65,0x4c,0xf9,0x26,0xbc,0xf1,0xa5,0x95, - 0x0b,0x4a,0x81,0x7f,0x74,0x06,0x32,0x2b,0xc6,0x1d,0x4b,0xe3,0x5c,0x23,0x22,0x63, - 0xdd,0x0b,0x54,0x70,0x02,0x7c,0x63,0xc9,0x4c,0x16,0x40,0x86,0xf1,0x88,0x93,0x8e, - 0x4e,0x29,0xc7,0x54,0x08,0xe8,0xe3,0x1c,0x0e,0x92,0xb2,0x79,0xa8,0x36,0xf0,0xac, - 0x46,0x18,0x7c,0x3d,0x06,0xe1,0x0a,0x18,0xb6,0x0d,0xa4,0x40,0x66,0xdb,0xf8,0x94, - 0x62,0xb2,0x2a,0xce,0xf4,0xf4,0x1a,0x61,0x13,0x7a,0x64,0x9f,0x32,0xb6,0xf1,0xeb, - 0x69,0x06,0xeb,0xa6,0x44,0x33,0x11,0x5b,0x28,0x90,0x04,0xe6,0xa2,0x4d,0x8b,0x32, - 0x9b,0x9d,0xba,0x63,0x10,0x3b,0x50,0x6e,0x7a,0x31,0x49,0x22,0x4a,0xb1,0xc7,0x63, - 0xe7,0xdb,0x4e,0x9d,0x72,0xa5,0xbf,0x47,0xc8,0x14,0x80,0x8c,0x56,0x67,0xf0,0x4c, - 0xa6,0xfe,0x22,0xc4,0x98,0x25,0x40,0x30,0x26,0x59,0x16,0x77,0xb7,0xb7,0xdb,0xcf, - 0x3b,0x4e,0x7b,0xff,0xc0,0xd9,0x75,0xda,0x44,0x71,0x55,0x9b,0xa0,0x6e,0x06,0x4b, - 0xa2,0x36,0x6b,0x4c,0xec,0x78,0xc9,0x8e,0x59,0x9a,0xa0,0x3c,0x81,0x83,0x99,0xcb, - 0x64,0x8c,0xcd,0x53,0x4c,0x36,0x42,0x10,0xd1,0x93,0x8b,0x39,0xc6,0x7f,0xe9,0x99, - 0xda,0x38,0xde,0xc8,0x4f,0x31,0xa3,0x09,0xba,0x89,0xf8,0x32,0x7e,0x23,0x37,0x28, - 0x79,0x9c,0xb6,0x24,0x37,0x3d,0x89,0x55,0x16,0x88,0x67,0x1b,0x73,0x6e,0x03,0x23, - 0xa7,0x93,0x6b,0x5f,0xa4,0xe1,0x32,0x44,0x6a,0x7a,0xe4,0xf1,0xb9,0xe3,0x48,0x29, - 0x53,0xe2,0xea,0x48,0xc6,0xd6,0xd2,0x48,0xc6,0x51,0x34,0x9c,0x51,0xea,0xae,0x4b, - 0x2f,0x7b,0x19,0x78,0xf8,0xf3,0xc7,0xdb,0xd7,0xa3,0xc6,0xa6,0xd8,0x8c,0x9b,0x96, - 0x43,0x40,0x7c,0x03,0xc4,0xc5,0xc1,0xd0,0x29,0x60,0x26,0x9b,0x18,0xdf,0xba,0xb9, - 0x66,0xe8,0x23,0xac,0xef,0x80,0x22,0x45,0x45,0xc8,0xa3,0xc6,0x84,0x35,0x7a,0x21, - 0xc4,0xee,0xb5,0xc8,0x04,0x97,0xd5,0x09,0x85,0x00,0x46,0x85,0x9a,0xa9,0x34,0x7c, - 0x44,0x3c,0x76,0x76,0xf3,0x6c,0xdb,0x3b,0xbb,0xec,0x5b,0xa1,0xb1,0x90,0x32,0xf9, - 0xe0,0x9c,0xb4,0x94,0x14,0xbe,0xd0,0xaf,0x08,0x36,0x66,0x33,0x41,0x21,0x0d,0xa7, - 0x22,0x1b,0xb1,0x5e,0x7e,0x9a,0x5e,0xaa,0x81,0xd4,0x24,0x78,0x29,0x90,0x36,0xf3, - 0x28,0xcf,0x38,0x19,0x17,0xc5,0x39,0xad,0x45,0x3f,0x5e,0x8e,0xcc,0xef,0xaa,0x91, - 0xc6,0x5d,0x24,0x17,0x87,0x6e,0xb1,0x05,0x4c,0xb6,0x0a,0x12,0xa3,0x42,0x88,0xca, - 0xea,0x75,0x5a,0xd5,0xa4,0xfd,0x38,0x3c,0xb7,0x30,0xc4,0xfa,0x61,0x68,0xf9,0x8c, - 0x3b,0xfb,0x35,0xcd,0xe8,0x43,0x19,0x46,0xb3,0x30,0x33,0x97,0xe1,0x06,0xa0,0xbe, - 0x1f,0x67,0x47,0x4f,0x4c,0xcc,0x83,0x27,0x1c,0xbb,0x7a,0x4f,0x00,0x80,0xc6,0xe9, - 0xcb,0x77,0x67,0xaf,0xdf,0xbd,0x7c,0xd3,0x37,0x7f,0x10,0xff,0x13,0x1f,0xd2,0xac, - 0xbf,0x40,0x45,0xa2,0x6b,0x9a,0x36,0x45,0xfe,0x8d,0xba,0x84,0xa1,0xf6,0xc4,0x4d, - 0xdf,0x82,0xf6,0x9d,0x3f,0xbd,0x08,0x7c,0xf1,0x20,0xed,0x78,0xa7,0x9c,0xb0,0x40, - 0x55,0x09,0x3d,0x6f,0x94,0x4a,0x0b,0xa0,0x78,0x07,0xac,0x2d,0xed,0xb6,0xec,0xe1, - 0xf8,0xb2,0x1b,0xce,0x82,0xc0,0x46,0xdf,0xa7,0xee,0xe2,0xde,0xa6,0xab,0xf2,0xf0, - 0x07,0xe7,0x9c,0x4a,0xbb,0xe7,0x17,0x76,0x48,0xca,0x3a,0x94,0x26,0x8d,0x1f,0xcf, - 0x4d,0x12,0x78,0x00,0x6a,0x98,0x75,0x17,0x68,0x99,0xa0,0x32,0x68,0x53,0x80,0x1f, - 0xf7,0x36,0x72,0xf1,0x33,0x80,0xa4,0x87,0x57,0xa6,0xf1,0x13,0xd7,0xb8,0xef,0x3d, - 0x79,0x32,0x9e,0x85,0x6c,0x9f,0xfe,0xbe,0x91,0x5a,0x0b,0xb1,0xf5,0xd4,0xee,0x05, - 0x19,0x2c,0xb9,0x3d,0x25,0x03,0x5b,0x94,0x40,0x81,0x7b,0xad,0xbc,0x5e,0xe1,0x24, - 0x49,0xdc,0x5b,0x10,0x3b,0xa2,0x2c,0x42,0xe1,0xc4,0x49,0x03,0xa4,0xc9,0x43,0x17, - 0xa4,0xcd,0xfa,0xb6,0x50,0x0e,0x4d,0x2d,0xbd,0x3d,0x62,0xf7,0x8d,0xa9,0xb5,0x40, - 0x60,0x67,0xfd,0xef,0x1b,0xe6,0x77,0x2c,0x01,0x58,0xbd,0xcc,0x41,0x32,0xf4,0x42, - 0xdc,0x72,0x3d,0x85,0xe7,0x9c,0x76,0x00,0x32,0x36,0x4c,0x24,0x1c,0x50,0x8e,0x4e, - 0xa2,0x70,0x6e,0x68,0x09,0xcb,0x9c,0xcf,0x13,0xac,0xfa,0x79,0xd2,0x07,0xb0,0xc9, - 0xb7,0xb2,0xbb,0x86,0xb5,0xc8,0xaa,0x14,0x48,0x34,0x74,0x6f,0x77,0x40,0xb3,0xd1, - 0x07,0x07,0xf2,0x99,0x36,0x5b,0xf4,0xca,0x0b,0x2f,0xe1,0x85,0x93,0x70,0xd2,0xa4, - 0xc6,0xf6,0xf9,0xc6,0xe1,0x91,0x79,0xb1,0x7d,0x69,0xab,0x0e,0x86,0xb2,0xf8,0xc2, - 0xdc,0x30,0xbb,0x26,0x1d,0xf4,0x99,0xb6,0x79,0x88,0xbf,0x83,0x0c,0x7f,0x1e,0xe1, - 0xcf,0x4b,0xfc,0xb9,0x69,0x6e,0xc2,0xcf,0x3f,0x67,0x11,0x3c,0xdc,0x9f,0x0f,0x2f, - 0xee,0xa1,0xef,0xbc,0x73,0xd0,0xa3,0x1a,0x98,0x34,0xc4,0x8e,0xe2,0x0c,0x06,0x01, - 0xe8,0x8c,0x3f,0xfa,0xf8,0x9f,0xbb,0xbb,0xc5,0x3d,0xa6,0xa7,0xf6,0xc7,0x8d,0xa7, - 0xf8,0x0c,0x20,0x07,0x51,0xda,0xca,0x7f,0x22,0x93,0x6a,0xa6,0x00,0x70,0xcf,0xec, - 0x19,0xdb,0xdb,0x74,0x59,0x09,0x7d,0x48,0x51,0xbf,0x82,0xbd,0xf5,0xf3,0xd9,0xd9, - 0x07,0xe3,0xe4,0xc3,0x6b,0xe3,0xef,0x2f,0xcf,0x52,0xc3,0x1d,0x26,0x11,0xb0,0x9c, - 0x14,0xb6,0x37,0xf4,0x9c,0x72,0xcb,0xd4,0x5a,0xc6,0x00,0xdc,0xd8,0x10,0xb2,0xc2, - 0xc9,0x00,0x10,0xfa,0x05,0x5b,0x4f,0x02,0x2f,0xa1,0x61,0x19,0xd8,0x03,0xe6,0x7b, - 0xcc,0x2f,0x56,0xe1,0xfc,0x1c,0xa8,0x22,0xb8,0xc6,0xd8,0x43,0xf7,0x1a,0x79,0x58, - 0x82,0x96,0x8d,0x6b,0x4e,0xda,0xe6,0x02,0x13,0x77,0x47,0xda,0x61,0x49,0x4f,0x36, - 0xe6,0x62,0x2f,0x98,0x00,0x84,0x84,0x16,0x17,0x7b,0xda,0x4c,0x29,0x4f,0xe4,0x2d, - 0x39,0x75,0x53,0x04,0x6d,0x8a,0xda,0x04,0x72,0x30,0xaa,0x85,0xe8,0x33,0xcc,0x82, - 0x3e,0x9e,0x8f,0x94,0x06,0xd9,0xb0,0x7a,0x34,0x17,0xcc,0x5e,0xe5,0x06,0x7d,0x28, - 0x25,0x7e,0x72,0x7f,0xf5,0x68,0x82,0xa5,0x68,0x18,0x0d,0xc0,0x0b,0x1d,0x14,0x74, - 0x85,0xc5,0x3d,0xfc,0x2b,0xf9,0x15,0x4e,0x4f,0x5b,0x28,0x07,0xa3,0x83,0xf3,0x96, - 0x24,0x88,0x00,0xa0,0x89,0xf0,0x61,0xea,0xf7,0xfb,0xbb,0xad,0xb6,0xb5,0x40,0xac, - 0x7b,0x83,0xe9,0x6e,0x61,0x80,0xd9,0x04,0x13,0x75,0xe3,0xe0,0x5f,0x26,0x09,0xec, - 0x39,0x13,0x69,0x0d,0xa0,0x24,0xd5,0x15,0x3d,0x25,0xce,0x1f,0x29,0x8e,0xad,0xd4, - 0xc3,0x1f,0xa2,0x07,0x46,0x87,0xc4,0x89,0xae,0x36,0x36,0x64,0x57,0x4f,0xfb,0xfd, - 0x4e,0xab,0xa3,0x0a,0x30,0x98,0xbc,0x7e,0xde,0xcf,0x1f,0x78,0xf1,0x42,0x94,0xdc, - 0xdd,0x35,0x4c,0x42,0x09,0x73,0x4b,0x56,0xb5,0xac,0x9e,0xaa,0xe5,0xc9,0x91,0xcb, - 0x8f,0x3d,0x0f,0xf6,0xc0,0x9f,0x40,0x77,0xff,0xe0,0xbf,0x79,0x51,0x9e,0x88,0x27, - 0x5f,0xdc,0x8b,0xbf,0x62,0x0a,0x7f,0xf0,0xfb,0x7b,0x06,0x22,0x5d,0xa7,0xae,0xf0, - 0x3d,0x8e,0x80,0x10,0x10,0x1c,0x31,0x65,0xb8,0xda,0x75,0x6a,0x1b,0x2c,0xa6,0x1e, - 0x08,0x95,0xa3,0xae,0xf9,0xe1,0xfd,0xe9,0x99,0x69,0x73,0x96,0xe9,0xb4,0xbb,0x30, - 0x05,0x85,0x68,0x9e,0x01,0x09,0x82,0xbd,0x85,0xa1,0xb8,0x22,0x5b,0xd1,0x36,0x02, - 0xcc,0xbc,0xa7,0x06,0xbb,0xff,0x38,0x7d,0xff,0xce,0x49,0x69,0x0f,0xfb,0xe3,0xdb, - 0x06,0x75,0x72,0xaf,0xef,0xf6,0xe9,0xd5,0x47,0xef,0x4f,0x10,0x56,0x08,0x5c,0x08, - 0x28,0x97,0x00,0xf5,0xc9,0x0f,0xb3,0x03,0xa2,0x74,0x8d,0x03,0x4b,0xec,0x3a,0xb1, - 0x1b,0x86,0xc9,0x6d,0x9c,0x45,0x1b,0x1b,0xfc,0x17,0x85,0x9e,0x8f,0x2e,0x7c,0x98, - 0xfe,0x27,0x85,0x07,0x58,0xf5,0xaf,0x1b,0x2e,0xb5,0x42,0x71,0x11,0xb0,0x1b,0x1a, - 0xd8,0x93,0xdf,0x6f,0xf5,0xfc,0xc3,0x83,0x9e,0xbf,0xb5,0x65,0xb9,0xe7,0xfe,0x45, - 0xff,0x2d,0xcc,0xd8,0x21,0x0b,0x72,0x83,0x7e,0x26,0xd4,0x40,0xc3,0xfa,0xa1,0xb3, - 0xb7,0x4f,0xd5,0x97,0x50,0xe0,0xa9,0x1b,0x33,0xfd,0x75,0x73,0x92,0x34,0x10,0xeb, - 0xcf,0x55,0x1a,0x83,0xc3,0xf6,0xfe,0xb1,0xd9,0x02,0x48,0x99,0xd6,0xd6,0xc0,0xc9, - 0x22,0x41,0xd7,0xda,0xfb,0x62,0x55,0x9c,0x3f,0x22,0xc0,0x49,0xf8,0x8a,0xcb,0xa3, - 0xd3,0xa3,0x38,0xb8,0x7d,0xe5,0xc1,0xf2,0x03,0x47,0x62,0x20,0x01,0x33,0xd8,0x3c, - 0x97,0x47,0x4d,0x17,0x20,0xdc,0xc1,0x7c,0x5e,0x02,0x99,0xc9,0x91,0xd3,0x0b,0x44, - 0xe7,0x5e,0xa0,0xd1,0xdd,0x2c,0xba,0xbc,0x0c,0x80,0xee,0x92,0x2c,0x62,0x3f,0x6d, - 0xc0,0x2b,0xe4,0x83,0x1b,0xef,0xe8,0xdc,0x1c,0x2a,0x39,0xd8,0x28,0x6c,0x4c,0x7c, - 0x6d,0x59,0x0a,0x5b,0x78,0x59,0xc8,0xa1,0xfa,0x85,0x9b,0x8c,0x88,0x5b,0x54,0x02, - 0x42,0xe4,0x1a,0xa9,0x62,0x96,0xfa,0xb5,0xc6,0x10,0x1a,0xed,0xce,0xc1,0x1d,0x02, - 0x99,0x7b,0x85,0x19,0x9a,0x4e,0x31,0x93,0xc0,0x92,0x59,0xae,0x9a,0x60,0x9a,0x39, - 0x15,0x99,0xc0,0xba,0x97,0x03,0xc5,0xcf,0x2c,0x3f,0x08,0x58,0x11,0x2d,0x0b,0xfc, - 0x33,0x77,0x00,0x33,0xdc,0xfc,0x4e,0xbb,0xee,0xf9,0x5c,0x4f,0x57,0x01,0x00,0xef, - 0x41,0x75,0x2e,0x69,0xf1,0x1f,0xc9,0xd2,0xc4,0xe6,0x25,0x76,0x4a,0xd6,0x77,0xab, - 0xcc,0x3c,0x69,0x68,0x8a,0x98,0x41,0x3b,0x3c,0x0a,0x94,0x69,0x2c,0xc6,0x94,0x1e, - 0x1a,0xf1,0x81,0x1a,0xbf,0x0f,0x3d,0xe3,0xe7,0xb3,0xb7,0x6f,0x40,0xd5,0x43,0x23, - 0xb9,0x08,0xe9,0x42,0x2a,0x7e,0x6b,0xbc,0x3c,0xfd,0xb0,0xd3,0x21,0x5b,0x31,0xa7, - 0xfc,0xc5,0x78,0xff,0xb9,0x4b,0x29,0x8a,0x71,0x9a,0x29,0xea,0x20,0x94,0xa4,0xd3, - 0xcf,0xb8,0x31,0x77,0x98,0xcd,0x00,0x3f,0x6f,0xd1,0xd3,0x1d,0x33,0xba,0xd2,0x29, - 0xc5,0x20,0xf1,0x47,0x97,0x5e,0x0f,0x99,0x02,0x80,0xc8,0x0f,0x5d,0x68,0x79,0x30, - 0xf3,0x83,0x11,0xe7,0x57,0xb8,0x16,0x59,0x96,0x13,0x97,0x9a,0xca,0x80,0x97,0x70, - 0x63,0x48,0x41,0x91,0xa9,0x08,0x73,0xbe,0x30,0xba,0x93,0x0e,0x84,0x8e,0x5c,0xe1, - 0x08,0x19,0x11,0xe5,0x41,0x46,0xdc,0x75,0x04,0xfe,0xe0,0xa1,0xc9,0x2a,0xd8,0x92, - 0x6f,0xbc,0x00,0xae,0x28,0x6b,0x89,0xbf,0x05,0xf0,0x32,0x70,0xa9,0xf4,0x0a,0xe8, - 0x62,0x29,0xcc,0x53,0xf7,0x40,0x11,0x4a,0xa4,0xfd,0x40,0x99,0xf2,0x69,0xcf,0x83, - 0xbd,0x6a,0x29,0xae,0xad,0x82,0x30,0x65,0x3e,0x26,0xe1,0x35,0xc5,0x39,0x8d,0xfd, - 0xcb,0x99,0xb8,0x27,0x83,0xce,0x28,0xa0,0x9a,0xab,0x9d,0xba,0x70,0xdd,0x37,0x27, - 0xef,0x1c,0x53,0xeb,0x3e,0x4f,0x77,0x5b,0xea,0x9e,0x93,0xdf,0x42,0x97,0x9c,0xc1, - 0x35,0x14,0xc9,0x5d,0x73,0xc7,0x64,0xbd,0x15,0x2d,0x6f,0x60,0xa9,0x19,0x4e,0xc9, - 0x54,0x28,0xab,0xa7,0x70,0xb2,0x1c,0x1f,0x66,0x99,0x20,0xee,0x42,0x97,0xa5,0xbc, - 0x7f,0x95,0x74,0x4e,0x35,0xcd,0x50,0xb2,0x22,0xcb,0x91,0xea,0xad,0x26,0x1e,0x88, - 0x3c,0x57,0xf7,0x44,0x22,0xb7,0x7f,0x00,0x15,0x53,0xfe,0xcf,0x20,0xd5,0x53,0x7b, - 0xfe,0x61,0x3b,0xa7,0xa1,0x7c,0x2a,0x8c,0xbb,0x1c,0xd9,0x9a,0xb9,0x0d,0xff,0xdd, - 0x16,0x8e,0xcd,0x65,0xb6,0x9e,0x0a,0x62,0x00,0x6b,0x8b,0xfa,0x47,0x3f,0xa5,0x3f, - 0x3d,0x5c,0x6a,0x52,0x43,0xe0,0x05,0xfe,0xe8,0xe5,0xdb,0xb6,0xff,0xf4,0x29,0x14, - 0x82,0x1f,0x3d,0x45,0x50,0xe8,0x15,0x0c,0xbe,0x57,0x47,0x81,0xe8,0xa3,0x7c,0xfb, - 0x39,0x95,0xaf,0xb1,0x6c,0x41,0x53,0xa1,0x72,0xf4,0xe6,0xb3,0x2c,0xdd,0x13,0xe4, - 0xb2,0x8f,0xf2,0x66,0xec,0x0e,0xfc,0xc0,0xcf,0x7c,0x0f,0xe4,0xd2,0x16,0xd5,0x26, - 0x3d,0x05,0x07,0x88,0x41,0xc6,0xde,0x67,0x7a,0xbc,0xbb,0x4b,0x9d,0x04,0xb3,0x51, - 0x4c,0xd5,0x8b,0x56,0x4e,0xa4,0xc4,0x8d,0x15,0xc5,0xf5,0x85,0x5e,0xe1,0xe5,0xdd, - 0x9d,0x29,0x4d,0x27,0xa6,0x5e,0x01,0xef,0xaf,0x28,0x97,0x47,0x42,0xb4,0x65,0x1a, - 0x3f,0x80,0x1c,0x93,0x3a,0xe3,0xb9,0xfa,0x49,0x8e,0xb7,0x3d,0x45,0x5c,0x07,0x7d, - 0x6e,0x82,0xaf,0xae,0xb0,0x7a,0x83,0x1a,0x9d,0x20,0xdf,0x4b,0x4c,0x1d,0x79,0x15, - 0xfa,0x7d,0x93,0x8c,0x2e,0xa6,0xb5,0x18,0x94,0x50,0x1a,0xef,0x1c,0x30,0x0b,0x4d, - 0xb1,0x92,0xc2,0xc5,0xef,0x91,0xdf,0x97,0xeb,0xc0,0x7e,0x31,0x59,0x42,0x2a,0x31, - 0xd7,0x9e,0x2e,0xf7,0xe5,0x2b,0x7c,0x9c,0x63,0x8d,0x50,0x0b,0x91,0x06,0x60,0xae, - 0x10,0x5d,0x74,0x15,0xfa,0x47,0xae,0x38,0xde,0xdf,0x5b,0x5d,0xfd,0x51,0xb0,0xf9, - 0x22,0xbe,0xc5,0x39,0xbe,0x89,0xa2,0xfd,0x58,0xfe,0xba,0xbb,0x3b,0xbf,0xc8,0x21, - 0x91,0x55,0x40,0x81,0x74,0xf8,0x57,0xca,0xb9,0xd5,0x10,0x22,0x2b,0x49,0x37,0x82, - 0xb7,0x31,0xbe,0x16,0xe4,0xdd,0xbc,0xd0,0x22,0x4f,0x7e,0x75,0xcf,0xc3,0x2a,0xcd, - 0xc7,0xcb,0x05,0x68,0xcf,0x11,0x01,0x3b,0x20,0xd6,0x0a,0x01,0x79,0xb1,0x0c,0x17, - 0x4c,0xcd,0x07,0x0b,0xaf,0x7a,0xf5,0x46,0x46,0x93,0x35,0x08,0x60,0x18,0x66,0x4f, - 0x13,0xfa,0x71,0x4f,0xda,0x9d,0x16,0x69,0x7e,0x52,0x28,0xcd,0x77,0x2c,0x0e,0xba, - 0x71,0x6d,0x2d,0xce,0xcd,0xef,0xe4,0x8d,0x24,0x36,0xfe,0x14,0xb7,0x46,0xd0,0x6f, - 0x4c,0x98,0x75,0x51,0x95,0x0a,0xfc,0x11,0x0e,0x0f,0xfe,0x5b,0x4f,0xa6,0xa1,0xa7, - 0xef,0xa1,0xe5,0xe5,0xa8,0x37,0x8b,0x81,0x23,0x79,0x78,0xd0,0xf2,0xa3,0x9b,0x34, - 0x74,0x49,0x55,0x83,0x24,0x41,0xb5,0x91,0x0f,0xce,0xaa,0xd0,0x23,0x7a,0x5f,0x22, - 0x48,0x08,0x35,0xed,0xea,0x11,0x0b,0xc7,0xf5,0x12,0xcf,0x0e,0xde,0x50,0x10,0x37, - 0xc8,0x60,0xf2,0x34,0x2c,0x17,0x23,0xbd,0x6b,0x5a,0x09,0xef,0xba,0x72,0xce,0x20, - 0x05,0xb3,0x41,0x16,0xf6,0xf3,0x96,0xf1,0xe8,0x08,0xb6,0x56,0x16,0x3a,0xf2,0x12, - 0xef,0x3e,0x9e,0x7b,0xf4,0xf2,0x12,0x98,0x51,0xad,0xb4,0x6a,0xb4,0xc3,0x59,0xa5, - 0x62,0x3c,0x17,0x20,0xff,0x0b,0xc2,0x3f,0x61,0x4f,0x9d,0x06,0xb0,0x90,0xd4,0xac, - 0x9b,0x0f,0x09,0x7d,0xad,0x2c,0x87,0xce,0x8c,0x41,0x37,0xb0,0xa0,0x6e,0x55,0x9f, - 0x7b,0x50,0x1d,0x13,0x1b,0x30,0xed,0x4a,0x75,0xc9,0xfe,0xa3,0xfb,0xc7,0x7d,0x7d, - 0x7b,0x37,0x02,0xbd,0x0b,0x70,0x22,0xd3,0x90,0xda,0x70,0x37,0x0e,0xaa,0x8c,0x80, - 0xa1,0xb0,0xd8,0x8a,0xfa,0x97,0x40,0xa9,0x8d,0x1b,0x81,0x58,0xdc,0x54,0x62,0x3b, - 0xae,0x02,0xfc,0x8d,0xa3,0x34,0x41,0xbe,0x82,0x45,0x6c,0x1a,0xb3,0x57,0xbb,0x29, - 0x81,0xfa,0x55,0xc7,0xbb,0x72,0x61,0xb5,0xed,0x48,0xfd,0x98,0xb4,0xd1,0xe0,0x5f, - 0x85,0xd4,0xb9,0x33,0xd3,0x82,0x54,0x41,0xb5,0xfe,0xe8,0xd6,0x60,0x2f,0x2a,0x04, - 0x4b,0x87,0x06,0x0f,0x40,0x27,0x30,0x35,0x43,0xbe,0xaf,0xee,0x0d,0x16,0x6c,0x0c, - 0x0c,0xa5,0x32,0xb6,0x8d,0x01,0x3a,0x59,0x2e,0xe1,0xdc,0xc3,0xf1,0x25,0xa8,0x6f, - 0x8d,0x2b,0x6b,0x81,0x82,0x27,0xa8,0x59,0x86,0x6b,0xfc,0x0b,0xa8,0xc8,0xbf,0x28, - 0x18,0x2d,0x13,0x72,0x92,0xc8,0xe7,0xc5,0x69,0x99,0x18,0xd3,0x6c,0xf6,0xd5,0x44, - 0x86,0x39,0xbe,0x14,0x1b,0x65,0xd8,0xe7,0x47,0x7b,0x6a,0x27,0xfd,0xa1,0x43,0x02, - 0x8e,0xfd,0x27,0xfc,0x42,0xee,0x7d,0x77,0xb7,0x60,0x3b,0x1f,0x1a,0xf1,0xe6,0xf0, - 0x12,0x85,0x38,0xa1,0x2a,0x34,0xa6,0xfd,0x2b,0x87,0x92,0x43,0x35,0xb6,0xff,0x89, - 0x85,0x1b,0xe7,0xed,0xe6,0xf3,0x0b,0xeb,0x77,0xa7,0xf1,0xfb,0x7c,0xcb,0xfa,0x7e, - 0x1b,0xd4,0x16,0xb2,0xa4,0xa5,0xfd,0xc6,0x9f,0x8e,0x60,0xb3,0xe7,0x17,0xd6,0xf9, - 0xd6,0xf4,0xbc,0x7d,0xd1,0x6c,0x5f,0x90,0xe5,0x08,0x0b,0x5c,0xf7,0xd3,0xf3,0xe9, - 0x79,0xe7,0xe2,0x42,0xda,0xdd,0xaf,0xfb,0x7d,0x34,0x40,0x1e,0x9b,0x66,0x57,0x28, - 0x84,0xd7,0x84,0x3c,0x1c,0x1e,0x85,0x13,0x27,0x4c,0xc2,0xb0,0x42,0xce,0x46,0xd7, - 0x55,0xfb,0x00,0x1f,0x7b,0xf4,0x01,0x53,0xc3,0x75,0x8b,0xe6,0xb2,0xc4,0x81,0x97, - 0x96,0xf8,0x0c,0x1b,0xb2,0xfa,0x39,0x0a,0x05,0xbf,0xa3,0x32,0xd9,0x4d,0xb5,0x48, - 0x76,0x23,0x1a,0x70,0xc7,0xd5,0x8f,0xee,0x58,0xaf,0x2e,0x33,0xc1,0x54,0x8a,0x89, - 0x0f,0xa2,0xa1,0x6c,0x59,0xb1,0x4c,0x16,0xcb,0x9b,0xc4,0x9c,0x26,0xf9,0x64,0xe1, - 0xe9,0x18,0xe3,0x52,0xba,0xe4,0xcb,0xc0,0xad,0x15,0xd2,0x87,0xe4,0x45,0xf9,0x85, - 0x5e,0x5a,0x1b,0x67,0x39,0x55,0x47,0x5e,0x0d,0x5e,0x7e,0x5e,0x59,0x95,0x13,0x5f, - 0x68,0xfd,0xd0,0x8b,0xfa,0xc2,0x79,0x70,0x7f,0x65,0xaa,0xf4,0xe9,0x33,0x7c,0xb2, - 0xea,0xca,0xcb,0xe8,0xfa,0xe5,0xd5,0x3e,0x73,0x89,0xfa,0xda,0x2a,0x88,0x7d,0x45, - 0x7d,0x59,0x46,0x6f,0x41,0x0f,0xfe,0xce,0x67,0x88,0x6f,0x3f,0xf3,0x5b,0xad,0x6c, - 0x39,0x47,0x45,0x15,0x35,0xa8,0xc0,0x67,0x59,0xa0,0x3a,0xd2,0x07,0x5b,0xe0,0xc1, - 0xae,0x68,0x47,0xf9,0xec,0x8a,0xaa,0xa6,0x0e,0xfc,0xdc,0xe1,0x59,0x36,0x3c,0xa7, - 0xc7,0x5e,0xfe,0x15,0x09,0x77,0xfe,0x11,0x9e,0xf4,0x6f,0xca,0xfb,0x56,0x2b,0x21, - 0xdf,0x69,0xdd,0xe8,0x41,0xd3,0xb2,0xe4,0x9f,0xe2,0x45,0x2f,0x2f,0x41,0xc1,0xc5, - 0xf9,0x77,0x7c,0x2c,0x37,0x22,0x74,0x91,0xbc,0x10,0xbf,0xa8,0x20,0x7c,0x21,0x96, - 0x31,0x2f,0x2d,0xde,0xd4,0x63,0xa2,0x0a,0x1b,0xcc,0xcb,0xc3,0x53,0x7d,0xd3,0x1a, - 0x11,0xf8,0x13,0x1e,0xb4,0x2f,0x89,0xfe,0x25,0xb9,0x59,0xd1,0xd5,0xb2,0x45,0xfd, - 0xd3,0xa9,0x5b,0xc7,0x52,0x24,0x5d,0xde,0x87,0x7a,0x57,0x3f,0xd2,0x9a,0x78,0xa7, - 0x4a,0x7f,0xaa,0xcc,0xe7,0xe5,0x3d,0x67,0xb1,0xde,0x67,0x16,0x57,0x96,0x97,0xa2, - 0xab,0xb5,0xd5,0xc5,0x67,0x6d,0x1c,0x1c,0x88,0x9c,0x7f,0xa7,0x67,0x9d,0xac,0xca, - 0x90,0x19,0x0d,0xae,0xe2,0x55,0xaf,0x50,0x40,0xc6,0xa8,0x54,0x66,0x21,0x0b,0x7c, - 0xe6,0x02,0xfa,0x14,0x28,0x44,0x44,0x43,0x1a,0x78,0xac,0x40,0xab,0x14,0xde,0x51, - 0x2c,0xfd,0x59,0x7d,0x28,0x5a,0xd7,0x71,0x37,0xe9,0xe7,0x31,0x01,0xf2,0x60,0x61, - 0x53,0x44,0x85,0x20,0x70,0xc8,0x29,0xa6,0xaf,0xc5,0xad,0x08,0xe3,0x15,0x19,0x1c, - 0xd9,0x9b,0xb0,0x84,0x23,0xf9,0x77,0x62,0xd3,0x85,0x0e,0x52,0x3c,0xb6,0x86,0x66, - 0xed,0xeb,0x95,0x5d,0xe4,0x6d,0xf7,0x1b,0xd7,0xf8,0x29,0x0a,0xcd,0xdc,0xa8,0x2b, - 0x5b,0xee,0x5f,0x17,0xda,0x26,0x8a,0xff,0x22,0x9a,0x0e,0xa2,0xdc,0xc2,0x1c,0xf7, - 0x81,0x1d,0xa3,0x8d,0xf1,0x5c,0x24,0xc4,0xbb,0x58,0x66,0x5e,0x8c,0xcf,0x35,0xab, - 0x28,0x94,0xeb,0xcb,0x5e,0xee,0x75,0x73,0x70,0xec,0x60,0x96,0xc5,0x2d,0x50,0x4b, - 0xb6,0x62,0x67,0x30,0x17,0x3f,0xd2,0xb1,0xf8,0x31,0x4c,0x0a,0x23,0x42,0xb1,0x87, - 0x7d,0x43,0x79,0x44,0x9a,0xc5,0x9d,0x85,0x2f,0x16,0x8f,0x2a,0xa6,0x89,0x61,0xae, - 0x2a,0x82,0x10,0xd3,0x1f,0xa2,0xce,0x8f,0xd4,0x06,0x27,0x03,0x3f,0xe9,0xe8,0xb2, - 0xcf,0x27,0x54,0x65,0xcb,0xe2,0x82,0xcc,0x7a,0xa7,0x28,0x98,0x80,0xb4,0x98,0x47, - 0xd2,0x81,0xa2,0xa0,0x7f,0x50,0xf9,0xc9,0x85,0xfc,0x9a,0x83,0xe8,0x6a,0x29,0x84, - 0x9e,0xe4,0x27,0x1c,0x57,0x7d,0x0d,0x58,0x57,0xf6,0x75,0x5f,0x49,0x6f,0xf2,0x5c, - 0x42,0x8c,0xf8,0xfc,0xea,0x02,0x56,0x49,0x5b,0xf5,0x5e,0xc1,0x8c,0x2b,0x15,0x32, - 0x9a,0x91,0xa9,0x2a,0x63,0x17,0x81,0x3b,0xe8,0x53,0x59,0xbc,0xe6,0x0f,0x84,0x55, - 0x67,0x0c,0xdf,0x49,0xb6,0x9b,0xf4,0xe1,0xdb,0xc6,0x06,0xfc,0xa7,0x74,0x8e,0x6a, - 0x3a,0x98,0x4e,0xd4,0x64,0xa3,0xed,0xc4,0x1a,0x4e,0x4a,0xc6,0xda,0x7b,0x69,0xb4, - 0x55,0x93,0x1d,0x8e,0xa7,0x17,0x2b,0xec,0xcd,0x52,0xdc,0xaf,0x56,0x5c,0x81,0x48, - 0xaa,0x9e,0xd8,0xd8,0x42,0xfc,0x2c,0xa1,0xd7,0x6a,0x40,0xc8,0x0e,0x05,0x14,0xb9, - 0x85,0xbe,0x8e,0xe0,0x3d,0x77,0xfc,0x33,0x10,0xbb,0x86,0x55,0x87,0x04,0xf9,0xa9, - 0x47,0xbb,0xe7,0x1f,0xf6,0x95,0xc5,0x88,0x8e,0x3f,0x40,0x7b,0x02,0x1c,0x4e,0x27, - 0x88,0x0a,0xaf,0xd0,0x27,0x30,0x6d,0x7c,0x5f,0x44,0x15,0xdb,0x47,0x67,0x91,0xba, - 0x42,0x39,0xda,0x40,0x19,0xc6,0x9c,0x92,0x12,0x5d,0x73,0xee,0x34,0x75,0x93,0xab, - 0x9f,0x70,0x5e,0x0d,0xc0,0x14,0x3b,0x27,0x2e,0xb8,0xb1,0x73,0x34,0xb1,0x16,0x20, - 0x17,0x82,0x00,0x62,0x48,0x04,0x87,0x77,0x6c,0xd3,0xd1,0x5e,0xf4,0xaf,0xef,0x25, - 0xd5,0xb0,0x6a,0xcf,0x03,0x18,0x7e,0xf6,0x15,0xba,0xea,0xc8,0x7a,0xf2,0x18,0xe0, - 0x0a,0x09,0x09,0x08,0xb8,0x96,0x0e,0xba,0xca,0xf0,0xef,0x9f,0xc8,0xc3,0xd6,0x37, - 0xaf,0x37,0x53,0xe3,0x5f,0xa3,0x59,0x76,0x3b,0xbc,0x1d,0x06,0xde,0xbf,0xe4,0x9d, - 0x7f,0xa8,0x88,0xa0,0x75,0x15,0xb4,0xf4,0xb1,0xd1,0x44,0xf7,0x1f,0xbc,0xf0,0xdc, - 0x98,0x27,0x3e,0xe6,0x2b,0x76,0x39,0x0b,0xe4,0x67,0x4e,0x62,0x68,0xb8,0x29,0xb6, - 0xd7,0x6e,0xb5,0xb6,0x1b,0xa3,0xa1,0xd5,0x6c,0x3b,0xc6,0xa9,0xb0,0xa9,0xa3,0x29, - 0x16,0xc3,0x08,0x01,0x59,0x50,0xa5,0xc1,0x7e,0x0c,0xea,0xc8,0x18,0x78,0x20,0xc2, - 0x78,0xc6,0xbf,0xdc,0xf1,0xbf,0x8c,0x4b,0xe5,0x56,0xc4,0x31,0x8f,0xd8,0x1a,0x27, - 0x9c,0xc7,0x30,0x62,0x65,0xec,0x4f,0x3c,0x37,0xc5,0x23,0xa4,0x01,0x3a,0x85,0xc9, - 0x6b,0x00,0x5d,0xf4,0x6c,0x8c,0x40,0xe5,0x62,0xd7,0xcf,0x31,0x3a,0x74,0x60,0xcf, - 0xd1,0xb5,0xf0,0xf9,0xc5,0x19,0x38,0xda,0x01,0x94,0x80,0x8b,0x24,0x9e,0x1e,0x19, - 0x1b,0x38,0xd7,0x24,0x6d,0xaa,0xa7,0x9e,0x3a,0xb9,0x10,0x07,0x78,0xe3,0x7e,0xec, - 0x26,0xa9,0xf7,0x0a,0x23,0x50,0x1a,0xea,0xb0,0x8a,0x33,0x5b,0xe2,0x79,0x15,0xed, - 0x04,0xa6,0xdc,0x05,0x75,0xd5,0x4f,0x5f,0xd1,0xc5,0xf6,0x0d,0x50,0x2e,0x36,0x36, - 0xdc,0xf1,0x51,0xbf,0x75,0x6c,0xfe,0x6f,0xc3,0xdc,0x12,0xa7,0x71,0x78,0xa3,0x70, - 0x03,0xa0,0x06,0x60,0x73,0xc7,0x5b,0x6d,0xcb,0xda,0x6e,0xb7,0xac,0x2d,0xf3,0x6f, - 0x1a,0x98,0xf0,0x7c,0x0d,0x97,0x4b,0x39,0x42,0x54,0x4d,0x2c,0xe4,0x34,0x5e,0xb5, - 0xb0,0xd0,0xdc,0x82,0xbe,0x77,0xed,0x64,0xe4,0xbe,0xd1,0x53,0x9c,0x48,0x91,0xb4, - 0xfc,0xdc,0x9d,0x72,0x49,0x8f,0x66,0x6c,0xf5,0xf0,0x18,0x94,0xa9,0xd1,0x10,0xd7, - 0x47,0x5e,0xa7,0x68,0x59,0x34,0xd2,0x09,0x1e,0xc9,0x80,0xa2,0x9a,0x92,0x2b,0x60, - 0x7a,0x1b,0x0e,0x25,0xc1,0xc8,0x41,0xb2,0xb9,0xa5,0xf7,0xb0,0xb5,0x69,0xd6,0x1e, - 0xe8,0x45,0xd6,0x02,0x7d,0x03,0x9e,0xf6,0x81,0xfa,0x59,0x4c,0x38,0x23,0x5b,0xf1, - 0x65,0x45,0x19,0xf2,0x4d,0x55,0xa0,0xc4,0xaa,0x20,0xee,0x33,0xda,0x8b,0xd2,0x50, - 0xa8,0x9f,0xf9,0x8d,0xa7,0x62,0x7e,0xda,0xf9,0x22,0x66,0x83,0x2e,0x8c,0x10,0xde, - 0xac,0x35,0xc6,0x48,0x90,0xbb,0x22,0xbf,0xac,0xeb,0x37,0xb9,0xd4,0xce,0xdf,0xae, - 0x8b,0xe4,0x6c,0x3d,0xfa,0x0a,0x1d,0x47,0xcb,0x77,0xfd,0xf5,0x53,0x45,0x50,0x98, - 0x62,0xd6,0x40,0x4b,0x44,0x31,0x20,0x27,0x52,0x43,0x49,0xfb,0xf2,0x54,0x02,0x99, - 0x87,0x5a,0x74,0xdc,0x74,0x62,0x99,0xc5,0xdd,0x76,0x18,0x54,0x8a,0xfe,0xc0,0x06, - 0x5e,0x36,0x9b,0x66,0x8a,0xf2,0x6e,0x6c,0xa4,0x0e,0xc7,0xcd,0xa5,0x0e,0x3b,0xf1, - 0x17,0xb8,0xe5,0xb4,0xdf,0x6c,0xf7,0x3e,0x9e,0xfc,0xf4,0xfa,0xfd,0xe7,0x0f,0x1f, - 0x5f,0x9e,0xbe,0x3c,0x3b,0xad,0xce,0x2c,0x06,0x82,0x8a,0x40,0x1d,0x22,0x3c,0x5e, - 0xfe,0xd9,0xe0,0x1e,0x19,0x3a,0xb1,0x05,0xc3,0xb5,0xa6,0x7d,0xff,0x3e,0x67,0xaf, - 0x02,0xe8,0x53,0xdc,0x38,0x82,0xd1,0x4c,0xad,0x6e,0x43,0xce,0xaf,0x40,0xff,0x8e, - 0x4d,0x9c,0x8f,0xd9,0x4d,0xb5,0xed,0xc8,0x42,0xe0,0x7d,0xd1,0x42,0x54,0x22,0x86, - 0x72,0xaf,0x84,0xfd,0xf7,0x03,0xcc,0x6e,0xe3,0x20,0x82,0x37,0x54,0xb3,0x62,0xaa, - 0x8a,0x16,0xc4,0xf1,0x7f,0xfa,0xa9,0x3f,0x08,0xbc,0xfe,0xd3,0xef,0x1b,0xd2,0x6c, - 0xab,0x9b,0x5f,0x45,0x36,0xbf,0xb4,0x72,0x34,0x27,0x1c,0xf4,0xad,0x9a,0xa5,0x25, - 0xcf,0x21,0x3b,0x3c,0x6a,0x01,0x95,0x50,0x1d,0x14,0x6a,0x36,0xc3,0x92,0x29,0x2c, - 0xdc,0x6a,0x84,0x40,0xea,0xdb,0xc7,0xa6,0xf0,0x9f,0x07,0x4a,0x21,0x3d,0xe9,0xcd, - 0x22,0x6b,0xd2,0x9c,0xf7,0x17,0xba,0xb0,0x56,0xb1,0x84,0xb1,0x17,0x95,0xc9,0xfe, - 0xfd,0xa0,0x74,0x5b,0x35,0xb6,0x2e,0xe2,0x8c,0xcb,0xcc,0x5b,0xa9,0x64,0xa4,0x3f, - 0x67,0xd3,0xa0,0xe1,0xdb,0x63,0x1b,0xa5,0x5e,0x9b,0xc2,0x63,0x6c,0x3c,0xc1,0xd3, - 0x04,0xc4,0xcd,0x42,0xac,0xa9,0x91,0x8e,0x9b,0x9b,0x5b,0x63,0xd8,0x8b,0x32,0xc8, - 0x74,0x73,0x8b,0xfe,0x6e,0x6d,0xd6,0x06,0xb9,0x6e,0x6e,0xe1,0x5f,0x28,0x5e,0xc8, - 0x59,0xb0,0xb9,0xe5,0x6f,0x6d,0x3a,0xdc,0xce,0x5f,0xb8,0xa4,0x68,0x73,0xab,0x81, - 0x83,0x3d,0xde,0xac,0x26,0x31,0xd8,0xdc,0xc2,0xbf,0x38,0x28,0x74,0x06,0xdc,0xec, - 0x6e,0x6e,0x5a,0xea,0xa1,0x00,0x74,0x4d,0x10,0x4d,0x85,0x34,0x80,0xf8,0x33,0x89, - 0x52,0x74,0x4f,0xc3,0x57,0xc4,0x6b,0xf0,0xb9,0xc4,0x6e,0x26,0x00,0x3b,0x69,0xc2, - 0x5e,0x29,0xe2,0xa8,0xad,0x4d,0x1e,0x5d,0x9b,0xb5,0x61,0xb3,0x98,0xcc,0x09,0x78, - 0x60,0x53,0x85,0xbc,0x6e,0xf6,0x4a,0xe7,0x33,0x35,0xbb,0x14,0xe8,0x0f,0xb4,0xb8, - 0x55,0x69,0x12,0x28,0x67,0x3a,0x6c,0xc4,0x64,0xa9,0xb3,0x70,0xa9,0x4a,0x2f,0xf2, - 0x4e,0xe4,0x1e,0xae,0x6f,0x67,0x08,0x92,0x44,0x34,0x35,0x8f,0xf8,0x2f,0x5d,0xf6, - 0x5b,0x1c,0x1d,0x82,0x00,0xab,0xe9,0x61,0x18,0x30,0x6f,0xb1,0xd8,0xf8,0x13,0x07, - 0xe3,0xd3,0x10,0x04,0xad,0xd8,0x2a,0x96,0xce,0xe8,0xa2,0x64,0x84,0xbf,0x41,0x05, - 0xd9,0x23,0x9b,0x96,0xa9,0xb6,0x82,0x16,0x5b,0x5d,0xb8,0x9d,0xb3,0x2e,0x05,0x0d, - 0x63,0x19,0x83,0x0f,0x61,0x40,0x93,0xdc,0x2c,0xc5,0x12,0xab,0x6e,0xca,0x7b,0xc2, - 0xcc,0xa2,0x2b,0x0f,0x8f,0x7f,0x94,0xcd,0xc7,0x36,0x4f,0x86,0x43,0x74,0xd6,0x96, - 0x5f,0x4c,0x6b,0x69,0xe5,0x59,0x8a,0xc9,0x7f,0xa7,0x1e,0x94,0xa2,0x00,0x31,0xdb, - 0xfc,0x94,0xbf,0x59,0x51,0x4f,0xeb,0x4c,0xfb,0xf9,0x21,0xff,0xb9,0xa2,0x2e,0x47, - 0x7f,0xe6,0x3d,0x72,0x56,0x13,0x42,0xe5,0x07,0x3a,0x8d,0x12,0x2c,0x21,0xf2,0x58, - 0x40,0x77,0xfc,0xbc,0xa2,0x06,0xa5,0x97,0xca,0x7b,0x3a,0xa3,0x3b,0xcf,0x32,0x0f, - 0x76,0x27,0x66,0x59,0x84,0x16,0xf2,0xb0,0xae,0xb4,0x6b,0x2c,0xd0,0xf8,0x74,0x6f, - 0x2c,0xd8,0x8b,0x1f,0x7e,0x20,0x4d,0xc0,0x3f,0x08,0xc7,0xfb,0x15,0xdd,0xb8,0xb3, - 0x91,0xef,0x85,0x43,0x0d,0x8a,0xff,0xf8,0xf5,0xcc,0xd0,0xde,0xaa,0x0b,0xb6,0x9a, - 0xda,0xcd,0xc1,0x08,0xfb,0x6d,0x84,0x9e,0xc1,0xa7,0x86,0x39,0x22,0xc9,0xfd,0xcf, - 0xfc,0x06,0x01,0xa3,0xf9,0x0c,0x20,0x32,0x97,0xe8,0x71,0x59,0xeb,0xc0,0x1a,0xb6, - 0x2f,0x55,0x86,0x3a,0xa2,0x80,0x9e,0x1a,0x7d,0x6a,0xb8,0xa8,0x0d,0x0a,0xc1,0x46, - 0xdf,0x12,0xc2,0xe7,0xe4,0x29,0x56,0x29,0xb5,0xc2,0x38,0xfb,0x32,0xe8,0xe3,0xb7, - 0xfa,0x96,0xea,0x70,0x1c,0x5b,0x94,0x5c,0x12,0xe3,0x22,0x65,0x33,0xd2,0xf4,0xc1, - 0x9f,0x46,0xde,0x98,0x6c,0xff,0xbd,0x07,0x68,0x0b,0x8c,0x8d,0x29,0x06,0xb0,0x2f, - 0x22,0x1c,0x58,0x31,0xce,0x1d,0xb3,0xc8,0x47,0xa0,0x2f,0x0a,0x48,0x5a,0x71,0x2c, - 0x7f,0x74,0x31,0x07,0xc0,0x31,0xfc,0xcb,0xbe,0x04,0x5d,0xa6,0x73,0xaa,0x32,0x72, - 0xd2,0xb7,0x6e,0xdc,0x67,0x1c,0xe8,0x72,0x5b,0xfd,0xbe,0xdc,0x74,0x72,0xfb,0xe4, - 0x1f,0xf0,0x0d,0xae,0xa9,0x79,0x77,0xa7,0xde,0x89,0xae,0xf8,0xcc,0x4f,0x1d,0xef, - 0xad,0xac,0xa2,0x36,0x55,0x4d,0x33,0xbc,0x7f,0xba,0x4b,0x9a,0x87,0x3d,0x51,0xfd, - 0x44,0xfb,0xa0,0xfa,0x5a,0x62,0x68,0xe5,0x0b,0x59,0x49,0x0a,0xf2,0x0b,0xc3,0xa1, - 0x46,0xba,0x1c,0x6b,0x8c,0x63,0x5e,0x87,0x08,0xa6,0x03,0xbc,0xd8,0xdc,0x1a,0x13, - 0x16,0xcd,0xad,0xf9,0x0a,0x87,0x34,0xee,0xe4,0x7c,0x7c,0xa1,0x69,0xc4,0x2b,0x34, - 0x15,0x21,0xa8,0x3c,0xa8,0xaa,0xd8,0xd3,0x1a,0x65,0x65,0x63,0xa3,0x31,0x2d,0x18, - 0x64,0xea,0x4f,0xb0,0x18,0xf3,0xf8,0x08,0x8b,0xe6,0x79,0xae,0x6b,0xfd,0xb6,0xa6, - 0xdd,0xd7,0x9c,0xb5,0x13,0xa7,0xae,0x33,0x0a,0xe0,0x07,0x9b,0x4e,0xbf,0x84,0xb4, - 0xcd,0x32,0x65,0x59,0x36,0x42,0x57,0xe5,0x6d,0x8c,0xa4,0x99,0x05,0xcb,0x85,0x24, - 0x34,0xdf,0xbc,0x8a,0xd0,0x3f,0xd1,0x8e,0xae,0xec,0x69,0x7a,0xa9,0x60,0x50,0x6b, - 0x0d,0xc2,0xad,0x0c,0x1f,0x4a,0x3b,0x19,0x48,0x60,0x7f,0xb5,0x6d,0x08,0x4a,0x58, - 0xf0,0x6f,0xc1,0x3a,0xc4,0xa7,0x86,0x6a,0x85,0x86,0xa0,0x50,0x67,0x9e,0x88,0xb0, - 0x69,0x60,0x3a,0x01,0x61,0x80,0x1c,0xf2,0x9a,0xbf,0xe3,0x20,0x68,0xbc,0xbd,0x06, - 0x74,0xd6,0xe8,0xea,0xd8,0x8c,0xae,0x40,0xcc,0xa4,0x73,0xd9,0xde,0xb0,0xe8,0x57, - 0x9f,0x5e,0x62,0xc5,0x9a,0x21,0x11,0x1f,0x45,0xdf,0x80,0x38,0xf6,0xc2,0xd1,0x8b, - 0x09,0x88,0x45,0x8d,0x61,0x51,0x40,0x45,0xb8,0xbd,0x60,0xd1,0xb5,0x01,0x6d,0x26, - 0xae,0x3d,0x1c,0x28,0xa0,0xc0,0x6a,0x4e,0xdd,0x58,0x18,0x02,0x41,0x6b,0x79,0x81, - 0x7e,0xfa,0x38,0x7f,0x41,0xcd,0x52,0xf4,0x16,0x04,0xf2,0xc9,0x57,0xd7,0xc1,0xa8, - 0x31,0x83,0x05,0x7a,0xdc,0x8a,0xcf,0xf2,0x03,0xba,0xc6,0x88,0x60,0x78,0x56,0x6f, - 0x9d,0x27,0xc2,0xe5,0xdc,0x4f,0xf1,0x16,0xc8,0x54,0xdc,0x0c,0xcf,0x71,0xf9,0xe4, - 0x2e,0xe8,0x1a,0xb3,0xd0,0x87,0xe9,0x48,0xcd,0x08,0x6f,0xe8,0x09,0x0d,0xe0,0x46, - 0xa0,0x9d,0x23,0x0e,0xa1,0xfe,0x81,0xd2,0x15,0x8e,0xdf,0x29,0x6f,0x40,0xa5,0x40, - 0xa4,0xe8,0xfe,0xad,0x50,0xcc,0xb5,0x07,0xda,0x06,0x8c,0xdd,0xfe,0x76,0x8e,0xb3, - 0x0e,0x5e,0xde,0xd7,0x70,0xad,0x63,0x55,0xfb,0xdc,0xbd,0xc0,0x5d,0x4e,0x34,0xee, - 0xb8,0xd5,0x6d,0x5b,0xdd,0x4e,0xae,0xd1,0xc5,0x83,0x6a,0xe5,0x81,0x5e,0x79,0xb0, - 0xa4,0xb2,0x34,0xf3,0xba,0xcd,0x78,0x70,0x77,0xe7,0x3a,0x74,0x6d,0xb6,0xf7,0x02, - 0x73,0x9f,0x26,0x1e,0x34,0x21,0x4e,0xeb,0x2b,0xdb,0xe3,0xca,0x5a,0xf0,0x6a,0xa0, - 0x91,0x4a,0xb7,0x60,0xe5,0xbe,0x1b,0xd1,0xe8,0xb6,0x8f,0x85,0xba,0x5c,0x50,0x2e, - 0x1a,0xa6,0x8f,0x46,0x97,0x19,0x5a,0x18,0x3c,0x0d,0xa2,0x8d,0x90,0x78,0x94,0x58, - 0xd7,0x0d,0x30,0x21,0x11,0x4a,0x40,0x6e,0xe2,0xc3,0x22,0x8d,0x28,0xe1,0xb1,0x4a, - 0x64,0xe0,0x18,0x1c,0xd6,0x24,0x3c,0x3f,0xc7,0x1c,0x81,0x9e,0xce,0x48,0x66,0x1a, - 0xcf,0x02,0x63,0x40,0xd7,0x1c,0x09,0x3f,0x7f,0x76,0x02,0x35,0xd0,0xf9,0x38,0xcd, - 0xef,0x96,0x95,0x02,0x3e,0x07,0x09,0xbb,0xe1,0x88,0x1b,0x93,0x54,0x1b,0x56,0xf6, - 0x52,0x38,0x98,0x86,0x40,0xac,0xdc,0x11,0x67,0x55,0x97,0xde,0x06,0x30,0xee,0x81, - 0x97,0xcd,0x31,0xde,0x0d,0x1b,0xcb,0xe6,0x91,0xb0,0x83,0x39,0xe2,0x3c,0x9d,0x90, - 0x76,0x63,0x83,0xfe,0x38,0x1c,0x43,0x64,0xa1,0x33,0xbc,0x9e,0xaa,0x28,0x14,0x98, - 0x9c,0x7f,0xa0,0x8c,0x3c,0xf9,0x7b,0x0b,0x81,0x27,0x6a,0xb3,0x63,0x06,0xb7,0x5e, - 0xc0,0x2a,0x2e,0x2a,0x94,0x52,0x58,0x5d,0xd0,0x18,0x9f,0x6a,0xf5,0x94,0x0e,0xf7, - 0x4e,0xa4,0x70,0xc5,0xeb,0xb6,0xf1,0xe8,0x4f,0x06,0xc6,0xdd,0x8b,0x85,0x62,0xbf, - 0x7b,0xe5,0xab,0xde,0x13,0x6d,0xe0,0x4b,0xe5,0x8a,0xaf,0x74,0x4f,0xf2,0xc1,0x29, - 0xf9,0xdf,0x3c,0x31,0x0c,0xcd,0xbf,0x42,0x98,0xf8,0xd9,0xe7,0xbe,0xa2,0x57,0xc6, - 0x51,0x10,0x7c,0x24,0xa2,0xd8,0x18,0x0e,0xec,0x16,0xfc,0x83,0x4d,0xd8,0xd4,0x91, - 0x2d,0xa6,0xc4,0x5e,0x2d,0x6b,0x78,0x6d,0xf5,0x8b,0x5e,0x5b,0x75,0x03,0x64,0x3f, - 0x0e,0xb4,0x33,0x0c,0xac,0xe1,0xa0,0x41,0x11,0x51,0xf4,0x4e,0x87,0x82,0x68,0x55, - 0x0b,0xa6,0x68,0xdd,0xdd,0xe9,0xcf,0xed,0x1d,0x65,0xdf,0x58,0xdd,0x51,0x1e,0x37, - 0x51,0xed,0x8f,0x0c,0x44,0xbc,0x26,0x6a,0x06,0x77,0x77,0x26,0xc5,0xbd,0x27,0x1e, - 0x67,0xd3,0xcd,0x8f,0x04,0x72,0x1a,0x2f,0x63,0x1e,0xca,0x63,0x7c,0xae,0x87,0x69, - 0xc8,0xf0,0x89,0x3e,0xaf,0xd9,0x7a,0x80,0x2e,0x80,0xe0,0x1b,0x4d,0xcd,0x3c,0x09, - 0xd9,0x05,0x97,0x39,0x3c,0xda,0x90,0x07,0x1e,0xa2,0x1f,0x39,0x52,0xe5,0x4e,0x73, - 0x28,0x9e,0x25,0x59,0x70,0xbb,0x6a,0xc6,0x48,0x2d,0x94,0xe7,0x30,0xba,0x0f,0xcd, - 0x30,0x3d,0x09,0x46,0x02,0x71,0x7a,0x13,0x84,0x5b,0x1a,0x47,0x21,0x3c,0x30,0x2d, - 0xc0,0x57,0xe8,0xcf,0x05,0xcc,0x27,0x1c,0x79,0x23,0xc7,0xf8,0x00,0x50,0xc0,0xb7, - 0xb2,0x39,0xe6,0xc7,0xc6,0xe0,0x56,0xe6,0x05,0x30,0x80,0x06,0xa4,0x48,0xcb,0x79, - 0xd0,0x94,0x4e,0x05,0x08,0x20,0xf3,0x6f,0xe1,0xeb,0x3d,0xf0,0x8c,0x29,0x08,0x2e, - 0x2e,0x88,0x8d,0x24,0xfc,0xfb,0x99,0x23,0xe4,0xb5,0x32,0x88,0x39,0x02,0xaf,0x08, - 0xe3,0xea,0xf9,0x80,0x4a,0x7a,0x20,0xf9,0x1a,0x92,0x00,0x66,0x7a,0xa3,0x7e,0x85, - 0x52,0x48,0xe6,0x51,0xa5,0x15,0xba,0xb9,0x9f,0x98,0xc0,0x7c,0xf4,0x32,0x49,0xfa, - 0x94,0x55,0xe6,0x03,0xfd,0x06,0xa6,0xab,0xb5,0x2d,0xcf,0x05,0xb8,0x9c,0xb4,0x88, - 0x2e,0xf1,0x59,0x67,0xb3,0xda,0x05,0xc5,0xd7,0xfa,0xc3,0x2b,0x69,0xb1,0xe4,0x45, - 0x16,0x2d,0xe8,0x2c,0x44,0x6a,0x3c,0x3a,0x03,0x97,0xa7,0x0b,0x78,0x88,0x3f,0xea, - 0xea,0x99,0xe9,0xb0,0x06,0xe0,0x42,0xa7,0xd5,0xe1,0xcb,0xc1,0xc5,0xba,0xd9,0x8a, - 0x74,0x13,0x1d,0xa7,0xc4,0x76,0x03,0xe4,0xb9,0x11,0x12,0x78,0x58,0xd0,0x27,0x84, - 0x12,0x79,0x4e,0x07,0x11,0x97,0x3b,0x21,0x89,0x82,0xf3,0xb4,0xc2,0x0c,0x45,0x28, - 0x81,0xcc,0xf0,0x41,0xfc,0xc1,0xc3,0xbb,0x36,0x88,0xc1,0xf3,0x4d,0x32,0xc8,0xe6, - 0xb1,0x35,0x0e,0x06,0x4b,0x0d,0xc0,0x76,0xa4,0xee,0x33,0xcc,0x0c,0x6a,0x0e,0xf9, - 0x9e,0x41,0x57,0x1c,0x6a,0x62,0x26,0x01,0xf2,0xb9,0xa2,0x14,0x16,0x80,0x91,0x7c, - 0xed,0x5d,0x98,0x99,0x8e,0x1e,0x6d,0xa4,0x63,0x03,0x08,0x46,0xa9,0xed,0x8f,0x02, - 0x2f,0xb5,0x69,0xf6,0x12,0x29,0xc8,0x07,0x12,0x83,0x35,0xc8,0xf3,0x11,0x0a,0xf5, - 0xf1,0x3f,0xe8,0xb9,0x4c,0x85,0xfb,0xf4,0x5f,0xe1,0xc8,0xac,0x9a,0x46,0xd4,0x6f, - 0x3c,0x44,0xe3,0x9e,0x54,0x77,0x27,0xf5,0x5c,0xd8,0x9d,0x2e,0x1f,0x5f,0x23,0xfa, - 0x8d,0x6c,0x5c,0x72,0xa3,0x30,0xd5,0xb9,0x9b,0xd2,0x74,0x69,0x93,0x92,0x0f,0x1b, - 0x30,0x0d,0x98,0xac,0x3f,0xbe,0x05,0x61,0x8f,0x28,0x95,0x72,0x7d,0xa5,0xf3,0x6a, - 0x3d,0x31,0x21,0x82,0x9a,0xb6,0x36,0x9f,0xb1,0x96,0x0f,0x7d,0xb7,0x79,0xef,0x1d, - 0x33,0x9d,0x32,0xb7,0x40,0x79,0x01,0x5c,0xf8,0xf4,0xf1,0x35,0x0a,0x1b,0x20,0x97, - 0x80,0xd8,0xc9,0xa4,0xcb,0x5e,0x88,0xc0,0xb8,0x2e,0xd0,0xe1,0xd6,0xfd,0xca,0xf8, - 0x37,0xaa,0xf1,0x54,0x11,0x3d,0x06,0x54,0x99,0xb2,0xb3,0xd3,0x24,0x71,0x0b,0x94, - 0x3b,0xd1,0x5d,0x97,0xa4,0x97,0x9a,0x20,0xbd,0xf2,0x96,0xe6,0x15,0xd4,0x29,0xa7, - 0x5a,0xc0,0x7b,0x1b,0x86,0xb7,0xac,0x2f,0xf4,0x27,0x1e,0x91,0x36,0x2a,0x69,0x34, - 0xc6,0x4c,0x42,0x63,0x5d,0x8d,0x46,0xf9,0x57,0x5e,0x70,0x2b,0xb0,0x89,0xd3,0x31, - 0x21,0x5c,0x71,0x51,0xf0,0x10,0x0c,0xcf,0xd9,0x5c,0x10,0x3e,0x51,0x20,0xc7,0xed, - 0x8e,0x3e,0x0f,0x5a,0x5b,0xd9,0xa6,0xc4,0x64,0x74,0xad,0x0c,0xf0,0xa8,0x4b,0x09, - 0x44,0xda,0xa2,0x0c,0x02,0x3f,0xce,0x29,0x37,0x4d,0xe7,0x70,0x77,0xbd,0xd9,0xe7, - 0x28,0xbc,0xd5,0x5e,0x82,0xc4,0xf7,0xf6,0xb3,0x32,0x0c,0x0c,0xa3,0xb0,0x08,0x3a, - 0x39,0x5f,0x03,0x7f,0x29,0x1a,0x0b,0xea,0xa7,0xfd,0x96,0x4d,0x14,0x88,0xe5,0x3d, - 0xbd,0x08,0xad,0x3a,0x69,0x55,0xe4,0x88,0x58,0xe3,0x25,0x9d,0x15,0x0e,0x23,0xa2, - 0xab,0xfe,0xf6,0x3f,0xdf,0xff,0xb2,0xed,0xb3,0x54,0xec,0x67,0x14,0x6c,0x7b,0x6b, - 0x69,0x0c,0xed,0x69,0x74,0x65,0x51,0xaf,0x5b,0x5b,0xfa,0xdb,0xe1,0x60,0x63,0x03, - 0x63,0x1f,0xd5,0x7c,0x41,0xef,0xf4,0x49,0xfa,0x22,0xf2,0xab,0xa0,0x00,0x25,0xa4, - 0x00,0xcc,0x9f,0x51,0xd6,0x56,0x9f,0xe5,0x3b,0xab,0x7c,0xdc,0x2b,0xde,0x6b,0x3d, - 0xf2,0x1b,0xc4,0x53,0xa6,0xbe,0x5a,0x98,0x25,0x7c,0xdd,0x66,0x62,0x57,0x99,0x88, - 0xa5,0x03,0x4a,0x0a,0x64,0x7a,0xa8,0xa4,0xd0,0xa9,0x97,0x04,0x63,0x6f,0xf2,0xc1, - 0x85,0xa1,0x1d,0xdb,0xf1,0x38,0x84,0xfd,0x48,0x93,0x31,0x02,0xab,0xa8,0xb0,0xa2, - 0x1a,0xf8,0xfe,0x17,0xb3,0x2b,0x87,0x92,0x87,0x31,0xff,0x13,0xd8,0x43,0x23,0x4a, - 0xac,0xe3,0xf3,0xae,0x7d,0x71,0xfc,0x7b,0xfa,0xc3,0xb6,0x8f,0xc6,0xbe,0xb2,0x5f, - 0x82,0x8a,0xb6,0xa3,0xdb,0x4f,0xad,0xe5,0xe6,0x05,0x6d,0x8a,0x96,0x4e,0xe9,0x16, - 0x40,0xea,0x92,0xca,0x26,0xa4,0xb5,0xb4,0x98,0xe2,0xf1,0xba,0x9a,0x28,0x59,0x67, - 0x1c,0x6c,0xad,0x84,0x2d,0xd8,0x6c,0xa9,0x27,0x4f,0x38,0xa5,0x1c,0x22,0x8f,0xf1, - 0x12,0x25,0x46,0xe7,0x0e,0x1e,0x45,0x3f,0x14,0x83,0x2c,0x4c,0x22,0x31,0x63,0x42, - 0x4e,0xb5,0x9f,0xd5,0x66,0x33,0x8c,0x63,0xc3,0x3c,0x93,0x0c,0x0f,0x55,0x10,0x6f, - 0x54,0xbd,0x8a,0x5d,0x70,0x24,0xbe,0x39,0x1d,0xd3,0xbe,0x71,0x46,0x04,0x94,0x41, - 0x6e,0x8d,0x6b,0x3e,0xfb,0x11,0xc9,0xe7,0x7c,0x99,0xfb,0x2d,0x45,0x25,0x48,0xec, - 0x70,0x0e,0x92,0x8a,0xf1,0x74,0x0e,0x45,0x97,0xc0,0x8d,0x31,0x17,0x1c,0x65,0x08, - 0x71,0x33,0x29,0x39,0x09,0xad,0x17,0xb3,0x8f,0x72,0xea,0x7b,0xee,0xc4,0xc9,0x73, - 0x64,0x75,0xb5,0xa1,0xfa,0xa9,0x4c,0xff,0x81,0x42,0x9b,0xd2,0x9a,0x70,0xdf,0xf2, - 0x98,0x25,0x24,0x0b,0xe3,0x28,0x75,0x49,0x93,0x51,0x1d,0x52,0xbb,0x2e,0xfb,0x11, - 0x70,0x72,0xd9,0x26,0xeb,0xe6,0xde,0x28,0x1f,0x8c,0xad,0x46,0xc3,0x42,0x94,0x79, - 0x2a,0xba,0x11,0x5d,0x9f,0xbf,0xff,0xe5,0xc2,0x90,0x89,0x38,0x90,0x84,0xdb,0x0a, - 0xe4,0x36,0xbf,0x3c,0xa5,0xb3,0xff,0x14,0xf0,0xa1,0x5e,0xa4,0xd4,0x85,0xd4,0x53, - 0xd5,0xa6,0x5c,0xf7,0xfc,0x34,0xec,0x34,0x1a,0x67,0xd2,0xaf,0xe3,0x91,0x5a,0x48, - 0x3d,0xff,0x59,0xa5,0x59,0xb4,0x76,0x6b,0x6b,0xa1,0x31,0x02,0x73,0x58,0x62,0x72, - 0x41,0x05,0x54,0x72,0x73,0xef,0xaa,0xe5,0x90,0x17,0xd0,0xa0,0x40,0x7c,0x15,0x46, - 0xc0,0x8c,0xf1,0x8b,0x40,0x06,0xbe,0xb0,0x57,0x49,0xd1,0x1f,0xf8,0x36,0x0a,0xc9, - 0x4e,0x1a,0x63,0xd8,0x68,0x13,0xe9,0xc1,0x81,0x12,0x6f,0x40,0x6b,0x6b,0x39,0xc6, - 0x2f,0x78,0x12,0x8c,0x8c,0x00,0x97,0xbf,0x49,0x4d,0xb2,0x4d,0x5d,0xb6,0x85,0x26, - 0xf7,0x54,0x0a,0xdf,0x28,0x4c,0xf3,0x85,0xc0,0x73,0x10,0x92,0xdd,0x10,0x11,0x39, - 0x8b,0x28,0x29,0xa0,0x12,0x93,0x11,0x02,0xc0,0x4a,0x0e,0x3b,0xad,0xf5,0xf9,0x0e, - 0x70,0x9c,0x55,0xc2,0x53,0x0d,0xdf,0x11,0x40,0xac,0x4a,0xdb,0xe5,0x85,0x5d,0xb0, - 0x1e,0xd0,0x44,0x9f,0x05,0x83,0x5d,0x5a,0xe5,0xed,0x6b,0xd2,0x8f,0x64,0x18,0x44, - 0x83,0x81,0x97,0xd0,0xad,0xb3,0x3c,0x47,0xa4,0x7b,0x69,0x8d,0x50,0xf3,0xb0,0x27, - 0xdb,0x37,0xf0,0x36,0xa3,0xb4,0x0b,0xa3,0x95,0x2e,0x67,0x5c,0x44,0xa3,0xd5,0x8d, - 0xa2,0x47,0x90,0xc6,0xa2,0xae,0x2e,0xc8,0x6b,0x00,0xcb,0xd3,0x7a,0xb0,0xa3,0x9a, - 0xf6,0xb9,0xe4,0xa9,0x45,0x01,0x46,0xd2,0x4d,0x4b,0x74,0x40,0x6e,0x4a,0x55,0xf7, - 0xa5,0x9e,0xd6,0x1c,0xb5,0xbf,0xda,0xe7,0xab,0xc0,0x0b,0x90,0xe1,0x36,0xb4,0x84, - 0xc5,0xfa,0xe8,0xad,0xaf,0x76,0x60,0xe3,0x84,0x2b,0x38,0x96,0x8f,0xe4,0x51,0x76, - 0x2e,0x9c,0xd3,0xc8,0xab,0xd1,0x96,0x0f,0x83,0xb9,0xfa,0x99,0x8e,0xd5,0xcf,0x61, - 0x72,0x21,0x02,0xd7,0x6d,0xb3,0xd6,0x3d,0x4d,0x6b,0x38,0xef,0x0d,0x3d,0x0f,0xb8, - 0xaf,0x5a,0xd7,0x84,0x8d,0x8d,0xa7,0xd2,0xe3,0x41,0xbe,0x13,0xd1,0x1a,0x5a,0x6b, - 0x1a,0x48,0x54,0x73,0x65,0xf1,0x81,0x6b,0xe5,0xfd,0xd2,0xe3,0x07,0xcc,0xda,0xd4, - 0x6f,0xa8,0x4a,0xc7,0xc5,0xd2,0x5d,0xbd,0x0f,0x27,0x05,0x7d,0x3c,0xd3,0x26,0xb7, - 0x96,0xb3,0x9f,0x86,0xa5,0xfe,0xe8,0xa6,0xbf,0x40,0x38,0x76,0x5b,0xf6,0x60,0xde, - 0x6d,0xdb,0xe9,0xb8,0xdb,0xb1,0x87,0x49,0x77,0xe7,0xbe,0xe4,0xfb,0xd7,0x53,0x68, - 0x23,0x16,0x28,0x1f,0xec,0x39,0xb4,0x72,0xd1,0x5b,0xe9,0xda,0xa6,0x20,0xd0,0x2b, - 0x23,0xcc,0xff,0x23,0x0f,0xc0,0xa2,0x27,0x62,0xad,0x3f,0x60,0x5d,0xac,0x50,0x4d, - 0x64,0x2f,0x29,0xde,0xdb,0xe4,0x51,0xb5,0xe4,0x84,0x20,0x8f,0xbb,0x42,0xd0,0xab, - 0xd0,0x3c,0xf2,0x34,0x21,0x2b,0xbb,0xee,0xc4,0x51,0x65,0x56,0xf5,0x31,0x8e,0x82, - 0x09,0xbe,0x62,0x55,0x2e,0x8b,0x38,0x5c,0x49,0x52,0x34,0x1e,0x29,0xc5,0xa2,0xc3, - 0xf0,0x6a,0x43,0xf9,0xc8,0x24,0x50,0x7f,0x78,0x33,0xc8,0xcf,0x6e,0xf2,0xd3,0x0b, - 0x91,0x62,0x90,0x4f,0x30,0xf4,0xf3,0x8b,0xef,0x65,0x37,0x86,0x2c,0x52,0x45,0xbb, - 0x1b,0x6b,0x71,0x53,0x83,0x1e,0x50,0xd8,0xbe,0x01,0x16,0x3a,0x10,0xc7,0x30,0xe7, - 0xd2,0xd3,0x89,0xc3,0xed,0x6d,0x32,0x96,0xc0,0x1f,0xce,0x42,0x6d,0x73,0x82,0x83, - 0x6a,0xeb,0x19,0xeb,0xd5,0x59,0xd3,0xdc,0xca,0x96,0x0b,0xa0,0x19,0x00,0x6f,0xa0, - 0x50,0x3a,0xcb,0x53,0x2d,0x68,0x2f,0x29,0x7e,0x95,0x3a,0xb3,0x38,0x31,0x23,0xfe, - 0x6e,0x08,0xf5,0x1b,0x93,0xf4,0xca,0x17,0xb5,0x35,0x29,0xa5,0x82,0xce,0x1e,0x41, - 0x5a,0xc7,0x80,0xeb,0xd7,0xe8,0xe9,0x62,0xb7,0x44,0x6c,0x5b,0x09,0x7f,0x30,0x01, - 0xaf,0x4c,0x68,0x5c,0xc2,0x20,0x91,0xf2,0xe1,0x67,0x1f,0xf7,0xfc,0x6d,0xff,0xfc, - 0xc2,0xce,0x9f,0x4e,0x32,0x50,0xaf,0xe0,0xf1,0xe3,0x2c,0x0c,0x81,0xc9,0xf5,0x45, - 0xe2,0xa8,0xc0,0xff,0x8f,0x99,0x07,0xbb,0x93,0xcb,0xd2,0x6f,0x59,0xf2,0x47,0x4c, - 0xee,0x06,0xc5,0xa5,0x22,0xa6,0xdb,0xb2,0xf0,0x28,0x27,0xf0,0x1b,0x78,0x04,0xa4, - 0xb0,0x00,0x66,0x40,0x3e,0x66,0x5a,0xde,0x60,0x9a,0x37,0xfc,0xd6,0xcf,0x8a,0xb6, - 0xfa,0xf8,0x20,0x00,0x52,0xfa,0x26,0x0c,0xdb,0x47,0x6d,0x8a,0xa4,0x2d,0x7d,0xec, - 0x97,0x0b,0x53,0xa2,0xa8,0x46,0x13,0x3d,0x28,0x5b,0xaa,0xa7,0x74,0x88,0xf9,0x7a, - 0xce,0xa2,0xb8,0x9f,0x3f,0xfd,0x4c,0x69,0xbe,0x0a,0x12,0x82,0x8f,0x70,0xa6,0x19, - 0xe2,0xd5,0x8d,0x0d,0xed,0xb8,0x89,0x45,0x75,0xcc,0xeb,0x40,0x0e,0x47,0xe7,0xa5, - 0x54,0xc0,0x17,0x5d,0xe1,0xb4,0xbf,0xa9,0xbb,0xcc,0xcb,0x6a,0xc0,0x73,0xc5,0x2f, - 0x41,0xf5,0xfa,0x32,0x69,0x70,0xa1,0xf7,0xc2,0x42,0x73,0xe7,0x8d,0xf2,0x88,0x8e, - 0x25,0x2c,0xb9,0xbe,0xd5,0x95,0xcf,0x32,0xf9,0xae,0x85,0xfb,0x06,0xda,0x69,0x14, - 0x65,0x1f,0xa6,0x4d,0xd0,0x56,0x71,0x62,0xd4,0x4a,0xbf,0xdc,0x89,0xb4,0xf3,0x17, - 0xda,0x5d,0xba,0x23,0xa8,0x8d,0x42,0x1d,0x31,0xb6,0xe5,0x4a,0x5c,0xb5,0x0a,0x25, - 0xcb,0x2d,0x3a,0xb3,0x51,0xa1,0x63,0x13,0x70,0x8d,0x87,0x89,0x76,0x27,0x2c,0xc5, - 0x3e,0x50,0x05,0x50,0x95,0x03,0x2d,0x5e,0x28,0x8c,0x6e,0x24,0xfc,0x97,0x66,0xac, - 0x21,0xba,0x78,0x5d,0x3f,0x53,0x65,0x8f,0xa8,0x29,0x25,0xe7,0xb6,0xaa,0x8c,0x98, - 0x4c,0x6d,0x91,0xa5,0x18,0x54,0xe7,0x6d,0x4a,0x25,0x81,0x76,0xe3,0x9f,0x4a,0x73, - 0x25,0xe9,0x16,0xf7,0xe6,0x6d,0xe6,0xbd,0xa1,0xbd,0x92,0xef,0x41,0xd8,0x4e,0xe8, - 0x00,0x13,0x8d,0x8d,0x33,0x78,0xf5,0x92,0x4c,0x6d,0x09,0x32,0x00,0xcc,0x94,0x3b, - 0x46,0x65,0x54,0x85,0xb5,0xe0,0x31,0x9c,0x56,0x08,0xd8,0x08,0x5b,0xe6,0xb8,0xb1, - 0x92,0xcf,0xe3,0x00,0xfa,0x4a,0xfb,0x35,0xa6,0x3b,0x2e,0x2c,0x8e,0xe1,0xff,0x76, - 0xde,0x6a,0x3e,0x3f,0x69,0xbe,0xba,0x58,0x74,0xee,0xef,0x9c,0xed,0x4b,0x7d,0x77, - 0x50,0x0b,0xc7,0xf4,0x5f,0xd1,0x76,0xb7,0x55,0xcc,0xda,0x84,0xfe,0xd5,0x12,0x33, - 0x8b,0x54,0x05,0xb3,0x9a,0xa6,0x44,0x32,0x84,0xf0,0xb2,0xfd,0x7b,0xf2,0x7b,0x78, - 0xf7,0x7b,0x72,0xf7,0x7b,0xb8,0x6d,0xd9,0x32,0x6b,0x73,0x9f,0x73,0x0b,0x14,0x53, - 0x21,0x51,0x5d,0x39,0x9b,0xdc,0x67,0x8e,0x8e,0xd4,0xf1,0xd3,0xb9,0x7f,0xe1,0x64, - 0x89,0x3f,0x6d,0x58,0xd6,0x50,0x24,0x5e,0xcd,0x0d,0x0d,0x05,0x20,0xcb,0xe2,0xd6, - 0x51,0x7b,0xef,0xb9,0x55,0xc9,0xb3,0x45,0x29,0x8a,0xcd,0xad,0x86,0xbf,0xd5,0xb6, - 0xb6,0x4c,0xc3,0xbb,0x21,0xdb,0xb3,0x01,0x65,0x79,0xee,0x52,0xdc,0x92,0xa3,0x75, - 0xe2,0x59,0xaa,0x35,0x9a,0x77,0x2a,0xbf,0x4b,0x3a,0x08,0x84,0xad,0x9a,0xd4,0x8b, - 0x37,0x09,0x27,0x7d,0x17,0x77,0x47,0xbb,0x98,0x3e,0x3d,0xcd,0x0a,0x89,0xac,0xcd, - 0x72,0x92,0x31,0xf9,0xa1,0x48,0x8a,0x30,0x1b,0xfd,0x84,0xb7,0x51,0x43,0x9e,0x93, - 0x8e,0xf2,0x63,0x0a,0x37,0xa5,0x45,0xe9,0x6b,0x0c,0xc1,0xce,0xa2,0x0c,0x53,0xa0, - 0x09,0x7e,0xa1,0xa1,0x8b,0xc6,0x4e,0xf4,0x47,0xe4,0x28,0xe2,0xb9,0xcc,0x54,0x8c, - 0xd2,0x26,0x16,0x87,0x4b,0x4f,0x24,0x38,0x78,0x38,0x1b,0x1b,0x72,0x1c,0x62,0x09, - 0x73,0x16,0x64,0x9e,0x0b,0x68,0x88,0xb2,0x5d,0x58,0x07,0x1a,0x1f,0xac,0x83,0xdc, - 0xe7,0x5b,0x0d,0x1e,0x31,0xf9,0xcb,0x02,0x71,0x01,0xd0,0x6c,0x99,0x17,0xbf,0x87, - 0xa6,0x66,0x93,0xd2,0x77,0xbc,0x8a,0x80,0x17,0xf0,0xab,0xd0,0xa0,0xe2,0xe8,0xac, - 0xaa,0x1d,0x41,0x27,0x52,0x20,0x05,0x08,0xf0,0x0a,0xa1,0x4c,0x01,0x37,0xc1,0xf8, - 0x3a,0x22,0x52,0x25,0x58,0x36,0x73,0xc8,0xf5,0x9e,0xe8,0xb3,0x15,0x2d,0x6c,0x99, - 0x72,0xf0,0x02,0x53,0x05,0x54,0x37,0x36,0x54,0x93,0x47,0xad,0xa5,0xa0,0xc2,0x11, - 0xc5,0x78,0x38,0x63,0x6e,0xa9,0xe2,0x05,0x68,0xe5,0xe3,0x2a,0x40,0x8c,0x02,0x43, - 0xf0,0x08,0x44,0x81,0x8e,0x80,0xa3,0x23,0x90,0x5c,0xbe,0xfb,0xe2,0x19,0x09,0xf6, - 0xce,0xfa,0x3c,0x6c,0x37,0xd0,0xf0,0x45,0x57,0xa5,0x1c,0x36,0x30,0x91,0xff,0x0b, - 0x07,0x06,0x95,0xfd,0x24,0x4f,0x05,0xa7,0x7e,0x4a,0xb4,0x4c,0xcb,0xd7,0x52,0x7b, - 0x8a,0x20,0x35,0xf1,0xa5,0x66,0x8d,0x65,0xb3,0xbd,0xb7,0xdb,0x7b,0xad,0xd5,0x87, - 0xbd,0x95,0xa3,0x84,0x9a,0xd1,0x0a,0x03,0x8c,0x4a,0x9c,0x24,0xc7,0x9b,0x2f,0x34, - 0xcd,0x39,0x0e,0x6e,0xef,0xee,0xcc,0x06,0xdd,0xd0,0x8d,0x26,0x63,0x5c,0xc1,0x1c, - 0xe5,0xd1,0xb6,0xfc,0x4f,0x98,0xc2,0xef,0xe9,0x16,0xca,0xcc,0xbf,0xe3,0x89,0x1f, - 0xfc,0x8e,0xc6,0xe3,0xdf,0xd3,0x1f,0xbe,0x97,0xf6,0x66,0x39,0x72,0x35,0x69,0x1d, - 0x99,0x94,0x10,0x2a,0xd9,0x95,0xb6,0xa3,0x1e,0x87,0xdf,0x1a,0xc9,0x5d,0x81,0xc8, - 0xff,0x6e,0x64,0xd6,0xcd,0xe7,0xb5,0x48,0xbd,0x6a,0xdd,0xd1,0x87,0x06,0x7f,0x3a, - 0x7c,0x86,0xd6,0x90,0xe6,0xaa,0xdc,0x0c,0x29,0x27,0x01,0x3c,0xfd,0x1d,0xca,0x3e, - 0x81,0x2f,0x6e,0x91,0x68,0xe8,0xb1,0x0a,0x8f,0xb5,0x3e,0x6a,0xd4,0xc5,0x3c,0xa7, - 0x3c,0x16,0xc0,0xc7,0x78,0x28,0xea,0x02,0xb7,0x0b,0xb3,0x62,0x9e,0x24,0xc4,0x3c, - 0x6c,0x77,0x1e,0x8f,0xcd,0x5b,0x6d,0x0d,0x9f,0x77,0x5b,0xab,0xf0,0xb9,0x30,0x36, - 0xbe,0x81,0x44,0x43,0x5b,0x61,0x68,0x94,0x07,0xb6,0x53,0x17,0xb3,0xb9,0x5d,0x7b, - 0x72,0x41,0xc9,0x36,0x69,0xe7,0x77,0x9c,0xd8,0x68,0xec,0x66,0xa3,0xaf,0x97,0x5f, - 0x78,0x26,0x4c,0xb9,0x25,0x01,0xa9,0x06,0xc4,0x0b,0x45,0x2b,0x05,0xde,0x1d,0x95, - 0x71,0x52,0x80,0xa2,0xb0,0xf2,0x94,0x62,0xbf,0xee,0xc4,0x9b,0x34,0x2d,0x6e,0x5b, - 0xb5,0x73,0x9e,0x37,0xbe,0xb5,0x75,0x51,0xa4,0xdb,0xe6,0x11,0xe0,0xa7,0xa8,0x90, - 0x6f,0xc3,0x3a,0x4f,0x9c,0x92,0x63,0x0d,0x68,0x88,0xf6,0x82,0xca,0x74,0x79,0x11, - 0x44,0x23,0x5d,0xf1,0xb7,0x26,0xa1,0x89,0xf2,0x53,0xc8,0x57,0xae,0xa5,0xd6,0x6c, - 0xb5,0x89,0xbb,0xb0,0x62,0xc2,0xf4,0x0c,0x44,0xd8,0x51,0x6c,0xa7,0x00,0x6f,0xc9, - 0x36,0xbf,0x55,0xf6,0x1f,0x5e,0x1f,0x21,0x09,0x94,0x1c,0x1e,0xeb,0xd5,0x17,0x3b, - 0x17,0x69,0x0c,0x23,0x4b,0x6e,0x17,0x05,0x29,0x4b,0xe8,0x15,0x45,0xe1,0xb2,0x96, - 0xd7,0x5b,0xdd,0xf3,0xaa,0x6e,0x40,0x5f,0xf2,0x8c,0x55,0xac,0xca,0x6c,0x6c,0x14, - 0x05,0x43,0xd9,0xd7,0x79,0x6b,0x99,0x6c,0x28,0x11,0x7c,0x89,0x50,0x78,0xcf,0x4b, - 0xe1,0xd5,0x70,0xeb,0xda,0x05,0xc8,0xa9,0x55,0x11,0x23,0xd5,0xa4,0x95,0x0c,0x39, - 0xf6,0x03,0x20,0xd1,0x9a,0x99,0x5a,0x32,0xdd,0xa2,0x38,0x28,0x24,0xe0,0x3c,0x91, - 0x64,0x49,0x08,0xd5,0x16,0x22,0x6f,0xba,0xac,0xbe,0xe8,0x0c,0xdd,0xd0,0x6c,0x10, - 0x2c,0xe8,0xea,0xc8,0x27,0x97,0x59,0x16,0x10,0x92,0xee,0x4e,0xcb,0xd2,0x5e,0xa6, - 0x13,0x3f,0x3f,0x8f,0x11,0x92,0x63,0x6e,0xc7,0xa8,0xd4,0x96,0x23,0x67,0x65,0x73, - 0xc9,0x4a,0x0a,0x79,0x4e,0x89,0xa8,0x3a,0xea,0xac,0x12,0x54,0xa9,0x51,0x69,0xbf, - 0xa1,0x0e,0xf4,0x35,0x12,0x1a,0x1a,0xe3,0x67,0x37,0xdf,0xe4,0x12,0x7a,0x05,0x56, - 0x54,0xfa,0xb6,0x44,0x1e,0x2d,0x0a,0xc4,0x8a,0x04,0xd5,0xf2,0x0d,0x84,0xce,0x57, - 0x29,0x9a,0x38,0x1f,0xd2,0x35,0x97,0xbb,0x69,0x17,0x0c,0x09,0xb2,0xaf,0x0a,0x6c, - 0xab,0xf5,0xaf,0xbc,0x5b,0x8c,0x1f,0xac,0x6e,0x7b,0x64,0x61,0xd7,0x78,0xca,0x8c, - 0xe2,0xcd,0x09,0x20,0xf7,0xfc,0x53,0x6c,0x6e,0x6c,0x94,0xde,0xfd,0x84,0x75,0x35, - 0xa4,0x5b,0x4e,0x2c,0xb8,0x62,0xbf,0xd0,0x98,0x8e,0x28,0x47,0x3a,0x4e,0x9d,0x64, - 0xcd,0x66,0x7d,0x3d,0xea,0xb0,0x58,0xf3,0xb0,0x82,0x62,0x85,0x96,0xd8,0x65,0x00, - 0x3d,0x54,0x05,0x6a,0xad,0xae,0x7b,0x9c,0xbf,0x39,0xd7,0x4b,0x5e,0x74,0x19,0x25, - 0xeb,0x79,0xaf,0xb2,0xf2,0xd3,0x3a,0xf5,0x2b,0x90,0x17,0x9b,0x89,0x16,0x11,0x5a, - 0xe0,0xb3,0x7d,0xa8,0xfa,0x11,0x57,0x8f,0x97,0x99,0x47,0x27,0x46,0x61,0x57,0x5f, - 0xf1,0x06,0x53,0x86,0xc6,0x32,0x69,0x7c,0xdc,0xd2,0x22,0x58,0x87,0x59,0x12,0xfc, - 0xe2,0x81,0xdc,0x09,0xbf,0xa7,0x5e,0xe6,0xc2,0x6f,0x4b,0x2e,0x30,0xc2,0x9b,0x6e, - 0x62,0x92,0x22,0xf4,0x92,0x85,0xcd,0xd5,0x31,0xc1,0x57,0x84,0x7c,0x7e,0x4a,0xcc, - 0xa4,0xb1,0xc2,0xfd,0x1e,0x4d,0xad,0xcb,0x6c,0xea,0xe3,0x69,0xf6,0x29,0xc6,0x74, - 0x98,0x14,0xaa,0xa2,0x67,0x46,0x4e,0xb7,0x0f,0xf6,0x51,0x8e,0xb1,0x27,0x85,0xb7, - 0x7f,0xa3,0xb7,0xdb,0x3b,0x98,0xcc,0xdd,0x9e,0x16,0x3f,0xe1,0xcb,0xed,0xfd,0x96, - 0x6e,0xe2,0x18,0x1d,0xb5,0x8e,0x81,0xb3,0x8f,0x80,0x02,0xc0,0x9e,0x9f,0x98,0xdd, - 0xc6,0x04,0xde,0xe0,0x4f,0x78,0x33,0xdd,0x32,0xa7,0x66,0x97,0xfe,0x5b,0xc8,0x5a, - 0xef,0x07,0xb0,0x50,0xf6,0xc0,0x56,0xd9,0xe1,0x37,0xcb,0x17,0x12,0xe2,0xbd,0xba, - 0x47,0x22,0x82,0xcd,0xc7,0x90,0xb6,0xcd,0xad,0x01,0x47,0xb3,0x6d,0x6e,0x35,0xd2, - 0xe3,0x4d,0xe3,0x30,0x85,0x5f,0x1c,0x7e,0x56,0x0a,0x47,0x2c,0xa8,0xa0,0xb9,0x69, - 0x7a,0xa1,0x5b,0xa5,0x51,0x4a,0x90,0xbf,0x53,0x4e,0xdc,0xc3,0x37,0x02,0x60,0xda, - 0xfb,0xd7,0x22,0xcd,0x4c,0x43,0x95,0xb2,0x77,0x5a,0xc5,0xd4,0xf6,0x5a,0x5b,0x0b, - 0x3e,0x9b,0xc9,0x9b,0xb0,0x16,0x94,0x52,0x5f,0xb5,0x52,0xfc,0x58,0xea,0xae,0x75, - 0x5f,0xd2,0x18,0x65,0xab,0x95,0x24,0xa7,0xcb,0x73,0x9c,0x52,0x7e,0xcd,0x1a,0x7d, - 0x8d,0x13,0xe3,0xca,0xfb,0x24,0x2b,0x19,0xe5,0x82,0x80,0x7d,0xbc,0x1d,0xc7,0x31, - 0x0b,0x62,0xf2,0xca,0x7a,0xa6,0x76,0xfa,0x37,0xe8,0x6b,0xf1,0xfc,0xa9,0x83,0x97, - 0x28,0x7c,0x1e,0x27,0x9e,0xb7,0x8d,0xb7,0xa6,0xea,0x39,0x94,0xe9,0x5a,0x49,0x3d, - 0x9f,0xac,0x10,0x9d,0x09,0x11,0xcc,0x4f,0x31,0xaa,0xb4,0xb0,0xb7,0x18,0x55,0x9d, - 0x59,0xcc,0x69,0x4e,0x2d,0x6b,0xab,0x50,0xee,0x47,0xbe,0xc6,0xd4,0xb4,0xa1,0x0c, - 0x5e,0x69,0xfa,0x79,0x7a,0xbd,0x8d,0x36,0x70,0x18,0x61,0xf4,0xca,0xbf,0xf1,0x46, - 0x8d,0x8e,0x65,0x9b,0xff,0x69,0x96,0xaa,0xa9,0xab,0x29,0x4d,0xfb,0x6a,0x60,0x9b, - 0xbf,0xfc,0x68,0xfc,0x80,0x37,0x48,0x03,0x7a,0x56,0x47,0x0f,0xef,0x79,0xf0,0xa5, - 0x36,0xde,0xba,0x37,0x86,0x1b,0x00,0x79,0x30,0xed,0x9a,0x4a,0x98,0xcc,0x0b,0x3f, - 0x72,0x55,0xec,0xa2,0x3c,0x06,0xed,0x4e,0x4b,0xd3,0x4e,0x1d,0xba,0x66,0xc2,0x36, - 0x47,0x3f,0x4e,0xcb,0x05,0xdf,0xb8,0xa0,0x90,0x7f,0x3c,0x3d,0x7d,0x8d,0xc5,0x92, - 0x34,0xf5,0xa9,0x14,0x8c,0xf8,0xf4,0xdd,0x47,0xca,0xc5,0x9a,0x86,0x49,0xa9,0xca, - 0x09,0xe7,0xa4,0x30,0xce,0x7e,0xcb,0x41,0x28,0xf3,0x54,0x54,0x60,0x28,0x0b,0x7f, - 0xd4,0x0a,0x27,0x30,0xfc,0x65,0xe5,0x3f,0x70,0x4e,0x2a,0x2a,0x0f,0x25,0xbd,0xe1, - 0xb5,0x8d,0x71,0x30,0x34,0x14,0xa8,0x07,0x3f,0x97,0x54,0x38,0xa3,0x0a,0xa8,0x67, - 0x96,0x97,0x03,0x73,0x82,0x41,0x7b,0xdb,0x67,0xaa,0xc9,0xcf,0x94,0x27,0x0c,0x24, - 0x86,0x6d,0x9e,0x22,0x54,0xe2,0x57,0xa5,0xaa,0x3f,0xf9,0x18,0xbf,0x5c,0xaa,0x3b, - 0xa2,0x97,0xc5,0xca,0xfc,0xae,0x54,0xfb,0xec,0x37,0xe3,0x4f,0x94,0x75,0xb0,0x66, - 0x76,0xf3,0x99,0x7e,0xab,0x22,0xda,0xf1,0xea,0xb1,0x58,0x72,0xcc,0x99,0xad,0x2a, - 0xe0,0x31,0x9b,0xa8,0x42,0x99,0xdf,0x0b,0x2d,0x93,0xa7,0x90,0x5c,0x34,0xf2,0xd3, - 0x51,0x2b,0x57,0x2e,0xfa,0xfa,0x03,0x96,0xf1,0xe3,0xbb,0x3b,0xb3,0x29,0x59,0x19, - 0x89,0x8a,0xd8,0x3f,0x1e,0x23,0x20,0x46,0x01,0x9e,0x02,0x89,0xd2,0xdf,0x32,0xc2, - 0x08,0xc4,0x91,0xe7,0xf3,0x78,0x0b,0x2a,0x89,0xf5,0xda,0xed,0xaa,0x96,0xad,0x37, - 0x04,0xd4,0xa6,0x54,0x88,0x2f,0x4d,0xcd,0x4b,0xe9,0x0d,0x96,0x72,0x0d,0xe5,0x3e, - 0x05,0x41,0x3f,0xcd,0xb3,0x21,0xf6,0x0a,0x34,0x42,0x9e,0x17,0x6b,0x7b,0x3b,0x0d, - 0x24,0xfb,0x87,0x5f,0x53,0x37,0xae,0x66,0xeb,0x94,0x6a,0xe4,0x2c,0xc4,0x03,0xfc, - 0x1b,0x27,0xba,0xe2,0xec,0x89,0x77,0x77,0x37,0x98,0x46,0x93,0x1f,0x2c,0x92,0x10, - 0x61,0x4d,0xf1,0x3b,0x2c,0x6f,0x74,0x45,0x2b,0x4c,0x25,0xd0,0xd4,0x9c,0x24,0x66, - 0x51,0x03,0x2f,0xf0,0x90,0xab,0x6b,0xe2,0x20,0x22,0x10,0xfa,0xc6,0x09,0x45,0x32, - 0x63,0x8c,0xdb,0xbe,0xc9,0xe3,0xb6,0x89,0xb5,0x14,0xee,0x2a,0x8a,0xd1,0xcb,0x74, - 0x53,0x94,0x23,0xa2,0xaa,0x45,0x7c,0xe7,0x6f,0xc4,0x6d,0x45,0x9b,0x5b,0x3c,0x8b, - 0x42,0xa0,0xb5,0x3c,0xba,0x57,0x37,0x01,0x74,0x61,0xff,0xcb,0xbb,0x64,0x55,0x36, - 0x70,0x6f,0xe4,0x98,0xab,0xec,0x1f,0xf2,0x14,0x3d,0xe7,0x0e,0x88,0x10,0x2e,0xe6, - 0xd6,0x72,0x59,0xbb,0xb8,0xa6,0x23,0x66,0x57,0x6a,0x12,0xc0,0x97,0x5d,0xa9,0x40, - 0xe8,0xac,0x8a,0x30,0x00,0xf6,0x2d,0x9e,0xc0,0x2a,0x0b,0xec,0x28,0x4e,0xfa,0xe2, - 0x1a,0x06,0xf6,0x78,0xfa,0x00,0x04,0x34,0xf8,0x88,0xf6,0x93,0xbb,0xbb,0x36,0x26, - 0xce,0xbc,0x76,0xf8,0xba,0xd7,0x5f,0xf1,0x46,0x24,0x90,0x11,0xf6,0xf6,0x49,0x41, - 0xb8,0x76,0xe8,0x8a,0xa4,0xfe,0xfc,0x07,0x68,0xa2,0x07,0x8f,0xe2,0xfe,0xdd,0x09, - 0x3d,0x8b,0xd6,0x2f,0xb1,0xfa,0xa5,0xc7,0x5c,0xe3,0x06,0x74,0xfa,0x0e,0xca,0x6d, - 0x97,0x4e,0x8a,0xf1,0x56,0x0d,0x28,0x68,0xc3,0xbf,0xf8,0x82,0x38,0xe5,0x47,0xd8, - 0xab,0x0d,0x8c,0x28,0x98,0xdb,0x13,0x29,0xe2,0xe2,0x60,0xc5,0xc4,0x0e,0x3b,0x25, - 0x95,0x78,0x1a,0x32,0xdf,0x01,0x62,0xed,0x50,0x78,0x1b,0x3b,0x80,0xd3,0xfc,0xec, - 0xe9,0x8d,0xf8,0x88,0x39,0x18,0x4b,0x1f,0x45,0xdb,0x53,0x3c,0x49,0x9f,0x86,0xd6, - 0x62,0x7a,0xb3,0xd5,0x6f,0xf7,0xa6,0x61,0xb3,0xdf,0xc6,0x35,0xb8,0xc4,0xec,0xb6, - 0xd1,0x95,0x77,0x4a,0xf7,0x28,0xd1,0xf0,0xa7,0x20,0x39,0x7a,0x23,0x7a,0x91,0xdf, - 0xc9,0x23,0x7f,0x88,0x00,0x42,0x0b,0x67,0xfa,0x21,0xc1,0x10,0xba,0xec,0x96,0x2e, - 0xa9,0x68,0x98,0xcd,0xa6,0x3b,0x1c,0x22,0xeb,0x24,0x15,0x13,0x36,0xfb,0x77,0x9d, - 0xc1,0xb3,0x91,0xf7,0x9c,0x56,0xfc,0xd2,0xc1,0x03,0x10,0x82,0x6b,0xbf,0xd3,0xe3, - 0xa7,0x7f,0x00,0xaa,0xe0,0xc5,0xd9,0x33,0x3c,0xfd,0xbb,0x74,0x06,0xde,0xa5,0x1f, - 0x7e,0x80,0x79,0xb0,0xd8,0x47,0xd0,0xa8,0x68,0x34,0xd7,0xb6,0x7e,0xe5,0xc1,0x4d, - 0xdf,0xdf,0xd6,0xc1,0xd6,0x6c,0x5b,0x3f,0x34,0xe6,0xcd,0x5d,0x6b,0xab,0x63,0xdf, - 0xf6,0x27,0xcd,0xdd,0x66,0xe3,0xba,0x09,0xb3,0xde,0x86,0xf9,0xe3,0xdf,0x1f,0x1a, - 0x50,0x44,0x5a,0xb1,0xfc,0x63,0x1e,0xc6,0x59,0xd4,0xb8,0xb1,0x6f,0xad,0xee,0xa5, - 0x83,0xde,0x4b,0xe2,0x29,0xd7,0x46,0x25,0x84,0x1a,0xe2,0x09,0x34,0xed,0x40,0x40, - 0x4b,0x87,0x1d,0x4c,0x00,0x6f,0x99,0xc2,0xdb,0xd3,0xe3,0x1b,0x23,0xbd,0x05,0x29, - 0x7a,0xda,0x9c,0xf9,0x38,0x31,0x44,0x87,0x13,0xbc,0x19,0x0d,0x26,0x8b,0x78,0x63, - 0xe6,0x0d,0xe1,0xb9,0x1c,0x4d,0xe0,0xbc,0x30,0x8b,0x0b,0x7b,0xde,0xec,0xd8,0x34, - 0xd2,0x8a,0x4b,0x09,0xd2,0x5a,0xbe,0x21,0x73,0x89,0xf4,0x8b,0x21,0x75,0x20,0x8b, - 0x53,0x02,0xf3,0x06,0x3b,0x6a,0xe8,0x91,0xb7,0xfc,0x66,0x63,0x63,0xd9,0xc5,0x6b, - 0xa2,0x46,0x4f,0xe5,0xa4,0x1a,0x04,0xb3,0x24,0x0f,0x29,0x75,0xbd,0xdc,0xcf,0x98, - 0x93,0xb5,0x8b,0xca,0x02,0xd1,0x5c,0x6f,0x63,0xc3,0x45,0x9b,0xb6,0x2a,0x85,0x71, - 0x62,0xf8,0x8e,0x1a,0xb2,0xc4,0xdf,0xd2,0xb9,0x09,0xba,0x92,0xf2,0x3d,0x97,0xf9, - 0x78,0x51,0x68,0x54,0x17,0x52,0xf5,0xf3,0xc4,0x46,0xdb,0xdb,0xc6,0x5b,0x8c,0xd8, - 0x24,0x67,0x48,0xba,0xdb,0xd3,0x0f,0xc5,0x9d,0x79,0xc0,0x40,0xe7,0x06,0xc1,0xd8, - 0x18,0x78,0xf8,0x9b,0x42,0xf8,0xa8,0xa6,0x76,0x65,0x68,0x17,0x2f,0x37,0x7a,0xa2, - 0x72,0x21,0xb1,0xaf,0x23,0x1d,0xfb,0x78,0x32,0xb3,0x94,0x74,0xcd,0x75,0x07,0xb2, - 0xa7,0x2b,0x01,0x51,0x15,0xc1,0xc2,0x5d,0xd3,0xc5,0xa4,0x4f,0x84,0xc7,0x3e,0xb9, - 0xf5,0xa6,0xaa,0x32,0xaa,0x3b,0x78,0x9b,0x05,0x3b,0xc9,0xe6,0x57,0x1d,0x25,0xde, - 0x18,0xba,0xc2,0x14,0x10,0xb4,0x56,0x85,0xe6,0x9d,0x27,0x55,0x77,0xee,0xfa,0x05, - 0xb2,0x69,0x00,0xa4,0x2a,0x6a,0x37,0x9a,0xe6,0x57,0x9e,0xf4,0xbd,0x00,0xb4,0xb1, - 0x72,0x84,0xb1,0xb8,0xe8,0x64,0x63,0x63,0xec,0x60,0x00,0x68,0x98,0xe1,0x9d,0xd9, - 0x96,0xfe,0x00,0x5c,0x0d,0xc8,0x76,0xf6,0x23,0xdd,0x28,0xdd,0xa0,0x56,0x6d,0x0c, - 0xc0,0xbf,0xc9,0x4e,0x61,0x56,0x68,0xac,0x23,0xb3,0x25,0xbe,0x5f,0x99,0x03,0x1f, - 0x20,0x32,0x80,0x7e,0x9b,0xde,0x78,0x8c,0x57,0x33,0xc9,0x09,0xf2,0x94,0x1b,0x74, - 0xad,0x2a,0x3a,0xce,0x25,0x9e,0x1b,0x88,0x3b,0x1b,0x7b,0x25,0x38,0xc1,0x8e,0x81, - 0x21,0x40,0xa9,0x0c,0xe3,0x05,0xeb,0xd0,0x7a,0xb9,0x42,0x5d,0x57,0x3a,0x3f,0x92, - 0xc9,0xef,0x55,0x65,0x73,0x7f,0xe1,0xbc,0x5d,0x5d,0x7b,0xba,0x28,0xc3,0x76,0xc9, - 0xe5,0x19,0x85,0xdb,0xc4,0x24,0xd6,0x92,0xe2,0x53,0x51,0xcb,0xa8,0xcb,0x28,0x19, - 0xb2,0x05,0x50,0xb5,0x4f,0x37,0xa7,0xea,0xf2,0xc4,0xe6,0xb7,0xbc,0x45,0x75,0x93, - 0xed,0xcc,0x40,0xb6,0x54,0xf7,0xc7,0x42,0xc7,0x82,0x17,0xc7,0x09,0xdd,0x29,0xdb, - 0x6f,0x83,0xe4,0xa1,0x5e,0x96,0xb2,0x0a,0x0d,0xdc,0x24,0x6d,0xa0,0x7c,0xc7,0x2a, - 0x75,0xd8,0xc7,0xdf,0x47,0xcd,0xbd,0xbd,0xe3,0xdd,0x2e,0xff,0xdc,0x7f,0x76,0xbc, - 0x23,0x7e,0x3e,0x3b,0x38,0xee,0x74,0xdb,0x94,0xd9,0x30,0xaa,0xcf,0x6d,0xb3,0x4b, - 0xe7,0xf3,0x11,0x66,0x7d,0xd1,0xef,0x0e,0x1d,0xf9,0x69,0x1c,0xb8,0xb7,0x5d,0xbe, - 0x05,0x98,0x2d,0x13,0x3d,0x71,0x75,0x62,0x7c,0x23,0xef,0x93,0x4c,0xf8,0xce,0x53, - 0x78,0x31,0x88,0x12,0xd8,0x7c,0x74,0xd9,0xc7,0x2c,0xa5,0x37,0xe2,0x72,0x45,0x50, - 0x97,0x77,0xb6,0xfc,0x1f,0x76,0x40,0x66,0xc1,0x62,0x20,0x9d,0x5f,0x12,0x97,0xc1, - 0x0f,0xd0,0x7d,0x78,0x6c,0xf2,0x0d,0x88,0xc0,0xaf,0x2c,0x98,0x35,0x3f,0x60,0x9f, - 0x78,0x6e,0xb6,0xa9,0xa0,0xb7,0xd9,0xd3,0x12,0x18,0xad,0x18,0x28,0x5e,0x08,0xdc, - 0xa3,0xf5,0x69,0xfa,0x40,0xf5,0xd3,0x2e,0xbe,0x68,0x82,0x4a,0x2a,0xc7,0x83,0x37, - 0xb6,0x52,0xf2,0x98,0x2d,0x53,0x34,0x6d,0x96,0x35,0x60,0x5c,0x99,0x59,0x12,0x28, - 0x1d,0x18,0x7f,0x3f,0x74,0x2f,0x18,0x5f,0xa8,0x80,0x31,0x8c,0x22,0xe2,0x49,0x43, - 0xbd,0xfe,0xf2,0x33,0x1e,0xea,0x4b,0x5f,0x6a,0xda,0x17,0x25,0x6f,0x6b,0x4e,0xce, - 0x81,0xa2,0x6a,0xe2,0xc8,0x5b,0x90,0x45,0x38,0x4e,0xc9,0xd2,0x1c,0x2a,0x1b,0x73, - 0x48,0x41,0x8d,0xd2,0x7d,0x13,0x6b,0xd7,0xc5,0xc3,0x4b,0x37,0x11,0xd2,0xf4,0x9a, - 0x2e,0xfd,0x29,0x78,0xf3,0x7a,0x5e,0x88,0x21,0x18,0xd4,0x3b,0x35,0x52,0xd3,0x23, - 0x8a,0xed,0x50,0xee,0x9c,0xbb,0xbc,0xb0,0xf4,0x9b,0x40,0x7b,0xfa,0x17,0x40,0x38, - 0xf1,0x0d,0x6d,0xab,0x7a,0x5c,0x4a,0xfd,0xc6,0xa3,0x0e,0x85,0x28,0x4f,0xbf,0x0b, - 0xc2,0x7c,0xa8,0x84,0x79,0x89,0x16,0xe2,0xee,0xe5,0xe2,0x45,0xcb,0xf2,0xd6,0x57, - 0x4f,0x65,0x31,0x4a,0xf1,0x80,0x9b,0xe5,0x69,0x1e,0x98,0x10,0xb0,0x69,0x63,0x85, - 0x04,0x03,0x4b,0x9d,0x7b,0x6e,0x6d,0x16,0x77,0x74,0x2a,0x45,0x71,0x59,0x55,0x61, - 0xd1,0x16,0xbc,0xf2,0xc2,0xe1,0xb1,0xb9,0xf1,0x5d,0xbb,0x73,0xd0,0x79,0xb6,0xdb, - 0xe3,0x3b,0xba,0xcc,0xc3,0x74,0x0a,0xda,0x39,0x14,0xe0,0xb6,0xa9,0x06,0xbd,0x51, - 0x77,0x38,0x9b,0x55,0x89,0x7d,0x33,0xbf,0x4f,0xb3,0x72,0x4f,0xa8,0xa2,0x38,0x1d, - 0xba,0xe4,0x33,0x52,0x57,0x63,0xc3,0xde,0x86,0x9d,0xe5,0xe0,0xf5,0x99,0x9b,0xcb, - 0x5c,0x63,0x57,0xd1,0xb9,0x75,0xbb,0x44,0xac,0x15,0xb1,0x89,0xdc,0x59,0x7e,0x2c, - 0xa5,0x37,0xfd,0xd7,0x5d,0x59,0x11,0xdd,0x6b,0x1c,0x59,0x1f,0x60,0xca,0x05,0x61, - 0x45,0x24,0xf6,0x15,0xf6,0xe2,0xdc,0x0f,0x94,0x12,0x79,0xf3,0x27,0x24,0x21,0x08, - 0x26,0x1a,0x6c,0x83,0x4e,0x90,0xe8,0x97,0xcc,0xf0,0xb8,0x18,0xcc,0x06,0x20,0x43, - 0xa4,0x5d,0x46,0x5b,0x71,0x2e,0xa2,0x58,0x53,0xad,0xb3,0xa8,0x48,0xed,0x57,0x14, - 0x0b,0x81,0x0d,0x7f,0x04,0xc1,0x9a,0x12,0x1c,0x91,0xff,0x6c,0x9e,0x2b,0x63,0xe0, - 0x5e,0xe1,0xad,0xe9,0xb0,0x17,0xbd,0xa1,0x3b,0x13,0x01,0xd7,0x7c,0x41,0xf0,0xc9, - 0x07,0xba,0x6d,0x2b,0x8c,0x0c,0x5f,0xdc,0xc7,0x4c,0x91,0xb9,0xa7,0xd1,0x0c,0x98, - 0x47,0xd7,0xc0,0x5b,0x90,0xd3,0xee,0x36,0xd2,0x10,0x3c,0xad,0x9a,0x0c,0x81,0x4f, - 0x3b,0xe1,0x17,0xa2,0x29,0xd7,0x6d,0x11,0x3a,0x61,0x34,0xf8,0x6f,0x5e,0xc2,0x8f, - 0x2c,0xdb,0xf8,0xc7,0x2c,0xc0,0xb0,0xe0,0x7d,0x6a,0x70,0xdc,0x7f,0xfb,0xf3,0x17, - 0x18,0xc9,0xbc,0x7f,0xf5,0xf3,0x17,0x0c,0xd7,0x92,0x11,0x20,0x06,0xf4,0x8e,0x97, - 0x25,0x88,0x31,0x8f,0xed,0xc1,0xdc,0x46,0xd7,0xfd,0xe4,0x5f,0x0e,0x79,0xbe,0x16, - 0x92,0x0f,0xf6,0xcf,0x01,0x3a,0x8b,0xac,0x6b,0x7e,0x3a,0x3d,0xd9,0x7e,0xe1,0x86, - 0xee,0xc8,0x35,0x1a,0x18,0xbb,0x34,0x85,0xf5,0x19,0x79,0x23,0x0b,0x96,0xbf,0x6b, - 0x3e,0x6f,0xb7,0x9c,0xbd,0xce,0x9e,0x89,0xbe,0xec,0xe6,0x7e,0xc7,0x81,0x5f,0x29, - 0xbc,0x7e,0x66,0xa2,0x4b,0xbb,0xb9,0xc7,0xf7,0x7e,0x60,0x2b,0x27,0x33,0x90,0xeb, - 0x81,0x9e,0xbb,0xa2,0xda,0x9e,0x73,0xd0,0x6a,0x71,0xb5,0xce,0x5e,0x8b,0x6b,0xb5, - 0x5b,0xcb,0xab,0x19,0x8d,0x77,0x2e,0x9e,0x35,0xc8,0x6e,0xf7,0x9d,0xbd,0x67,0xcb, - 0xba,0x3d,0xa8,0xad,0xff,0xd6,0x57,0x63,0xde,0x73,0x5a,0xb2,0x72,0xbb,0x23,0xea, - 0x3e,0x5f,0xde,0x77,0xd7,0x38,0x3d,0xb1,0x8d,0x5f,0x4f,0xb8,0x76,0x67,0xc7,0x69, - 0x57,0x67,0x7c,0xb0,0xbc,0xeb,0xae,0xf1,0x1f,0x6f,0x7e,0x5a,0xa3,0xae,0xd6,0xf5, - 0x8f,0x89,0xfb,0xc5,0x0f,0x1e,0xd7,0xdf,0xcb,0x4f,0xdb,0x9f,0x7e,0x29,0x82,0xe9, - 0x60,0xff,0xb9,0xb3,0xdf,0x3e,0x58,0xbb,0xee,0x4f,0x1e,0xe0,0x31,0x26,0x30,0x1d, - 0xe5,0xf5,0xd5,0xea,0xe6,0xcb,0xd4,0xae,0x8c,0xf7,0xc5,0x17,0x6f,0x38,0x81,0x3d, - 0x11,0xcf,0x40,0x9a,0x1d,0x56,0x07,0xb1,0xbb,0xd3,0x79,0x18,0x45,0x5e,0x7e,0x32, - 0x76,0x77,0x76,0x00,0x77,0x8d,0xc6,0x9b,0x28,0xbc,0x34,0xe8,0x1c,0x86,0xdb,0x80, - 0xf7,0x0e,0xde,0x31,0xfc,0xe0,0x40,0xb4,0x36,0xf4,0x31,0x14,0xea,0xaf,0x02,0xc4, - 0x3b,0xca,0x62,0x12,0x90,0xf7,0xdf,0x0a,0x08,0x56,0x07,0x8f,0x57,0x34,0xff,0x37, - 0x88,0xdd,0x78,0xb2,0xc4,0x48,0xf6,0xcc,0xd9,0x79,0xb6,0x06,0xe8,0xb4,0x8a,0x65, - 0x1c,0xd7,0x5a,0x58,0xd5,0x35,0x66,0x85,0x9b,0x5d,0x02,0x29,0x82,0x59,0xaa,0xd9, - 0xd6,0xd4,0x14,0x18,0xbe,0x5f,0x53,0xf3,0x60,0xff,0x60,0x8d,0xe9,0x6a,0x35,0x4f, - 0xe7,0x7e,0xf6,0x85,0x01,0xf5,0x38,0x4c,0xfb,0x4f,0xdf,0xcb,0x42,0x77,0x5a,0x9a, - 0x6a,0xa7,0xe5,0x74,0x96,0x2e,0xcf,0x5e,0x4d,0xed,0x32,0xa6,0x16,0x5a,0xa8,0x03, - 0xf7,0x93,0x0b,0xcd,0xff,0xbe,0x98,0x36,0x75,0x91,0x67,0x6d,0x5f,0x91,0xb2,0x5d, - 0x57,0x67,0x44,0x2c,0x12,0x89,0x5e,0xa8,0x93,0x85,0xc0,0xbe,0x12,0xc0,0xfb,0x21, - 0x67,0x00,0xea,0x1a,0x3c,0x09,0x90,0x24,0xf1,0x2f,0x0c,0xc6,0xa0,0x31,0xc1,0x33, - 0xfc,0x71,0x5a,0xa6,0x38,0x6d,0x77,0xef,0xee,0x14,0x3b,0x34,0x94,0x3b,0x27,0xdb, - 0x60,0x5c,0x2d,0xd2,0xc8,0xbe,0x05,0x7e,0x57,0x0c,0x3c,0x2a,0xba,0xe9,0xb2,0xec, - 0x0f,0x4d,0x8a,0x9b,0x3b,0x6f,0xd0,0x27,0xf6,0x69,0xbf,0x2f,0x1e,0x6f,0xf1,0xb1, - 0xd2,0x8f,0x26,0xc2,0x15,0xcd,0x83,0x04,0x9c,0x9f,0x28,0x31,0x65,0x11,0x36,0x06, - 0xee,0x2b,0x75,0x73,0xdf,0x8f,0xbf,0x4a,0x50,0xe5,0xef,0x4e,0x5f,0x49,0xa8,0xe5, - 0xef,0x5e,0x7c,0xac,0x00,0x70,0xfe,0x85,0x62,0x93,0x5e,0xf9,0x94,0x52,0x35,0x0f, - 0x1c,0xd0,0x33,0xec,0xda,0xc3,0x59,0x52,0xcc,0xd3,0x7b,0x77,0xc7,0x8a,0x8f,0xca, - 0xf1,0x59,0x4e,0x8b,0x09,0x02,0x0d,0x27,0x9b,0xa4,0x00,0xdf,0x84,0x38,0x74,0x7e, - 0x91,0xbb,0x9e,0x21,0x93,0xac,0x7f,0x28,0x2a,0x60,0xce,0x5d,0x78,0x5e,0x27,0xed, - 0xae,0xe6,0x62,0x5c,0x97,0x7b,0x17,0x86,0x6b,0x59,0xdc,0xa6,0x5f,0x4c,0xc2,0x59, - 0xce,0x01,0xea,0xeb,0x99,0x3f,0x33,0x10,0x5a,0x01,0x56,0x23,0x37,0x9d,0xf4,0x8c, - 0xcd,0xad,0x02,0xf0,0xf5,0x84,0xa0,0xb9,0xed,0xac,0xbe,0x59,0xca,0xdd,0x7b,0x44, - 0x71,0xa8,0xf2,0xfa,0x23,0x19,0x72,0x5c,0x6c,0x04,0xc0,0x5c,0xc9,0xad,0xc8,0xb9, - 0x9c,0xb0,0x6b,0x02,0x9f,0x32,0xaf,0x70,0x0c,0x72,0x80,0x81,0xa9,0xb7,0x0c,0x2f, - 0x4c,0x5d,0x8e,0x71,0xb3,0x73,0x89,0x27,0x3d,0xbe,0x3b,0x72,0xee,0xf3,0x35,0xba, - 0x43,0x4c,0xfa,0x35,0x9c,0x44,0x1c,0xfa,0x8a,0x36,0x52,0xac,0x75,0xd4,0x6f,0x59, - 0x69,0x39,0xe3,0x3d,0x7d,0xd1,0xb2,0xaa,0x35,0x38,0xfa,0x93,0x82,0x2e,0xe1,0xaf, - 0xc8,0xcd,0x2c,0xef,0xd8,0x56,0x98,0xd1,0x84,0xe9,0x15,0xaf,0x94,0x7c,0xc1,0xf3, - 0x0d,0x6e,0xbb,0xc6,0xe1,0xe0,0x88,0x4d,0xf3,0x43,0x8a,0x55,0xb4,0xea,0x70,0x96, - 0x3f,0x0f,0xe6,0x56,0x19,0x71,0xf9,0x43,0x3a,0xb6,0xca,0xd8,0xcb,0x1f,0x86,0x09, - 0x29,0x09,0xd0,0x03,0xad,0xee,0x16,0x4f,0xe0,0xb0,0x75,0x6c,0x1a,0x8d,0x51,0xe4, - 0xa5,0xe1,0x66,0xc6,0x30,0x02,0x08,0x24,0x52,0x3a,0x64,0x20,0x59,0xa4,0x41,0x48, - 0xe8,0xaf,0xb8,0xd8,0xa0,0xba,0x47,0x00,0xa3,0x0b,0xf9,0x6f,0xaf,0xfb,0x0a,0x90, - 0x36,0x61,0x62,0x2f,0x4f,0x93,0x6f,0x9a,0x77,0x77,0xf4,0x97,0x90,0xc1,0x5a,0xd0, - 0xf7,0xe2,0x1e,0xba,0x17,0xb7,0x63,0x2c,0xf8,0xda,0x8b,0x02,0xde,0x9f,0x6f,0x5d, - 0x5f,0xf4,0xb8,0x4e,0x11,0xb7,0xef,0x19,0x41,0x46,0x98,0x6c,0x83,0xe5,0x59,0x4c, - 0x64,0x3e,0x32,0xfe,0x45,0x4d,0x8a,0xab,0xb7,0x26,0x49,0x34,0xbb,0x94,0x61,0xef, - 0x53,0x34,0xe5,0x65,0x13,0x14,0x38,0x39,0x78,0xfe,0xda,0x0d,0x87,0x50,0xc3,0x1b, - 0xf9,0x59,0x94,0x3c,0x79,0x4c,0xd8,0x24,0x67,0x79,0xc3,0x08,0x4d,0x1a,0x9a,0x4e, - 0x16,0x1f,0x1b,0x4b,0xa9,0x30,0x30,0xce,0x83,0x28,0x97,0xdf,0x8a,0x2c,0x22,0x29, - 0xa9,0xd7,0x4a,0xca,0xf0,0x7c,0x3b,0xa6,0x5f,0x57,0xb9,0x9a,0x6b,0x9c,0xca,0x96, - 0x3c,0xdf,0xb5,0x0b,0x1d,0x0b,0x91,0x8c,0xe2,0xda,0xc3,0x6a,0x30,0x23,0x59,0x37, - 0x34,0xf2,0xfa,0xf0,0x69,0x10,0x5d,0xa1,0x1a,0xaa,0xd4,0xa9,0xf9,0xc1,0xdd,0xc6, - 0x06,0xe6,0xc7,0xde,0xb1,0xc2,0xfe,0x2e,0x45,0x0a,0xb6,0xed,0x8e,0xbd,0x63,0xef, - 0xd6,0xdd,0xb0,0xa8,0xb2,0x22,0xe1,0x1e,0x35,0xb7,0xfc,0xe5,0xe1,0x4c,0x3e,0x00, - 0x23,0xd4,0x0d,0x06,0x59,0xbc,0xa4,0x3c,0x46,0x2a,0xa2,0x5d,0x49,0x03,0x35,0x8c, - 0x8f,0x87,0xa4,0xd2,0xee,0x7f,0x48,0x3c,0xb4,0xf8,0x1b,0xd1,0x80,0x4f,0xc5,0x28, - 0xbd,0x6a,0x9e,0x7f,0x81,0xe8,0x17,0xbd,0x6a,0x72,0x96,0x23,0xce,0x0a,0xc1,0x19, - 0x60,0x28,0x2f,0x2d,0x5a,0x9e,0xd5,0x75,0xf7,0xa1,0xe1,0xdd,0x70,0x42,0x23,0x79, - 0x23,0x1d,0x87,0xff,0xbb,0xe1,0x0c,0xb6,0x32,0xe2,0x2e,0x5d,0xee,0x40,0xa9,0x67, - 0x44,0xb4,0xbb,0x37,0xca,0xef,0xbd,0x8f,0xe8,0x2a,0xe3,0xf9,0x97,0x1d,0xa3,0x90, - 0x09,0x56,0x5e,0x09,0xa5,0xd2,0x82,0x90,0x19,0x68,0xaa,0xf2,0xff,0x89,0xdb,0x55, - 0x25,0x8a,0x88,0xbb,0x56,0x4d,0x75,0x94,0x1a,0x6d,0x6c,0x3c,0x15,0xd9,0xf3,0x61, - 0x49,0xa6,0x98,0xdb,0x9e,0xb1,0x38,0x9c,0xf6,0xa2,0x47,0xeb,0xbc,0xf7,0xf9,0x4d, - 0xd3,0x08,0xcb,0x5d,0x8b,0xf2,0x6c,0xf3,0x2d,0xbc,0x7c,0x20,0x21,0x4e,0xf3,0x64, - 0x18,0x20,0x1e,0xa8,0x95,0xe3,0x11,0xe2,0xf7,0x32,0xe8,0x0e,0x53,0x2e,0x78,0x9c, - 0x2d,0x4c,0xdd,0x5f,0x4c,0x56,0x59,0x80,0x66,0x3a,0xe7,0x6c,0x00,0xef,0x4f,0x8d, - 0xa1,0x1b,0xe3,0xa9,0x06,0xe6,0xa4,0x1a,0x88,0x94,0x01,0x86,0x29,0x32,0x10,0x9a, - 0xc2,0xf8,0x2f,0xee,0x92,0x26,0x8b,0x5f,0x28,0xac,0xd5,0x68,0x9f,0x18,0x78,0xe8, - 0x1f,0x3f,0xf5,0x31,0x95,0x95,0x4a,0x36,0x80,0xa6,0xe8,0x58,0xde,0x8f,0xc1,0x8e, - 0xce,0x94,0xaf,0x63,0xee,0x06,0x57,0x79,0x63,0x98,0x68,0x56,0x51,0xa6,0xc4,0xc3, - 0x03,0x11,0x59,0x07,0xd3,0xc6,0xba,0x94,0x06,0x88,0x53,0x5a,0x92,0xc9,0x7c,0x00, - 0x12,0x29,0x54,0x71,0x0a,0x3e,0x33,0x5c,0x72,0x1b,0x50,0x23,0x2b,0x5f,0x80,0xb9, - 0x32,0x6a,0x62,0x63,0x23,0x71,0xd0,0xe2,0x88,0x68,0x8e,0x95,0x9b,0xf0,0x50,0x72, - 0x82,0xa1,0x02,0xf9,0x66,0xa0,0x52,0xd1,0x75,0xd5,0x26,0x4e,0xe9,0xed,0x97,0x06, - 0x49,0x8b,0xc0,0xe4,0x17,0xd1,0x2c,0x18,0x21,0x13,0x82,0xa9,0x0c,0x35,0xfc,0x6f, - 0xa2,0x5f,0xaf,0xe1,0xd2,0xed,0x76,0x56,0x69,0xf3,0x7b,0xe3,0x31,0x26,0xa5,0x14, - 0x42,0x5e,0x29,0xfd,0xc1,0xb1,0x96,0xa4,0xa0,0xdb,0xc8,0xf3,0x26,0x20,0x76,0xea, - 0x56,0xf8,0x62,0xea,0x34,0x76,0xff,0x96,0x61,0x84,0x82,0xa4,0x54,0xaf,0x5d,0xd7, - 0xaf,0x69,0x63,0x5e,0x84,0x63,0xc9,0x53,0x19,0x80,0x40,0xd5,0x2f,0x5e,0x3b,0xa1, - 0x3e,0xa9,0x1b,0x43,0xe4,0xc1,0x8b,0xec,0x91,0x44,0x87,0xc2,0x9d,0xc9,0xb0,0x75, - 0x62,0xd9,0xd5,0xa9,0x87,0x48,0xc9,0xa3,0x55,0x89,0x2b,0x4d,0x79,0xa3,0x63,0x7c, - 0x77,0x37,0xb4,0x36,0x36,0x62,0xa0,0x4f,0x43,0x59,0xe3,0xa4,0x50,0x34,0x35,0x46, - 0x11,0xc5,0x3c,0x70,0x8c,0x4b,0x6f,0xd9,0xed,0x58,0x85,0x0d,0xa5,0x02,0x95,0x00, - 0xb1,0x30,0x44,0x78,0xa4,0xb6,0x7d,0x21,0x57,0x19,0x7c,0x6e,0xd0,0x6d,0x57,0x58, - 0x8e,0x0f,0xe5,0xeb,0xbc,0x10,0x36,0xb7,0x02,0x14,0x41,0xc8,0xd9,0x80,0xc5,0x92, - 0x6b,0x21,0x93,0xf0,0xa1,0x04,0xa7,0x87,0xc0,0xc6,0x4c,0xfd,0xfe,0x70,0xd3,0x1e, - 0x9d,0x6b,0x09,0xf2,0x2e,0x14,0xb1,0x29,0xbd,0xc5,0x58,0x17,0x12,0x1b,0x2d,0x46, - 0xb6,0xbc,0xa1,0x3c,0xe5,0xb9,0x6c,0x09,0xf3,0xe9,0x5d,0x1c,0x9b,0x3f,0xd0,0xff, - 0xcc,0x1c,0x3d,0x0a,0x5f,0x1b,0x30,0x3f,0x91,0x45,0x0f,0x24,0xa0,0x06,0xe5,0xb6, - 0x11,0x63,0xb2,0x44,0x5e,0x23,0x3c,0xa2,0xaf,0xa1,0x80,0x16,0xf5,0xfd,0x4e,0xd2, - 0x6f,0xe8,0xb7,0xae,0x90,0x0a,0x01,0x18,0xf6,0x47,0x52,0x47,0xa8,0xa8,0x0c,0x76, - 0x12,0x73,0x5a,0xed,0x35,0xe4,0xfc,0x55,0x97,0x6b,0x24,0x43,0xd0,0xa1,0x62,0x99, - 0x68,0x9b,0xc6,0xf7,0x91,0x39,0x77,0x23,0x89,0x8f,0x13,0x10,0xe5,0x41,0x6e,0x6c, - 0x1a,0x6c,0x2c,0x4e,0x86,0x9a,0xa0,0x22,0xac,0xc1,0xe8,0x76,0x62,0x6d,0x35,0xc4, - 0x48,0xd9,0x2b,0x45,0x87,0x90,0x06,0x90,0xec,0x86,0x01,0x70,0xf6,0x9b,0x41,0x37, - 0x0d,0xe2,0xfc,0xb3,0x1b,0x68,0x9f,0x3c,0x81,0x44,0xb1,0xba,0x7d,0xb5,0xb1,0xd1, - 0x28,0xef,0x81,0xbb,0xbb,0xd2,0xce,0xb2,0xb8,0xed,0x22,0x76,0x9b,0x76,0xa9,0xd4, - 0xb1,0x49,0xd7,0xcf,0x95,0x71,0xa2,0xce,0xb7,0x87,0x9a,0x7b,0xaf,0xb3,0x60,0xc2, - 0x93,0x02,0xf3,0xd3,0x70,0xae,0xf4,0xbe,0xdc,0x83,0x68,0xef,0xf5,0xc9,0xd9,0x09, - 0x80,0x56,0xb6,0x43,0xb7,0x26,0x56,0x5a,0x91,0x6f,0xf5,0x36,0x40,0x82,0xf8,0x14, - 0xc7,0x5e,0xf2,0x02,0x84,0xbf,0x86,0xa5,0xf1,0x5c,0x50,0x5a,0xfa,0x6a,0x58,0x74, - 0x8f,0x5f,0x75,0x54,0xfc,0x3a,0xe7,0xbd,0xf3,0x90,0x81,0xf5,0x1e,0x3f,0xa0,0xa0, - 0x6b,0xda,0xf0,0x2e,0x8f,0xb2,0x3f,0xc6,0x27,0x0e,0xa3,0x6f,0xd9,0xed,0x0e,0xec, - 0x46,0xf4,0x82,0xec,0x62,0xbd,0xbc,0x63,0x6f,0xaa,0xfa,0xe5,0xfb,0x01,0x2b,0xfd, - 0x8a,0xd7,0xaa,0x5f,0x6f,0xaa,0x77,0xcb,0x5f,0x6d,0x78,0xc9,0x05,0xd6,0xb8,0xd5, - 0x42,0x12,0x55,0xd1,0x2f,0xc8,0x58,0x30,0x32,0x99,0x8d,0xbe,0xd4,0x7b,0xf1,0xa3, - 0x16,0x4e,0x16,0x33,0x3d,0x14,0x19,0xe2,0x69,0x40,0xe4,0x07,0x05,0x15,0xec,0xb8, - 0x70,0x3f,0x8c,0x98,0x27,0xa6,0xef,0x2d,0xdc,0x02,0x33,0xaa,0x1e,0x6d,0x29,0x76, - 0xf3,0x74,0xfb,0x9f,0x0d,0x8e,0x9c,0xbb,0x93,0x58,0xf7,0xfd,0x1d,0x22,0xcf,0xf7, - 0x77,0xb4,0x41,0xbe,0xbf,0xcb,0x6e,0xbe,0xbf,0xc3,0x01,0xfe,0x2e,0xb0,0x45,0x3c, - 0xe0,0xa2,0xcb,0xf7,0x08,0x1f,0xf1,0x9b,0xa0,0x44,0xbf,0x29,0x95,0xf9,0xef,0x8e, - 0x25,0x52,0x3a,0x5f,0xe1,0x35,0xc4,0x85,0x38,0x07,0x1a,0xa8,0x80,0xb0,0x96,0x76, - 0x35,0x05,0x18,0xe3,0x97,0x42,0xe2,0xb3,0xfc,0x02,0x1a,0x54,0x3f,0x89,0xac,0x17, - 0xd4,0x4f,0xa2,0xd8,0xf2,0xb8,0xa7,0x28,0x59,0x93,0x90,0x9e,0x27,0x2d,0x15,0xbc, - 0x90,0x59,0xaa,0x4e,0x79,0x81,0x4b,0xd5,0x11,0x64,0x3e,0xe3,0x81,0x5e,0x6b,0xee, - 0x71,0xfe,0x10,0x78,0x6e,0x4a,0x07,0x0b,0x20,0xa0,0xea,0xb4,0x1e,0x1d,0x10,0xbd, - 0xd8,0x68,0x5b,0x05,0x5f,0x5c,0xe8,0x5a,0x33,0xad,0xe4,0x99,0x15,0xcc,0x35,0xfa, - 0x00,0x6d,0x1e,0x13,0xc7,0xba,0x85,0x93,0x0e,0xd1,0x4d,0xc7,0x32,0x9a,0x24,0x65, - 0xa4,0x20,0xb0,0xf9,0x31,0x0b,0x76,0x80,0x2e,0xcd,0x4f,0xa7,0x78,0x9d,0x01,0xfa, - 0xa4,0xa7,0x85,0x81,0x2c,0xcd,0xb8,0xaa,0xa2,0x34,0xb4,0x54,0xab,0x4b,0x87,0xc6, - 0x05,0x60,0x99,0xe4,0x28,0xca,0x93,0xd5,0xb5,0x18,0xc0,0xb3,0xf3,0x93,0xe6,0x7f, - 0xbb,0xcd,0x2f,0x17,0x8b,0x9d,0x7b,0x99,0xe7,0x3b,0x5f,0x88,0x75,0xc8,0x8b,0x69, - 0x15,0x54,0x9c,0x15,0xd0,0x22,0x5f,0x02,0x00,0xd6,0x4e,0x33,0xf0,0xd0,0xcb,0xd8, - 0x40,0x3a,0x26,0xec,0x01,0x06,0x86,0xe2,0x1a,0x0d,0x72,0x08,0xc5,0x81,0x17,0x87, - 0xbd,0xbc,0x69,0x25,0x24,0x0d,0xa4,0x81,0x8c,0x53,0x52,0xdb,0x03,0xca,0x0d,0xdf, - 0x1f,0xe8,0xc5,0x7b,0x83,0x52,0xa2,0xe9,0x41,0xb1,0xb1,0x33,0x8f,0x35,0x1c,0xca, - 0xa4,0xe7,0xb0,0xeb,0xa0,0x9e,0x7e,0x76,0xc1,0x21,0x82,0xac,0x2f,0xe4,0x27,0x7c, - 0x89,0xc8,0x8b,0x2a,0xae,0x49,0x2f,0xe7,0xa1,0x1c,0x94,0xd2,0x51,0xe0,0xb8,0xf2, - 0xc0,0xaf,0x44,0x4f,0xba,0xac,0xb7,0x23,0xb3,0xb6,0x29,0x5b,0x13,0x68,0x04,0xb1, - 0x0c,0x4f,0xe4,0x74,0xb6,0x8d,0x4e,0xab,0x63,0x71,0xd6,0x55,0x29,0xfc,0x53,0x4a, - 0x3c,0x8e,0xe2,0x9d,0xcb,0x7c,0xc3,0x7a,0x73,0x68,0x9f,0x42,0x23,0x16,0x6d,0x6d, - 0xce,0x6a,0xcb,0x1e,0x48,0x48,0x67,0xa0,0x71,0x4c,0x00,0x03,0x5b,0xc0,0x1f,0x71, - 0xc0,0xa9,0xaa,0xaa,0x65,0x26,0x34,0xeb,0x87,0x24,0x4d,0x6a,0x94,0x18,0x4e,0x24, - 0xd8,0xa5,0xcb,0xc5,0x71,0x78,0x1c,0x8c,0xa9,0x25,0x86,0xe5,0x88,0x4d,0x99,0x1e, - 0xd6,0x31,0x5e,0xcb,0x1c,0xbb,0xa2,0x19,0x31,0x1e,0x9b,0x26,0x04,0xfa,0x16,0xe6, - 0x47,0xc7,0x31,0xa4,0xc6,0xef,0x26,0x6b,0xf4,0x9c,0xc6,0xef,0xe9,0xef,0x26,0xe5, - 0x06,0xcc,0xb3,0x19,0x12,0x28,0x80,0x7b,0xef,0xb4,0xc4,0xe5,0x7d,0x29,0xb5,0xae, - 0xbc,0xa5,0x38,0x29,0xfa,0x2c,0x36,0x30,0x8c,0xc9,0xce,0x33,0xb1,0xa3,0x7a,0xf6, - 0xd6,0x4b,0x27,0x2f,0xa2,0xc4,0x6b,0x9e,0x52,0x09,0x6c,0x58,0x5c,0xe8,0x24,0xf2, - 0xd3,0x89,0x3c,0x8f,0x04,0x32,0x3d,0xc5,0x60,0x4d,0x92,0x41,0x34,0x33,0x36,0x45, - 0xae,0x5b,0xa5,0x71,0xe4,0xc1,0xc0,0xb5,0xf9,0x1f,0x8b,0x81,0xae,0x79,0xc4,0xf0, - 0x72,0xfc,0x5f,0xa6,0xd9,0x88,0xdc,0xec,0x8e,0xf1,0x82,0xb2,0xea,0x52,0xc2,0x7b, - 0x76,0x42,0x35,0x54,0xc6,0xe2,0x88,0x2d,0x99,0xa5,0x49,0x6b,0x1a,0x23,0x25,0xdf, - 0x75,0xcc,0xe5,0x41,0xb8,0x79,0x76,0xd5,0x4a,0x12,0xd5,0x12,0x6b,0xbb,0xd1,0x58, - 0x5b,0x9e,0x3b,0xf5,0x46,0x64,0x1c,0xd5,0x52,0x70,0x51,0x73,0xe5,0x3b,0xe0,0x96, - 0xce,0x9f,0x4b,0x97,0x1d,0xa0,0x85,0xce,0x76,0x43,0x49,0x47,0xcd,0x2e,0x79,0x34, - 0x53,0x47,0xca,0xfb,0x00,0xfd,0x94,0x1f,0x0a,0x96,0x17,0xc9,0x20,0xf3,0x28,0xe6, - 0xba,0x2d,0x20,0x12,0x71,0x6e,0x82,0xca,0x81,0x32,0x62,0xce,0xa2,0x34,0xf7,0x0a, - 0x73,0xf3,0xdf,0x9b,0x96,0x53,0x5b,0xb1,0x6a,0x4e,0xce,0x4a,0x1a,0x4c,0xe2,0x88, - 0xa2,0xdd,0x9a,0x34,0x98,0x46,0x1a,0xa1,0x79,0xe9,0xc1,0x5c,0x98,0x85,0xb0,0xf3, - 0x32,0x68,0x14,0x2d,0x00,0xf9,0x5c,0xa4,0xfa,0xcc,0xe9,0x00,0xc1,0x6d,0xe4,0x8f, - 0x48,0x61,0x84,0x65,0x41,0x6f,0x3d,0x97,0x82,0x5a,0x55,0xb2,0x4f,0x31,0xf8,0x62, - 0x3e,0xd0,0xcd,0x3c,0xbf,0x68,0x05,0xd8,0x00,0x63,0x02,0x2c,0x7a,0x15,0xf0,0x09, - 0x07,0xa5,0x05,0x45,0x60,0x6d,0xa2,0x33,0xe6,0xdc,0x03,0xd5,0x73,0x13,0x5b,0x01, - 0x8d,0x98,0x32,0x49,0x7e,0x45,0x7e,0x50,0x72,0x72,0x5e,0x06,0x8e,0x27,0x35,0x5b, - 0xba,0x36,0x45,0x9b,0xc8,0x30,0xbe,0xc4,0x99,0xb6,0xdc,0xbc,0xc4,0x65,0x32,0x99, - 0xf0,0xc7,0xcf,0x7e,0xf8,0x79,0x9a,0x1e,0x4b,0x0f,0xec,0x46,0x9b,0xc3,0x65,0x86, - 0x9e,0x1f,0x34,0x8a,0x65,0x38,0x72,0xc7,0xea,0xee,0xb4,0x34,0xd1,0x6b,0x24,0xd2, - 0xde,0xe6,0x82,0x97,0x20,0xcc,0x78,0x39,0x1f,0x0d,0x8d,0x88,0x05,0xdb,0x07,0xa3, - 0xf9,0xb1,0x69,0x69,0x5e,0x29,0x5a,0xf4,0x38,0x77,0x64,0xda,0x8b,0x65,0x51,0xe2, - 0xb5,0xfb,0xa5,0x90,0x0d,0x56,0xdf,0x16,0x94,0x9a,0x1d,0xad,0x31,0x94,0xed,0x53, - 0x2e,0x3b,0x5e,0xa0,0x87,0x1c,0x03,0x93,0xaa,0x03,0x25,0xb2,0x95,0x28,0x74,0xff, - 0xe8,0xe4,0x74,0x62,0x72,0xec,0xd2,0x53,0x08,0x7c,0x2e,0x5b,0x7e,0xb4,0x81,0x4f, - 0xd3,0x4b,0x5b,0x8d,0xc6,0xce,0xfc,0x2c,0xf0,0x6c,0xd8,0x13,0x82,0xb1,0x28,0xa7, - 0x4a,0x86,0x45,0x93,0xbe,0x97,0x28,0x14,0xbd,0x03,0x31,0xe9,0xa3,0xc4,0x23,0x29, - 0x58,0x68,0xf5,0xa0,0x97,0x52,0x2d,0x71,0xe5,0x0d,0x69,0x32,0xc0,0x4b,0xe7,0x7d, - 0xad,0x34,0x5e,0xc2,0x03,0x83,0x08,0xfc,0xf0,0xaa,0xf8,0x1a,0x5d,0x0a,0xaf,0x94, - 0x2a,0x8a,0x94,0x0b,0xeb,0xcf,0x92,0xa0,0x6f,0xa2,0x03,0x4d,0x77,0x7b,0x1b,0xb4, - 0x93,0x78,0xcb,0xdc,0x36,0x7b,0x5c,0xbd,0xd0,0x25,0x9a,0xdb,0xc4,0xeb,0x49,0xe2, - 0x8d,0xc5,0x33,0xf4,0xbd,0xd4,0xeb,0x56,0x1d,0x98,0xd4,0xb4,0x66,0x9a,0x85,0xc6, - 0xcc,0xef,0xcc,0x4a,0x63,0x9a,0x4b,0xeb,0x7d,0x11,0x1e,0xab,0xed,0x7c,0xec,0x11, - 0x2a,0x96,0xe0,0xee,0x6e,0xa7,0xd5,0xd3,0xaa,0x52,0xa8,0x48,0xf9,0x3a,0x4b,0x59, - 0x2b,0x2b,0x44,0x1e,0x96,0xf1,0x35,0x6c,0x36,0x1f,0x6c,0xe9,0xa8,0x75,0x1c,0x76, - 0x35,0x03,0x74,0x78,0xd8,0xdf,0xc3,0x74,0x22,0x02,0x43,0x14,0xaf,0x18,0x7b,0x88, - 0x9b,0x79,0x60,0xe1,0x0c,0xf4,0xa7,0xc5,0x10,0x38,0xb4,0x87,0x97,0x8a,0x35,0x29, - 0x6d,0x66,0xad,0x89,0x94,0x38,0x4f,0x74,0x55,0x8e,0x6f,0xcc,0xac,0x5e,0x35,0x09, - 0xc8,0xb2,0x78,0x96,0x02,0x1f,0x83,0x11,0xb6,0x6a,0x5a,0xd3,0x18,0x6b,0x69,0xbe, - 0x9a,0xfa,0x26,0xa7,0x75,0xbc,0x79,0xe8,0x1a,0xbc,0x8e,0xdb,0xe6,0xd1,0x47,0xea, - 0xff,0x70,0xdb,0x3d,0xda,0xec,0x9a,0xff,0x15,0xcd,0x38,0x57,0x94,0xb8,0x41,0x43, - 0xde,0xab,0xa3,0x87,0xdd,0xd8,0x6d,0xce,0xa1,0x07,0xa4,0x90,0x09,0x4f,0xef,0xc9, - 0xe1,0x36,0xc8,0x74,0x7e,0x9c,0x1d,0xc1,0x2f,0xf4,0xd7,0xc7,0xbf,0x78,0xde,0x7a, - 0xf4,0xe4,0xff,0x00,0xd5,0x9e,0x0e,0x8a,0x88,0x14,0x01,0x00, + 0x1f,0x8b,0x08,0x00,0x00,0x00,0x00,0x00,0x02,0x03,0xd4,0xbd,0x69,0x7b,0xdb,0x46, + 0xb2,0x28,0xfc,0x9d,0xbf,0x02,0x86,0x73,0x24,0x22,0x02,0x21,0x92,0x5a,0x2c,0x93, + 0xa2,0x74,0x15,0x2f,0x13,0x9f,0x78,0xd1,0xb1,0xe4,0x99,0xcc,0x95,0x35,0xbe,0x20, + 0x09,0x92,0x18,0x81,0x00,0x02,0x80,0x5a,0x42,0xe9,0xfe,0xf6,0xb7,0x96,0xee,0x46, + 0x63,0x21,0x25,0x27,0x39,0xcf,0xb9,0x6f,0x66,0x2c,0x02,0x8d,0x5e,0xab,0xab,0xab, + 0xab,0xaa,0xab,0xaa,0x0f,0x9f,0xbd,0xfe,0xf4,0xea,0xfc,0x9f,0xa7,0x6f,0x8c,0x59, + 0x36,0x0f,0x8e,0x1a,0x87,0xf8,0x63,0x04,0x6e,0x38,0x1d,0x98,0x5e,0x68,0x62,0x82, + 0xe7,0x8e,0xe1,0x67,0xee,0x65,0xae,0x31,0x9a,0xb9,0x49,0xea,0x65,0x03,0x73,0x91, + 0x4d,0x5a,0x07,0xa6,0x4c,0x0e,0xdd,0xb9,0x37,0x30,0xaf,0x7d,0xef,0x26,0x8e,0x92, + 0xcc,0x34,0x46,0x51,0x98,0x79,0x21,0x64,0xbb,0xf1,0xc7,0xd9,0x6c,0x30,0xf6,0xae, + 0xfd,0x91,0xd7,0xa2,0x17,0xdb,0xf0,0x43,0x3f,0xf3,0xdd,0xa0,0x95,0x8e,0xdc,0xc0, + 0x1b,0x74,0x6c,0x63,0xee,0xde,0xfa,0xf3,0xc5,0x3c,0x4f,0x90,0x15,0xb5,0x26,0x7e, + 0x36,0x18,0x45,0xd7,0x5e,0x82,0x2d,0x65,0x7e,0x16,0x78,0x47,0x1f,0xbc,0x74,0xf6, + 0x2a,0x4a,0x3c,0xe3,0x55,0x14,0x4e,0xfc,0xe9,0xe1,0x36,0x27,0x37,0x0e,0xd3,0xec, + 0x0e,0x7f,0x7b,0x49,0x14,0x65,0xcb,0x46,0xab,0x35,0x9c,0xf6,0x9e,0x4f,0x76,0x27, + 0xfb,0x93,0x83,0xbe,0xd1,0x6a,0x8d,0xdc,0x64,0x0c,0xef,0x93,0x09,0xbe,0xf8,0xe1, + 0x55,0xef,0x79,0xc7,0xed,0xee,0xec,0xb4,0xf1,0x75,0xbe,0xc8,0x7a,0xcf,0xf7,0xf7, + 0x5f,0xec,0x1c,0xb8,0xf8,0x1a,0xf8,0xa1,0xd7,0x7b,0xee,0x75,0xbd,0x17,0x9e,0xd7, + 0x87,0x8a,0xdc,0xd1,0xa8,0xf7,0xbc,0x3b,0x7c,0x31,0xf6,0x5e,0xe2,0x67,0x78,0xe5, + 0x0a,0x44,0x65,0x11,0xd6,0x35,0x7e,0x39,0xde,0xa3,0x37,0x2f,0x49,0x7a,0xcf,0xc7, + 0xfb,0xbb,0x7b,0xbb,0x7b,0xf8,0x7a,0xe3,0x26,0x61,0xef,0xf9,0xe8,0xe0,0xe0,0x45, + 0xc7,0xc5,0xba,0x46,0x33,0x3f,0x86,0xba,0xbd,0x49,0x77,0xf2,0x82,0x7b,0xc2,0xfd, + 0x94,0xfd,0x12,0x8d,0x8f,0x0e,0xc6,0xdd,0xb1,0x87,0x49,0xe9,0xcc,0x1d,0x47,0x37, + 0xbd,0xb6,0xd1,0x89,0x6f,0x8d,0x1d,0xf8,0x97,0x4c,0x87,0x6e,0xb3,0xb3,0x6f,0x77, + 0x0f,0xec,0xdd,0x3d,0xdb,0x69,0x1f,0x58,0xfd,0xc6,0x43,0xe3,0x7f,0xcd,0xbd,0xb1, + 0xef,0x1a,0xcd,0x38,0xf1,0x26,0x5e,0x92,0xb6,0x46,0x51,0x10,0x25,0x00,0xd0,0x99, + 0x37,0xf7,0x7a,0x63,0x37,0xb9,0xb2,0x96,0x05,0xc0,0x74,0xda,0x9d,0xbd,0xce,0x28, + 0x07,0x0c,0x00,0xa3,0xd3,0x1d,0x2a,0xd8,0x78,0xfb,0xde,0x70,0xd2,0x55,0xb0,0x39, + 0x18,0xbe,0x3c,0x70,0x87,0x39,0x6c,0xba,0xee,0xce,0xee,0x6e,0x57,0xc1,0x66,0xd7, + 0x7d,0xb9,0x3b,0x21,0x48,0xf2,0xf0,0xba,0x3b,0xdd,0xf1,0x8e,0xab,0x0d,0xaf,0xb3, + 0x0b,0xf5,0x77,0x0b,0x23,0xdc,0x71,0x77,0xf7,0xf7,0xf6,0x57,0x8f,0xb0,0x6d,0xe3, + 0xff,0x9c,0x5d,0x1c,0xdd,0x43,0xe3,0xc7,0xe5,0x30,0xba,0x6d,0xa5,0xfe,0xef,0x7e, + 0x38,0xed,0x0d,0xa3,0x64,0xec,0x25,0x2d,0x48,0xe9,0xcf,0xdd,0x64,0xea,0x87,0xbd, + 0xf6,0x43,0x03,0xb1,0x76,0xd9,0xba,0xf1,0x86,0x57,0x7e,0xd6,0xca,0xbc,0xdb,0x0c, + 0x73,0x7b,0x2d,0x77,0xfc,0xef,0x45,0x9a,0xf5,0x3a,0xed,0xf6,0x7f,0x3c,0x34,0x86, + 0xd1,0xf8,0x6e,0x39,0x01,0xd4,0xec,0x75,0xf6,0xe2,0xdb,0xed,0x8e,0xb3,0xbb,0x67, + 0xa4,0x77,0x69,0xe6,0xcd,0x5b,0x0b,0xdf,0x6e,0xb9,0x71,0x1c,0x78,0x2d,0x4e,0xb0, + 0xcd,0x33,0x6f,0x1a,0x79,0xc6,0x97,0x77,0xa6,0xfd,0x39,0x1a,0x46,0x59,0x64,0xa7, + 0x6e,0x98,0xb6,0x52,0x2f,0xf1,0x27,0xfd,0xa1,0x3b,0xba,0x9a,0x26,0xd1,0x22,0x1c, + 0xf7,0xae,0xdd,0xa4,0x89,0x20,0xb5,0xfa,0x04,0x72,0xf1,0x0e,0x40,0xb4,0xfa,0xb1, + 0x3b,0x1e,0x43,0x7f,0xa1,0xa3,0x59,0x16,0xcd,0x7b,0x2f,0xdb,0xf1,0x6d,0x1f,0x71, + 0x79,0x12,0x44,0x37,0xad,0xdb,0xde,0xcc,0x1f,0x8f,0xbd,0x10,0x7a,0xde,0xa1,0x3e, + 0x51,0x7f,0x7b,0x9d,0x17,0x90,0x89,0x5e,0x6f,0x3c,0x7f,0x3a,0xcb,0x7a,0xfb,0x7b, + 0x38,0xb8,0xae,0x9e,0x65,0xb7,0x9a,0xa5,0x4f,0x23,0xce,0x12,0xe8,0xe2,0x24,0x4a, + 0xe6,0xbd,0x45,0x1c,0x7b,0xc9,0xc8,0x4d,0xbd,0x7e,0xe0,0x65,0x19,0x00,0x2b,0x8d, + 0xdd,0x11,0xc2,0xce,0x69,0xef,0x79,0xf3,0x42,0x57,0x61,0x82,0x2d,0x09,0xc7,0x6e, + 0x17,0xc0,0x0f,0x13,0x01,0x3d,0xc5,0x46,0x7b,0x13,0x3f,0x49,0x33,0x9c,0xd4,0x60, + 0xbc,0xe4,0x2c,0xad,0x2c,0x8a,0x11,0xdc,0xee,0x52,0xaf,0x03,0xb0,0xc0,0x82,0x02, + 0x40,0x27,0xbc,0x64,0x39,0xf6,0xd3,0x38,0x70,0xef,0x7a,0x93,0xc0,0xbb,0xed,0xbb, + 0x81,0x3f,0x0d,0x5b,0x3e,0x40,0x34,0xed,0x8d,0x80,0x22,0x78,0x49,0x7f,0xea,0xc6, + 0x3d,0x6c,0x41,0xc2,0xa7,0xd7,0xc1,0x56,0x3b,0xfb,0x90,0x52,0x81,0x2b,0x22,0xa7, + 0xd5,0x57,0x13,0x4e,0x70,0x44,0x34,0x49,0xa3,0xc0,0x1f,0x1b,0x9c,0x07,0xf1,0x09, + 0x80,0x1d,0xa5,0x40,0x55,0xa2,0xb0,0x97,0x66,0xfe,0xe8,0xea,0xae,0x4f,0xfd,0xec, + 0xff,0x0e,0x33,0x31,0xf6,0x6e,0xa1,0x3d,0xd9,0x3d,0x23,0xbd,0x9e,0x2e,0xb1,0x6b, + 0xbd,0x30,0x0a,0xbd,0x87,0x86,0x33,0x43,0x02,0xb6,0x9c,0xc3,0xd0,0x88,0x3c,0xe1, + 0xe0,0x38,0xcd,0x18,0xfb,0xd7,0x3a,0xd8,0xa1,0x97,0x55,0xc0,0xdd,0xcc,0x60,0x6c, + 0x04,0x5d,0x0f,0x2a,0xbc,0x49,0xdc,0x58,0x4d,0xb1,0x98,0x60,0x9e,0x1a,0x95,0xe8, + 0x05,0x81,0x1f,0xa7,0x7e,0x0a,0xad,0x0c,0xdd,0xf1,0xd4,0x93,0x70,0x0d,0xbc,0x49, + 0xd6,0x73,0x17,0x59,0xd4,0x57,0x9d,0xeb,0x6b,0x8d,0x77,0x6a,0xe6,0x5c,0xc2,0x0f, + 0xd7,0xcc,0x4b,0x84,0x1e,0x83,0x29,0x71,0xc7,0xfe,0x22,0xed,0xbd,0x7c,0x59,0x0b, + 0x50,0x58,0xa0,0x56,0x65,0x18,0xb2,0x33,0x0e,0x90,0xf6,0x45,0xbc,0xd4,0x4a,0x3d, + 0x9f,0xec,0x78,0x07,0xe3,0x1d,0x51,0xe2,0xf9,0x81,0xbb,0x37,0x6a,0x13,0x84,0x7c, + 0x20,0xf1,0x39,0x1c,0xfb,0xfa,0x28,0x0e,0xb4,0xb9,0xc5,0xbe,0x1d,0xa8,0xbe,0x55, + 0xe6,0x4e,0x90,0x03,0xab,0xd4,0x77,0x5c,0x04,0x8d,0x4a,0xdf,0x89,0x98,0x54,0x3b, + 0xdf,0x1f,0x2d,0x92,0x14,0x52,0xe2,0xc8,0x27,0xfc,0xc2,0x0a,0x5b,0x33,0x06,0x53, + 0xc7,0xd9,0xe9,0x37,0x68,0xc5,0xbf,0x68,0x03,0x5e,0x77,0x69,0xd5,0xef,0x18,0x0b, + 0xbf,0x35,0x8f,0xc2,0x88,0xa6,0xcd,0xfe,0xe0,0x85,0x41,0x64,0xc3,0x6e,0x02,0x1d, + 0x73,0x53,0x5b,0x7d,0x90,0xa3,0xec,0xcd,0x70,0xf2,0x2a,0xf8,0x2e,0xbb,0x5c,0x5d, + 0x07,0x4f,0xa0,0xc5,0x2b,0xc1,0xbd,0xe3,0x76,0x27,0x9d,0x5d,0x09,0x6e,0xaf,0x3d, + 0xdc,0xdd,0x1b,0x01,0xfd,0x9b,0xbb,0x7e,0x08,0x98,0x72,0x2b,0x70,0x74,0x7f,0x17, + 0xd7,0x8f,0x24,0x7e,0x06,0xa1,0x8d,0x5a,0x4d,0xfb,0xb8,0x78,0x1d,0x5c,0x39,0xcb, + 0xb5,0xeb,0x69,0xc5,0x42,0x2a,0xce,0x04,0x61,0xbd,0x5e,0x75,0x9f,0x08,0x31,0x93, + 0x6c,0x2e,0xc6,0x2f,0x92,0x86,0xa8,0x55,0xba,0x4b,0xdd,0xc8,0xdc,0x61,0x5a,0xa4, + 0x08,0xb8,0xfc,0x77,0x57,0xa3,0xa6,0x6c,0x6b,0xb7,0x82,0xd0,0x9d,0x7c,0xcc,0x35, + 0x8d,0x18,0xc3,0x05,0x24,0x09,0x94,0xec,0xc8,0x21,0xb6,0xf5,0x66,0x08,0x51,0x2b, + 0xd8,0x43,0xd8,0xe1,0x87,0x33,0x20,0xec,0x99,0xbe,0xe2,0x76,0x9c,0xbd,0xf2,0x9a, + 0x6b,0xe7,0x6b,0x0e,0x30,0xd4,0xa8,0x76,0x11,0xf1,0xb6,0x88,0x8d,0xc5,0xde,0x39, + 0xd0,0xc1,0x15,0x73,0x52,0xd9,0x3d,0x56,0xc1,0x19,0x6a,0x9c,0x2c,0x4b,0x60,0xd8, + 0x21,0x30,0x4c,0x80,0x6b,0x1b,0x7a,0x81,0x02,0xf7,0x30,0x88,0x46,0x57,0xfa,0x90, + 0xba,0xb5,0x43,0x2a,0xd6,0xb5,0x2b,0xaa,0x02,0xdc,0x0f,0x33,0x9d,0xfc,0x75,0xa8, + 0xf0,0x8a,0x9d,0x83,0xb6,0x05,0xea,0x85,0x1f,0xc6,0x8b,0xec,0x22,0xbb,0x8b,0xbd, + 0x01,0x12,0xbe,0x4b,0x5b,0x4b,0x88,0xdd,0x34,0xbd,0x01,0x80,0x15,0x12,0xc3,0xc5, + 0x7c,0xe8,0x25,0x85,0x24,0x0f,0xd0,0x3d,0xb8,0xb4,0x53,0x2f,0xf0,0x46,0xc0,0xb1, + 0x30,0xce,0xe3,0x16,0xae,0xc0,0x7f,0x80,0x3b,0x46,0x5b,0x0c,0xa6,0x6e,0xf6,0xf6, + 0x4b,0x7d,0x65,0x90,0xae,0xa0,0x28,0x8d,0xef,0xa3,0x4e,0x48,0xcf,0xa2,0x45,0x46, + 0x7c,0x0c,0x6d,0x22,0x86,0xb1,0xfd,0xa3,0x71,0x34,0xc0,0x46,0x7b,0x46,0x0a,0xa0, + 0x48,0x0d,0xff,0xd3,0x99,0xf1,0x7b,0x14,0xcd,0x5b,0x51,0xd8,0x9a,0x44,0xa3,0x45, + 0x6a,0x34,0x53,0x7f,0xec,0xdd,0xb8,0x77,0xc0,0x43,0x8c,0x92,0x28,0x08,0x80,0x0d, + 0x36,0x46,0x6e,0x9c,0xf9,0xd7,0x9e,0x91,0xce,0x3c,0x2f,0xb3,0x8c,0x1f,0xb7,0x19, + 0x7c,0x3d,0x2a,0x21,0xc6,0xcf,0x2f,0xcb,0x15,0xd4,0x46,0x47,0x93,0xb6,0x81,0xff, + 0xc3,0xcd,0x94,0x29,0xce,0xdc,0xbf,0x6d,0x42,0x23,0x29,0xf0,0x53,0x76,0x5e,0xc2, + 0xe8,0xee,0xfd,0x87,0x4d,0x9c,0x42,0xec,0x26,0xb0,0x23,0x5b,0x62,0xce,0x9c,0xb1, + 0x9f,0x64,0x77,0xa2,0x51,0x7e,0xa9,0x6b,0x14,0x19,0x5a,0xc4,0xc1,0xf8,0x6a,0xaa, + 0x10,0x6d,0x9a,0xf8,0xe3,0x3e,0xfe,0x01,0xc6,0x6b,0x0e,0x29,0xb0,0x23,0x42,0x91, + 0xc5,0x3c,0x4c,0x7b,0x89,0x17,0x7b,0x6e,0xd6,0x44,0x0a,0x05,0xcc,0x7c,0x10,0xd8, + 0xb0,0xd1,0x02,0x1d,0x6b,0x76,0x90,0xd5,0xb0,0x3b,0x93,0xc4,0xb2,0x88,0x22,0x74, + 0xc5,0x3e,0xf1,0x07,0xa6,0x42,0xc3,0x89,0x55,0x33,0x8c,0x18,0xcd,0x43,0x32,0xa8, + 0xe3,0x6b,0xc7,0xf5,0x24,0x06,0x66,0x5f,0x2e,0xa4,0x55,0x2b,0x6b,0x57,0xad,0x2c, + 0x20,0x42,0x0d,0x64,0x2f,0xc4,0x96,0xd4,0xdd,0xaf,0x23,0x12,0xf1,0x95,0x41,0xb3, + 0xb0,0x14,0xc8,0x8e,0xd5,0xc9,0x3d,0x6c,0x4f,0xa3,0xf5,0x7d,0x9d,0x75,0x81,0x42, + 0x30,0x89,0xe1,0xf2,0x69,0x1c,0x47,0x0d,0xaf,0x42,0x75,0xb8,0x4f,0xe7,0xd8,0xb4, + 0xc5,0xbe,0x57,0x04,0x00,0x51,0x07,0xa8,0x2e,0x08,0xaf,0x96,0x65,0x92,0x2b,0xa6, + 0x94,0x9e,0xe5,0x64,0xb5,0x57,0xae,0xdc,0x2a,0x9d,0x21,0x3c,0x6f,0x94,0x76,0x79, + 0x1a,0xe3,0xd8,0x1b,0x45,0x89,0x4b,0x6c,0x1f,0xb4,0xe6,0x25,0x88,0x22,0x38,0xa6, + 0x49,0xba,0x98,0x2f,0x57,0x91,0x2a,0xc5,0x69,0x41,0xce,0x24,0xba,0xa9,0xee,0x4e, + 0xc4,0xfe,0xe2,0xa7,0x23,0xa0,0xb4,0x62,0x3f,0x29,0xf2,0x87,0xe9,0xcd,0xa3,0x30, + 0x43,0xb1,0xc3,0x9f,0xdc,0xb5,0x84,0x24,0xdc,0x23,0xb0,0xb7,0x86,0x5e,0x76,0xe3, + 0xc1,0x0c,0x55,0x78,0xe0,0x97,0xc8,0x78,0x53,0xcd,0x34,0xa5,0xc6,0x70,0x35,0x15, + 0xaf,0xdd,0x98,0xb4,0xa2,0xbe,0xa0,0xda,0x28,0x0e,0x03,0xd0,0x93,0xb9,0x1b,0xf4, + 0x1f,0xa5,0xe3,0xb8,0x4f,0x4d,0x83,0xa5,0xe2,0xa2,0x13,0x0f,0x96,0x31,0x90,0xa6, + 0x3e,0x0f,0x7a,0xb7,0x9b,0xe3,0x63,0x97,0xe4,0x0f,0x0d,0x0d,0xa1,0xa0,0x40,0x5e, + 0x55,0x1c,0xb6,0x3c,0x20,0x00,0x99,0xd7,0x8f,0x50,0xee,0xc8,0xee,0x60,0xbe,0x35, + 0x2a,0x2e,0x11,0x1b,0x9f,0x15,0x62,0x57,0xf7,0x4d,0xa8,0x76,0x51,0x53,0xa5,0x1f, + 0x02,0xdb,0x54,0xdc,0xd9,0xd7,0xd2,0x08,0x62,0x84,0x89,0xe0,0x71,0x45,0x4e,0x67, + 0x2f,0xed,0x8b,0x66,0x5a,0xde,0x35,0x4c,0x4e,0xaa,0x8f,0x64,0xd1,0x1b,0x7a,0x20, + 0x43,0x79,0x4b,0x39,0x73,0xa6,0xd9,0xaf,0x76,0x42,0xec,0x77,0x7d,0x42,0x26,0x7c, + 0x10,0xc3,0x3b,0xd0,0xd6,0x6d,0x89,0x20,0x91,0x90,0x5f,0xec,0xda,0x1e,0x00,0xa0, + 0xd4,0x33,0x1d,0x9e,0x3d,0x60,0x19,0x47,0x57,0xde,0x78,0x6b,0x51,0xe5,0x1a,0x98, + 0xd5,0xac,0xcb,0x2b,0xfb,0x4f,0x5d,0xeb,0x76,0x70,0x59,0x0a,0xee,0x63,0x98,0x85, + 0x0a,0xad,0xfc,0x90,0xf8,0xe4,0xa7,0xa2,0x6f,0x89,0xf4,0x29,0xf6,0x6a,0xd5,0x2a, + 0xae,0x93,0x51,0x1b,0x3a,0xb2,0x77,0xf6,0xab,0x12,0x4b,0x85,0x30,0xf6,0x6b,0x87, + 0x5d,0x26,0x0e,0xb4,0xb9,0xeb,0x83,0x04,0xbe,0x7a,0xb4,0x7c,0x82,0xe8,0x53,0x2e, + 0xd7,0x03,0xe0,0xb8,0xc3,0xc0,0x1b,0x2f,0x25,0xda,0x3a,0x7b,0xb2,0x47,0x63,0x6f, + 0xe2,0x2e,0x82,0xac,0xd8,0xcc,0x7c,0x59,0xd9,0x03,0xe4,0x18,0xf7,0x05,0x87,0x82, + 0xac,0x38,0x34,0x5c,0x86,0x7b,0x65,0x55,0xaf,0x13,0xf0,0xba,0x05,0x21,0xaa,0x80, + 0xd7,0x3a,0x4d,0x43,0x88,0x02,0xd1,0x07,0xf9,0xd7,0x0d,0x5a,0x34,0xa7,0xbd,0x8e, + 0xea,0x80,0x13,0x15,0x08,0xf3,0x2a,0x0e,0x21,0xba,0xb2,0x8c,0x4e,0x89,0x41,0x28, + 0xc0,0x2c,0x42,0x90,0x71,0x8d,0x5e,0x92,0x3c,0xa5,0x4a,0xc8,0xf6,0x48,0x9d,0x98, + 0x03,0x09,0x98,0x7b,0xed,0x0d,0xdd,0x24,0x5f,0xf2,0x13,0xff,0xd6,0x1b,0xf3,0x12, + 0x6b,0xf7,0x13,0x02,0x0b,0x2c,0x7b,0xe6,0x53,0x73,0xd9,0xbe,0xdb,0x7e,0x44,0x6f, + 0x80,0x6b,0xb5,0x5e,0xd6,0x51,0x18,0x89,0x33,0x45,0x28,0x09,0x2c,0x59,0x30,0x6a, + 0xd2,0xeb,0x96,0xe1,0x85,0xd7,0xcd,0xd4,0x9d,0x78,0x2d,0xe8,0xb3,0xdb,0x22,0xba, + 0x23,0xd8,0x64,0xe0,0x5a,0xe4,0x84,0xd2,0xb6,0xb6,0x6a,0xd3,0xec,0x12,0xfc,0xc5, + 0xc0,0x9c,0x74,0x56,0xda,0x6e,0xf2,0x6f,0x40,0xf0,0xcb,0x24,0x5e,0xfb,0xe8,0xa4, + 0xb1,0xd8,0x88,0x30,0x31,0x88,0xb2,0xe5,0xf7,0x88,0x70,0x65,0x5d,0x4b,0x59,0x88, + 0x62,0x2c,0xc5,0x6a,0xa1,0xa1,0xec,0x49,0xdc,0xc0,0xc1,0x63,0xb5,0xd4,0x8f,0x27, + 0x06,0x0e,0x50,0x4f,0x6f,0x57,0xb6,0xb2,0x17,0xed,0x27,0xa1,0xfd,0x13,0xf5,0x1a, + 0xdf,0xa1,0x16,0xdb,0xf5,0xe6,0xa2,0x83,0x7f,0xed,0x4a,0xa1,0x1a,0x27,0x20,0xcf, + 0xd8,0xfc,0x08,0xd0,0x1d,0xfd,0xa5,0xab,0x86,0x6a,0xbd,0x71,0xfd,0xec,0x29,0xb5, + 0x12,0x97,0xbb,0xbe,0x5a,0xc9,0x08,0x67,0x7e,0xe0,0xa5,0x7f,0x8a,0xc5,0x47,0x25, + 0x85,0xc6,0xe2,0x0b,0x0c,0xc1,0x7a,0x57,0x11,0xe7,0x75,0x88,0x4b,0x2b,0xb4,0xab, + 0xaa,0xa8,0x65,0x73,0x56,0xf2,0x4c,0x75,0xd4,0xf5,0x4f,0x23,0x0b,0x75,0xa3,0x80, + 0xe7,0x07,0x75,0xca,0x5b,0xce,0x97,0x2e,0xcb,0x1c,0x6b,0x45,0xc5,0xb7,0x82,0x31, + 0x1b,0xb9,0xe1,0xb5,0x9b,0x2e,0xab,0xfc,0xd3,0x1e,0x12,0xfc,0xc2,0x90,0xa1,0xb1, + 0xd0,0xcb,0x9e,0xce,0xcf,0x6b,0x75,0x3e,0x49,0x61,0x42,0x52,0x74,0x61,0xcb,0x2f, + 0x52,0x50,0x6d,0x4f,0x5f,0xab,0xac,0xad,0xe3,0xe5,0x79,0xc3,0x42,0x62,0xcf,0xa3, + 0xe8,0x05,0xae,0xd2,0x3d,0x17,0xeb,0x6c,0x73,0x06,0x20,0x33,0xa9,0x64,0xcf,0xcb, + 0x3a,0x8d,0x3f,0x23,0x11,0x61,0xd5,0x29,0x60,0x53,0xb0,0xac,0x61,0x92,0x53,0x98, + 0x0d,0x60,0xb2,0xbd,0xe0,0xbb,0x29,0xf0,0xca,0x3d,0x6a,0xad,0x3a,0xad,0xd7,0x12, + 0x2a,0x79,0x56,0xf1,0xa8,0xe6,0x71,0x4b,0x68,0xa1,0x46,0xfb,0x0f,0x49,0xab,0x7f, + 0xfd,0x01,0x42,0x65,0x99,0x1a,0xbb,0xa5,0x0e,0x3f,0xc7,0x47,0x00,0x0f,0xec,0x30, + 0xa8,0xc8,0x14,0x38,0xbc,0xbb,0x77,0x3d,0xcb,0x67,0xab,0xa0,0xc8,0x6c,0x23,0xf1, + 0x37,0x58,0x9b,0x99,0x66,0x5e,0x5c,0xa3,0x47,0xdc,0xaf,0x6e,0x43,0x5a,0x7e,0x24, + 0x11,0x8c,0x1e,0xb2,0xb1,0xca,0x66,0xd2,0xad,0x9b,0x18,0x9a,0xc2,0xbc,0x92,0x5a, + 0xa5,0x9d,0x64,0xbf,0x23,0x40,0xd2,0x32,0xc3,0x82,0xfc,0xc6,0xfe,0xae,0x14,0x0e, + 0x14,0x8f,0x4f,0x70,0xa5,0x27,0x24,0x9f,0xbf,0x36,0x5b,0xf0,0xc5,0x52,0x5c,0xcc, + 0x6e,0xad,0x40,0x73,0x55,0xa4,0xcd,0xa4,0xac,0x7a,0xb2,0x8e,0x72,0x0d,0x9b,0x9d, + 0x8b,0x65,0xba,0xf8,0xd1,0xad,0x17,0x8c,0xfa,0xb9,0xe2,0xf9,0x65,0xfb,0xfa,0x46, + 0x0e,0x9b,0xd9,0x19,0x59,0x13,0x72,0x26,0x38,0x8f,0x30,0x3f,0x65,0x78,0x48,0x81, + 0x4d,0x0e,0x75,0xaf,0x5d,0x7f,0x80,0xf6,0x18,0x47,0xb5,0x42,0x26,0xd1,0xa8,0x87, + 0x48,0x51,0x0c,0x04,0xe3,0xa0,0xe8,0x56,0x3d,0xfb,0x15,0xfb,0xa1,0x20,0xad,0x5d, + 0x5d,0xc6,0xed,0xe6,0x42,0xce,0xce,0xba,0x15,0x0e,0x73,0xdd,0x5a,0x75,0x32,0xa0, + 0x09,0x79,0x6e,0xe8,0xcf,0x99,0xea,0x27,0x46,0x27,0x35,0xb0,0x0a,0x60,0xeb,0xfc, + 0x70,0x82,0x67,0xe1,0x5e,0xbf,0x4e,0x38,0x78,0x68,0xfc,0xaf,0x2b,0xef,0x6e,0x92, + 0xb8,0x73,0x2f,0x35,0x92,0x65,0x16,0x2d,0x73,0x2c,0x4a,0xa2,0x0c,0x50,0xa8,0xb9, + 0xb3,0xdf,0x1e,0x7b,0x53,0xeb,0x01,0xc6,0x01,0xcc,0x00,0x9d,0x7a,0x2e,0xcb,0x1c, + 0x42,0x41,0x73,0x50,0x54,0xe1,0x10,0x13,0x97,0x2b,0xa6,0x70,0xf7,0x82,0x9a,0xae, + 0xae,0x2b,0x9c,0x5b,0xbf,0xb0,0xf0,0x9e,0xa0,0xd9,0xe8,0x96,0x04,0xa1,0xf6,0x53, + 0x76,0x05,0x6a,0x7b,0x3d,0xd5,0xbf,0xba,0xae,0xdd,0xf6,0x6b,0xe8,0x34,0xe4,0x14, + 0x3b,0xb3,0xb6,0x30,0x34,0x4c,0x21,0x69,0xe2,0xe9,0x47,0x75,0xf0,0xdd,0x5b,0xea, + 0xf8,0xf9,0xcc,0x9f,0xa3,0x81,0x82,0x1b,0xd2,0x6e,0x15,0x65,0x5e,0x55,0x12,0xac, + 0xd0,0xc8,0x27,0x31,0x3d,0x07,0x25,0xa5,0x10,0xb1,0x2e,0xb5,0xea,0x7e,0xc0,0xf4, + 0xf9,0xb2,0xd5,0xca,0x26,0xbd,0xe7,0xe3,0x17,0x5e,0xdb,0x73,0xfb,0xf0,0x32,0xf6, + 0xe7,0xbd,0xe7,0xfb,0xe3,0x17,0xa3,0x83,0x09,0xbe,0xd2,0xf1,0xfc,0xde,0x81,0xbb, + 0x3f,0xa1,0xd7,0x29,0x1a,0x23,0xec,0xbd,0x18,0x75,0xf7,0x3d,0x7c,0x25,0x5b,0x85, + 0xc9,0xe4,0xc5,0xf0,0x45,0x17,0x5f,0xd9,0x56,0xc1,0xdb,0x19,0xee,0xec,0x76,0x0a, + 0xc7,0x6f,0xcf,0xdb,0xc3,0xb6,0xd7,0xe9,0x16,0x46,0x94,0x4d,0x6a,0xce,0x91,0x9e, + 0x77,0xdb,0xdd,0x83,0x9d,0x6e,0xdd,0x19,0x12,0x93,0xad,0x89,0x3b,0xf7,0x83,0xbb, + 0x5e,0xe1,0xfc,0xed,0xec,0xed,0x07,0x78,0x6e,0x7d,0xf6,0xa6,0x8b,0xc0,0x4d,0xca, + 0xc7,0x71,0xe6,0x7b,0x7f,0xe8,0x31,0xc7,0x64,0x60,0x3e,0x33,0x3f,0xa0,0x2b,0x92, + 0x42,0xa4,0xb7,0x85,0x93,0xbf,0xbd,0x22,0xda,0xe2,0x9f,0xd6,0xd8,0x4f,0xbc,0x11, + 0xad,0x43,0xe6,0x5e,0x2b,0x18,0xd0,0x10,0xc5,0x5f,0x1c,0xc0,0xbe,0xa4,0x6b,0x6d, + 0x77,0x89,0x7b,0xe5,0xa3,0xbd,0xa6,0x20,0x92,0x50,0x09,0x5a,0xc3,0x58,0x4b,0x9e, + 0x8b,0xe2,0xf1,0xc3,0x03,0xdb,0x1b,0xe0,0xf1,0x4f,0x6b,0x14,0xf8,0xcb,0x92,0x35, + 0x40,0x5b,0xcc,0x60,0x6b,0x36,0x7e,0xb2,0xc4,0x95,0x1f,0xb7,0x96,0xb5,0x0c,0xc5, + 0xe9,0xea,0x74,0x3a,0xfb,0x9d,0xf1,0xca,0x35,0x27,0x67,0x29,0xef,0xaf,0x73,0xd0, + 0x2d,0x6d,0xea,0x88,0x48,0x56,0xde,0xc5,0xa2,0x04,0x2a,0x13,0xf5,0x23,0x37,0xd6, + 0xf4,0xd6,0x6b,0x7e,0xf5,0xa6,0xdd,0x9d,0x9d,0x9d,0x8a,0x1a,0x0c,0x07,0x50,0x69, + 0xbd,0xc8,0x68,0x2a,0x06,0x43,0x08,0x85,0x55,0x75,0x61,0xa1,0x53,0x35,0x87,0xb6, + 0x39,0xc6,0x0a,0x8a,0xfd,0x7c,0x67,0xbc,0xeb,0xee,0xb9,0xb2,0x6c,0xb4,0xc8,0x24, + 0xbf,0xa0,0xac,0x34,0xee,0x98,0x25,0xa9,0x58,0x6d,0x14,0xf8,0x9d,0xbe,0x34,0x3b, + 0x51,0xd9,0xf8,0x84,0x07,0xbf,0x67,0xd1,0x62,0x34,0xd3,0x5a,0x38,0x42,0xa3,0x02, + 0x9d,0xed,0x8c,0x13,0xaf,0x45,0x66,0x03,0x78,0x30,0xd6,0x1a,0x26,0x9e,0x7b,0xd5, + 0xa3,0xbf,0x2d,0x4c,0xd0,0x4a,0x1a,0xce,0x68,0x3e,0x16,0x03,0x42,0x55,0x62,0xf9, + 0x53,0x45,0x6d,0x79,0x64,0x98,0xc5,0x29,0x9d,0x0a,0xc1,0x4e,0x95,0x02,0xc9,0x4d, + 0x56,0xf8,0xc2,0xf3,0x5e,0x1c,0xbc,0x28,0x7c,0x45,0xf5,0x4e,0xa1,0xbc,0x10,0x38, + 0xf3,0x1c,0xe9,0x5d,0xba,0x5c,0x89,0x34,0x94,0x03,0x30,0x77,0x29,0x56,0x90,0xb3, + 0xb7,0xc7,0x52,0x13,0x7e,0x74,0xc3,0xd1,0x2c,0x4a,0x6a,0xd4,0xcd,0x22,0xb3,0x76, + 0xce,0x91,0xeb,0x79,0x64,0xe1,0x74,0x31,0xad,0x51,0x0b,0xaf,0x52,0x13,0x69,0xcc, + 0xa6,0x60,0xb0,0x17,0x53,0x48,0xb3,0x77,0x77,0xaf,0x67,0x56,0x65,0xa6,0x8b,0x64, + 0xcf,0xeb,0xec,0x74,0xdc,0x7a,0x35,0x52,0x4e,0xea,0xb4,0xf3,0xb7,0x16,0xb1,0xbf, + 0xc8,0x76,0x14,0x8c,0x98,0xf6,0xf6,0x88,0x9d,0x9c,0xd6,0x1f,0x35,0x14,0x56,0xfc, + 0x10,0x58,0x6e,0x24,0x63,0x0a,0xc1,0xf6,0xe4,0x0a,0xaf,0x20,0x7c,0x3a,0xc5,0x2d, + 0xae,0x68,0x2c,0x22,0x36,0xbb,0xbd,0xb6,0x14,0x6f,0x9f,0x8f,0x26,0xde,0x0e,0x10, + 0xff,0x12,0xca,0x89,0xe2,0xc6,0xa2,0x38,0x7f,0xc4,0xbd,0x94,0xd5,0x31,0x75,0x72, + 0x2b,0x97,0xf7,0x75,0xb3,0x99,0xfe,0xba,0x53,0x07,0xe7,0x60,0xb7,0x8e,0xc0,0xf4, + 0x1b,0x7f,0x46,0x50,0x4b,0xa7,0x25,0xd6,0xfc,0x79,0xc7,0xeb,0x0e,0x77,0x5c,0x7d, + 0x4e,0x88,0xf7,0x24,0xab,0x32,0x3c,0x1b,0xd5,0x86,0x29,0xcb,0x03,0x0c,0x0b,0x6b, + 0x8a,0x13,0x7d,0x99,0x78,0x30,0x7c,0x39,0x74,0xd5,0x32,0xc4,0x99,0xf9,0x13,0xd4, + 0xfa,0xa0,0x96,0x5a,0x3f,0x01,0xcb,0x64,0xfb,0x71,0xaa,0x4d,0x78,0xcd,0xda,0x7e, + 0x4e,0xb9,0xfc,0xb0,0xe6,0xd4,0x4a,0x3b,0x79,0xcb,0x45,0xfd,0x02,0x15,0x5e,0x25, + 0xfa,0xd3,0xb9,0x45,0xa3,0x40,0x8c,0xf5,0xd3,0xef,0x3e,0xc8,0xb1,0x5e,0xd6,0x5a, + 0xd3,0x9b,0xc2,0x09,0x36,0x35,0xab,0xa6,0x87,0x71,0x49,0x65,0xec,0x01,0x60,0x47, + 0xde,0x2c,0x0a,0xc6,0x8a,0x76,0x3f,0xdf,0x7d,0xb9,0xf7,0x62,0xff,0x85,0x96,0x49, + 0x29,0xe9,0x57,0x92,0x9e,0x69,0xb4,0xac,0x02,0xbf,0x2b,0x81,0xff,0xc4,0x4d,0x69, + 0xbf,0x34,0x4f,0x55,0x98,0x57,0x37,0xa9,0xfa,0x4d,0x69,0x1a,0xd5,0xed,0x46,0x08, + 0xa3,0xb5,0xfb,0x11,0x14,0xab,0x9e,0x47,0xec,0x56,0xce,0x23,0x38,0xf3,0x68,0x32, + 0x5f,0x3e,0x36,0x30,0x79,0x56,0x55,0x60,0xbd,0x89,0xeb,0xab,0x63,0x42,0x8b,0x1c, + 0x45,0xb7,0x73,0xd0,0x99,0x54,0x11,0x59,0x28,0x29,0xf6,0xc5,0xe9,0xa5,0xec,0x8a, + 0xe1,0xcc,0x8a,0x83,0xe5,0x46,0x8a,0x6c,0xec,0x9e,0xe2,0x62,0xb9,0x48,0x10,0xca, + 0x29,0x77,0x5f,0x0e,0x5f,0x8c,0xf6,0xfb,0x4f,0xdf,0x26,0x95,0x1a,0x8a,0xc7,0xc8, + 0x8d,0x86,0xf6,0xce,0x68,0x26,0xb4,0x7a,0xb8,0x83,0xa0,0xc4,0x88,0xca,0xfd,0x56, + 0xc7,0xf8,0xd1,0xd0,0xb3,0x58,0xa5,0x6e,0xac,0xd2,0xd5,0xad,0xc4,0x37,0x2a,0x77, + 0xb3,0x66,0xc4,0xd5,0x03,0x73,0xe7,0x65,0xbe,0x19,0x52,0xf1,0x61,0x16,0xd6,0x28, + 0x38,0x24,0x1d,0xa1,0xb1,0xf7,0x08,0x00,0x45,0x01,0x4e,0xaf,0x43,0xb0,0x63,0xdf, + 0x81,0xdf,0x85,0x29,0x1e,0x76,0xf7,0x76,0xda,0x15,0x1e,0xbf,0x51,0xcb,0x85,0xed, + 0x0a,0x2d,0xcf,0x0a,0x8c,0xcf,0x7b,0xe3,0xc0,0x5a,0x2c,0xb4,0x32,0xde,0x19,0x76, + 0x0f,0x4a,0x78,0xdf,0x9d,0xec,0x0f,0x77,0xf7,0x94,0x8d,0xe1,0xc4,0x6b,0xbb,0x7b, + 0xd5,0xaa,0xea,0x56,0x43,0xf5,0x78,0xce,0x01,0x5e,0xbb,0x95,0xf9,0xf1,0xa3,0x96, + 0x9b,0x1a,0x1c,0x5f,0x96,0x45,0x5c,0x25,0xc4,0x16,0x61,0x9f,0xd7,0x6e,0x5c,0x0d, + 0xc7,0xcb,0xea,0xd9,0x67,0x9d,0x78,0xc3,0xf2,0x4c,0x2e,0xb4,0xac,0x90,0x02,0x1b, + 0xc5,0xd9,0xd9,0xd5,0xc4,0xc0,0x36,0xeb,0xd2,0x0e,0xb7,0x85,0x85,0xfb,0xe1,0xb6, + 0xb0,0xc8,0x47,0xf9,0x42,0xd8,0xe7,0x7b,0x09,0x5a,0xc0,0x5f,0x4f,0x0d,0x36,0xb8, + 0x37,0xbb,0xfb,0xa6,0xc1,0x5c,0x0f,0x3f,0xa3,0x45,0xfd,0x4f,0xd1,0xed,0xc0,0x24, + 0x13,0xa1,0x5d,0xf8,0xbf,0x69,0xa0,0xb2,0x7e,0x60,0x22,0x6a,0x9b,0x46,0x9a,0x25, + 0xd1,0x15,0x9a,0xf0,0x2b,0x2d,0x86,0x4c,0x6b,0xc9,0x1a,0x55,0x02,0x92,0xfe,0x91, + 0x1b,0x0f,0x4c,0x1a,0x05,0x5a,0xe6,0x8f,0xfc,0x64,0x14,0x78,0xc6,0x08,0xea,0xef, + 0x40,0xbe,0xd1,0x1d,0xff,0x26,0x50,0xca,0xe9,0xca,0x86,0xaa,0x55,0x8b,0xc6,0xb7, + 0xa1,0x86,0xd8,0xcd,0x66,0xc6,0x78,0x60,0x7e,0x78,0xe1,0xbc,0x30,0xe0,0x9f,0xbb, + 0x6f,0xec,0x1b,0x6d,0xf1,0xbf,0x03,0x67,0xff,0x43,0x67,0xdf,0xd9,0x29,0x7c,0xe8, + 0x88,0x0f,0xbb,0xce,0x4b,0x03,0xfe,0xb9,0x1d,0xb4,0x5b,0x56,0x45,0x3a,0xbb,0x4e, + 0xf7,0x43,0xe7,0xa5,0xd3,0x29,0x7d,0xeb,0x88,0x6f,0xd4,0xe8,0x36,0x00,0x0c,0x7e, + 0x80,0x25,0x37,0x46,0x20,0x6b,0xa6,0x03,0x93,0x0c,0x7f,0xc9,0xe7,0xa1,0x63,0xf8, + 0xd0,0x9d,0x59,0x0b,0x3d,0x1b,0x4c,0xe5,0x74,0x00,0xa0,0xef,0x88,0x12,0xfc,0x39, + 0x5d,0x0c,0xcd,0x23,0x60,0xb9,0x43,0x14,0x2b,0xc3,0xe9,0xc6,0x8c,0x58,0x96,0xfe, + 0xe1,0x36,0x64,0xc1,0x16,0xf8,0x87,0xec,0x32,0x44,0x13,0x64,0xf9,0x69,0xa0,0x42, + 0xc1,0x14,0x75,0x50,0x8a,0x79,0x04,0xdd,0x81,0x6c,0x38,0xad,0x84,0xee,0xaa,0x4b, + 0x68,0x83,0x5a,0xc8,0x3f,0x22,0xd9,0x18,0x5e,0xc9,0x58,0xce,0xe4,0xec,0xa6,0x11, + 0x85,0x80,0x9c,0xa3,0x2b,0xf4,0xda,0x80,0x95,0xc3,0x02,0xb4,0xd7,0xb4,0xcc,0x06, + 0x39,0x48,0x0c,0x4c,0x91,0x62,0xb4,0x5a,0x46,0x7a,0x05,0x28,0x4c,0xc6,0xa7,0x86, + 0x1b,0x8e,0x8d,0xd8,0x4d,0x33,0xcf,0x70,0x0d,0x20,0xb2,0x78,0x22,0x34,0x46,0xf7, + 0x8d,0x89,0x3f,0x35,0x1b,0x6e,0xe2,0xbb,0x2d,0x32,0x2a,0x1c,0x98,0x9f,0x62,0x2f, + 0x34,0x64,0xd3,0x47,0x1b,0xd3,0xac,0xff,0xed,0x70,0x9b,0xdb,0x96,0x28,0x49,0x48, + 0x88,0xa6,0xaa,0x38,0x64,0x96,0xb3,0xa9,0xcb,0xd7,0xad,0x20,0x82,0xb5,0x66,0xe6, + 0x43,0x82,0xc1,0x14,0x01,0x8f,0xca,0x70,0x82,0x7b,0xf7,0xe8,0x64,0x0c,0x6c,0x8b, + 0x41,0x25,0xa0,0xda,0x2e,0x22,0x87,0x41,0x98,0x3f,0x30,0x2b,0x8a,0xa9,0x55,0xab, + 0x5a,0xca,0xbc,0xb0,0x86,0xcd,0xa3,0x37,0x08,0x0f,0x23,0x9b,0xf9,0xa9,0x11,0x46, + 0x63,0x6f,0x33,0x35,0x5c,0x6a,0x42,0x9a,0x23,0x1a,0xcd,0x6c,0xe6,0x19,0x29,0x4c, + 0x34,0xc0,0xd0,0x33,0x16,0x29,0x40,0x00,0x44,0x29,0x23,0xf1,0xe6,0x11,0xc0,0xe5, + 0xd5,0xfb,0x77,0x5c,0xc0,0x72,0x0e,0xb7,0x63,0xe8,0x0e,0xaa,0xe0,0x68,0x5c,0xd4, + 0xc7,0x16,0xbe,0x96,0x46,0x33,0x81,0xd9,0x24,0xb0,0x61,0x3d,0x32,0x5f,0x7c,0x03, + 0x23,0x3c,0x15,0x6d,0x1e,0x6e,0xd3,0x77,0x28,0x46,0xd6,0x1b,0x62,0x2a,0x65,0x8f, + 0x4c,0xad,0x7a,0x2c,0x46,0x16,0xbe,0xa3,0x68,0x1e,0x07,0x5e,0x06,0xd9,0x80,0xe6, + 0xe1,0xb9,0x5d,0x2b,0xcf,0x8e,0xdf,0x89,0xcb,0x3a,0x92,0x28,0x57,0x44,0x22,0xd8, + 0x5d,0x24,0xba,0x00,0xc6,0xce,0xfd,0x4c,0x6f,0x01,0x3f,0x1e,0x9d,0x01,0x1f,0x6b, + 0x20,0xc4,0xd5,0x94,0x6a,0xe3,0x91,0xda,0x45,0xbd,0x14,0xa4,0x99,0xaa,0xb5,0x6d, + 0x04,0x42,0x8e,0xef,0xdb,0x62,0xf6,0x2b,0x78,0x70,0xe3,0xff,0x8e,0x13,0xbd,0x06, + 0x11,0x50,0x97,0xc6,0xcd,0x8c,0x42,0xb7,0x45,0x6f,0x47,0xef,0x42,0x03,0x67,0xe8, + 0x1f,0xfe,0x5b,0xdf,0x48,0x89,0xdf,0x86,0xc9,0x8b,0xe2,0x45,0x7c,0x6c,0xbc,0xcb, + 0xa0,0x60,0x94,0x7a,0xa9,0xe1,0x4f,0x8c,0xbb,0x68,0x91,0x18,0xf1,0x0c,0x27,0x31, + 0x0d,0x3c,0x2f,0x4e,0x9d,0xc6,0xa1,0x6b,0xcc,0x12,0x6f,0x32,0x30,0x9f,0x6b,0x0b, + 0x04,0xca,0x7e,0x5a,0x64,0x4d,0xab,0x0f,0x8c,0xeb,0x22,0x09,0x8d,0x89,0x1b,0xa4, + 0x44,0x03,0x09,0xc9,0xaa,0xb2,0x86,0x79,0x44,0xc8,0x0f,0xad,0x52,0x0b,0xc3,0x24, + 0xba,0x49,0xbd,0xe4,0x70,0xdb,0x3d,0x32,0xb2,0xc8,0xb8,0x82,0x96,0x44,0xd3,0x49, + 0x34,0x4d,0xbc,0x34,0x75,0x24,0x1c,0xb4,0x71,0xd1,0x61,0x02,0x00,0xcc,0xa7,0xb1, + 0xa5,0x59,0xdc,0x51,0x50,0x80,0xc5,0x0b,0x80,0xf4,0xb5,0x6f,0xdd,0x72,0xc2,0x4e, + 0x39,0x61,0x57,0x24,0x68,0x0d,0xe1,0x97,0x9b,0xdf,0x3b,0xab,0x17,0xd6,0x19,0x74, + 0x01,0x08,0xe0,0xc6,0x1c,0x44,0xad,0x28,0xeb,0x13,0x3c,0xff,0x92,0x15,0x26,0x9a, + 0x86,0xd9,0x9d,0xf8,0x40,0x9d,0xe2,0x3b,0xf3,0xe8,0x15,0x93,0xc4,0x7c,0xd5,0x21, + 0xa4,0x08,0x48,0x34,0x8b,0xa1,0x97,0x01,0xe2,0x5e,0x01,0x83,0x62,0xf8,0x30,0x83, + 0x40,0x1d,0x81,0x97,0x1b,0xcd,0x68,0x9a,0x3f,0xfc,0xd7,0xf9,0x39,0x10,0xa7,0x04, + 0x38,0xe6,0x54,0xac,0xb8,0x32,0x7e,0x1c,0x1d,0x0e,0x8f,0x4e,0x89,0x58,0x01,0xcd, + 0xd5,0xd7,0xea,0xd8,0x8b,0x83,0xe8,0x6e,0x0e,0x8b,0xe3,0x18,0x30,0xf9,0xc8,0x38, + 0xc7,0xe6,0x19,0xe9,0x8c,0xcc,0x4b,0xb3,0x34,0x47,0x24,0x56,0x97,0x40,0xc1,0x61, + 0x14,0x65,0x8e,0xf1,0x6e,0x42,0x9f,0xf2,0x0a,0x54,0x1f,0x69,0x00,0x99,0xe1,0x5e, + 0xbb,0x7e,0x80,0xfc,0x07,0xec,0xab,0x89,0x67,0x23,0x99,0x30,0xbe,0x9c,0xfd,0x84, + 0x3d,0xf5,0xdd,0xc0,0x70,0x33,0x03,0x3a,0xc5,0x84,0xd3,0x99,0xc3,0x76,0x01,0xbc, + 0xa3,0xe7,0xf8,0x11,0x75,0x03,0x89,0x6c,0xb2,0x08,0x31,0x07,0x8a,0xa3,0x08,0x27, + 0x27,0x4d,0x81,0x35,0x73,0x1c,0x47,0x65,0xd0,0x3f,0xc2,0xa2,0x97,0xdf,0x1c,0x18, + 0x84,0xc7,0x20,0x44,0x73,0x10,0x1a,0xc1,0x1c,0x72,0x65,0x33,0xd4,0xb0,0xe4,0x5b, + 0x0f,0x37,0xe2,0x65,0x89,0x0f,0x79,0xdc,0x09,0x92,0xbc,0x7c,0x30,0x75,0x18,0xa9, + 0x88,0xd4,0xd1,0x47,0x31,0x52,0xdc,0xeb,0x8c,0xe6,0xd9,0xd9,0xbb,0xd7,0x56,0x4e, + 0x9f,0xb4,0x12,0x80,0xf7,0x6a,0x95,0x08,0xee,0xd4,0x2c,0x51,0x30,0xa6,0x12,0x63, + 0x37,0x73,0x5b,0x57,0xe8,0xfd,0x27,0x06,0xaa,0x50,0x84,0x5f,0x88,0x9c,0xb9,0xb1, + 0x9f,0x81,0xf8,0xfc,0x3b,0x94,0x8a,0x26,0x13,0x49,0xe3,0x12,0x54,0xd0,0x8a,0x14, + 0x89,0x93,0x24,0xcc,0x9a,0x75,0x44,0x0d,0xa0,0x3f,0xaa,0xee,0x83,0xf3,0x08,0x69, + 0x78,0x74,0x13,0x02,0xe5,0xc2,0x63,0x2c,0x07,0x36,0x36,0xfc,0x7d,0xcd,0x4c,0x22, + 0xec,0x89,0x39,0x2d,0x88,0x60,0x65,0x9f,0x01,0x02,0x36,0x37,0x45,0xef,0x36,0x2d, + 0x20,0x86,0x90,0xa0,0x6f,0x6e,0x08,0xbb,0xb5,0x10,0x7c,0x2a,0x59,0x2f,0x00,0xa6, + 0x86,0xb2,0xe3,0xb0,0x4b,0x1c,0x09,0x30,0xd6,0xe6,0xd1,0x7b,0x0f,0xa6,0xde,0x18, + 0x06,0x6e,0x78,0x45,0xe8,0x0e,0x0b,0x06,0x3b,0x2e,0x71,0xd4,0x31,0xba,0xce,0xee, + 0x06,0xac,0xb5,0x30,0x8d,0xfb,0x7f,0xfb,0xf9,0x77,0x99,0x9e,0xc2,0x38,0x83,0x3b, + 0xe7,0x09,0x03,0xf8,0x88,0xf8,0x85,0xf3,0x5f,0x3f,0x82,0xe2,0xb4,0x12,0x4f,0x84, + 0x5e,0x9b,0x81,0x17,0x4e,0x91,0x37,0xdc,0xe9,0xd4,0xf5,0x5a,0xce,0x39,0x66,0x6f, + 0xf1,0x38,0xce,0x66,0x30,0x2b,0xd0,0x29,0x5a,0x6b,0xb8,0x4a,0x08,0x6b,0x81,0xb0, + 0xf2,0xa2,0xcf,0xdc,0x6c,0x91,0x62,0x7a,0xea,0x4e,0xbd,0xb4,0xd4,0xef,0xd5,0x1b, + 0x9b,0x9a,0xcc,0x9b,0xdf,0xff,0x16,0x35,0xbb,0x30,0x81,0x1f,0xa1,0xbf,0x3d,0xe3, + 0x33,0xf0,0xd2,0x51,0x79,0x1e,0x75,0x62,0xd9,0x7d,0x22,0x4b,0x42,0x94,0xb3,0x9b, + 0x53,0x4e,0x51,0xf1,0x5f,0xc1,0x9c,0x20,0xe0,0x79,0x9a,0x80,0xae,0xb8,0x89,0x41, + 0x64,0x30,0x02,0xf0,0x24,0x06,0xed,0x9a,0x80,0xb6,0x68,0xcf,0x67,0xa0,0x5c,0x10, + 0x21,0xcf,0x86,0x6b,0x3d,0x75,0x8c,0x53,0x18,0x30,0x41,0x11,0x70,0x1b,0xc9,0x06, + 0xd1,0xd6,0x20,0xc2,0xac,0x04,0x57,0x58,0x01,0x35,0xd4,0x33,0x9f,0xf0,0xcf,0xde, + 0x14,0xb7,0x65,0x2e,0x9d,0x4f,0x3a,0x9b,0xff,0xcb,0x99,0x4b,0x62,0x02,0xee,0xcc, + 0x0d,0xa7,0xb8,0x31,0xfe,0x4e,0xe3,0x3e,0xf3,0x82,0x26,0x92,0x75,0x8b,0x58,0x56, + 0xca,0xbf,0x66,0xf2,0x93,0xb8,0x05,0xec,0x8a,0xe4,0x14,0xd6,0xe2,0xe0,0xf9,0xaf, + 0xb0,0xab,0xdf,0xc0,0xc0,0x9b,0xe3,0x9f,0xe6,0x56,0x3d,0x22,0xb2,0xb7,0x48,0x8e, + 0x8a,0x19,0xec,0x3e,0xc0,0x9a,0x0d,0xcc,0xd6,0x4b,0xc2,0x48,0xc0,0xc5,0xb6,0x22, + 0x1c,0xf9,0xb9,0x75,0xa7,0xdb,0x66,0x5a,0x55,0x59,0x59,0x1f,0xd8,0xf7,0xd8,0x08, + 0xbc,0x29,0x80,0x8e,0xdb,0x87,0x29,0x43,0x22,0x3a,0xbc,0x03,0x7a,0x4a,0x50,0xda, + 0x98,0x8f,0xdd,0x74,0xd6,0x37,0xc8,0xc6,0x56,0x40,0x39,0x59,0x04,0x15,0x14,0xad, + 0xec,0x53,0x1f,0x3d,0xe0,0x25,0xe5,0xa4,0xc1,0x74,0xc1,0x2e,0x81,0x26,0x57,0x9b, + 0x99,0x81,0x86,0x10,0xde,0xf8,0xd8,0x78,0x35,0x8b,0x80,0x7d,0x41,0xe2,0xff,0x0b, + 0xf2,0x11,0x82,0xb5,0x53,0x65,0x70,0x07,0xb0,0x0d,0x3c,0x26,0x86,0x39,0x25,0x8e, + 0xdd,0xc6,0x59,0x0f,0x31,0xc9,0x6b,0x65,0x0b,0x60,0x75,0x7c,0x5e,0x4d,0xb5,0x9c, + 0x0e,0x89,0x02,0x27,0x71,0x5c,0xe6,0x75,0x80,0xc9,0xbe,0x76,0xc3,0x11,0x74,0xce, + 0x1b,0xfb,0x59,0x44,0xbc,0x8c,0x53,0x5a,0x68,0xa5,0xa5,0xa0,0xd8,0xe5,0x56,0x85, + 0x57,0x8f,0x15,0xf5,0xfb,0x2b,0x56,0xc4,0x67,0x8f,0xb4,0x80,0xbc,0x55,0x4f,0xdc, + 0x11,0x74,0xef,0x4e,0x35,0xe1,0x20,0xc7,0x07,0x3b,0x32,0x31,0xe9,0xc0,0x52,0xa4, + 0xcc,0xb6,0xd2,0x9e,0x6c,0xa0,0x5d,0x45,0x42,0x14,0x05,0xbe,0x10,0xcb,0x8e,0x40, + 0x4e,0xa8,0x22,0xda,0x40,0x99,0x4b,0x80,0xa5,0x86,0xea,0x38,0x45,0x83,0xd6,0x2d, + 0x93,0x8f,0xde,0x8d,0x36,0xbc,0xa7,0x11,0xf7,0x3c,0x45,0x23,0x91,0x9d,0xbd,0x32, + 0xad,0x0f,0xbd,0x9b,0x9c,0x83,0x5f,0x87,0x9a,0x9d,0x3d,0x72,0xbb,0x07,0x48,0x30, + 0x47,0xf4,0xe8,0x42,0x22,0xcf,0x78,0x90,0x51,0xbe,0xab,0xe3,0xa3,0xc9,0xfc,0x0f, + 0x76,0x7d,0x35,0xde,0x20,0xc3,0xb0,0x7a,0xdb,0x2e,0x92,0xed,0x0e,0xd0,0x93,0x9f, + 0xdc,0xd1,0x95,0x46,0xaf,0x6b,0x48,0x7d,0x91,0x25,0x50,0x44,0x06,0xe9,0x52,0x2b, + 0xa4,0x4d,0xaa,0x58,0xeb,0x8e,0xda,0x0c,0x70,0x87,0xa9,0xee,0x05,0x95,0x2d,0x61, + 0xe7,0x7b,0xb6,0x84,0x9d,0x7c,0x4b,0xe0,0xea,0x09,0xff,0x6b,0xe7,0xe4,0xd3,0x90, + 0x59,0x5a,0xc1,0x64,0x45,0x89,0x0f,0x88,0x6f,0x3d,0x65,0xb7,0x9d,0xff,0x96,0x65, + 0x0e,0xe7,0xaf,0xe7,0x9c,0xea,0x70,0xe7,0xe7,0xe8,0x86,0x79,0xef,0x48,0xb6,0xcb, + 0x5b,0xc9,0x04,0x09,0x9b,0x9f,0x01,0xd9,0x9e,0xe0,0xb2,0x89,0x17,0x43,0x20,0x44, + 0x33,0x24,0x52,0xb4,0xf7,0x6e,0x83,0xd4,0x73,0x05,0xbb,0x89,0xda,0x82,0x8d,0x77, + 0x21,0x8a,0x76,0xa4,0x05,0x05,0xa2,0x47,0xfb,0x94,0xa4,0x86,0xc8,0x15,0x2b,0x61, + 0x39,0x8b,0x62,0x7f,0x04,0xcd,0x25,0xd8,0xc5,0x19,0xef,0x5a,0x58,0x08,0x76,0x2a, + 0xa0,0x4a,0x7e,0x10,0x40,0x3e,0x64,0x5a,0xf3,0xc5,0x88,0x80,0x78,0x0a,0x36,0xbf, + 0x3b,0x39,0x3f,0x91,0x84,0x78,0x04,0x05,0x9f,0x0c,0x33,0x1f,0x5e,0x8a,0x6c,0x4a, + 0x15,0x7e,0xf9,0xc2,0x52,0xb8,0xb5,0xd2,0x12,0xee,0x29,0xdb,0xc9,0x47,0xd8,0xc3, + 0x41,0xce,0x30,0x5c,0x3f,0x41,0xfb,0x10,0xea,0xb0,0x6d,0x78,0xce,0xd4,0x41,0x12, + 0xff,0xfa,0xcd,0x47,0x66,0xe9,0xbf,0x20,0xd8,0x90,0x70,0x23,0xd4,0x0c,0x54,0x7e, + 0x3d,0x69,0x61,0x7f,0xba,0x09,0x61,0x26,0x69,0xd2,0x46,0x20,0x77,0xde,0x3d,0x1d, + 0x7d,0xb0,0x60,0x01,0x16,0xfb,0xbb,0x4f,0x66,0xc3,0x9f,0xc6,0xa2,0x7e,0x8a,0x05, + 0x8e,0x48,0xf4,0xd8,0xdf,0x6d,0x21,0x74,0x81,0x34,0xdf,0x6a,0x5d,0x36,0x22,0x21, + 0xa7,0x63,0x8d,0x6e,0x88,0x93,0x8a,0xd8,0x80,0x88,0x36,0x0a,0x16,0x63,0x06,0x0b, + 0xa2,0x90,0xf1,0x9f,0xff,0x38,0x4f,0x51,0x40,0x44,0xfc,0xf5,0x79,0x3f,0x80,0xed, + 0x33,0x85,0xf9,0x40,0xc0,0x0a,0xb1,0x31,0x60,0xd6,0x07,0xbb,0xe2,0xcf,0x73,0x69, + 0xf3,0xe9,0xc0,0x24,0x5f,0xd3,0x7a,0x38,0xd2,0xa7,0x12,0x20,0x45,0xda,0x53,0x97, + 0x61,0x05,0x28,0xb0,0xfb,0x82,0xc8,0x5b,0x3b,0x54,0x5c,0x45,0x34,0x0c,0x14,0xe6, + 0x72,0xb9,0x19,0x00,0x94,0x46,0x40,0x32,0x24,0x18,0x4a,0x43,0x43,0x52,0xc4,0xb2, + 0xb2,0x46,0x7d,0xa4,0xb0,0x15,0x44,0x59,0xfa,0x57,0xd1,0xe7,0xee,0x1f,0xa1,0xcf, + 0xc5,0x2a,0x76,0x2d,0xdc,0xdd,0x51,0x15,0xfd,0x04,0x42,0xbc,0xfb,0x34,0x42,0xac, + 0xb6,0x00,0xaa,0xb7,0x45,0xba,0x4e,0x93,0xa9,0xf3,0xae,0xc6,0xb0,0xd3,0x57,0x63, + 0xc3,0x9d,0xc7,0x7d,0x12,0xa3,0xab,0xc0,0xe2,0x0a,0xd6,0x41,0x8b,0x59,0xba,0x33, + 0xf7,0x9a,0x26,0x48,0x69,0x13,0x98,0x05,0x86,0xd9,0x44,0xb5,0x82,0x6d,0xa0,0x95, + 0x61,0xaa,0xa8,0xdc,0x26,0xcc,0x22,0x34,0x2c,0x75,0x09,0xee,0x78,0x8c,0x0a,0x22, + 0xc1,0xc3,0xb1,0xee,0x01,0xe8,0x71,0x08,0xfc,0x0a,0x9f,0x99,0xa3,0x58,0x97,0x08, + 0xc2,0x79,0x2e,0x74,0x4b,0xc4,0xe5,0x03,0x35,0xd6,0x54,0x10,0xc4,0xe9,0xd8,0x05, + 0x9d,0x82,0x37,0x5c,0xf8,0x00,0x6f,0xa2,0x2f,0xdf,0x3d,0xd5,0x45,0x20,0xe2,0x11, + 0x45,0xdd,0x46,0xfa,0x47,0xb7,0x67,0x04,0xb8,0x5e,0x1f,0xe9,0x63,0x00,0x8e,0xa8, + 0x9d,0x46,0x78,0x7a,0x62,0x62,0x18,0x1e,0xf5,0x02,0x5b,0xad,0x26,0x12,0xea,0x2e, + 0xaa,0x21,0xf9,0x27,0x2e,0x90,0xf2,0x82,0x69,0xa8,0x76,0xea,0x83,0x46,0x5a,0x50, + 0xf4,0x7b,0xd8,0xe6,0x3a,0xc6,0x16,0x2b,0xa9,0xe1,0xa6,0x99,0xa7,0x5c,0xa9,0x08, + 0x75,0xe3,0x78,0x1d,0x7e,0x63,0xcc,0x00,0x1e,0x22,0x3d,0xe5,0xc0,0x26,0x62,0x04, + 0x84,0x99,0x38,0x9e,0x82,0x06,0xb1,0x2c,0xec,0x96,0x0a,0x20,0xf5,0x02,0x96,0xb2, + 0xc8,0x04,0x95,0xf2,0xa0,0x56,0xc2,0x3c,0x62,0xb5,0xe0,0xaa,0x3c,0xc8,0x27,0xa4, + 0xb8,0xc0,0xe0,0x67,0x75,0x2e,0x80,0xa3,0x79,0xf4,0xea,0xfd,0xbb,0xd5,0xc2,0x77, + 0xc6,0x4c,0xdb,0x6a,0x06,0xeb,0x23,0x6d,0xf5,0xab,0x19,0xaa,0x8f,0x9a,0xb2,0xe2, + 0x7b,0x75,0x15,0x2b,0xd6,0x87,0x60,0x84,0xf1,0x68,0xab,0xb3,0x4a,0xa3,0x7f,0xf4, + 0x1e,0x56,0x67,0xb6,0x18,0xd7,0xb7,0x5d,0x16,0x4f,0x61,0x9d,0x22,0xd2,0x78,0x50, + 0xa3,0x1b,0xde,0x99,0xeb,0x37,0xa4,0xf7,0x51,0x38,0xfd,0x8e,0xaa,0xa3,0xb0,0xb6, + 0xea,0xbf,0x48,0x88,0xab,0xd2,0x3d,0x58,0x45,0xa9,0x12,0xb5,0x50,0xc8,0xd2,0x18, + 0x3d,0x34,0x51,0x66,0x0d,0x2c,0x1f,0xad,0xc0,0xe6,0x3e,0x87,0x2c,0x69,0x49,0xd0, + 0x32,0x74,0x3d,0x56,0xa6,0x91,0x37,0x29,0xf6,0xe6,0x62,0xde,0x3a,0xe1,0xe7,0x7f, + 0x5a,0x56,0xfb,0xff,0x87,0xf0,0xa5,0xcd,0xf0,0xfb,0xe8,0xb3,0x2b,0x34,0x48,0x85, + 0xb3,0x48,0x3c,0x7a,0x36,0x80,0x82,0x6a,0xc7,0x8b,0x2c,0x4a,0xa1,0xf5,0x82,0x79, + 0xc4,0xf4,0x98,0x84,0xe9,0x38,0x0e,0xee,0xc4,0x01,0xe5,0x2a,0x04,0x79,0x9b,0x78, + 0xbf,0x2d,0xbc,0x70,0x74,0x67,0x1b,0x43,0x98,0x7b,0x11,0xec,0xed,0xec,0x2d,0xe1, + 0xca,0xab,0xcf,0xc6,0x7c,0x01,0x7c,0x31,0x48,0x12,0xa3,0x19,0x33,0x80,0xa4,0xa6, + 0xf2,0x6e,0x61,0x66,0x35,0x99,0xc2,0x35,0x6e,0x12,0x58,0x06,0xc6,0xb5,0x1b,0x2c, + 0x3c,0x23,0x73,0xaf,0x88,0xef,0x53,0xbc,0xd0,0x84,0xb5,0xf4,0xc0,0x5d,0x1b,0x0b, + 0xc0,0xbc,0xc0,0x20,0x0f,0x03,0xc6,0x32,0x56,0xc4,0xaf,0xd9,0xfb,0x6a,0xa7,0x4b, + 0xf5,0xda,0x68,0x7e,0xf8,0xf9,0x77,0xeb,0xf1,0xa5,0x97,0x4c,0xa1,0x38,0x14,0x92, + 0x8b,0xaf,0xed,0xb4,0xdb,0x1d,0xa1,0x84,0xea,0xec,0xb5,0x85,0x16,0xaa,0xbb,0xd7, + 0x6e,0x3f,0x82,0x28,0x3f,0x49,0x20,0x19,0xcd,0xab,0x27,0xb7,0x3c,0xbc,0xd1,0xda, + 0x95,0xcd,0xbe,0x10,0x8d,0x16,0xda,0xfc,0x3e,0x30,0x9c,0xc5,0x89,0xe7,0x8e,0xe9, + 0x34,0x85,0x34,0x2e,0x15,0x8d,0xa0,0xea,0x41,0x8a,0x85,0x22,0xe2,0x6b,0x8f,0xf6, + 0x0e,0xb7,0xc5,0x93,0x4c,0xd9,0xaf,0xa4,0xbc,0xa8,0xa4,0x1c,0x54,0x52,0x5e,0x56, + 0x52,0x3a,0xed,0x6a,0x52,0xa7,0x9a,0xd4,0xcd,0x93,0xa4,0x2a,0xf2,0x91,0xc5,0x49, + 0x63,0x04,0x0e,0xcb,0x5b,0x3d,0xc2,0x51,0xf2,0x17,0x8e,0xb0,0xdc,0xad,0xef,0x57, + 0x80,0xfe,0x85,0xfa,0xcf,0x27,0x11,0x0b,0xc9,0xcc,0x54,0xd7,0x79,0x1d,0x02,0x69, + 0x7b,0x66,0x57,0x0d,0xe4,0xc4,0x4f,0x32,0x7f,0xee,0x95,0x70,0x69,0xed,0x40,0xdc, + 0x49,0x8e,0xd8,0x12,0xaf,0xdb,0x2b,0x4f,0x15,0xdc,0x49,0x6b,0x3c,0x5a,0xab,0x54, + 0xd6,0xfa,0xb5,0x9b,0x2b,0xbd,0x7f,0x35,0xc6,0x5e,0xe0,0xde,0x19,0x68,0x66,0xfb, + 0xa4,0x6e,0x25,0xb7,0x54,0xa0,0xae,0x6f,0x62,0xa1,0xaf,0x5d,0x72,0x85,0x7e,0xec, + 0x75,0xba,0xfa,0x54,0x73,0x4f,0xbe,0x03,0x44,0xd9,0xa3,0x7d,0x79,0x7c,0xe6,0xf5, + 0x13,0xe9,0x02,0xc3,0x73,0x00,0x79,0x98,0xbc,0x0f,0x8f,0x5e,0x9d,0xbc,0x36,0x9a, + 0xa4,0xa0,0x0e,0xe5,0x31,0x29,0xe9,0x43,0xe6,0x7e,0x66,0xa1,0x7c,0x71,0xe8,0x1f, + 0xbd,0x9a,0xb9,0x21,0x7a,0xf5,0x41,0xef,0xfd,0x6b,0x3f,0xbb,0x83,0xd1,0x64,0xcc, + 0xe0,0xf2,0xb1,0xb4,0x30,0x65,0xd1,0x37,0x9b,0x6c,0x1a,0x98,0xc5,0x01,0x92,0x3a, + 0x7d,0x18,0xdd,0xe6,0x43,0x1c,0xb9,0xb8,0xa5,0x2d,0xa0,0x82,0xc5,0x91,0xda,0x6e, + 0x1e,0xe9,0x79,0x67,0x5f,0xeb,0x3a,0xcc,0x30,0xec,0x59,0xa8,0x5a,0x37,0xa6,0x2e, + 0x99,0x33,0x60,0x6f,0xcf,0x7e,0xed,0x74,0xf7,0x6f,0x81,0x45,0x19,0x79,0x3e,0x8a, + 0xca,0xa4,0xc7,0xfa,0x53,0x1d,0xa5,0x6d,0xd2,0x49,0x6e,0xb1,0x91,0xef,0xee,0x71, + 0xb7,0xbd,0xab,0x83,0xfb,0xed,0x9b,0x0f,0x86,0xec,0xb7,0xe8,0xf0,0x9b,0x5b,0x60, + 0x77,0x50,0x6f,0x30,0x49,0x50,0xd4,0xf0,0xc2,0x71,0x6b,0x1e,0x8d,0x17,0x20,0xf9, + 0xbd,0xff,0x78,0xf2,0x17,0x74,0x7c,0xe2,0xcd,0xff,0x68,0xe7,0x77,0xba,0x3a,0xb8, + 0xc9,0xf7,0x5b,0x74,0xfa,0x6d,0x94,0xdc,0x20,0x8f,0x49,0x1b,0x3b,0x20,0xcc,0x64, + 0xe2,0x8f,0x1c,0xe3,0x13,0xec,0xd9,0x03,0x3e,0xee,0x08,0x5b,0xa4,0x3f,0x6c,0xa6, + 0xb0,0x73,0x07,0x52,0x33,0x99,0xb2,0x09,0x92,0x50,0x44,0xa6,0x68,0x64,0xf3,0x67, + 0x86,0x47,0x1d,0x7a,0xc2,0xa0,0xf4,0xb3,0x6c,0x4d,0x28,0x44,0x99,0x50,0x1b,0xec, + 0xfe,0xee,0x6a,0x39,0x80,0x4e,0x81,0xdc,0x31,0x46,0xdc,0x30,0x9a,0x73,0x4d,0x99, + 0xbb,0x9e,0xce,0x51,0x01,0x87,0xcc,0x25,0x81,0xc9,0x29,0xaf,0xe0,0xdd,0x5a,0x9a, + 0x77,0xd4,0x06,0x10,0x02,0xf3,0xf3,0x04,0xad,0xd6,0xdb,0x20,0x8a,0xc6,0xaa,0x5b, + 0xb3,0x24,0x7d,0x5a,0xb7,0x26,0x58,0xcc,0x59,0xdf,0xb9,0xce,0xfe,0xc1,0xba,0xce, + 0xd9,0x86,0x07,0x32,0xb2,0xb1,0xb3,0x11,0x12,0x2b,0x07,0xb9,0x6b,0xcf,0x6f,0x57, + 0xec,0x3d,0x1a,0xf3,0x49,0x7d,0xd1,0x05,0x14,0x1a,0x13,0xee,0xdb,0xac,0x26,0x80, + 0xb7,0x38,0x5d,0xc5,0x85,0xfe,0x1c,0xc5,0x80,0x6c,0x40,0xa9,0x58,0xa1,0x46,0x75, + 0x49,0x64,0xd4,0x38,0xc9,0x09,0xe3,0x6a,0x0a,0x22,0x89,0x38,0xf2,0x03,0x7e,0x33, + 0xa5,0xb8,0xc5,0xb0,0x28,0x20,0x8f,0x9f,0xde,0x19,0x21,0x1a,0x50,0x0e,0x81,0x57, + 0x4d,0xfb,0xc0,0x04,0x47,0xd0,0xf0,0x8d,0x60,0x49,0x63,0x37,0x81,0x06,0xa2,0x49, + 0x7e,0x8e,0x8d,0xc6,0x18,0xf0,0x4e,0x66,0x2c,0xce,0x13,0xe4,0xcb,0xee,0xc1,0x4a, + 0xcc,0x02,0xd1,0x47,0xf4,0x7b,0x46,0x03,0x7d,0xf2,0xf4,0xc1,0x2c,0x95,0xa6,0xac, + 0x82,0xbf,0x62,0xc6,0xc4,0x1c,0xed,0xef,0x3a,0x86,0x30,0x8a,0x30,0xe0,0xf9,0x09, + 0xe8,0x85,0x7d,0x13,0xc8,0xf5,0xfd,0x9d,0x13,0xf8,0xf5,0xc7,0xfb,0x78,0x50,0x6f, + 0x12,0xb0,0x6a,0xc3,0x25,0x20,0x6b,0x1d,0x5f,0x84,0xe9,0x28,0x8a,0xf1,0xc0,0xe2, + 0x0f,0x42,0xd7,0x91,0x35,0x54,0x87,0xf0,0x3d,0x67,0xce,0x27,0x20,0x4d,0xe1,0x39, + 0x0c,0x08,0x56,0x7c,0xde,0x92,0xf2,0xd1,0x48,0x18,0xc9,0x73,0x0e,0x6a,0xc5,0x31, + 0x34,0x18,0xac,0x63,0x75,0x1e,0x25,0x63,0xdd,0xbd,0x7d,0x8d,0x6e,0xc1,0x0a,0xd1, + 0x76,0xeb,0x3a,0x2e,0x98,0xd4,0x0b,0x51,0xec,0x70,0x36,0xc5,0x0d,0xf3,0x2a,0x11, + 0xda,0xee,0x4f,0x48,0x8e,0x8a,0x7c,0xaf,0xfc,0x8e,0x07,0xaf,0x73,0x20,0x1f,0x47, + 0x1f,0xf8,0x61,0x65,0x3e,0x58,0x89,0xc8,0x8a,0x43,0x46,0xf1,0xb4,0x2a,0x67,0x9a, + 0x25,0x3e,0x76,0xe4,0x8c,0x7e,0xab,0xdc,0x75,0x0d,0x90,0x5f,0x27,0x18,0x87,0x91, + 0x67,0x5a,0x42,0x99,0x0f,0xe0,0x03,0x14,0x75,0xee,0x0c,0x20,0x30,0xc9,0x5d,0xc1, + 0x2c,0x74,0xe6,0xa2,0x8e,0x82,0xdb,0x00,0x9a,0x30,0x30,0x26,0x1e,0xd2,0x06,0xde, + 0x53,0x70,0xba,0x02,0xea,0xe4,0x58,0x32,0x45,0x63,0x68,0x22,0x06,0xba,0xe4,0x3c, + 0x46,0xe8,0x58,0xe1,0x45,0x2a,0xb8,0xa7,0x9d,0x2e,0xbe,0xa3,0xc3,0xba,0xec,0xee, + 0x8f,0x9f,0x28,0xfe,0x8f,0x1f,0x28,0xca,0x23,0xc4,0x3e,0xba,0xb9,0x97,0x8e,0x10, + 0x1d,0xe3,0x27,0x52,0x01,0x0d,0xfe,0x9a,0x93,0xc0,0xff,0xf1,0x83,0xc0,0x3f,0x74, + 0x68,0xf7,0xdf,0x79,0x66,0x57,0x37,0x83,0xab,0xcf,0xe3,0x70,0x13,0xa3,0xd6,0x36, + 0xd3,0xd2,0xb1,0x1c,0xa0,0x95,0x38,0xc1,0xb2,0xfe,0xe0,0x91,0xda,0x13,0x4f,0xd4, + 0x9e,0x26,0x9a,0x9e,0x32,0x76,0xc1,0x92,0x5b,0xb5,0xfd,0x9f,0x6b,0xe7,0x66,0xc0, + 0x6a,0x7a,0x88,0xa8,0xc0,0xa7,0x2e,0xe2,0x20,0x72,0xc7,0x64,0x4d,0x83,0x54,0x97, + 0xac,0xcd,0x5a,0x78,0x80,0x3d,0x23,0x95,0x47,0x12,0xcd,0x0d,0x17,0xed,0x7a,0xaf, + 0x10,0xaf,0x51,0xcb,0xf9,0x6f,0x3e,0xd2,0xa1,0x93,0x19,0xcd,0x4e,0xa4,0x8e,0x37, + 0xce,0xb9,0x61,0x32,0xa2,0xe3,0x83,0x74,0xc1,0x12,0x9f,0x7a,0x89,0x0f,0xac,0xcb, + 0x08,0x6d,0xbb,0x82,0x6c,0xb6,0x4d,0xda,0x73,0x43,0xd4,0x29,0xcc,0xdd,0xe8,0x0c, + 0xf8,0x4f,0x31,0xbd,0x04,0x47,0xae,0xed,0x69,0xec,0x7c,0xde,0x65,0x01,0x51,0x49, + 0x21,0x45,0xb7,0x3f,0xb8,0x64,0x37,0x93,0xc2,0xae,0x34,0x9a,0xb1,0xc1,0x21,0x30, + 0xec,0xc2,0x36,0x40,0x32,0x53,0x30,0xbb,0xd1,0xcd,0x9f,0xef,0xb7,0x68,0xf9,0x7b, + 0x3b,0x7e,0x32,0x1e,0x1b,0x89,0x7b,0x63,0x70,0x80,0x0c,0xd1,0xef,0x93,0x80,0xce, + 0x8b,0xd1,0x00,0x08,0x6d,0x87,0x16,0xd0,0x69,0xc4,0x76,0xca,0x23,0xa1,0x8e,0x85, + 0xfe,0x22,0x90,0x43,0x55,0xdf,0xdb,0xed,0xcf,0x2c,0x82,0x8e,0x4b,0x00,0x07,0x39, + 0x0a,0x0f,0xb5,0xe5,0x3e,0x85,0x96,0x80,0xe3,0x5c,0x77,0xee,0xfa,0xc9,0x5f,0xd0, + 0xd9,0xdb,0x3f,0x8a,0x1b,0x82,0x0d,0x4e,0x2a,0x48,0x2d,0x3f,0x00,0x53,0x8c,0x87, + 0x92,0x28,0xc7,0x11,0xe3,0x92,0x1a,0xcd,0xd3,0xb3,0xcf,0x27,0x1f,0x40,0xa4,0x45, + 0x1e,0x9b,0x96,0xa2,0xf5,0xe7,0x47,0xa0,0xfa,0xf1,0xe7,0xc4,0xbb,0x95,0xda,0x37, + 0xa1,0xe2,0xc8,0xf4,0xd9,0x59,0xc1,0x1d,0x51,0x7f,0xb2,0xdb,0xef,0xe6,0x8c,0xf0, + 0xa0,0xee,0x04,0xb0,0xf2,0xfc,0xd7,0x55,0x39,0x04,0x8b,0x8c,0xa4,0x54,0x30,0xda, + 0x52,0x5b,0xf1,0x04,0x9e,0xa7,0x84,0x46,0xb9,0xb8,0x83,0x6b,0xe2,0x49,0x16,0x26, + 0x67,0x4c,0x93,0xa4,0x04,0xf8,0x1d,0x82,0x2d,0xef,0xb4,0x45,0xc9,0xb1,0x23,0xf9, + 0xe3,0x47,0xf5,0xd2,0xdf,0x39,0x57,0x1f,0x25,0x2a,0x68,0x3d,0x9d,0xa1,0xa0,0xf6, + 0x1d,0x7d,0x55,0xe8,0x54,0xe9,0x75,0x57,0xaa,0x52,0x77,0xf6,0x57,0xf2,0x42,0xd1, + 0x04,0xb5,0x63,0xc8,0xc1,0x8b,0x55,0x42,0x27,0xfe,0xc5,0x05,0xd1,0xec,0x74,0x5b, + 0x50,0x87,0x6d,0x08,0xef,0x44,0xa3,0xbb,0x6b,0x39,0xdf,0x23,0x14,0xaf,0xb3,0xef, + 0x70,0xe3,0xf8,0x71,0x03,0x0f,0xad,0x96,0x73,0x54,0xc8,0xb2,0x00,0x7d,0xf6,0xf1, + 0xc3,0xe9,0xda,0xa3,0xd5,0xf3,0x53,0xe1,0x80,0xf1,0x54,0x5e,0x25,0xcc,0xe2,0x15, + 0xac,0x89,0xe6,0xd1,0x8d,0x8e,0x37,0x51,0x80,0x79,0x81,0xfb,0x9c,0xd6,0x42,0x96, + 0x5d,0xb5,0x80,0xf6,0xa0,0x63,0xa0,0xdc,0xad,0x47,0x01,0x90,0xc3,0xd5,0xc8,0xbb, + 0xee,0x58,0x03,0x07,0xfd,0x3b,0xfa,0x03,0x35,0x4f,0x3f,0x9d,0xbd,0xfb,0xf5,0x29, + 0xdc,0x71,0x26,0x8a,0x3c,0x65,0x3c,0x1f,0xce,0xce,0x5f,0x7c,0x78,0x7d,0x6e,0x7f, + 0xd8,0x71,0xba,0x4e,0xdb,0xfe,0xd0,0xe9,0x38,0x1d,0x67,0xd5,0xb9,0x4f,0x8d,0x84, + 0xd8,0x61,0xee,0x91,0xfb,0xf4,0xe5,0xfc,0x15,0xaa,0x51,0x34,0x73,0xec,0xf5,0xda, + 0x60,0xd1,0x4f,0x87,0xcb,0xc8,0x63,0x00,0x85,0xbc,0x9d,0xdd,0x75,0x88,0x51,0x20, + 0xf4,0x88,0x0f,0x06,0xf0,0xea,0x61,0xa6,0xb6,0x23,0xe0,0x26,0x80,0x90,0xe0,0xd9, + 0x98,0x9f,0xe0,0x51,0xc0,0x9f,0xa0,0xdd,0x69,0x38,0x8f,0x9f,0x40,0xb1,0x35,0x1a, + 0x84,0xdd,0xc1,0x13,0xe4,0x45,0x48,0x22,0xd0,0xa3,0x53,0x86,0x2d,0x38,0xaa,0xc0, + 0x0a,0x99,0xe6,0x31,0xe9,0x8c,0x8c,0x1f,0x9e,0x26,0x9d,0x91,0xff,0x90,0xf4,0xbc, + 0x89,0xc2,0x55,0xbc,0x28,0xea,0xc8,0xa7,0xc8,0x5f,0x52,0xfe,0x84,0x61,0xca,0xa6, + 0x41,0x79,0x61,0x75,0xf2,0x49,0xc4,0x9a,0xcc,0x7c,0x6e,0x50,0x3b,0x8a,0xb2,0x25, + 0x9b,0x32,0xe7,0xc6,0xcb,0xb0,0xe4,0x61,0x25,0xa4,0xd2,0x7f,0x42,0x33,0x2c,0x72, + 0x8c,0xb7,0x3e,0x3a,0x52,0x64,0x6c,0x6e,0xf4,0xee,0x94,0x79,0x5a,0x3a,0x67,0x4d, + 0xa2,0x05,0x2e,0xaa,0x28,0x11,0xd6,0x4a,0xe4,0xae,0x24,0x1c,0x39,0xff,0xdf,0xf0, + 0x0b,0x22,0x52,0xf6,0xff,0xae,0x63,0x90,0xec,0xde,0x5f,0xe2,0x19,0xf4,0x67,0x1c, + 0x83,0x1e,0xd5,0x77,0xb5,0xbb,0xf9,0x59,0xd7,0x29,0xe9,0x35,0xd9,0xd6,0x6d,0x05, + 0x17,0xc3,0x8d,0x61,0x3e,0xb2,0xd0,0xaa,0x51,0xe3,0x08,0x15,0xce,0x62,0xbe,0x8a, + 0x5d,0x21,0x07,0x6e,0x10,0x7c,0x90,0xc4,0x8a,0x5d,0x0e,0x15,0xb4,0x37,0x68,0x03, + 0x8b,0x46,0x6a,0xe1,0x08,0x39,0xbf,0x15,0xea,0x1f,0xf7,0x36,0x37,0x72,0x6f,0x0e, + 0x5d,0x8c,0x33,0xa9,0xe7,0x7e,0xfc,0xe8,0x54,0x5b,0x93,0x1f,0x5c,0xdc,0xc5,0x43, + 0x3c,0xb9,0x5c,0x79,0x70,0xb9,0x02,0x4b,0x56,0xd9,0x2c,0x8e,0xa3,0xcf,0x64,0x00, + 0xd1,0x24,0xb3,0x45,0x32,0x85,0x08,0x49,0xeb,0xb0,0xda,0x02,0xae,0xb6,0x42,0xe1, + 0x33,0x0b,0xcb,0xb0,0x55,0x74,0x57,0x1a,0x47,0xef,0x29,0x99,0xcc,0xe0,0xd0,0x91, + 0x01,0x9e,0x57,0x5a,0x46,0x56,0x08,0x16,0x5b,0x62,0x3d,0x8d,0x62,0xbd,0xa6,0x0b, + 0xf8,0x84,0xf1,0x07,0xbb,0x90,0x42,0xe1,0x16,0x50,0x1b,0xad,0xb7,0x91,0x9b,0x89, + 0xb8,0x78,0xe5,0x58,0x42,0x25,0x4d,0x08,0x45,0xc3,0x28,0xc5,0x05,0x6d,0x9b,0x47, + 0x2b,0xed,0x43,0x28,0x94,0xb0,0x30,0x67,0xa3,0xc7,0xa7,0x4d,0xe9,0xdb,0xc4,0x43, + 0x67,0x4b,0x37,0x36,0x9a,0xbf,0xfc,0x64,0x51,0xb5,0x87,0x1c,0x0c,0x97,0x87,0x10, + 0xbb,0xc9,0x15,0xc6,0x40,0x8d,0xf3,0x50,0x07,0xa4,0xe7,0xdc,0xe6,0x4c,0xd2,0x8c, + 0xcc,0x4f,0x3d,0x52,0x03,0xaa,0x63,0xf5,0xda,0x7a,0x50,0xe3,0xef,0xad,0xac,0x68, + 0xdd,0xa1,0x05,0x32,0x40,0x3c,0x19,0xfa,0x99,0x85,0xee,0xc5,0x5a,0xe2,0xdc,0x30, + 0xaf,0x60,0xdd,0x9e,0xec,0x29,0x83,0x6b,0x8c,0xd6,0xad,0x71,0xe7,0x65,0xce,0x63, + 0xa8,0x81,0xe6,0x77,0x8f,0x21,0x86,0x6c,0x5a,0x8f,0x0c,0x5e,0xb6,0x42,0xf4,0x92, + 0xb9,0x98,0x36,0xaf,0xe2,0xe3,0x2e,0xe2,0xad,0x99,0x25,0x8e,0x20,0x8d,0xab,0x91, + 0x0c,0x8a,0x64,0x58,0xef,0x97,0xaa,0xbc,0x45,0xbe,0x4d,0x4f,0xa3,0xd1,0x0d,0xb5, + 0x80,0xd0,0xd6,0xf6,0x1f,0x64,0x4d,0x8a,0x4b,0x68,0x23,0x70,0x93,0xa4,0xcf,0x5e, + 0x52,0xd5,0x45,0xfa,0x67,0xf6,0x02,0xf8,0xf9,0xd9,0x0b,0x62,0x6c,0x64,0x06,0xbf, + 0x7f,0x79,0xe5,0xaf,0x90,0xc3,0xc5,0xda,0x89,0xd5,0x5d,0x6b,0x00,0x2b,0x63,0xab, + 0x69,0xa0,0xc3,0xb7,0xa3,0x55,0x59,0x39,0xd2,0x1a,0x7c,0x2f,0x7f,0x48,0x17,0x53, + 0xa3,0x3c,0x07,0x8b,0xe9,0x1a,0x9b,0x0a,0x15,0x01,0xab,0x3c,0xe5,0x22,0x34,0x15, + 0x07,0x85,0x50,0x73,0x5f,0xdd,0xfd,0x55,0x3b,0x4a,0xef,0x5c,0xd8,0xde,0x56,0x79, + 0x1e,0x94,0xd8,0x81,0x46,0x1a,0x7b,0x41,0x40,0x6c,0x26,0xac,0x1b,0x36,0xcb,0x25, + 0x7b,0xdd,0x2b,0xef,0x0e,0x85,0x08,0x60,0x41,0xa2,0x12,0x9b,0x8e,0x9d,0x30,0x5c, + 0x69,0x94,0x58,0xdd,0x09,0x44,0xb8,0xa5,0x3f,0xc2,0x2f,0x34,0xf4,0x69,0x3c,0xa3, + 0xd0,0x0a,0x38,0x8f,0xc9,0x22,0x5c,0x6b,0xe2,0x2e,0x17,0x22,0x47,0x93,0x91,0x00, + 0x3d,0x3a,0xbc,0x1a,0x8e,0x8f,0xce,0xdd,0xe1,0xe1,0x36,0x3e,0x18,0x12,0x3e,0x92, + 0xb0,0xe6,0x79,0x36,0x16,0x88,0xea,0x9c,0x8d,0x13,0xc6,0x79,0x02,0x4c,0x6b,0x8a, + 0x4e,0x73,0x05,0x8e,0x9d,0xb3,0xbd,0x49,0x47,0x22,0xcf,0xd8,0x4f,0xe7,0x7e,0x9a, + 0x16,0xaa,0xe6,0x38,0x20,0x73,0x18,0x99,0x1f,0xa3,0x91,0x39,0xcc,0x35,0x29,0x2b, + 0xd1,0xcc,0xdc,0x85,0x75,0x45,0x86,0x72,0x9e,0xaa,0xb6,0x96,0x08,0x69,0x16,0xcf, + 0xdb,0x22,0x14,0x88,0x2e,0x76,0xf0,0x85,0x06,0x8c,0x0b,0xf2,0x05,0xe7,0x43,0x25, + 0xb4,0x28,0x18,0xc2,0xf0,0xe8,0x51,0xa2,0x52,0xdc,0x74,0x8d,0x74,0xae,0x2d,0x29, + 0x9c,0xa5,0x24,0x3b,0x09,0x02,0xde,0xba,0xf1,0x65,0xfd,0xae,0x3d,0xcf,0x7b,0x54, + 0xda,0xa3,0x81,0xf9,0x2b,0x58,0xaa,0xb3,0x33,0x6c,0xba,0x76,0x8d,0xe6,0x41,0xaf, + 0xf3,0x05,0x46,0xd1,0xaf,0x29,0xb1,0x44,0x47,0x45,0x1c,0x6f,0x76,0x1d,0x1a,0xde, + 0x29,0xd7,0x6d,0x06,0x40,0x89,0xd3,0x7d,0x02,0x10,0xfe,0x0c,0x29,0x22,0x09,0x85, + 0x98,0xde,0x2c,0x59,0x78,0x04,0xbb,0xb4,0xc8,0xf0,0xfe,0xf7,0xb4,0x4b,0x21,0x3c, + 0xa8,0x5d,0x68,0xf3,0x15,0xbe,0xa8,0x26,0xcb,0x9b,0x9b,0x8a,0x22,0x2e,0x48,0xda, + 0x4a,0x4b,0x7f,0xb5,0xaf,0x91,0x34,0x52,0x42,0x27,0xb2,0x7b,0xd1,0x45,0xd1,0xd5, + 0x6b,0x94,0x62,0x5d,0x0b,0xe2,0x45,0x8f,0x75,0xb4,0x51,0x84,0x98,0xe6,0x6c,0xde, + 0x2d,0xc5,0x02,0x95,0xf3,0x5c,0x11,0xfb,0x77,0xf1,0xd0,0xa8,0x1c,0xf4,0x5e,0x04, + 0x1d,0x2a,0xaa,0xc1,0x34,0xbf,0xd8,0x6a,0x98,0x6b,0x0a,0xb2,0x91,0xf9,0xe1,0xc2, + 0xab,0x46,0x26,0xa1,0x28,0x45,0x51,0x50,0xc3,0x5e,0xec,0x55,0xe2,0xe4,0xbe,0xec, + 0x17,0x82,0xa7,0x75,0xcb,0xd1,0xb2,0x26,0x53,0x0b,0x3b,0x17,0xf8,0x40,0x99,0x62, + 0xf2,0x62,0x93,0xba,0x99,0x66,0x44,0xba,0x9a,0x57,0xc8,0x74,0x07,0x98,0x62,0x49, + 0x67,0x64,0x20,0xe3,0x78,0x12,0x44,0x67,0x4b,0x14,0x81,0x23,0x04,0x4c,0x70,0x0e, + 0x87,0x89,0xdc,0x3a,0x6a,0x1d,0x29,0x6a,0xb8,0x9e,0x77,0x13,0xc3,0x4d,0xaf,0xbc, + 0xb1,0x0d,0x0b,0x4f,0x3a,0x4a,0x7f,0x49,0x31,0x84,0x07,0xc7,0xbf,0x78,0x87,0x90, + 0x08,0x3d,0x52,0x5c,0x18,0xdb,0xf8,0xf5,0x2c,0x73,0xef,0xa4,0x7c,0x8d,0x9a,0x0b, + 0x72,0x87,0x11,0x33,0x0d,0x63,0xa0,0x81,0x50,0x38,0x97,0x33,0x77,0xe2,0x26,0x3e, + 0xca,0xc5,0xaf,0x66,0x49,0x44,0xc1,0x7f,0xb8,0xb7,0x7c,0x5b,0xb5,0x93,0x67,0xff, + 0x5b,0x84,0x54,0x10,0x88,0x54,0xb5,0xd7,0x2f,0x64,0xf8,0x13,0x9a,0xf4,0x45,0x02, + 0xab,0x7b,0x96,0x65,0x71,0x6f,0x7b,0xbb,0xf3,0xb2,0xeb,0x74,0xf6,0x0f,0x9c,0x5d, + 0xa7,0x43,0xf4,0x8c,0x6b,0xdb,0x8e,0x82,0xba,0x78,0x37,0x55,0xed,0x27,0xde,0xa0, + 0x64,0x96,0x06,0x23,0x8f,0x30,0x60,0x94,0x32,0x20,0x55,0xeb,0x4c,0x70,0x3b,0x47, + 0x85,0xc0,0x2a,0x8e,0xf1,0x4f,0x3d,0x20,0x0d,0xdb,0xec,0xfb,0x29,0x3a,0x9e,0xe3, + 0xb1,0xb7,0x2f,0x6d,0xa6,0x73,0xe5,0x83,0x27,0xe2,0x41,0x29,0x35,0x85,0x98,0x49, + 0x81,0x52,0xb6,0x71,0xc3,0x75,0xa0,0x07,0x5e,0x72,0xed,0x8b,0x40,0x24,0x46,0x3a, + 0x02,0x26,0x1d,0xa3,0x35,0x8d,0xae,0xf2,0x83,0xf0,0x27,0x7b,0xe0,0xb4,0x57,0x7a, + 0xe0,0x8c,0xa3,0xd1,0x82,0x02,0x97,0x4c,0xbd,0xec,0x4d,0xe0,0xe1,0xe3,0x4f,0x77, + 0xef,0xc6,0xcd,0x4d,0xb1,0xb8,0x36,0x2d,0x87,0x00,0xf8,0x1e,0x28,0x81,0x83,0xae, + 0x07,0x40,0xa2,0x37,0x91,0x1d,0xdc,0x7c,0xa2,0xcb,0x0e,0xcc,0xe9,0x90,0xdc,0x99, + 0x94,0xab,0xce,0x2a,0x02,0x50,0x58,0xdb,0x6c,0x12,0x9f,0xaf,0xee,0x0a,0xf7,0xeb, + 0xe7,0x5e,0x50,0xbc,0xda,0x77,0x76,0xf3,0xb0,0xf2,0x3b,0xbb,0x7c,0x5a,0x2c,0x88, + 0x78,0x79,0xbd,0x93,0x4b,0x92,0x41,0xf7,0x1e,0x14,0x5a,0x13,0x4e,0x6d,0x2c,0x83, + 0x16,0xa2,0x8c,0xd1,0x3a,0x8f,0xf5,0xbc,0xf3,0x74,0xaa,0x3a,0x50,0xe3,0x6e,0x5f, + 0xa0,0x41,0xa6,0x18,0x7a,0x5c,0xe5,0xc9,0x45,0x6d,0xc0,0x9b,0xac,0xc4,0xcd,0x8f, + 0x55,0xbf,0xb6,0x1e,0xae,0x70,0x98,0xcf,0x42,0x0d,0xc8,0x33,0x5e,0x99,0xf9,0x1c, + 0x57,0x26,0xa4,0xdb,0xae,0xde,0x47,0x81,0x5d,0x73,0x55,0xf7,0xea,0xbb,0xa0,0x85, + 0x53,0xec,0xee,0xd7,0x54,0xa1,0x77,0x63,0x14,0x2d,0xc2,0xcc,0xac,0x9d,0x6a,0x40, + 0x61,0x3f,0xce,0x8e,0x1a,0x26,0x46,0xf4,0x11,0x86,0x26,0xfd,0x06,0x40,0xcd,0x38, + 0x7b,0xf3,0xf1,0xfc,0xdd,0xc7,0x37,0xef,0x07,0xe6,0x8f,0xe2,0x3f,0xf1,0x21,0xcd, + 0x06,0x4b,0x34,0x5e,0xe9,0x99,0xa6,0x4d,0x5e,0x30,0xe3,0x1e,0x61,0x9b,0x3d,0x73, + 0xd3,0x0f,0x20,0x0e,0xe6,0x6f,0xaf,0x02,0x5f,0xbc,0x48,0x5d,0xcf,0x19,0x3b,0xb0, + 0xaa,0x22,0xa1,0xe7,0x8d,0x53,0xa9,0x25,0x12,0x69,0xc0,0xf3,0xa6,0xbd,0xb6,0x3d, + 0x9a,0x4c,0x7b,0xe1,0x22,0x08,0x6c,0x34,0xd6,0xe8,0x2d,0x1f,0x6c,0xba,0xc2,0x12, + 0x1f,0x38,0x6c,0x47,0xda,0xbb,0xb8,0xb4,0x43,0x92,0x24,0x21,0x37,0x89,0xa0,0xa8, + 0xf0,0x4e,0xe0,0x05,0x59,0xc0,0xde,0x12,0x85,0x63,0xca,0x83,0xc2,0x2d,0x3c,0x3c, + 0xd8,0xb8,0x7d,0x9e,0x03,0x18,0x3d,0xbc,0xd7,0x8e,0xdf,0xb8,0xc4,0x43,0xbf,0x31, + 0x59,0x84,0xac,0x92,0xfc,0xa1,0x99,0x5a,0x4b,0xb1,0x8a,0xd4,0x42,0x04,0xe6,0x2f, + 0xb9,0x3b,0x23,0x8d,0x4c,0x94,0x40,0x86,0x07,0x2d,0xbf,0x5e,0xe0,0x24,0x49,0xdc, + 0x3b,0xd8,0xec,0xa3,0x2c,0x42,0x96,0xc0,0x49,0x03,0x24,0xa6,0x23,0x17,0x98,0xb1, + 0xfa,0xba,0x90,0x4d,0x4b,0x2d,0xbd,0x3e,0xda,0x67,0x9b,0x73,0x6b,0x89,0xb0,0xce, + 0x06,0x3f,0x34,0xcd,0xe7,0xbc,0xf5,0x5a,0xfd,0xcc,0x41,0x8a,0xf2,0x8a,0x63,0x5c, + 0x0f,0xe6,0xf0,0x9e,0x93,0x01,0x40,0xc2,0x26,0x89,0x84,0x90,0x8f,0x04,0x29,0x1c, + 0x1a,0xaa,0x57,0x32,0xe7,0x1b,0x06,0x02,0x85,0xbf,0x03,0x80,0x9a,0x4c,0x95,0xcd, + 0x35,0xad,0x65,0x56,0x25,0x26,0xa2,0xa2,0x07,0xbb,0xbb,0xdf,0x6e,0xeb,0x9d,0x03, + 0x8e,0x48,0x1b,0x2d,0x1a,0x0b,0x85,0x53,0x48,0xc0,0x60,0xda,0x28,0x73,0x34,0xb7, + 0x2f,0x36,0x0e,0x8f,0xcc,0xcb,0xed,0xa9,0xad,0x1a,0x18,0xc9,0xec,0x4b,0x73,0xc3, + 0xec,0x99,0x74,0x08,0x63,0xda,0xe6,0x21,0x3e,0x07,0x19,0x3e,0x1e,0xe1,0xe3,0x14, + 0x1f,0x37,0xcd,0x4d,0x78,0xfc,0x6d,0x11,0xc1,0xcb,0xc3,0xc5,0xe8,0xf2,0x41,0x6f, + 0x1b,0xfa,0x94,0x64,0x7f,0xf7,0x40,0x54,0x5c,0x12,0x1e,0x4e,0x6e,0x06,0xd0,0xe3, + 0xc9,0xcd,0xfd,0x3d,0x4c,0xe6,0x7c,0xb0,0xfd,0xaf,0xe6,0xf5,0xf1,0xd7,0xf1,0xd6, + 0x57,0x47,0xfe,0x69,0x1e,0xf7,0xe8,0xd7,0x3a,0xb6,0xb6,0x1d,0xef,0xd6,0x1b,0x35, + 0x27,0x37,0x96,0xc0,0xe1,0xc1,0xfc,0x78,0x7e,0xd1,0xb9,0xec,0x35,0xa9,0xfc,0x22, + 0xbc,0x0a,0x31,0x36,0x0f,0xea,0x2b,0xa0,0x29,0x00,0x61,0xc3,0x9f,0x34,0xb7,0x5b, + 0xb0,0x19,0x36,0x5b,0xf7,0x3f,0x40,0x71,0xf4,0x78,0xc5,0xe2,0x56,0xba,0x35,0x30, + 0x31,0xdd,0xec,0x1b,0xda,0x7f,0xdb,0xdb,0xc6,0xa7,0xf3,0x93,0x6f,0xaf,0x7e,0x3e, + 0xf9,0x08,0x4b,0xe6,0xdb,0xf9,0xc9,0xdf,0x06,0x43,0x2f,0x73,0x31,0x63,0x83,0x0c, + 0x43,0xa9,0x3a,0x4c,0xaa,0xad,0x0f,0x3f,0x88,0x0a,0xa1,0x26,0xd7,0xa0,0xb2,0x64, + 0xc3,0x31,0x62,0x53,0x73,0x1b,0x23,0xbc,0x21,0x2b,0xdf,0x10,0xa0,0x87,0x91,0x0f, + 0x17,0x7e,0x30,0x3e,0x4e,0xb7,0x4c,0xa3,0x69,0x6e,0xc9,0xf7,0x2d,0xd3,0x32,0x7b, + 0x69,0xbf,0xa1,0xc1,0x0d,0x44,0xc8,0x26,0x3a,0xd9,0xdb,0x51,0x9c,0xc1,0xdc,0x35, + 0xf0,0x67,0x80,0x7f,0xee,0xef,0x97,0x0f,0x34,0xd0,0x67,0xf8,0x06,0x58,0x0a,0x72, + 0xa5,0x95,0x3f,0xe2,0xf6,0xdc,0x42,0x59,0xca,0x83,0xae,0x41,0xb7,0xf0,0x56,0x5f, + 0xfa,0x90,0xa2,0x80,0x09,0xa4,0xe8,0xe7,0xf3,0xf3,0x53,0xe3,0xe4,0xf4,0x9d,0xf1, + 0xb7,0x37,0xe7,0xa9,0xe1,0x8e,0x92,0x08,0x36,0xdb,0x14,0x28,0x21,0xb4,0x9a,0x62, + 0xbd,0x54,0x57,0xc6,0x18,0xb7,0xb1,0x21,0xf8,0xa0,0x93,0x21,0x4c,0x23,0xe2,0x30, + 0x46,0x89,0xf7,0x12,0x31,0x99,0xa3,0x2c,0x18,0xa0,0xf2,0xbe,0xf4,0xb5,0x69,0xf5, + 0xa9,0x12,0x8c,0x74,0xe2,0x06,0x03,0xc8,0x25,0x1e,0xfb,0x8d,0x7a,0x64,0xc6,0x1c, + 0x2e,0xd6,0xd1,0x04,0xec,0xd5,0xdb,0xb7,0x10,0x26,0x72,0x73,0xf4,0xb2,0xd1,0x4c, + 0x83,0x89,0x83,0xae,0x7c,0x79,0x2d,0xd8,0x27,0xe8,0x7d,0x22,0xac,0x4a,0x06,0x83, + 0xc1,0x6e,0xbb,0x63,0x2d,0x71,0x4d,0xbc,0xc7,0xe8,0x7f,0xd0,0xa9,0x0c,0x98,0xa6, + 0x1b,0x3a,0x6d,0x78,0x93,0x24,0x40,0x11,0x4c,0x24,0x84,0xb0,0x60,0x64,0x0b,0x89, + 0xf3,0xef,0x14,0xfb,0x03,0x90,0xc4,0xa6,0xb4,0x0e,0x72,0xcd,0xd1,0x95,0x55,0xa9, + 0x62,0x11,0xa2,0x69,0x1e,0x9d,0x9d,0xe2,0x72,0xba,0xe3,0x53,0x0c,0x79,0x02,0x82, + 0x38,0x29,0x2a,0xc7,0x59,0x7b,0x28,0x77,0xfa,0xdf,0x5c,0xf5,0x33,0xac,0x7b,0x63, + 0x43,0xf6,0xfd,0xd9,0x60,0xd0,0x6d,0x77,0x05,0x8c,0xbd,0x41,0xde,0xda,0xbf,0x31, + 0xc2,0x7d,0x94,0xdc,0xdf,0x37,0x4d,0x9a,0x46,0x73,0x4b,0x16,0xb1,0xac,0xbe,0x27, + 0x47,0x2e,0xd3,0x20,0x25,0xf1,0x7e,0x83,0x1d,0xe5,0xdf,0xfc,0x0b,0xef,0x57,0xde, + 0x1d,0xbc,0xc1,0x5f,0x01,0x0b,0x4f,0x83,0xef,0xbf,0xb1,0x7f,0xe2,0x5f,0x8e,0x87, + 0x71,0x04,0x28,0x4f,0x40,0xc7,0xd0,0xad,0x8a,0x88,0x28,0xf4,0x5c,0xce,0x3d,0x60, + 0x6b,0xc7,0x3d,0xf3,0xf4,0xd3,0xd9,0xb9,0x69,0x73,0x4c,0xcd,0xb4,0xb7,0x34,0x05, + 0xc1,0x6b,0x9d,0x03,0x45,0x05,0x52,0x81,0xee,0x78,0x22,0xbe,0xc6,0x36,0x42,0xd9, + 0x7c,0xa0,0x0a,0x7b,0xff,0x79,0xf6,0xe9,0xa3,0x93,0x12,0x49,0xf2,0x27,0x77,0x4d, + 0x6a,0xa4,0x40,0x40,0xe6,0x57,0x9f,0xbd,0xdf,0x80,0x8d,0x12,0xe0,0x70,0x09,0x1c, + 0x5f,0xfc,0x30,0x3b,0x20,0xb2,0xdd,0x3c,0xe0,0x65,0x2f,0xf0,0x74,0x94,0xdc,0xc5, + 0x59,0xb4,0xb1,0xc1,0xbf,0xc8,0x8a,0x7d,0x76,0xe1,0xc3,0xfc,0xef,0x64,0x2c,0x6d, + 0xd5,0x27,0x37,0x5d,0xa8,0x83,0x16,0xfc,0x04,0x80,0x8c,0xad,0xf8,0x83,0x76,0xdf, + 0x3f,0x3c,0xe8,0xfb,0x5b,0x5b,0x96,0x7b,0xe1,0x5f,0x0e,0x3e,0xc0,0x58,0x1d,0x52, + 0xc9,0x36,0xe9,0x31,0xa1,0xe2,0x4d,0xeb,0xc7,0xee,0xde,0x7e,0x3e,0xc7,0xe5,0x8d, + 0x64,0xee,0xc6,0xbc,0x8d,0xb8,0x39,0x65,0x1d,0xc2,0x40,0x38,0x7b,0x73,0x78,0xd8, + 0xd9,0x3f,0x36,0xdb,0x00,0x1d,0xd3,0xda,0x1a,0x3a,0x59,0x24,0x48,0x73,0x67,0x9f, + 0xe6,0xc1,0xf9,0x77,0x04,0x68,0x6b,0x9a,0x56,0x89,0x30,0x00,0x96,0xbd,0xf5,0x60, + 0x82,0x61,0x43,0x45,0xa8,0xc0,0x56,0xb6,0x79,0x21,0x4f,0x54,0x2e,0x81,0xcb,0x84, + 0x41,0xbc,0x71,0x75,0xfc,0xf5,0x02,0xc8,0xe6,0x05,0xda,0x8e,0x91,0x45,0xd3,0x69, + 0x00,0x3b,0x06,0x71,0x4f,0xf6,0xb3,0x26,0x24,0xe1,0x06,0xbe,0xf1,0x91,0x4e,0x6c, + 0xa1,0x80,0x83,0x15,0xc2,0x62,0xc5,0x64,0xcb,0x12,0x68,0x41,0xf4,0x1b,0xad,0x53, + 0x5f,0xb9,0xc9,0x98,0xf6,0xb8,0x8a,0xf5,0x3b,0x4f,0x86,0xca,0x64,0xa9,0xa7,0x27, + 0x34,0xde,0xec,0x74,0x0f,0xee,0x11,0x9e,0xd8,0x1e,0x8c,0xca,0x74,0x8a,0xae,0xbf, + 0x2b,0x46,0xb6,0x6e,0x60,0x69,0xe6,0x54,0x98,0x18,0xeb,0x81,0x3b,0x89,0x1f,0x99, + 0xdd,0x91,0xd4,0x2c,0xf0,0xcf,0xdd,0x21,0x8c,0x6b,0xf3,0x39,0xba,0xb0,0x8b,0x18, + 0xce,0x17,0xba,0x83,0x38,0x80,0xb7,0x0f,0x45,0x39,0xa7,0xc5,0x3f,0x72,0xfb,0xc5, + 0x4e,0xe3,0xb6,0x4f,0xaa,0x6c,0xab,0xbc,0xc9,0x53,0x87,0x68,0x26,0xa1,0x3c,0xb7, + 0x8c,0x6c,0x97,0xc5,0xa8,0xc0,0xb0,0x45,0xb5,0xfc,0xba,0x1e,0x90,0x35,0xae,0xe8, + 0x82,0xc8,0x6b,0x89,0xdf,0x4a,0x27,0x28,0xef,0xca,0x5e,0x60,0x1e,0x0c,0x1c,0xb4, + 0x36,0x03,0x05,0xf1,0x5c,0x9b,0xa3,0x7c,0x8e,0xf0,0x48,0x7b,0x5a,0xb4,0x4d,0xab, + 0xc0,0x14,0x99,0xdf,0x13,0x7b,0x33,0x15,0xb1,0x7e,0x17,0xe2,0x8a,0x1d,0x3a,0x80, + 0x86,0x62,0x79,0xa8,0x89,0x48,0xe8,0x16,0xde,0x9f,0x7c,0x74,0x4c,0xd5,0x78,0x1e, + 0xd3,0xaf,0xd4,0x38,0x47,0xf8,0x83,0x06,0x39,0x86,0x5d,0x28,0xc2,0xdb,0xe5,0x46, + 0x94,0x79,0x1d,0x5a,0xd4,0xa6,0x52,0x25,0x1c,0xae,0x43,0xcb,0xa9,0x07,0xf7,0xb0, + 0x1c,0x1f,0xc6,0x97,0xfc,0x7c,0xfe,0x01,0x18,0xf3,0x72,0x18,0xa6,0x4a,0xa0,0x8f, + 0x4a,0x25,0x14,0xdc,0xc2,0x72,0xa4,0xa4,0xa9,0xed,0x4b,0x22,0xcc,0xc9,0x43,0x81, + 0x42,0xf0,0x01,0xe0,0xb2,0x81,0x34,0xda,0xdc,0x86,0xbf,0xdb,0xc2,0xd0,0xb2,0xbc, + 0xfb,0x20,0x5f,0x01,0x33,0x85,0x62,0xc1,0x20,0xa5,0x9f,0x3e,0x4e,0x1c,0x49,0x07, + 0x90,0x80,0x0f,0xfd,0x1c,0x55,0x07,0xcf,0x9e,0x41,0x26,0x78,0xe8,0xab,0x85,0x43, + 0x49,0xd0,0xa9,0x7e,0xa3,0x6e,0xa1,0xd1,0x57,0x99,0xfa,0x2d,0x95,0xc9,0x58,0xbc, + 0x20,0x41,0x50,0x3e,0x4a,0xf9,0x26,0x73,0x53,0x85,0x94,0x44,0xca,0x02,0x2d,0x07, + 0x89,0xfc,0x7d,0x41,0x32,0x06,0xc8,0xf4,0xc4,0xee,0xd0,0x0f,0xfc,0xcc,0xf7,0x80, + 0x35,0x6a,0x73,0x41,0x92,0x2f,0x06,0x79,0xd7,0x8f,0x9b,0x30,0x1a,0x74,0xfb,0xf3, + 0xbe,0xd1,0xa7,0xfb,0xfb,0xd4,0x49,0xd0,0x1b,0x7b,0xae,0x12,0xda,0x16,0x5e,0x7a, + 0x8f,0x6b,0x13,0xbd,0x26,0xbe,0x8d,0xe6,0xe3,0xf4,0xa8,0x6d,0xbd,0x7a,0xff,0xee, + 0xdb,0x87,0x93,0x5f,0x07,0x79,0x22,0x35,0x80,0x4c,0xac,0xe0,0x61,0xfb,0x92,0x87, + 0x83,0x14,0xfa,0xfd,0x06,0xab,0xd5,0x93,0x5f,0x80,0x1f,0x42,0xd0,0xe2,0x8f,0xca, + 0x8c,0x06,0x86,0x98,0x19,0x7f,0x29,0x91,0x66,0x5b,0x84,0xed,0x2e,0x62,0x15,0x0c, + 0x1a,0x12,0x21,0x93,0xd4,0x9d,0xa8,0xcc,0x18,0xc4,0xbb,0x9c,0x17,0x5b,0x01,0xbe, + 0xf2,0x47,0x60,0x0b,0x72,0xae,0x5b,0x26,0x70,0x7b,0x4c,0x67,0x86,0x03,0xae,0x84, + 0xa3,0x78,0x5b,0xfd,0x61,0x8d,0x2c,0x21,0x57,0x2e,0x43,0x04,0x31,0x64,0x30,0x30, + 0xf9,0xc4,0xcb,0x5a,0x0e,0x4b,0x0b,0xe8,0xcd,0xf9,0x97,0x53,0xb3,0x50,0x0d,0x0b, + 0x36,0x9c,0xfd,0x01,0xb7,0xd6,0x72,0x19,0x58,0x9b,0xe6,0x83,0x18,0x8c,0x0c,0xd1, + 0xbd,0x92,0x7c,0x94,0xf6,0xbc,0xbe,0xc6,0x4f,0xcb,0x19,0xce,0x91,0x5d,0x08,0x9a, + 0x66,0x0d,0x1b,0x27,0x44,0x9a,0x5c,0x14,0x7d,0x78,0xb0,0x7a,0xfa,0x6b,0x0d,0x8b, + 0x16,0xf3,0x22,0x11,0x99,0x06,0xb1,0x7c,0xba,0xbf,0xbf,0xb8,0xec,0x0b,0x62,0x5e, + 0x86,0x8f,0x7e,0xc8,0xf7,0xa0,0x44,0x89,0x67,0x6a,0x71,0x15,0xd8,0x52,0xce,0xb0, + 0xcc,0x03,0xb8,0x3c,0x60,0x27,0x4a,0x7d,0xf7,0x98,0x51,0xf4,0x1c,0xe1,0xa3,0x00, + 0x2c,0xa2,0xe0,0x5e,0x97,0xab,0x10,0xc2,0xd4,0x2c,0x76,0xf0,0x36,0x5f,0x6f,0x6c, + 0xb4,0x28,0xda,0xee,0x1d,0xf0,0x18,0x80,0x8a,0x39,0x27,0x8e,0x04,0xc3,0xee,0xb6, + 0x49,0x68,0x2c,0xb1,0x7f,0xd8,0xcf,0xe6,0xb5,0xb5,0xbc,0x30,0x9f,0xcb,0x90,0xe8, + 0x36,0x3e,0x8a,0xa8,0xd8,0xf4,0x8c,0x81,0x61,0x2e,0xab,0x7b,0xb3,0x3f,0xc6,0xbe, + 0xc1,0xdf,0xfa,0x59,0x85,0x86,0x7e,0x80,0x9a,0xd7,0xa0,0x9e,0x92,0xbd,0xe9,0x52, + 0xb2,0x6a,0x3e,0x71,0x4d,0x19,0x64,0x65,0x09,0x2c,0xc1,0x5b,0xaf,0xe7,0xe8,0xeb, + 0x72,0x47,0xb4,0x1b,0xbe,0x1b,0x44,0x31,0x51,0x11,0xc8,0xe4,0x1c,0x03,0xa3,0xfb, + 0xac,0x06,0xa4,0x90,0xdb,0x48,0xf9,0x1b,0x8b,0x18,0x97,0x2c,0x1e,0x95,0xfc,0x84, + 0x47,0x9a,0xa5,0xd1,0x8b,0x59,0xa2,0x19,0x6b,0xe6,0x50,0xb0,0x18,0x79,0xb5,0x78, + 0xea,0x16,0x8e,0xee,0x0d,0x9e,0x17,0xbc,0x27,0xcf,0x51,0x58,0x7d,0x32,0x72,0x79, + 0xce,0xfd,0x79,0xd7,0xc8,0x8a,0x5d,0x57,0x4e,0x16,0xc4,0xc2,0xcc,0xc2,0x41,0x5e, + 0x2b,0x9e,0xea,0xc0,0xe2,0xcc,0x42,0x47,0x5e,0x23,0x31,0xc0,0x33,0x8e,0x7e,0x9e, + 0x03,0x23,0x09,0x95,0x26,0x1d,0x28,0x04,0x0b,0x4a,0xbc,0x18,0xc4,0x94,0xfd,0x09, + 0x2e,0xbd,0x51,0xc7,0xa6,0x2f,0x25,0xa1,0xee,0xe5,0x9d,0x41,0x13,0x20,0xcb,0x21, + 0x43,0x19,0x60,0xe0,0xad,0x46,0x55,0x3e,0x2b,0x8b,0x59,0x15,0x59,0x48,0xac,0xcf, + 0xb4,0x27,0x45,0x18,0xfb,0xdf,0xbd,0x7f,0x3f,0xd4,0xd5,0x76,0x0b,0x60,0x2c,0x40, + 0x86,0xb4,0x50,0xb4,0x1e,0x6f,0x1d,0x14,0xfe,0x00,0x9d,0x61,0xce,0xd4,0x6e,0x56, + 0x02,0x9c,0xd6,0x57,0x04,0x99,0xbe,0xf6,0x68,0xb5,0xae,0x03,0xf1,0xad,0xa3,0xc4, + 0x30,0x8e,0x2b,0x2f,0x56,0x97,0xd9,0xaf,0x59,0xb7,0x40,0x2b,0xab,0xbd,0x5c,0x3b, + 0x81,0xda,0xaa,0xa5,0x56,0x4c,0x29,0x94,0x29,0xac,0xcc,0x6d,0x70,0x96,0x24,0x9d, + 0xa9,0x99,0xc6,0x53,0x75,0x7b,0x59,0xa1,0x60,0x3a,0x1c,0xb8,0x03,0x3a,0xf5,0xa9, + 0xe9,0x72,0x41,0xfe,0x1a,0x4d,0xa6,0x20,0x25,0x35,0xaf,0xac,0x25,0xae,0x31,0x90, + 0x68,0x0c,0xd7,0xf8,0x3f,0x40,0x3c,0xfe,0x0f,0xb9,0xe2,0x64,0x82,0x23,0x13,0xc1, + 0x70,0x38,0x06,0x0a,0xe3,0x89,0xcd,0xa2,0x30,0x6e,0xd5,0x93,0x29,0x33,0xd8,0x03, + 0x7e,0xb1,0xe7,0x76,0x32,0x18,0x39,0xc4,0x4c,0xd9,0xbf,0xc1,0x13,0x72,0x15,0xf7, + 0xf7,0x4b,0x56,0x0b,0xa2,0xce,0xef,0x06,0x12,0x91,0x59,0xa4,0x09,0x6d,0xce,0x07, + 0x57,0x0e,0x45,0x61,0x69,0x6e,0xff,0x0b,0xb3,0x36,0x2f,0x3a,0xad,0x97,0x97,0xd6, + 0x57,0xa7,0xf9,0xf5,0x66,0xcb,0xfa,0x61,0x1b,0x04,0x86,0x25,0xeb,0x87,0x9a,0xbf, + 0x39,0x62,0x3f,0xbf,0xb8,0xb4,0x2e,0xb6,0x50,0x59,0xd4,0xea,0x5c,0x92,0xca,0x04, + 0x33,0x5c,0x0f,0xd2,0x8b,0xf9,0x45,0xf7,0xf2,0x52,0x2a,0xdc,0xaf,0x07,0x03,0xd4, + 0x56,0x1e,0x9b,0x66,0x4f,0xc8,0x5e,0xd7,0x30,0x76,0x76,0x22,0xc1,0x21,0x37,0xd0, + 0x79,0x8a,0x03,0x32,0xf5,0x14,0xf6,0xe2,0x6b,0x9f,0x3e,0x60,0xac,0xa4,0x5e,0x51, + 0xad,0x96,0x38,0x90,0x68,0x89,0xcf,0xb0,0x80,0xaa,0x9f,0xa3,0x10,0x26,0x93,0xbe, + 0x67,0xb7,0xd5,0xcf,0xd9,0xad,0x28,0xec,0x4e,0xaa,0x1f,0xdd,0x89,0x2c,0x2a,0x83, + 0x3c,0x54,0xb2,0x88,0x0f,0xa2,0x92,0x6c,0x55,0xb6,0x4c,0x66,0xe3,0xea,0x30,0x88, + 0x41,0x3e,0x40,0x78,0x3b,0x46,0xbb,0xff,0x1e,0x59,0x2f,0x70,0x4d,0x85,0xe8,0x01, + 0x79,0x56,0x4e,0xd0,0x73,0x37,0xb4,0xec,0x9a,0xcf,0x7e,0x5e,0x04,0x12,0xbf,0xad, + 0x2c,0xc6,0xbe,0xf0,0x5a,0xfd,0x94,0x50,0xcd,0x98,0x7b,0x0f,0x57,0x86,0x46,0x9f, + 0xbe,0xc1,0x27,0xab,0x9c,0x57,0x3a,0xf3,0xae,0x2e,0xf2,0x8d,0x73,0x54,0x4b,0x2a, + 0x2f,0xda,0x35,0x65,0x65,0x1e,0x59,0x5a,0xf7,0x47,0xcd,0x47,0x84,0xa9,0xdf,0x38, + 0x55,0xe4,0x2b,0xbb,0xb0,0x57,0xa7,0x9d,0x32,0x7c,0x93,0x19,0x8a,0xbd,0x7b,0xb4, + 0x34,0x77,0x70,0x45,0x1d,0xca,0x9c,0x53,0x14,0x33,0x25,0x80,0x73,0x7b,0x57,0x59, + 0xe1,0x0d,0xbd,0xf6,0xf3,0xaf,0x48,0x44,0xf3,0x8f,0xf0,0xa6,0x7f,0x53,0x26,0x9a, + 0x5a,0x0e,0x99,0x26,0x9a,0xd0,0x9d,0x3a,0x65,0xae,0xdf,0x44,0x42,0x3f,0xcf,0x41, + 0xae,0x90,0xf9,0x77,0x7c,0xd5,0x2b,0x10,0xb2,0x4d,0x9e,0x81,0x13,0x2a,0x08,0x5c, + 0xf0,0xdc,0xca,0x73,0x8b,0x94,0x2a,0x86,0x29,0x77,0xa9,0x3c,0x2f,0xbc,0xd5,0x57, + 0xab,0x2d,0xe4,0xdf,0xe0,0x45,0xfb,0x92,0xe8,0x5f,0x92,0xdb,0x15,0xcd,0xac,0x9a, + 0xbc,0xdf,0x9c,0xf2,0x7c,0x95,0xbc,0x8a,0xf2,0xba,0x55,0x5a,0x7d,0x0f,0x6b,0x3c, + 0x47,0x2a,0x6d,0xa9,0x3c,0xdf,0xea,0x5b,0xcd,0x62,0xbd,0xbd,0x2c,0x2e,0x4c,0x23, + 0x79,0x7e,0x6a,0xb3,0x88,0xef,0x5a,0xfb,0xec,0x32,0x99,0x7f,0xa7,0x77,0x49,0x06, + 0xa5,0xfb,0x82,0x06,0x43,0x91,0xd4,0x2f,0x64,0x90,0x7e,0x03,0x95,0x9e,0xcb,0x0c, + 0xdf,0x38,0x83,0xec,0x36,0x99,0xf1,0x6b,0x88,0x01,0xaf,0x15,0xe8,0x94,0x0c,0xf1, + 0x8b,0xb9,0xbf,0xa9,0x0f,0xb9,0x9e,0x14,0x57,0x88,0x7e,0xb6,0x12,0xe0,0xee,0x48, + 0x1a,0x36,0xe4,0xcf,0x03,0x87,0x8c,0x4a,0x06,0x9a,0x57,0x81,0x50,0xf1,0x18,0xa8, + 0xa7,0xc2,0x34,0xaf,0x40,0x5c,0x1b,0xf9,0x57,0xda,0x3c,0x8b,0xdc,0x37,0x9e,0x24, + 0x43,0xa5,0xf6,0xf5,0x9a,0xea,0xf3,0x7a,0x07,0xcd,0x6b,0xfc,0xc4,0x47,0x21,0xc4, + 0xc7,0xc8,0x5a,0x07,0xd7,0x85,0x7a,0x89,0x3a,0xbf,0x8a,0xe6,0xc3,0x48,0xaa,0x55, + 0xe3,0x01,0x6c,0x91,0xa8,0x73,0xbb,0x10,0x31,0xa5,0x2e,0x57,0xa9,0xdb,0xe2,0x0b, + 0x4d,0x3b,0x08,0xf9,0x06,0xb2,0x8d,0x87,0x5c,0x2e,0x8b,0x1d,0x0c,0x37,0xb6,0x05, + 0xe2,0xc1,0x56,0xec,0x0c,0x6f,0xc4,0x43,0x3a,0x11,0x0f,0xa3,0xa4,0xd0,0x1b,0xf4, + 0x73,0xa5,0x10,0x74,0xd3,0xa6,0xd2,0x8d,0x1a,0xb9,0x3c,0x27,0x6e,0x76,0x2a,0x73, + 0x35,0x23,0x96,0xcb,0x80,0x9d,0x18,0x8c,0x50,0xae,0x46,0xba,0x81,0x83,0x80,0x47, + 0x3a,0x73,0x1c,0x88,0x63,0x12,0x4d,0xd7,0xb6,0x24,0x01,0xfd,0x0c,0x19,0x04,0xe0, + 0xc0,0x72,0x6f,0x24,0x60,0xb2,0xf5,0x0f,0x2a,0x0c,0x2d,0xf2,0xf8,0x0a,0x24,0x57, + 0x2b,0x21,0x42,0x00,0xbc,0x1a,0x68,0x60,0xb9,0xb2,0xaf,0x07,0x8a,0x6f,0x22,0x8d, + 0x01,0xf6,0xee,0xe2,0xea,0x12,0xe6,0x41,0x9b,0xd3,0x7e,0x41,0x75,0x29,0xc5,0x1a, + 0xea,0xbd,0x29,0x24,0x82,0xc0,0x1d,0x0e,0x28,0x17,0x5e,0x18,0x04,0xac,0x9e,0x33, + 0x81,0x2f,0xc4,0x4b,0xcd,0x06,0xf0,0x6d,0x63,0x03,0xfe,0x94,0xce,0x39,0x4d,0x07, + 0x63,0xe4,0x99,0xac,0xa8,0x9c,0x59,0xa3,0x99,0xa6,0x1b,0x7c,0x10,0x9a,0xd5,0x0b, + 0x19,0xc2,0xef,0x72,0x8d,0x56,0x55,0xb2,0xc7,0xc5,0x42,0x6b,0x50,0x43,0x95,0x11, + 0x0b,0x53,0xb0,0x78,0x25,0x84,0x59,0x3f,0xec,0x87,0x1c,0x5e,0x5c,0x7a,0xa0,0x23, + 0x6b,0xdf,0x9d,0xfc,0x0c,0xc4,0xa9,0x49,0x97,0xfc,0x49,0x55,0x7d,0xa7,0xef,0x1f, + 0x0e,0x94,0xda,0x87,0x74,0xf6,0x20,0x49,0x00,0x06,0xa6,0x33,0x9c,0xd2,0xb7,0xbe, + 0x17,0x8c,0xd3,0xe6,0x0f,0xc5,0x29,0xb7,0x7d,0xb4,0xba,0xa8,0xcb,0x94,0x4f,0x3f, + 0xe4,0x01,0x66,0xf0,0x2e,0x1c,0x9d,0xd2,0x26,0xf1,0xd6,0x0f,0xd0,0xd9,0x1e,0x1b, + 0x2f,0x89,0x8c,0x95,0x53,0x93,0xb9,0x9b,0x5c,0xbd,0xc6,0x01,0x35,0x01,0x15,0x6c, + 0x49,0x1d,0x70,0x75,0xe6,0xb8,0x60,0x2d,0x81,0x05,0x03,0x3e,0xc0,0x90,0x18,0x0b, + 0x69,0xac,0x2a,0xd1,0x12,0x06,0xd7,0x0f,0xbc,0xf0,0xad,0x5a,0x35,0x37,0x03,0xcd, + 0xbe,0x42,0xd3,0x17,0x59,0x8a,0x95,0x36,0x57,0x48,0x09,0x80,0x87,0xb4,0x72,0x88, + 0x55,0x3b,0xad,0x9d,0x2a,0x88,0x5c,0xf2,0xcc,0x89,0xa0,0x45,0x11,0xcb,0x08,0x8f, + 0x9e,0x79,0x05,0x45,0xb5,0x3b,0x19,0xc4,0x78,0x8d,0xf8,0x5b,0x34,0xc0,0x6f,0xaa, + 0xf3,0x07,0x8e,0x89,0x86,0x47,0x10,0x84,0x06,0x48,0x84,0x0a,0xb2,0x8d,0x9f,0xbe, + 0xf5,0x81,0x98,0x7a,0x4d,0xe0,0x63,0x37,0x36,0xdc,0xc9,0xd1,0xa0,0x7d,0x6c,0xfe, + 0x5f,0xc3,0xdc,0x12,0x67,0x2a,0x78,0x37,0x5f,0xb3,0xd3,0x6e,0xb7,0xb7,0x21,0xc7, + 0x56,0xc7,0xb2,0xb6,0x3b,0x6d,0x6b,0xcb,0xfc,0x0f,0x63,0xbc,0xc8,0xee,0x8c,0xd1, + 0xdd,0x28,0x40,0x19,0x95,0xe8,0xaf,0xd2,0x0d,0x54,0x65,0x6e,0xb2,0xe2,0x2d,0x8b, + 0xdc,0x34,0xaa,0x60,0x00,0x92,0x77,0x46,0xa6,0x04,0x7d,0x41,0x4e,0xd5,0x8a,0x2d, + 0x1c,0xa4,0xc0,0x40,0x36,0xb7,0xf4,0xaf,0x5b,0x9b,0x66,0xed,0xc9,0x4a,0x64,0x2d, + 0xf1,0xc4,0xf4,0xd9,0x00,0x16,0xa9,0xc5,0x2b,0x3b,0xb2,0xd5,0x96,0x40,0xc8,0x9c, + 0xa3,0x42,0x81,0x40,0xa8,0x4c,0x88,0x1d,0x30,0x22,0xa9,0x24,0xd2,0x8f,0x5e,0x26, + 0xf3,0xe2,0xf1,0x0e,0x46,0xdc,0x2c,0xf4,0x0b,0x52,0x9e,0xd4,0xb3,0x48,0xac,0x4b, + 0x9d,0x54,0xd7,0xb5,0x98,0x4c,0x05,0xa8,0xae,0x8b,0xab,0xee,0xf1,0xe5,0x0f,0xcd, + 0x45,0xab,0xf1,0xf3,0xfa,0x99,0x42,0x7c,0x5e,0xd4,0x25,0xc8,0x88,0xd8,0xc0,0x48, + 0x12,0xc5,0x41,0xbf,0x54,0x80,0x23,0x1d,0x43,0xb9,0x53,0x85,0x5d,0x15,0x37,0x63, + 0x89,0x2b,0x64,0xd0,0xe5,0x0c,0x0d,0x36,0x0d,0xbc,0x41,0x2d,0xcd,0x88,0xde,0x6f, + 0x6c,0xa4,0x0e,0xfb,0xe3,0xa4,0x0e,0x87,0xa1,0x10,0x83,0x9a,0x0f,0x5a,0x9d,0xfe, + 0xe7,0x93,0xd7,0xef,0x3e,0x7d,0x3b,0xfd,0xfc,0xe6,0xec,0xcd,0xf9,0x59,0x75,0x1c, + 0x31,0xac,0x79,0x04,0xdc,0x08,0x47,0xfe,0xe6,0xb7,0x26,0xb7,0xc3,0x70,0x88,0x2d, + 0xe8,0xa0,0x35,0x1f,0xf8,0x44,0xa3,0x04,0x48,0xe7,0x88,0xc4,0x82,0xde,0xcd,0xad, + 0x5e,0x53,0x8e,0xa5,0xb0,0x22,0x8f,0x4d,0xec,0xbf,0xd9,0x4b,0xd5,0xc2,0x78,0x68, + 0x3c,0x14,0x85,0xfa,0xd2,0xe2,0xe4,0x1e,0x87,0x83,0x4f,0x43,0x0c,0xeb,0x80,0x27, + 0xb6,0x69,0x53,0x55,0x27,0x86,0x25,0xd6,0x62,0x1c,0xff,0xdd,0x4f,0xfd,0x61,0xe0, + 0x0d,0x9e,0xfd,0xd0,0x94,0xfa,0x38,0x5d,0xaf,0x26,0x22,0x44,0xa5,0xa5,0xd3,0x1d, + 0x61,0xca,0x6c,0xd5,0x4c,0x1b,0x19,0x92,0xd8,0xe1,0x51,0x1b,0x56,0xa9,0xaa,0x5e, + 0x2b,0xd7,0x0a,0x4b,0x5a,0x8b,0x70,0xab,0x19,0x02,0xc1,0xe9,0x1c,0x9b,0xc2,0xda, + 0x18,0xd6,0xa9,0xb4,0x3b,0x2e,0x9d,0x5f,0x6a,0x86,0xce,0x4b,0x7d,0xe7,0xaf,0x28, + 0x2d,0xd8,0xa4,0xc6,0x64,0x5b,0x68,0x90,0xbc,0xe8,0x58,0x98,0x6c,0x9c,0xde,0x7f, + 0x3a,0x47,0xad,0xfc,0xfb,0x37,0x1f,0x07,0x90,0xeb,0xca,0x0b,0x7b,0xbb,0x2f,0xec, + 0x45,0x8a,0xe1,0xe7,0xe6,0x5e,0x6f,0xa7,0xa3,0x0c,0x97,0x7a,0xfb,0x3b,0x36,0x3b, + 0xb4,0xe0,0x13,0x85,0x50,0xe8,0xbd,0xdc,0xb3,0xdd,0xc5,0xd8,0x47,0xb3,0x70,0x48, + 0x7c,0x60,0x20,0x9e,0xfe,0x72,0xfe,0xed,0xfc,0x9f,0xa7,0x6f,0xce,0x06,0x17,0x17, + 0x6d,0xdb,0xfc,0xfc,0xe6,0xbf,0xcc,0x4b,0xfb,0xa2,0x83,0x4f,0x67,0xa7,0x9f,0x3e, + 0x9e,0xbd,0xc1,0xd7,0xae,0x6d,0x9e,0xff,0x0a,0x2d,0x9f,0xfd,0x0d,0xdf,0x76,0x6c, + 0xf3,0xe4,0xd5,0x2f,0xf8,0xb4,0x0b,0x4f,0xaf,0xff,0xfe,0xe6,0xf3,0x39,0xbc,0x34, + 0x2e,0xf6,0x6c,0xf3,0x6f,0x9f,0x4f,0xbf,0x41,0x4e,0xfc,0xb6,0xcf,0x6f,0xaf,0x4f, + 0xce,0x4f,0xf0,0xf5,0x05,0x64,0xfd,0xf8,0xe9,0xe3,0x37,0xd1,0xc0,0x81,0x6d,0x9e, + 0x9e,0x9c,0xff,0x8c,0x8f,0x2f,0xa1,0xf2,0xcf,0x27,0xaf,0xde,0x50,0x1d,0x1d,0xe8, + 0xc3,0x87,0x2f,0xef,0xcf,0xdf,0x9d,0x9e,0x50,0xad,0x17,0x1d,0xe8,0xca,0xab,0x4f, + 0x1f,0xcf,0x3f,0x7f,0x7a,0x4f,0xaf,0xd0,0x15,0x44,0x4c,0x18,0xd9,0x98,0xde,0x77, + 0x4a,0xef,0xbb,0x85,0x77,0xa8,0x10,0x7a,0xf5,0xf9,0xe4,0x1f,0xdf,0x5e,0x7d,0x39, + 0x3b,0xff,0xf4,0xc1,0xbc,0xbc,0xcc,0x87,0xfd,0xf1,0xe4,0xc3,0x9b,0xd7,0x12,0xcf, + 0x46,0x09,0x48,0xdb,0x5e,0x13,0x75,0x22,0x30,0x6b,0x0a,0x2a,0x35,0xeb,0x84,0x56, + 0x49,0x7c,0xd1,0xb9,0x44,0x4d,0xb4,0x6a,0xcb,0x52,0x35,0x5e,0xe0,0x37,0x40,0x28, + 0x8a,0x55,0xf6,0x0a,0xa8,0x4b,0xd3,0xba,0x1c,0xc4,0x17,0xed,0xcb,0x02,0xd6,0x4f, + 0x68,0x4b,0xfd,0xe0,0xa6,0x57,0x4d,0x44,0x27,0x81,0xf6,0x99,0x64,0x20,0x30,0x2d, + 0xd7,0xd0,0x50,0x0e,0x07,0xbe,0xcc,0x9b,0xbc,0xc7,0x65,0xb8,0xc7,0x99,0xf7,0xf7, + 0xf4,0xeb,0x06,0x81,0x62,0xb5,0xdb,0xb7,0x6f,0xe1,0xbf,0x3c,0x0f,0xb9,0xfc,0xa9, + 0x8f,0xe2,0xb0,0x15,0x1a,0x1d,0xb4,0xed,0xe8,0x8a,0x75,0x83,0x0d,0x90,0x24,0xe3, + 0xc0,0x07,0x84,0xb3,0xeb,0xe8,0x1b,0x46,0x4f,0x93,0xbd,0x83,0x22,0xf8,0x2a,0x7a, + 0x62,0x87,0x48,0x56,0xc8,0xf2,0xe8,0x5f,0x17,0xed,0xd6,0xcb,0xcb,0xad,0x1f,0x84, + 0xa1,0x10,0x64,0xb4,0xac,0x90,0x77,0xca,0x77,0x21,0xbd,0xdb,0xb0,0xa1,0xf5,0xd5, + 0x81,0x00,0x24,0x14,0x21,0x84,0x44,0x43,0x41,0x10,0x8a,0xe6,0xd0,0xac,0x64,0xe5, + 0xb3,0x87,0xf0,0xb0,0x7d,0x7f,0x1f,0x1e,0x75,0xf6,0x80,0xfc,0x5e,0x09,0xe5,0x1e, + 0x0f,0xf3,0xa1,0x81,0x03,0xbc,0x1f,0x34,0x3b,0x87,0x87,0xa1,0x60,0x50,0x04,0x00, + 0xa2,0xab,0x63,0xfc,0xd6,0x93,0x50,0x7a,0x28,0xcf,0xc7,0x39,0x00,0xba,0x89,0x59, + 0x98,0x6d,0x21,0x48,0x0d,0x06,0x9c,0x5d,0x40,0x91,0xc0,0xdd,0xd7,0xbf,0xca,0x0f, + 0x04,0x6b,0x06,0x71,0xb4,0xc8,0x06,0x78,0x48,0x52,0xb4,0xaa,0xe8,0xec,0x13,0x8b, + 0x26,0x8a,0x6e,0x60,0x07,0x7d,0xcb,0x82,0xbc,0x4e,0xbc,0x48,0x67,0x4d,0x5f,0xeb, + 0x28,0xa4,0xb1,0x0d,0x84,0x5d,0x22,0x22,0xa9,0x64,0xd9,0x7e,0xce,0xe6,0x41,0xd3, + 0xb7,0x27,0x36,0xca,0x45,0x36,0x39,0x8d,0xda,0x78,0xc6,0xab,0x04,0x89,0xcd,0x82, + 0xeb,0xa9,0x91,0x4e,0x5a,0x9b,0x5b,0x13,0xd8,0x34,0xa5,0xcf,0xe9,0xe6,0x16,0xfd, + 0x6e,0x6d,0xd6,0x7a,0xbb,0x6e,0x6e,0xe1,0x2f,0x64,0x2f,0xc4,0x0e,0xd8,0xdc,0xf2, + 0xb7,0x36,0x1d,0xae,0xe7,0x4f,0xdc,0x48,0xb2,0xb9,0xd5,0xc4,0xae,0x1e,0x6f,0x56, + 0x83,0x0a,0x6c,0x6e,0xe1,0x2f,0x76,0x0a,0x0d,0x3e,0x37,0x7b,0x9b,0x9b,0x96,0x7a, + 0x29,0x9a,0xe7,0x68,0x9c,0x29,0xc3,0x42,0x60,0x29,0x88,0x86,0x5e,0x3a,0xc8,0x57, + 0xf0,0xdc,0x8d,0x8b,0x67,0x5a,0x12,0x3c,0x7c,0xe9,0xab,0x68,0x3d,0xbe,0x5a,0xef, + 0x1b,0x1f,0x4f,0x10,0x26,0x3e,0x8e,0x5b,0x48,0x07,0x9b,0x5b,0xb8,0xa8,0x11,0xa0, + 0xec,0xaa,0x79,0x24,0x13,0x0c,0x7c,0xe8,0x5c,0x62,0xb7,0xa5,0x29,0x3b,0x03,0xbc, + 0x64,0xda,0xb2,0x6a,0x9a,0x18,0x15,0x35,0xa7,0x64,0x8e,0xe4,0x03,0xbd,0x52,0x31, + 0x4e,0x36,0x1b,0x50,0xbb,0xde,0x5b,0xd8,0xdf,0xc6,0x5e,0x58,0x37,0x59,0xb2,0x36, + 0x2a,0xa2,0x35,0x15,0x5f,0x4d,0x11,0xda,0x04,0x2d,0x05,0xe1,0x4a,0x1e,0x17,0xaf, + 0xef,0x5c,0xe3,0x18,0x18,0xa0,0xb5,0xb0,0x00,0x10,0xb0,0x0b,0x02,0x46,0x3d,0x5c, + 0x23,0x18,0x3b,0x45,0x79,0x86,0x50,0xcd,0x7f,0xa0,0xa2,0xdc,0x65,0xb9,0x58,0x95, + 0x6e,0xc7,0x0d,0x05,0x16,0xf3,0x92,0x8b,0x48,0x79,0x24,0x8c,0x5c,0xff,0x98,0xf9, + 0x23,0x0c,0x9f,0x74,0x87,0xdb,0x2f,0x43,0x14,0xaf,0x78,0x33,0x38,0xe2,0x94,0xb4, + 0xd9,0xf7,0x53,0x11,0x5d,0xca,0x31,0xb4,0xe0,0x7f,0xc3,0x28,0x93,0x91,0x97,0xf8, + 0xf6,0x4e,0xf7,0x46,0x94,0x4b,0xfb,0x32,0x3a,0x14,0xa6,0x2b,0x5d,0x12,0xd5,0x1c, + 0x52,0x50,0x2b,0x9e,0x03,0x6f,0xec,0x18,0xe4,0x3c,0x88,0x61,0x06,0x30,0xfd,0x0e, + 0x71,0x55,0x56,0x22,0x03,0x5c,0x15,0x62,0x67,0x14,0x51,0xbe,0x46,0x20,0x2b,0xf0, + 0xee,0x3f,0x0c,0x4c,0x39,0xdd,0xb5,0x9c,0xb1,0x64,0x07,0xfd,0xc1,0x2c,0xe7,0xc7, + 0xeb,0xcf,0x17,0xb6,0x2d,0xc0,0x60,0xa6,0x65,0x68,0xdf,0x02,0x05,0x72,0x41,0x1c, + 0x49,0x90,0x10,0x8e,0xf0,0x5b,0xd1,0x90,0x07,0x69,0xa2,0xb6,0xb7,0xcd,0x14,0xdf, + 0x87,0x59,0xab,0xb6,0xc4,0xa2,0xeb,0xf9,0xea,0xaa,0xed,0xf8,0x08,0x4d,0xc7,0x46, + 0xc3,0x5c,0xdb,0xa3,0xe8,0xa7,0xda,0x62,0xe0,0x2b,0xb5,0x84,0xfb,0x8c,0x65,0xc1, + 0xd6,0xdc,0xce,0x2d,0xb7,0x00,0x3d,0x06,0xd5,0xe6,0xd1,0xca,0x8a,0x30,0x47,0x9c, + 0xf5,0x2f,0xe6,0x16,0xfc,0x2b,0x9a,0x2c,0xeb,0xf4,0xff,0x18,0x29,0x3f,0x23,0x8d, + 0xd9,0x53,0xc4,0xff,0xd8,0x94,0x81,0xc9,0x24,0x12,0x99,0xbd,0xf2,0x5e,0x22,0xba, + 0x81,0xb7,0x13,0x17,0x00,0x39,0x11,0x4d,0xe3,0x07,0x0b,0xff,0xac,0x96,0x23,0xb4, + 0x09,0x2b,0x49,0xbc,0x65,0xab,0x45,0x0d,0x3f,0x28,0xa6,0x85,0x37,0x86,0x5d,0x82, + 0xe6,0x69,0xa9,0xed,0xfe,0x7f,0xc1,0x7c,0xa0,0xac,0xa0,0xa6,0xc4,0xca,0xf7,0xdc, + 0xfa,0x29,0xc9,0x67,0x43,0x50,0x27,0x13,0xaa,0x57,0xe8,0x6a,0x5f,0x0f,0xca,0x40, + 0xeb,0x97,0xa5,0xd2,0x95,0xa2,0x28,0xa0,0xf5,0x4c,0x2a,0xfb,0x8a,0xd2,0xd5,0x15, + 0xc9,0x55,0xb5,0x7a,0x0c,0xdd,0x6a,0x28,0x57,0x81,0xa5,0x4a,0xab,0x35,0x8b,0x52, + 0x34,0x66,0xc7,0x04,0xc2,0x74,0x7c,0x2f,0x60,0xfa,0x0c,0xf6,0x1c,0x3e,0x74,0x5e, + 0xab,0x8e,0x61,0x76,0x00,0x4d,0x98,0x37,0x6b,0xa3,0x31,0x60,0x60,0xbb,0xd0,0x33, + 0x5a,0x2a,0x92,0xc2,0x66,0x5f,0x33,0xba,0xa8,0x65,0x3f,0xb1,0xb6,0xad,0x4a,0x75, + 0x20,0x1e,0xa7,0xa3,0x66,0x4c,0x67,0x74,0x16,0xee,0x4a,0xa5,0x84,0xbc,0x01,0x84, + 0x51,0x7d,0x1d,0xa3,0x45,0x9a,0x45,0x40,0x4a,0xf9,0x97,0xee,0x34,0xce,0x7b,0x85, + 0x03,0xc6,0x22,0xba,0x3b,0x24,0x52,0x02,0xa6,0xda,0xf8,0x28,0x71,0xa6,0x42,0x7d, + 0xc9,0x11,0x6f,0x78,0x84,0x30,0x36,0x28,0x0b,0x3b,0x59,0xd5,0x11,0x6a,0x2d,0x18, + 0x47,0xe1,0xb6,0xd3,0xba,0xa0,0x5e,0xbc,0xc1,0x31,0xa8,0x70,0xbc,0x34,0xa8,0xcd, + 0x52,0x28,0x0a,0x68,0xa0,0xcc,0x33,0x99,0x24,0x3a,0x01,0x6b,0xa5,0xce,0x80,0x40, + 0x38,0x19,0x8d,0xd0,0xf7,0x4a,0x7e,0x31,0xad,0x9a,0x62,0x52,0xd0,0x82,0xef,0xe4, + 0x24,0x6d,0x9b,0x5f,0xf2,0x94,0xda,0x12,0x5a,0x03,0xda,0xe3,0x69,0xfe,0x58,0x5b, + 0x8a,0x05,0xb7,0xbc,0x15,0x8e,0x29,0x45,0x28,0xb9,0xb2,0xa1,0x28,0xc1,0x6f,0x22, + 0xee,0x10,0x34,0xc1,0xef,0xb5,0x79,0x49,0x1a,0xcc,0x6b,0x3f,0xa7,0x6b,0xed,0x32, + 0x0f,0x38,0x34,0x0c,0xef,0x0a,0x65,0x73,0x77,0xec,0xb4,0x67,0x2c,0xf1,0xd8,0xe9, + 0xc1,0x58,0xb2,0xa3,0x1d,0x3c,0x20,0x05,0xc4,0x1f,0x84,0xd4,0x43,0x6d,0x03,0x52, + 0xca,0xcc,0xdb,0xf8,0xcf,0x7f,0x9c,0x1b,0x5a,0xaa,0xba,0x69,0xad,0xa5,0x5d,0x93, + 0x8c,0xd0,0xdd,0x46,0x28,0x19,0x6c,0xde,0xd3,0xd8,0xaa,0x61,0xf1,0x1a,0x05,0x66, + 0x10,0x21,0xa2,0x19,0x18,0x22,0x86,0x96,0x24,0xee,0xb2,0x9a,0x13,0x4b,0xa0,0xa2, + 0xa3,0x51,0xbb,0xa6,0x79,0xab,0xc3,0x4a,0x8b,0x3b,0x85,0x20,0x43,0x3a,0x8e,0x0b, + 0x63,0xd4,0xea,0x0e,0xc8,0xe8,0xf8,0x26,0xa8,0xdb,0x71,0x72,0x72,0x56,0x41,0x5f, + 0xac,0x8f,0x35,0x1f,0x80,0x4e,0x03,0x59,0x89,0x3c,0x19,0xc1,0x0f,0x63,0x6f,0x42, + 0x02,0x61,0xff,0x11,0xf2,0x80,0xd2,0x29,0x2d,0x7a,0xd8,0x9e,0x68,0xed,0x63,0xc1, + 0x58,0x52,0x5f,0xb2,0x25,0x1c,0x88,0xcf,0x72,0xc1,0x1f,0xcb,0x87,0x1e,0xc6,0x85, + 0x39,0x86,0x7f,0x6c,0x73,0xd8,0x13,0xf2,0xa3,0xd8,0x46,0x67,0xd1,0xcd,0x07,0xd8, + 0xc2,0x84,0xe6,0x81,0x6b,0x1a,0x0c,0xe4,0x6a,0x52,0x6a,0x08,0xf5,0x01,0x53,0x70, + 0x42,0x41,0x50,0x55,0x69,0xa2,0x21,0xbb,0xa1,0x34,0x15,0x6b,0xb3,0xab,0x55,0x53, + 0x53,0x85,0xd0,0x6f,0xd4,0x54,0x0d,0xc8,0x5f,0x4d,0x66,0x25,0x48,0x25,0x59,0xa9, + 0x44,0x2a,0x5f,0x78,0x47,0xea,0xe1,0x98,0x9e,0x49,0x51,0xfa,0xa1,0xdf,0x28,0x28, + 0xa6,0x18,0x24,0x35,0x9b,0xd2,0x44,0x90,0xfe,0x9b,0x7a,0xce,0x03,0xd8,0x7c,0x73, + 0x6b,0x42,0x28,0x74,0x63,0xdd,0xac,0x31,0xe3,0xe6,0x06,0x2e,0x26,0x97,0x6a,0xab, + 0x5f,0xa3,0x00,0x16,0x1a,0xa8,0x47,0x34,0xc0,0xf6,0xbc,0xa2,0x03,0xde,0xd8,0x68, + 0xce,0x0b,0xc7,0x38,0xf5,0x5c,0x21,0xa3,0x1d,0x9b,0x9d,0x34,0x2e,0xf4,0x53,0x05, + 0x5b,0x3b,0x3d,0xa8,0xb1,0x88,0xa3,0x7d,0xb5,0xee,0xd0,0x01,0x3f,0xd8,0x64,0xad, + 0x62,0xad,0x52,0xcd,0xc6,0x12,0x94,0xb4,0x3a,0x0b,0x47,0x42,0x82,0x13,0x25,0x56, + 0x04,0xd7,0x61,0x1d,0xf3,0x53,0xa8,0x89,0x19,0xa1,0xbe,0x50,0x41,0xae,0x83,0x24, + 0x5a,0xcf,0xd5,0x01,0x72,0x98,0xc3,0x51,0x76,0xe4,0xb8,0x92,0x22,0x55,0xc6,0x24, + 0xca,0x5c,0x9a,0x16,0x39,0x06,0xb2,0xe9,0xfd,0xb0,0x48,0x2c,0xd0,0x7b,0x77,0x30, + 0xd4,0x7a,0x88,0x7f,0x85,0x3a,0xa6,0x87,0xe7,0x30,0xa4,0x70,0x49,0x41,0xae,0xb4, + 0x81,0xfb,0x14,0x2f,0x9d,0x4b,0xa9,0xf6,0xd1,0xc8,0xd6,0xf0,0x09,0x1c,0xfa,0x9f, + 0xe5,0xf9,0x34,0x16,0x1c,0x5a,0x47,0x10,0xae,0x61,0x37,0x4b,0xb6,0x5b,0x78,0x34, + 0xf7,0x36,0x42,0x0f,0x0b,0x3b,0xba,0xb2,0xe7,0xa9,0x54,0xb8,0xd7,0x9e,0xf3,0x61, + 0xcf,0xe1,0x43,0x01,0x56,0xb0,0x21,0x0d,0xd6,0x9f,0xf9,0x41,0x0e,0x0b,0xfe,0x69, + 0xa7,0x7e,0x6c,0x7b,0xa5,0xe6,0x99,0x95,0x7c,0xc2,0x45,0xb9,0x89,0xc1,0x84,0xe8, + 0xc0,0x78,0xc4,0xeb,0x0f,0x2f,0xa9,0x13,0xd7,0x6c,0x99,0x5b,0xcd,0xe8,0xea,0xd8, + 0x8c,0xae,0x60,0x12,0xc8,0x52,0xad,0x3f,0x2a,0x0a,0x06,0xe9,0xb4,0xdf,0xa8,0xe9, + 0x0c,0xf1,0x2a,0x68,0x0f,0x19,0xc7,0x5e,0x38,0x7e,0x35,0x03,0xc6,0xb2,0x39,0x2a, + 0x29,0x70,0xdc,0x6b,0x8f,0x21,0x95,0x36,0xa1,0xc6,0xc4,0xb5,0x89,0x9b,0x26,0x02, + 0xeb,0x65,0x73,0x97,0xce,0xa3,0x1b,0xf5,0xda,0xef,0x14,0x1d,0xc5,0xd4,0x8c,0xb8, + 0xb6,0x2c,0x18,0xbb,0x83,0xed,0x7c,0x75,0xb2,0x0e,0xce,0xb5,0x8e,0x55,0xc9,0x0b, + 0xf7,0x52,0x29,0x03,0x8f,0xdb,0xbd,0x8e,0xd5,0xeb,0x0a,0xfc,0x1b,0x56,0x0b,0x0e, + 0xf5,0x82,0xc3,0x9a,0x82,0xf2,0xa8,0x1b,0x50,0x66,0x78,0x7f,0xef,0x3a,0x74,0xc5, + 0xba,0xf7,0x0a,0x63,0x51,0x27,0x1e,0x14,0x27,0x35,0x47,0x05,0x7f,0xae,0xac,0x25, + 0x8f,0x0f,0xcf,0xf6,0xf4,0x83,0x3f,0x69,0x01,0x1a,0x8d,0xef,0x06,0x98,0xa5,0xc7, + 0xd9,0xf8,0x0c,0x9b,0x41,0xb4,0xb1,0x41,0x3f,0x0e,0xfb,0x07,0x5b,0xe8,0x05,0xa6, + 0x47,0xa5,0x0b,0x05,0xe4,0xf2,0x0f,0x14,0x94,0x2d,0x4f,0xb7,0xc8,0x8e,0x96,0x4b, + 0x0b,0xa5,0x27,0xd4,0x5d,0x80,0x31,0x67,0x14,0xe7,0x0b,0x28,0xd8,0x6d,0x6c,0x3c, + 0xd3,0x4a,0x29,0xa5,0xfc,0x47,0x21,0xed,0xe1,0x75,0xe6,0x68,0xe4,0x63,0x29,0xad, + 0x23,0x0e,0x82,0xbd,0xce,0x94,0xe3,0x56,0x5f,0xd4,0x80,0x89,0xec,0x88,0x96,0x1f, + 0x24,0x90,0x8d,0x6b,0xc9,0xbe,0xb5,0xa1,0x59,0x35,0x8a,0xc3,0x7f,0x76,0x3e,0xab, + 0x1c,0x11,0xc4,0x51,0x10,0x7c,0xf6,0x52,0xb4,0xa0,0x1d,0x0d,0xed,0x36,0xfc,0x0f, + 0x2b,0xb0,0xa9,0x11,0x5b,0x0c,0x06,0x6d,0x47,0x1f,0x31,0xa6,0x1e,0x14,0x8d,0xa9, + 0xeb,0x3a,0xc6,0xaa,0x55,0x12,0xf6,0xac,0xd1,0x90,0x14,0xe4,0x36,0xa5,0xe5,0x23, + 0xa7,0x1a,0x35,0xe7,0xc3,0xf6,0xfd,0xbd,0xfe,0xde,0xd9,0x41,0x35,0xc5,0xda,0xea, + 0xb9,0x0a,0x98,0x09,0x56,0x68,0x3c,0x6e,0x78,0x50,0xda,0xbf,0xa0,0x19,0x2e,0xae, + 0x51,0x19,0xf6,0xce,0x56,0x43,0xbd,0xbf,0x37,0x13,0x8f,0x23,0x85,0x9b,0x6a,0x2f, + 0xad,0x1d,0x16,0xed,0x41,0x3c,0xdd,0x4d,0xbd,0x38,0x85,0x94,0xc9,0xeb,0xd8,0xe2, + 0x1e,0x1f,0x9b,0x3d,0x20,0x17,0xf4,0x88,0xde,0x6c,0x4a,0xaf,0x27,0xaa,0x2f,0x00, + 0xe6,0xa5,0x04,0x3f,0xa3,0xc4,0x80,0x91,0xe2,0x69,0xb3,0xa9,0xa0,0xfd,0x38,0x24, + 0xd7,0x0e,0xc9,0x3c,0x09,0xd9,0xdb,0x87,0x99,0x05,0x34,0x0a,0x1f,0x7a,0x88,0xd1, + 0x64,0x01,0x9d,0x9b,0xca,0xb3,0xff,0x72,0x70,0x67,0x16,0x46,0x54,0xed,0x29,0x83, + 0xb9,0xd8,0xd5,0xb2,0x52,0x42,0xc5,0xe5,0x11,0x3c,0x11,0x2c,0x51,0xb1,0x59,0x0c, + 0x2a,0x2b,0x59,0x12,0xbb,0xea,0x5a,0xce,0x75,0x1e,0x44,0xba,0x6e,0xc6,0x6f,0x92, + 0x64,0x40,0x57,0x58,0x9e,0xd2,0x33,0x4c,0xbb,0x56,0x33,0xb3,0x05,0x9c,0x8b,0xf0, + 0xaf,0xde,0xb3,0x8c,0xcf,0x2c,0x2f,0x29,0x44,0x05,0x6c,0xf9,0xb8,0x69,0x30,0x9c, + 0x44,0x49,0x7d,0xf0,0x3a,0xe9,0x2e,0xfb,0x8a,0xea,0x60,0x81,0x1d,0x23,0xb5,0xfd, + 0x71,0xe0,0xa5,0x36,0x6e,0x8f,0x63,0x09,0x1d,0x32,0x89,0x47,0x3f,0xbc,0x65,0x03, + 0xb3,0x0c,0xf0,0x0f,0x7a,0xee,0x50,0xd6,0x01,0xfd,0x25,0x47,0x9e,0xfc,0x50,0xc2, + 0xf5,0xf1,0xb0,0xf0,0xbb,0x66,0x9c,0x5a,0x2c,0xcc,0xb8,0xcb,0x96,0x57,0x38,0x03, + 0x63,0x1b,0xc7,0xcf,0x6e,0x64,0xe2,0x96,0x07,0xe3,0xc6,0x4d,0x0d,0xd8,0x7c,0x33, + 0x9a,0x78,0x56,0x8c,0x46,0xe8,0x59,0xee,0x4f,0xee,0x60,0xf7,0x23,0xa4,0x57,0x4e, + 0x14,0xb4,0xfb,0xeb,0x01,0x51,0x49,0x13,0x8a,0xe8,0x82,0x26,0x43,0x65,0xcb,0xa5, + 0xed,0x84,0x40,0x72,0xcc,0xd8,0x0e,0x8b,0x24,0xc4,0x8b,0x20,0xbe,0x7c,0x7e,0x87, + 0x7b,0x05,0x6c,0x29,0xb0,0x07,0xf3,0x02,0xb0,0x97,0xc2,0xeb,0xb9,0x07,0xc4,0xa3, + 0xfd,0xb0,0xd2,0xc1,0x99,0x72,0x3f,0x53,0xcb,0x86,0xc1,0xa3,0x93,0x21,0xb6,0x9e, + 0x27,0xb2,0x86,0x1b,0x30,0xba,0x7b,0xd0,0xb6,0x53,0xe3,0x79,0x5d,0xc6,0x64,0x9e, + 0x2f,0x7d,0xdd,0xa9,0xe9,0x7a,0xb0,0xa1,0x5b,0x75,0xed,0xa0,0x58,0x30,0x26,0x09, + 0x89,0x3a,0x48,0x55,0x1c,0xee,0x3e,0xad,0xc5,0x1c,0x49,0xb6,0x3a,0x2b,0xd0,0xe4, + 0xc1,0x7e,0xa1,0xb7,0x5b,0x18,0x2e,0xa2,0xdf,0x23,0x78,0x41,0x4e,0xab,0x50,0x26, + 0x1d,0xb4,0x6d,0x42,0x73,0x8e,0x49,0x22,0x3f,0x13,0x3c,0xb1,0x37,0x6c,0x33,0x5e, + 0xe3,0xc6,0x22,0xcf,0xfd,0xa2,0xab,0xc1,0xf6,0xbf,0x3e,0xfd,0xb2,0xed,0x33,0x83, + 0xe0,0x67,0x14,0x3f,0x41,0x98,0xdb,0x3c,0x8b,0xae,0x2c,0x6a,0x65,0x6b,0x8b,0xdf, + 0x47,0xc3,0x8d,0x0d,0xf4,0xe9,0x56,0xe3,0x00,0xe1,0xc2,0xa7,0x6d,0x96,0x56,0xb1, + 0x1a,0x1d,0xe4,0x90,0x5c,0x00,0x7f,0x46,0x56,0x43,0x7d,0x96,0x69,0x56,0xd9,0x54, + 0x48,0xa4,0x53,0x5b,0xfc,0x8c,0xb3,0xcd,0xcb,0x98,0xa7,0x61,0x9b,0xb7,0xec,0x4a, + 0x77,0x2d,0x1d,0x08,0xbc,0xdf,0x3e,0x48,0xe1,0x68,0x45,0xe4,0x8c,0x4d,0x36,0x2a, + 0x30,0x34,0x35,0x24,0xb7,0x29,0x34,0x00,0xc2,0x42,0xa9,0xc8,0xdb,0x22,0xe7,0xf8, + 0xe9,0x17,0xb3,0x27,0x1b,0xce,0xa3,0x4d,0xfc,0x0b,0x28,0x4a,0x33,0x4a,0xac,0xe3, + 0x8b,0x9e,0x7d,0x79,0xfc,0x35,0xfd,0x71,0xdb,0xb7,0x79,0xcf,0x78,0x10,0x36,0x06, + 0xda,0x8d,0xae,0xd6,0x6a,0xa9,0x50,0x1b,0x88,0x25,0xd7,0x3f,0x30,0xe7,0xb0,0x50, + 0x74,0x24,0xa5,0x59,0xb1,0x98,0x06,0xf0,0x0c,0x99,0xc8,0x10,0x65,0x1c,0x03,0x43, + 0xed,0x64,0xb0,0xac,0x53,0x0f,0xd6,0x38,0x89,0x64,0xa6,0x76,0x48,0x9b,0x28,0xde, + 0xa7,0x51,0x30,0x2d,0x44,0x99,0x54,0x04,0x5b,0x4d,0xc8,0x57,0xe1,0x9b,0x1f,0x37, + 0x8e,0x0d,0xf3,0x5c,0x06,0x3f,0xc6,0x23,0x2e,0x6f,0x5c,0xbd,0x6f,0xdd,0x31,0xce, + 0x55,0xdc,0x64,0xba,0x93,0x98,0x03,0xd1,0xe0,0xe9,0xc9,0x9d,0x71,0xcd,0xb6,0x16, + 0xc6,0x0d,0x30,0xcd,0x14,0xba,0x48,0x5c,0xb8,0xee,0x18,0x9f,0x3d,0x41,0x70,0xd8, + 0xb7,0x35,0x46,0x8b,0x17,0x8c,0x04,0x15,0xb8,0x31,0xc8,0xf4,0x7c,0x66,0xe3,0x66, + 0xd2,0x55,0x56,0x5c,0xd7,0x8e,0x11,0x81,0xf9,0x3e,0x06,0x6e,0xc4,0x31,0x1b,0x3d, + 0xad,0x8b,0x7e,0x2a,0x83,0x27,0xe1,0xd6,0x47,0x17,0x3a,0x51,0x77,0x91,0x46,0xb2, + 0xe7,0xad,0x80,0x54,0xa1,0xfd,0x52,0x53,0x34,0x08,0xd5,0x10,0xd5,0x0b,0x1f,0xd1, + 0x2b,0x84,0xc3,0x38,0xb7,0x60,0xfa,0xfc,0x29,0x82,0x42,0x75,0xc2,0x6e,0xf0,0x7e, + 0x69,0x9e,0x89,0xea,0x45,0x93,0x17,0x9f,0x7e,0xb9,0x24,0x02,0x2c,0xa2,0x06,0x99, + 0xb6,0x02,0xad,0xcd,0x89,0x67,0xd8,0x0b,0x90,0x99,0x93,0x02,0x8b,0xa1,0x6f,0xec, + 0x67,0xaa,0x26,0x9c,0xc5,0xdc,0xc2,0xe4,0x2c,0x9a,0x64,0x6c,0xa9,0xf7,0x1d,0xec, + 0x60,0x95,0xb6,0xae,0x63,0xf1,0xda,0xbb,0xb5,0x25,0x80,0xc4,0x1d,0x76,0xdb,0x4f, + 0xa7,0x87,0x40,0x09,0xd7,0x6d,0x9b,0xf5,0xf4,0xb0,0xcc,0x6c,0x94,0x07,0xbe,0x14, + 0x1e,0x72,0x78,0x0c,0x60,0xb0,0x8d,0xbd,0xbc,0x41,0xee,0x46,0x04,0x26,0x03,0xe1, + 0x73,0x38,0xf4,0xe8,0x5c,0x8e,0x49,0x22,0x49,0xab,0x69,0xe3,0xfb,0x4c,0x71,0xff, + 0xb0,0xc9,0x2c,0x05,0x2d,0x19,0xaf,0xb0,0x9b,0xe5,0x8f,0x2c,0x0c,0x15,0xcf,0x7e, + 0x34,0x02,0x7a,0x75,0x49,0x76,0x66,0x98,0x93,0xa0,0xcd,0x76,0xb6,0xda,0xe7,0x92, + 0xe9,0x29,0x79,0x23,0x4a,0xbb,0x53,0xf6,0xc2,0xac,0x9a,0x64,0xf6,0xb5,0x8a,0xa8, + 0xe6,0xf5,0xe6,0xab,0x0d,0x19,0x5d,0xa0,0xa9,0x85,0xca,0xd6,0xfb,0x6b,0xfd,0x21, + 0xfb,0x5b,0x0e,0xe1,0x84,0xed,0x7f,0x26,0xa3,0xd8,0x0b,0x61,0x5b,0x4b,0x86,0xd6, + 0xb6,0x7c,0x19,0xde,0xa8,0xc7,0x74,0xa2,0x1e,0x47,0xc9,0xa5,0x6e,0x46,0x51,0xb4, + 0xae,0xd5,0x2a,0x15,0x07,0x53,0x9e,0x17,0x73,0x1b,0xb5,0xa6,0x6c,0x20,0x10,0x4a, + 0xdb,0x38,0x99,0x26,0x1c,0xb9,0xb4,0x9a,0x04,0x08,0x54,0x55,0xe5,0xed,0x8b,0x4b, + 0x70,0x7b,0xf4,0x78,0x4a,0x1a,0xa0,0xa6,0x2a,0x70,0x5c,0xcc,0xd9,0xd3,0xeb,0xd6, + 0xec,0x74,0x9e,0x68,0x97,0xcc,0xc7,0xbd,0xe3,0xdb,0xc1,0x12,0xa1,0xd5,0x6b,0xdb, + 0xc3,0x9b,0x5e,0xc7,0x4e,0x27,0xbd,0xae,0x3d,0x4a,0x7a,0x3b,0x0f,0x25,0x03,0x65, + 0xa4,0xfd,0x02,0xf8,0x79,0xe7,0x2e,0xa0,0xfc,0x65,0x7f,0xad,0x11,0xae,0x1a,0xad, + 0xd0,0xf9,0xfc,0x4f,0x1a,0x28,0xaf,0xb3,0xfa,0xad,0xf5,0xf8,0x2b,0x3a,0x35,0x28, + 0x7f,0xc8,0x65,0x43,0xf9,0xbd,0x92,0x59,0xa1,0x4e,0x51,0x9b,0x75,0x94,0xb4,0xde, + 0x49,0x59,0xd0,0xe6,0xb7,0xcc,0x41,0x67,0x11,0x91,0x27,0x43,0x92,0x93,0xba,0xd6, + 0x5f,0xb1,0x4f,0x38,0xf6,0x20,0xef,0x4d,0xbf,0xf1,0x48,0x70,0x8e,0x5c,0x7c,0x61, + 0xbe,0x10,0xb3,0xd6,0x3a,0xe6,0x7e,0x8f,0x66,0xb3,0x29,0xad,0x34,0x58,0x23,0x97, + 0xeb,0xe3,0x7e,0x90,0x4d,0x18,0x32,0x43,0x15,0xff,0x6e,0xad,0xe5,0x6d,0x0d,0xce, + 0x40,0x66,0xfb,0x16,0xf6,0x8c,0x21,0x0d,0xfe,0x42,0x1a,0xc0,0xf2,0x99,0xb0,0x4d, + 0x22,0x1f,0xfc,0x70,0xbc,0x74,0x9b,0x47,0x57,0xad,0x3b,0x63,0xa1,0x28,0x6b,0x99, + 0x5b,0xd9,0x6a,0x3e,0x29,0x83,0x69,0xc8,0x75,0xab,0x99,0x0c,0x7f,0xa2,0x25,0x91, + 0xeb,0x3a,0x35,0x65,0x71,0x38,0x55,0x7c,0x6e,0x0a,0xe9,0x09,0x96,0x5e,0x2c,0x13, + 0x56,0xba,0x64,0xcb,0x16,0xa5,0x4b,0xb6,0x5d,0xaa,0x9d,0xdd,0xb4,0x6b,0x9a,0xa5, + 0x0f,0x3c,0xe1,0x81,0xdf,0x2c,0x6b,0x4b,0x27,0xf3,0xec,0x4b,0x8c,0x61,0x2a,0xe8, + 0xe4,0x47,0x0f,0xc2,0x93,0x6e,0x1f,0xec,0xa3,0x38,0x62,0xcf,0x0a,0xa9,0xff,0x41, + 0xa9,0xdb,0x3b,0x18,0x00,0xcd,0x9e,0x17,0x3f,0x61,0xe2,0xf6,0x7e,0x3b,0x37,0x2f, + 0x1b,0x1f,0xb5,0x8f,0xc7,0x5b,0xe6,0xd8,0x30,0xb7,0x66,0x5b,0xe6,0xcc,0xec,0x35, + 0x67,0x90,0x82,0x8f,0x90,0x32,0xdf,0x32,0xe7,0x66,0x8f,0xfe,0x16,0xe2,0xbc,0x01, + 0x02,0x37,0x7d,0x7b,0x68,0xab,0x78,0x6a,0x9b,0xe5,0xb0,0xf1,0x68,0x41,0x75,0x24, + 0xce,0x77,0x7d,0x3c,0xf0,0xdd,0xdc,0x1a,0xf2,0x59,0xef,0xe6,0x56,0x33,0x3d,0xde, + 0x34,0x0e,0x53,0x78,0xe2,0x23,0xda,0x92,0x69,0x97,0xae,0x20,0xd5,0x26,0x62,0xa9, + 0xcf,0x01,0xf2,0x0b,0xf2,0x39,0x65,0xf7,0x37,0x0e,0xa1,0x87,0x81,0xe2,0xde,0x09, + 0xc7,0xad,0xa6,0xca,0x65,0xef,0xb4,0x8b,0xc1,0xe0,0xb4,0xba,0x96,0xec,0x1e,0x93, + 0x57,0x01,0xec,0x33,0x5a,0xe6,0xa8,0x5a,0x8a,0x1f,0x4b,0xcd,0xb5,0x1f,0x4a,0xa2, + 0xbf,0xac,0xb5,0x14,0x78,0xa4,0x3e,0xee,0x08,0xc5,0x95,0xa8,0x11,0x53,0x39,0xf0, + 0x8c,0x8c,0xf4,0x5f,0xf1,0x8d,0x0e,0x90,0x91,0x46,0xfb,0x20,0xc7,0x31,0x8b,0xba, + 0xa0,0x55,0x65,0xc4,0xe9,0xc0,0xd5,0x70,0xa0,0x39,0x19,0xa4,0x0e,0xc6,0x19,0xfc, + 0x06,0xd4,0xd5,0xdb,0xc6,0x2b,0x28,0x64,0xd8,0x1c,0x0a,0xf5,0xaf,0x47,0x6b,0x69, + 0xd0,0x84,0x9b,0x5f,0x62,0x14,0xcd,0x81,0x5e,0x30,0x42,0x3a,0x8b,0x98,0x63,0x87, + 0x58,0xd6,0x96,0xc8,0xf1,0x13,0xdf,0x05,0x61,0xda,0xf0,0x15,0xef,0x85,0xf8,0x36, + 0xbf,0xde,0x46,0x57,0x06,0xe8,0x4d,0xf4,0xd6,0xbf,0xf5,0xc6,0xcd,0xae,0x65,0x9b, + 0x7f,0x37,0x55,0x01,0x75,0x55,0x00,0xec,0x1c,0x43,0xdb,0xfc,0xe5,0x27,0xe3,0x47, + 0xbc,0x0f,0x07,0x50,0xaf,0xda,0x4f,0x48,0xe7,0x6e,0xaa,0xd2,0x74,0xa9,0x6d,0x10, + 0x44,0x23,0xd3,0xae,0xc9,0x8e,0x8e,0xac,0xf8,0x91,0x0b,0x61,0xe5,0x79,0xbb,0xda, + 0xed,0x02,0xa6,0x9d,0x3a,0x14,0x67,0xd1,0x36,0xc7,0x3f,0xcd,0xf3,0x2c,0xef,0x81, + 0x58,0x1b,0x9f,0xcf,0xce,0xde,0x61,0x86,0x04,0xb8,0x77,0xfa,0x0e,0xfd,0x3b,0xfb, + 0xf8,0x99,0x62,0x89,0xa4,0x61,0xa2,0x32,0x9f,0xc0,0xee,0x87,0xf7,0x34,0x9d,0xff, + 0x9a,0x83,0xc7,0xe5,0x34,0x1d,0x3e,0x32,0xdb,0x67,0x2d,0x5b,0x02,0xdd,0xac,0xe6, + 0x3c,0x15,0x57,0x81,0x61,0x4e,0xc8,0xe3,0x8d,0xae,0x6d,0x3c,0x7d,0xa0,0x86,0xa1, + 0x04,0x3c,0x56,0xb2,0x9e,0x53,0xd6,0x14,0xe6,0x3b,0x07,0x2f,0xdd,0xa2,0xfa,0xf9, + 0xd7,0xed,0x73,0x55,0xcd,0x37,0xf2,0x84,0x05,0xf1,0x6f,0x9b,0x07,0x01,0xd9,0x39, + 0x49,0x15,0x7a,0xed,0xa3,0xc5,0x66,0xa9,0xd4,0x98,0x12,0x8b,0xc5,0x38,0x4d,0x95, + 0x3b,0xff,0xd5,0x00,0x89,0x79,0xe1,0x61,0x99,0xec,0xf6,0x1b,0x3d,0xc3,0x47,0xcd, + 0xf7,0xec,0x58,0x4c,0x1b,0x5e,0xb1,0xa0,0xb2,0x22,0xb1,0x17,0x99,0x29,0x04,0x98, + 0xa8,0x8d,0x04,0x2f,0x09,0x7e,0x12,0x7f,0xd4,0x1c,0xe4,0x99,0xde,0x9d,0xe2,0x57, + 0x3f,0xbe,0xbf,0x37,0x5b,0x48,0x5a,0xc9,0x76,0x16,0xdb,0x43,0x72,0x8c,0x58,0x00, + 0x58,0x05,0xc4,0x42,0x4f,0xe5,0xa9,0x16,0x53,0x8e,0x8c,0x20,0xde,0x22,0x41,0x1c, + 0x85,0x76,0x2f,0x85,0x65,0xeb,0x95,0xc0,0x9a,0x2f,0x65,0xe2,0x4b,0x27,0xf2,0x5c, + 0xb2,0xb2,0x92,0xa7,0x1d,0x33,0xac,0xc1,0x20,0xcd,0xfd,0xf1,0xfb,0x6a,0x9d,0x4a, + 0xf6,0x45,0x5b,0x67,0x69,0x20,0x0e,0x1d,0x8e,0xe1,0xa9,0x60,0xab,0x7a,0x2b,0x03, + 0x47,0x0e,0x9a,0xb7,0x64,0x28,0xc5,0x06,0xe1,0x16,0x5a,0x84,0x17,0x68,0xef,0xd5, + 0x35,0x51,0x5e,0x26,0xba,0x85,0x90,0xad,0x22,0xc0,0x2a,0xec,0x0c,0x35,0xe1,0x9a, + 0xc5,0xf9,0xb6,0xb1,0xd9,0x20,0x4b,0x21,0xd9,0x06,0xdb,0xb4,0xe1,0x1d,0x31,0x65, + 0xcb,0x36,0xab,0x60,0xda,0xc6,0xf9,0x2d,0xab,0x60,0xcd,0x23,0x8e,0xdc,0x30,0x80, + 0x6b,0x8a,0xdd,0x8e,0xae,0xb8,0xd3,0xf7,0xf7,0xb7,0x18,0x3a,0x42,0x1b,0x81,0x09, + 0x38,0x85,0xdf,0x01,0xbd,0xa2,0x2b,0xc2,0x30,0xca,0x01,0xaf,0x78,0xe0,0x56,0x6b, + 0x1d,0x2b,0xc6,0x29,0xcc,0x88,0x6e,0x9d,0x50,0x84,0xf8,0xe1,0xde,0x2b,0x3b,0x27, + 0xda,0x7a,0x8a,0x16,0xa2,0x78,0x49,0xd3,0xa6,0xc8,0x47,0xa4,0x57,0xb3,0x90,0xca, + 0x53,0x84,0x05,0xe9,0xe6,0x16,0xf7,0xbf,0x30,0xb0,0xad,0x49,0xc1,0x7a,0xb7,0x07, + 0xd4,0x44,0xde,0x18,0xa2,0xa2,0x72,0x79,0x63,0xc7,0xe4,0xb3,0xe5,0x47,0xf9,0x4c, + 0x42,0x4f,0x17,0xbd,0x5b,0x5d,0x36,0xfe,0xbe,0x26,0x16,0xcb,0x15,0xb8,0x70,0x04, + 0xbb,0xb5,0xeb,0xa4,0x33,0x1f,0x05,0x58,0x7d,0x0b,0x23,0x9c,0x04,0xba,0x80,0xac, + 0x84,0x40,0x8f,0x71,0x9c,0x0c,0x44,0x0c,0x40,0xb6,0xd1,0x39,0x05,0xa2,0x1b,0x7c, + 0x46,0xe5,0xee,0xfd,0x7d,0x07,0x03,0x4a,0x5c,0x23,0x73,0x08,0x8b,0xf7,0x1f,0x18, + 0x4d,0x18,0xf8,0x86,0xbd,0xfd,0x7e,0x03,0x12,0x29,0xb8,0xf0,0xe0,0xe6,0x47,0xa8, + 0xa0,0x0f,0xaf,0xe2,0x36,0x95,0x19,0xbd,0x53,0xcd,0x53,0x2c,0x3a,0xf5,0x78,0x47, + 0x81,0x19,0x37,0xbb,0x78,0x74,0x32,0x75,0x52,0x3c,0xef,0x6b,0x42,0x36,0x1b,0xfe, + 0x61,0x02,0xed,0x9d,0x9f,0x81,0x2e,0x34,0xf1,0x44,0xe0,0xc6,0x9e,0xf1,0xb2,0xc0, + 0x4e,0x8a,0x01,0x1d,0x76,0x8b,0x66,0xa2,0x21,0xef,0x49,0x40,0xde,0x1d,0x0a,0x71, + 0xc4,0xba,0x6b,0x1a,0x95,0x3d,0xbf,0x15,0x1f,0x31,0x6e,0x41,0xe9,0x23,0x1b,0xde, + 0x23,0xff,0x38,0x0f,0xad,0xe5,0xfc,0x76,0x0b,0x44,0x8c,0x79,0xd8,0x1a,0x74,0x1e, + 0x1a,0x53,0x8c,0xd2,0x12,0x5d,0x79,0x67,0x84,0xff,0xd4,0xed,0x79,0xbc,0xc8,0xbc, + 0x31,0x25,0xe4,0xf1,0x6c,0xe5,0x83,0x38,0x06,0xb6,0x70,0x84,0xa7,0x49,0x14,0x7b, + 0x20,0xcb,0x50,0x54,0xc4,0xa6,0xd9,0x6a,0xb9,0xa3,0x91,0x29,0x3d,0x2e,0x80,0xd0, + 0x3c,0xef,0x0e,0x5f,0x8c,0xbd,0x97,0x30,0xbb,0x53,0x07,0x03,0xb2,0x13,0x24,0x07, + 0xdd,0x3e,0xbf,0xfd,0x67,0x84,0xb7,0xb9,0xd1,0x46,0x64,0x42,0xd2,0xd0,0x9b,0xfa, + 0xe1,0x29,0x8c,0x80,0xf8,0x46,0x84,0x41,0x85,0x7f,0xbd,0xb6,0xa5,0xb5,0xfa,0xed, + 0xc0,0xdf,0xd6,0x01,0xd5,0xea,0x58,0x3f,0x36,0x6f,0x5a,0xbb,0xd6,0x56,0xd7,0xbe, + 0x1b,0xcc,0x5a,0xbb,0xad,0xe6,0x75,0x0b,0xc6,0xba,0x0d,0xa3,0xc6,0xdf,0x1f,0x9b, + 0x90,0x05,0x59,0x39,0xff,0x98,0x1b,0x3f,0x8f,0x9a,0xb7,0xf6,0x9d,0xd5,0x9b,0x3a, + 0x28,0x8e,0x8b,0x37,0xe6,0x26,0x25,0x44,0x9a,0xf4,0x0c,0xab,0x35,0x10,0xb0,0xd1, + 0x21,0x05,0x1d,0x46,0x52,0x81,0x17,0x52,0xc5,0xb7,0x46,0x7a,0x07,0x72,0xc2,0xbc, + 0xb5,0xf0,0x71,0x20,0x38,0xe9,0x27,0x18,0x0e,0x1c,0x06,0x87,0xb8,0x61,0xca,0x6a, + 0x68,0xfd,0x63,0xa7,0x2f,0x0a,0x3d,0xbf,0xb4,0x6f,0x5a,0x5d,0x76,0xf4,0xd0,0x70, + 0x16,0x3d,0xd5,0x7e,0xf1,0xee,0x28,0x0a,0x57,0x93,0xe5,0x8b,0xdc,0x28,0x85,0xdf, + 0x37,0x36,0x56,0x85,0x10,0x17,0xf9,0xfb,0xca,0x13,0x74,0x18,0x2c,0x12,0x79,0xb2, + 0xef,0x7a,0xb9,0xde,0x96,0x83,0x9c,0x89,0xa2,0x84,0x25,0xae,0xb7,0xb1,0xe1,0xa2, + 0x24,0x56,0x60,0xdd,0x31,0x8d,0x2a,0xb1,0xc4,0x6f,0xe9,0x24,0x47,0xdd,0x24,0xa6, + 0x7a,0x8a,0xbc,0x9f,0x0a,0xc4,0x3c,0x90,0x4e,0x94,0x65,0xbd,0x71,0x7d,0xbf,0x6d, + 0xba,0x44,0x82,0x7c,0xfa,0xb4,0x3b,0x25,0x64,0x40,0xc9,0x81,0x17,0x6c,0x6c,0x54, + 0x6c,0x1f,0x48,0x75,0xbb,0xb1,0x31,0x71,0xf0,0x38,0x3d,0xcc,0xf0,0x9e,0x62,0x4b, + 0x7f,0x81,0xbd,0x03,0x08,0x4f,0xf6,0x13,0x5d,0x6b,0xde,0xa4,0x1a,0x6d,0xb4,0xd7, + 0xba,0xcd,0xce,0xfc,0x21,0x60,0xc4,0x94,0xcc,0x32,0xc2,0x5a,0x5d,0x8d,0x0a,0x30, + 0x55,0x33,0x27,0xab,0x42,0xd4,0xd6,0xe5,0x7d,0xb0,0x3b,0x7b,0x6d,0xd2,0xa8,0xc8, + 0x3b,0x28,0xf8,0xa0,0x51,0x8f,0x9e,0x93,0x5f,0x13,0xb1,0x2c,0x8f,0x7f,0x45,0xb4, + 0xb3,0x42,0x1c,0x68,0x09,0x76,0x62,0xc0,0x2b,0xe2,0x01,0x35,0x19,0x25,0x23,0x4f, + 0x1e,0x2d,0xab,0x9b,0x26,0xf4,0xbd,0x75,0xf3,0xaf,0xbc,0x75,0x62,0xb3,0x4f,0xa7, + 0x9f,0x79,0xd3,0xc7,0x82,0xd3,0x87,0x84,0xe3,0x84,0xee,0xde,0x18,0x74,0x60,0x5f, + 0x53,0x89,0x25,0xcf,0x9d,0xa1,0x9b,0xa4,0x4d,0xe4,0x67,0x58,0xa8,0x0b,0x07,0xf8, + 0x7c,0xd4,0xda,0xdb,0x3b,0xde,0xed,0xf1,0xe3,0xfe,0x8b,0xe3,0x1d,0xf1,0xf8,0xe2, + 0xe0,0xb8,0xdb,0xeb,0x90,0xbf,0x7b,0x54,0x67,0x65,0xbc,0x4b,0xba,0x94,0x08,0xed, + 0x71,0xf5,0x2b,0x1a,0xc6,0x7e,0x1a,0x07,0xee,0x5d,0xcf,0x0f,0xe9,0xda,0x88,0x21, + 0x86,0xfa,0xef,0x8b,0xd0,0xf6,0xf1,0xad,0x0c,0xe7,0x9f,0xf0,0x65,0x12,0x90,0x30, + 0x8c,0x92,0xb1,0x97,0x50,0x70,0xc7,0x45,0x4a,0x29,0x22,0xf8,0x3d,0x08,0x6c,0x3b, + 0x5b,0xfe,0x8f,0x3b,0xb0,0x2b,0x62,0x36,0xe0,0x36,0xa7,0x44,0xdf,0xf0,0x03,0x34, + 0x1f,0x1e,0x9b,0xcc,0x49,0x00,0x9d,0x04,0xae,0x40,0xbc,0x60,0x9b,0x96,0x49,0x5b, + 0xab,0xdc,0x4a,0xf3,0x7d,0x7c,0x4d,0x37,0xf1,0xc2,0x94,0x3e,0xcd,0x4c,0xcb,0x07, + 0xea,0x93,0xf6,0x30,0xa1,0x05,0x82,0x91,0xec,0x0d,0x5e,0x84,0x41,0x06,0xbe,0x5b, + 0xa6,0xa8,0xd8,0x2c,0x4b,0x60,0x38,0x2b,0x8b,0x24,0x10,0x32,0x18,0x3e,0xad,0x8b, + 0x92,0xcc,0x31,0xeb,0xa0,0x4c,0x28,0x8e,0x08,0x35,0x64,0x1b,0xac,0xd6,0x56,0x53, + 0x2b,0xfa,0x04,0xd3,0x3a,0x68,0x17,0x6d,0x40,0x42,0x0c,0x96,0x07,0xed,0xc8,0xbb, + 0x61,0xc4,0xe9,0x1a,0x31,0x4e,0x79,0x85,0xa1,0x0a,0x0a,0x16,0xd2,0xe1,0x37,0xd2, + 0x6b,0x2c,0x59,0x67,0xe3,0x23,0x32,0x0e,0x49,0x22,0x69,0xb9,0xf4,0xa3,0x54,0xa3, + 0x9e,0x17,0xe2,0xa9,0x0c,0xb5,0x4a,0x15,0xd4,0xb4,0x84,0x2c,0x2a,0xe4,0xbb,0xe0, + 0xa6,0x2e,0x2d,0xfd,0xd2,0x85,0xbe,0xfe,0x05,0x90,0x4b,0x7c,0xc3,0xf3,0x31,0x79, + 0x28,0x55,0xbf,0xb8,0xa8,0x31,0xc1,0xba,0xd2,0x73,0x81,0x79,0x0d,0x35,0x47,0xab, + 0x75,0x7e,0x3f,0xa1,0xa7,0x2c,0xc8,0x53,0x3c,0x24,0x66,0x9e,0x8c,0xbb,0x23,0x98, + 0x34,0x5a,0x38,0x21,0x8d,0xda,0x2a,0xfb,0xfe,0xa4,0xa9,0x64,0xe3,0x64,0x11,0x85, + 0x21,0x5b,0x90,0xe4,0x85,0xa3,0x63,0x73,0xe3,0x79,0xa7,0x7b,0xd0,0x7d,0xb1,0xdb, + 0xe7,0x70,0xc3,0xe6,0x61,0x3a,0x07,0x19,0x11,0x32,0x70,0x9d,0x54,0x82,0x52,0x94, + 0x8b,0x91,0x59,0xe4,0xf5,0x36,0xf3,0x5b,0x0d,0x2a,0xb7,0x34,0x14,0x6f,0x64,0x03, + 0xa6,0x50,0x4e,0xbb,0x31,0xc1,0xf5,0xe2,0xe0,0x25,0x06,0x9b,0xf5,0xaa,0xc7,0x75, + 0x74,0xeb,0xa9,0x0d,0x22,0x46,0x8a,0xe3,0x79,0x6e,0xea,0x41,0x69,0x00,0xf5,0xaa, + 0xff,0xac,0x1a,0x10,0x91,0xb9,0xa2,0x04,0x7c,0x64,0x0b,0x2c,0xec,0x9b,0x96,0xa6, + 0x5a,0xce,0x15,0x61,0x14,0x56,0x09,0x3f,0x20,0x41,0x40,0xf0,0x50,0x27,0x9b,0x14, + 0xf6,0x9b,0x9e,0x64,0x48,0x83,0xe5,0x70,0x31,0x1c,0x06,0x5e,0xda,0x63,0xb4,0x44, + 0x9b,0xbd,0x7c,0x7b,0xc9,0x5d,0x4b,0x0a,0x2e,0xee,0x83,0x8b,0xc6,0x32,0xeb,0x99, + 0x5f,0xce,0x4e,0xb6,0x5f,0xb9,0xa1,0x3b,0x76,0x8d,0x26,0x9e,0xe4,0xcd,0xa1,0x83, + 0x24,0xcb,0xd8,0x93,0x9e,0xf9,0xb2,0xd3,0x76,0xf6,0xba,0x7b,0x26,0xea,0xc5,0xcd, + 0xfd,0xae,0x03,0x4f,0x29,0x24,0xbf,0x30,0x51,0x3d,0x6e,0xee,0x61,0x10,0x40,0xac, + 0xe3,0x64,0x01,0x0c,0x13,0x10,0x28,0x57,0x14,0xda,0x73,0x0e,0xda,0x6d,0x2e,0xd4, + 0xdd,0x6b,0x73,0x99,0x4e,0x7b,0x55,0x21,0xa3,0xf9,0xd1,0x4d,0x92,0xe8,0x46,0x36, + 0xb9,0xef,0xec,0xbd,0x58,0xd5,0xe4,0x41,0x4d,0xe9,0x0f,0xbe,0xea,0xed,0x9e,0xd3, + 0x96,0x45,0x3b,0x5d,0x51,0xf2,0xe5,0xaa,0x76,0x7b,0xc6,0xd9,0x89,0x6d,0xfc,0xe3, + 0x84,0xcb,0x76,0x77,0x9c,0x4e,0x75,0xa4,0x07,0xab,0x9a,0xed,0x19,0xff,0xf5,0xfe, + 0xf5,0x13,0x4a,0xaa,0x66,0x7f,0x4a,0xdc,0xdf,0xfd,0xe0,0x7b,0xda,0x7a,0xf3,0x65, + 0xfb,0xcb,0x2f,0x45,0xe0,0x1c,0xec,0xbf,0x74,0xf6,0x3b,0x07,0x4f,0x2c,0xf9,0xda, + 0x8b,0x13,0x0f,0x96,0x95,0x9c,0x4d,0x2c,0xad,0x66,0x33,0x9f,0x98,0x4e,0xa9,0xa7, + 0xaf,0x7e,0xf7,0x46,0x33,0xe3,0xb3,0x47,0x97,0x81,0x8f,0xaa,0x1d,0xd8,0xdd,0xe9, + 0x3e,0x86,0x10,0x6f,0xbe,0x18,0xbb,0x3b,0x3b,0x1f,0x7e,0xfe,0xdd,0x68,0xbe,0x8f, + 0x40,0x42,0xfe,0x8c,0x46,0x41,0x5c,0x03,0xa4,0x3b,0x78,0xa1,0xc9,0x23,0x9d,0xd0, + 0x6a,0xd0,0xdb,0x2f,0x94,0x5e,0x0d,0x80,0x8f,0x1e,0x9a,0x70,0x05,0x6e,0x38,0x4e, + 0xd7,0xc1,0xad,0xdc,0x6d,0xbc,0x05,0xe6,0x7f,0x7b,0x2e,0x96,0x13,0x08,0xf5,0xc2, + 0xd9,0x79,0xf1,0x28,0xc0,0xb4,0x62,0x65,0x5c,0xd6,0xca,0xaf,0x6e,0x16,0x9d,0x51, + 0x16,0x53,0x37,0xc0,0x11,0xab,0x51,0xd6,0x94,0x13,0x98,0xbc,0x5f,0x29,0x77,0xb0, + 0x7f,0xf0,0x84,0x61,0xaa,0x72,0x67,0x37,0x7e,0xf6,0x3b,0x83,0xe7,0x7b,0xb0,0xea, + 0xef,0xbe,0x97,0x85,0xee,0xbc,0x34,0xc4,0x6e,0xdb,0xe9,0xae,0x9c,0x90,0xbd,0x4a, + 0xd9,0x32,0x4e,0x16,0xca,0xd7,0x81,0xb8,0x71,0xa9,0xa9,0xfd,0x8b,0x41,0x37,0x96, + 0x79,0x50,0xa9,0x35,0x11,0xa5,0x74,0x9e,0x5b,0x9c,0x4f,0x12,0xd7,0x80,0xc7,0xde, + 0x21,0x50,0xe6,0x04,0x70,0x7c,0xc4,0xc6,0xb9,0x3d,0x83,0x87,0x00,0xcc,0x0f,0xfe, + 0x42,0x67,0x0c,0xea,0x13,0xbc,0xc3,0x8f,0xd3,0x36,0xe9,0xf0,0xd2,0xbd,0xbf,0x57, + 0x54,0xde,0xd0,0x6c,0x89,0x6e,0x07,0xae,0x76,0x02,0x09,0xd2,0xe9,0xb0,0x70,0x20, + 0x59,0xf4,0x75,0xdf,0x95,0xae,0xee,0x22,0xb0,0xfe,0xed,0x85,0x7f,0x89,0x6e,0x99, + 0xe2,0xf5,0x0e,0x5f,0x4b,0x6d,0x68,0x9c,0x47,0x51,0x57,0x42,0x40,0x79,0x4d,0xde, + 0x6c,0x45,0x98,0x18,0xb8,0x7e,0x54,0x38,0xf1,0x9f,0xfe,0x21,0x41,0x94,0xa7,0x9d, + 0xbd,0x95,0xd0,0xca,0xd3,0x5e,0x7d,0xae,0x00,0xee,0xe6,0x77,0x3a,0xbf,0x7c,0xeb, + 0x53,0x70,0x0e,0xc1,0x58,0x05,0x7a,0x2c,0x16,0x7b,0xb4,0x48,0x8a,0x91,0x5c,0x38, + 0x82,0xb5,0x72,0x05,0x2c,0xfb,0xd2,0xc1,0xde,0xcc,0x1e,0x6b,0x7c,0xaf,0xb7,0x37, + 0x85,0x6f,0xf9,0x5d,0x50,0xb9,0x5b,0x1d,0xfb,0x64,0xc2,0xde,0x47,0xc1,0x14,0x9e, + 0x12,0xa4,0xa5,0xb1,0x3a,0x4a,0x0b,0x74,0xd2,0xb2,0xb8,0x36,0x3f,0xf7,0xd8,0x2b, + 0x3b,0x0a,0xfa,0xba,0x7b,0x60,0x06,0x5c,0x96,0xba,0x5a,0x6c,0x73,0xab,0x00,0x6c, + 0xdd,0x6b,0x90,0x77,0xd9,0xfa,0x2a,0x29,0xc6,0xcb,0xd1,0x2f,0x18,0xa9,0x46,0xc6, + 0x43,0x95,0xe6,0x33,0x05,0xbf,0x46,0xd8,0xe9,0xcb,0xde,0x5a,0x14,0x0c,0x01,0xfa, + 0x7b,0x34,0x68,0x5b,0x69,0x39,0x8c,0x16,0x7d,0x51,0x66,0xfc,0x4d,0xb6,0xb4,0x20, + 0xb3,0x07,0xf8,0x15,0xf1,0x74,0xf8,0x56,0x18,0x35,0x53,0x2d,0xe8,0x40,0x31,0xea, + 0xfc,0x2b,0xee,0x51,0x70,0xd7,0xc3,0xbb,0xd8,0x58,0x5b,0x38,0x22,0xeb,0x01,0xab, + 0x0e,0x83,0xf8,0xf3,0xf0,0xc6,0x2a,0xa3,0x11,0x7f,0x48,0x27,0x56,0x19,0x97,0xf8, + 0xc3,0x28,0x21,0xde,0x13,0x5a,0x68,0x6c,0x71,0xc7,0x0f,0xdb,0xc7,0xa6,0xd1,0x1c, + 0x47,0x1e,0x5d,0xe5,0x46,0x49,0x86,0x2b,0x30,0x01,0x88,0x1a,0x43,0xda,0x22,0xa6, + 0x94,0x61,0xb3,0x26,0x22,0x5a,0x15,0x57,0x01,0xbb,0x34,0x77,0xd5,0xeb,0x81,0x02, + 0x9e,0x4d,0xb8,0xd1,0x97,0x61,0xb7,0x30,0x4c,0x08,0xfd,0xd2,0x24,0x59,0x4b,0xfa, + 0x5a,0xc4,0x64,0x61,0x01,0xc2,0xd1,0xf0,0x0a,0x18,0x78,0xb1,0x75,0x7d,0xd9,0xe7, + 0x12,0x45,0x4c,0x7b,0xf8,0x0e,0xfb,0x03,0x76,0x72,0xa1,0x5a,0x74,0xfa,0xf1,0x87, + 0xcc,0x12,0xe2,0xdc,0x22,0xa1,0xb1,0xce,0x24,0x81,0x5a,0xab,0xc4,0x5e,0x62,0x1c, + 0x4e,0xff,0x48,0xc1,0x6a,0xc8,0x26,0xca,0x59,0x8d,0x19,0x2e,0xa3,0x9f,0x6b,0xf6, + 0x03,0x22,0x64,0x78,0xd9,0x84,0x80,0xe4,0x53,0x8d,0xf2,0x3c,0xae,0x35,0xa6,0x4b, + 0x0f,0x42,0xe1,0xbd,0x98,0x1f,0x3a,0x6c,0x6c,0x60,0x08,0xa2,0x1d,0x2b,0x1c,0xec, + 0xf6,0x1b,0x17,0x1d,0xbb,0x6b,0xef,0xd8,0xbb,0x75,0x71,0xc9,0x85,0xb2,0xe4,0xe6, + 0x77,0x73,0xcb,0x5f,0x7d,0x6c,0xee,0xc3,0xf0,0xc3,0xfc,0x26,0x8c,0x15,0x79,0xf1, + 0x1c,0x1f,0x35,0x01,0x02,0xa8,0x18,0x92,0x85,0x3a,0x21,0x4c,0x46,0xe9,0x2a,0x8f, + 0x9b,0xdf,0x77,0x8c,0x82,0xcb,0xa3,0x0c,0x7a,0x9a,0x7b,0x3b,0xce,0x95,0x2f,0x88, + 0x88,0xf8,0x2f,0x01,0x2f,0xe2,0xff,0x73,0xa8,0x95,0x68,0x63,0xe3,0x99,0x08,0xe5, + 0x05,0x83,0x9d,0x63,0xc8,0x2d,0xc6,0x87,0x70,0xde,0x8f,0xbe,0x5b,0x7a,0x78,0x60, + 0xd3,0x7c,0xec,0xef,0xae,0x45,0xde,0xdf,0x7c,0x13,0x05,0x8a,0x12,0x42,0x93,0x9e, + 0x8e,0xf0,0x42,0xa7,0xf3,0x08,0x15,0xda,0x65,0x53,0xef,0xf8,0xd3,0xa2,0x74,0xcb, + 0x04,0x3a,0x1c,0xba,0xc1,0x36,0x5e,0x77,0x58,0x8e,0x38,0xbe,0xd2,0x98,0x19,0x2f, + 0x3d,0x42,0x35,0x05,0x42,0x59,0xdd,0x47,0x59,0x3c,0xc1,0xa5,0x0c,0x3c,0x0f,0xf2, + 0x9a,0x52,0x6b,0xc5,0x15,0x6a,0xb5,0x02,0xa6,0xb0,0x3d,0x79,0x15,0x2d,0x82,0x31, + 0x92,0xa0,0xc4,0x03,0x74,0xc8,0x6d,0x15,0x5b,0x06,0x9a,0xf9,0xbb,0x14,0xa4,0xb8, + 0x62,0x89,0x32,0x99,0xa0,0xc7,0x8e,0xd8,0x6c,0x4b,0x86,0x67,0xc7,0x9a,0x91,0x58, + 0xaf,0x99,0xdb,0xaa,0xe1,0x74,0xe9,0xaa,0xba,0xa2,0x6d,0xfe,0xc4,0x0f,0xfd,0x14, + 0xcf,0x93,0x72,0xfc,0xad,0xde,0x50,0x93,0x47,0x1a,0x65,0x6a,0x84,0xfd,0xc8,0xcd, + 0xc9,0x60,0x83,0x1b,0x14,0x83,0xc2,0xa9,0x4f,0x2a,0xf6,0x1e,0xeb,0x4f,0x65,0x5b, + 0xb4,0x59,0x14,0xee,0xdc,0x00,0x4c,0x8a,0x65,0x33,0x67,0x14,0xcb,0x83,0xfb,0x69, + 0xa8,0xaa,0xd8,0xd6,0x2e,0xbe,0xbf,0x1f,0x59,0x1b,0x1b,0x31,0x2c,0x86,0x91,0xcc, + 0x7f,0x52,0xc8,0x98,0x1a,0xe3,0x08,0xa3,0x76,0x30,0x69,0x57,0x47,0x56,0xa5,0x30, + 0xa9,0x05,0xfc,0xe2,0xc5,0x81,0xb7,0x74,0x0e,0x2e,0x2e,0xed,0xb1,0xc2,0x7d,0x8d, + 0x05,0x84,0x8f,0x4d,0x0a,0x7b,0x8a,0xb9,0xf8,0x6c,0xa8,0xee,0x10,0x6c,0x73,0x2b, + 0xc0,0xed,0x86,0xce,0xba,0x78,0x0b,0xba,0x16,0xfb,0x0f,0x6b,0x2b,0xe9,0x72,0x2f, + 0xa4,0x3f,0xfa,0x35,0x32,0xa6,0x3d,0xbe,0xd0,0xfc,0x2d,0x2e,0xd5,0x6a,0x2b,0xa5, + 0x9a,0x78,0xf4,0x67,0xd0,0xee,0x84,0x46,0x23,0xaa,0x9a,0xdc,0x87,0x5d,0xd6,0x83, + 0xce,0x19,0x97,0xc7,0xe2,0xca,0x45,0x33,0x47,0x86,0xc2,0xd7,0x26,0x8c,0x4d,0x38, + 0x65,0xc0,0x6e,0xd7,0x24,0x8b,0x59,0xd1,0x23,0x8b,0xac,0x9f,0xf1,0xac,0xa8,0x66, + 0xf9,0x5b,0xd4,0x32,0xea,0xbc,0x0d,0x76,0xb2,0xaf,0xcd,0x24,0x8c,0xe9,0x46,0x83, + 0xb1,0xe4,0xcb,0x2a,0x6c,0x9a,0x9d,0xc4,0xec,0x3a,0xfd,0x28,0x7f,0xb5,0x2e,0x04, + 0x5e,0x32,0x02,0x8e,0x35,0x66,0x57,0x6a,0xea,0xd9,0x67,0xde,0x08,0x9a,0x49,0x7c, + 0x9c,0x00,0x23,0x05,0x7c,0x41,0xcb,0x60,0x1d,0x53,0x32,0xd2,0x63,0x6a,0xb1,0x22, + 0x09,0xcf,0x38,0xad,0xad,0xa6,0xe8,0x25,0x1f,0x81,0xea,0x90,0x51,0x80,0xc8,0x6e, + 0x79,0xe0,0xe7,0xbf,0x1a,0x14,0x38,0x1a,0xc7,0x9d,0xdd,0x42,0xed,0x74,0x0c,0x2e, + 0x0f,0x9c,0x2b,0x6b,0x67,0x63,0xa3,0x59,0xc6,0xf5,0xfb,0xfb,0xd2,0xfa,0xb1,0xb8, + 0xe6,0x22,0x1e,0x9b,0x76,0x29,0xd7,0xb1,0x49,0xb1,0x86,0x8b,0x58,0x50,0x3e,0xe6, + 0xa6,0x8a,0x3e,0x0d,0xf9,0x0c,0x54,0xce,0xcf,0x45,0x91,0xca,0x6b,0xf8,0x55,0x4a, + 0xaf,0xc1,0xb0,0x77,0x18,0x29,0xce,0x6e,0xca,0x3a,0x28,0xf8,0x75,0xa5,0x06,0x99, + 0xaa,0x97,0x87,0x2d,0xe9,0x4b,0x1c,0xcb,0xf0,0x60,0x02,0x1f,0xf0,0x2e,0x6b,0xd5, + 0x1d,0x0a,0xd1,0x5c,0xed,0x0d,0x27,0xf3,0x06,0x73,0x13,0x32,0x68,0x3e,0x61,0x22, + 0x06,0xf8,0x37,0x6d,0x48,0x93,0xc7,0xb1,0x9d,0xee,0x31,0xbe,0xd1,0xf5,0x98,0xb0, + 0x25,0x74,0xba,0xb0,0xd6,0xd0,0x10,0xa7,0x87,0xe5,0x84,0x26,0x6c,0xae,0xda,0xe3, + 0x90,0xcf,0x95,0xf6,0x44,0x32,0x3b,0x0d,0xcc,0xf5,0xe6,0xf8,0x8b,0x0d,0x89,0x4f, + 0x0a,0x27,0x12,0xcb,0x96,0x38,0x86,0x8a,0x8c,0x20,0x50,0x6a,0xaf,0xf8,0x91,0xbd, + 0xa1,0x98,0x9e,0xc9,0xa8,0x70,0xd8,0x01,0x3a,0x4c,0x87,0xac,0x76,0x2c,0x62,0x33, + 0xd2,0x58,0xd0,0xdb,0xb2,0x10,0x81,0x71,0x5c,0xd5,0x60,0xab,0xed,0xe1,0xd9,0xf6, + 0xbf,0xc8,0xf1,0xea,0xab,0x73,0x2f,0x71,0xe8,0x87,0x7b,0x44,0x88,0x1f,0xee,0x09, + 0xd5,0x7f,0xb8,0xcf,0x6e,0x7f,0xb8,0xc7,0x2e,0x7d,0x15,0x18,0x20,0x5e,0x70,0x32, + 0x65,0x3a,0xc2,0x41,0x3c,0x13,0x34,0xe8,0x99,0xfc,0xce,0xbf,0x3a,0xf2,0xfe,0xc9, + 0x2b,0xbc,0xb3,0x41,0x46,0x80,0x44,0x20,0x62,0x37,0x05,0x1c,0x35,0x97,0xb6,0x14, + 0x20,0x89,0x5f,0x0a,0xee,0x0e,0xda,0x5d,0x5c,0x7c,0xb7,0x54,0x41,0x48,0x20,0x5a, + 0x5b,0x7b,0xe9,0x1c,0xf3,0x6e,0xd2,0x7b,0x4d,0xec,0x5b,0xbc,0xf9,0xe9,0x34,0x13, + 0x76,0x95,0x3a,0x52,0xca,0x4a,0x5e,0x68,0xb3,0xe6,0xba,0x8b,0xd3,0xc0,0x73,0x53, + 0xba,0xc9,0x19,0xa4,0x82,0xc2,0x65,0x5f,0xb0,0xd0,0x40,0x92,0xea,0x58,0x9a,0xa1, + 0x17,0x34,0xac,0x09,0xa3,0x82,0xf9,0x41,0xe6,0xfe,0x09,0x2d,0x88,0xcb,0xb8,0x5d, + 0x36,0x38,0x96,0x01,0x44,0xb9,0x91,0xae,0x65,0xb4,0xc4,0xbd,0x5f,0x29,0x3a,0x3e, + 0x93,0x27,0x04,0xa0,0x47,0xeb,0xcb,0x19,0x06,0x9c,0xc0,0x2b,0x5a,0x52,0xad,0x1b, + 0x2b,0x1d,0xef,0xe8,0x0a,0xfa,0x82,0xc7,0xdd,0xca,0x6e,0x71,0x06,0xbc,0x06,0x54, + 0xf4,0xa0,0x38,0x4c,0x9d,0xad,0x05,0xcc,0xba,0x38,0x69,0xfd,0x6f,0xb7,0xf5,0xfb, + 0xe5,0x72,0xe7,0x41,0x3a,0x28,0xe7,0x13,0xf0,0x14,0x42,0x61,0x5a,0x8a,0xeb,0x5d, + 0x03,0x23,0x3a,0x10,0x04,0x10,0xed,0xb4,0x02,0x0f,0xcd,0xd9,0x0c,0xa4,0x46,0x42, + 0x56,0x33,0xd0,0x6b,0xcd,0x68,0x92,0x05,0x13,0x76,0xd9,0x2a,0x19,0xe0,0xd5,0x57, + 0x6c,0xea,0x37,0x4e,0xa1,0xe1,0x32,0x79,0x0b,0xdb,0x43,0x72,0x11,0x1f,0x14,0xee, + 0x84,0xea,0x0f,0x4b,0x7e,0xc0,0xa5,0x1b,0xa3,0xce,0x61,0xd4,0x68,0x87,0x43,0x3e, + 0x34,0x68,0xfe,0x57,0xf0,0x44,0x5c,0xb2,0x7f,0x08,0x33,0xb7,0xb9,0x42,0x3f,0x11, + 0x9e,0x80,0xcb,0xc6,0xb0,0xec,0x19,0x56,0xac,0x9e,0x7b,0xc4,0x76,0xeb,0x82,0x39, + 0x95,0x25,0x35,0x4f,0x9f,0xdc,0x75,0xc6,0x1d,0x8d,0xbc,0x38,0x53,0xfe,0x3d,0xc2, + 0x8d,0x05,0x23,0xb8,0xc4,0x49,0x84,0x41,0x74,0xe8,0x06,0x14,0x74,0x33,0xe4,0xcb, + 0xe5,0x35,0x57,0x43,0xbe,0x27,0x54,0x3a,0x1c,0x3a,0xc6,0xbb,0x89,0x88,0x83,0x26, + 0xaa,0xa1,0x2d,0x09,0x9d,0x14,0xd1,0x87,0x46,0xdc,0x56,0x8e,0x7d,0x48,0x8d,0xaf, + 0x26,0x8b,0x4d,0xec,0x2e,0xf3,0xec,0xab,0x49,0xbe,0x37,0xb9,0x97,0x10,0x21,0x2e, + 0xec,0x69,0x3b,0x6d,0xa8,0x8b,0x1c,0x65,0xa8,0x76,0xbc,0x01,0x3d,0x73,0xef,0x52, + 0xe1,0x0b,0xbe,0x88,0x0d,0xbc,0xb0,0xca,0x86,0x52,0x9a,0x4b,0x4f,0xf1,0x7a,0x76, + 0xaa,0x58,0x44,0x30,0xa4,0x11,0x28,0x3f,0x29,0xf2,0x90,0xaa,0x75,0xe1,0x41,0x85, + 0x47,0x4b,0xf8,0x4c,0x16,0x6e,0x40,0x2d,0xf8,0x4d,0x15,0x1d,0xf7,0xea,0xb1,0x66, + 0x15,0xaf,0x2e,0xdc,0xcd,0x1d,0xe3,0x15,0x79,0x60,0x52,0x90,0x3a,0xb6,0xf6,0x41, + 0x57,0x26,0x40,0x8f,0x20,0x90,0x17,0xf7,0x95,0x86,0x83,0x48,0x23,0x7c,0xa2,0xc8, + 0x51,0xd3,0x31,0xf5,0x9e,0xe4,0x3e,0x82,0x15,0x77,0xc0,0x12,0xb9,0xbf,0xd5,0xc8, + 0x7d,0xee,0x0b,0x78,0x2b,0x7c,0xeb,0x84,0x08,0x48,0x55,0xe5,0xb1,0x87,0x57,0x8e, + 0x93,0xf3,0x95,0xad,0xdd,0x84,0xb4,0x71,0x4b,0x0e,0x76,0xe4,0x56,0x2d,0xaa,0x57, + 0x87,0x71,0x68,0xee,0x55,0xf6,0xb0,0x56,0x8e,0x52,0xab,0x10,0x56,0xb8,0xa3,0x6d, + 0x02,0x9b,0x8c,0x7c,0x4e,0x4e,0x9e,0xad,0xc6,0x96,0xb9,0xf9,0xdf,0xeb,0x94,0xa6, + 0x41,0xbf,0xea,0x91,0xa6,0x30,0x89,0xa8,0xbf,0xa8,0xaf,0xc6,0x19,0xcc,0x48,0x23, + 0x94,0xb6,0xd7,0x7a,0x84,0x71,0xfc,0xf4,0x0a,0x00,0xd4,0xfa,0x04,0x3e,0x52,0xb8, + 0xb7,0xe5,0x6b,0x93,0xa0,0x33,0xf6,0xc7,0x24,0xc4,0x00,0xa0,0x81,0x8d,0x04,0x12, + 0x18,0x56,0xaf,0x8a,0x2f,0xf9,0xc0,0x6d,0x9a,0x8d,0x0a,0x28,0x01,0x92,0x04,0x3e, + 0xbc,0x97,0x9d,0x35,0x9e,0xe4,0x02,0x87,0x20,0xd9,0x9c,0xc2,0x76,0x73,0xe3,0x81, + 0x20,0xb4,0x89,0xa5,0x41,0x36,0xc3,0x95,0xfb,0x3d,0xbe,0x70,0x64,0xff,0x55,0x37, + 0xe8,0xe2,0x02,0x7b,0x28,0xc7,0x5b,0x2e,0x16,0x50,0xb7,0x70,0xa1,0xe8,0xcd,0x1f, + 0xbf,0xf9,0xe1,0xb7,0x79,0x7a,0x2c,0xcd,0xcd,0x9a,0x1d,0xb6,0x29,0x1e,0x79,0x7e, + 0xd0,0x2c,0xe6,0x61,0x93,0x66,0xab,0xb7,0xd3,0xd6,0x4d,0x9b,0x84,0x13,0xa6,0x64, + 0x0d,0x04,0xb1,0xc3,0xd0,0xcd,0x98,0xac,0x5d,0xea,0x19,0x46,0x37,0xc7,0xa6,0xa5, + 0x8e,0x4e,0xb5,0x38,0x0f,0xdc,0x48,0xfd,0xed,0x55,0xf5,0xf8,0x5c,0xf0,0x5d,0xd4, + 0xd1,0xf7,0x06,0xa9,0x00,0xca,0xf4,0x00,0xe5,0x9c,0xc2,0x61,0xfc,0x55,0xa4,0xbf, + 0x78,0x1d,0x2f,0xac,0x7e,0x5b,0x6c,0xd5,0x0f,0xdf,0xed,0xc3,0x22,0x06,0xc5,0xa7, + 0xcd,0x1c,0xf4,0x40,0x64,0xb5,0x6a,0xee,0xce,0x13,0xdd,0x9e,0xa7,0x53,0x5b,0xf5, + 0xc5,0xa6,0x4b,0x41,0x6d,0xc0,0x60,0x41,0xa4,0x05,0x8d,0x60,0x18,0xa8,0x2b,0x43, + 0x75,0x4a,0x41,0x69,0xb0,0x8d,0x7f,0x96,0xd8,0xc0,0x1b,0xa0,0x56,0x0a,0x5a,0x28, + 0x95,0xa1,0xe8,0x2c,0xc4,0x43,0xc3,0x8e,0x74,0x33,0xd0,0xf2,0x62,0xa4,0x18,0x68, + 0x3e,0xf0,0xc3,0xab,0x62,0x32,0xda,0xae,0x5c,0x09,0x31,0x07,0x29,0x09,0x96,0x5e, + 0x24,0xc1,0xc0,0x9c,0x65,0x59,0xdc,0xdb,0xde,0x06,0x0e,0x39,0xde,0x32,0xb7,0xcd, + 0x3e,0x17,0x2e,0x34,0x87,0x4a,0x1a,0x91,0x3c,0x4b,0xbc,0x89,0x78,0x4f,0xa2,0x9b, + 0x95,0x06,0x58,0x42,0xe1,0x5a,0x53,0x97,0x69,0x16,0xaa,0x32,0x9f,0x9b,0x95,0xaa, + 0xf4,0x9b,0x04,0x75,0x38,0xac,0xd3,0x0e,0xb1,0xc1,0x91,0x00,0xfa,0xfd,0xfd,0x4e, + 0xbb,0xaf,0x15,0x24,0x6b,0xd7,0x72,0x58,0xf3,0xbe,0x88,0x47,0xad,0xbb,0x56,0xe8, + 0x98,0x19,0xb6,0x5a,0x8f,0xd6,0x71,0xd4,0x3e,0x0e,0x7b,0x42,0x83,0x17,0x1e,0x0e, + 0xf6,0x60,0xdd,0x49,0x4c,0x80,0x1a,0xf4,0xcb,0xfa,0xc4,0x55,0x46,0xf6,0x92,0x2e, + 0x6c,0xef,0xe5,0x37,0xb9,0xd7,0xa9,0xcf,0xe4,0x8d,0xe4,0x25,0x97,0x8d,0xcc,0xea, + 0x63,0x24,0x1a,0xcc,0xe5,0x70,0x20,0x83,0xa6,0xf5,0xb0,0xda,0x14,0x97,0xfb,0xd4, + 0xae,0xa9,0xa5,0x51,0x33,0x2e,0x4d,0x54,0x90,0x43,0x38,0xde,0x3c,0x74,0x0d,0x9e, + 0xa5,0x6d,0xf3,0xe8,0x33,0xb5,0x78,0xb8,0xed,0x1e,0x6d,0xf6,0xcc,0x7f,0x46,0x0b, + 0x63,0xe4,0x0a,0x1b,0x38,0xa6,0x02,0xb8,0x40,0x85,0x75,0xb0,0x4d,0xa4,0x44,0xee, + 0xbe,0xf2,0xa2,0xd5,0xee,0x6e,0x5f,0xbd,0xff,0xfd,0xcd,0xe7,0x9f,0xd0,0x7e,0xe1, + 0xc2,0xe4,0x38,0x80,0x6f,0x81,0xa8,0xdc,0x60,0x38,0x59,0x34,0x37,0x86,0x41,0x50, + 0x64,0x73,0x93,0x2c,0xf2,0xe0,0xeb,0x4f,0xf8,0x4b,0xc4,0x77,0x06,0x0f,0x94,0xcf, + 0x0f,0x27,0x11,0x67,0x1a,0xa1,0x09,0x18,0xc6,0x10,0x04,0x4c,0x20,0xa2,0x2f,0xb8, + 0x06,0x4a,0x37,0x9a,0x5f,0xce,0x5f,0x59,0x9c,0x91,0x7c,0x14,0x28,0xd8,0xa0,0x60, + 0xdc,0x28,0xc3,0x12,0x76,0x85,0xd1,0xac,0x25,0x30,0xe7,0x81,0xb3,0x32,0x5b,0xac, + 0x57,0x9a,0x5b,0x40,0x0b,0x9e,0x99,0x33,0xce,0x01,0xeb,0xd1,0x33,0xc4,0xfc,0xd9, + 0x73,0x99,0xb1,0x3a,0x3d,0xfb,0x7c,0xf2,0xc1,0x20,0xef,0x13,0x74,0xed,0x08,0xd0, + 0x38,0x24,0xcd,0x0c,0x32,0x54,0xe3,0x32,0xf9,0x15,0x4f,0x36,0xa9,0x7e,0x52,0x74, + 0x15,0x49,0xb0,0xde,0x11,0x9d,0xe2,0xd8,0x2c,0xa3,0xa0,0x97,0x00,0xef,0xd7,0x53, + 0xaf,0x58,0x50,0xac,0x35,0x1c,0xcb,0xeb,0x04,0xb6,0x62,0xdc,0x98,0xc5,0x37,0xd8, + 0x96,0x96,0xfb,0xbb,0xad,0x99,0x77,0xdb,0x02,0x51,0x31,0x69,0x01,0x9f,0x21,0x86, + 0x04,0x1c,0xf2,0x08,0x0a,0x25,0x8e,0xde,0xfc,0x49,0x7a,0x95,0x97,0xa4,0x20,0xc0, + 0xb0,0xea,0xc2,0x4c,0x30,0x79,0xf3,0xd4,0x0b,0xae,0x41,0xd8,0x2c,0x96,0xa7,0x8b, + 0xd0,0xb0,0xf0,0x2b,0xf6,0xd8,0xd1,0x9a,0xe6,0x4f,0x46,0x93,0x54,0x34,0xbc,0x01, + 0xab,0x58,0xc1,0xa4,0x25,0x14,0x33,0x01,0x4b,0x2e,0xf6,0x92,0xb9,0x9c,0x0c,0x97, + 0xa8,0xfe,0x66,0x6a,0x9c,0xbc,0x7a,0x6f,0xe0,0x07,0x3f,0x45,0x1c,0x48,0xab,0x43, + 0x31,0x96,0x78,0x39,0xbc,0x18,0x91,0xb8,0xf1,0x0d,0xeb,0x40,0x28,0xa1,0x96,0x13, + 0x13,0x70,0x27,0x32,0xf8,0x9a,0x72,0x4f,0x36,0x28,0x2e,0x52,0xfb,0xdd,0x4b,0xa2, + 0x59,0x14,0xd7,0x14,0xc9,0x61,0x40,0x5b,0x8c,0x60,0x17,0xf0,0xbe,0x3a,0x46,0x1d, + 0x6f,0x1e,0xb3,0x6c,0x89,0xe1,0x24,0x61,0xff,0x11,0x82,0xa6,0x9b,0xb8,0xf3,0xdc, + 0xc9,0x1a,0x36,0x77,0xe4,0x65,0xe8,0xc8,0xc7,0x1e,0xde,0xd8,0xe8,0xa7,0x9b,0x3c, + 0x48,0x2c,0xf5,0xc8,0xcd,0x97,0x9d,0xfe,0x3e,0x93,0x80,0x8a,0x00,0x2a,0xc4,0x5b, + 0xc6,0x3a,0x69,0x3d,0xc2,0x22,0x10,0x90,0xe2,0x0b,0xb8,0x91,0x50,0x20,0xc0,0x13, + 0x4f,0x7d,0x37,0x9a,0xe4,0x3e,0x43,0xac,0xb9,0x6d,0x08,0x5f,0x1b,0x4b,0x2f,0x25, + 0x2f,0x44,0xb3,0xcd,0x53,0x2f,0x11,0x6f,0x2d,0x34,0x51,0xab,0x6d,0x43,0x7a,0x29, + 0x7e,0x2e,0xf4,0xc2,0x68,0x22,0x1a,0xda,0xe8,0x1d,0x64,0x1b,0xec,0x0f,0x52,0x29, + 0xd4,0x1a,0xfb,0x2e,0xb0,0x30,0xe6,0x9b,0xdb,0x8c,0x0c,0x8f,0xc4,0x40,0x30,0x35, + 0x04,0x2e,0xc0,0x1f,0x89,0x76,0x82,0x68,0xca,0xb6,0xad,0x08,0x7f,0xfc,0x95,0x83, + 0x87,0x0f,0x53,0x11,0x71,0x0a,0x41,0x32,0x41,0x4f,0x2d,0xb2,0xd2,0xd6,0xcb,0xf1, + 0xb4,0x21,0xe7,0x59,0x2c,0x95,0xe7,0xf1,0x12,0xbc,0xf4,0x0b,0xd6,0x04,0x3b,0x24, + 0x93,0xf0,0x46,0xbe,0xc5,0x5a,0x09,0x39,0x64,0x2f,0x4c,0x91,0x95,0xf5,0x29,0xde, + 0x28,0xee,0x26,0x86,0x9b,0x65,0x48,0x99,0xc7,0x06,0x7f,0x2b,0x66,0x44,0xfe,0x8e, + 0xa6,0xcd,0x1d,0xf3,0xe5,0xb2,0x9c,0x0c,0xd8,0x38,0xf6,0x6e,0x1f,0x0a,0x59,0x53, + 0xce,0xfa,0x8f,0xc4,0xcf,0xbc,0x9a,0xbc,0xc6,0x92,0x6f,0x69,0xe1,0x32,0xd3,0x18, + 0x6f,0x23,0xa7,0x68,0xa6,0xc0,0xbe,0x1a,0x0b,0xbe,0x07,0xe5,0x6f,0xa7,0x67,0xda, + 0xe7,0xc9,0x44,0x7d,0xa7,0xeb,0x50,0x2a,0x39,0xd0,0x49,0xb8,0x42,0xd6,0x10,0xa1, + 0xe4,0x66,0xc1,0x97,0x75,0x56,0xcb,0x79,0x19,0x39,0x99,0x01,0x5e,0xc5,0x2c,0xd4, + 0xc9,0xa3,0x6d,0xc8,0x06,0xb3,0x70,0x0b,0x44,0x16,0xdd,0x79,0xdd,0x6c,0x3b,0x90, + 0xf4,0x0e,0xcb,0xa9,0x65,0x16,0x12,0xb4,0xf9,0x84,0xc0,0x0f,0x47,0xc1,0x62,0x4c, + 0x2d,0x18,0xc0,0xfb,0xf9,0xd4,0xae,0x2f,0x17,0x58,0x5a,0x29,0x9d,0xc2,0x92,0xc6, + 0xe2,0x27,0xf4,0xea,0xa7,0x3c,0x5f,0x81,0x7f,0x5d,0xac,0xa2,0x52,0x2e,0x86,0xed, + 0x28,0xad,0x94,0x13,0xf3,0x5c,0xe8,0xaa,0xbc,0x92,0x90,0xf8,0xeb,0x9c,0x90,0x53, + 0x72,0x4b,0x2c,0x55,0x94,0x95,0x2b,0xb9,0x79,0x46,0xde,0x84,0x74,0xdf,0x3c,0xa5, + 0x8b,0x95,0x5d,0x93,0x93,0x26,0xe7,0x35,0x2b,0x1f,0xea,0xf2,0x0a,0x6d,0x21,0x62, + 0x04,0x6b,0x33,0x84,0x7c,0xa4,0x6b,0xa7,0x8d,0x25,0x08,0x24,0x2d,0xf9,0x26,0x89, + 0x5b,0x80,0xa3,0x45,0x09,0x54,0x51,0x2b,0x7a,0x33,0xf8,0x83,0x90,0x84,0xb5,0x5d, + 0x09,0x35,0x83,0x68,0x7a,0x4e,0xa5,0xa3,0xcc,0xe5,0x48,0x46,0xd4,0x2e,0xca,0xd3, + 0xa8,0xbd,0x20,0xc9,0x07,0x7a,0x48,0xa4,0x98,0xcf,0xed,0x69,0xee,0x26,0xc0,0x52, + 0xcd,0xac,0xbc,0x24,0xbb,0x95,0xd3,0xdc,0xde,0x84,0xe4,0xcd,0x8d,0xd8,0x44,0xb9, + 0x04,0x51,0x57,0xb5,0x28,0xb9,0x9b,0x58,0x7c,0x49,0x19,0x12,0x92,0x86,0x64,0x7c, + 0x37,0xb1,0xd8,0x99,0x49,0xa0,0x73,0x44,0xd9,0x7d,0xbc,0x28,0xbc,0xae,0x8c,0xe1, + 0xc6,0xab,0x8a,0xa1,0xde,0x04,0x57,0x01,0xeb,0x3a,0x4e,0x4e,0x65,0x79,0xa0,0x0b, + 0xc5,0x26,0x51,0x44,0xcd,0x8b,0xca,0x6d,0x9d,0x25,0x11,0xa4,0xbf,0xa2,0x72,0xa1, + 0xc9,0x10,0x34,0xfa,0x6a,0x65,0x0e,0x9b,0xb5,0x8a,0x09,0x4d,0xbb,0x5a,0x66,0x1a, + 0x46,0xe8,0xeb,0x54,0x89,0x9e,0x98,0xc8,0x1d,0x9c,0x2d,0x32,0x5c,0xbd,0xb5,0x59, + 0x8c,0x66,0xea,0x02,0x6b,0xe2,0xa6,0x86,0xac,0x49,0x4c,0x86,0xa4,0x68,0x6f,0xf0, + 0xb7,0x44,0x1b,0x8d,0x56,0x2b,0x97,0x7d,0x49,0x55,0x44,0x3b,0x77,0x76,0x67,0x5e, + 0x36,0x2e,0x73,0x46,0xeb,0x97,0x37,0xff,0x64,0x3e,0x4b,0x04,0x71,0xd6,0xce,0x9a, + 0xda,0x4c,0x39,0xdd,0x4c,0x2d,0x27,0x5c,0x40,0x7e,0xb6,0x18,0xe7,0x1f,0x69,0x29, + 0xc8,0x8f,0x51,0x38,0x2d,0x7c,0x65,0xf3,0x42,0x87,0x8e,0x12,0xcc,0x73,0x29,0x09, + 0xc2,0x3e,0x2e,0xec,0x0e,0xe9,0x43,0x87,0xb3,0x26,0xd7,0x22,0x1f,0x02,0x16,0xf7, + 0x31,0xd8,0x7c,0x65,0x8f,0x6b,0x58,0x16,0xbb,0xcb,0x13,0x86,0x57,0xc7,0x8b,0x4e, + 0xf3,0x33,0x57,0x57,0xdc,0xac,0x68,0x1b,0xf6,0x68,0xbb,0x2a,0xef,0xbd,0xa2,0xa7, + 0x98,0x8a,0x5c,0x26,0xfc,0x2c,0xbc,0x70,0x44,0x71,0x9a,0x3e,0xfc,0xfc,0xbb,0xfc, + 0x9c,0xdd,0xe2,0x00,0xc4,0x61,0x14,0x7e,0xc3,0x73,0x28,0xf1,0xcd,0xc5,0x69,0x95, + 0x1e,0xae,0x13,0x17,0x83,0x26,0xc9,0x4f,0x78,0x75,0x19,0xdf,0x5c,0x06,0x4b,0x45, + 0x5d,0x63,0x86,0x1c,0x0c,0x32,0x72,0x46,0xf3,0x06,0xf7,0x80,0x54,0xee,0xcd,0xa2, + 0xb0,0x25,0x4b,0xe3,0xbd,0xbc,0xbc,0xf7,0xc0,0x02,0x1a,0x92,0xaf,0x0b,0x88,0xb6, + 0x6e,0x98,0xd2,0xad,0xe2,0x6d,0xdb,0x8c,0xc2,0x7b,0x85,0x3f,0x85,0x2b,0x7a,0x01, + 0xbd,0x7f,0xed,0x74,0xf7,0x6f,0x8d,0xcf,0xbf,0x1a,0x80,0xae,0x29,0x6a,0x54,0xf8, + 0x43,0x6d,0x29,0xed,0xa6,0x5e,0x04,0x01,0x7a,0x4d,0x22,0x3d,0x01,0xda,0xb7,0x08, + 0xd0,0x69,0x57,0x96,0xd5,0x8a,0xdc,0xa0,0x44,0x31,0x8e,0xa6,0xa5,0x85,0xc0,0x1b, + 0xbb,0x3f,0x31,0x52,0x40,0xc3,0x50,0xac,0x4d,0x44,0x0b,0x63,0xd9,0x6e,0x75,0xba, + 0x6d,0x74,0x72,0x56,0x30,0x87,0xcd,0x03,0x24,0x1b,0x8c,0x71,0x01,0xb5,0x90,0xf4, + 0x31,0xf1,0x12,0x8c,0x43,0x6c,0x70,0x6a,0x14,0x8c,0x15,0x8c,0xa7,0x23,0x87,0x74, + 0xf6,0xf9,0x0d,0xa6,0x00,0xf3,0xbf,0xbd,0x32,0x58,0x91,0x2f,0x13,0x59,0xc7,0x49, + 0x7c,0xba,0xea,0xae,0xb8,0x04,0x19,0xfa,0xf9,0xab,0x41,0x8f,0xc6,0x90,0xd6,0x8c, + 0x9c,0x58,0xf9,0xf9,0x5c,0x7e,0xe6,0x59,0xc0,0x0e,0x77,0x55,0x57,0xd9,0xe5,0xd7, + 0x59,0x9d,0x1b,0xa9,0x27,0x67,0x12,0xbc,0x44,0x5a,0xac,0x40,0xf0,0x8a,0x00,0xde, + 0x28,0xb9,0x41,0x5e,0x7e,0xee,0x21,0xad,0x4c,0xdc,0xc9,0x04,0x03,0x91,0x97,0xa6, + 0x65,0xbe,0x08,0x32,0xdf,0x81,0x7a,0x52,0xe6,0x9a,0x12,0x17,0x38,0xdf,0x5f,0x88, + 0xfd,0x06,0x66,0x21,0x87,0x4a,0x10,0x80,0x58,0x9c,0x00,0xcf,0xe1,0x44,0x61,0x80, + 0xdd,0x3a,0xc1,0x14,0x54,0x9c,0x8e,0x5b,0x98,0x62,0xa0,0x1c,0x90,0xb1,0xbe,0x3a, + 0x4d,0x2b,0xcd,0x94,0x6f,0x0f,0x06,0x64,0xc3,0x38,0xa6,0x72,0x27,0xd5,0xa1,0x0a, + 0xf3,0xb6,0x40,0x54,0x6d,0xb6,0x8d,0x81,0x41,0xf4,0x47,0x2e,0x9c,0xda,0x7b,0x88, + 0x6d,0xe1,0x82,0x5d,0x53,0xd3,0x8c,0x98,0xe8,0x15,0xf5,0xe0,0x9d,0xce,0x36,0xf9, + 0xb7,0xd3,0x3b,0x64,0x8e,0x09,0x90,0xfb,0xbb,0x0f,0x95,0x8c,0x8e,0x62,0xef,0xc9, + 0x1f,0x9e,0x5b,0x5a,0x5b,0x40,0xdd,0xe3,0xcc,0x45,0x28,0x2f,0x85,0x5d,0x17,0xe9, + 0xdc,0x68,0xb9,0xbc,0x7e,0x93,0xb3,0x90,0xa7,0x44,0x3e,0x37,0x40,0x48,0xdf,0x61, + 0xf0,0xe1,0xe4,0x8e,0xc9,0x7e,0x4e,0xe2,0x66,0x2e,0xe2,0x35,0x42,0x7c,0x32,0xb9, + 0x07,0xf0,0xf9,0x73,0x37,0xb8,0x47,0x76,0x22,0x81,0xad,0xf3,0x1e,0x6f,0x49,0x1f, + 0x65,0x92,0x07,0xc8,0x66,0x78,0xe0,0x33,0x73,0x88,0xdb,0xc0,0xe0,0xf8,0x20,0xdd, + 0x61,0x02,0xb1,0x1f,0xd0,0x9d,0xfb,0xce,0x7d,0x8e,0x4a,0x74,0x4e,0xe8,0x90,0x78, + 0x6b,0x8b,0xc3,0x53,0x7c,0x31,0x50,0xef,0x60,0x34,0xef,0x81,0x52,0x8c,0xa2,0x39, + 0x52,0x15,0xdc,0x86,0xd9,0x1f,0x49,0x94,0x9c,0x2e,0x3c,0xed,0xd4,0x1c,0x4a,0xff, + 0x0d,0x13,0xb4,0x43,0x70,0x81,0x56,0xe3,0x91,0x43,0x18,0x88,0xc1,0x30,0x51,0xf6, + 0x16,0xe1,0x0d,0x8c,0x93,0xd7,0xaf,0x0c,0xfd,0x03,0x67,0xcf,0x35,0xa0,0x76,0xc1, + 0xd2,0xa2,0xb0,0x89,0x28,0x5b,0x08,0xbb,0x6c,0x46,0xa1,0x7f,0x57,0xf7,0x46,0xcb, + 0x5c,0x92,0x15,0xf3,0x98,0x11,0x43,0x70,0x22,0x57,0x89,0xf0,0xbc,0x47,0x5c,0xd1, + 0x3a,0x20,0xd4,0x27,0x05,0x45,0x32,0xf0,0x87,0xb6,0xf1,0xee,0xd4,0xce,0x45,0xe5, + 0x85,0x88,0xe5,0xc0,0xbb,0x84,0x7e,0x22,0x6f,0x17,0x4f,0xf2,0x11,0x55,0x69,0x97, + 0x4a,0x91,0xe5,0x17,0x4a,0x3e,0x45,0x55,0xf2,0xf3,0x35,0xdb,0xac,0x1c,0x93,0x2d, + 0x52,0x28,0x01,0xc5,0x29,0xda,0xba,0x81,0xb3,0x5b,0x2c,0xa7,0xba,0x8a,0x52,0x17, + 0x1e,0x6c,0xeb,0xd1,0x24,0xe5,0xc7,0x4e,0x31,0x3f,0x65,0xe7,0xee,0x6c,0x53,0xd0, + 0x80,0x5c,0x44,0xd3,0xb3,0x8a,0xa0,0xf8,0x48,0x09,0xae,0x5d,0x3f,0x20,0xa6,0x93, + 0x2f,0xf2,0x31,0xd4,0x27,0x2d,0x3b,0x73,0x43,0x78,0x94,0xca,0xb3,0x37,0x23,0x9b, + 0x7a,0x3e,0x42,0xc1,0xa3,0x3e,0xc1,0x6c,0x01,0x52,0x2f,0x88,0x81,0x2d,0xb6,0x95, + 0x4b,0x8f,0xdc,0x31,0x29,0x45,0x69,0xc0,0x2a,0x91,0x35,0x79,0xab,0x36,0x12,0xaa, + 0x34,0x92,0xf0,0x35,0x26,0x0b,0x90,0xa8,0xf1,0x32,0xa1,0x09,0x6e,0xd3,0xab,0xca, + 0xdd,0x56,0x5a,0x92,0x7a,0x11,0xd4,0x3a,0x30,0xdb,0xec,0x27,0xf5,0x85,0xb3,0xba, + 0xc2,0xb9,0x6a,0x1a,0x89,0x6a,0xaa,0x16,0x2b,0x94,0x16,0xa4,0x45,0x9b,0xeb,0x9c, + 0xb2,0x9d,0xf1,0x45,0x47,0xb2,0xf3,0x8a,0xb6,0x2d,0x3b,0xad,0xfd,0xe2,0x06,0x57, + 0xba,0xaf,0x3b,0xef,0x81,0xa6,0xf8,0x58,0x40,0xcf,0x69,0x9a,0x9a,0xac,0x17,0x22, + 0x8d,0x56,0x6a,0xd5,0x0f,0xa3,0xe6,0x22,0x6f,0xe0,0x7d,0x54,0x45,0x35,0x5d,0xea, + 0xb6,0x76,0x76,0xf6,0x99,0xe6,0x16,0xbb,0xc5,0x96,0x1f,0x92,0x80,0xe4,0xfc,0x58, + 0x1d,0xab,0xa5,0x15,0x13,0x66,0x1a,0xba,0xd1,0x86,0x3c,0x21,0x29,0x8e,0x3a,0x43, + 0x06,0xfd,0xe3,0xf9,0xa9,0x88,0x10,0x80,0x91,0x14,0x42,0xe4,0x89,0x61,0xc2,0x52, + 0xe0,0xd1,0xad,0x72,0x6e,0x47,0xe8,0x08,0x28,0xfc,0x08,0x95,0xc3,0x58,0x75,0x7c, + 0x1c,0x27,0xd1,0x4e,0xdd,0x15,0x6e,0xa3,0x61,0xe1,0xbb,0x5f,0x0d,0x99,0x60,0xa4, + 0x64,0x2c,0xad,0x36,0xf5,0xd2,0x95,0xe1,0xb6,0xf9,0xe5,0xfc,0x95,0xc1,0x2f,0xf9, + 0x16,0xb4,0x04,0x86,0x04,0x77,0xd4,0x4e,0x4e,0xea,0xe9,0xce,0x70,0x98,0xe1,0x8f, + 0x1f,0x4e,0x51,0xa9,0x86,0x2c,0x9a,0x38,0x66,0xc0,0xb0,0xd6,0x0b,0x3f,0x21,0x97, + 0x86,0xd2,0xcc,0x94,0x2e,0x11,0x17,0xc5,0x55,0x42,0xa9,0x6f,0x24,0x9d,0xd1,0x02, + 0xc0,0xed,0x4a,0xca,0x64,0x95,0x9d,0x19,0xbf,0x3a,0x71,0x7a,0x55,0xce,0x89,0xcb, + 0xb8,0x45,0xa2,0xf1,0x98,0xb9,0x67,0xad,0x56,0xda,0x43,0x32,0x82,0x62,0xb1,0x8c, + 0x4a,0xd7,0x33,0x2b,0x0d,0x26,0xe7,0x15,0x04,0x4c,0x5e,0x46,0xa4,0xe7,0xd4,0x70, + 0xed,0x03,0xee,0x63,0x8b,0xb9,0x62,0x07,0x86,0x40,0xea,0xf1,0x84,0x9a,0x32,0xa6, + 0xc5,0x62,0x22,0x9a,0x15,0x8b,0xa6,0xf4,0x9d,0x4e,0x9a,0x81,0xae,0xac,0x18,0xae, + 0x88,0x7b,0x75,0x22,0xe5,0x57,0xde,0x02,0x92,0x08,0x56,0xc7,0xbc,0x4a,0x14,0x86, + 0x89,0x3f,0x9e,0x7a,0x8e,0x47,0x52,0xf8,0x98,0xaf,0x88,0xf1,0xd1,0xa6,0x9c,0x89, + 0x10,0x7f,0x5e,0x55,0x2a,0x05,0x0c,0x18,0xf1,0x56,0x2b,0x69,0x81,0x27,0x8a,0xd0, + 0x6e,0xee,0x0b,0x2a,0x94,0xdc,0xde,0x67,0xb7,0x85,0x92,0x43,0x77,0x41,0x0a,0x67, + 0xce,0x9b,0x72,0x9b,0x98,0x68,0x24,0x24,0x12,0xb7,0xf5,0xcc,0x92,0x5b,0x54,0xb9, + 0x01,0x14,0x22,0xad,0x90,0x4f,0xa1,0x81,0xcc,0x99,0xe3,0x45,0xa1,0xd7,0xde,0x28, + 0x21,0x74,0x96,0xf5,0x31,0x1e,0xc8,0xe4,0x76,0x41,0xae,0xc3,0x8b,0x52,0x49,0xae, + 0x13,0xe6,0x4f,0xb6,0x7a,0xe8,0x08,0x6d,0x94,0xb8,0x61,0x87,0xef,0xfa,0x90,0xdb, + 0x04,0x5e,0xfd,0x42,0x1b,0xb7,0x10,0x5c,0x09,0x59,0xe5,0x16,0x82,0xa7,0xa3,0x48, + 0xe6,0xf6,0xf6,0x76,0xf6,0x84,0x1a,0x42,0xbb,0x10,0x48,0x3d,0x16,0x34,0x1b,0xfa, + 0xbd,0x3f,0xbc,0x3a,0xc5,0xfd,0x42,0xf4,0x8b,0x0b,0x8c,0x17,0x96,0x31,0x84,0xa5, + 0x02,0x8c,0x8b,0xdc,0xa8,0xa4,0x4e,0x5e,0xdc,0xd4,0x23,0x7a,0x99,0x15,0x6e,0xea, + 0xb1,0x0d,0xcf,0x99,0x3a,0xe2,0x7e,0x9e,0x6d,0x79,0x3f,0xcf,0x36,0xdf,0xcf,0x23, + 0x50,0x2b,0xbf,0x72,0x47,0xbf,0x81,0x07,0x85,0x63,0x46,0x9d,0xd4,0x78,0x33,0xee, + 0xee,0xed,0x75,0x5e,0xd2,0x9d,0x3b,0xb6,0xb8,0x8d,0x87,0x09,0x15,0x57,0x21,0xd7, + 0x84,0x19,0x6b,0xd7,0x10,0x22,0x2e,0x0b,0x7a,0xdb,0xc3,0x60,0x4c,0x36,0xa9,0xbb, + 0x6c,0x83,0xb4,0x28,0xaf,0xce,0xfe,0x0e,0xe4,0x86,0xf8,0x88,0x74,0x1b,0x04,0x9f, + 0xbd,0xa2,0xc8,0xcd,0x97,0xcd,0x9a,0x28,0x88,0xc2,0x58,0xe3,0x28,0x4c,0x3d,0x3b, + 0xbb,0xcd,0xbe,0xe1,0x41,0x21,0x34,0x61,0xf3,0xee,0x63,0x4f,0x93,0xf8,0x1b,0x24, + 0xd3,0x2f,0x9a,0xfb,0xda,0x2e,0x34,0xf1,0x0d,0x4b,0x21,0x4f,0x61,0x83,0x54,0x30, + 0xf2,0x6c,0xe6,0xc7,0x80,0x42,0xd9,0x78,0xd3,0x2f,0x88,0xc0,0x36,0x6c,0xa4,0xdf, + 0xc4,0xfd,0x2d,0x05,0xe7,0x24,0xf2,0x20,0x09,0xfc,0xc1,0x12,0xf5,0x32,0x59,0xaf, + 0x6d,0x67,0xc3,0xa0,0x77,0x71,0x69,0x63,0x24,0x1f,0xfe,0xbd,0xed,0xb5,0x3a,0xf6, + 0x18,0x64,0x92,0xac,0x67,0x9a,0x76,0xba,0x98,0x62,0x72,0xea,0x05,0x98,0x3c,0x5c, + 0xa4,0x77,0x3d,0x3e,0xac,0xc6,0x13,0xb5,0x50,0x3c,0x03,0x4b,0xc7,0x4f,0x0f,0xfa, + 0x3d,0x17,0x81,0x7f,0x8e,0xa0,0x15,0x67,0xc2,0xf0,0xea,0x50,0x9b,0x7c,0x03,0x3a, + 0xdb,0xf2,0x49,0xc7,0x27,0xfc,0x08,0x1d,0x91,0x67,0x6d,0xea,0xe8,0x47,0x18,0x1a, + 0x42,0xbf,0xa5,0x92,0xa2,0xf6,0x3e,0x05,0xf6,0xfc,0x80,0x0e,0x0c,0xae,0x2e,0x76, + 0x2e,0x8f,0x4d,0x63,0x69,0x6e,0xe1,0xd3,0x96,0xf9,0x60,0xca,0x73,0xb7,0xab,0x8b, + 0x2e,0xc6,0x06,0xed,0x5a,0xe2,0x92,0x51,0x60,0x84,0x51,0x05,0x0b,0xf9,0x60,0x75, + 0x5d,0x5d,0x74,0x2e,0x2f,0x2d,0x3d,0x5f,0x27,0xcf,0x97,0xaa,0x7c,0x5b,0xa6,0x61, + 0x52,0xde,0x2d,0x6c,0xec,0xb2,0x14,0xe5,0x31,0x1c,0x74,0xfa,0xa1,0x6e,0xa8,0x18, + 0x92,0xa1,0xa2,0x5c,0x86,0xf5,0x57,0xe3,0x14,0x7a,0xc3,0xd6,0x8a,0xe1,0x96,0xe9, + 0x98,0x5b,0x13,0xec,0x97,0xb4,0x4a,0xc4,0x00,0x3f,0x98,0xc4,0xbd,0x2c,0xf4,0xac, + 0x5c,0x86,0xfa,0x58,0x5f,0x8e,0xcf,0xfb,0x04,0xac,0x07,0x59,0x3f,0x9f,0x92,0xbc, + 0xcf,0xca,0xf9,0xac,0x70,0x8e,0x0d,0x39,0xdf,0x84,0x8b,0x79,0x93,0x2f,0x26,0xe0, + 0x9b,0xb4,0xe1,0xb9,0x70,0x39,0x8e,0xb0,0x51,0x6c,0x7e,0xbd,0xd9,0xb2,0x7e,0xd8, + 0x16,0x01,0x6a,0xc4,0xf5,0xb1,0xe2,0xa6,0x16,0x41,0x7b,0xe4,0xac,0x6b,0xf7,0x37, + 0x95,0xef,0x26,0x55,0xce,0x6d,0xb8,0x7a,0xf0,0xec,0x32,0x0a,0x47,0x6e,0x06,0x43, + 0x96,0x77,0x12,0xb1,0xe9,0xa6,0x98,0x33,0x59,0xbf,0x58,0xa3,0xb2,0x7e,0x92,0x8d, + 0x55,0x56,0x59,0x87,0x5a,0x7b,0x79,0x18,0x40,0x36,0x89,0x7e,0x28,0x79,0xee,0x29, + 0x94,0x13,0x96,0x96,0x6c,0x77,0x0a,0xcd,0xc9,0x0f,0x17,0xfe,0x25,0x00,0x1c,0x1a, + 0x46,0xb0,0x88,0xaa,0xf4,0x6f,0x80,0x8a,0xc5,0x57,0xb9,0x12,0xef,0xd5,0x4d,0x38, + 0x0f,0x95,0x2e,0xe8,0x20,0x1f,0xcd,0x22,0x58,0x37,0xa9,0xad,0x5d,0x8c,0x8c,0x17, + 0xea,0x52,0x9c,0x3a,0x2f,0x99,0xb7,0xe0,0x45,0xae,0x69,0x37,0x7b,0x13,0x8e,0x07, + 0x78,0x5d,0xae,0xf4,0xd8,0x88,0xb7,0xf0,0x8d,0x03,0x1e,0xfd,0x4c,0xe1,0x23,0x8e, + 0xb4,0xef,0x9c,0xd2,0x92,0x87,0xad,0xe3,0x95,0x77,0xc3,0x8c,0xfd,0x6b,0x0a,0x94, + 0xa2,0x5d,0x0d,0x03,0x5d,0xea,0x8f,0x8b,0xc6,0x09,0xf0,0xdc,0x6f,0x60,0xed,0xfa, + 0x85,0x2f,0x78,0xf5,0x00,0x19,0x0c,0x35,0xa9,0x27,0x98,0x46,0x07,0x99,0xd2,0x98, + 0x18,0xa3,0x37,0xe2,0x17,0x3e,0xa7,0xe4,0x32,0xf8,0x3e,0xf1,0x93,0x34,0xa3,0x57, + 0x9e,0x5e,0x1a,0x9b,0x55,0x18,0x5b,0x65,0x24,0x79,0x48,0xc7,0x32,0x14,0xff,0xe6, + 0x62,0x10,0x53,0x1e,0xa4,0x04,0xaa,0x39,0x25,0xd5,0xb1,0xba,0xb4,0xc4,0x18,0x17, + 0xcb,0xd0,0xed,0xa4,0x22,0x32,0x41,0x0e,0x69,0xdd,0x99,0xce,0xc4,0xd5,0xf3,0x13, + 0xee,0xcf,0x18,0x38,0x47,0xe5,0xf3,0x39,0x02,0x28,0x60,0x69,0xb3,0x18,0x03,0x46, + 0xe5,0x5d,0x36,0x54,0x27,0xd2,0x3b,0x14,0x07,0xa4,0x99,0x1a,0x86,0x96,0xc3,0x9b, + 0x26,0xfe,0x8e,0xb9,0xc8,0xb1,0x5f,0xcf,0x86,0xc6,0x9e,0xa8,0xda,0x24,0x43,0x4e, + 0x11,0xee,0x0b,0xd3,0x48,0x52,0xe0,0xc4,0x06,0x2c,0xc4,0xeb,0xe3,0xaf,0xe3,0xad, + 0xaf,0x8e,0xfc,0xa3,0x59,0x8a,0x4e,0x6e,0x28,0xd7,0x31,0x16,0xa5,0x8b,0x6a,0xa4, + 0x66,0xfe,0x13,0x08,0xae,0x61,0x94,0x69,0x87,0x04,0xa6,0xb8,0xbf,0x83,0x8d,0x50, + 0x51,0xe6,0xa6,0x28,0x21,0xa8,0x4a,0x37,0xab,0xfd,0x3f,0x53,0xe6,0x84,0xb8,0x9d, + 0xc2,0x2e,0x8f,0xaa,0x26,0xdc,0xbf,0x63,0xe2,0x4b,0xb8,0x5a,0x71,0x00,0xc0,0xee, + 0x28,0x7c,0x70,0x6d,0x6e,0x35,0xcc,0xaf,0xf9,0xd1,0xc7,0x61,0xe8,0xdd,0x1c,0x09, + 0x73,0xc6,0xaf,0x42,0x05,0xff,0xd5,0x74,0xe0,0xf9,0xb0,0xc5,0x6a,0x7c,0xf8,0xc8, + 0xb3,0x95,0xca,0x23,0xc0,0xe9,0xc2,0xc7,0x83,0x44,0xb4,0x45,0x4d,0x1d,0x53,0x52, + 0x37,0xbd,0x73,0xe7,0x78,0x8e,0x39,0xf3,0x82,0x58,0x9c,0x6c,0xa4,0x8b,0xf9,0xdc, + 0x4d,0xee,0xc4,0x0e,0x8d,0xa7,0x44,0xfe,0x2d,0x35,0x09,0x5b,0x15,0xd6,0x2a,0xef, + 0x67,0x76,0x4a,0xd6,0xcf,0x50,0xed,0xcf,0x50,0x8b,0x98,0x3e,0x42,0x29,0x6d,0x86, + 0x46,0x73,0x64,0x70,0xb0,0x19,0x53,0x4f,0x06,0x10,0x98,0x76,0x03,0x4d,0x17,0x8c, + 0x6d,0x16,0xe9,0xe0,0x97,0xcf,0xdd,0xf8,0x3f,0xa9,0xd9,0xb6,0x8d,0x89,0x34,0x6c, + 0xc0,0xbe,0xa0,0xe4,0xf2,0x35,0x44,0xf8,0xe0,0xa6,0x70,0x08,0xd4,0xe5,0xc8,0x28, + 0xff,0x87,0x3a,0x27,0xc8,0xac,0x2c,0x46,0x39,0x7f,0xaa,0xf2,0x1f,0xd2,0xf9,0x61, + 0x5e,0x4e,0x5c,0x72,0x52,0x29,0x21,0x34,0x66,0x35,0xff,0xa5,0x95,0xe3,0x72,0x2e, + 0x91,0x9f,0xd5,0x97,0xff,0x0b,0x6b,0xac,0x14,0xb8,0x0c,0xdb,0x3f,0xc0,0xe8,0xf3, + 0x83,0x6a,0x51,0x06,0x72,0x07,0x80,0x0c,0x52,0x8f,0x91,0x0f,0x5b,0x53,0x92,0x68, + 0x4d,0xc4,0x52,0x59,0x22,0x98,0xd1,0x5c,0x67,0x22,0x8a,0x32,0xe2,0xd4,0x8d,0x27, + 0x29,0x1f,0xe6,0x54,0xd6,0x98,0x89,0x48,0x20,0x31,0x20,0x25,0x58,0x09,0x0c,0x69, + 0x9a,0x5b,0x39,0x3b,0x23,0xc8,0x17,0x2c,0x42,0x14,0xef,0x30,0xf4,0x80,0x71,0x15, + 0xa2,0xff,0x83,0x43,0x38,0x7d,0x4a,0x2b,0x20,0x45,0x7b,0x5e,0x58,0x64,0xa8,0x7a, + 0x23,0x7c,0x4d,0x16,0x74,0x98,0x35,0x67,0xed,0x34,0x9d,0x2f,0x78,0x0e,0x73,0x98, + 0x20,0x41,0xc7,0x19,0xdd,0xae,0x8c,0xba,0x08,0xe1,0x78,0x27,0xed,0xf4,0x2b,0x64, + 0xa5,0x8c,0x97,0x67,0x8b,0xe9,0x27,0xa0,0xb9,0x4d,0xb5,0x69,0x3e,0x53,0xc5,0x80, + 0x79,0x2b,0x98,0x42,0x21,0x73,0xe8,0xfa,0x61,0x9a,0x9b,0x4d,0x15,0x10,0x1c,0xd2, + 0xa0,0x32,0x9d,0xec,0x95,0x2b,0xd0,0x83,0x54,0x95,0x32,0x95,0x69,0xa3,0x03,0xc9, + 0x78,0xf5,0x3b,0x3d,0x7a,0xc1,0xa0,0xd5,0x29,0xb6,0xf6,0x1e,0x6d,0xf1,0x9b,0x33, + 0xf7,0xce,0xfe,0xad,0xe0,0x1e,0xfb,0x9b,0xd8,0x24,0xb7,0xbf,0xa6,0x5b,0xdb,0x15, + 0x6f,0x7a,0xca,0x53,0xd8,0x90,0xd1,0xd2,0x9e,0x7d,0x61,0x2f,0x37,0x36,0xa0,0x3a, + 0x87,0x0e,0xd0,0x3f,0xe5,0xa9,0xd6,0x61,0xbb,0xe0,0x5f,0xff,0xb0,0xd2,0xbf,0x1e, + 0x7a,0xf5,0x81,0x18,0x1a,0x60,0x9e,0xa5,0xf3,0xfb,0x00,0x9e,0xb5,0x7b,0x2b,0xb0, + 0x53,0xb4,0x6f,0xb0,0x07,0x84,0xa8,0x78,0x19,0x08,0xee,0x19,0xd0,0xa5,0xd7,0x7e, + 0xe0,0x2d,0xf5,0x3a,0xc5,0xed,0x86,0x02,0xe9,0x71,0xad,0x29,0x97,0xba,0x56,0x5c, + 0xef,0x75,0xca,0x39,0x83,0x68,0x90,0x3a,0x59,0xf4,0x1e,0xf5,0x9c,0xec,0xbb,0x83, + 0x15,0xa1,0x63,0x5c,0x80,0x40,0x42,0x28,0x36,0x34,0xf4,0xab,0x3a,0x18,0x8b,0xce, + 0x66,0x03,0x0f,0xf8,0x91,0x62,0x4d,0xd4,0x64,0xa6,0x80,0x12,0x44,0x16,0x06,0xb6, + 0xb4,0xa0,0x7e,0x66,0x22,0x3d,0xed,0x9e,0x0b,0x6a,0x4d,0xc6,0x29,0xdc,0x6d,0x6f, + 0x6c,0xa8,0x69,0xca,0xa0,0x27,0x96,0xc5,0xdf,0x55,0x31,0x61,0xea,0x8c,0x55,0x09, + 0x43,0x67,0x1d,0x1a,0x98,0x2c,0xdd,0x84,0xf6,0xdb,0x34,0xa2,0x9e,0x9c,0x39,0x65, + 0x03,0xce,0x59,0xb9,0x5e,0x99,0x79,0x57,0x64,0x46,0x38,0x16,0x27,0xa7,0x00,0x4b, + 0xc9,0x86,0xa6,0x8a,0x09,0x05,0xba,0x06,0xd3,0xd3,0xfc,0x7a,0xb6,0x65,0xe1,0xaf, + 0xf3,0xa3,0x62,0x43,0x9f,0xcd,0xad,0x02,0x9b,0xa5,0xc4,0x05,0xc9,0xd7,0xd2,0xbd, + 0x8b,0x9c,0x15,0xd3,0xd7,0x4d,0x2b,0x10,0xac,0x31,0xc5,0x34,0xe5,0xde,0xca,0x21, + 0xb5,0xe6,0x20,0x37,0x48,0x38,0xd8,0x28,0x1c,0x8e,0x07,0x98,0x24,0x24,0x2e,0xe0, + 0xda,0x28,0xcd,0x81,0xa5,0x94,0xbd,0x13,0x93,0xc1,0x12,0x19,0xca,0x46,0x8b,0x0c, + 0xdd,0xfe,0x97,0x58,0xf7,0x96,0xc8,0x28,0x1b,0x80,0x6f,0x5b,0x1d,0xab,0xcf,0x35, + 0xea,0x9f,0xf8,0xc3,0x83,0xc4,0x20,0xfe,0x54,0xc4,0x22,0x64,0x16,0x2f,0xa8,0x0b, + 0x41,0xcd,0x15,0xa9,0xd7,0xcc,0xcd,0x5e,0x17,0x0b,0x55,0x50,0x05,0x39,0x2d,0x96, + 0x37,0xa8,0x7b,0xd7,0xb6,0x9c,0x89,0x8f,0x51,0xe6,0x11,0xe4,0xec,0x6b,0x4b,0x8a, + 0x17,0x3a,0xe0,0xa0,0x20,0x41,0x0e,0x8b,0x49,0x9a,0x29,0x80,0x20,0xb1,0xa0,0x66, + 0x7e,0xa9,0x56,0xd8,0xc2,0x6c,0xe8,0xb4,0xb8,0xc6,0x46,0x17,0x2f,0x8a,0x57,0xf4, + 0x21,0xe3,0x8d,0x16,0x94,0x05,0x1a,0xa1,0x89,0x15,0x65,0xce,0x3d,0xff,0x04,0x84, + 0xc1,0x11,0x17,0x9a,0x62,0x43,0x48,0x19,0xfd,0x50,0x38,0xcd,0x96,0x72,0x15,0xaf, + 0x77,0x95,0x2e,0xb0,0x6c,0xbc,0xe6,0x8a,0x8b,0x92,0xd7,0x95,0x53,0xd7,0xb9,0x96, + 0x8b,0x4a,0x55,0x09,0x6d,0xfa,0x9a,0xd7,0xad,0xee,0x3e,0x2b,0x49,0x85,0x1b,0xe4, + 0xf7,0x0f,0x9a,0xb0,0x69,0x5c,0x0b,0x73,0x4a,0xda,0x0c,0x17,0x21,0x9e,0x36,0x98, + 0xbd,0xa6,0xc8,0x96,0xdf,0x66,0x2b,0x79,0x3a,0xa1,0xda,0xa2,0x3d,0x73,0x78,0x67, + 0xcc,0x30,0x22,0x4c,0x4f,0xb8,0x78,0x95,0x41,0xcc,0x52,0x54,0x09,0xc4,0x2c,0xe0, + 0x2a,0xd9,0x49,0x61,0x09,0x02,0x2f,0x97,0xa3,0x30,0xd2,0x03,0xd4,0xab,0xa9,0x48, + 0x90,0x4d,0x15,0xbd,0xd2,0x54,0x12,0xc7,0x9d,0xbd,0x1e,0x39,0xcc,0xd7,0xbb,0x0a, + 0xf3,0xae,0xd6,0x54,0x6b,0x5c,0x11,0xe6,0xe2,0x86,0x48,0x2c,0x0e,0xef,0xe2,0xb4, + 0xdb,0xcc,0x1d,0xc4,0x3b,0x6d,0xc7,0x91,0x17,0x2a,0xde,0x0e,0x8a,0x9b,0x95,0x20, + 0x0a,0x94,0x5d,0x39,0x69,0xe8,0x3b,0x60,0xc1,0xcf,0x6b,0x46,0x3e,0x4c,0x22,0x77, + 0x95,0xf8,0xaa,0x08,0xa6,0x44,0x19,0xd0,0x01,0x99,0xa8,0xb0,0x5c,0xbf,0x73,0xc4, + 0x09,0x0b,0x48,0x82,0xeb,0x07,0xe5,0xaf,0xe2,0x5b,0xbf,0x31,0x2b,0xdf,0x27,0x3e, + 0x15,0xb1,0xe0,0x7c,0x15,0xa6,0x84,0xa2,0xc8,0x73,0x89,0x63,0xf3,0x70,0x71,0x64, + 0x6e,0xd1,0x72,0x34,0x0f,0xb7,0xe1,0x99,0xd6,0x98,0xb5,0x85,0x6d,0x88,0xd8,0x1b, + 0x20,0x0d,0x78,0xb0,0x34,0x8f,0x37,0xc9,0x45,0x9a,0xdb,0x05,0x1a,0xc7,0xae,0xd2, + 0x32,0xfe,0x1b,0x3b,0x48,0xf3,0xca,0x05,0x28,0xe9,0x31,0x49,0xfa,0xf8,0xbe,0x32, + 0x6e,0x26,0x7e,0xcc,0x65,0xb1,0x76,0x79,0xf2,0x4e,0x5d,0xba,0x79,0x62,0x29,0xae, + 0x47,0x10,0x70,0x37,0x1c,0x32,0x50,0xaf,0x89,0x8f,0xa1,0x40,0x18,0x85,0x03,0x1f, + 0x50,0x45,0xcc,0x60,0xfd,0x5d,0x1b,0xa8,0xae,0x8e,0x42,0x9e,0xc3,0x28,0xa4,0xb8, + 0xa1,0xdc,0x95,0x77,0x61,0x16,0xfd,0xdd,0xf7,0x6e,0xac,0x4a,0x4a,0x73,0x49,0x76, + 0xb5,0x3d,0x58,0x79,0x2e,0xb2,0x81,0x74,0x9f,0xca,0x43,0x85,0x93,0xfa,0x80,0x43, + 0x1c,0x4b,0x17,0x06,0x46,0xaa,0xe2,0xd6,0x26,0x70,0x0d,0x90,0x4b,0xfc,0x02,0xc2, + 0x37,0xf1,0x12,0x81,0x76,0xaf,0x98,0xbf,0xd5,0xb1,0x7a,0x4d,0x91,0x67,0x6b,0xbc, + 0x55,0xaa,0xec,0x3f,0x8a,0xef,0x54,0xab,0x80,0x59,0xb9,0x4f,0x27,0xe4,0x64,0xd6, + 0x94,0x00,0xf2,0x06,0xa2,0x28,0x50,0x18,0x8a,0x42,0xe7,0x15,0xa2,0xd0,0xf9,0x61, + 0x3c,0x28,0xac,0x11,0x80,0x52,0x18,0x0b,0x2f,0x49,0x44,0xbc,0x3e,0xbe,0x2a,0x46, + 0x12,0xeb,0x9a,0xf9,0xb7,0xb4,0x52,0xe4,0x9a,0xeb,0x13,0xb3,0xcc,0x37,0x05,0xc9, + 0xc0,0xd8,0xae,0xe4,0x74,0xd9,0x97,0x27,0x73,0xaf,0x88,0x3d,0x36,0xdc,0x64,0x4a, + 0x3a,0x03,0xb2,0xa5,0x8d,0x26,0x13,0x34,0xad,0xe1,0x1b,0x85,0x4a,0xa3,0x00,0xd6, + 0xe5,0x17,0xef,0xae,0x59,0x0f,0xda,0xa5,0x6a,0x7a,0x25,0xdc,0x1f,0xa4,0x06,0x11, + 0xc0,0x79,0xc4,0xb6,0xea,0x12,0x32,0x22,0xb5,0xb8,0x64,0x31,0x46,0x51,0x0d,0xad, + 0xa8,0x61,0xe4,0xec,0x60,0x14,0x2b,0x98,0xb6,0x51,0x9f,0xa3,0xe8,0xc9,0x6a,0x46, + 0x2b,0x04,0x94,0x67,0xc5,0x46,0x78,0x08,0xe5,0x45,0x5f,0x37,0x36,0xc2,0x43,0x5a, + 0xdc,0xf2,0x15,0x3f,0xe1,0x01,0xda,0x49,0xd6,0x0c,0x71,0x2f,0xa5,0x8f,0x2a,0xc1, + 0x0a,0xf1,0xa2,0x38,0x6c,0x1e,0xf3,0x49,0x72,0xa1,0x85,0x11,0xc9,0x6b,0x3e,0x82, + 0xf1,0x28,0x68,0xd5,0x8c,0x0b,0x6b,0xe8,0x2b,0x28,0x4a,0x40,0xe4,0x30,0x6a,0xe7, + 0xb7,0xa1,0xe4,0x0c,0x7b,0x35,0x14,0xe2,0x3c,0xc2,0x6d,0x84,0xcc,0xcf,0x0a,0xe1, + 0x10,0xbd,0x6b,0xa4,0x3a,0x98,0xf7,0x35,0x7b,0xc2,0x36,0xad,0x07,0x5d,0x3a,0x59, + 0x55,0xdf,0xca,0xd0,0x8a,0x49,0x6d,0x68,0x45,0xa4,0x0e,0x7d,0x76,0xbe,0xd4,0x56, + 0x9b,0x18,0xc2,0x56,0xa2,0x02,0xe1,0xf8,0x95,0x1b,0x95,0x61,0xf5,0x20,0x83,0x42, + 0x66,0x0e,0x4a,0xd9,0x8c,0x1a,0xed,0x0b,0xf9,0x90,0x87,0x66,0x86,0x0d,0x9f,0xf2, + 0xa9,0x2f,0xb1,0x2a,0xda,0xd7,0x8b,0x6a,0x01,0xc8,0x55,0x92,0x88,0x43,0x5e,0x5c, + 0x38,0x65,0x21,0x2a,0xcd,0x14,0x1d,0x59,0xb5,0x24,0x05,0x9a,0x6b,0xcd,0xa8,0x11, + 0xab,0x1e,0xdc,0x1e,0x32,0x9a,0x3b,0xa4,0x8a,0x1f,0xa8,0x65,0xdc,0x97,0x6d,0x97, + 0x6a,0x78,0x90,0x9d,0xda,0x1a,0x8c,0x4b,0xd5,0xc8,0x12,0x6d,0x3d,0xfd,0xa8,0x5c, + 0x01,0xb7,0x26,0x46,0x95,0x53,0x0d,0xd5,0x85,0x07,0x21,0x27,0x14,0xbe,0x68,0x40, + 0xbe,0xe5,0x95,0x93,0xef,0xa1,0x62,0xa9,0xa8,0x02,0x8a,0xda,0x65,0xc9,0xdd,0x12, + 0x53,0x61,0x32,0x39,0x34,0x18,0xc0,0x8e,0x02,0x07,0x36,0x43,0xc0,0xff,0x07,0xf6, + 0x53,0x81,0x99,0x7c,0xd0,0x91,0x96,0xb0,0xbd,0x8a,0x63,0x22,0xb2,0x8d,0xe6,0xd5, + 0xa2,0x8d,0x42,0x2e,0x89,0x87,0x8a,0x2c,0x5d,0xad,0x08,0x43,0x60,0x17,0xea,0xd1, + 0xa2,0xdf,0xe6,0xc3,0xb2,0x3b,0xdd,0xf6,0x93,0xaa,0x23,0x1a,0x5b,0xee,0xd7,0x5b, + 0x3f,0x7b,0x5a,0x67,0x80,0xe9,0xaa,0x59,0x87,0xf2,0xca,0x34,0xb2,0x91,0xe4,0xc3, + 0x0a,0xe4,0xab,0x80,0xb8,0x9a,0xb5,0x8b,0xb4,0xaf,0x51,0x5e,0xdd,0xb3,0x9c,0x4a, + 0x9d,0x60,0x68,0xbf,0x2f,0xb1,0x79,0x7f,0x9f,0xbf,0xa2,0x21,0xaf,0x29,0xa3,0xe9, + 0x0f,0x4a,0xe9,0xc7,0x9d,0x1e,0xa2,0x7b,0x5d,0x3b,0x02,0xad,0x94,0x26,0xc2,0xca, + 0x37,0x53,0xbe,0x41,0xa8,0xb8,0x30,0x4a,0x7e,0xab,0xd4,0xce,0x9b,0x90,0x34,0xf7, + 0xcb,0x27,0xd5,0x4f,0x92,0xe9,0x53,0xf6,0x02,0x2a,0x84,0xb7,0xe6,0x15,0xae,0x0b, + 0x55,0x6d,0xa6,0x23,0x37,0x96,0x77,0x96,0x16,0xfa,0x5f,0xcf,0x0c,0x22,0xc3,0xad, + 0xae,0x64,0xeb,0x6b,0x78,0x56,0xa8,0x9a,0xeb,0x0e,0x24,0x60,0xdf,0x9b,0xd0,0x5d, + 0x18,0xd4,0x28,0x4b,0x82,0x5f,0xf0,0xf8,0x64,0xc5,0x44,0x09,0x95,0xf3,0x43,0xe3, + 0x29,0xf8,0x41,0x5a,0xd6,0x3a,0xec,0x18,0x51,0xd4,0x5a,0xa8,0x2e,0x26,0x7d,0xe3, + 0x6b,0x0c,0x2c,0x71,0x2f,0x34,0x4a,0x85,0x54,0x79,0x45,0x69,0x81,0x7d,0x40,0x35, + 0xfe,0x60,0x34,0xc4,0x00,0xb6,0x98,0xa7,0x49,0xa1,0xc2,0x4d,0x4b,0x45,0x9f,0x7a, + 0xb6,0x7d,0xf1,0x35,0xf9,0x1a,0x5e,0x0a,0xd1,0x80,0x8e,0x25,0x54,0x05,0xb5,0x13, + 0x47,0x7d,0x9a,0x8f,0x49,0xd2,0x3e,0x75,0x13,0x54,0x25,0x60,0x21,0xf1,0x01,0x2d, + 0xe6,0x06,0x39,0x54,0x79,0x9e,0x31,0x51,0x5c,0x38,0x00,0xf3,0x0c,0x65,0x73,0xfa, + 0x04,0x2f,0x78,0xe0,0x42,0x59,0xb6,0xc4,0x5b,0xbf,0x30,0x2b,0x0f,0x3c,0xac,0x42, + 0xa9,0xa2,0x8e,0x4f,0xbb,0xb7,0x1c,0x55,0x72,0xad,0x16,0xf3,0x34,0xac,0xb7,0x9e, + 0x79,0x01,0x3a,0x01,0x2b,0xc5,0x9e,0x63,0x16,0x16,0x8e,0x56,0xef,0x00,0x8f,0x08, + 0x97,0x5a,0xdb,0xb2,0x3b,0x75,0xdb,0xb0,0x46,0x13,0x71,0xa6,0x85,0x73,0x2c,0x96, + 0xa8,0xdb,0xca,0x72,0x30,0xe5,0x47,0x3e,0x28,0xc5,0x63,0x92,0x52,0x8e,0xc1,0x34, + 0xdc,0x7f,0x4d,0xee,0xbf,0x86,0xdb,0x35,0x1c,0x75,0xae,0xbf,0x0a,0x48,0x7f,0xc5, + 0xd0,0xe4,0x29,0x0c,0xee,0xef,0x03,0xc9,0x84,0xb4,0x91,0x2b,0x31,0x9f,0x4b,0xc1, + 0xb4,0x5f,0xd1,0x99,0x02,0x1b,0x48,0x27,0xe1,0xa9,0x08,0x06,0x80,0x31,0x83,0xb3, + 0xb4,0x11,0x0c,0x02,0x8d,0x95,0x6a,0x1e,0xf7,0x2e,0x8e,0x7e,0xb8,0xbc,0x47,0x63, + 0x56,0xd4,0xec,0x7e,0x4d,0x7f,0x84,0xf7,0xe7,0x97,0x96,0x52,0x96,0x61,0x3d,0x59, + 0x14,0x90,0xd1,0x23,0xaa,0xdd,0x5d,0xb2,0x45,0x26,0x2b,0xe6,0x51,0xe2,0xc7,0x19, + 0x71,0x3b,0xb9,0xa2,0x21,0x28,0x28,0x13,0x70,0xfc,0xba,0x8f,0xe0,0xe7,0x77,0x67, + 0xbf,0xa0,0x49,0xc3,0xf6,0xbf,0xc8,0xee,0xfd,0x87,0x6d,0x9b,0x0d,0xe0,0xd3,0x3a, + 0xcb,0x77,0x76,0xf8,0x28,0x18,0xc0,0xeb,0xe6,0x99,0xb9,0x31,0xbc,0x8d,0x15,0x2a, + 0xcb,0x7e,0xa8,0x93,0x05,0xf9,0x92,0xab,0x8c,0xd0,0x21,0xa7,0x45,0x8f,0x80,0xed, + 0x7f,0xe9,0xa5,0x6a,0x33,0x34,0xa5,0xc9,0xfe,0xbd,0x34,0xf1,0xb7,0x20,0x37,0x9b, + 0x3a,0x56,0xfd,0x00,0xb6,0xff,0x95,0xfb,0x57,0x40,0xb6,0xb1,0xf0,0xaf,0x48,0x73, + 0x07,0x0b,0x74,0xc9,0xf0,0x6e,0xd4,0xb1,0x41,0xc1,0xbf,0x22,0x15,0x75,0x14,0xbd, + 0x1d,0x10,0x4e,0x98,0x92,0x56,0xfc,0x1d,0x48,0x73,0x66,0x70,0xe0,0x9a,0x26,0x9a, + 0x70,0xde,0xc7,0x37,0x63,0x98,0x3c,0x28,0x20,0xe2,0xc8,0xb0,0x11,0x0e,0x2d,0x95, + 0x82,0x8b,0x35,0xde,0x88,0xa0,0x55,0x41,0x76,0xdf,0x85,0x72,0x49,0xd9,0xf0,0x1e, + 0xea,0x70,0x8d,0x9b,0x04,0x6d,0xc1,0x69,0xd9,0x08,0x59,0x22,0x37,0xc2,0x43,0x3f, + 0x07,0x69,0xc4,0x97,0xd7,0x8c,0x86,0xf9,0x85,0x8a,0x69,0xaa,0x95,0x99,0xfe,0x1f, + 0xa8,0x55,0xaa,0x5b,0x2a,0xb5,0x96,0x62,0xa0,0xe5,0x7d,0x88,0x93,0xeb,0xaf,0xb8, + 0xf1,0x52,0x09,0x81,0xfd,0xe9,0x0a,0x6c,0x2a,0xc4,0xb2,0x85,0x35,0xfd,0xd9,0x4f, + 0xaf,0x52,0x5e,0xf0,0x4b,0x3d,0x5a,0xbd,0xd2,0xd0,0x11,0x65,0xa9,0xac,0xe3,0x91, + 0x30,0x1f,0x40,0x94,0xaf,0x59,0xe5,0x22,0x80,0x1e,0xe9,0x7c,0x91,0x1e,0x63,0x98, + 0xb6,0x67,0x14,0xc5,0x3e,0xa1,0x13,0xff,0x65,0xfe,0x3c,0xe8,0xf4,0xd5,0xf2,0xc2, + 0x04,0xb1,0xcf,0x54,0x57,0x59,0xe1,0x14,0x54,0x27,0x54,0x82,0x16,0x5d,0x7b,0xc9, + 0x40,0x23,0x83,0x47,0xc2,0x6b,0x97,0xa5,0x71,0xf8,0x68,0x31,0xb5,0xc7,0x1c,0x52, + 0xa4,0x11,0x59,0x64,0x7c,0x32,0x04,0xc5,0xa0,0x08,0x13,0x90,0xe7,0xbd,0x41,0x89, + 0xb8,0xf6,0x57,0x1c,0x77,0xdb,0xa8,0xce,0x79,0xe4,0xb0,0x5a,0xe9,0x2e,0xe8,0xb8, + 0xda,0xa4,0xc2,0xa3,0xc9,0xfc,0xff,0xeb,0xed,0x6a,0x7b,0xda,0x38,0x82,0xf0,0x77, + 0xff,0x0a,0x73,0x1f,0x90,0x2f,0xb6,0xc1,0xd0,0x24,0x6d,0x6d,0x8c,0x95,0x54,0x4a, + 0x85,0xd4,0xa6,0x15,0x51,0xd5,0x0f,0x6d,0x3f,0xd8,0xf8,0x30,0x16,0xc6,0x47,0x7c, + 0x87,0x09,0x05,0xfe,0x7b,0xe7,0x99,0x99,0xbd,0x7d,0xb9,0x3b,0x1f,0xea,0x5b,0x14, + 0x01,0xf6,0xed,0xde,0xee,0xce,0xee,0xce,0xee,0xbc,0x3d,0x63,0x50,0x6a,0x1b,0xaa, + 0x5f,0xb9,0x75,0x01,0x67,0x77,0xe5,0xd9,0xba,0xa9,0xaf,0x93,0xe8,0x9c,0xad,0x39, + 0xcb,0xcc,0x9c,0x14,0x93,0x68,0xd8,0xd1,0x2f,0x89,0x6f,0xb4,0xa3,0xae,0x33,0x1a, + 0xd7,0x50,0x44,0x4b,0x8b,0x33,0x4d,0x4c,0x4c,0x37,0x5d,0x7b,0xb9,0x41,0x78,0xbd, + 0x37,0xb0,0xaf,0xee,0x39,0x56,0xdc,0x9e,0x59,0xf7,0x02,0x4c,0x7c,0xdc,0x9f,0x4d, + 0xbe,0x1e,0xa4,0xea,0xa1,0xeb,0x62,0xe7,0xbe,0x0b,0x44,0xae,0x8b,0x2b,0x36,0x6e, + 0x55,0xaf,0xa9,0x42,0xd9,0xd2,0x64,0xf3,0x77,0x4d,0xfe,0xd1,0x6a,0x1d,0x99,0x0b, + 0x7d,0x5d,0x2d,0xce,0x99,0xbd,0xf6,0x68,0xc5,0x18,0x94,0x74,0x85,0x9c,0x3e,0x74, + 0xee,0xfb,0x3a,0xa8,0x65,0xf7,0xa8,0x30,0xa1,0x1d,0xc5,0x05,0xa0,0x09,0x20,0xe1, + 0xf0,0x88,0xa8,0xd5,0x8e,0xe0,0x6f,0xe0,0x52,0x66,0x8d,0xde,0x78,0xae,0x05,0x7e, + 0x1f,0x90,0x87,0x07,0xae,0x05,0xb4,0x05,0x2a,0xe8,0x3a,0xb7,0x32,0x35,0x2f,0x51, + 0x63,0x2d,0xd9,0x3d,0xfc,0xdc,0x1d,0xfe,0x7d,0x04,0xb7,0x1b,0x0f,0xa0,0xe0,0x27, + 0x84,0x56,0xc8,0x91,0xb3,0xc9,0xe0,0x6c,0xa3,0xeb,0x9c,0x06,0x20,0x46,0x3f,0x89, + 0x3f,0xe7,0x78,0xc7,0xb9,0x30,0xcf,0x84,0x0f,0x10,0x81,0x20,0x01,0xef,0xbc,0x95, + 0x44,0x59,0x61,0x7f,0x73,0xa3,0x96,0xe5,0xbd,0x62,0x51,0x64,0x44,0x74,0xde,0xdd, + 0xeb,0x4d,0xd8,0xeb,0x8d,0xdf,0xeb,0x5f,0xdf,0x9d,0x7f,0x3c,0xfb,0xf8,0xfd,0x50, + 0xd0,0x32,0xa2,0xae,0x34,0x21,0x73,0x30,0xe2,0xf3,0x05,0x9a,0xc1,0xaa,0x5e,0x6d, + 0x4c,0xaf,0x4a,0x3e,0x01,0x48,0x08,0xbc,0x62,0xc7,0x7e,0xbd,0xb0,0xc1,0x83,0xcf, + 0xe3,0x62,0xca,0x63,0xe8,0x1c,0x94,0x23,0xea,0xa9,0x38,0x69,0x63,0xa3,0x67,0xbe, + 0x20,0xc1,0xc8,0xc2,0xba,0xed,0x1c,0xe3,0x6d,0x38,0xc6,0xdb,0x9a,0x31,0x9a,0x58, + 0xd7,0x20,0x90,0x92,0x27,0x6c,0xc8,0xd7,0xbf,0xc0,0x01,0xa1,0x08,0xaf,0x3c,0x85, + 0xeb,0x01,0xac,0xb9,0x05,0xd8,0x08,0x1f,0x78,0x9b,0xe4,0xf2,0x8e,0xf3,0x8c,0x1b, + 0x4f,0x06,0x89,0x2f,0xd4,0x38,0x45,0x89,0x02,0x7a,0x40,0x38,0x6f,0x15,0x01,0x6f, + 0x0b,0xa8,0x83,0x59,0xbe,0xce,0x1a,0xc6,0x88,0x22,0xee,0x30,0xf1,0x59,0x77,0xe0, + 0x22,0xad,0xad,0x5b,0x64,0xa9,0x5d,0xa4,0x6e,0xe5,0x45,0x8a,0x8c,0x55,0x69,0x25, + 0x1b,0x33,0x8c,0xab,0x8e,0x65,0x15,0xa8,0x19,0x2f,0x68,0x75,0x9d,0x06,0x20,0x4e, + 0xd3,0xf5,0x45,0xb2,0x02,0x29,0x30,0x1a,0x97,0x16,0x8b,0x54,0x87,0xe8,0xed,0xf2, + 0x54,0x34,0xcb,0xee,0x77,0x28,0xe4,0x5e,0x90,0x71,0x95,0x5b,0x15,0xd7,0x63,0x7e, + 0x85,0xea,0xa6,0x4d,0x1e,0x98,0x06,0xc2,0x66,0xc1,0xe2,0xc9,0xca,0x8e,0x4c,0x61, + 0x1f,0xf8,0x96,0x0e,0xfa,0xa5,0xeb,0x42,0x45,0x36,0x5e,0xfb,0x1f,0x1b,0x15,0x66, + 0x23,0x79,0x01,0xeb,0xc4,0xc6,0xbe,0x9a,0x01,0x03,0x32,0xd3,0xb1,0x56,0x64,0x97, + 0xa1,0xf9,0x50,0x3b,0x31,0x28,0xc5,0x92,0x23,0x15,0x94,0x63,0x94,0xda,0x58,0xef, + 0x6a,0x43,0xe7,0x63,0x25,0xec,0xc8,0x24,0x35,0x04,0x0a,0x19,0xd0,0x01,0x0e,0xa2, + 0x3a,0x3f,0xa6,0xb2,0x67,0x17,0x51,0x28,0x1e,0x35,0x39,0x64,0xf9,0x2f,0xd3,0x14, + 0x63,0x22,0x54,0x08,0x62,0x9a,0x38,0x41,0xb4,0xd5,0x1c,0xb6,0xa6,0xff,0x19,0x47, + 0x0c,0x4b,0x90,0xe3,0x0d,0x47,0x3a,0x3e,0xdc,0x52,0x1f,0x4b,0xb6,0x21,0x11,0xe9, + 0x1d,0xbf,0xd2,0xec,0x61,0xb7,0x1e,0xbc,0x07,0x96,0xec,0xa8,0xa2,0x8c,0x44,0xe4, + 0x0b,0x67,0x2c,0x1f,0x41,0x13,0xe8,0xe8,0x1f,0xad,0x66,0xd1,0x51,0x8a,0xa9,0x3c, + 0xcc,0x2f,0xe5,0x14,0xc6,0x24,0xbb,0x93,0xc0,0x6f,0x3f,0x67,0x11,0x0b,0x9e,0x2a, + 0xd4,0xbb,0x32,0xa4,0x29,0xc3,0xde,0x42,0xb6,0xca,0x44,0x2a,0x88,0x93,0xd1,0xc8, + 0xd7,0x7a,0xcb,0x4d,0xe9,0x37,0x14,0xfd,0x23,0x76,0xe5,0x5a,0x73,0x41,0xd3,0x47, + 0xae,0xc8,0x89,0x45,0x61,0xbe,0x0e,0xc8,0xf7,0x9e,0xa8,0x45,0xeb,0x4d,0x5c,0x99, + 0x98,0x76,0xc4,0x0c,0xc2,0xe9,0x2a,0x90,0xf2,0xf4,0xd1,0x01,0x3f,0x22,0x46,0x12, + 0x3c,0x02,0xc9,0x60,0x85,0x51,0x1d,0x03,0xdb,0xe2,0x7f,0x4c,0xe6,0xcb,0x69,0x27, + 0x22,0x99,0x87,0x83,0x06,0x86,0x97,0x92,0x0e,0x4b,0x1e,0x26,0x59,0xfc,0x22,0xb7, + 0x96,0x62,0x55,0x73,0x37,0xb9,0xcf,0x0a,0xd8,0x64,0x1d,0xb0,0xf8,0x40,0x4c,0x3e, + 0x2f,0xe7,0xe3,0x9b,0xeb,0xf3,0xe4,0xf3,0xd9,0xbc,0x60,0x02,0xec,0x37,0x34,0xf6, + 0x45,0x7f,0xf7,0xda,0x7a,0x34,0x89,0x36,0xba,0xbf,0x06,0x87,0xc1,0x0e,0x93,0xed, + 0x27,0xdb,0xcb,0x05,0xa8,0xe2,0xac,0xd6,0x8f,0xdc,0xde,0x90,0x7f,0xf2,0x1b,0x87, + 0xf8,0x51,0x05,0x59,0x85,0xb5,0x43,0xbb,0xa1,0x63,0x8b,0xf6,0x06,0x3d,0xe9,0x58, + 0x4f,0xc1,0xb4,0xab,0x90,0xa7,0x5a,0x2e,0xf4,0xd4,0xb8,0x80,0x9e,0x7a,0x34,0x44, + 0x90,0xfc,0x72,0x23,0x79,0x91,0xe5,0x7d,0xce,0x8a,0x49,0xd4,0x6d,0x0a,0xa8,0xde, + 0x83,0xd7,0xa2,0x00,0x41,0x66,0x60,0x69,0x5a,0x22,0xb0,0x0b,0x7f,0xb3,0xab,0x69, + 0x26,0x0a,0x0f,0xce,0xb5,0xde,0xa6,0x1d,0xce,0xb3,0x56,0xc7,0x20,0xea,0x9a,0xf9, + 0x76,0x7f,0x1f,0xd6,0x15,0x1a,0xe9,0xde,0x78,0xcc,0xbf,0x4b,0xed,0xbe,0xc3,0xfb, + 0x00,0x7c,0x60,0xf6,0x7d,0x81,0xd7,0x67,0x66,0xa2,0xdf,0x17,0x64,0xbe,0x36,0xfb, + 0x41,0x02,0xa3,0xab,0xbe,0xc1,0xc1,0xd3,0x93,0xfb,0xf9,0xe8,0xab,0xb0,0xbd,0x82, + 0x86,0x8c,0x99,0xa5,0xc8,0x85,0x33,0x8b,0x4a,0xe8,0xbe,0xfc,0x25,0x53,0x55,0xf6, + 0x87,0x2e,0x9a,0xba,0xc9,0x16,0xce,0x2a,0xd5,0x09,0x6a,0xe9,0x0c,0x39,0xa7,0x0d, + 0xe7,0x73,0xd5,0xaf,0x4b,0x30,0x5d,0x81,0xab,0xa9,0x65,0xac,0x95,0x0f,0x0c,0x8f, + 0x0d,0xec,0xae,0xc1,0x20,0x00,0xe8,0x61,0xc6,0x41,0x8d,0x67,0xbd,0xe5,0x7c,0x95, + 0x64,0x1e,0x62,0x3b,0xd5,0x3a,0x94,0xf8,0xaa,0x89,0xec,0xa4,0xa8,0x4b,0x93,0x43, + 0xf4,0xf9,0xe5,0xfc,0x0c,0x79,0x43,0xe9,0x5c,0xa2,0xb3,0x53,0x66,0xb4,0x1b,0xed, + 0xe3,0x8d,0x54,0x84,0x5f,0xfc,0x98,0x8b,0x7a,0x7c,0xf8,0x66,0x30,0x18,0x3c,0xc7, + 0xad,0x9d,0x79,0xe6,0x00,0xa2,0x86,0xd6,0x23,0xc1,0x9c,0xdb,0xb5,0x50,0x7e,0xa0, + 0x2d,0x07,0xe5,0xd0,0xc5,0x35,0xc2,0x27,0xc4,0xcb,0xc1,0xac,0x19,0x5e,0x22,0x8c, + 0x35,0x01,0x67,0x4d,0x85,0x80,0x4c,0x8c,0x00,0xce,0x2d,0x05,0xab,0xc6,0xc9,0x73, + 0xc7,0xed,0x4b,0xaf,0x98,0x10,0x27,0x6f,0x3c,0x2d,0xbf,0xaf,0xab,0x7f,0x19,0x2d, + 0xe1,0x81,0xd3,0x7b,0xed,0x66,0xbe,0x2b,0xed,0x36,0xbd,0x3f,0xae,0xe1,0x10,0xc8, + 0x6e,0x91,0xcb,0xad,0x62,0x86,0xea,0x49,0x6e,0x3b,0x8c,0xf9,0x2c,0xc1,0x4f,0x86, + 0x02,0xdc,0x17,0x16,0xe0,0x7c,0xdf,0x53,0x56,0x3e,0xa2,0x87,0xdd,0xa5,0x40,0xbd, + 0xaa,0xf4,0x7c,0x2d,0xa9,0x87,0xf7,0x34,0xdb,0x30,0xfe,0x1e,0xee,0xc1,0xf8,0xf9, + 0x8a,0x86,0x10,0x40,0x57,0x3a,0xf5,0xf8,0xf3,0xd8,0x7b,0xe0,0xe8,0xfb,0x4e,0x7f, + 0xcf,0x26,0xd6,0x09,0x4e,0x60,0x2f,0x4d,0x77,0xd2,0xeb,0x09,0xbb,0xbd,0x0e,0x79, + 0xb1,0xf7,0xe4,0xa1,0xea,0x38,0xd1,0xbd,0x12,0xba,0xa6,0x83,0x62,0xec,0xac,0x94, + 0x39,0xe3,0x32,0x3f,0x06,0x3a,0xd7,0xd3,0xa3,0xb8,0x62,0xf7,0x14,0x8c,0x5c,0x96, + 0x25,0xc0,0xe9,0xca,0xfc,0xbc,0x2e,0xa7,0x67,0xc3,0x44,0x63,0xf3,0x3f,0xf7,0x8e, + 0x39,0xc5,0xa7,0x9d,0xa2,0x90,0xf7,0x96,0xb6,0x3e,0x2c,0x12,0x97,0x00,0x0d,0xe5, + 0x17,0x6e,0xfe,0x27,0x56,0x1f,0xb0,0xc4,0x4a,0xd6,0x6f,0x16,0x63,0xca,0x78,0x0c, + 0x88,0xf4,0x9a,0x66,0x9c,0x8b,0x05,0x53,0xc2,0x3e,0xd2,0xde,0x7e,0x0b,0x39,0x30, + 0xad,0xfb,0x93,0xe3,0xc1,0x3f,0xd8,0x35,0xdd,0x23,0xe5,0x41,0xcf,0xbd,0xaf,0x77, + 0xec,0x1a,0x61,0x01,0x82,0x5c,0x63,0x62,0xa2,0x05,0x7a,0xd4,0xbb,0x39,0xe2,0x64, + 0xf2,0x0e,0x8f,0x66,0xce,0x50,0xc1,0xc9,0xbd,0xa9,0x52,0xdd,0x40,0x7a,0x07,0x90, + 0x27,0x85,0x09,0xde,0xa1,0x9d,0x63,0xdf,0x29,0x47,0x97,0x68,0x64,0xd9,0x58,0x05, + 0x62,0x81,0x31,0x96,0x62,0x1d,0xa2,0x5a,0x01,0x0c,0x1e,0xbb,0x85,0x4d,0x73,0xea, + 0x91,0xaa,0x4e,0x9b,0xf2,0x65,0x6c,0x53,0xa5,0x7c,0x4a,0x2f,0x8d,0x35,0xcc,0x85, + 0xee,0x84,0x37,0x3d,0x0c,0x17,0x2e,0x57,0xe0,0xdd,0x07,0x5b,0x87,0x03,0x9e,0xca, + 0x5a,0x52,0x03,0x30,0xcc,0xa4,0xd3,0x63,0xf1,0xa0,0xfd,0x01,0x28,0x4e,0x0a,0x2f, + 0xc6,0x52,0xb1,0xf1,0xbf,0x97,0xac,0x18,0x07,0x65,0xac,0x59,0xdb,0xfe,0x7f,0x8b, + 0xce,0x69,0x5b,0x6d,0x98,0x84,0x2a,0x0d,0x7b,0x41,0xe0,0xba,0x0e,0x72,0x1d,0x45, + 0x68,0x72,0xb1,0x5d,0x05,0xe1,0x85,0xfa,0x37,0xe3,0xa7,0x44,0x1a,0x64,0x7e,0xa5, + 0x5e,0x29,0x50,0xeb,0xcf,0x6e,0x3d,0xb9,0x28,0x1a,0x27,0x5a,0xd7,0x8a,0xd0,0xd8, + 0xfe,0xbf,0x02,0x5f,0x1a,0xae,0xe7,0x0f,0x22,0x1b,0xb1,0x93,0xeb,0xd6,0x64,0x44, + 0xdf,0x2e,0xb3,0xbb,0xe9,0x0a,0xce,0x50,0xd0,0xfc,0xf7,0xac,0x56,0xd5,0x38,0x24, + 0xe4,0x4f,0x4f,0xfc,0x55,0x1f,0xd7,0xdc,0x5d,0x4e,0xe3,0x52,0x6e,0xdb,0x27,0x79, + 0x70,0x67,0xb9,0x42,0x88,0xca,0x55,0x5d,0xa9,0x79,0xd5,0x49,0x76,0x6a,0x57,0xff, + 0x83,0xb5,0x09,0xa7,0xbe,0x46,0x77,0x70,0xad,0xec,0x2a,0x49,0x72,0x00,0x69,0x70, + 0x65,0xf1,0xd0,0xdd,0x7a,0xb2,0x1e,0x80,0x5c,0xc6,0xb9,0x46,0x79,0x9b,0x7b,0x91, + 0x05,0xef,0xa7,0xce,0xde,0x70,0x62,0x17,0x58,0x35,0xdf,0x23,0x15,0x29,0x51,0xfb, + 0x3b,0x8e,0x8a,0xe2,0x44,0xed,0x24,0x99,0xa6,0x39,0x51,0xb6,0x9f,0x37,0x14,0xe8, + 0xbe,0x55,0xd9,0x42,0x90,0xcc,0x81,0x47,0x31,0x23,0xa9,0x8f,0x5d,0x56,0xb2,0x59, + 0x25,0x21,0x04,0x75,0x34,0x16,0x10,0xfe,0x31,0x15,0x2a,0x77,0x32,0xa7,0x1b,0x5e, + 0x7d,0xcb,0xf4,0xb4,0xbf,0xdd,0x6a,0x35,0xba,0x0c,0x6a,0xac,0x17,0x50,0x1d,0x24, + 0x35,0x3c,0xe7,0x22,0xee,0x6c,0x4d,0xd2,0xfa,0x3e,0x2a,0x48,0x36,0x00,0x89,0x9c, + 0x42,0xc9,0x13,0x50,0x28,0x0e,0x66,0xc1,0x22,0xfd,0x7e,0x33,0xe8,0x71,0x31,0xba, + 0xde,0xdd,0x7e,0x89,0xec,0x74,0x05,0xda,0xe5,0xec,0x6e,0xc1,0xb8,0x2c,0x4e,0xb3, + 0x3e,0xd1,0x5f,0x0d,0x0e,0xde,0x1e,0xcb,0x4b,0xe2,0x3a,0xe5,0x7d,0xa3,0xce,0x80, + 0x99,0x4b,0xe5,0x92,0xa5,0x15,0x5d,0xf9,0x7d,0x85,0x75,0x9c,0x76,0xd5,0xf2,0xcf, + 0x24,0xea,0xc9,0x5e,0xb0,0xb9,0x8c,0x1a,0x2b,0x4a,0x5f,0x9c,0x8a,0x6e,0x22,0x20, + 0x88,0xb2,0x34,0x39,0x1d,0x85,0xea,0x15,0xcf,0x27,0x89,0x89,0xaa,0xcd,0x20,0xa5, + 0xaa,0xd2,0x3d,0x1b,0x3e,0x65,0xbd,0x7f,0x38,0x80,0x56,0xdc,0x55,0xf8,0x4f,0xe1, + 0xfe,0x4e,0xa4,0xd8,0x73,0x75,0x0e,0x26,0xab,0x41,0x41,0x17,0x96,0xc8,0xa0,0xb6, + 0x14,0xd3,0xd5,0xfd,0x15,0x44,0xb8,0x7c,0x3a,0x1b,0x31,0xf0,0x30,0x02,0x92,0x71, + 0x83,0x5e,0xe6,0x2d,0xc3,0x15,0x46,0x0e,0x79,0xff,0xb6,0xa8,0xfe,0xa8,0xba,0xad, + 0x4f,0xdc,0xad,0xa1,0x49,0x90,0xfc,0xdc,0x12,0x04,0xe8,0x51,0xeb,0xe4,0x50,0xcc, + 0xba,0xa7,0xf4,0x17,0x52,0xcd,0xe3,0x37,0x92,0xdc,0x9d,0xb6,0xfe,0x02,0x2e,0x67, + 0xf7,0x3d,0xb7,0x57,0x01,0x00, }; diff --git a/src/helpers/esp32/WebConfigServer.cpp b/src/helpers/esp32/WebConfigServer.cpp index 25f139ce..3abe763a 100644 --- a/src/helpers/esp32/WebConfigServer.cpp +++ b/src/helpers/esp32/WebConfigServer.cpp @@ -20,6 +20,7 @@ #ifdef WITH_MQTT_BRIDGE #include #include + #include #include #endif @@ -58,6 +59,89 @@ static bool isSecretKey(const char* key) { return key && wcIsSecretKey(key); } +// Commands whose CLI handler never returns would take the node down mid-drain, +// before the client could read a single result. `reboot` is deferred instead: +// it is not passed to the CLI at all, and the batch arms the ordinary reboot +// path once the operator has read the results. The rest (clkreboot, poweroff, +// ota update) do real work on the way down and cannot be faked, so they run +// normally and the connection drops -- the UI warns before sending them. +// CommonCLI dispatches on a 6-byte PREFIX (memcmp(command, "reboot", 6)), so +// `reboot now` and `rebooted` reach Board::reboot() too. Matching exactly here +// let those through to the CLI, which took the node down mid-drain with no +// results and no deferral -- the precise failure the deferral exists to avoid. +// Whatever CommonCLI would treat as a reboot, this must intercept. +static inline bool wcIsDeferredReboot(const char* cmd) { + return strncmp(cmd, "reboot", 6) == 0; +} + +// Commands the CLI reaches but the portal cannot honestly serve. Rejected at +// POST so nothing in the sequence runs, rather than failing halfway with a +// reply that does not explain itself. Returns the reason, or NULL if fine. +static const char* wcCliUnavailable(const char* cmd) { + // ESP32Board::startOTAUpdate() does `new AsyncWebServer(80)` with no bind + // check and answers "Started" regardless. The portal already holds port 80, + // so from here it can only leak the allocation, inhibit sleep, and lie. + if (strncmp(cmd, "start ota", 9) == 0) { + return "start ota needs port 80, which this portal is using. " + "Run it from the serial console, or use `ota update`."; + } + // `clock sync` sets the clock from the CALLER's timestamp. Web requests carry + // none (execCommand passes 0), so CommonCLI always rejects it as moving the + // clock backwards. `time ` is the one that works over this transport. + if (strncmp(cmd, "clock sync", 10) == 0) { + return "clock sync takes its time from the caller, which a web request has " + "no way to supply. Use `time ` instead."; + } + // Both write their real output to Serial and hand back a stub the terminal + // would render as success. Bare `log` also streams a whole file from the loop + // task, stalling the mesh and the radio while it does. + if (strcmp(cmd, "log") == 0) { + return "log writes the packet log to the serial console, not here, and " + "blocks the radio while it does. Use `log start` / `log stop`."; + } + if (strcmp(cmd, "get acl") == 0) { + return "get acl writes to the serial console, not here."; + } + return NULL; +} +// The `password` command echoes the new password back in its reply, and replies +// are served to the client over the open setup AP. The config path overwrites it +// by key; a CLI entry has no key, so match on the command itself. +static inline bool wcCliEchoesSecret(const char* cmd) { + return strncmp(cmd, "password ", 9) == 0; +} + +// CommonCLI splits its surface by CALLER, not by command: a serial caller +// (sender_timestamp 0, physical access) reads secrets in plaintext, while a +// remote one gets "******** (serial only)". Its own comments say so -- "Serial +// only (WiFi creds grant LAN access); remote sees set/unset". +// +// execCommand passes 0, which is what makes `erase`, `stats-*` and `set freq` +// reachable from the terminal at all. Left alone, that also claims +// physical-access trust for an HTTP request: `get prv.key` would hand this +// node's identity to anyone associated with the open setup AP, and `get +// wifi.pwd` would hand over the operator's network. Reading a secret and +// writing one are not the same capability -- the wizard has always been able to +// REPLACE these; nothing in the portal could ever READ them, because +// /api/config masks them (wcIsSecretKey). +// +// So the command surface stays whole and only the READ is masked, restoring the +// distinction CommonCLI intended for a caller who is not at the serial port. +// Which commands those are lives in WebConfigKeys.h (wcIsSecretReadCommand), +// beside the rest of the secret classification and host-tested with it. + +// Keep the set/unset signal, which is the useful part and what CommonCLI itself +// reports remotely; only the value goes. A getter answers "> value". +static void wcMaskSecretReply(char* reply) { + const char* val = reply; + if (val[0] == '>' && val[1] == ' ') val += 2; + const bool unset = (val[0] == 0 || strcmp(val, "(not set)") == 0); + strcpy(reply, unset ? "> (not set)" : "> ******** (serial only)"); +} +// Reply classification lives in WebConfigBatch.h with the rest of the decisions +// (WebConfigBatch::cliReplyIsFailure / cliReplyGatesReboot / cliWriteSucceeded), +// so the shapes CommonCLI actually emits are enumerated in one host-tested place. + // Constant-time-ish comparison so login timing doesn't leak a prefix match. static bool fixedTimeEquals(const char* a, const char* b, size_t max_len) { size_t la = strnlen(a, max_len), lb = strnlen(b, max_len); @@ -90,13 +174,29 @@ struct WCLock { WebConfigServer* WebConfigServer::_active = NULL; AsyncWebServer* WebConfigServer::_host = NULL; static volatile bool webconfig_button_toggle_requested = false; + +// Out-of-line definitions for the in-class-initialised constants. An in-class +// initialiser is only a declaration under C++11 (what the xtensa-esp32 +// toolchain builds with), so any use that binds a reference rather than reading +// the value -- ArduinoJson takes its argument as `const T&` -- needs the symbol to +// exist. Comparisons like `count >= MAX_BATCH` never did, which is why this only +// surfaced when MAX_BATCH started being reported in JSON, and then only on the +// targets where the compiler happened not to fold it. +const int WebConfigServer::MAX_BATCH; +const size_t WebConfigServer::MAX_BODY; +const uint32_t WebConfigServer::STOP_WARN_MS; + +// Protects the permanent route host's active-session pointer and handler +// references across the loop and async_tcp cores. The critical sections only +// copy a pointer/update a counter; handlers themselves never run under it. static portMUX_TYPE s_wc_route_mux = portMUX_INITIALIZER_UNLOCKED; WebConfigServer::WebConfigServer(Callbacks* callbacks, void* mqtt_prefs, bool owns_wifi, const uint8_t* pub_key, const char* fw_ver, + const char* build_date, const char* role, const char* board_name) : _cb(callbacks), _mqtt_prefs(mqtt_prefs), _owns_wifi(owns_wifi), _pub_key(pub_key), - _fw_ver(fw_ver), _role(role), _board_name(board_name) { + _fw_ver(fw_ver), _build_date(build_date), _role(role), _board_name(board_name) { _mux = xSemaphoreCreateMutex(); _cli_enabled = loadCliEnabled(true); @@ -645,10 +745,8 @@ void WebConfigServer::finalizeTeardown() { _setup_wifi_handoff_pending = false; _setup_wifi_handoff_deadline = 0; _setup_wifi_handoff_ip[0] = 0; - _cli_state = CLI_IDLE; - _cli_reqid[0] = 0; - _cli_command[0] = 0; - _cli_reply[0] = 0; + _batch_kind = BATCH_CONFIG; + _admin_pwd_set = false; _session_token[0] = 0; _stats_json[0] = 0; if (_cb) _cb->onWebConfigStopped(); @@ -763,7 +861,6 @@ void WebConfigServer::tick(uint32_t now) { if (_dns) _dns->processNextRequest(); - if (_cli_state == CLI_PENDING) drainCliCommand(); if (_setup_wifi_handoff_pending) serviceSetupWiFiHandoff(now); if (_batch_state == BATCH_PENDING && !_setup_wifi_handoff_pending) { drainBatch(now); @@ -818,26 +915,6 @@ void WebConfigServer::tick(uint32_t now) { } } -void WebConfigServer::drainCliCommand() { - char command[160]; - { - WCLock lock(_mux); - if (_cli_state != CLI_PENDING) return; - strncpy(command, _cli_command, sizeof(command) - 1); - command[sizeof(command) - 1] = 0; - } - - char reply[160] = ""; - _cb->execAdminCommand(command, reply); - if (reply[0] == 0) strcpy(reply, "(no reply)"); - - WCLock lock(_mux); - if (_cli_state != CLI_PENDING) return; - strncpy(_cli_reply, reply, sizeof(_cli_reply) - 1); - _cli_reply[sizeof(_cli_reply) - 1] = 0; - _cli_state = CLI_DONE; -} - void WebConfigServer::finishBatch(uint32_t now) { WCLock lock(_mux); _batch_state = BATCH_DONE; @@ -916,10 +993,28 @@ void WebConfigServer::drainBatch(uint32_t now) { BatchEntry& e = _batch[_batch_next++]; e.reply[0] = 0; uint32_t t0 = millis(); - const bool admin_pwd = wcIsAdminPasswordKey(e.key); - const char* value = admin_pwd ? e.cmd + strlen("password ") - : e.cmd + strlen("set ") + strlen(e.key) + 1; - if ((_mqtt_prefs == NULL || !_owns_wifi) && strcmp(e.key, "wifi.ssid") == 0) { + if (_batch_kind == BATCH_CLI) { + if (wcIsDeferredReboot(e.cmd)) { + strcpy(e.reply, "OK - reboot queued"); + } else { + _cb->execAdminCommand(e.cmd, e.reply); + } + if (e.reply[0] == 0) strcpy(e.reply, "(no reply)"); + const bool set_admin_pwd = wcCliEchoesSecret(e.cmd); + if (set_admin_pwd) { + strcpy(e.reply, "OK"); + _admin_pwd_set = true; + } + if (wcIsSecretReadCommand(e.cmd)) wcMaskSecretReply(e.reply); + if (WebConfigBatch::cliReplyGatesReboot(e.cmd)) { + _batch_all_ok = WebConfigBatch::nextAllOk( + _batch_all_ok, WebConfigBatch::cliWriteSucceeded(e.reply)); + } + } else { + const bool admin_pwd = wcIsAdminPasswordKey(e.key); + const char* value = admin_pwd ? e.cmd + strlen("password ") + : e.cmd + strlen("set ") + strlen(e.key) + 1; + if ((_mqtt_prefs == NULL || !_owns_wifi) && strcmp(e.key, "wifi.ssid") == 0) { if (!value[0] || strlen(value) >= sizeof(_wifi_ssid)) { strcpy(e.reply, "Error: WiFi SSID must be 1-31 characters"); } else { @@ -967,13 +1062,23 @@ void WebConfigServer::drainBatch(uint32_t now) { } else if (strncmp(e.reply, "OK", 2) == 0 && strcmp(e.key, "wifi.powersave") == 0) { _wifi_power_save = strcmp(value, "max") == 0 ? 2 : strcmp(value, "min") == 0 ? 0 : 1; } + } + if (e.reply[0] == 0) strcpy(e.reply, "OK"); + _batch_all_ok = WebConfigBatch::nextAllOk( + _batch_all_ok, strncmp(e.reply, "OK", 2) == 0); } - if (e.reply[0] == 0) strcpy(e.reply, "OK"); - _batch_all_ok = WebConfigBatch::nextAllOk( - _batch_all_ok, strncmp(e.reply, "OK", 2) == 0); _batch_last_cmd = millis(); - Serial.printf("WC: cmd %d/%d '%s' took %lums\n", (int)_batch_next, (int)_batch_count, - e.key, (unsigned long)(_batch_last_cmd - t0)); + // Config entries are named by their (non-secret) key. A CLI command is + // deliberately not logged: the operator can see what they typed, and a + // `set wifi.pwd` or `password` from the terminal must not reach the serial + // log, which is a different audience from the browser session. + if (_batch_kind == BATCH_CLI) { + Serial.printf("WC: cli %d/%d took %lums\n", (int)_batch_next, (int)_batch_count, + (unsigned long)(_batch_last_cmd - t0)); + } else { + Serial.printf("WC: cmd %d/%d '%s' took %lums\n", (int)_batch_next, (int)_batch_count, + e.key, (unsigned long)(_batch_last_cmd - t0)); + } if (!WebConfigBatch::drainFinished(_batch_next, _batch_count)) { return; // more commands next tick } @@ -1218,6 +1323,9 @@ void WebConfigServer::handleStatus(AsyncWebServerRequest* req) { for (int i = 0; i < 8; i++) sprintf(&node_id[i * 2], "%02x", _pub_key[i]); doc["node_id"] = node_id; doc["fw"] = _fw_ver; + // The page shows a trimmed version -- base + build number + channel -- and + // pairs it with this, the way `ver` does. Both come from the same defines. + doc["build_date"] = _build_date; doc["role"] = _role; doc["board"] = _board_name; doc["uptime_s"] = millis() / 1000; @@ -1234,6 +1342,10 @@ void WebConfigServer::handleStatus(AsyncWebServerRequest* req) { doc["cli"] = _cli_enabled && _mode == MODE_LAN && WiFi.status() == WL_CONNECTED && _cb->supportsCliTerminal(); doc["capabilities"] = node.capabilities; + // Commands the terminal may submit at once. The CLI shares the config + // batch's fixed slot, so the cap is MAX_BATCH -- reported rather than + // duplicated in the page, which cannot know how this build was sized. + doc["max_cmds"] = MAX_BATCH; AsyncResponseStream* res = req->beginResponseStream("application/json"); serializeJson(doc, *res); @@ -1374,6 +1486,15 @@ void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) { s["token"] = obs->mqtt_slot_token[i][0] ? SECRET_SENTINEL : ""; s["topic"] = (const char*)obs->mqtt_slot_topic[i]; s["audience"] = (const char*)obs->mqtt_slot_audience[i]; + char filter_text[MQTTPacketFilter::kFilterTextSize]; + if (MQTTPacketFilter::format(obs->mqtt_slot_packet_filter[i], + filter_text, sizeof(filter_text))) { + // Mutable char input is copied into the ArduinoJson document; the + // stack buffer is reused on the next slot. + s["filter"] = filter_text; + } else { + s["filter"] = "all"; + } } } #endif @@ -1406,13 +1527,9 @@ void WebConfigServer::handleConfigPost(AsyncWebServerRequest* req) { JsonObject set = doc["set"]; WCLock lock(_mux); - if (_cli_state == CLI_PENDING) { - req->send(409, "application/json", - "{\"error\":\"CLI command is still running\"}"); - return; - } const WebConfigBatch::State bstate = toSpecState(_batch_state); - const bool reqid_matches = strcmp(reqid, _batch_reqid) == 0; + const bool reqid_matches = _batch_kind == BATCH_CONFIG + && strcmp(reqid, _batch_reqid) == 0; const WebConfigBatch::PostOutcome pre = WebConfigBatch::classifyPost(bstate, reqid_matches, 1, reboot_after); if (pre == WebConfigBatch::PostOutcome::Replay) { @@ -1476,11 +1593,32 @@ void WebConfigServer::handleConfigPost(AsyncWebServerRequest* req) { // smuggle in a second command. Admin password uses the top-level command. int pos = admin_pwd ? snprintf(e.cmd, sizeof(e.cmd), "password ") : snprintf(e.cmd, sizeof(e.cmd), "set %s ", key); - for (const char* p = val; *p && pos < (int)sizeof(e.cmd) - 1; p++) { + bool overflow = false; + for (const char* p = val; *p; p++) { if (*p == '\r' || *p == '\n') continue; + if (pos >= (int)sizeof(e.cmd) - 1) { overflow = true; break; } e.cmd[pos++] = *p; } e.cmd[pos] = 0; + // A value that doesn't fit used to be truncated here and applied anyway. + // For length-checked keys the CLI would reject the remainder, but a key + // whose grammar stays valid when clipped (mqttN.filter: "advert,2" cut to + // "advert") would silently persist a *different* setting and still answer + // OK. Refuse the batch instead -- the caller can shorten and retry. + if (overflow) { + // Name the key, like the "bad key" rejection above: a 20-field batch is + // rejected whole, so without it the operator has nothing to correct. + char safe_key[33]; + strncpy(safe_key, key, sizeof(safe_key) - 1); + safe_key[sizeof(safe_key) - 1] = 0; + StaticJsonDocument<128> ed; + ed["error"] = "value too long"; + ed["key"] = safe_key; + String out; + serializeJson(ed, out); + req->send(400, "application/json", out); + return; + } count++; } if (WebConfigBatch::classifyPost(bstate, reqid_matches, count, reboot_after) == @@ -1488,6 +1626,7 @@ void WebConfigServer::handleConfigPost(AsyncWebServerRequest* req) { req->send(400, "application/json", "{\"error\":\"no changes\"}"); return; } + _batch_kind = BATCH_CONFIG; _batch_count = count; _batch_next = 0; _batch_reboot = reboot_after; @@ -1531,9 +1670,13 @@ void WebConfigServer::handleConfigResult(AsyncWebServerRequest* req) { // fires but no branch print follows, the handler is blocked on _mux. Serial.printf("WC: result entry mode=%d state=%d\n", (int)_mode, (int)_batch_state); WCLock lock(_mux); - const WebConfigBatch::ResultOutcome outcome = WebConfigBatch::classifyResult( - toSpecState(_batch_state), - strcmp(requested_reqid.c_str(), _batch_reqid) == 0); + // A CLI sequence occupying the shared slot is not a config save, whatever the + // reqid says: its entries have no `key` and its results belong to the + // terminal's reader. Treat it as unknown here (and vice versa there). + const bool mine = (_batch_kind == BATCH_CONFIG) && + (strcmp(requested_reqid.c_str(), _batch_reqid) == 0); + const WebConfigBatch::ResultOutcome outcome = + WebConfigBatch::classifyResult(toSpecState(_batch_state), mine); if (outcome == WebConfigBatch::ResultOutcome::Idle) { Serial.println("WC: result read -> idle"); StaticJsonDocument<64> idle; @@ -1605,6 +1748,22 @@ void WebConfigServer::handleConfigResult(AsyncWebServerRequest* req) { req->send(res); } +// --------------------------------------------------------------------------- +// CLI terminal (/api/cli). Same 202 + reqid + poll contract as a config save, +// and for the same reason: CommonCLI touches prefs, the radio and the +// filesystem, none of which may be reached from the async_tcp task. The +// commands go into the shared deferred slot and tick() drains them. +// +// Unlike a save this is not allowlisted. It is restricted to authenticated LAN +// mode and uses the remote-admin callback, so serial-only secrets stay hidden. +// --------------------------------------------------------------------------- + +// Commands whose CLI handler never returns would take the node down mid-drain, +// before the client could read a single result. `reboot` is deferred instead: +// it is not passed to the CLI at all, and the batch arms the ordinary reboot +// path once the operator has read the results. The rest (clkreboot, poweroff, +// ota update) do real work on the way down and cannot be faked, so they run +// normally and the connection drops -- the UI warns before sending them. void WebConfigServer::handleCliPost(AsyncWebServerRequest* req) { if (_mode != MODE_LAN) { req->send(403, "application/json", @@ -1621,69 +1780,137 @@ void WebConfigServer::handleCliPost(AsyncWebServerRequest* req) { "{\"error\":\"terminal disabled; set wifi.cli on\"}"); return; } - if (!checkAuth(req)) { - req->send(401, "application/json", "{\"error\":\"auth\"}"); - return; - } - if (req->contentLength() > 512) { + if (!checkAuth(req)) { req->send(401, "application/json", "{\"error\":\"auth\"}"); return; } + if (req->contentLength() > MAX_BODY || req->_tempObject == NULL) { req->send(413, "application/json", "{\"error\":\"body too large\"}"); return; } - const char* body = (const char*)req->_tempObject; - DynamicJsonDocument doc(512); + DynamicJsonDocument doc(6144); if (!body || deserializeJson(doc, body) != DeserializationError::Ok) { req->send(400, "application/json", "{\"error\":\"bad json\"}"); return; } const char* reqid = doc["reqid"] | ""; - const char* command = doc["command"] | ""; if (!wcIsValidReqId(reqid)) { req->send(400, "application/json", "{\"error\":\"bad reqid\"}"); return; } - if (!wcIsValidCliCommand(command)) { - req->send(400, "application/json", - "{\"error\":\"command must be one nonblank line up to 159 bytes\"}"); + JsonArray cmds = doc["cmds"]; + if (cmds.isNull()) { + req->send(400, "application/json", "{\"error\":\"no commands\"}"); return; } WCLock lock(_mux); - const bool same_request = strcmp(reqid, _cli_reqid) == 0; - if (_cli_state == CLI_PENDING || same_request) { - if (!same_request) { - StaticJsonDocument<96> busy; - busy["error"] = "busy"; - busy["reqid"] = (const char*)_cli_reqid; - String out; - serializeJson(busy, out); - req->send(409, "application/json", out); - return; - } - StaticJsonDocument<96> replay; - replay["state"] = _cli_state == CLI_DONE ? "done" : "pending"; - replay["reqid"] = (const char*)_cli_reqid; + // Replay/Busy exactly as a config save classifies them: a repeated POST is + // acknowledged rather than executed twice, and a different sequence while one + // is still draining is refused. + const WebConfigBatch::State bstate = toSpecState(_batch_state); + const bool reqid_matches = _batch_kind == BATCH_CLI + && strcmp(reqid, _batch_reqid) == 0; + const WebConfigBatch::PostOutcome pre = + WebConfigBatch::classifyPost(bstate, reqid_matches, 1 /* count unknown yet */, false); + if (pre == WebConfigBatch::PostOutcome::Replay) { + StaticJsonDocument<96> ack; + ack["state"] = (bstate == WebConfigBatch::State::Done) ? "done" : "running"; + ack["total"] = _batch_count; + ack["reqid"] = (const char*)_batch_reqid; String out; - serializeJson(replay, out); + serializeJson(ack, out); req->send(202, "application/json", out); return; } - if (_batch_state == BATCH_PENDING) { - req->send(409, "application/json", - "{\"error\":\"configuration change is still running\"}"); + if (pre == WebConfigBatch::PostOutcome::Busy) { + StaticJsonDocument<96> bd; + bd["error"] = "busy"; + bd["reqid"] = (const char*)_batch_reqid; + String out; + serializeJson(bd, out); + req->send(409, "application/json", out); return; } - strncpy(_cli_reqid, reqid, sizeof(_cli_reqid) - 1); - _cli_reqid[sizeof(_cli_reqid) - 1] = 0; - strncpy(_cli_command, command, sizeof(_cli_command) - 1); - _cli_command[sizeof(_cli_command) - 1] = 0; - _cli_reply[0] = 0; - _cli_state = CLI_PENDING; + int count = 0; + bool defer_reboot = false, seq_sets_pwd = false, seq_sets_ssid = false; + for (JsonVariant v : cmds) { + const char* raw = v.as(); + if (!raw) continue; + if (count >= MAX_BATCH) { + StaticJsonDocument<96> ed; + ed["error"] = "too many commands"; + ed["max"] = MAX_BATCH; + String out; + serializeJson(ed, out); + req->send(413, "application/json", out); + return; + } + // Strip CR/LF so one entry cannot smuggle a second command past the + // operator's confirmation, and skip whatever is left blank. + BatchEntry& e = _batch[count]; + int pos = 0; + for (const char* p = raw; *p; p++) { + if (*p == '\r' || *p == '\n') continue; + if (pos == 0 && (*p == ' ' || *p == '\t')) continue; // leading space + if (pos >= (int)sizeof(e.cmd) - 1) { + req->send(400, "application/json", "{\"error\":\"command too long\"}"); + return; + } + e.cmd[pos++] = *p; + } + while (pos > 0 && (e.cmd[pos - 1] == ' ' || e.cmd[pos - 1] == '\t')) pos--; + e.cmd[pos] = 0; + if (pos == 0) continue; + // Reject before anything runs, so a sequence never half-applies and then + // stops on a command that was never going to work here. + const char* why = wcCliUnavailable(e.cmd); + if (why) { + StaticJsonDocument<256> ed; + ed["error"] = why; + String out; + serializeJson(ed, out); + req->send(400, "application/json", out); + return; + } + e.key[0] = 0; // CLI entries have no config key + if (wcIsDeferredReboot(e.cmd)) defer_reboot = true; + if (strncmp(e.cmd, "password ", 9) == 0) seq_sets_pwd = true; + if (strncmp(e.cmd, "set wifi.ssid ", 14) == 0) seq_sets_ssid = true; + count++; + } + if (count == 0) { + req->send(400, "application/json", "{\"error\":\"no commands\"}"); + return; + } + + // The same invariant handleConfigPost enforces, and for the same reason: the + // reboot is what commits first onboarding, and a node that reboots onto the + // LAN still holding the factory password is a known credential on someone + // else's network. The terminal warned about this client-side, which is a + // reminder, not a rule -- a pasted script or a direct POST ignored it. + if (_mode == MODE_SETUP && _initial_setup && !seq_sets_pwd && !_admin_pwd_set && + (defer_reboot || seq_sets_ssid)) { + req->send(400, "application/json", + "{\"error\":\"admin password required for initial setup -- " + "run `password ` first\"}"); + return; + } + + _batch_kind = BATCH_CLI; + _batch_count = count; + _batch_next = 0; + _batch_reboot = defer_reboot; + _batch_reboot_armed = false; + _batch_all_ok = true; + strncpy(_batch_reqid, reqid, sizeof(_batch_reqid) - 1); + _batch_reqid[sizeof(_batch_reqid) - 1] = 0; + _batch_state = BATCH_PENDING; // tick() picks it up on the loop task + Serial.printf("WC: cli POST accepted, %d cmds, reboot=%d\n", count, (int)defer_reboot); StaticJsonDocument<96> ack; - ack["state"] = "pending"; - ack["reqid"] = (const char*)_cli_reqid; + ack["state"] = "running"; + ack["total"] = count; + ack["reqid"] = (const char*)_batch_reqid; String out; serializeJson(ack, out); req->send(202, "application/json", out); @@ -1700,10 +1927,12 @@ void WebConfigServer::handleCliResult(AsyncWebServerRequest* req) { "{\"error\":\"terminal unavailable on this role\"}"); return; } - if (!checkAuth(req)) { - req->send(401, "application/json", "{\"error\":\"auth\"}"); + if (!_cli_enabled) { + req->send(403, "application/json", + "{\"error\":\"terminal disabled; set wifi.cli on\"}"); return; } + if (!checkAuth(req)) { req->send(401, "application/json", "{\"error\":\"auth\"}"); return; } if (!req->hasParam("reqid")) { req->send(400, "application/json", "{\"error\":\"bad reqid\"}"); return; @@ -1713,9 +1942,21 @@ void WebConfigServer::handleCliResult(AsyncWebServerRequest* req) { req->send(400, "application/json", "{\"error\":\"bad reqid\"}"); return; } + int from = 0; + if (req->hasParam("from")) { + from = req->getParam("from")->value().toInt(); + if (from < 0) from = 0; + } WCLock lock(_mux); - if (_cli_state == CLI_IDLE) { + // A config save occupying the slot is not this client's sequence, whatever + // the reqid says; treat it as unknown rather than serving `set` results + // through the terminal's reader. + const bool mine = (_batch_kind == BATCH_CLI) && + (strcmp(requested_reqid.c_str(), _batch_reqid) == 0); + const WebConfigBatch::ResultOutcome outcome = + WebConfigBatch::classifyResult(toSpecState(_batch_state), mine); + if (outcome == WebConfigBatch::ResultOutcome::Idle) { StaticJsonDocument<64> idle; idle["state"] = "idle"; idle["reqid"] = requested_reqid; @@ -1724,15 +1965,42 @@ void WebConfigServer::handleCliResult(AsyncWebServerRequest* req) { req->send(200, "application/json", out); return; } - if (strcmp(requested_reqid.c_str(), _cli_reqid) != 0) { + if (outcome == WebConfigBatch::ResultOutcome::Unknown) { req->send(404, "application/json", "{\"error\":\"unknown request\"}"); return; } - DynamicJsonDocument doc(512); - doc["state"] = _cli_state == CLI_DONE ? "done" : "pending"; - doc["reqid"] = (const char*)_cli_reqid; - if (_cli_state == CLI_DONE) doc["reply"] = (const char*)_cli_reply; + // Results stream: hand back whatever has drained since the client's cursor, + // capped so the document stays small on the async_tcp task. + const int produced = _batch_next; + const int page = WebConfigBatch::cliPageCount(from, produced, WebConfigBatch::kCliResultPage); + const bool final_read = WebConfigBatch::cliReadIsFinal(toSpecState(_batch_state), + from, page, _batch_count); + DynamicJsonDocument doc(4096); + doc["state"] = final_read ? "done" : "running"; + doc["reqid"] = (const char*)_batch_reqid; + doc["total"] = _batch_count; + doc["from"] = from; + JsonArray results = doc.createNestedArray("results"); + for (int i = from; i < from + page; i++) { + JsonObject r = results.createNestedObject(); + // The command is deliberately NOT echoed: it may hold a password or token, + // and the client already has the sequence it sent. It matches by index. + r["ok"] = !WebConfigBatch::cliReplyIsFailure(_batch[i].reply); + r["reply"] = (const char*)_batch[i].reply; + } + if (final_read) { + doc["all_ok"] = _batch_all_ok; + const bool rebooting = WebConfigBatch::cliRebootAllowed(_batch_reboot, _batch_all_ok); + doc["reboot"] = rebooting; + // Tell the operator why a `reboot` they asked for is not happening. + if (_batch_reboot && !_batch_all_ok) doc["reboot_withheld"] = true; + if (WebConfigBatch::shouldArmConfirmReboot(toSpecState(_batch_state), _batch_reboot, + _batch_all_ok, _batch_reboot_armed)) { + _batch_reboot_armed = true; + _reboot_at = WebConfigBatch::confirmRebootAt(millis()); + } + } AsyncResponseStream* res = req->beginResponseStream("application/json"); serializeJson(doc, *res); req->send(res); diff --git a/src/helpers/esp32/WebConfigServer.h b/src/helpers/esp32/WebConfigServer.h index acce1e51..042e26f1 100644 --- a/src/helpers/esp32/WebConfigServer.h +++ b/src/helpers/esp32/WebConfigServer.h @@ -117,6 +117,7 @@ public: // repeater/room-server builds), false when the companion/MQTT runtime owns it. WebConfigServer(Callbacks* callbacks, void* mqtt_prefs, bool owns_wifi, const uint8_t* pub_key, const char* fw_ver, + const char* build_date, const char* role, const char* board_name); ~WebConfigServer(); @@ -179,18 +180,26 @@ private: static const size_t MAX_BODY = 4096; static const uint32_t STOP_WARN_MS = WebConfigBatch::kStopWarnMs; enum BatchState : uint8_t { BATCH_IDLE = 0, BATCH_PENDING, BATCH_DONE }; - enum CliState : uint8_t { CLI_IDLE = 0, CLI_PENDING, CLI_DONE }; - static WebConfigBatch::State toSpecState(BatchState state) { - switch (state) { + // What filled the shared slot. A config save comes from allowlisted form + // fields; a CLI sequence is arbitrary commands typed into the terminal. They + // share the slot (see WebConfigBatch.h) but differ in how results are read + // and in whether `key` means anything, so every reader checks the kind. + enum BatchKind : uint8_t { BATCH_CONFIG = 0, BATCH_CLI }; + + // BatchState and WebConfigBatch::State are deliberately kept as separate + // types (the enum is stored in a volatile member and used in prints); this + // is the one conversion point. + static WebConfigBatch::State toSpecState(BatchState s) { + switch (s) { case BATCH_PENDING: return WebConfigBatch::State::Pending; case BATCH_DONE: return WebConfigBatch::State::Done; default: return WebConfigBatch::State::Idle; } } struct BatchEntry { - char key[24]; // allowlisted config key (echoed back to the UI) + char key[24]; // allowlisted config key (echoed back to the UI); empty for CLI entries char cmd[160]; // full CLI command (may contain secrets - never echoed) - char reply[160]; + char reply[160]; // CLI reply budget, same 160 bytes the serial console gets }; Callbacks* _cb; @@ -198,6 +207,7 @@ private: bool _owns_wifi; const uint8_t* _pub_key; const char* _fw_ver; + const char* _build_date; const char* _role; const char* _board_name; @@ -213,6 +223,10 @@ private: char _wifi_password[64] = {0}; uint8_t _wifi_power_save = 1; bool _cli_enabled = true; + // A `password` command has succeeded this session. Lets the CLI satisfy the + // initial-setup invariant across separate submissions; the form batch always + // sends the password with the rest, so it never needed the memory. + bool _admin_pwd_set = false; char _ap_ssid[33] = {0}; WiFiReconnectPolicy::Tracker _wifi_reconnect_tracker; bool _retry_saved_wifi_in_setup = false; @@ -227,6 +241,7 @@ private: // Command batch: filled by async_tcp under _mux, drained by tick(). volatile BatchState _batch_state = BATCH_IDLE; + volatile BatchKind _batch_kind = BATCH_CONFIG; uint8_t _batch_count = 0; uint8_t _batch_next = 0; // drain progress (one command per tick) uint32_t _batch_last_cmd = 0; @@ -240,13 +255,6 @@ private: char _setup_wifi_handoff_ip[16] = {0}; BatchEntry _batch[MAX_BATCH]; - // Browser terminal command: filled by async_tcp under _mux and executed by - // tick() on the loop task. Separate from the allowlisted config batch. - volatile CliState _cli_state = CLI_IDLE; - char _cli_reqid[24] = {0}; - char _cli_command[160] = {0}; - char _cli_reply[160] = {0}; - // LAN-mode session (single slot; new login evicts the old session) char _session_token[33] = {0}; uint32_t _session_last_seen = 0; @@ -282,7 +290,6 @@ private: void drainBatch(uint32_t now); void serviceSetupWiFiHandoff(uint32_t now); void finishBatch(uint32_t now); - void drainCliCommand(); void finalizeTeardown(); bool checkAuth(AsyncWebServerRequest* req); static void collectBody(AsyncWebServerRequest* req, uint8_t* data, size_t len, diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index 15926597..4c0a5ada 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -29,6 +29,7 @@ bool SH1106Display::begin() // preferred alternate explicitly; otherwise try the other address in the // pair. Always run the Adafruit init so its buffer and device objects exist, // even when no panel answers the probe. + _initialized = false; uint8_t addr = 0; if (i2c_probe(Wire, DISPLAY_ADDRESS)) { addr = DISPLAY_ADDRESS; @@ -43,17 +44,23 @@ bool SH1106Display::begin() addr = DISPLAY_ADDRESS ^ 1; } bool ok = display.begin(addr ? addr : DISPLAY_ADDRESS, true); - return addr != 0 && ok; + _initialized = addr != 0 && ok; + return _initialized; } void SH1106Display::turnOn() { + if (!_initialized) return; display.oled_command(SH110X_DISPLAYON); _isOn = true; } void SH1106Display::turnOff() { + if (!_initialized) { + _isOn = false; + return; + } display.oled_command(SH110X_DISPLAYOFF); _isOn = false; } diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index 4e269d5e..ddbcf80a 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -17,16 +17,20 @@ class SH1106Display : public DisplayDriver { Adafruit_SH1106G display; + bool _initialized; bool _isOn; uint8_t _color; bool i2c_probe(TwoWire &wire, uint8_t addr); public: - SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { + _initialized = false; + _isOn = false; + } bool begin(); - bool isOn() override { return _isOn; } + bool isOn() override { return _initialized && _isOn; } void turnOn() override; void turnOff() override; void clear() override; diff --git a/test/README.md b/test/README.md index 70ac973a..2167fc3d 100644 --- a/test/README.md +++ b/test/README.md @@ -28,14 +28,15 @@ does not reflect the GoogleTest count -- run the built binary directly | `test_webconfig_keys` | `src/helpers/WebConfigKeys.h` | POST-key allowlist, secret detection, admin-password and browser-terminal command validation, slot-index bounds, and short-input guards | | `test_webconfig_batch` | `src/helpers/WebConfigBatch.h` | config POST/replay/busy decisions; paced command drain; setup WiFi validation/IP handoff; reboot confirmation/fallback; stop/refcount gating; exact timing and rollover boundaries | | `test_topic_template` | `src/helpers/MQTTTopicTemplate.h` | `{iata}/{device}/{token}/{type}` expansion, overflow/NUL-termination, and a buffer-size fuzz | -| `test_mqtt_topic_router` | `src/helpers/MQTTTopicRouter.h` | complete preset/custom topic-routing contract; MeshRank packets-only behavior; required identifiers; invalid inputs/slots; exact buffer boundaries | +| `test_mqtt_topic_router` | `src/helpers/MQTTTopicRouter.h` | complete preset/custom topic-routing contract; MeshRank all types except raw; required identifiers; invalid inputs/slots; exact buffer boundaries | | `test_mqtt_connection_policy` | `src/helpers/MQTTConnectionPolicy.h` | reconnect guard/backoff/stagger and breaker transitions; stable reset; JWT lifetime/renewal policy; exact timing boundaries and 32-bit `millis()` rollover | | `test_wifi_reconnect_policy` | `src/helpers/WiFiReconnectPolicy.h` | five-minute forced reconnect cadence, connection resets, duplicate disconnect observations, and 32-bit `millis()` rollover | | `test_radio_liveness` | `src/helpers/RadioLivenessTracker.h`, `src/helpers/radiolib/RxBoostedGainDefaults.h` | staged radio recovery, activity reset and rollover handling; target-specific RX boosted-gain defaults and SX126x precedence | | `test_lr1110_rx_recovery` | `src/helpers/radiolib/LR1110RxRecovery.h` | LR1110 four-byte RX-buffer shift signature; captured and accumulated shifts; ordinary/scoped packet exclusions | | `test_mqtt_packet_queue_policy` | `src/helpers/MQTTPacketQueuePolicy.h` | queue-full eviction; stale-disconnect flush; adaptive drain limits; bounded QoS0 retries; exact timing boundaries and 32-bit `millis()` rollover | +| `test_mqtt_packet_filter` | `src/helpers/MQTTPacketFilter.h` | per-slot 0-15 allowlist parsing/formatting, numeric and named spellings; exact bounds; membership; candidate/eligible split and retry-completion policy; pre-queue union gate; default-mask detection | | `test_mqtt_runtime_buffer_lifecycle` | `src/helpers/MQTTRuntimeBufferLifecycle.h` | idempotent allocation/release; partial-allocation degradation; retry of only missing buffers | -| `test_mqtt_prefs_codec` | `src/helpers/MQTTPrefsStorage.h`, `src/helpers/MQTTPrefsCodec.h` | binary pre-slot/3-slot/6-slot migration fixtures; v1 header integrity; downgrade preservation | +| `test_mqtt_prefs_codec` | `src/helpers/MQTTPrefsStorage.h`, `src/helpers/MQTTPrefsCodec.h` | binary pre-slot/3-slot/6-slot migration fixtures; v1 header integrity; downgrade preservation; shortest-payload write policy (default filters stay downgrade-readable) | | `test_mqtt_prefs_atomic_store` | `src/helpers/MQTTPrefsAtomicStore.h` | transactional MQTT writes and legacy `/node_prefs` handoff; exact short-write detection; begin/finish/rename failure cleanup; original-file preservation | | `test_prefs_save_routing` | `src/helpers/PrefsSaveRouting.h` | runtime common/observer setters write only their owning preference image; mixed-owner setters and migrations can deliberately write both | | `test_mqtt_payload_builder` | `src/helpers/MQTTPayloadBuilder.cpp` | status/packet/raw JSON contracts; optional fields; escaping; RX metrics and path; score handling; exact buffer bounds; maximum representative payloads | diff --git a/test/mocks/Wire.h b/test/mocks/Wire.h new file mode 100644 index 00000000..baea51f5 --- /dev/null +++ b/test/mocks/Wire.h @@ -0,0 +1,3 @@ +#pragma once + +class TwoWire { }; diff --git a/test/test_mqtt_packet_filter/test_mqtt_packet_filter.cpp b/test/test_mqtt_packet_filter/test_mqtt_packet_filter.cpp new file mode 100644 index 00000000..cda621dc --- /dev/null +++ b/test/test_mqtt_packet_filter/test_mqtt_packet_filter.cpp @@ -0,0 +1,303 @@ +#include + +#include "helpers/MQTTPacketFilter.h" + +namespace Filter = MQTTPacketFilter; + +TEST(MQTTPacketFilter, EmptyAndAllMeanEveryType) { + for (const char* value : {"", " ", "\tall\r\n"}) { + uint16_t mask = 0; + ASSERT_TRUE(Filter::parse(value, &mask)) << value; + EXPECT_EQ(Filter::kAllPacketTypes, mask); + } +} + +TEST(MQTTPacketFilter, NoneMeansNoTypes) { + uint16_t mask = Filter::kAllPacketTypes; + ASSERT_TRUE(Filter::parse(" none ", &mask)); + EXPECT_EQ(0u, mask); +} + +TEST(MQTTPacketFilter, ParsesDecimalCsvWithWhitespaceAndDuplicates) { + uint16_t mask = 0; + ASSERT_TRUE(Filter::parse(" 2, 4,\t15,2 ", &mask)); + EXPECT_EQ(static_cast((1u << 2) | (1u << 4) | (1u << 15)), mask); +} + +TEST(MQTTPacketFilter, FullNumericListCanonicalizesToAll) { + uint16_t mask = 0; + ASSERT_TRUE(Filter::parse("0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15", &mask)); + EXPECT_EQ(Filter::kAllPacketTypes, mask); + char output[Filter::kFilterTextSize]; + ASSERT_TRUE(Filter::format(mask, output, sizeof(output))); + EXPECT_STREQ("all", output); +} + +TEST(MQTTPacketFilter, ParsesPayloadTypeNames) { + uint16_t mask = 0; + ASSERT_TRUE(Filter::parse("advert", &mask)); + EXPECT_EQ(static_cast(1u << 4), mask); + + ASSERT_TRUE(Filter::parse(" txt_msg , grp_txt ", &mask)); + EXPECT_EQ(static_cast((1u << 2) | (1u << 5)), mask); + + // Names and numbers are interchangeable within one list. + ASSERT_TRUE(Filter::parse("advert,2,raw_custom", &mask)); + EXPECT_EQ(static_cast((1u << 4) | (1u << 2) | (1u << 15)), mask); + + // Output stays numeric regardless of how the value was spelled. + char output[Filter::kFilterTextSize]; + ASSERT_TRUE(Filter::format(mask, output, sizeof(output))); + EXPECT_STREQ("2,4,15", output); +} + +TEST(MQTTPacketFilter, EveryNamedTypeResolvesToItsPacketHeaderValue) { + // Pins the table against src/Packet.h's PAYLOAD_TYPE_* values. + const struct { const char* name; uint8_t type; } expected[] = { + {"req", 0}, {"response", 1}, {"txt_msg", 2}, {"ack", 3}, {"advert", 4}, + {"grp_txt", 5}, {"grp_data", 6}, {"anon_req", 7}, {"path", 8}, + {"trace", 9}, {"multipart", 10}, {"control", 11}, {"raw_custom", 15}, + }; + for (const auto& e : expected) { + uint16_t mask = 0; + ASSERT_TRUE(Filter::parse(e.name, &mask)) << e.name; + EXPECT_EQ(static_cast(1u << e.type), mask) << e.name; + } + // Reserved types have no name and stay reachable by number only. + size_t count = 0; + const Filter::NamedPacketType* names = Filter::namedPacketTypes(&count); + EXPECT_EQ(sizeof(expected) / sizeof(expected[0]), count); + for (size_t i = 0; i < count; ++i) { + EXPECT_TRUE(names[i].type < 12 || names[i].type == 15) << names[i].name; + } +} + +TEST(MQTTPacketFilter, RejectsMalformedOrOutOfRangeValuesWithoutChangingOutput) { + const char* invalid[] = { + ",", "1,", ",1", "1,,2", "1, ,2", "-1", "+1", "0x2", "16", + "999999999999999999999", "ALL", "All", "all,2", "none,2", "2-4", "2x", + // Names are lowercase, exact, and are not whole-value keywords. + "ADVERT", "Advert", "advert_", "adver", "advertx", "reserved", "12_reserved", + "2,all", "advert,none", "advert advert", "advert;2", + }; + for (const char* value : invalid) { + uint16_t mask = 0x1234; + EXPECT_FALSE(Filter::parse(value, &mask)) << value; + EXPECT_EQ(0x1234, mask) << value; + } + EXPECT_FALSE(Filter::parse(nullptr, nullptr)); +} + +TEST(MQTTPacketFilter, FormatsSubsetsInAscendingOrder) { + const uint16_t mask = static_cast( + (1u << 15) | (1u << 2) | (1u << 10) | (1u << 0)); + char output[Filter::kFilterTextSize]; + ASSERT_TRUE(Filter::format(mask, output, sizeof(output))); + EXPECT_STREQ("0,2,10,15", output); +} + +TEST(MQTTPacketFilter, FormatsNoneAndHonorsExactBufferBoundaries) { + char none[5]; + ASSERT_TRUE(Filter::format(0, none, sizeof(none))); + EXPECT_STREQ("none", none); + + char too_small[4] = {'x', 'x', 'x', '\0'}; + EXPECT_FALSE(Filter::format(0, too_small, sizeof(too_small))); + EXPECT_STREQ("", too_small); + + char all_types[Filter::kFilterTextSize]; + const uint16_t subset = static_cast(Filter::kAllPacketTypes & ~(1u << 14)); + ASSERT_TRUE(Filter::format(subset, all_types, sizeof(all_types))); + EXPECT_STREQ("0,1,2,3,4,5,6,7,8,9,10,11,12,13,15", all_types); + char one_short[34]; + EXPECT_FALSE(Filter::format(subset, one_short, sizeof(one_short))); + EXPECT_STREQ("", one_short); +} + +TEST(MQTTPacketFilter, MembershipIsBoundedToFourBitTypes) { + EXPECT_TRUE(Filter::allows(static_cast(1u << 0), 0)); + EXPECT_TRUE(Filter::allows(static_cast(1u << 15), 15)); + EXPECT_FALSE(Filter::allows(Filter::kAllPacketTypes, 16)); + EXPECT_FALSE(Filter::allows(0, 0)); +} + +TEST(MQTTPacketFilter, EligibilityIgnoresConnectionButRequiresTopicAndFilter) { + const uint16_t only_advert = static_cast(1u << 4); + EXPECT_TRUE(Filter::slotEligible(true, true, only_advert, 4)); + EXPECT_FALSE(Filter::slotEligible(false, true, only_advert, 4)); + EXPECT_FALSE(Filter::slotEligible(true, false, only_advert, 4)); + EXPECT_FALSE(Filter::slotEligible(true, true, only_advert, 2)); +} + +TEST(MQTTPacketFilter, AllFilteredOrTopicIncompatibleTargetsCompleteIntentionally) { + const uint16_t only_text = static_cast(1u << 2); + const bool filtered = Filter::slotEligible(true, true, only_text, 4); + const bool topic_incompatible = + Filter::slotEligible(true, false, Filter::kAllPacketTypes, 4); + EXPECT_FALSE(filtered || topic_incompatible); + EXPECT_TRUE(Filter::publishComplete(filtered || topic_incompatible, false)); +} + +TEST(MQTTPacketFilter, EligibleDisconnectedTargetStillRequiresRetry) { + const uint16_t only_advert = static_cast(1u << 4); + // Connection state is intentionally absent from slotEligible(): the first + // slot remains a target while disconnected. A second connected slot whose + // filter rejects the advert cannot turn that into intentional completion. + const bool disconnected_eligible = + Filter::slotEligible(true, true, only_advert, 4); + const bool connected_filtered = + Filter::slotEligible(true, true, static_cast(1u << 2), 4); + ASSERT_TRUE(disconnected_eligible); + ASSERT_FALSE(connected_filtered); + EXPECT_FALSE(Filter::publishComplete( + disconnected_eligible || connected_filtered, false)); +} + +TEST(MQTTPacketFilter, PublishFailureRetriesAndAnySuccessCompletes) { + EXPECT_FALSE(Filter::publishComplete(true, false)); + EXPECT_TRUE(Filter::publishComplete(true, true)); + EXPECT_TRUE(Filter::publishComplete(false, true)); // defensive: success wins +} + +TEST(MQTTPacketFilter, UnsupportedRawPathCannotHideEligiblePacketFailure) { + const bool packet_eligible = true; + const bool packet_published = false; + const bool raw_eligible = false; // e.g. MeshRank has no raw topic + const bool raw_published = false; + + EXPECT_FALSE(Filter::publishComplete( + packet_eligible || raw_eligible, + packet_published || raw_published)); + EXPECT_TRUE(Filter::publishComplete(false, false)); +} + +TEST(MQTTPacketFilter, PacketAndRawCanShareTheSameMaskDecision) { + const uint16_t text_and_advert = static_cast((1u << 2) | (1u << 4)); + for (uint8_t type = 0; type <= 15; ++type) { + const bool expected = type == 2 || type == 4; + EXPECT_EQ(expected, Filter::allows(text_and_advert, type)) << unsigned(type); + } +} + +TEST(MQTTPacketFilter, CandidateIsTheTopicFreePartOfEligibility) { + const uint16_t only_advert = static_cast(1u << 4); + EXPECT_TRUE(Filter::slotCandidate(true, only_advert, 4)); + EXPECT_FALSE(Filter::slotCandidate(false, only_advert, 4)); + EXPECT_FALSE(Filter::slotCandidate(true, only_advert, 2)); + // A candidate is exactly an eligible slot minus the topic-support term. The + // bridge evaluates the cheap half first and only builds a topic for slots + // that pass it, so the two must stay in this exact relationship. + for (int enabled = 0; enabled <= 1; ++enabled) { + for (int topic = 0; topic <= 1; ++topic) { + for (uint8_t type = 0; type <= 15; ++type) { + EXPECT_EQ(Filter::slotCandidate(enabled != 0, only_advert, type) && topic != 0, + Filter::slotEligible(enabled != 0, topic != 0, only_advert, type)); + } + } + } +} + +// The gate has to resolve topic support before anything expensive runs: a +// connected slot that cannot form a topic (MeshRank raw, or a meshcore preset +// with no IATA) must reach "intentionally complete" without a serialisation. +TEST(MQTTPacketFilter, TopicIncompatibleTargetsAreCompleteWithoutPublishing) { + const uint16_t all = Filter::kAllPacketTypes; + for (uint8_t type = 0; type <= 15; ++type) { + const bool eligible = Filter::slotEligible(true, false, all, type); + EXPECT_FALSE(eligible) << unsigned(type); + EXPECT_TRUE(Filter::publishComplete(eligible, false)) << unsigned(type); + } + // With topic support restored the same slot becomes a retry target again. + EXPECT_TRUE(Filter::slotEligible(true, true, all, 4)); + EXPECT_FALSE(Filter::publishComplete(Filter::slotEligible(true, true, all, 4), false)); +} + +TEST(MQTTPacketFilter, EnabledUnionIgnoresDisabledSlotsAndGatesTheQueue) { + const uint16_t masks[3] = { + static_cast(1u << 2), // text only + static_cast(1u << 4), // adverts only + Filter::kAllPacketTypes, // everything, but disabled below + }; + const bool enabled[3] = {true, true, false}; + + const uint16_t combined = Filter::enabledUnion(masks, enabled, 3); + EXPECT_EQ(static_cast((1u << 2) | (1u << 4)), combined); + EXPECT_TRUE(Filter::allows(combined, 2)); + EXPECT_TRUE(Filter::allows(combined, 4)); + EXPECT_FALSE(Filter::allows(combined, 3)); // no enabled slot wants ACKs + + // A null `enabled` array counts every slot; no slots means no destination. + EXPECT_EQ(Filter::kAllPacketTypes, Filter::enabledUnion(masks, nullptr, 3)); + EXPECT_EQ(0u, Filter::enabledUnion(masks, enabled, 0)); + EXPECT_EQ(0u, Filter::enabledUnion(nullptr, enabled, 3)); + + // The union must never reject a type any single slot would have published. + for (uint8_t type = 0; type <= 15; ++type) { + const bool any_slot_allows = + (enabled[0] && Filter::allows(masks[0], type)) || + (enabled[1] && Filter::allows(masks[1], type)) || + (enabled[2] && Filter::allows(masks[2], type)); + EXPECT_EQ(any_slot_allows, Filter::allows(combined, type)) << unsigned(type); + } +} + +TEST(MQTTPacketFilter, AllMasksDefaultDrivesTheShortPrefsPayload) { + uint16_t masks[6]; + for (int i = 0; i < 6; ++i) masks[i] = Filter::kAllPacketTypes; + EXPECT_TRUE(Filter::allMasksDefault(masks, 6)); + EXPECT_TRUE(Filter::allMasksDefault(nullptr, 6)); + EXPECT_TRUE(Filter::allMasksDefault(masks, 0)); + + // Any slot off the default -- including "none" -- opts into the longer payload. + for (int i = 0; i < 6; ++i) { + masks[i] = 0; + EXPECT_FALSE(Filter::allMasksDefault(masks, 6)) << i; + masks[i] = static_cast(Filter::kAllPacketTypes & ~1u); + EXPECT_FALSE(Filter::allMasksDefault(masks, 6)) << i; + masks[i] = Filter::kAllPacketTypes; + } + EXPECT_TRUE(Filter::allMasksDefault(masks, 6)); +} + +// countTypes() backs the diag line's fallback when the exact list won't fit, +// so it has to agree with what format() would have emitted. +TEST(MQTTPacketFilter, CountTypesMatchesTheFormattedList) { + EXPECT_EQ(0, Filter::countTypes(0)); + EXPECT_EQ(16, Filter::countTypes(Filter::kAllPacketTypes)); + EXPECT_EQ(1, Filter::countTypes(static_cast(1u << 15))); + + for (uint32_t mask = 0; mask <= 0xFFFFu; ++mask) { + uint8_t expected = 0; + for (uint8_t type = 0; type <= 15; ++type) { + if ((mask & (1u << type)) != 0) expected++; + } + ASSERT_EQ(expected, Filter::countTypes(static_cast(mask))) << mask; + } +} + +// The diag fallback exists because a clipped list parses as a real, different +// allowlist. Pin that hazard so the fallback is never "simplified" away. +TEST(MQTTPacketFilter, ClippedListStillParsesAsADifferentAllowlist) { + const uint16_t configured = static_cast(Filter::kAllPacketTypes & ~1u); + char text[Filter::kFilterTextSize]; + ASSERT_TRUE(Filter::format(configured, text, sizeof(text))); + ASSERT_STREQ("1,2,3,4,5,6,7,8,9,10,11,12,13,14,15", text); + + // Drop the trailing "15" the way a bounded reply buffer would. + char clipped[Filter::kFilterTextSize]; + memcpy(clipped, text, sizeof(clipped)); + clipped[strlen("1,2,3,4,5,6,7,8,9,10,11,12,13,14,")] = '\0'; + + uint16_t misread = 0; + ASSERT_FALSE(Filter::parse(clipped, &misread)); // trailing comma is rejected + clipped[strlen("1,2,3,4,5,6,7,8,9,10,11,12,13,14")] = '\0'; + ASSERT_TRUE(Filter::parse(clipped, &misread)); // but one char earlier reads clean + EXPECT_NE(configured, misread) << "a clipped list must not be mistaken for the real one"; + EXPECT_EQ(14, Filter::countTypes(misread)); + EXPECT_EQ(15, Filter::countTypes(configured)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test_mqtt_payload_builder/test_mqtt_payload_builder.cpp b/test/test_mqtt_payload_builder/test_mqtt_payload_builder.cpp index 015d3aa9..23b83c2e 100644 --- a/test/test_mqtt_payload_builder/test_mqtt_payload_builder.cpp +++ b/test/test_mqtt_payload_builder/test_mqtt_payload_builder.cpp @@ -13,6 +13,12 @@ namespace { constexpr const char* kTimestamp = "2026-07-18T12:34:56.123456+00:00"; +struct RejectAllJsonAllocations : ArduinoJson::Allocator { + void* allocate(size_t) override { return nullptr; } + void deallocate(void*) override {} + void* reallocate(void*, size_t) override { return nullptr; } +}; + static int buildMinimalStatus(JsonDocument& scratch, char* buffer, size_t buffer_size) { return MQTTPayloadBuilder::buildStatusMessage( scratch, "DEN Repeater", "0123456789ABCDEF", "Heltec V3", "v1.16.0", @@ -250,8 +256,8 @@ TEST(MQTTPayloadBuilder, NeighborsMessageRoundTripsSelfAndEntries) { JsonDocument scratch; char buffer[1024]; int len = MQTTPayloadBuilder::buildNeighborsMessage( - scratch, "DEN Repeater", "0123456789ABCDEF", kTimestamp, "DEN,APRS", - neighbors, 2, buffer, sizeof(buffer)); + scratch, "DEN Repeater", "0123456789ABCDEF", kTimestamp, "DEN,APRS", "*", + neighbors, 2, buffer, sizeof(buffer), 5, 2, true); ASSERT_GT(len, 0); EXPECT_EQ(static_cast(len), strlen(buffer)); @@ -261,6 +267,10 @@ TEST(MQTTPayloadBuilder, NeighborsMessageRoundTripsSelfAndEntries) { EXPECT_STREQ("DEN Repeater", parsed["origin"].as()); EXPECT_STREQ("0123456789ABCDEF", parsed["origin_id"].as()); EXPECT_STREQ("DEN,APRS", parsed["self"]["scopes"].as()); + EXPECT_STREQ("*", parsed["self"]["default_scope"].as()); + EXPECT_EQ(5, parsed["total_neighbors"].as()); + EXPECT_EQ(2, parsed["queried_neighbors"].as()); + EXPECT_TRUE(parsed["truncated"].as()); JsonArray arr = parsed["neighbors"].as(); ASSERT_EQ(2U, arr.size()); @@ -274,17 +284,128 @@ TEST(MQTTPayloadBuilder, NeighborsMessageRoundTripsSelfAndEntries) { EXPECT_STREQ("stale", arr[1]["status"].as()); } +TEST(MQTTPayloadBuilder, NeighborsMessageMeasurementsMatchCompletePayload) { + MQTTPayloadBuilder::NeighborsMessageEntry neighbors[] = { + {"0011223344556677", 9.75f, UINT32_MAX, "DEN,APRS", "responded"}, + {"8899AABBCCDDEEFF", -3.5f, UINT32_MAX, "", "timeout"}, + }; + + size_t measured = + MQTTPayloadBuilder::measureNeighborsMessageBase( + "DEN Repeater", "0123456789ABCDEF", kTimestamp, "DEN,APRS", "*", 2) + + MQTTPayloadBuilder::measureNeighborsMessageEntry(neighbors[0]) + + 1 // array comma + + MQTTPayloadBuilder::measureNeighborsMessageEntry(neighbors[1]); + + JsonDocument scratch; + char buffer[1024]; + int len = MQTTPayloadBuilder::buildNeighborsMessage( + scratch, "DEN Repeater", "0123456789ABCDEF", kTimestamp, "DEN,APRS", "*", + neighbors, 2, buffer, sizeof(buffer), 2, 2, false); + + ASSERT_GT(len, 0); + EXPECT_EQ(measured, static_cast(len)); +} + +TEST(MQTTPayloadBuilder, NeighborsMessageMeasuredPrefixStopsBeforeOverflow) { + MQTTPayloadBuilder::NeighborsMessageEntry neighbors[20]; + static char keys[20][17]; + static char long_scopes[96]; + memset(long_scopes, 'S', sizeof(long_scopes) - 1); + long_scopes[sizeof(long_scopes) - 1] = '\0'; + for (int i = 0; i < 20; i++) { + snprintf(keys[i], sizeof(keys[i]), "%016X", i); + neighbors[i] = { + keys[i], static_cast(i), UINT32_MAX, + long_scopes, "responded"}; + } + + constexpr size_t kBufferSize = 512; + size_t used = MQTTPayloadBuilder::measureNeighborsMessageBase( + "node", "id", kTimestamp, "DEN", "*", 20); + int published = 0; + int queried = 0; + while (queried < 20) { + size_t added = + MQTTPayloadBuilder::measureNeighborsMessageEntry(neighbors[queried]) + + (published > 0 ? 1U : 0U); + queried++; + if (used + added >= kBufferSize) break; + used += added; + published++; + } + + ASSERT_GT(published, 0); + ASSERT_LT(published, 20); + ASSERT_EQ(published + 1, queried); // only the non-fitting candidate was extra + + JsonDocument scratch; + char buffer[kBufferSize]; + int len = MQTTPayloadBuilder::buildNeighborsMessage( + scratch, "node", "id", kTimestamp, "DEN", "*", + neighbors, published, buffer, sizeof(buffer), + 20, queried, true); + + ASSERT_GT(len, 0); + EXPECT_LT(static_cast(len), sizeof(buffer)); + JsonDocument parsed; + ASSERT_FALSE(deserializeJson(parsed, buffer)); + EXPECT_EQ(static_cast(published), + parsed["neighbors"].as().size()); + EXPECT_EQ(20, parsed["total_neighbors"].as()); + EXPECT_EQ(queried, parsed["queried_neighbors"].as()); + EXPECT_TRUE(parsed["truncated"].as()); +} + +TEST(MQTTPayloadBuilder, NeighborsMessageFallbackMarksTruncated) { + MQTTPayloadBuilder::NeighborsMessageEntry neighbors[] = { + {"0011223344556677", 9.75f, 1, "DEN", "responded"}, + {"8899AABBCCDDEEFF", -3.5f, 2, "APRS", "responded"}, + }; + size_t first_only_size = + MQTTPayloadBuilder::measureNeighborsMessageBase( + "node", "id", kTimestamp, "DEN", "*", 2) + + MQTTPayloadBuilder::measureNeighborsMessageEntry(neighbors[0]); + std::vector buffer(first_only_size + 1); + + JsonDocument scratch; + int len = MQTTPayloadBuilder::buildNeighborsMessage( + scratch, "node", "id", kTimestamp, "DEN", "*", + neighbors, 2, buffer.data(), buffer.size(), + 2, 2, false); + + ASSERT_GT(len, 0); + JsonDocument parsed; + ASSERT_FALSE(deserializeJson(parsed, buffer.data())); + EXPECT_EQ(1U, parsed["neighbors"].as().size()); + EXPECT_TRUE(parsed["truncated"].as()); +} + +TEST(MQTTPayloadBuilder, NeighborsMessageFailsCleanlyOnAllocationFailure) { + MQTTPayloadBuilder::NeighborsMessageEntry neighbor = { + "0011223344556677", 9.75f, 1, "DEN", "responded"}; + RejectAllJsonAllocations allocator; + JsonDocument scratch(&allocator); + char buffer[512]; + + EXPECT_EQ(0, MQTTPayloadBuilder::buildNeighborsMessage( + scratch, "node", "id", kTimestamp, "DEN", "*", + &neighbor, 1, buffer, sizeof(buffer), + 1, 1, false)); +} + TEST(MQTTPayloadBuilder, NeighborsMessageHandlesEmptyTableAndNullScopes) { JsonDocument scratch; char buffer[256]; int len = MQTTPayloadBuilder::buildNeighborsMessage( - scratch, "node", "id", kTimestamp, nullptr, nullptr, 0, + scratch, "node", "id", kTimestamp, nullptr, nullptr, nullptr, 0, buffer, sizeof(buffer)); ASSERT_GT(len, 0); JsonDocument parsed; ASSERT_FALSE(deserializeJson(parsed, buffer)); EXPECT_STREQ("", parsed["self"]["scopes"].as()); + EXPECT_STREQ("", parsed["self"]["default_scope"].as()); JsonArray arr = parsed["neighbors"].as(); ASSERT_TRUE(arr.isNull() == false); EXPECT_EQ(0U, arr.size()); @@ -300,6 +421,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageDropsTailWhenBufferFills) { neighbors[i].pubkey_hex = keys[i]; neighbors[i].snr = static_cast(i); neighbors[i].heard_secs_ago = static_cast(i) * 10U; + neighbors[i].heard_unknown = false; neighbors[i].scopes = "DEN"; neighbors[i].status = "active"; } @@ -307,7 +429,7 @@ TEST(MQTTPayloadBuilder, NeighborsMessageDropsTailWhenBufferFills) { JsonDocument scratch; char buffer[512]; int len = MQTTPayloadBuilder::buildNeighborsMessage( - scratch, "node", "id", kTimestamp, "DEN", neighbors, 20, + scratch, "node", "id", kTimestamp, "DEN", "*", neighbors, 20, buffer, sizeof(buffer)); ASSERT_GT(len, 0); @@ -318,10 +440,52 @@ TEST(MQTTPayloadBuilder, NeighborsMessageDropsTailWhenBufferFills) { ASSERT_FALSE(arr.isNull()); EXPECT_GT(arr.size(), 0U); EXPECT_LT(arr.size(), 20U); + EXPECT_FALSE(parsed["truncated"].is()); // Kept entries are the head of the input, in order. EXPECT_STREQ(keys[0], arr[0]["pubkey"].as()); } +TEST(MQTTPayloadBuilder, NeighborsMessageRendersUnknownHeardAgeAsNull) { + // A neighbour stamped before the clock was set has no usable age. The key + // stays present and null so a consumer cannot read it as "heard just now". + MQTTPayloadBuilder::NeighborsMessageEntry neighbors[2]; + neighbors[0] = {"0011223344556677", 9.75f, 42, "DEN", "responded"}; + neighbors[1] = {"8899AABBCCDDEEFF", -3.5f, 0, "DEN", "responded"}; + neighbors[1].heard_unknown = true; + + JsonDocument scratch; + char buffer[1024]; + int len = MQTTPayloadBuilder::buildNeighborsMessage( + scratch, "node", "id", kTimestamp, "DEN", "*", neighbors, 2, + buffer, sizeof(buffer), 2, 2, false); + + ASSERT_GT(len, 0); + JsonDocument parsed; + ASSERT_FALSE(deserializeJson(parsed, buffer)); + JsonArray arr = parsed["neighbors"].as(); + ASSERT_EQ(2U, arr.size()); + EXPECT_EQ(42U, arr[0]["heard_secs_ago"].as()); + EXPECT_FALSE(arr[0]["heard_secs_ago"].isNull()); + EXPECT_TRUE(arr[1]["heard_secs_ago"].isNull()); + EXPECT_NE(nullptr, strstr(buffer, "\"heard_secs_ago\":null")); + // The rest of the entry still renders. + EXPECT_STREQ("8899AABBCCDDEEFF", arr[1]["pubkey"].as()); + EXPECT_STREQ("responded", arr[1]["status"].as()); +} + +TEST(MQTTPayloadBuilder, NeighborsMessageUnknownHeardAgeFitsMeasuredWidth) { + // Paced discovery measures each entry with a known UINT32_MAX age; a null age + // must never serialize wider than what that reserved. + MQTTPayloadBuilder::NeighborsMessageEntry measured = { + "0011223344556677", 9.75f, UINT32_MAX, "DEN", "responded"}; + MQTTPayloadBuilder::NeighborsMessageEntry unknown = measured; + unknown.heard_unknown = true; + unknown.heard_secs_ago = 0; + + EXPECT_LE(MQTTPayloadBuilder::measureNeighborsMessageEntry(unknown), + MQTTPayloadBuilder::measureNeighborsMessageEntry(measured)); +} + } // namespace int main(int argc, char** argv) { diff --git a/test/test_mqtt_prefs_codec/test_mqtt_prefs_codec.cpp b/test/test_mqtt_prefs_codec/test_mqtt_prefs_codec.cpp index 6d1fba4b..25d38501 100644 --- a/test/test_mqtt_prefs_codec/test_mqtt_prefs_codec.cpp +++ b/test/test_mqtt_prefs_codec/test_mqtt_prefs_codec.cpp @@ -5,6 +5,7 @@ #define WITH_MQTT_BRIDGE 1 #include "helpers/MQTTPrefsCodec.h" +#include "helpers/MQTTPacketFilter.h" namespace Codec = MQTTPrefsCodec; @@ -20,6 +21,7 @@ MQTTPrefs defaults() { prefs.wifi_power_save = 1; for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { strncpy(prefs.mqtt_slot_preset[i], "none", sizeof(prefs.mqtt_slot_preset[i]) - 1); + prefs.mqtt_slot_packet_filter[i] = MQTTPacketFilter::kAllPacketTypes; } strncpy(prefs.snmp_community, "public", sizeof(prefs.snmp_community) - 1); prefs.radio_watchdog_minutes = 5; @@ -218,6 +220,10 @@ TEST(MQTTPrefsCodec, MigratesAllLegacySixSlotPrefixesWithoutClobberingDefaults) } else { EXPECT_STREQ("time.example", prefs.mqtt_ntp_server); } + for (int slot = 0; slot < MQTT_PREFS_SLOT_COUNT; ++slot) { + EXPECT_EQ(MQTTPacketFilter::kAllPacketTypes, + prefs.mqtt_slot_packet_filter[slot]) << size << ":" << slot; + } } } @@ -228,6 +234,9 @@ TEST(MQTTPrefsCodec, CurrentVersionedPayloadRoundTripsExactly) { strncpy(source.alert_region, "PNW", sizeof(source.alert_region) - 1); source.mqtt_neighbors_enabled = 1; source.mqtt_neighbors_interval = MQTT_NEIGHBORS_MAX_INTERVAL_MS; + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + source.mqtt_slot_packet_filter[i] = static_cast(1u << i); + } std::vector bytes(Codec::kEncodedSize); ASSERT_EQ(Codec::kEncodedSize, Codec::encode(source, bytes.data(), bytes.size())); @@ -240,6 +249,114 @@ TEST(MQTTPrefsCodec, CurrentVersionedPayloadRoundTripsExactly) { EXPECT_EQ(0, memcmp(&source, &loaded, sizeof(source))); } +// /mqtt_prefs also carries the WiFi credentials, so a payload older firmware +// rejects strands a downgraded node with no network and no way to save. The +// filter tail is only written once it actually holds something. +TEST(MQTTPrefsCodec, DefaultFiltersKeepTheDowngradeReadablePayloadLength) { + MQTTPrefs source = defaults(); + strncpy(source.wifi_ssid, "home-net", sizeof(source.wifi_ssid) - 1); + strncpy(source.mqtt_slot_preset[0], "analyzer-us", sizeof(source.mqtt_slot_preset[0]) - 1); + + ASSERT_EQ(Codec::kV1PreFilterPayloadSize, Codec::payloadLenFor(source)); + std::vector bytes(Codec::kEncodedSize, 0xEE); + const size_t written = Codec::encode(source, bytes.data(), bytes.size()); + ASSERT_EQ(sizeof(MQTTPrefsHeader) + Codec::kV1PreFilterPayloadSize, written); + EXPECT_LT(written, Codec::kEncodedSize); + + MQTTPrefsHeader header; + memcpy(&header, bytes.data(), sizeof(header)); + EXPECT_EQ(Codec::kV1PreFilterPayloadSize, header.payload_len); + // Nothing past the boundary was touched, so the file really is the short one. + for (size_t i = written; i < bytes.size(); ++i) { + EXPECT_EQ(0xEE, bytes[i]) << i; + } + + bytes.resize(written); + const Codec::DecodePlan plan = classify(bytes); + ASSERT_EQ(Codec::Source::Current, plan.source); + ASSERT_EQ(Codec::kV1PreFilterPayloadSize, plan.payload_len); + ASSERT_FALSE(plan.preserve_file); + + MQTTPrefs loaded = defaults(); + memcpy(&loaded, bytes.data() + sizeof(MQTTPrefsHeader), plan.payload_len); + EXPECT_EQ(0, memcmp(&source, &loaded, sizeof(source))); +} + +TEST(MQTTPrefsCodec, AnyNonDefaultFilterOptsIntoTheLongerPayload) { + MQTTPrefs source = defaults(); + strncpy(source.wifi_ssid, "home-net", sizeof(source.wifi_ssid) - 1); + + // "none" is as much a real setting as a subset, and must survive a reload. + for (int slot = 0; slot < MQTT_PREFS_SLOT_COUNT; ++slot) { + for (uint16_t mask : {static_cast(0), + static_cast(1u << 4), + static_cast(MQTTPacketFilter::kAllPacketTypes & ~1u)}) { + source.mqtt_slot_packet_filter[slot] = mask; + ASSERT_EQ(Codec::kV1BaselinePayloadSize, Codec::payloadLenFor(source)) << slot; + + std::vector bytes(Codec::kEncodedSize); + ASSERT_EQ(Codec::kEncodedSize, Codec::encode(source, bytes.data(), bytes.size())); + const Codec::DecodePlan plan = classify(bytes); + ASSERT_EQ(Codec::kV1BaselinePayloadSize, plan.payload_len); + + MQTTPrefs loaded = defaults(); + memcpy(&loaded, bytes.data() + sizeof(MQTTPrefsHeader), plan.payload_len); + EXPECT_EQ(mask, loaded.mqtt_slot_packet_filter[slot]) << slot; + EXPECT_EQ(0, memcmp(&source, &loaded, sizeof(source))); + } + source.mqtt_slot_packet_filter[slot] = MQTTPacketFilter::kAllPacketTypes; + } + // Clearing the last non-default filter returns the node to the short payload. + EXPECT_EQ(Codec::kV1PreFilterPayloadSize, Codec::payloadLenFor(source)); +} + +TEST(MQTTPrefsCodec, EncodeRefusesAnOutputBufferShorterThanItsChosenPayload) { + MQTTPrefs shortest = defaults(); + const size_t short_size = sizeof(MQTTPrefsHeader) + Codec::kV1PreFilterPayloadSize; + std::vector bytes(Codec::kEncodedSize); + + EXPECT_EQ(0u, Codec::encode(shortest, bytes.data(), short_size - 1)); + EXPECT_EQ(short_size, Codec::encode(shortest, bytes.data(), short_size)); + EXPECT_EQ(0u, Codec::encode(shortest, nullptr, bytes.size())); + + // A buffer that fits the short payload is not enough for the long one. + MQTTPrefs longest = defaults(); + longest.mqtt_slot_packet_filter[0] = 0; + EXPECT_EQ(0u, Codec::encode(longest, bytes.data(), short_size)); + EXPECT_EQ(Codec::kEncodedSize, Codec::encode(longest, bytes.data(), bytes.size())); +} + +TEST(MQTTPrefsCodec, PreFilterV1PayloadDefaultsEverySlotToAllTypes) { + MQTTPrefs source = defaults(); + strncpy(source.mqtt_origin, "pre-filter-node", sizeof(source.mqtt_origin) - 1); + source.mqtt_neighbors_enabled = 1; + source.mqtt_neighbors_interval = MQTT_NEIGHBORS_MAX_INTERVAL_MS; + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + source.mqtt_slot_packet_filter[i] = 0; + } + + std::vector bytes(sizeof(MQTTPrefsHeader) + Codec::kV1PreFilterPayloadSize, 0); + writeHeader(&bytes, MQTT_PREFS_VERSION, + static_cast(Codec::kV1PreFilterPayloadSize)); + memcpy(bytes.data() + sizeof(MQTTPrefsHeader), &source, Codec::kV1PreFilterPayloadSize); + + const Codec::DecodePlan plan = classify(bytes); + ASSERT_EQ(Codec::Source::Current, plan.source); + ASSERT_EQ(Codec::kV1PreFilterPayloadSize, plan.payload_len); + ASSERT_TRUE(plan.observer_fields_present); + ASSERT_FALSE(plan.preserve_file); + + MQTTPrefs loaded = defaults(); + memcpy(&loaded, bytes.data() + sizeof(MQTTPrefsHeader), plan.payload_len); + EXPECT_STREQ("pre-filter-node", loaded.mqtt_origin); + EXPECT_EQ(1u, loaded.mqtt_neighbors_enabled); + EXPECT_EQ(MQTT_NEIGHBORS_MAX_INTERVAL_MS, loaded.mqtt_neighbors_interval); + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + EXPECT_EQ(MQTTPacketFilter::kAllPacketTypes, + loaded.mqtt_slot_packet_filter[i]) << i; + } +} + TEST(MQTTPrefsCodec, CompatibleShortV1PayloadPreservesDefaultsBeyondObserverBoundary) { MQTTPrefs source = defaults(); strncpy(source.mqtt_origin, "short-v1-node", sizeof(source.mqtt_origin) - 1); @@ -304,6 +421,10 @@ TEST(MQTTPrefsCodec, PreNeighborsV1PayloadLoadsObserverFieldsAndDefaultsNeighbor // Interval begins at 2860 (beyond the read) -> keeps the caller's default. EXPECT_EQ(0u, loaded.mqtt_neighbors_enabled); EXPECT_EQ(MQTT_NEIGHBORS_DEFAULT_INTERVAL_MS, loaded.mqtt_neighbors_interval); + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + EXPECT_EQ(MQTTPacketFilter::kAllPacketTypes, + loaded.mqtt_slot_packet_filter[i]) << i; + } } TEST(MQTTPrefsCodec, CorruptOrShortVersionedInputsArePreserved) { @@ -378,18 +499,83 @@ TEST(MQTTPrefsCodec, UnsupportedHeaderlessSizesArePreserved) { } } -TEST(MQTTPrefsCodec, NewerAndSameVersionExtendedPayloadsAreHeld) { +// A different version tag means the layout may have changed shape, so it is +// still refused outright. That refusal is what makes reading longer same-version +// payloads safe, so the two belong in one test. +TEST(MQTTPrefsCodec, ADifferentVersionTagIsStillRefusedAndHeld) { std::vector newer(sizeof(MQTTPrefsHeader), 0); writeHeader(&newer, MQTT_PREFS_VERSION + 1, 0); - Codec::DecodePlan plan = classify(newer); + const Codec::DecodePlan plan = classify(newer); EXPECT_EQ(Codec::Source::UnsupportedVersion, plan.source); EXPECT_TRUE(plan.preserve_file); +} - std::vector extended(sizeof(MQTTPrefsHeader) + Codec::kV1BaselinePayloadSize + 1, 0); - writeHeader(&extended, MQTT_PREFS_VERSION, - static_cast(Codec::kV1BaselinePayloadSize + 1)); - plan = classify(extended); - EXPECT_EQ(Codec::Source::UnsupportedVersion, plan.source); +// The downgrade contract: a v1 payload longer than this build's baseline was +// appended to by a later build, so the baseline prefix is present verbatim. +// Read it and drop the tail -- never refuse the file, which would cost the +// operator WiFi and every broker slot to save settings they don't understand. +TEST(MQTTPrefsCodec, LongerSameVersionPayloadLoadsTheBaselineAndIgnoresTheTail) { + MQTTPrefs source = defaults(); + strncpy(source.mqtt_origin, "future-node", sizeof(source.mqtt_origin) - 1); + strncpy(source.wifi_ssid, "field-ssid", sizeof(source.wifi_ssid) - 1); + strncpy(source.wifi_password, "field-secret", sizeof(source.wifi_password) - 1); + strncpy(source.mqtt_iata, "SEA", sizeof(source.mqtt_iata) - 1); + strncpy(source.mqtt_slot_preset[0], "meshrank", sizeof(source.mqtt_slot_preset[0]) - 1); + source.mqtt_neighbors_enabled = 1; + for (int i = 0; i < MQTT_PREFS_SLOT_COUNT; ++i) { + source.mqtt_slot_packet_filter[i] = static_cast(1u << i); + } + + // Baseline image plus a 40-byte tail of fields this build has never heard of. + const size_t kTail = 40; + const size_t payload_len = Codec::kV1BaselinePayloadSize + kTail; + std::vector bytes(sizeof(MQTTPrefsHeader) + payload_len, 0xA5); + writeHeader(&bytes, MQTT_PREFS_VERSION, static_cast(payload_len)); + memcpy(bytes.data() + sizeof(MQTTPrefsHeader), &source, sizeof(source)); + + const Codec::DecodePlan plan = classify(bytes); + ASSERT_EQ(Codec::Source::Current, plan.source); + EXPECT_FALSE(plan.preserve_file) << "refusing the file would strand the node"; + EXPECT_TRUE(plan.observer_fields_present); + EXPECT_FALSE(plan.rewrite_legacy); + ASSERT_EQ(Codec::kV1BaselinePayloadSize, plan.payload_len); + + // Reading plan.payload_len bytes recovers this build's whole struct exactly, + // and cannot run past it into the unknown tail. + MQTTPrefs loaded = defaults(); + memcpy(&loaded, bytes.data() + sizeof(MQTTPrefsHeader), plan.payload_len); + EXPECT_EQ(0, memcmp(&source, &loaded, sizeof(source))); + EXPECT_STREQ("future-node", loaded.mqtt_origin); + EXPECT_STREQ("field-ssid", loaded.wifi_ssid); + EXPECT_STREQ("field-secret", loaded.wifi_password); + EXPECT_STREQ("meshrank", loaded.mqtt_slot_preset[0]); + EXPECT_EQ(1u, loaded.mqtt_neighbors_enabled); + EXPECT_EQ(1u, loaded.mqtt_slot_packet_filter[0]); +} + +// The rule has to hold for any appended size, including a single byte and a +// tail far larger than the baseline. +TEST(MQTTPrefsCodec, EveryLongerSameVersionLengthReadsTheBaseline) { + for (const size_t tail : {size_t(1), size_t(2), size_t(12), size_t(64), + size_t(512), size_t(4096)}) { + const size_t payload_len = Codec::kV1BaselinePayloadSize + tail; + std::vector bytes(sizeof(MQTTPrefsHeader) + payload_len, 0); + writeHeader(&bytes, MQTT_PREFS_VERSION, static_cast(payload_len)); + const Codec::DecodePlan plan = classify(bytes); + EXPECT_EQ(Codec::Source::Current, plan.source) << tail; + EXPECT_FALSE(plan.preserve_file) << tail; + EXPECT_EQ(Codec::kV1BaselinePayloadSize, plan.payload_len) << tail; + } +} + +// A length that merely *declares* a longer payload without the bytes to back it +// is still corrupt -- the header/file-size agreement check must run first. +TEST(MQTTPrefsCodec, LongerDeclaredLengthWithoutTheBytesIsStillCorrupt) { + std::vector bytes(sizeof(MQTTPrefsHeader) + Codec::kV1BaselinePayloadSize, 0); + writeHeader(&bytes, MQTT_PREFS_VERSION, + static_cast(Codec::kV1BaselinePayloadSize + 16)); + const Codec::DecodePlan plan = classify(bytes); + EXPECT_EQ(Codec::Source::Corrupt, plan.source); EXPECT_TRUE(plan.preserve_file); } diff --git a/test/test_mqtt_topic_router/test_mqtt_topic_router.cpp b/test/test_mqtt_topic_router/test_mqtt_topic_router.cpp index 09e63669..3d688651 100644 --- a/test/test_mqtt_topic_router/test_mqtt_topic_router.cpp +++ b/test/test_mqtt_topic_router/test_mqtt_topic_router.cpp @@ -44,24 +44,27 @@ TEST(MQTTTopicRouter, EveryMeshCorePresetSupportsEveryPublicationType) { } } -TEST(MQTTTopicRouter, MeshRankContractIsPacketsOnly) { +// Guards the raw exclusion: a merge from observer-firmware must not re-enable it. +TEST(MQTTTopicRouter, MeshRankTakesEveryTypeExceptRaw) { const MQTTPresetDef* preset = findMQTTPreset("meshrank"); ASSERT_NE(nullptr, preset); ASSERT_EQ(MQTT_TOPIC_MESHRANK, preset->topic_style); - char topic[128]; - EXPECT_FALSE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, MQTT_PUBLICATION_STATUS, - nullptr, IATA, DEVICE, TOKEN, topic, sizeof(topic))); - EXPECT_STREQ("", topic); - ASSERT_TRUE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, MQTT_PUBLICATION_PACKETS, - nullptr, IATA, DEVICE, TOKEN, topic, sizeof(topic))); - EXPECT_STREQ("meshrank/uplink/account-token/0123456789ABCDEF/packets", topic); - EXPECT_FALSE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, MQTT_PUBLICATION_RAW, - nullptr, IATA, DEVICE, TOKEN, topic, sizeof(topic))); - EXPECT_STREQ("", topic); - EXPECT_FALSE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, MQTT_PUBLICATION_NEIGHBORS, - nullptr, IATA, DEVICE, TOKEN, topic, sizeof(topic))); - EXPECT_STREQ("", topic); + for (const TypeCase& type : kTypes) { + char topic[128]; + if (type.type == MQTT_PUBLICATION_RAW) { + EXPECT_FALSE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, type.type, nullptr, + IATA, DEVICE, TOKEN, topic, sizeof(topic))); + EXPECT_STREQ("", topic); + continue; + } + ASSERT_TRUE(mqttBuildPublicationTopic(MQTT_ROUTE_MESHRANK, type.type, nullptr, + IATA, DEVICE, TOKEN, topic, sizeof(topic))) + << type.name; + EXPECT_EQ(std::string("meshrank/uplink/account-token/0123456789ABCDEF/") + type.name, + topic) + << type.name; + } } TEST(MQTTTopicRouter, MeshCoreRequiresUsableIataAndDevice) { diff --git a/test/test_webconfig_batch/test_webconfig_batch.cpp b/test/test_webconfig_batch/test_webconfig_batch.cpp index 28b6f215..d3c8b6c3 100644 --- a/test/test_webconfig_batch/test_webconfig_batch.cpp +++ b/test/test_webconfig_batch/test_webconfig_batch.cpp @@ -210,6 +210,108 @@ TEST(WebConfigBatch, StopWarnsOnceAfterTheDeadlineThenKeepsWaiting) { EXPECT_EQ(Batch::StopAction::Wait, Batch::stopStep(2, false, 0, 999999)); } +// -------------------------------------------------------------------------- +// CLI sequences (/api/cli), which share the deferred-command slot +// -------------------------------------------------------------------------- +TEST(WebConfigBatch, CliReadPagesResultsAndNeverOverrunsWhatHasDrained) { + const int page = Batch::kCliResultPage; + // Nothing drained past the cursor yet. + EXPECT_EQ(0, Batch::cliPageCount(/*from=*/0, /*produced=*/0, page)); + EXPECT_EQ(0, Batch::cliPageCount(/*from=*/3, /*produced=*/3, page)); + // Partial progress: hand back exactly what exists. + EXPECT_EQ(3, Batch::cliPageCount(0, 3, page)); + EXPECT_EQ(2, Batch::cliPageCount(5, 7, page)); + // More available than fits in one read: cap at the page size. + EXPECT_EQ(page, Batch::cliPageCount(0, page + 5, page)); + // A cursor beyond what has drained (stale or crafted) yields nothing rather + // than a negative count that would index backwards through the batch. + EXPECT_EQ(0, Batch::cliPageCount(/*from=*/9, /*produced=*/4, page)); +} + +TEST(WebConfigBatch, CliReadIsDoneOnlyOnceEveryResultHasBeenHandedOver) { + // Still executing: never final, however much has been read. + EXPECT_FALSE(Batch::cliReadIsFinal(State::Pending, /*from=*/0, /*page=*/8, /*total=*/8)); + // Execution finished but the client has only seen the first page. Reporting + // "done" here would make a client that stops polling lose the rest. + EXPECT_FALSE(Batch::cliReadIsFinal(State::Done, /*from=*/0, /*page=*/8, /*total=*/20)); + EXPECT_FALSE(Batch::cliReadIsFinal(State::Done, /*from=*/8, /*page=*/8, /*total=*/20)); + // The read that hands over the last result is the final one. + EXPECT_TRUE(Batch::cliReadIsFinal(State::Done, /*from=*/16, /*page=*/4, /*total=*/20)); + // Re-reading past the end stays final (polls after the last page). + EXPECT_TRUE(Batch::cliReadIsFinal(State::Done, /*from=*/20, /*page=*/0, /*total=*/20)); +} + +// Every string below is a literal lifted from CommonCLI.cpp / +// CommonCLI_Observer.cpp. Testing only for an "Err" prefix passed five of these +// off as success, which both coloured them green and let a queued reboot go +// ahead after them. +TEST(WebConfigBatch, CliFailureRepliesAreRecognisedInEveryShapeCommonCLIEmits) { + const char* failures[] = { + "Err - bad params", // MyMesh setperm + "ERR: bad pubkey", // neighbor.remove + "Error: IATA code must be exactly 3 letters",// observer setters + "(ERR: clock cannot go backwards)", // clock sync, parenthesised + "Unknown command", // top-level fallthrough + "unknown config: mqtt.nope", // set fallthrough + "??: mqtt.nope", // get fallthrough + "Can't find GPS", // gps + "File system erase: Err", // failure reported at the end + }; + for (const char* f : failures) { + EXPECT_TRUE(Batch::cliReplyIsFailure(f)) << f; + } + + const char* successes[] = { + "OK", + "OK - slot 1 preset: meshrank", + "> 22", // getter value + "> msgs: on, 1: analyzer-us (ok)", // getter, contains "ok" + "File system erase: OK", // same shape, succeeded + "Free: 142832, Min: 126808", // memory + "v1.16.0 (Build: 6 Jun 2026)", // ver + }; + for (const char* s : successes) { + EXPECT_FALSE(Batch::cliReplyIsFailure(s)) << s; + } + // An empty reply is normalised to "OK" before it ever reaches the client. + EXPECT_FALSE(Batch::cliReplyIsFailure("")); + EXPECT_FALSE(Batch::cliReplyIsFailure(NULL)); +} + +TEST(WebConfigBatch, OnlyWritesGateTheDeferredReboot) { + // Writes gate it: these are what can leave a config not worth rebooting into. + EXPECT_TRUE(Batch::cliReplyGatesReboot("set tx 22")); + EXPECT_TRUE(Batch::cliReplyGatesReboot("set mqtt1.preset meshrank")); + EXPECT_TRUE(Batch::cliReplyGatesReboot("password hunter2")); + // Diagnostics do not. `memory` answering "Free: ..." must not be read as a + // failure and strand the operator's reboot, and a getter's "> value" must not + // be read as a success either -- neither is asked. + EXPECT_FALSE(Batch::cliReplyGatesReboot("memory")); + EXPECT_FALSE(Batch::cliReplyGatesReboot("get tx")); + EXPECT_FALSE(Batch::cliReplyGatesReboot("reboot")); + EXPECT_FALSE(Batch::cliReplyGatesReboot("advert")); + EXPECT_FALSE(Batch::cliReplyGatesReboot(NULL)); + // "settle" must not be mistaken for a `set`; the space is part of the token. + EXPECT_FALSE(Batch::cliReplyGatesReboot("settle")); + + // A write counts only on the "OK" prefix every setter keeps. + EXPECT_TRUE(Batch::cliWriteSucceeded("OK")); + EXPECT_TRUE(Batch::cliWriteSucceeded("OK - reboot to apply")); + EXPECT_FALSE(Batch::cliWriteSucceeded("unknown config: nope")); + EXPECT_FALSE(Batch::cliWriteSucceeded("Error: expected a number")); + EXPECT_FALSE(Batch::cliWriteSucceeded("")); +} + +TEST(WebConfigBatch, CliRebootIsWithheldWhenAnyCommandInTheSequenceFailed) { + EXPECT_TRUE(Batch::cliRebootAllowed(/*has_reboot=*/true, /*all_ok=*/true)); + // Same rule a config save follows: do not reboot into a half-applied config + // over a link the operator may not get back. + EXPECT_FALSE(Batch::cliRebootAllowed(true, false)); + // No `reboot` in the sequence: nothing to allow either way. + EXPECT_FALSE(Batch::cliRebootAllowed(false, true)); + EXPECT_FALSE(Batch::cliRebootAllowed(false, false)); +} + // -------------------------------------------------------------------------- // Wrap-around guard shared with the production _reboot_at assignments // -------------------------------------------------------------------------- diff --git a/test/test_webconfig_keys/test_webconfig_keys.cpp b/test/test_webconfig_keys/test_webconfig_keys.cpp index ed296fa7..9a6d50c4 100644 --- a/test/test_webconfig_keys/test_webconfig_keys.cpp +++ b/test/test_webconfig_keys/test_webconfig_keys.cpp @@ -22,6 +22,7 @@ TEST(WebConfigKeys, AllowsPerSlotKeys) { EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.preset")); EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.server")); EXPECT_TRUE(wcIsAllowedSetKey("mqtt1.token")); + EXPECT_TRUE(wcIsAllowedSetKey("mqtt2.filter")); EXPECT_TRUE(wcIsAllowedSetKey("mqtt6.audience")); // MAX_MQTT_SLOTS == 6 } @@ -102,6 +103,7 @@ TEST(WebConfigKeys, NonSecretKeysNotFlagged) { EXPECT_FALSE(wcIsSecretKey("wifi.ssid")); EXPECT_FALSE(wcIsSecretKey("mqtt1.username")); // username is not masked EXPECT_FALSE(wcIsSecretKey("mqtt1.server")); + EXPECT_FALSE(wcIsSecretKey("mqtt1.filter")); EXPECT_FALSE(wcIsSecretKey("mqtt.origin")); EXPECT_FALSE(wcIsSecretKey("name")); EXPECT_FALSE(wcIsSecretKey("")); @@ -117,6 +119,45 @@ TEST(WebConfigKeys, EverySecretKeyIsAlsoAllowed) { } } +// ---- CLI secret reads ---------------------------------------------------- +// The web CLI runs commands with sender_timestamp 0, which is how CommonCLI +// recognises the serial console and answers secret getters in plaintext. These +// are the reads that must be masked back down for an HTTP caller. + +TEST(WebConfigKeys, MasksEverySecretReadTheCliCanReach) { + const char* masked[] = { + "get prv.key", // this node's identity -- the worst one to leak + "get wifi.pwd", // grants the operator's LAN, not just the node + "get guest.password", + "get alert.psk", + "get bridge.secret", + "get mqtt1.password", "get mqtt1.token", + "get mqtt6.password", "get mqtt6.token", + "get wifi.pwd", // extra space after the verb + }; + for (const char* c : masked) EXPECT_TRUE(wcIsSecretReadCommand(c)) << c; +} + +TEST(WebConfigKeys, DoesNotMaskReadsThatCarryNoSecret) { + const char* plain[] = { + "get wifi.ssid", "get mqtt1.username", "get mqtt1.server", "get tx", + "get public.key", // public half, safe to read + "get mqtt.owner", // an owner's public key, not a credential + }; + for (const char* c : plain) EXPECT_FALSE(wcIsSecretReadCommand(c)) << c; +} + +TEST(WebConfigKeys, OnlyMasksReads) { + // Writing a secret has always been the portal's job and reveals nothing; + // only the read is restricted. Nor may a prefix be mistaken for a `get`. + EXPECT_FALSE(wcIsSecretReadCommand("set wifi.pwd hunter2")); + EXPECT_FALSE(wcIsSecretReadCommand("set prv.key aabb")); + EXPECT_FALSE(wcIsSecretReadCommand("password hunter2")); + EXPECT_FALSE(wcIsSecretReadCommand("getwifi.pwd")); + EXPECT_FALSE(wcIsSecretReadCommand("get")); + EXPECT_FALSE(wcIsSecretReadCommand("")); +} + // ---- request correlation ------------------------------------------------- TEST(WebConfigKeys, AcceptsExactHexRequestIds) { diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 9e59d9a7..f24291e7 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -113,7 +113,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D MESH_PACKET_LOGGING=1 @@ -206,7 +205,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 71e27f51..6059a1ed 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -185,7 +185,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y @@ -221,7 +222,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y @@ -257,8 +259,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y @@ -294,8 +297,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 1076d739..21207e44 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -115,6 +115,7 @@ lib_deps = [env:Heltec_v3_repeater_observer_mqtt] extends = Heltec_lora32_v3 upload_port = /dev/cu.usbserial-4 +; External I2C sensors (BMP280 etc.) on header pins 33/34, same as the Heltec_v3_sensor env extra_scripts = ${esp32_base.extra_scripts} pre:scripts/generate_cert_bundle.py @@ -129,7 +130,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; Periodic 30s heap/pub-stats serial log -- enable only for debugging (use `get mqtt.stats` on demand instead). @@ -147,6 +149,8 @@ build_flags = # -D MQTT_PORT=1883 # -D MQTT_USERNAME='"your-username"' # -D MQTT_PASSWORD='"your-password"' + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + @@ -195,6 +199,7 @@ lib_deps = [env:Heltec_v3_room_server_observer_mqtt] extends = Heltec_lora32_v3 +; External I2C sensors (BMP280 etc.) on header pins 33/34, same as the Heltec_v3_sensor env extra_scripts = ${esp32_base.extra_scripts} pre:scripts/generate_cert_bundle.py @@ -209,8 +214,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -219,6 +225,8 @@ build_flags = -D ESP32_CPU_FREQ=160 -D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm -D WITH_SNMP=1 + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + @@ -542,7 +550,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; Periodic 30s heap/pub-stats serial log -- enable only for debugging (use `get mqtt.stats` on demand instead). @@ -591,8 +600,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; Keep default observer profile less verbose to reduce runtime contention. diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 14716f8a..540dcb12 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -163,7 +163,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MQTT_MEMORY_DEBUG=1 @@ -214,7 +213,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MQTT_MEMORY_DEBUG=1 @@ -317,7 +315,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -360,7 +357,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 9902396a..e4d02302 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -118,7 +118,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 ; -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -164,7 +163,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index e13ac0bd..5f685d95 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -214,7 +214,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D PERSISTANT_GPS=1 -D ENV_SKIP_GPS_DETECT=1 @@ -261,7 +260,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D PERSISTANT_GPS=1 -D ENV_SKIP_GPS_DETECT=1 diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index 44ca697e..20263afd 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -171,7 +171,6 @@ build_flags = -D WITH_MQTT_BRIDGE=1 -D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; retain scope support within this observer's tight internal DRAM -D OTA_MANIFEST_BASE='"https://observer.gessaman.com/v"' - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 ; -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -214,7 +213,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index c77587a4..aad99a10 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -167,7 +167,6 @@ build_flags = -D WITH_MQTT_BRIDGE=1 -D FLOOD_CHANNEL_SCOPE_SLOTS=31 ; retain scope support within this observer's tight internal DRAM -D OTA_MANIFEST_BASE='"https://observer.gessaman.com/v"' - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 ; -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -210,7 +209,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 45e1c0fe..6e48021e 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -122,7 +122,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D MESH_PACKET_LOGGING=1 @@ -165,7 +164,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index e15fa56d..a4a158ce 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -186,7 +186,6 @@ build_flags = -D WEBCONFIG_DISABLED=1 ; async portal exceeds this 4 MB target's dual-OTA app slot; use CLI setup -D FLOOD_CHANNEL_SCOPE_SLOTS=4 ; no-PSRAM observer must retain internal DRAM for MQTT/TLS -D OTA_MANIFEST_BASE='"https://observer.gessaman.com/v"' - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_TASK_STACK_SIZE=16384 -D ESP32_CPU_FREQ=240 @@ -234,7 +233,6 @@ build_flags = -D WITH_MQTT_BRIDGE=1 -D WEBCONFIG_DISABLED=1 ; async portal exceeds this 4 MB target's dual-OTA app slot; use CLI setup -D OTA_MANIFEST_BASE='"https://observer.gessaman.com/v"' - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_TASK_STACK_SIZE=16384 -D ESP32_CPU_FREQ=240 diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 200f5d20..ea797cab 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -100,6 +100,7 @@ lib_deps = [env:RAK_3112_repeater_observer_mqtt] extends = rak3112 +; External I2C sensors on pins 33/34, same as the RAK_3112_sensor env extra_scripts = ${esp32_base.extra_scripts} pre:scripts/generate_cert_bundle.py @@ -113,7 +114,8 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D MESH_PACKET_LOGGING=1 @@ -128,6 +130,8 @@ build_flags = # -D MQTT_PORT=1883 # -D MQTT_USERNAME='"your-username"' # -D MQTT_PASSWORD='"your-password"' + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 build_src_filter = ${rak3112.build_src_filter} + + @@ -163,6 +167,7 @@ lib_deps = [env:RAK_3112_room_server_observer_mqtt] extends = rak3112 +; External I2C sensors on pins 33/34, same as the RAK_3112_sensor env extra_scripts = ${esp32_base.extra_scripts} pre:scripts/generate_cert_bundle.py @@ -176,8 +181,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 +; Non-PSRAM board: neighbors table costs ~4 KB of internal DRAM (see MQTTBridge.h). + -D MQTT_NEIGHBORS_WITHOUT_PSRAM=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 @@ -186,6 +192,8 @@ build_flags = -D ESP32_CPU_FREQ=160 -D MQTT_WIFI_TX_POWER=WIFI_POWER_11dBm -D WITH_SNMP=1 + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 build_src_filter = ${rak3112.build_src_filter} + + diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index ad73c4a1..1a4de29f 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -26,8 +26,8 @@ build_flags = -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 - -D PIN_GPS_RX=7 - -D PIN_GPS_TX=15 + -D PIN_GPS_RX=15 + -D PIN_GPS_TX=7 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 @@ -202,7 +202,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -346,7 +345,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 # -D MESH_PACKET_LOGGING=1 # -D MESH_DEBUG=1 diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index 00a05713..82bb5b81 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -160,3 +160,90 @@ lib_deps = extends = Station_G3_ESP32 build_src_filter = ${Station_G3_ESP32.build_src_filter} +<../examples/kiss_modem/> + +[env:Station_G3_ESP32_repeater_observer_mqtt] +extends = Station_G3_ESP32 +board_build.partitions = default_16MB.csv ; standard 16MB partition table +extra_scripts = + ${esp32_base.extra_scripts} + pre:scripts/generate_cert_bundle.py +board_ssl_cert_source = adafruit-full +board_build.embed_files = src/certs/x509_crt_bundle.bin +build_flags = + ${Station_G3_ESP32.build_flags} + -D ADVERT_NAME='"MQTT Observer"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_MQTT_BRIDGE=1 + -D MQTT_MAX_PACKET_SIZE=1024 + -D MQTT_DEBUG=1 +# -D MESH_PACKET_LOGGING=1 +# -D MESH_DEBUG=1 + -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y + -D ESP32_CPU_FREQ=160 + -D WITH_SNMP=1 +# -D WIFI_SSID='"ssid"' +# -D WIFI_PWD='"password"' +# -D MQTT_SERVER='"your-mqtt-broker.com"' +# -D MQTT_PORT=1883 +# -D MQTT_USERNAME='"your-username"' +# -D MQTT_PASSWORD='"your-password"' +build_src_filter = ${Station_G3_ESP32.build_src_filter} + + + + + + + + + + + +<../examples/simple_repeater> +lib_deps = + ${Station_G3_ESP32.lib_deps} + ${esp32_ota.lib_deps} + elims/PsychicMqttClient@^0.2.4 + bblanchon/ArduinoJson @ 7.4.3 + arduino-libraries/NTPClient + JChristensen/Timezone + paulstoffregen/Time@1.6.1 + 0neblock/SNMP_Agent + +[env:Station_G3_ESP32_room_server_observer_mqtt] +extends = Station_G3_ESP32 +board_build.partitions = default_16MB.csv ; standard 16MB partition table +extra_scripts = + ${esp32_base.extra_scripts} + pre:scripts/generate_cert_bundle.py +board_ssl_cert_source = adafruit-full +board_build.embed_files = src/certs/x509_crt_bundle.bin +build_flags = + ${Station_G3_ESP32.build_flags} + -D ADVERT_NAME='"Station G3 ESP32 Room Observer"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D WITH_MQTT_BRIDGE=1 + -D MAX_NEIGHBOURS=50 + -D MQTT_MAX_PACKET_SIZE=1024 + -D MQTT_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y + -D ESP32_CPU_FREQ=160 + -D WITH_SNMP=1 +build_src_filter = ${Station_G3_ESP32.build_src_filter} + + + + + + + + + + + +<../examples/simple_room_server> +lib_deps = + ${Station_G3_ESP32.lib_deps} + ${esp32_ota.lib_deps} + elims/PsychicMqttClient@^0.2.4 + bblanchon/ArduinoJson @ 7.4.3 + arduino-libraries/NTPClient + JChristensen/Timezone + paulstoffregen/Time@1.6.1 + 0neblock/SNMP_Agent diff --git a/variants/thinknode_m7/platformio.ini b/variants/thinknode_m7/platformio.ini index 7b9c27da..7da14112 100644 --- a/variants/thinknode_m7/platformio.ini +++ b/variants/thinknode_m7/platformio.ini @@ -28,8 +28,9 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_TX_LED=46 ; blue -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 +; No blanket + here: it would undo arduino_base's exclusion of the +; MQTT-only sources, which need Timezone/NTPClient that only observer envs pull in. build_src_filter = ${esp32_base.build_src_filter} - + + + +<../variants/thinknode_m7> @@ -167,7 +168,7 @@ build_src_filter = ${ThinkNode_M7.build_src_filter} ${ThinkNode_M7_ethernet.build_src_filter} + + - + + + ; defines UIColor::window_bkg for DISPLAY_CLASS +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${ThinkNode_M7.lib_deps} @@ -178,3 +179,95 @@ lib_deps = ${ThinkNode_M7.lib_deps} extends = ThinkNode_M7 build_src_filter = ${ThinkNode_M7.build_src_filter} +<../examples/kiss_modem/> + +; MQTT observer envs are WiFi-only: the bridge's link management is bound to the +; WiFi station API, so the onboard CH390 cannot carry MQTT yet. The board has +; PSRAM, so MAX_NEIGHBOURS enables WITH_MQTT_NEIGHBORS (see MQTTBridge.h). +[env:ThinkNode_M7_repeater_observer_mqtt] +extends = ThinkNode_M7 +extra_scripts = + ${esp32_base.extra_scripts} + pre:scripts/generate_cert_bundle.py +board_ssl_cert_source = adafruit-full +board_build.embed_files = src/certs/x509_crt_bundle.bin +build_flags = + ${ThinkNode_M7.build_flags} + -D ADVERT_NAME='"MQTT Observer"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_MQTT_BRIDGE=1 + -D MQTT_MAX_PACKET_SIZE=1024 + -D MQTT_DEBUG=1 +# -D MESH_PACKET_LOGGING=1 +# -D MESH_DEBUG=1 + -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y + -D ESP32_CPU_FREQ=160 + -D WITH_SNMP=1 +# -D WIFI_SSID='"ssid"' +# -D WIFI_PWD='"password"' +# -D MQTT_SERVER='"your-mqtt-broker.com"' +# -D MQTT_PORT=1883 +# -D MQTT_USERNAME='"your-username"' +# -D MQTT_PASSWORD='"your-password"' +build_src_filter = ${ThinkNode_M7.build_src_filter} + + + + + + + + + +<../examples/simple_repeater> +lib_deps = + ${ThinkNode_M7.lib_deps} + ${esp32_ota.lib_deps} + elims/PsychicMqttClient@^0.2.4 + bblanchon/ArduinoJson @ 7.4.3 + arduino-libraries/NTPClient + JChristensen/Timezone + paulstoffregen/Time@1.6.1 + 0neblock/SNMP_Agent + +[env:ThinkNode_M7_room_server_observer_mqtt] +extends = ThinkNode_M7 +extra_scripts = + ${esp32_base.extra_scripts} + pre:scripts/generate_cert_bundle.py +board_ssl_cert_source = adafruit-full +board_build.embed_files = src/certs/x509_crt_bundle.bin +build_flags = + ${ThinkNode_M7.build_flags} + -D ADVERT_NAME='"ThinkNode M7 Room Observer"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D MAX_NEIGHBOURS=50 + -D WITH_MQTT_BRIDGE=1 + -D MQTT_MAX_PACKET_SIZE=1024 + -D MQTT_DEBUG=1 +# -D MESH_PACKET_LOGGING=1 +# -D MESH_DEBUG=1 + -D CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y + -D ESP32_CPU_FREQ=160 + -D WITH_SNMP=1 +# -D WIFI_SSID='"ssid"' +# -D WIFI_PWD='"password"' +# -D MQTT_SERVER='"your-mqtt-broker.com"' +# -D MQTT_PORT=1883 +# -D MQTT_USERNAME='"your-username"' +# -D MQTT_PASSWORD='"your-password"' +build_src_filter = ${ThinkNode_M7.build_src_filter} + + + + + + + + + +<../examples/simple_room_server> +lib_deps = + ${ThinkNode_M7.lib_deps} + ${esp32_ota.lib_deps} + elims/PsychicMqttClient@^0.2.4 + bblanchon/ArduinoJson @ 7.4.3 + arduino-libraries/NTPClient + JChristensen/Timezone + paulstoffregen/Time@1.6.1 + 0neblock/SNMP_Agent diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 7194ff2c..26099ba9 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -4,7 +4,9 @@ board = seeed_xiao_esp32s3 board_check = true board_build.mcu = esp32s3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/xiao_s3_wio + -UENV_INCLUDE_GPS -D SEEED_XIAO_S3 -D PIN_VBAT_READ=1 ; D0 -D P_LORA_DIO_1=39 @@ -16,6 +18,8 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=9 -D PIN_USER_BTN=21 -D PIN_STATUS_LED=48 + -D PIN_BOARD_SDA=D4 + -D PIN_BOARD_SCL=D5 -D SX126X_RXEN=38 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=true @@ -29,6 +33,9 @@ build_flags = ${esp32_base.build_flags} build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_s3_wio> + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} [env:Xiao_S3_WIO_repeater] extends = Xiao_S3_WIO @@ -60,8 +67,9 @@ lib_deps = ; -D ADMIN_PASSWORD='"password"' ; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 -; -D WITH_RS232_BRIDGE_RX=5 -; -D WITH_RS232_BRIDGE_TX=6 +; RS232 bridge Pins have been relocated from 5,6 which is the i2c bus on xiao_s3 +; -D WITH_RS232_BRIDGE_RX=3 +; -D WITH_RS232_BRIDGE_TX=4 ; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 @@ -104,7 +112,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_MQTT_BRIDGE=1 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 -D MESH_PACKET_LOGGING=1 @@ -148,7 +155,6 @@ build_flags = -D ROOM_PASSWORD='"hello"' -D WITH_MQTT_BRIDGE=1 -D MAX_NEIGHBOURS=50 - -D MAX_MQTT_BROKERS=3 -D MQTT_MAX_PACKET_SIZE=1024 -D MQTT_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/webui/index.html b/webui/index.html index 9bd8e667..a96cfae4 100644 --- a/webui/index.html +++ b/webui/index.html @@ -27,6 +27,10 @@ header svg{flex:none} .hmeta div{font-size:12px;color:var(--mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .badge{margin-left:auto;flex:none;font-size:11px;font-weight:650;padding:3px 9px;border-radius:99px;background:var(--chip);color:var(--mut)} .badge.setup{background:#f3e8d3;color:#8a5c00} +.hicon{flex:none;margin-left:8px;padding:3px 8px;border:1px solid var(--in-line);border-radius:7px; + background:var(--in-bg);color:var(--mut);cursor:pointer;line-height:1.3; + font:700 12px/1.3 ui-monospace,Menlo,Consolas,monospace} +.hicon:hover{color:var(--acc);border-color:var(--acc)} @media (prefers-color-scheme:dark){.badge.setup{background:#3a2f14;color:#e0b45c}} main{max-width:640px;margin:0 auto;padding:16px} .card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px;box-shadow:var(--shadow);margin-bottom:14px} @@ -41,6 +45,18 @@ input[type=text],input[type=password],input[type=number],input[type=email],selec border:1px solid var(--in-line);border-radius:8px;outline:none} /* >=16px: stops iOS zoom-on-focus (sideways-scroll in captive sheet) */ input:focus,select:focus{border-color:var(--acc);box-shadow:0 0 0 2px color-mix(in srgb,var(--acc) 25%,transparent)} input.dirty,select.dirty{border-color:var(--warn)} +/* packet-type allowlist: auto-fit so six slots stay readable on a phone */ +.pkg{display:grid;grid-template-columns:repeat(auto-fill,minmax(122px,1fr));gap:2px 8px; + border:1px solid var(--in-line);border-radius:8px;padding:8px;background:var(--in-bg)} +.f.dirty .pkg{border-color:var(--warn)} +.pk{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:400;margin:0; + min-height:26px;cursor:pointer} +.pk input{width:15px;height:15px;margin:0;flex:none} +.pk span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.pka{display:flex;align-items:center;gap:10px;margin-top:5px;font-size:11.5px} +.lnk{background:none;border:none;padding:0;font:inherit;font-size:11.5px;color:var(--acc); + cursor:pointer;text-decoration:underline} +.pfsum{color:var(--mut);margin-left:auto} .row{display:flex;gap:10px}.row>.f{flex:1;min-width:0} .sw{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 0} .sw span b{display:block;font-size:13.5px;font-weight:600} @@ -106,17 +122,86 @@ canvas{width:100%;height:56px;display:block} .kv b{font-weight:600;text-align:right;overflow:hidden;text-overflow:ellipsis} .hide{display:none!important} .note{font-size:12.5px;color:var(--mut);background:var(--chip);border-radius:8px;padding:9px 11px;margin-bottom:13px} -.cli-out{min-height:260px;max-height:52vh;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere; - background:#0b1016;color:#dce7f5;border:1px solid var(--line);border-radius:9px;padding:11px; - font:13px/1.45 ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace;margin-bottom:10px} -.cli-mode{display:flex;gap:16px;margin:0 0 9px;font-size:13px;color:var(--mut)} -.cli-mode label{display:flex;align-items:center;gap:6px;cursor:pointer} -.cli-mode input{margin:0} -.cli-row{display:flex;align-items:flex-end;gap:8px} -.cli-row input,.cli-row textarea{flex:1;min-width:0;padding:9px 10px;font:16px ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace; - color:var(--ink);background:var(--in-bg);border:1px solid var(--in-line);border-radius:8px;outline:none} -.cli-row textarea{width:100%;min-height:150px;resize:vertical} -.cli-row input:focus,.cli-row textarea:focus{border-color:var(--acc);box-shadow:0 0 0 2px color-mix(in srgb,var(--acc) 25%,transparent)} + +/* ---------------- CLI terminal ---------------- + Deliberately not themed: a console reads as a console in either colour + scheme, and the reply colours below are tuned against this one background. */ +.term{--tf:#d7e0ea;--tdim:#6d7c8f;--tacc:#58a6ff;--tgrn:#57c26e;--terr:#ff7b72;--twarn:#e3b341; + background:#0b0e12;color:var(--tf);border:1px solid #202832;border-radius:12px; + font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace; + font-size:13px;line-height:1.5;display:flex;flex-direction:column;overflow:hidden; + /* generous by design: cliFit() clamps this down to the room actually left + below the tabs, so the terminal fills the screen without scrolling it */ + height:78vh;min-height:240px} +/* >=16px stops iOS zooming the page when the prompt takes focus (same reason the + form inputs are 16px) -- and on a phone 16px mono is the readable size anyway. */ +@media(pointer:coarse){.term{font-size:16px}} +/* body's bottom padding reserves room for the save bar so it can't cover the + last card. The CLI tab sizes itself around the save bar instead (cliFit), so + the reservation there would only add dead space below the terminal. */ +body.tab-cli{padding-bottom:0} +.term-hd{display:flex;align-items:center;gap:8px;flex:none;padding:6px 10px; + background:#11161d;border-bottom:1px solid #202832;font-size:.82em;color:var(--tdim)} +.term-hd .sp{flex:1} +.term-hd button{flex:none;background:none;border:1px solid #2a333f;border-radius:5px; + color:var(--tdim);font:inherit;padding:1px 8px;cursor:pointer} +.term-hd button:hover{color:var(--tf);border-color:#3d4a5a} +.term-out{flex:1;overflow-y:auto;overflow-x:hidden;padding:10px;-webkit-overflow-scrolling:touch} +.term-out>div{white-space:pre-wrap;word-break:break-word} +.term-out .cmd{color:#fff} +.term-out .cmd:before{content:"> ";color:var(--tgrn)} +/* A reply is green because it succeeded; red is reserved for a node that said + no. Anything else reads every `get` as a failure. */ +.term-out .rep{color:#7ee787} +.term-out .err{color:var(--terr)} +.term-out .sys{color:var(--tdim)} +.term-out .gap{height:.55em} +/* zero-height rail: the suggestion list hangs off it and overlays the output + instead of reflowing it, so the line you are typing never moves */ +.term-anchor{position:relative;height:0;flex:none;z-index:2} +/* --sugmax is set from the terminal's own height (see cliFit) so the list can + never be taller than the box it hangs inside and lose rows off the top */ +.term-sug{position:absolute;left:0;right:0;bottom:0;max-height:var(--sugmax,44vh);overflow-y:auto; + background:#0e131a;border-top:1px solid #202832;box-shadow:0 -10px 24px rgba(0,0,0,.55)} +.sg{display:flex;gap:10px;align-items:baseline;padding:5px 10px;cursor:pointer} +.sg b{flex:none;font-weight:500;color:#cfe3ff;white-space:pre} +.sg b u{color:var(--tacc);font-weight:700;text-decoration:none} +.sg i{min-width:0;font-style:normal;font-size:.84em;color:var(--tdim); + overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.sg.on{background:#1e2b3a;box-shadow:inset 3px 0 0 var(--tacc)} +.sg.on b{color:#fff} +.sg.on i{color:#8b9bad} +.term-line{display:flex;align-items:center;gap:8px;flex:none;padding:8px 10px; + background:#0e131a;border-top:1px solid #202832} +.term-ps{flex:none;color:var(--tgrn)} +#term-in{flex:1;min-width:0;padding:0;border:0;border-radius:0;background:none;color:#fff; + font:inherit;outline:none;caret-color:var(--tgrn)} +#term-in:focus{border:0;box-shadow:none} +#term-in::placeholder{color:#495767} +#term-in:disabled{color:var(--tdim)} +.term-go{flex:none;padding:2px 10px;border:1px solid #2a333f;border-radius:6px; + background:none;color:var(--tdim);font:inherit;cursor:pointer} +.term-go:hover{color:var(--tgrn);border-color:#3d4a5a} +.term-go:disabled{opacity:.4;cursor:default} +/* pasted-sequence confirmation, rendered in the scrollback rather than as a + modal so it reads as part of the session (and behaves on a phone) */ +.term-cfm{border:1px solid #2a333f;border-left:3px solid var(--twarn);border-radius:8px; + background:#12181f;padding:8px 10px;margin:6px 0} +.term-cfm .h{color:var(--twarn);margin-bottom:5px} +/* --n is the width of the "12 " gutter: hang the wrap so a long command's + continuation lines up under the command, not under its number */ +.term-cfm .ln{color:#a9b7c6;white-space:pre-wrap;word-break:break-word; + padding-left:var(--n,3ch);text-indent:calc(-1 * var(--n,3ch))} +.term-cfm .ln s{text-decoration:none;color:var(--tdim)} +.term-cfm .w{color:var(--twarn);margin-top:5px;font-size:.9em} +.term-cfm .btns{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px} +.term-cfm button{border:1px solid #2a333f;border-radius:6px;background:#1b2530;color:var(--tf); + font:inherit;padding:4px 12px;cursor:pointer} +.term-cfm button.go{background:#1d3b28;border-color:#2f6b45;color:#8fe0a5} +.term-cfm button:disabled{opacity:.45;cursor:default} +.cli-tip{font-size:12px;color:var(--mut);margin-top:9px;display:flex;gap:12px;flex-wrap:wrap} +.cli-tip kbd{font:inherit;font-family:ui-monospace,Menlo,monospace;background:var(--chip); + border-radius:4px;padding:0 4px} @@ -130,6 +215,12 @@ canvas{width:100%;height:56px;display:block}
connecting…
+ +
@@ -412,24 +503,29 @@ canvas{width:100%;height:56px;display:block}
-
-

Node terminal

-
Available only while connected as a WiFi client; control it with set wifi.cli on|off. Runs one administrator CLI command at a time. Commands that reboot the node or stop WebConfig can close this page before their reply arrives.
-
WebConfig CLI ready.
-
-
- - +
+
+
+ + + + +
+
+
+
+ > + + +
+
+
+ Tab complete history + Esc dismisspaste multiple lines to run a sequence
-
- - - -
@@ -487,6 +583,18 @@ function $(s){return document.querySelector(s)} function $$(s){return Array.prototype.slice.call(document.querySelectorAll(s))} function toast(m){var t=$("#toast");t.textContent=m;t.classList.add("show");clearTimeout(t._h);t._h=setTimeout(function(){t.classList.remove("show")},2600)} function esc(s){return String(s).replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})} +// build.sh embeds base[.build][-observer][-channel]-hash, e.g. +// v1.16.0.5-observer-beta-dev-a1b2c3d. What identifies a build to a person is +// the base, the published build number and the channel -- the -observer variant +// tag is the same on every observer build and the commit is for machines. `ver` +// still prints the whole string when you want it. +function shortVer(){ + var fw=st.fw||"",m=/^(v?\d+\.\d+\.\d+(?:\.\d+)?)/.exec(fw); + var s=m?m[1]:(fw||"unknown version"); + if(/-dev(-|$)/.test(fw))s+="-dev"; // OTA_CHANNEL_TAG=beta-dev + else if(/-beta(-|$)/.test(fw))s+="-beta"; // a beta-only channel, if ever + return st.build?s+" ("+st.build+")":s; +} function api(path,opts){ opts=opts||{}; @@ -499,11 +607,18 @@ function api(path,opts){ } return fetch(path,opts).then(function(r){ if(r.status===401){showLogin();throw new Error("auth")} - return r.json().then(function(j){ + // An error response need not carry JSON -- a bare 404 from handleNotFound + // has an empty body. Letting the parse failure escape would strip the HTTP + // status off the error and leave callers unable to tell "no such endpoint" + // from "the network dropped". Successful responses must still parse. + return r.json().catch(function(){ + if(r.ok)throw new Error("unreadable reply from the node"); + return {}; + }).then(function(j){ if(!r.ok&&r.status!==202){ - var e=new Error(j.error||("HTTP "+r.status)); - e.status=r.status;e.reqid=j.reqid; - throw e; + // Carry the HTTP status and any batch reqid so callers can tell a + // definite rejection (400/409/413) from an ambiguous network failure. + var e=new Error(j.error||("HTTP "+r.status));e.status=r.status;e.reqid=j.reqid;e.key=j.key;throw e; } return j; }); @@ -550,11 +665,20 @@ function applyFeatures(){ /* ---------- boot ---------- */ function boot(){ api("/api/status").then(function(s){ - st.mode=s.mode;st.authed=s.auth;st.hasMqtt=!!s.mqtt;st.hasCli=!!s.cli;st.passwordSupported=!!s.password_supported;st.needsPassword=!!s.needs_password;st.caps=s.capabilities||0;st.nslots=s.active_slots||s.runtime_slots||0; + // Show only as many server slots as the board can keep active at once + // (active_slots: 2 without PSRAM, 5 with). Fall back to the runtime array + // size for older firmware that doesn't report it. + st.mode=s.mode;st.authed=s.auth;st.hasMqtt=!!s.mqtt;st.hasCli=!!s.cli; + st.passwordSupported=!!s.password_supported;st.needsPassword=!!s.needs_password; + st.needsSetup=!!s.needs_setup;st.caps=s.capabilities||0; + st.nslots=st.hasMqtt?(s.active_slots||s.runtime_slots||0):0; + if(s.max_cmds>0)CLI_MAX=s.max_cmds; + st.fw=s.fw||"";st.build=s.build_date||"";st.role=s.role||"";st.board=s.board||""; $("#h-name").textContent=s.name||"MeshCore"; - $("#h-sub").textContent=s.role+" * "+s.fw+" * "+s.board; + $("#h-sub").textContent=s.role+" * "+shortVer()+" * "+s.board; var b=$("#h-badge");b.classList.remove("hide"); if(s.mode==="setup"){b.textContent="SETUP";b.classList.add("setup")}else{b.textContent="LAN"} + $("#h-console").classList.add("hide"); applyFeatures(); return st.hasMqtt?api("/api/presets").catch(function(){return{presets:[]}}):{presets:[]}; }).then(function(p){ @@ -567,7 +691,9 @@ function boot(){ }); } -function show(v){["#v-login","#v-wizard","#v-app"].forEach(function(id){$(id).classList.add("hide")});$(v).classList.remove("hide");updateSaveBar()} +function show(v){["#v-login","#v-wizard","#v-app"].forEach(function(id){$(id).classList.add("hide")});$(v).classList.remove("hide"); + document.body.classList.remove("tab-cli"); // re-armed by the tab click when the CLI is the one shown + updateSaveBar()} function showLogin(){show("#v-login")} /* ---------- login ---------- */ @@ -640,7 +766,8 @@ function loadConfig(){ $$("[data-cfm]").forEach(function(el){el.value=""}); $$("[data-rg]").forEach(function(el){el.value=String(c.radio[el.dataset.rg]);el.classList.remove("dirty")}); st.orig.radio=radioCombo();afHint(); - if(st.hasMqtt)for(var i=1;i<=st.nslots;i++){refreshSlotFields($("#app-slots"),i);refreshSlotFields($("#wz-slots"),i)} + for(var i=1;i<=st.nslots;i++){refreshSlotFields($("#app-slots"),i);refreshSlotFields($("#wz-slots"),i)} + syncPacketFilters(); updateSaveBar(); }); } @@ -686,9 +813,101 @@ function updateSaveBar(){ function revertAll(){loadConfig().then(function(){toast("Reverted")})} /* ---------- slots ---------- */ +// maxlength per destination MQTTPrefs buffer minus the NUL (host[64], +// username[32], password[64], token[48], topic[96], audience[64]). Bounds the +// common browser case; the backend still owns authoritative length limits. +// mqttN.filter is deliberately absent: it is a bitmask, not a text buffer, and +// its accepted input (names, spaces) is longer than its canonical output -- a +// maxlength here would silently truncate a valid list into a different one. +var SLOT_MAXLEN={token:47,username:31,password:63,server:63,topic:95,audience:63}; + +// Payload types, mirroring PAYLOAD_TYPE_* in src/Packet.h. 12-14 are reserved +// upstream with no name, so they are offered by number only. +var PKT_TYPES=[[0,"REQ"],[1,"RESPONSE"],[2,"TXT_MSG"],[3,"ACK"],[4,"ADVERT"], + [5,"GRP_TXT"],[6,"GRP_DATA"],[7,"ANON_REQ"],[8,"PATH"],[9,"TRACE"], + [10,"MULTIPART"],[11,"CONTROL"],[12,"reserved"],[13,"reserved"],[14,"reserved"], + [15,"RAW_CUSTOM"]]; +// Name -> type, for the names MQTTPacketFilter::resolveToken actually accepts. +// "reserved" is a shared display label for 12-14, not a spelling the firmware +// resolves, so it is excluded -- otherwise "reserved" would silently mean 14. +// Null-prototype so a token like "constructor" can't match an inherited key. +var PKT_NAMED=Object.create(null); +PKT_TYPES.forEach(function(p){if(p[1]!=="reserved")PKT_NAMED[p[1].toLowerCase()]=p[0]}); +// Mirrors MQTTPacketFilter::parse/format. The text form must round-trip the +// firmware's canonical output exactly, or an unedited slot reads as dirty. +function filterMask(text){ + var t=String(text==null?"":text).trim(); + // Empty is "all" on purpose -- it is what the firmware stores for a missing + // or cleared value, so an absent `filter` in /api/config reads as unfiltered + // rather than as a slot that uploads nothing. + if(t===""||t==="all")return 0xFFFF; + if(t==="none")return 0; + var mask=0,ok=true; + t.split(",").forEach(function(part){ + var tok=part.trim(),n=-1; + if(/^[0-9]+$/.test(tok))n=parseInt(tok,10); + else if(tok.toLowerCase() in PKT_NAMED)n=PKT_NAMED[tok.toLowerCase()]; + if(n<0||n>15){ok=false;return} + mask|=(1<'+(hint?'
'+hint+'
':'')+'
'; } +// Checkboxes rather than a text field: the accepted grammar is wider than the +// canonical form, so free text invites a value that saves as something else. +// The hidden input stays the single source of truth for the generic dirty / +// save / result-chip machinery, which is keyed on [data-k]. +function packetFilterHtml(i){ + var boxes=PKT_TYPES.map(function(p){ + return''; + }).join(""); + return'
' + +'' + +'
'+boxes+'
' + +'
' + +'' + +'
' + +'
Which payload types are uploaded to this broker. Applies to both packet and raw uploads; status and neighbors are never filtered. Clearing every box uploads nothing.
'; +} +// Reflect each hidden filter value into its checkboxes and summary. Cheap +// enough to re-run wholesale after any load or edit. +function syncPacketFilters(){ + $$('[data-k$=".filter"]').forEach(function(h){ + var i=h.dataset.k.match(/^mqtt([1-9])\./)[1]; + var card=h.closest(".slot");if(!card)return; + var mask=filterMask(h.value); + card.querySelectorAll('[data-pf="'+i+'"]').forEach(function(cb){ + cb.checked=(mask&(1<'; } host.innerHTML=html; @@ -719,7 +939,7 @@ function refreshSlotFields(host,i){ var needs=name==="custom"?"custom":(def?def.needs:"none"); var showMap={token:needs==="token",username:needs==="userpass"||needs==="custom", password:needs==="userpass"||needs==="password"||needs==="custom",server:needs==="custom", - port:needs==="custom",topic:needs==="custom",audience:needs==="custom"}; + port:needs==="custom",topic:needs==="custom",audience:needs==="custom",filter:name!=="none"}; Object.keys(showMap).forEach(function(f){ var w=card.querySelector(".sf-"+f);if(w)w.classList.toggle("hide",!showMap[f]); }); @@ -728,8 +948,21 @@ document.addEventListener("change",function(ev){ var el=ev.target,m; if(el.dataset.k&&(m=el.dataset.k.match(/^mqtt([1-9])\.preset$/))){ ["#app-slots","#wz-slots"].forEach(function(sel){refreshSlotFields($(sel),+m[1])}); + }else if(el.dataset.pf){ + var card=el.closest(".slot"); + if(card)packetFilterChanged(el.dataset.pf,card); } }); +document.addEventListener("click",function(ev){ + var b=ev.target.closest?ev.target.closest("[data-pfset]"):null; + if(!b)return; + var parts=b.dataset.pfset.split(":"),i=parts[0],all=parts[1]==="all"; + // Write through the same path a checkbox edit takes, so both the app and + // wizard copies of this slot stay in step. + var card=b.closest(".slot");if(!card)return; + card.querySelectorAll('[data-pf="'+i+'"]').forEach(function(cb){cb.checked=all}); + packetFilterChanged(i,card); +}); /* ---------- save / results ---------- */ function chipFor(el,ok,msg){ @@ -761,7 +994,19 @@ function saveChanges(extra,cb){ if(e.message==="auth"){$("#save-btn").disabled=false;if(cb)cb(null,false);return} if(e.status===400||e.status===413){ $("#save-btn").disabled=false; - if(cb)cb(null,false);else toast(e.message||"Save rejected"); + // A whole-batch rejection names the offending key; chip it so the + // operator doesn't have to guess which of N edited fields was at fault. + // Every copy, not querySelector: the wizard's slot cards precede the + // app's in document order, so one lookup always lands on the hidden one. + // Comparing the property also keeps a device-echoed key -- "bad key" + // returns whatever was sent -- out of the selector grammar, where a quote + // would throw SyntaxError and swallow the rest of this handler. + if(e.key){ + $$("[data-k]").forEach(function(el){ + if(el.dataset.k===e.key)chipFor(el,false,e.message||"rejected"); + }); + } + if(cb)cb(null,false);else toast((e.message||"Save rejected")+(e.key?": "+e.key:"")); return; } if(e.status===409){ @@ -854,7 +1099,8 @@ function loadConfigSoft(){ // re-sync origin values without clobbering result ch var idx={freq:0,bw:1,sf:2,cr:3}[el.dataset.rg]; el.value=radioParts[idx];el.classList.toggle("dirty",keepRadio); }); - if(st.hasMqtt)for(var i=1;i<=st.nslots;i++){refreshSlotFields($("#app-slots"),i);refreshSlotFields($("#wz-slots"),i)} + for(var i=1;i<=st.nslots;i++){refreshSlotFields($("#app-slots"),i);refreshSlotFields($("#wz-slots"),i)} + syncPacketFilters(); afHint(); updateSaveBar(); }).catch(function(){}); @@ -865,167 +1111,22 @@ function enterApp(){ show("#v-app"); loadConfig().catch(function(e){if(e.message!=="auth")toast("Failed to load config")}); } +// Setup-mode shortcut: straight past the guided steps into the terminal, for +// operators who already have a config to paste. Setup mode authenticates by +// proximity to the AP, so this hands the whole command surface to anyone in +// range -- the same trust the wizard already extends, since it can set the admin +// password and reflash the node's identity too. +function enterConsole(){ + enterApp(); + $('#tabs button[data-t="cli"]').click(); +} $("#tabs").addEventListener("click",function(ev){ var b=ev.target.closest("button");if(!b)return; $$("#tabs button").forEach(function(x){x.classList.toggle("on",x===b)}); ["radio","mqtt","wifi","stats","cli"].forEach(function(t){$("#t-"+t).classList.toggle("hide",t!==b.dataset.t)}); if(b.dataset.t==="stats")startStats();else stopStats(); - if(b.dataset.t==="cli")setTimeout(focusCliInput,0); -}); - -/* ---------- CLI terminal ---------- */ -var cliHistory=[],cliHistoryAt=0,cliRunning=false,cliQueue=[],cliQueueAt=0,cliBlockRun=false; -function appendCli(text){ - var out=$("#cli-output"); - out.textContent+=text; - if(out.textContent.length>12000)out.textContent=out.textContent.slice(-10000); - out.scrollTop=out.scrollHeight; -} -function isCliBlockMode(){ - var selected=$('input[name="cli-mode"]:checked'); - return selected&&selected.value==="block"; -} -function focusCliInput(){ - (isCliBlockMode()?$("#cli-block"):$("#cli-command")).focus(); -} -function updateCliMode(){ - var block=isCliBlockMode(); - $("#cli-command").classList.toggle("hide",block); - $("#cli-block").classList.toggle("hide",!block); - $("#cli-send").textContent=block?"Run block":"Send"; - focusCliInput(); -} -function setCliRunning(running){ - cliRunning=running; - $("#cli-command").disabled=running; - $("#cli-block").disabled=running; - $("#cli-send").disabled=running; - $$('input[name="cli-mode"]').forEach(function(input){input.disabled=running}); -} -function cliByteLength(text){ - if(typeof TextEncoder!=="undefined")return new TextEncoder().encode(text).length; - var bytes=encodeURIComponent(text).match(/%[0-9A-F]{2}|./g); - return bytes?bytes.length:0; -} -function parseCliBlock(text){ - var lines=text.split(/\r\n|\r|\n/),commands=[]; - for(var i=0;i159)throw new Error("line "+(i+1)+" exceeds 159 bytes"); - commands.push(lines[i]); - if(commands.length>100)throw new Error("a block can contain at most 100 commands"); - } - return commands; -} -function finishCliRun(completed){ - var wasBlock=cliBlockRun,total=cliQueue.length; - cliQueue=[]; - cliQueueAt=0; - cliBlockRun=false; - setCliRunning(false); - if(completed&&wasBlock){ - appendCli("[block complete: "+total+" command"+(total===1?"":"s")+"]\n"); - $("#cli-block").value=""; - } - focusCliInput(); - if(completed)loadConfigSoft(); -} -function stopCliRun(message){ - var remaining=cliQueue.length-cliQueueAt; - appendCli(message+"\n"); - if(cliBlockRun&&remaining>0){ - appendCli("[block stopped: "+remaining+" command"+(remaining===1?"":"s")+" not sent]\n"); - } - finishCliRun(false); -} -function pollCli(reqid,tries,command){ - api("/api/cli/result?reqid="+encodeURIComponent(reqid),{timeout:4000}).then(function(r){ - if(r.reqid!==reqid)throw new Error("request mismatch"); - if(r.state==="pending"){ - setTimeout(function(){pollCli(reqid,tries,command)},150); - return; - } - if(r.state!=="done")throw new Error("result unavailable"); - appendCli((r.reply||"(no reply)")+"\n"); - if(/^set\s+wifi\.cli\s+off\s*$/i.test(command)){ - appendCli("[terminal disabled]\n"); - var remaining=cliQueue.length-cliQueueAt; - if(cliBlockRun&&remaining>0){ - appendCli("[block stopped: "+remaining+" command"+(remaining===1?"":"s")+" not sent]\n"); - } - finishCliRun(false); - setTimeout(function(){location.reload()},700); - }else{ - runNextCliCommand(); - } - }).catch(function(e){ - if(e.message==="auth"){stopCliRun("[authentication required]");return} - if(tries<12){ - setTimeout(function(){pollCli(reqid,tries+1,command)},400); - return; - } - stopCliRun("[reply unavailable - the command may have stopped WiFi, WebConfig, or rebooted the node]"); - }); -} -function runNextCliCommand(){ - if(cliQueueAt>=cliQueue.length){ - finishCliRun(true); - return; - } - var command=cliQueue[cliQueueAt++]; - appendCli("> "+command+"\n"); - var reqid=mkReqId(); - post("/api/cli",{reqid:reqid,command:command}).then(function(){ - pollCli(reqid,0,command); - }).catch(function(e){ - stopCliRun("[error: "+e.message+"]"); - }); -} -$("#cli-form").addEventListener("submit",function(ev){ - ev.preventDefault(); - if(cliRunning)return; - var block=isCliBlockMode(),commands; - try{ - commands=block?parseCliBlock($("#cli-block").value):[$("#cli-command").value]; - if(!block&&cliByteLength(commands[0])>159)throw new Error("command exceeds 159 bytes"); - }catch(e){ - appendCli("[error: "+e.message+"]\n"); - return; - } - commands=commands.filter(function(command){return command.trim()}); - if(!commands.length)return; - commands.forEach(function(command){ - cliHistory.push(command); - if(cliHistory.length>30)cliHistory.shift(); - }); - cliHistoryAt=cliHistory.length; - if(!block)$("#cli-command").value=""; - cliQueue=commands; - cliQueueAt=0; - cliBlockRun=block; - if(block)appendCli("[running block: "+commands.length+" command"+(commands.length===1?"":"s")+"]\n"); - setCliRunning(true); - runNextCliCommand(); -}); -$$('input[name="cli-mode"]').forEach(function(input){ - input.addEventListener("change",updateCliMode); -}); -$("#cli-command").addEventListener("keydown",function(ev){ - if(ev.key!=="ArrowUp"&&ev.key!=="ArrowDown")return; - ev.preventDefault(); - if(ev.key==="ArrowUp"&&cliHistoryAt>0)cliHistoryAt--; - if(ev.key==="ArrowDown"&&cliHistoryAtfilter: ' + +esc(x.filt===0?"none (nothing uploaded)":filterText(x.filt))+'
'; var counts=(x.ok==null||x.err==null)?"":" "+x.ok+" ok / "+x.err+" err"; - return'
Slot '+x.n+" * "+esc(x.name)+''+esc(x.state)+''+counts+'
'; + return'
Slot '+x.n+" * "+esc(x.name)+''+esc(x.state)+''+counts+'
'+f; }).join(""):"No servers configured."; } }).catch(function(){}); @@ -1329,6 +1435,665 @@ function showReboot(msg,reconnect,title,ip,seconds){ },1000); } +/* ---------- CLI: command reference ---------- + Drives autocomplete only -- the node remains the authority on what it accepts. + A trailing space marks a command that takes an argument, so accepting the + completion leaves the cursor ready for the value. */ +// Commands per submitted sequence. The node reports its own limit (MAX_BATCH, +// the fixed slot the CLI shares with config saves) in /api/status; this is only +// the fallback for firmware that doesn't say. +var CLI_MAX=24; +// [command, description] +var CLI_VERBS=[ + ["ver","Firmware version"], + ["board","Board and hardware info"], + ["clock","Show the device clock (UTC)"], + // `clock sync` is absent on purpose: it takes its time from the caller's + // timestamp, which a web request has none of, so it can only ever fail here. + ["time ","Set the clock {epoch-seconds}"], + ["region","Show the configured region"], + ["memory","Heap and PSRAM free/min/largest block"], + ["neighbors","Nodes heard recently, with RSSI and age"], + ["neighbor.remove ","Drop one neighbour {64-hex-char-key}"], + // Handled by MyMesh before it delegates to CommonCLI -- a whole second + // command surface the table missed until discover.* turned up absent. + ["discover.neighbors","Ask neighbours to identify themselves"], + ["discover.scopes","Collect neighbour scopes (needs the neighbors build)"], + ["setperm ","Set a node's ACL permissions {64-hex-char-key} {int8}"], + ["advert","Send an advert now (flooded)"], + ["advert.zerohop","Send an advert neighbours will not repeat"], + ["tempradio ","Try radio params without saving {freq,bw,sf,cr}"], + ["clear stats","Reset the packet and radio counters"], + ["stats-core","Core counters (recv, sent, airtime)"], + ["stats-packets","Per-packet-type counters"], + ["stats-radio","Radio counters (RSSI, SNR, noise)"], + ["stats-radio-diag","Extended radio diagnostics"], + // Bare `log` is absent: it streams the file to the serial console and stalls + // the radio doing it, and hands back only "EOF". + ["log start","Start packet logging to the filesystem"], + ["log stop","Stop packet logging"], + ["log erase","Delete the stored packet logs"], + ["sensor list","List attached sensors"], + ["sensor get ","Read one sensor {index}"], + ["sensor set ","Write one sensor {index} {value}"], + ["gps on","Power up the GPS"], + ["gps off","Power down the GPS"], + ["gps sync","Set the clock and location from the GPS"], + ["gps setloc","Copy the current GPS fix into lat/lon"], + ["gps advert none","Do not include GPS position in adverts"], + ["gps advert share","Advertise the live GPS position"], + ["gps advert prefs","Advertise the stored lat/lon"], + ["powersaving","Show the power-saving mode"], + ["powersaving on","Enable power saving"], + ["powersaving off","Disable power saving"], + ["password ","Change the admin password {new-password}"], + ["alert test","Send a test alert on the configured channel"], + ["ota check","Check for a newer build (does not flash)"], + ["ota update","Download and flash the newer build, then reboot"], + // `start ota` is absent: it binds port 80, which this portal is already using. + ["start webconfig","Start this portal on the LAN"], + ["start webconfig ap","Start this portal on its own setup AP"], + ["stop webconfig","Stop this portal"], + ["reboot","Restart the node"], + ["clkreboot","Restart the node, preserving the clock"], + ["poweroff","Power the node off"], + ["shutdown","Power the node off (same as poweroff)"], + ["erase","Erase the filesystem -- settings and identity"] +]; +/* [key, description, mode, values] + mode: 0 = get and set, 1 = get only, 2 = set only + values: enum offered as value completions after `set ` */ +var CLI_KEYS=[ + ["name","Node name",0], + ["lat","Advert latitude",0], + ["lon","Advert longitude",0], + ["public.key","This node's public key",1], + // `get acl` is absent: it prints to the serial console and returns nothing. + ["prv.key","Restore an identity {64-hex-char-key}",2], + ["role","Node role",1], + ["radio","Radio parameters {freq,bw,sf,cr}",0], + ["freq","Frequency in MHz",0], + ["tx","TX power in dBm",0], + ["af","Airtime factor",0], + ["dutycycle","Duty cycle percent (writes airtime factor)",0], + ["cad","Listen before transmit",0,"on|off"], + ["radio.rxgain","SX126x RX boosted gain",0,"on|off"], + ["radio.fem.rxgain","Front-end module RX gain",0], + ["radio.watchdog","Restart the radio if silent this long {0-120 min}",0], + ["int.thresh","Interference threshold",0], + ["agc.reset.interval","AGC reset interval in seconds",0], + ["rxdelay","RX delay base",0], + ["txdelay","TX delay factor {0-2}",0], + ["direct.txdelay","TX delay factor for direct packets {0-2}",0], + ["repeat","Forward mesh traffic",0,"on|off"], + ["multi.acks","Extra ACKs to send",0], + ["allow.read.only","Allow read-only remote access",0,"on|off"], + ["advert.interval","Local advert interval in minutes (0 = off)",0], + ["flood.advert.interval","Flood advert interval in hours (0 = off)",0], + ["flood.max","Max flood hops {0-64}",0], + ["flood.max.advert","Max advert hops {0-64}",0], + ["flood.max.unscoped","Max hops for unscoped floods {0-64}",0], + ["loop.detect","Drop floods already carrying this node's hash",0,"off|minimal|moderate|strict"], + ["path.hash.mode","Path hash mode {0|1|2}",0], + ["owner.info","Owner info text (| becomes a newline)",0], + ["guest.password","Guest password",0], + ["adc.multiplier","Battery ADC multiplier",0], + ["wifi.ssid","WiFi network name",0], + ["wifi.pwd","WiFi password",0], + ["wifi.powersave","WiFi power-save mode",0,"none|min|max"], + ["wifi.status","WiFi connection, IP, RSSI and uptime",1], + ["mqtt.origin","Observer name in published messages",0], + ["mqtt.iata","IATA region code used in topic paths",0], + ["mqtt.status","Per-slot connection status",1], + ["mqtt.stats","Publish/queue counters",1], + ["mqtt.presets","Available broker presets",1], + ["mqtt.config.valid","Whether the MQTT config is usable",1], + ["mqtt.packets","Publish packet messages",0,"on|off"], + ["mqtt.raw","Also publish full raw frames",0,"on|off"], + ["mqtt.rx","Publish packets heard over the air",0,"on|off"], + ["mqtt.tx","Publish packets this node sends",0,"off|on|advert"], + ["mqtt.interval","Status publish interval {1-60 min}",0], + ["mqtt.neighbors","Publish the neighbour table (PSRAM boards)",0,"on|off"], + ["mqtt.neighbors.interval","Neighbour publish interval {12-336 hours}",0], + ["mqtt.owner","Owner public key {64-hex-char-key}",0], + ["mqtt.email","Owner email address",0], + ["mqtt.ntp","NTP server (none clears it)",0], + ["mqtt.ntp.diag","Last NTP sync result",1], + ["timezone","POSIX timezone string",0], + ["timezone.offset","UTC offset in hours {-12 to 14}",0], + ["snmp","SNMP agent (restart required)",0,"on|off"], + ["snmp.community","SNMP community string",0], + ["alert","Alert channel",0,"on|off"], + ["alert.psk","Alert channel pre-shared key",0], + ["alert.hashtag","Alert channel hashtag",0], + ["alert.region","Alert region filter",0], + ["alert.interval","Minimum minutes between alerts",0], + ["alert.mqtt","Send alerts to MQTT",0,"on|off"], + ["alert.wifi","Alert on WiFi problems",0,"on|off"], + ["bridge.enabled","Serial packet bridge",0,"on|off"], + ["bridge.source","Packets the bridge carries",0,"rx|tx"], + ["bridge.baud","Bridge serial baud rate",0], + ["bridge.delay","Bridge send delay",0], + ["bridge.channel","Bridge channel",0], + ["bridge.secret","Bridge shared secret",0] +]; +// Per-slot keys, expanded across the slots this board actually runs. +// [field, description, values] +var CLI_SLOT=[ + ["preset","preset",1], + ["server","custom broker hostname"], + ["port","broker port {1-65535}"], + ["username","username"], + ["password","password"], + ["token","token (required by some presets)"], + ["topic","custom topic template, e.g. {iata}/{device}/{type}"], + ["audience","JWT audience -- enables Ed25519 auth, blank clears"], + ["filter","packet types to publish: all, none, or a CSV of names/0-15"] +]; +var CLI_TYPES="req,response,txt_msg,ack,advert,grp_txt,grp_data,anon_req,path,trace,multipart,control,raw_custom".split(","); + +var cli={built:0,tbl:[],hist:[],hix:-1,draft:"",sug:[],sel:-1,busy:false,shown:false,pwd:false}; + +// Rebuilt when the slot count changes: `active_slots` decides how many +// mqttN.* keys actually exist on this board. +function cliTable(){ + if(cli.built===st.nslots)return cli.tbl; + var t=CLI_VERBS.slice(); + CLI_KEYS.forEach(function(k){ + var vals=k[3]?" {"+k[3]+"}":""; + if(k[2]!==2)t.push(["get "+k[0],k[1]]); + if(k[2]!==1)t.push(["set "+k[0]+" ",k[1]+vals]); + }); + for(var n=1;n<=st.nslots;n++){ + CLI_SLOT.forEach(function(f){ + t.push(["get mqtt"+n+"."+f[0],"Slot "+n+" "+f[1]]); + t.push(["set mqtt"+n+"."+f[0]+" ","Slot "+n+" "+f[1]]); + }); + } + cli.tbl=t;cli.built=st.nslots; + return t; +} +// Value completions for `set `. Returns null when the key has no enum, +// which is also how the command list is suppressed once a value is being typed. +function cliEnum(key){ + var m=key.match(/^mqtt[1-9]\.(\w+)$/); + if(m){ + if(m[1]==="preset")return st.presets.map(function(p){return p.name}).concat(["custom","none"]); + if(m[1]==="filter")return ["all","none"].concat(CLI_TYPES); + return null; + } + for(var i=0;i=out.scrollHeight-24; + var d=document.createElement("div"); + d.className=cls;d.textContent=text; + out.appendChild(d); + while(out.childNodes.length>400)out.removeChild(out.firstChild); + if(atEnd)out.scrollTop=out.scrollHeight; + return d; +} +function cliGap(){var d=cliEcho("gap","");return d} +function cliClear(){$("#term-out").innerHTML="";cliBanner();$("#term-in").focus()} +function cliBanner(){ + cliEcho("sys","MeshCore "+shortVer()); + cliEcho("sys",(st.role||"")+" * "+(st.board||"")+ + (/^v?\d+\.\d+\.\d+$/.test(st.fw||"")?" * local build, OTA not configured":"")); + if(st.mode==="setup"){ + // The wizard refuses to finish without an admin password; nothing stops a + // console-driven setup from rebooting on the factory one, so say so here + // rather than only warning at the point of reboot. + cliEcho("sys","Setup mode -- paste a prepared config, then finish with "+ + "\"password \" and \"reboot\". \"<- setup\" returns to the guided steps."); + } + cliEcho("sys","Type help for a summary, or a prefix and Tab to complete."); +} +function cliHelp(){ + cliGap(); + cliEcho("cmd","help"); + cliEcho("rep", + "ver / board / clock identity, firmware and time\n"+ + "get read any setting\n"+ + "set change any setting\n"+ + "advert send an advert now\n"+ + "neighbors nodes heard recently\n"+ + "memory / stats-core health counters\n"+ + "get mqtt.status per-slot broker connections\n"+ + "reboot restart the node"); + cliEcho("sys","Tab completes any prefix ("+cliTable().length+" commands known). "+ + "Paste several lines to run them in sequence. clear empties this window."); + $("#term-in").focus(); +} + +/* ---------- CLI: autocomplete ---------- */ +function cliSugOpen(){return !$("#term-sug").classList.contains("hide")} +function cliHideSug(){$("#term-sug").classList.add("hide");$("#term-sug").innerHTML="";cli.sug=[];cli.sel=-1} +function cliLoose(hay,q){ + var parts=q.split(/\s+/); + for(var i=0;i ` is complete, complete the VALUE. Keys without an enum +// return an empty list so the command rows don't reappear under a typed value. +function cliValueMatch(s){ + var m=s.match(/^set\s+(\S+)\s+(.*)$/); + if(!m)return null; + var vals=cliEnum(m[1]); + if(!vals)return{list:[],pre:0}; + var head=s.slice(0,s.length-m[2].length),typed=m[2]; + // filter lists are CSV: complete the segment after the last comma + var cut=typed.lastIndexOf(","); + if(cut>=0){head+=typed.slice(0,cut+1);typed=typed.slice(cut+1)} + var lo=typed.toLowerCase(),out=[]; + vals.forEach(function(v){ + if(v.toLowerCase().indexOf(lo)===0)out.push([head+v,cliValueNote(m[1],v)]); + }); + return{list:out,pre:head.length+typed.length}; +} +// The value is already the row's label, so the second column only earns its +// place when it says something the value doesn't. +function cliValueNote(key,val){ + if(/^mqtt[1-9]\.preset$/.test(key)){ + for(var i=0;i'+(m.pre?""+head+"":head)+tail+""+ + (e[1]?''+esc(e[1])+"":"")+""; + }); + box.innerHTML=h;box.classList.remove("hide");box.scrollTop=0; +} +function cliPaint(){ + $$("#term-sug .sg").forEach(function(el,i){ + var on=i===cli.sel;el.classList.toggle("on",on); + if(on&&el.scrollIntoView)el.scrollIntoView({block:"nearest"}); + }); +} +function cliMove(d){ + if(!cli.sug.length)return; + cli.sel=cli.sel<0?(d>0?0:cli.sug.length-1):(cli.sel+d+cli.sug.length)%cli.sug.length; + cliPaint(); +} +function cliAccept(i){ + var e=cli.sug[i];if(!e)return; + var inp=$("#term-in"); + inp.value=e[0];inp.focus(); + cli.hix=-1; + cliSug(); // re-filter: a command that takes an argument now offers values +} +function cliTabKey(){ + if(!cli.sug.length){cliSug();if(!cli.sug.length)return} + if(cli.sel>=0){cliAccept(cli.sel);return} + // shell behaviour: extend to the longest prefix every match shares before + // committing to any single one + var cur=$("#term-in").value.replace(/^\s+/,""),lcp=cli.sug[0][0]; + cli.sug.forEach(function(e){ + var n=0;while(ncur.length){$("#term-in").value=lcp;cliSug();return} + cliAccept(0); +} +$("#term-sug").addEventListener("mousedown",function(ev){ev.preventDefault()}); +$("#term-sug").addEventListener("click",function(ev){ + var r=ev.target.closest(".sg");if(!r)return; + cliAccept(+r.dataset.i); +}); + +/* ---------- CLI: history ---------- + In memory for the session only, never localStorage: `set wifi.pwd ...` and + `password ...` pass through here and must not outlive the tab. */ +function cliPush(line){ + if(cli.hist[cli.hist.length-1]!==line)cli.hist.push(line); + if(cli.hist.length>60)cli.hist.shift(); + cli.hix=-1; +} +function cliHistMove(d){ + var inp=$("#term-in"); + if(!cli.hist.length)return; + if(cli.hix<0){cli.draft=inp.value;cli.hix=cli.hist.length} + cli.hix+=d; + if(cli.hix<0)cli.hix=0; + if(cli.hix>=cli.hist.length){cli.hix=-1;inp.value=cli.draft} + else inp.value=cli.hist[cli.hix]; + cliHideSug(); + var n=inp.value.length; + try{inp.setSelectionRange(n,n)}catch(e){} +} + +/* ---------- CLI: input ---------- */ +$("#term-in").addEventListener("input",function(){cli.hix=-1;cliSug()}); +$("#term-in").addEventListener("blur",function(){setTimeout(cliHideSug,120)}); +$("#term-in").addEventListener("focus",function(){cliFit()}); +$("#term-in").addEventListener("keydown",function(ev){ + var k=ev.key; + if(k==="Tab"){ev.preventDefault();cliTabKey();return} + if(k==="ArrowUp"||k==="ArrowDown"){ + var d=k==="ArrowDown"?1:-1; + ev.preventDefault(); + // The list owns the arrows while it is open; history takes them back once + // it is dismissed, which is what Esc is for. + if(cliSugOpen())cliMove(d);else cliHistMove(d); + return; + } + if(k==="Enter"){ + ev.preventDefault(); + // Enter runs what is typed, unless a suggestion was deliberately selected + // with the arrows -- then it accepts, and a second Enter runs. + if(cliSugOpen()&&cli.sel>=0){cliAccept(cli.sel);return} + cliSubmit();return; + } + if(k==="Escape"){ + if(cliSugOpen()){cliHideSug();return} + this.value="";cli.hix=-1;return; + } + if((k==="l"||k==="L")&&ev.ctrlKey){ev.preventDefault();cliClear()} +}); +// Multi-line paste: a pasted list is a sequence to confirm, not a line to +// mangle. (A single-line paste falls through to the browser's own insert.) +$("#term-in").addEventListener("paste",function(ev){ + var cb=ev.clipboardData||window.clipboardData; + if(!cb)return; + var text=cb.getData("text")||""; + if(!/[\r\n]/.test(text))return; + ev.preventDefault(); + var cmds=cliParse(text); + // Whatever was already typed joins the first pasted line verbatim -- trimming + // it would eat the space in a half-typed "set " and silently fuse the words. + var carry=this.value; + if(carry.trim()&&cmds.length){cmds[0]=carry+cmds[0];this.value=""} + if(!cmds.length){cliEcho("sys","Nothing to run -- that paste held no commands.");return} + if(cmds.length===1){this.value=cmds[0];cliSug();return} + cliHideSug(); + cliConfirm(cmds); +}); +function cliParse(text){ + var out=[]; + text.split(/\r\n|\r|\n/).forEach(function(raw){ + var l=raw.trim(); + if(!l||l.charAt(0)==="#")return; // blanks and comments + l=l.replace(/^(?:[>$]|meshcore\s*[>$#])\s+/,""); // tolerate a pasted transcript + if(l)out.push(l); + }); + return out; +} + +/* ---------- CLI: confirmation ---------- + Rendered into the scrollback rather than as a modal: it reads as part of the + session, and on a phone it can't end up behind the keyboard. */ +// Anchored the way CommonCLI dispatches, which is mostly on a PREFIX: `reboot` +// matches the first six bytes, so `reboot now` reboots too. Matching these +// exactly (/^reboot$/) let those variants skip the confirmation entirely. +var CLI_RISK=[ + [/^erase$/,"erases the filesystem -- stored settings and this node's identity"], + [/^clkreboot/,"resets the clock and restarts the node"], + [/^reboot/,"restarts the node"], + [/^(poweroff|shutdown)/,"powers the node off"], + [/^ota update/,"downloads and flashes new firmware, then reboots"], + [/^stop webconfig$/,"stops this portal"], + [/^set wifi\.(ssid|pwd)\s/,"changes WiFi -- this page will drop"], + [/^set radio\s/,"changes radio parameters -- a wrong value takes this node off the air"], + [/^set freq\s/,"changes the frequency -- a wrong value takes this node off the air"], + [/^password\s/,"changes the admin password"], + [/^set prv\.key\s/,"replaces this node's identity"] +]; +function cliRisks(cmds){ + var seen={},out=[]; + cmds.forEach(function(c){ + CLI_RISK.forEach(function(r){ + if(r[0].test(c)&&!seen[r[1]]){seen[r[1]]=1;out.push(r[1])} + }); + }); + return out; +} +function cliConfirm(cmds){ + var over=cmds.length>CLI_MAX; + if(over)cmds=cmds.slice(0,CLI_MAX); + var risks=cliRisks(cmds),one=cmds.length===1; + var out=$("#term-out"),box=document.createElement("div"); + box.className="term-cfm"; + var h=document.createElement("div");h.className="h"; + h.textContent=one?"Run this command?":("Run these "+cmds.length+" commands in order?"); + box.appendChild(h); + var w=String(cmds.length).length; + box.style.setProperty("--n",(w+2)+"ch"); + cmds.forEach(function(c,i){ + var d=document.createElement("div");d.className="ln"; + var n=document.createElement("s"); + n.textContent=(new Array(w-String(i+1).length+1)).join(" ")+(i+1)+" "; + d.appendChild(n);d.appendChild(document.createTextNode(c)); + box.appendChild(d); + }); + if(over){ + var t=document.createElement("div");t.className="w"; + t.textContent="Only the first "+CLI_MAX+" lines are included -- the rest were dropped."; + box.appendChild(t); + } + if(risks.length){ + var r=document.createElement("div");r.className="w"; + r.textContent="WARNING: This "+risks.join("; and ")+"."; + box.appendChild(r); + } + // Onboarding by paste skips the wizard's mandatory admin-password step, and + // rebooting is what commits the node to normal operation. + if(st.mode==="setup"&&!cli.pwd&&cmds.some(function(c){return /^(reboot|clkreboot)$/.test(c)})){ + var p=document.createElement("div");p.className="w"; + // The node enforces this too (handleCliPost) -- this is the earlier, kinder + // half of the same rule, so the operator finds out before sending. + p.textContent="WARNING: Set the admin password first: run \"password \". "+ + "The node will refuse to finish setup on the factory one."; + box.appendChild(p); + } + var btns=document.createElement("div");btns.className="btns"; + var go=document.createElement("button");go.className="go"; + go.textContent=one?"Run":("Run "+cmds.length+" commands"); + var no=document.createElement("button");no.textContent="Cancel"; + btns.appendChild(go);btns.appendChild(no);box.appendChild(btns); + function settle(text){ + btns.remove(); + var s=document.createElement("div");s.className="w";s.textContent=text;box.appendChild(s); + } + go.onmousedown=no.onmousedown=function(ev){ev.preventDefault()}; + go.onclick=function(){settle(one?"Running...":"Running "+cmds.length+" commands...");cliRun(cmds)}; + no.onclick=function(){settle("Cancelled -- nothing was sent.");$("#term-in").focus()}; + out.appendChild(box);out.scrollTop=out.scrollHeight; + $("#term-in").blur(); // the sequence needs an answer before more typing +} + +/* ---------- CLI: run ---------- + Same 202 + reqid + poll contract as a config save, and for the same reason: + the commands run on the node's main loop, not inside the request. Results + stream back, so a long sequence fills the window as it executes. */ +function cliSubmit(){ + if(cli.busy)return; + var inp=$("#term-in"),line=inp.value.trim(); + cliHideSug(); + if(!line)return; + cliPush(line);inp.value=""; + if(line==="clear"||line==="cls"){cliClear();return} + if(line==="help"||line==="?"){cliHelp();return} + if(cliRisks([line]).length){cliConfirm([line]);return} + cliRun([line]); +} +function cliBusy(on){ + cli.busy=on; + $("#term-in").disabled=on; + $(".term-go").disabled=on; + if(!on&&window.matchMedia("(pointer:fine)").matches)$("#term-in").focus(); +} +function cliRun(cmds){ + cliBusy(true); + cliGap(); + var reqid=mkReqId(); + var status=cliEcho("sys",cmds.length>1?"running 0/"+cmds.length+"...":"..."); + post("/api/cli",{reqid:reqid,cmds:cmds}).then(function(){ + cliPoll(reqid,cmds,0,status,0,0); + }).catch(function(e){ + if(e.message==="auth"){cliBusy(false);status.remove();return} + // No such endpoint: this firmware predates the console (or was built + // without it). Say so instead of retrying a route that will never exist. + if(e.status===404){cliEnd(status,"This firmware has no console endpoint -- nothing was sent.");return} + if(e.status===409&&e.reqid!==reqid){cliEnd(status,"Another sequence is still running -- retry shortly.");return} + if(e.status===400||e.status===413){cliEnd(status,e.message||"Rejected by the node.");return} + // Ambiguous: the request may have landed even though the reply was lost. + // Poll for it rather than reporting a failure that did not happen. + cliPoll(reqid,cmds,0,status,0,0); + }); +} +function cliEnd(status,msg){ + cliBusy(false); + status.className="err";status.textContent=msg; + $("#term-out").scrollTop=$("#term-out").scrollHeight; +} +function cliPoll(reqid,cmds,from,status,errs,idles){ + api("/api/cli/result?reqid="+encodeURIComponent(reqid)+"&from="+from,{timeout:5000}) + .then(function(r){ + if(r.state!=="idle"&&r.reqid!==reqid){cliEnd(status,"Lost track of this sequence -- reload to check the node's state.");return} + if(r.state==="idle"){ + // the POST may still be in flight after a connection blip + if(idles<5){setTimeout(function(){cliPoll(reqid,cmds,from,status,errs,idles+1)},400);return} + cliEnd(status,"The node never received the command.");return; + } + (r.results||[]).forEach(function(x,i){ + // The node never echoes the command back -- it may hold a password or a + // token, and we already have the sequence we sent. Match by index. + cliEcho("cmd",cmds[from+i]||""); + // `ok` is advisory; the node's own convention is authoritative, and only + // failure has a fixed shape there ("Err"/"ERR:"/"Error:"). + var ok=(x.ok!=null)?x.ok:!/^\s*err/i.test(x.reply||""); + // Getters answer "> value" -- that leading marker is the serial console's + // way of setting a value apart, and here it collides with the prompt glyph + // that means "you typed this". Drop it; the colour already says "reply". + var reply=(x.reply||"").replace(/^>\s?/,""); + if(reply)cliEcho(ok?"rep":"err",reply); + }); + from+=(r.results||[]).length; + // "running" also covers "finished, but more results are still to be paged + // over", so keep polling until the node says done. + if(r.state!=="done"){ + if(cmds.length>1)status.textContent="running "+from+"/"+cmds.length+"..."; + setTimeout(function(){cliPoll(reqid,cmds,from,status,0,0)},250); + return; + } + status.remove(); + cliBusy(false); + cliAfter(cmds,r); + }).catch(function(e){ + if(e.message==="auth"){cliBusy(false);status.remove();return} + if(e.status===400||e.status===404){cliEnd(status,"The node no longer has a result for this sequence.");return} + if(errs<20){setTimeout(function(){cliPoll(reqid,cmds,from,status,errs+1,idles)},700);return} + cliEnd(status,"Lost the connection while the sequence was running -- reload to check the node's state."); + }); +} +// A CLI `set` writes the same prefs the forms edit, so re-read them or the +// other tabs keep showing stale values. +function cliAfter(cmds,r){ + var touched=false; + cmds.forEach(function(c){ + if(/^password\s/.test(c))cli.pwd=true; + if(/^(set|password)\s/.test(c))touched=true; + }); + // Same prefix dispatch as the risk list: `poweroff now` powers off too. + if(touched)loadConfigSoft(); + // A `reboot` in the sequence is not run by the CLI -- Board::reboot() never + // returns, so the node answers it and schedules the restart for after this + // read. It withholds it when any command failed, exactly as a config save + // does; say which happened rather than leaving the operator to guess. + if(r&&r.reboot_withheld){ + cliEcho("err","Not rebooting -- a setting was rejected. Fix it and run \"reboot\" again."); + return; + } + if(r&&r.reboot){ + showReboot("The node is restarting. This page will try to reconnect automatically.",true); + return; + } + // clkreboot / poweroff / erase-and-flash take the node down themselves, so + // there is no result to wait for. + cmds.forEach(function(c){ + if(/^(poweroff|shutdown)/.test(c))showReboot("The node is powering off. Reconnect power to bring it back.",false,"Powering off..."); + else if(/^clkreboot/.test(c))showReboot("The node is restarting. This page will try to reconnect automatically.",true); + }); +} + +/* ---------- CLI: fit ---------- + dvh/vh both ignore the on-screen keyboard, so on a phone the prompt can end + up underneath it. visualViewport is the only thing that reports the space + actually left, so size the terminal from that while the CLI tab is open. */ +function cliFit(){ + var vv=window.visualViewport,t=$("#term"); + if(!t||$("#t-cli").classList.contains("hide")||$("#v-app").classList.contains("hide"))return; + t.style.height=""; // back to the stylesheet's height + if(!vv)return; + var base=t.offsetHeight; + // everything still owed below the terminal -- the shortcut row, the card's + // padding and margin, main's padding -- plus the save bar when it is up + var extra=$("main").getBoundingClientRect().bottom-t.getBoundingClientRect().bottom+6; + var sb=$("#savebar");if(sb.classList.contains("show"))extra+=sb.offsetHeight; + var top=t.getBoundingClientRect().top-vv.offsetTop; + var avail=Math.round(vv.height-top-extra); + // Only ever shrink. Growing to fill the viewport would lengthen the page, + // which lets it scroll, which frees more room -- a loop that never settles. + if(avail