diff --git a/.gitignore b/.gitignore index be9312e..d5cb742 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ /build/**/* -!/build/**/*.uf2 +/external/**/* diff --git a/CMakeLists.txt b/CMakeLists.txt index af7482d..361ffa5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,29 @@ -set(PROJECT hyperx_kb_rgb) +set(PROJECT elite2_rgb) cmake_minimum_required(VERSION 3.13) set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk) set(PICO_PIO_USB_PATH /home/kenji/programming/pico/c/Pico-PIO-USB) +set(TUSB_NETWORKING_PATH ${PICO_SDK_PATH}/lib/tinyusb/lib/networking) include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake) project(${PROJECT} C CXX ASM) +set(MAKE_FS_DATA_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/external/makefsdata) +set(HTML_DIR ${CMAKE_CURRENT_LIST_DIR}/html) + +if (NOT EXISTS ${MAKE_FS_DATA_SCRIPT}) + file(DOWNLOAD + https://raw.githubusercontent.com/lwip-tcpip/lwip/e799c266facc3c70190676eccad49d6c2db2caac/src/apps/http/makefsdata/makefsdata + ${MAKE_FS_DATA_SCRIPT} + ) +endif() +message("Running makefsdata script") +execute_process(COMMAND + perl ${MAKE_FS_DATA_SCRIPT} + WORKING_DIRECTORY ${HTML_DIR} + ECHO_OUTPUT_VARIABLE + ECHO_ERROR_VARIABLE +) +file(RENAME ${HTML_DIR}/fsdata.c ${CMAKE_CURRENT_LIST_DIR}/my_fsdata.c) + pico_sdk_init() add_subdirectory(${PICO_PIO_USB_PATH} pico_pio_usb) @@ -15,6 +34,10 @@ target_sources(${PROJECT} PRIVATE main.c usb_host.c usb_device.c + usb_server.c + websocket.c + ${TUSB_NETWORKING_PATH}/dhserver.c + ${TUSB_NETWORKING_PATH}/dnserver.c ) # print memory usage, enable all warnings @@ -25,9 +48,16 @@ target_compile_options(${PROJECT} PRIVATE ) #-Wall -Wextra target_compile_definitions(${PROJECT} PRIVATE PIO_USB_USE_TINYUSB) # needed so tinyusb can find tusb_config.h -target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(${PROJECT} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${TUSB_NETWORKING_PATH} +) target_link_libraries(${PROJECT} PRIVATE + pico_lwip + pico_lwip_arch + pico_lwip_http + pico_mbedtls pico_stdlib pico_pio_usb tinyusb_board diff --git a/COPYING b/COPYING index 529e42c..91c4b4a 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ -HyperX Alloy Elite 2 Automatic Backlight - turn on/off backlight using -a light dependent resistor +HyperX Alloy Elite 2 RGB Controller - configure individual LED settings using +a webpage served over USB and adjust brightness with a LDR Copyright (C) 2025 Kenji Kozai This program is free software: you can redistribute it and/or modify diff --git a/README.md b/README.md index cbb86cb..4598a60 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# HyperX Alloy Elite 2 Automatic Backlight +# HyperX Alloy Elite 2 RGB Controller -This project automatically controls the RGB backlight of a HyperX Alloy Elite 2 -keyboard based on ADC readings from an attached light dependent resistor. The -backlight automatically turns on to white when the LDR reading is below -a programmed threshold and otherwise turns all RGB lights completely off. +This project provides individual controls of the RGB LEDs of a HyperX Alloy +Elite 2 keyboard using a webpage served over a USB connection. ADC readings +from an attached light dependent resistor can also be used to make the +lighting adaptive to the environment. -![Keyboard RGB turning on and off with ambient lighting](lightonoff.gif) +![Web interface for setting individual LED RGB values](ui.png) ## Setup @@ -49,28 +49,34 @@ hole for the LDR to detect ambient light. ## Software -Flash the [hyperx_kb_rgb.uf2](hyperx_kb_rgb.uf2) file to the Raspberry Pi Pico, -and connect the keyboard to the female USB port and insert the male USB -connector of the device into your host device such as PC. +Flash the elite2_rgb.uf2 file from the latest +[release](https://git.kkozai.com/kenji/alloy_elite2_rgb/releases) to the +Raspberry Pi Pico, and connect the keyboard to the female USB port and insert +the male USB connector of the device into your host device such as PC. -If you wish to change the ADC reading threshold for changing the lighting, -you can do so by changing the values of `LDR_OFF_THRESHOLD` and -`LDR_ON_THRESHOLD` in [hyperx_elite2.h](hyperx_elite2.h). +To load the UI for configuring the RGB lighting, open a browser to the page at +http://alloyelite2.usb, or if that doesn't load, to http://192.168.226.1 +(226 is E2 for "Elite 2" in hexademical). From the webpage, you can click on +any individual key that you want to configure and change the color using the +color selector or by manually inputting the RGB color value into the text +boxes. To finalize setting the color for the selected key(s), click on the +"Set Color" button. -You can change the color used in the two different modes inside the `rgb_task` -function of [hyperx_elite2.c](hyperx_elite2.c). +To save the lighting configuration to the Pico 2 so that it loads the next time +it is powered on, click on the "Save" button under the "Flash Memory" section. +If you make unsaved changes and want to reload the configuration from memory, +you can also click the "Load" button to reset to the last saved setting. -If you used a different ADC pin than ADC2, you can change the selected ADC pin -and ADC channel by changing `LDR_PIN` and `LDR_ADC` -[hyperx_elite2.h](hyperx_elite2.h). +If the checkbox next to "Adaptive" is selected when setting the color, the key's +brightness will automatically adjust with the ambient lighting, as determined by +the ADC reading of the LDR. Leaving it unchecked will set the RGB color to be +constant regardless of the ambient light level. -For any of the changes above, you will need to modify the -[CMakeLists.txt](CMakeLists.txt) file and compile the program yourself. You -will need to change `PICO_SDK_PATH` and `PICO_PIO_USB_PATH` to the -directories where you have the -[Raspberry Pi Pico SDK](https://github.com/raspberrypi/pico-sdk) and -[Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library -downloaded on your machine. +The mute button has special behavior when set to adaptive mode. It will toggle +between the user-configured color and red each time that it is pressed as a +way to indicate whether system sounds are muted or not. The keyboard will +always boot in the "unmuted" setting, so if your system starts off muted, +the LED color on the keyboard and your system may not match. ## Licensing @@ -86,5 +92,6 @@ The project uses code from the following sources: - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB/) for templates used from the [host_hid_to_device_cdc](https://github.com/sekigon-gonnoc/Pico-PIO-USB/tree/control-keyboard-led/examples/host_hid_to_device_cdc) example released under the MIT license -- [TinyUSB](https://github.com/hathach/tinyusb) for templates used in - [tusb_config.h](tusb_config.h) distributed under the MIT license +- [TinyUSB](https://github.com/hathach/tinyusb) for templates used from the + [net_lwip_webserver](https://github.com/hathach/tinyusb/tree/master/examples/device/net_lwip_webserver) + example distributed under the MIT license diff --git a/build/hyperx_kb_rgb.uf2 b/build/hyperx_kb_rgb.uf2 deleted file mode 100644 index ef9e00f..0000000 Binary files a/build/hyperx_kb_rgb.uf2 and /dev/null differ diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..ed0a258 --- /dev/null +++ b/html/index.html @@ -0,0 +1,413 @@ + + + + + +HyperX Alloy Elite 2 RGB Configuration + + + + + + + + +
+
+ +
+ + + + diff --git a/hyperx_elite2.c b/hyperx_elite2.c index f0cc008..aef2e61 100644 --- a/hyperx_elite2.c +++ b/hyperx_elite2.c @@ -1,39 +1,171 @@ #include #include "pico/stdlib.h" -#include "tusb.h" +#include "pico/multicore.h" #include "hardware/adc.h" +#include "hardware/flash.h" +#include "tusb.h" + +#include "usb_device.h" +#include "websocket.h" #include "hyperx_elite2.h" static absolute_time_t lastSend; static absolute_time_t lastRead; -static bool backlight = false; static uint16_t adc_value = 0; static bool mute = false; -static const unsigned int SKIP_INDICES[] = { 23, 29, 41, 47, 70, 71, 76, 87, 88, 93, 99, 100, 102, 108, 113 }; - static unsigned char buf[BUF_SIZE]; static uint8_t buf_idx=0; +static uint8_t key_idx=0; +static uint8_t color_idx=0; static uint8_t packets_sent=0; -static uint8_t color_idx = 0; -static uint8_t skipped = 0; -const unsigned int* skip_idx = &SKIP_INDICES[0]; +static int64_t delay=0; +static uint8_t ws_buf[12]; +static uint16_t ws_len; -static void send_color(uint8_t dev_addr, uint8_t red, uint8_t green, uint8_t blue); +static void send_color(uint8_t dev_addr); static void send_initial(uint8_t dev_addr); +static struct key * find_key(char * name); +static void set_color(char * name, uint8_t red, uint8_t green, uint8_t blue, uint8_t mode); +static uint8_t hexint (const char c); +static uint8_t hexbyte (const char * s); + +static struct key key_list[NUM_KEYS] = +{ + INIT_KEY("Escape", KEY_ESC), + INIT_KEY("Backquote", KEY_GRAVE), + INIT_KEY("Tab", KEY_TAB), + INIT_KEY("CapsLock", KEY_CAPSLOCK), + INIT_KEY("ShiftLeft", KEY_LEFTSHIFT), + INIT_KEY("ControlLeft", KEY_LEFTCTRL), + INIT_KEY("Digit1", KEY_1), + INIT_KEY("KeyQ", KEY_Q), + INIT_KEY("KeyA", KEY_A), + INIT_KEY("KeyZ", KEY_Z), + INIT_KEY("MetaLeft", KEY_LEFTMETA), + INIT_KEY("F1", KEY_F1), + INIT_KEY("Digit2", KEY_2), + INIT_KEY("KeyW", KEY_W), + INIT_KEY("KeyS", KEY_S), + INIT_KEY("KeyX", KEY_X), + INIT_KEY("AltLeft", KEY_LEFTALT), + INIT_KEY("F2", KEY_F2), + INIT_KEY("Digit3", KEY_3), + INIT_KEY("KeyE", KEY_E), + INIT_KEY("KeyD", KEY_D), + INIT_KEY("KeyC", KEY_C), + INIT_KEY("F3", KEY_F3), + INIT_KEY("Digit4", KEY_4), + INIT_KEY("KeyR", KEY_R), + INIT_KEY("KeyF", KEY_F), + INIT_KEY("KeyV", KEY_V), + INIT_KEY("F4", KEY_F4), + INIT_KEY("Digit5", KEY_5), + INIT_KEY("KeyT", KEY_T), + INIT_KEY("KeyG", KEY_G), + INIT_KEY("KeyB", KEY_B), + INIT_KEY("Space", KEY_SPACE), + INIT_KEY("F5", KEY_F5), + INIT_KEY("Digit6", KEY_6), + INIT_KEY("KeyY", KEY_Y), + INIT_KEY("KeyH", KEY_H), + INIT_KEY("KeyN", KEY_N), + INIT_KEY("F6", KEY_F6), + INIT_KEY("Digit7", KEY_7), + INIT_KEY("KeyU", KEY_U), + INIT_KEY("KeyJ", KEY_J), + INIT_KEY("KeyM", KEY_M), + INIT_KEY("F7", KEY_F7), + INIT_KEY("Digit8", KEY_8), + INIT_KEY("KeyI", KEY_I), + INIT_KEY("KeyK", KEY_K), + INIT_KEY("Comma", KEY_COMMA), + INIT_KEY("AltRight", KEY_RIGHTALT), + INIT_KEY("F8", KEY_F8), + INIT_KEY("Digit9", KEY_9), + INIT_KEY("KeyO", KEY_O), + INIT_KEY("KeyL", KEY_L), + INIT_KEY("Period", KEY_DOT), + INIT_KEY("F9", KEY_F9), + INIT_KEY("Digit0", KEY_0), + INIT_KEY("KeyP", KEY_P), + INIT_KEY("Semicolon", KEY_SEMICOLON), + INIT_KEY("Slash", KEY_SLASH), + INIT_KEY("MetaRight", KEY_RIGHTMETA), + INIT_KEY("F10", KEY_F10), + INIT_KEY("Minus", KEY_MINUS), + INIT_KEY("BracketLeft", KEY_LEFTBRACE), + INIT_KEY("Quote", KEY_APOSTROPHE), + INIT_KEY("F11", KEY_F11), + INIT_KEY("Equal", KEY_EQUAL), + INIT_KEY("BracketRight", KEY_RIGHTBRACE), + INIT_KEY("ContextMenu", KEY_MENU), + INIT_KEY("F12", KEY_F12), + INIT_KEY("Backspace", KEY_BACKSPACE), + INIT_KEY("Backslash", KEY_BACKSLASH), + INIT_KEY("Enter", KEY_ENTER), + INIT_KEY("ShiftRight", KEY_RIGHTSHIFT), + INIT_KEY("ControlRight", KEY_RIGHTCTRL), + INIT_KEY("PrintScreen", KEY_SYSRQ), + INIT_KEY("Insert", KEY_INSERT), + INIT_KEY("Delete", KEY_DELETE), + INIT_KEY("ArrowLeft", KEY_LEFT), + INIT_KEY("ScrollLock", KEY_SCROLLLOCK), + INIT_KEY("Home", KEY_HOME), + INIT_KEY("End", KEY_END), + INIT_KEY("ArrowUp", KEY_UP), + INIT_KEY("ArrowDown", KEY_DOWN), + INIT_KEY("Pause", KEY_PAUSE), + INIT_KEY("PageUp", KEY_PAGEUP), + INIT_KEY("PageDown", KEY_PAGEDOWN), + INIT_KEY("ArrowRight", KEY_RIGHT), + INIT_KEY("NumLock", KEY_NUMLOCK), + INIT_KEY("Numpad7", KEY_KP7), + INIT_KEY("Numpad4", KEY_KP4), + INIT_KEY("Numpad1", KEY_KP1), + INIT_KEY("Numpad0", KEY_KP0), + INIT_KEY("NumpadDivide", KEY_KPSLASH), + INIT_KEY("Numpad8", KEY_KP8), + INIT_KEY("Numpad5", KEY_KP5), + INIT_KEY("Numpad2", KEY_KP2), + INIT_KEY_MUTE("Mute", KEY_MUTE), + INIT_KEY("NumpadMultiply", KEY_KPASTERISK), + INIT_KEY("Numpad9", KEY_KP9), + INIT_KEY("Numpad6", KEY_KP6), + INIT_KEY("Numpad3", KEY_KP3), + INIT_KEY("NumpadDecimal", KEY_KPDOT), + INIT_KEY("MediaTrackPrevious", KEY_MEDIA_PREVIOUSSONG), + INIT_KEY("NumpadSubtract", KEY_KPMINUS), + INIT_KEY("NumpadAdd", KEY_KPPLUS), + INIT_KEY("MediaPlayPause", KEY_MEDIA_PLAYPAUSE), + INIT_KEY("MediaTrackNext", KEY_MEDIA_NEXTSONG), + INIT_KEY("NumpadEnter", KEY_KPENTER), + INIT_KEY("Bar1", LED_BAR1), + INIT_KEY("Bar2", LED_BAR2), + INIT_KEY("Bar3", LED_BAR3), + INIT_KEY("Bar4", LED_BAR4), + INIT_KEY("Bar5", LED_BAR5), + INIT_KEY("Bar6", LED_BAR6), + INIT_KEY("Bar7", LED_BAR7), + INIT_KEY("Bar8", LED_BAR8), + INIT_KEY("Bar9", LED_BAR9), + INIT_KEY("Bar10", LED_BAR10), + INIT_KEY("Bar11", LED_BAR11), + INIT_KEY("Bar12", LED_BAR12), + INIT_KEY("Bar13", LED_BAR13), + INIT_KEY("Bar14", LED_BAR14), + INIT_KEY("Bar15", LED_BAR15), + INIT_KEY("Bar16", LED_BAR16), + INIT_KEY("Bar17", LED_BAR17), + INIT_KEY("Bar18", LED_BAR18) +}; void get_light() { // get ADC reading from LDR every 500ms - // if above threshold, set backlight to off if ( absolute_time_diff_us(lastRead, get_absolute_time()) >= 500000) { adc_value = adc_read(); - if (backlight && adc_value >= LDR_OFF_THRESHOLD) { - backlight = false; - } else if (!backlight && adc_value <= LDR_ON_THRESHOLD) { - backlight = true; - } } } @@ -44,7 +176,8 @@ void rgb_task(uint8_t dev_addr) { // updated color info (packets_sent>0) and continue to send the next // packet if so at a rate of one packet every 20ms // otherwise, wait 0.5s before sending the next set of color packets - if (packets_sent>0) { + + /*if (packets_sent>0) { if ( absolute_time_diff_us(lastSend, get_absolute_time()) >= 20000) { if (backlight) { send_color(dev_addr, 0x20, 0x20, 0x20); @@ -60,11 +193,21 @@ void rgb_task(uint8_t dev_addr) { send_initial(dev_addr); lastSend = get_absolute_time(); } + }*/ + if ( absolute_time_diff_us(lastSend, get_absolute_time()) >= delay) { + if ( packets_sent == 0) { + // first packet is initialization packet + send_initial(dev_addr); + } else { + // remaining packets are color packets + send_color(dev_addr); + } + lastSend = get_absolute_time(); } } // send an individual color packett with the desired RGB color -static void send_color(uint8_t dev_addr, uint8_t red, uint8_t green, uint8_t blue) { +static void send_color(uint8_t dev_addr) { memset(buf, 0x00, BUF_SIZE); buf_idx = 0; @@ -72,75 +215,58 @@ static void send_color(uint8_t dev_addr, uint8_t red, uint8_t green, uint8_t blu // check if there are still keys left to send and send updated colors // for those keys // each key gets 4 bytes - an init byte (0x81) plus 3 bytes for RGB - if(color_idx < NUM_KEYS) - { - while (color_idx < NUM_KEYS && buf_idx < BUF_SIZE) { - if (*skip_idx == color_idx + skipped) { - // keys in skip_idx are not assigned to a key, so send all 0x00 - buf[buf_idx] = 0x00; - buf[buf_idx + 1] = 0x00; - buf[buf_idx + 2] = 0x00; - buf[buf_idx + 3] = 0x00; - - skip_idx++; - - if(skip_idx >= SKIP_INDICES + sizeof(SKIP_INDICES) / sizeof(unsigned int)) - { - skip_idx = SKIP_INDICES; - } - skipped++; - } else { - // start by sending color init byte for the current key - buf[buf_idx] = 0x81; - // turn rewind, play, and fast forward keys to white - if(color_idx==105 || color_idx==108 || color_idx==109) { - buf[buf_idx + 1] = 0x20; - buf[buf_idx + 2] = 0x20; - buf[buf_idx + 3] = 0x20; - } else if(color_idx==99) { - // set color of mute button to green or red based on mute - // toggle - note that this toggle is not synced to the PC's - // audio state and is presumed to be unmuted when the keyboard - // first receives power - if(mute) { - buf[buf_idx + 1] = 0x40; - buf[buf_idx + 2] = 0x00; - buf[buf_idx + 3] = 0x00; + while (key_idx < NUM_KEYS && buf_idx < BUF_SIZE) { + if (key_list[key_idx].val != color_idx) { + // some color_idx are not assigned to a key, so send all 0x00 + buf[buf_idx] = 0x00; + buf[buf_idx + 1] = 0x00; + buf[buf_idx + 2] = 0x00; + buf[buf_idx + 3] = 0x00; + } else { + // start by sending color init byte for the current key + buf[buf_idx] = 0x81; + switch (key_list[key_idx].mode) { + case RGB_MODE_ADAPTIVE: // adjust brightness based on LDR ADC reading + buf[buf_idx+1] = (ADC_MAX-adc_value)*key_list[key_idx].red/ADC_MAX; + buf[buf_idx+2] = (ADC_MAX-adc_value)*key_list[key_idx].green/ADC_MAX; + buf[buf_idx+3] = (ADC_MAX-adc_value)*key_list[key_idx].blue/ADC_MAX; + break; + case RGB_MODE_MUTE: + if (mute) { + buf[buf_idx+1] = 0xFF; // red + buf[buf_idx+2] = 0x00; + buf[buf_idx+3] = 0x00; } else { - buf[buf_idx + 1] = 0x00; - buf[buf_idx + 2] = 0x40; - buf[buf_idx + 3] = 0x00; + buf[buf_idx+1] = key_list[key_idx].red; + buf[buf_idx+2] = key_list[key_idx].green; + buf[buf_idx+3] = key_list[key_idx].blue; } - } else { - // for a normal key, send the desired RGB colors - buf[buf_idx + 1] = red; - buf[buf_idx + 2] = green; - buf[buf_idx + 3] = blue; - } - color_idx++; + break; + default: + buf[buf_idx+1] = key_list[key_idx].red; + buf[buf_idx+2] = key_list[key_idx].green; + buf[buf_idx+3] = key_list[key_idx].blue; + break; } - buf_idx += 4; + key_idx++; } + color_idx++; + buf_idx += 4; + } - if(tuh_hid_set_report(dev_addr, 0, 0, HID_REPORT_TYPE_FEATURE, buf, BUF_SIZE)) - { - // packet sent successfully, increment - packets_sent++; - } - } else { - if(packets_sent < NUM_PACKETS) - { - // all keys have been sent, but the protocol expects NUM_PACKETS - // packets to be sent in total; if we have not sent enough packets, - // send extra packets of all 0x00 until done - if(tuh_hid_set_report(dev_addr, 0, 0, HID_REPORT_TYPE_FEATURE, buf, BUF_SIZE)) - { - packets_sent++; - } - } else { - // a full round of color packets completed, reset packets to 0 - packets_sent=0; - } + if(tuh_hid_set_report(dev_addr, RGB_ITF, RGB_REPORT_ID, HID_REPORT_TYPE_FEATURE, buf, BUF_SIZE)) + { + // packet sent successfully, increment + packets_sent++; + delay = 20000; + } + + if (key_idx >= NUM_KEYS) { + // all keys have been sent, reset and setup next cycle + packets_sent = 0; + delay = 500000; + key_idx = 0; + color_idx = 0; } } @@ -149,19 +275,96 @@ static void send_color(uint8_t dev_addr, uint8_t red, uint8_t green, uint8_t blu static void send_initial(uint8_t dev_addr) { memset(buf, 0x00, BUF_SIZE); - color_idx = 0; - skipped = 0; - skip_idx = &SKIP_INDICES[0]; - // send initialization packet buf[0x00] = 0x04; buf[0x01] = 0xf2; - if (tuh_hid_set_report(dev_addr, 0, 0, HID_REPORT_TYPE_FEATURE, buf, BUF_SIZE)) + if (tuh_hid_set_report(dev_addr, RGB_ITF, RGB_REPORT_ID, HID_REPORT_TYPE_FEATURE, buf, BUF_SIZE)) { // we have begun sending packets, so indicate first packet was sent // remaining packets will be sent at regular intervals packets_sent=1; + delay = 20000; + } +} + +// find key by name +static struct key * find_key(char * name) { + for (uint8_t i=0; ired = red; + set_key->green = green; + set_key->blue = blue; + if ( set_key->val == KEY_MUTE && mode == RGB_MODE_ADAPTIVE ) { + mode = RGB_MODE_MUTE; + } + set_key->mode = mode; + cdc_count = sprintf(cdc_buf, "key: %02X color: (%u,%u,%u) mode: %u\n", set_key->val, set_key->red, set_key->green, set_key->blue, set_key->mode); + tud_cdc_write(cdc_buf, cdc_count); + } +} + +// parse color request from webpage and update keyboard colors +void parse_colors(char * data, uint16_t len) { + (void) len; + + // Javascript sends the list of keys in a comma delimited format with the + // first entry being the color code, so split at commas + + char * token = strtok(data, ","); + if (token != NULL) { + // first string is the RGB color code + uint8_t red, green, blue; + // sscanf(token, "%2x%2x%2x", &red, &green, &blue); + // sscanf causes Pico 1 to crash + red = hexbyte(token); + green = hexbyte(token+2); + blue = hexbyte(token+4); + + token = strtok(NULL, ","); + if (token != NULL) { + // second string is mode flag + uint8_t mode = RGB_MODE_SOLID; + if ( strcmp(token, "1") == 0 ) { + mode = RGB_MODE_ADAPTIVE; + } + + // extract the keys from the rest of the string and set to the preceding color + token = strtok(NULL, ","); + while (token != NULL) { + set_color(token, red, green, blue, mode); + token = strtok(NULL, ","); + } + } + } +} + +// parse send color request and return color of first selected key +void get_color(char * data, uint16_t len) { + (void) len; + + // split at commas and get just the first key + char * token = strtok(data, ","); + if (token != NULL){ + struct key * getkey; + getkey = find_key(token); + if (getkey != NULL) { + ws_len=sprintf(ws_buf, "#%02x%02X%02X,%u", getkey->red, getkey->green, getkey->blue, getkey->mode); + ws_send_all(ws_buf, ws_len); + } } } @@ -181,3 +384,74 @@ bool forward_report(uint8_t instance, uint8_t const* report, uint16_t len) { return tud_hid_n_report(instance, 0, report, len); } + +// save RGB configuration to flash +void save_rgb_config(void) { + // set save signature and number of bytes to be written into config + uint16_t signature = CFG_SIGNATURE; + uint16_t key_list_size=sizeof(key_list); + uint8_t pages = (key_list_size+sizeof(signature)+sizeof(key_list_size) + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE; + + uint8_t flash_buf[pages*FLASH_PAGE_SIZE]; + memset(flash_buf, 0x00, pages*FLASH_PAGE_SIZE); + + // stage data for copying to flash + memcpy(flash_buf, &signature, sizeof(signature)); + memcpy(flash_buf+sizeof(signature), &key_list_size, sizeof(key_list_size)); + memcpy(flash_buf+sizeof(signature)+sizeof(key_list_size), key_list, key_list_size); + + // turn off web server and USB interrupts to allow flash writes + uint32_t interrupts = save_and_disable_interrupts(); + + // host core must have its TinyUSB interrupts disabled to allow flash writes + multicore_lockout_start_blocking(); + + // erase sector where save will go in flash + flash_range_erase(FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE); + + // write config to flash + flash_range_program(FLASH_TARGET_OFFSET, flash_buf, pages*FLASH_PAGE_SIZE); + + // restore interrupts on both core 1 and core 0 + multicore_lockout_end_blocking(); + restore_interrupts(interrupts); + + cdc_count = sprintf(cdc_buf, "Configuration saved to flash (%u:%u)\n", key_list_size, pages); + tud_cdc_write(cdc_buf, cdc_count); +} + +// load RGB configuration from flash - return true if valid config, false otherwise +bool load_rgb_config(void) { + uint16_t signature; + uint16_t key_list_size; + const uint8_t *data = (const uint8_t *) (XIP_BASE+FLASH_TARGET_OFFSET); + memcpy(&signature, data, sizeof(signature)); + memcpy(&key_list_size, data+sizeof(signature), sizeof(key_list_size)); + if (signature == CFG_SIGNATURE && key_list_size == sizeof(key_list) ) { + memcpy(&key_list, data+sizeof(signature)+sizeof(key_list_size), key_list_size); + cdc_count = sprintf(cdc_buf, "Configuration loaded from flash %u (%04x)\n", key_list_size, signature); + tud_cdc_write(cdc_buf, cdc_count); + return true; + } + + tud_cdc_write_str("Configuration failed to load\n"); + return false; +} + +// simple helper to convert hex char to uint8_t value +static uint8_t hexint (const char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } else if ( c >= 'a' && c <= 'f') { + return c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + return c- 'A' + 10; + } else { + return 0; + } +} + +// simple helper to convert 2 hex characters into a byte (uint8_t) +static uint8_t hexbyte (const char * s) { + return (hexint(s[0]) << 4) | (hexint(s[1])); +} diff --git a/hyperx_elite2.h b/hyperx_elite2.h index 755550b..b301b5d 100644 --- a/hyperx_elite2.h +++ b/hyperx_elite2.h @@ -3,18 +3,190 @@ #define LDR_PIN 28 #define LDR_ADC 2 -#define LDR_OFF_THRESHOLD 500 -#define LDR_ON_THRESHOLD 400 #define HYPERX_KEYBOARD_VID 0x0951 #define HYPERX_ELITE2_PID 0x1711 -#define NUM_KEYS 128 +#define RGB_ITF 0 +#define RGB_REPORT_ID 0 +#define NUM_KEYS 126 #define BUF_SIZE 64 -#define NUM_PACKETS 10 +#define ADC_MAX 4096 + +enum { + RGG_MODE_INVALID=0, + RGB_MODE_SOLID, + RGB_MODE_ADAPTIVE, + RGB_MODE_MUTE, +}; + +struct key { + unsigned char name[19]; + uint8_t val; + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t mode; +}; void get_light(); void rgb_task(uint8_t dev_addr); void startADC(); bool forward_report(uint8_t instance, uint8_t const* report, uint16_t len); +void parse_colors(char * data, uint16_t len); +void get_color(char * data, uint16_t len); +void save_rgb_config(void); +bool load_rgb_config(void); + +#define KEY_ESC 0 +#define KEY_GRAVE 1 +#define KEY_TAB 2 +#define KEY_CAPSLOCK 3 +#define KEY_LEFTSHIFT 4 +#define KEY_LEFTCTRL 5 +// Skip index 6 (backslash?) +#define KEY_1 7 +#define KEY_Q 8 +#define KEY_A 9 +#define KEY_Z 10 +#define KEY_LEFTMETA 11 +#define KEY_F1 12 +#define KEY_2 13 +#define KEY_W 14 +#define KEY_S 15 +#define KEY_X 16 +#define KEY_LEFTALT 17 +#define KEY_F2 18 +#define KEY_3 19 +#define KEY_E 20 +#define KEY_D 21 +#define KEY_C 22 +// Skip index 23 +#define KEY_F3 24 +#define KEY_4 25 +#define KEY_R 26 +#define KEY_F 27 +#define KEY_V 28 +// Skip index 29 +#define KEY_F4 30 +#define KEY_5 31 +#define KEY_T 32 +#define KEY_G 33 +#define KEY_B 34 +#define KEY_SPACE 35 +#define KEY_F5 36 +#define KEY_6 37 +#define KEY_Y 38 +#define KEY_H 39 +#define KEY_N 40 +// Skip index 41 +#define KEY_F6 42 +#define KEY_7 43 +#define KEY_U 44 +#define KEY_J 45 +#define KEY_M 46 +// Skip index 47 +#define KEY_F7 48 +#define KEY_8 49 +#define KEY_I 50 +#define KEY_K 51 +#define KEY_COMMA 52 +#define KEY_RIGHTALT 53 +#define KEY_F8 54 +#define KEY_9 55 +#define KEY_O 56 +#define KEY_L 57 +#define KEY_DOT 58 +// Skip index 59 +#define KEY_F9 60 +#define KEY_0 61 +#define KEY_P 62 +#define KEY_SEMICOLON 63 +#define KEY_SLASH 64 +#define KEY_RIGHTMETA 65 +#define KEY_F10 66 +#define KEY_MINUS 67 +#define KEY_LEFTBRACE 68 +#define KEY_APOSTROPHE 69 +// Skip index 70 +// Skip index 71 +#define KEY_F11 72 +#define KEY_EQUAL 73 +#define KEY_RIGHTBRACE 74 +// Skip index 75 (maybe pound?) +// Skip index 76 +#define KEY_MENU 77 +#define KEY_F12 78 +#define KEY_BACKSPACE 79 +#define KEY_BACKSLASH 80 +#define KEY_ENTER 81 +#define KEY_RIGHTSHIFT 82 +#define KEY_RIGHTCTRL 83 +#define KEY_SYSRQ 84 +#define KEY_INSERT 85 +#define KEY_DELETE 86 +// Skip index 87 +// Skip index 88 +#define KEY_LEFT 89 +#define KEY_SCROLLLOCK 90 +#define KEY_HOME 91 +#define KEY_END 92 +// Skip index 93 +#define KEY_UP 94 +#define KEY_DOWN 95 +#define KEY_PAUSE 96 +#define KEY_PAGEUP 97 +#define KEY_PAGEDOWN 98 +// Skip index 99 +// Skip index 100 +#define KEY_RIGHT 101 +// Skip index 102 +#define KEY_NUMLOCK 103 +#define KEY_KP7 104 +#define KEY_KP4 105 +#define KEY_KP1 106 +#define KEY_KP0 107 +// Skip index 108 +#define KEY_KPSLASH 109 +#define KEY_KP8 110 +#define KEY_KP5 111 +#define KEY_KP2 112 +// Skip index 113 +#define KEY_MUTE 114 +#define KEY_KPASTERISK 115 +#define KEY_KP9 116 +#define KEY_KP6 117 +#define KEY_KP3 118 +#define KEY_KPDOT 119 +#define KEY_MEDIA_PREVIOUSSONG 120 +#define KEY_KPMINUS 121 +#define KEY_KPPLUS 122 +#define KEY_MEDIA_PLAYPAUSE 123 +#define KEY_MEDIA_NEXTSONG 124 +#define KEY_KPENTER 125 +#define LED_BAR1 126 +#define LED_BAR2 127 +#define LED_BAR3 128 +#define LED_BAR4 129 +#define LED_BAR5 130 +#define LED_BAR6 131 +#define LED_BAR7 132 +#define LED_BAR8 133 +#define LED_BAR9 134 +#define LED_BAR10 135 +#define LED_BAR11 136 +#define LED_BAR12 137 +#define LED_BAR13 138 +#define LED_BAR14 139 +#define LED_BAR15 140 +#define LED_BAR16 141 +#define LED_BAR17 142 +#define LED_BAR18 143 + +#define INIT_KEY(name, code) {name, code, 0x80, 0x80, 0x80, RGB_MODE_ADAPTIVE} +#define INIT_KEY_MUTE(name, code) {name, code, 0x80, 0x80, 0x80, RGB_MODE_MUTE} + +#define CFG_SIGNATURE 0x9e4c + +#define FLASH_TARGET_OFFSET (PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE) #endif diff --git a/lightonoff.gif b/lightonoff.gif deleted file mode 100644 index 692b178..0000000 Binary files a/lightonoff.gif and /dev/null differ diff --git a/lwipopts.h b/lwipopts.h new file mode 100644 index 0000000..b068d05 --- /dev/null +++ b/lwipopts.h @@ -0,0 +1,68 @@ +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +#define NO_SYS 1 +#define MEM_ALIGNMENT 4 +#define LWIP_RAW 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define LWIP_DHCP 0 +#define LWIP_ICMP 1 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define LWIP_IPV4 1 +#define LWIP_IPV6 0 +#define ETH_PAD_SIZE 0 +#define LWIP_IP_ACCEPT_UDP_PORT(p) ((p) == PP_NTOHS(67)) + +#define TCP_MSS (1500 /*mtu*/ - 20 /*iphdr*/ - 20 /*tcphhr*/) +#define TCP_SND_BUF (4 * TCP_MSS) +#define TCP_WND (4 * TCP_MSS) + +#define ETHARP_SUPPORT_STATIC_ENTRIES 1 + +#define LWIP_SINGLE_NETIF 1 +#define LWIP_NETIF_LINK_CALLBACK 1 + +#define PBUF_POOL_SIZE 4 + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + + +// HTTPD stuff +#define LWIP_HTTPD 1 +#define LWIP_HTTPD_CGI 0 +#define LWIP_HTTPD_SSI 0 +#define LWIP_HTTPD_SSI_INCLUDE_TAG 0 +#define LWIP_HTTPD_SUPPORT_POST 0 +#define HTTPD_FSDATA_FILE "my_fsdata.c" + + +#endif /* __LWIPOPTS_H__ */ diff --git a/mbedtls_config.h b/mbedtls_config.h new file mode 100644 index 0000000..0e766d6 --- /dev/null +++ b/mbedtls_config.h @@ -0,0 +1,75 @@ +#ifndef MBEDTLS_CONFIG_H +#define MBEDTLS_CONFIG_H + +/* Workaround for some mbedtls source files using INT_MAX without including limits.h */ +#include + +#define MBEDTLS_NO_PLATFORM_ENTROPY +#define MBEDTLS_ENTROPY_HARDWARE_ALT + +#define MBEDTLS_SSL_OUT_CONTENT_LEN 2048 + +#define MBEDTLS_ALLOW_PRIVATE_ACCESS +#define MBEDTLS_HAVE_TIME +#define MBEDTLS_PLATFORM_MS_TIME_ALT + +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_SHA256_SMALLER +#define MBEDTLS_SSL_SERVER_NAME_INDICATION +#define MBEDTLS_AES_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C +#define MBEDTLS_MD_C +#define MBEDTLS_MD5_C +#define MBEDTLS_OID_C +#define MBEDTLS_PKCS5_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA1_C +#define MBEDTLS_SHA224_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA512_C +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_SRV_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_USE_C +#define MBEDTLS_AES_FEWER_TABLES + +/* TLS 1.2 */ +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED +#define MBEDTLS_GCM_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECP_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ASN1_WRITE_C + +// The following is needed to parse a certificate +#define MBEDTLS_PEM_PARSE_C +#define MBEDTLS_BASE64_C + +// The following significantly speeds up mbedtls due to NIST optimizations. +#define MBEDTLS_ECP_NIST_OPTIM + +#endif diff --git a/my_fsdata.c b/my_fsdata.c new file mode 100644 index 0000000..b43e45a --- /dev/null +++ b/my_fsdata.c @@ -0,0 +1,1181 @@ +static const unsigned char data_fsdata_c[] = { + /* /fsdata.c */ + 0x2f, 0x66, 0x73, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x63, 0, + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x20, 0x32, + 0x30, 0x30, 0x20, 0x4f, 0x4b, 0xd, 0xa, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x3a, 0x20, 0x6c, 0x77, 0x49, 0x50, 0x2f, + 0x70, 0x72, 0x65, 0x2d, 0x30, 0x2e, 0x36, 0x20, 0x28, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, + 0x73, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x2f, 0x7e, 0x61, + 0x64, 0x61, 0x6d, 0x2f, 0x6c, 0x77, 0x69, 0x70, 0x2f, 0x29, + 0xd, 0xa, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, + 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0xd, 0xa, 0xd, 0xa, +}; + +static const unsigned char data_index_html[] = { + /* /index.html */ + 0x2f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0, + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x30, 0x20, 0x32, + 0x30, 0x30, 0x20, 0x4f, 0x4b, 0xd, 0xa, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x3a, 0x20, 0x6c, 0x77, 0x49, 0x50, 0x2f, + 0x70, 0x72, 0x65, 0x2d, 0x30, 0x2e, 0x36, 0x20, 0x28, 0x68, + 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, + 0x73, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x2f, 0x7e, 0x61, + 0x64, 0x61, 0x6d, 0x2f, 0x6c, 0x77, 0x69, 0x70, 0x2f, 0x29, + 0xd, 0xa, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, + 0x74, 0x79, 0x70, 0x65, 0x3a, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0xd, 0xa, 0xd, 0xa, 0x3c, + 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45, 0x20, 0x68, + 0x74, 0x6d, 0x6c, 0x3e, 0xa, 0xa, 0x3c, 0x68, 0x74, 0x6d, + 0x6c, 0x3e, 0xa, 0xa, 0x3c, 0x68, 0x65, 0x61, 0x64, 0x3e, + 0xa, 0x3c, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x3e, 0x48, 0x79, + 0x70, 0x65, 0x72, 0x58, 0x20, 0x41, 0x6c, 0x6c, 0x6f, 0x79, + 0x20, 0x45, 0x6c, 0x69, 0x74, 0x65, 0x20, 0x32, 0x20, 0x52, + 0x47, 0x42, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3c, 0x2f, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x3e, 0xa, 0xa, 0x3c, 0x73, 0x74, 0x79, + 0x6c, 0x65, 0x3e, 0xa, 0xa, 0x64, 0x69, 0x76, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x20, 0x3e, + 0x20, 0x64, 0x69, 0x76, 0x20, 0x7b, 0xa, 0x9, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x64, 0x69, 0x76, + 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x7b, 0xa, + 0x9, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x34, 0x30, + 0x65, 0x6d, 0x3b, 0xa, 0x9, 0x77, 0x6f, 0x72, 0x64, 0x2d, + 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a, 0x20, 0x62, 0x72, 0x65, + 0x61, 0x6b, 0x2d, 0x61, 0x6c, 0x6c, 0x3b, 0xa, 0x7d, 0xa, + 0xa, 0x64, 0x69, 0x76, 0x23, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, + 0xa, 0x9, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, + 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x3b, 0xa, 0x9, 0x74, 0x65, 0x78, 0x74, + 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x64, 0x69, + 0x76, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x20, 0x7b, + 0xa, 0x9, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, + 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x3b, 0xa, 0x9, 0x76, 0x65, 0x72, 0x74, + 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, + 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x3b, 0xa, + 0x7d, 0xa, 0xa, 0x64, 0x69, 0x76, 0x23, 0x6b, 0x65, 0x79, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x7b, 0xa, 0x9, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, + 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x3b, 0xa, 0x9, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, + 0x20, 0x23, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3b, 0xa, + 0x7d, 0xa, 0xa, 0x64, 0x69, 0x76, 0x23, 0x6b, 0x65, 0x79, + 0x73, 0x20, 0x7b, 0xa, 0x9, 0x64, 0x69, 0x73, 0x70, 0x6c, + 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, + 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0xa, 0x7d, 0xa, + 0xa, 0x64, 0x69, 0x76, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x20, 0x7b, 0xa, 0x9, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x20, 0x31, 0x30, 0x65, 0x6d, 0x3b, 0xa, 0x9, 0x74, 0x65, + 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, + 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0xa, 0x9, 0x74, + 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x75, 0x6e, 0x64, 0x65, + 0x72, 0x6c, 0x69, 0x6e, 0x65, 0x3b, 0xa, 0x9, 0x66, 0x6f, + 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0xa, 0x7d, 0xa, 0xa, + 0x64, 0x69, 0x76, 0x2e, 0x6b, 0x65, 0x79, 0x20, 0x7b, 0xa, + 0x9, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, + 0x6e, 0x3a, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0xa, + 0x9, 0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, + 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x74, 0x6f, 0x70, + 0x3b, 0xa, 0x9, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, + 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0xa, 0x9, 0x62, 0x61, 0x63, + 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x64, 0x64, 0x64, 0x64, + 0x64, 0x64, 0x3b, 0xa, 0x9, 0x6d, 0x61, 0x72, 0x67, 0x69, + 0x6e, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x3b, 0xa, 0x9, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x2e, 0x35, 0x65, + 0x6d, 0x3b, 0xa, 0x9, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x3a, 0x20, 0x33, 0x65, 0x6d, 0x3b, 0xa, 0x9, 0x66, 0x6f, + 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x30, + 0x2e, 0x38, 0x65, 0x6d, 0x3b, 0xa, 0x9, 0x62, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, + 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x3b, 0xa, 0x9, + 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x64, 0xa, 0x7d, 0xa, 0xa, 0x64, 0x69, 0x76, 0x2e, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x7b, 0xa, + 0x9, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x73, 0x74, + 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x64, 0x6f, 0x75, 0x62, 0x6c, + 0x65, 0x3b, 0xa, 0x9, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x62, 0x6c, + 0x75, 0x65, 0x3b, 0xa, 0x9, 0x62, 0x6f, 0x72, 0x64, 0x65, + 0x72, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, + 0x70, 0x78, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x64, 0x69, 0x76, + 0x2e, 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x20, 0x7b, 0xa, 0x9, + 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x30, + 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x2e, 0x62, 0x79, 0x74, 0x65, 0x20, 0x7b, 0xa, 0x9, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x65, 0x6d, 0x3b, + 0xa, 0x7d, 0xa, 0xa, 0x3c, 0x2f, 0x73, 0x74, 0x79, 0x6c, + 0x65, 0x3e, 0xa, 0xa, 0x3c, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x3e, 0xa, 0x76, 0x61, 0x72, 0x20, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x5b, 0x5d, + 0x3b, 0xa, 0xa, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x6b, + 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x5f, 0x6c, 0x69, + 0x73, 0x74, 0x20, 0x3d, 0x20, 0x5b, 0xa, 0x9, 0x5b, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, + 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, + 0x39, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x54, 0x72, 0x61, + 0x63, 0x6b, 0x50, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x52, 0x65, 0x2d, 0x3c, 0x62, 0x72, 0x3e, 0x77, 0x69, + 0x6e, 0x64, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x50, + 0x6c, 0x61, 0x79, 0x50, 0x61, 0x75, 0x73, 0x65, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x50, + 0x6c, 0x61, 0x79, 0x3c, 0x62, 0x72, 0x3e, 0x50, 0x61, 0x75, + 0x73, 0x65, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x4e, 0x65, 0x78, 0x74, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, + 0x61, 0x73, 0x74, 0x3c, 0x62, 0x72, 0x3e, 0x46, 0x6f, 0x72, + 0x77, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x4d, 0x75, 0x74, 0x65, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4d, 0x75, + 0x74, 0x65, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x5d, 0x2c, 0xa, + 0x9, 0x5b, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x42, 0x61, 0x72, 0x31, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, + 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, + 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x32, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, + 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x33, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, + 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, + 0x34, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, + 0x61, 0x72, 0x35, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x42, 0x61, 0x72, 0x36, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, + 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, + 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x37, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, + 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x38, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, + 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, + 0x39, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, + 0x61, 0x72, 0x31, 0x30, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, + 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, + 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, + 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x42, 0x61, 0x72, 0x31, 0x31, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, + 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, + 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x31, 0x32, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, + 0x72, 0x31, 0x33, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x42, 0x61, 0x72, 0x31, 0x34, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, + 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, + 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x31, 0x35, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, + 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, + 0x31, 0x36, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x42, 0x61, 0x72, 0x31, 0x37, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, 0x32, 0x35, 0x2c, + 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x30, + 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x42, 0x61, 0x72, 0x31, 0x38, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, + 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x31, 0x2e, + 0x32, 0x35, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x3a, 0x20, 0x30, 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x5d, + 0x2c, 0xa, 0x9, 0x5b, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x45, 0x73, 0x63, 0x61, 0x70, 0x65, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x45, 0x53, 0x43, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x3a, 0x20, 0x32, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, 0x31, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x46, 0x31, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, 0x32, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x32, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x46, 0x33, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x33, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, + 0x34, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x46, 0x34, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, 0x35, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, + 0x35, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x46, 0x36, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x36, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x46, 0x37, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x46, 0x37, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, 0x38, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x46, 0x38, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x46, 0x39, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x39, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x46, 0x31, 0x30, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x31, 0x30, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x46, 0x31, 0x31, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x31, 0x31, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x46, + 0x31, 0x32, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x46, 0x31, 0x32, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x50, 0x72, + 0x69, 0x6e, 0x74, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x50, 0x72, 0x69, 0x6e, 0x74, 0x3c, 0x62, 0x72, 0x3e, 0x53, + 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x53, 0x63, 0x72, + 0x6f, 0x6c, 0x6c, 0x4c, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x53, 0x63, + 0x72, 0x6f, 0x6c, 0x6c, 0x3c, 0x62, 0x72, 0x3e, 0x4c, 0x6f, + 0x63, 0x6b, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x75, 0x73, 0x65, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x50, 0x61, 0x75, 0x73, 0x65, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x5d, 0x2c, 0xa, 0x9, 0x5b, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x42, 0x61, 0x63, 0x6b, 0x71, 0x75, + 0x6f, 0x74, 0x65, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x7e, 0x3c, 0x62, 0x72, 0x3e, 0x60, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x74, 0x31, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x21, + 0x3c, 0x62, 0x72, 0x3e, 0x31, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, + 0x69, 0x74, 0x32, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x40, 0x3c, 0x62, 0x72, 0x3e, 0x32, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x74, 0x33, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x23, + 0x3c, 0x62, 0x72, 0x3e, 0x33, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, + 0x69, 0x74, 0x34, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x24, 0x3c, 0x62, 0x72, 0x3e, 0x34, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x74, 0x35, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x25, + 0x3c, 0x62, 0x72, 0x3e, 0x35, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, + 0x69, 0x74, 0x36, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x5e, 0x3c, 0x62, 0x72, 0x3e, 0x36, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x74, 0x37, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x26, + 0x3c, 0x62, 0x72, 0x3e, 0x37, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, + 0x69, 0x74, 0x38, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x2a, 0x3c, 0x62, 0x72, 0x3e, 0x38, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x69, 0x67, 0x69, 0x74, 0x39, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x28, + 0x3c, 0x62, 0x72, 0x3e, 0x39, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x44, 0x69, 0x67, + 0x69, 0x74, 0x30, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x29, 0x3c, 0x62, 0x72, 0x3e, 0x30, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x4d, 0x69, 0x6e, 0x75, 0x73, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x5f, 0x3c, + 0x62, 0x72, 0x3e, 0x2d, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x45, 0x71, 0x75, 0x61, + 0x6c, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x2b, 0x3c, 0x62, 0x72, 0x3e, 0x3d, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x42, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x42, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x22, + 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x49, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x22, 0x20, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x48, 0x6f, 0x6d, + 0x65, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x48, 0x6f, 0x6d, 0x65, 0x22, 0x20, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x50, + 0x61, 0x67, 0x65, 0x55, 0x70, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x67, 0x65, + 0x3c, 0x62, 0x72, 0x3e, 0x55, 0x70, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, + 0x6d, 0x4c, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, 0x64, 0x44, 0x69, 0x76, + 0x69, 0x64, 0x65, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x2f, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, + 0x70, 0x61, 0x64, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x79, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x2a, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, + 0x64, 0x53, 0x75, 0x62, 0x74, 0x72, 0x61, 0x63, 0x74, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x2d, 0x22, 0x7d, 0xa, 0x9, 0x5d, 0x2c, 0xa, 0x9, 0x5b, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x54, + 0x61, 0x62, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x54, 0x61, 0x62, 0x22, 0x2c, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x4b, 0x65, 0x79, 0x51, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x51, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, + 0x79, 0x57, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x57, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x45, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x45, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x52, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x52, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x4b, 0x65, 0x79, 0x54, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x54, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x4b, 0x65, 0x79, 0x59, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x59, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, + 0x79, 0x55, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x55, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x49, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x49, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x4f, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4f, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x4b, 0x65, 0x79, 0x50, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x50, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x42, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x4c, 0x65, 0x66, + 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x7b, 0x3c, 0x62, 0x72, 0x3e, 0x5b, 0x22, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x42, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x69, 0x67, + 0x68, 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x7d, 0x3c, 0x62, 0x72, 0x3e, 0x5d, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x42, 0x61, 0x63, 0x6b, 0x73, 0x6c, 0x61, 0x73, 0x68, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x7c, 0x3c, 0x62, 0x72, 0x3e, 0x5c, 0x5c, 0x22, 0x2c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, + 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x64, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x45, 0x6e, 0x64, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x67, 0x65, + 0x44, 0x6f, 0x77, 0x6e, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x50, 0x61, 0x67, 0x65, 0x3c, + 0x62, 0x72, 0x3e, 0x44, 0x6f, 0x77, 0x6e, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, + 0x75, 0x6d, 0x70, 0x61, 0x64, 0x37, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x48, 0x6f, 0x6d, + 0x65, 0x3c, 0x62, 0x72, 0x3e, 0x37, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, + 0x6d, 0x70, 0x61, 0x64, 0x38, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x55, 0x70, 0x3c, 0x62, + 0x72, 0x3e, 0x38, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, + 0x64, 0x39, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x50, 0x67, 0x55, 0x70, 0x3c, 0x62, 0x72, + 0x3e, 0x39, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, 0x64, + 0x41, 0x64, 0x64, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x2b, 0x22, 0x2c, 0x20, 0x68, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x7d, + 0xa, 0x9, 0x5d, 0x2c, 0xa, 0x9, 0x5b, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x70, 0x73, + 0x4c, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x61, 0x70, 0x73, 0x3c, + 0x62, 0x72, 0x3e, 0x4c, 0x6f, 0x63, 0x6b, 0x22, 0x2c, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, + 0x65, 0x79, 0x41, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, + 0x53, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x53, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x44, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x44, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x46, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x46, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x4b, 0x65, 0x79, 0x47, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x47, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, + 0x65, 0x79, 0x48, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x48, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, + 0x4a, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x4a, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x4b, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x4b, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x4c, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4c, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x53, 0x65, 0x6d, 0x69, 0x63, 0x6f, 0x6c, 0x6f, 0x6e, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x3a, 0x3c, 0x62, 0x72, 0x3e, 0x3b, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x51, + 0x75, 0x6f, 0x74, 0x65, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x5c, 0x22, 0x3c, 0x62, 0x72, + 0x3e, 0x5c, 0x27, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x74, 0x65, 0x72, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x20, 0x77, + 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x20, 0x33, 0x2e, 0x35, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, + 0x61, 0x64, 0x34, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x4c, 0x65, 0x66, 0x74, 0x3c, 0x62, + 0x72, 0x3e, 0x34, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, + 0x64, 0x35, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x35, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, + 0x61, 0x64, 0x36, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x52, 0x69, 0x67, 0x68, 0x74, 0x3c, + 0x62, 0x72, 0x3e, 0x36, 0x22, 0x7d, 0xa, 0x9, 0x5d, 0x2c, + 0xa, 0x9, 0x5b, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x53, 0x68, 0x69, 0x66, 0x74, 0x4c, 0x65, 0x66, + 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x53, 0x68, 0x69, 0x66, 0x74, 0x22, 0x2c, 0x20, + 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x32, 0x2e, 0x35, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x4b, 0x65, 0x79, 0x5a, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x5a, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, + 0x65, 0x79, 0x58, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x58, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, + 0x43, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x43, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x56, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x56, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x4b, 0x65, 0x79, 0x42, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x42, 0x22, + 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, + 0x22, 0x4b, 0x65, 0x79, 0x4e, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4e, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4b, + 0x65, 0x79, 0x4d, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x4d, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x5c, 0x3c, 0x3c, 0x62, 0x72, 0x3e, 0x2c, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x5c, + 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x2e, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x53, 0x6c, + 0x61, 0x73, 0x68, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x3f, 0x3c, 0x62, 0x72, 0x3e, 0x2f, + 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x53, 0x68, 0x69, 0x66, 0x74, 0x52, 0x69, 0x67, + 0x68, 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x53, 0x68, 0x69, 0x66, 0x74, 0x22, 0x2c, + 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x7d, + 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x41, 0x72, 0x72, 0x6f, 0x77, 0x55, 0x70, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x55, 0x70, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, + 0x6d, 0x70, 0x61, 0x64, 0x31, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x64, 0x3c, + 0x62, 0x72, 0x3e, 0x31, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, + 0x61, 0x64, 0x32, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x44, 0x6f, 0x77, 0x6e, 0x3c, 0x62, + 0x72, 0x3e, 0x32, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, + 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, + 0x64, 0x33, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x50, 0x67, 0x44, 0x6e, 0x3c, 0x62, 0x72, + 0x3e, 0x33, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, + 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, 0x70, 0x61, 0x64, + 0x45, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x45, 0x6e, 0x74, 0x65, + 0x72, 0x22, 0x2c, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x7d, 0xa, 0x9, 0x5d, 0x2c, + 0xa, 0x9, 0x5b, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x4c, + 0x65, 0x66, 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, + 0x20, 0x32, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x4d, 0x65, 0x74, 0x61, 0x4c, 0x65, 0x66, + 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x4d, 0x65, 0x74, 0x61, 0x22, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x41, 0x6c, + 0x74, 0x4c, 0x65, 0x66, 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x6c, 0x74, 0x22, + 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, + 0x2e, 0x35, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x53, 0x70, 0x61, 0x63, 0x65, 0x22, 0x2c, + 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x53, + 0x70, 0x61, 0x63, 0x65, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, + 0x74, 0x68, 0x3a, 0x20, 0x36, 0x2e, 0x34, 0x7d, 0x2c, 0xa, + 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x41, 0x6c, + 0x74, 0x52, 0x69, 0x67, 0x68, 0x74, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x41, 0x6c, 0x74, + 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, + 0x32, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, + 0x20, 0x22, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x69, 0x67, 0x68, + 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, + 0x20, 0x22, 0x46, 0x4e, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, + 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, + 0x65, 0x78, 0x74, 0x4d, 0x65, 0x6e, 0x75, 0x22, 0x2c, 0x20, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x22, 0x4d, 0x65, 0x6e, + 0x75, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x52, 0x69, 0x67, 0x68, 0x74, 0x22, 0x2c, 0x20, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x43, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x36, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x41, 0x72, 0x72, + 0x6f, 0x77, 0x4c, 0x65, 0x66, 0x74, 0x22, 0x2c, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x4c, 0x65, 0x66, + 0x74, 0x22, 0x7d, 0x2c, 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, + 0x3a, 0x20, 0x22, 0x41, 0x72, 0x72, 0x6f, 0x77, 0x44, 0x6f, + 0x77, 0x6e, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3a, 0x20, 0x22, 0x44, 0x6f, 0x77, 0x6e, 0x22, 0x7d, 0x2c, + 0xa, 0x9, 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x41, + 0x72, 0x72, 0x6f, 0x77, 0x52, 0x69, 0x67, 0x68, 0x74, 0x22, + 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, 0x22, + 0x52, 0x69, 0x67, 0x68, 0x74, 0x22, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, + 0x70, 0x61, 0x64, 0x30, 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x3a, 0x20, 0x22, 0x49, 0x6e, 0x73, 0x3c, 0x62, + 0x72, 0x3e, 0x30, 0x22, 0x2c, 0x20, 0x77, 0x69, 0x64, 0x74, + 0x68, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x7d, 0x2c, 0xa, 0x9, + 0x9, 0x7b, 0x69, 0x64, 0x3a, 0x20, 0x22, 0x4e, 0x75, 0x6d, + 0x70, 0x61, 0x64, 0x44, 0x65, 0x63, 0x69, 0x6d, 0x61, 0x6c, + 0x22, 0x2c, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x3a, 0x20, + 0x22, 0x44, 0x65, 0x6c, 0x3c, 0x62, 0x72, 0x3e, 0x2e, 0x22, + 0x7d, 0xa, 0x9, 0x5d, 0x2c, 0xa, 0x5d, 0x3b, 0xa, 0xa, + 0x2f, 0x2f, 0x20, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x20, 0x70, 0x61, 0x67, 0x65, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, + 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x61, + 0x64, 0xa, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x6f, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x3d, 0x20, 0x28, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x29, 0x20, 0x3d, 0x3e, 0x20, 0x7b, + 0xa, 0x9, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, + 0x79, 0x73, 0x28, 0x22, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, + 0x72, 0x64, 0x22, 0x2c, 0x20, 0x6b, 0x65, 0x79, 0x62, 0x6f, + 0x61, 0x72, 0x64, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x29, 0x3b, + 0xa, 0xa, 0x9, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x20, + 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x57, 0x65, 0x62, 0x53, + 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x22, 0x77, 0x73, 0x3a, + 0x2f, 0x2f, 0x22, 0x20, 0x2b, 0x20, 0x77, 0x69, 0x6e, 0x64, + 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, + 0x20, 0x2b, 0x20, 0x22, 0x3a, 0x38, 0x30, 0x38, 0x30, 0x2f, + 0x22, 0x29, 0x3b, 0xa, 0x9, 0x73, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x2e, 0x6f, 0x6e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x29, + 0x20, 0x7b, 0x20, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x64, 0x61, + 0x74, 0x61, 0x29, 0x3b, 0x20, 0x7d, 0x3b, 0xa, 0x7d, 0xa, + 0xa, 0x2f, 0x2f, 0x20, 0x6d, 0x61, 0x63, 0x72, 0x6f, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x77, 0x65, 0x62, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x72, 0x65, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x69, 0x66, 0x20, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x64, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x73, 0x65, 0x6e, 0x64, 0x72, 0x65, 0x6c, 0x6f, 0x61, + 0x64, 0x28, 0x6d, 0x73, 0x67, 0x2c, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x69, 0x66, 0x20, 0x28, + 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x72, 0x65, 0x61, + 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, + 0x20, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x2e, 0x4f, 0x50, 0x45, 0x4e, 0x29, 0x20, 0x7b, 0xa, 0x9, + 0x9, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x73, 0x65, + 0x6e, 0x64, 0x28, 0x6d, 0x73, 0x67, 0x29, 0x3b, 0xa, 0x9, + 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, 0x20, + 0x28, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2e, 0x72, 0x65, + 0x61, 0x64, 0x79, 0x53, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, + 0x3d, 0x20, 0x57, 0x65, 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, + 0x74, 0x2e, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x44, 0x29, 0x20, + 0x7b, 0xa, 0x9, 0x9, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x57, 0x65, 0x62, + 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x28, 0x22, 0x77, 0x73, + 0x3a, 0x2f, 0x2f, 0x22, 0x20, 0x2b, 0x20, 0x77, 0x69, 0x6e, + 0x64, 0x6f, 0x77, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x2b, 0x20, 0x22, 0x3a, 0x38, 0x30, 0x38, 0x30, + 0x2f, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x9, 0x73, 0x6f, 0x63, + 0x6b, 0x65, 0x74, 0x2e, 0x6f, 0x6e, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x29, 0x20, 0x7b, 0x20, 0x73, 0x65, 0x74, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x64, 0x61, 0x74, 0x61, 0x29, 0x3b, 0x20, 0x7d, 0x3b, 0xa, + 0x9, 0x9, 0x73, 0x65, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, + 0x75, 0x74, 0x28, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x2c, 0x20, + 0x31, 0x30, 0x29, 0x3b, 0xa, 0x9, 0x7d, 0x20, 0x65, 0x6c, + 0x73, 0x65, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x73, 0x65, 0x74, + 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x28, 0x20, 0x66, + 0x75, 0x6e, 0x63, 0x2c, 0x20, 0x31, 0x30, 0x29, 0x3b, 0xa, + 0x9, 0x7d, 0xa, 0x7d, 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x74, + 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x20, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x70, + 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, + 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x28, 0x63, + 0x6f, 0x64, 0x65, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x69, 0x66, + 0x20, 0x28, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, + 0x2e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x28, + 0x63, 0x6f, 0x64, 0x65, 0x29, 0x29, 0x20, 0x7b, 0xa, 0x9, + 0x9, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x2e, + 0x73, 0x70, 0x6c, 0x69, 0x63, 0x65, 0x28, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x2e, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x4f, 0x66, 0x28, 0x63, 0x6f, 0x64, 0x65, 0x29, 0x2c, + 0x31, 0x29, 0x3b, 0xa, 0x9, 0x7d, 0x20, 0x65, 0x6c, 0x73, + 0x65, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x2e, 0x70, 0x75, 0x73, 0x68, 0x28, + 0x63, 0x6f, 0x64, 0x65, 0x29, 0x3b, 0xa, 0x9, 0x7d, 0xa, + 0xa, 0x9, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, + 0x79, 0x73, 0x28, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x29, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x2f, 0x2f, 0x20, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x47, 0x55, 0x49, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x73, 0xa, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x28, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6b, + 0x65, 0x79, 0x73, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x2f, 0x2f, + 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x68, 0x69, + 0x67, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x69, 0x6e, 0x67, + 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, 0x73, 0xa, 0x9, 0x70, + 0x72, 0x65, 0x76, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x3d, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x42, 0x79, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, + 0x6d, 0x65, 0x28, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x65, 0x64, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x66, 0x6f, 0x72, + 0x20, 0x28, 0x6c, 0x65, 0x74, 0x20, 0x6b, 0x65, 0x79, 0x20, + 0x6f, 0x66, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x29, 0x20, 0x7b, 0xa, + 0x9, 0x9, 0x6b, 0x65, 0x79, 0x44, 0x69, 0x76, 0x20, 0x3d, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x42, 0x79, 0x49, 0x64, 0x28, 0x6b, 0x65, 0x79, 0x29, 0x3b, + 0xa, 0x9, 0x9, 0x6b, 0x65, 0x79, 0x44, 0x69, 0x76, 0x2e, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x2e, + 0x61, 0x64, 0x64, 0x28, 0x22, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x7d, 0xa, + 0x9, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x6c, 0x65, 0x74, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x65, + 0x76, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x29, 0x20, 0x7b, 0xa, + 0x9, 0x9, 0x69, 0x66, 0x20, 0x28, 0x21, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, 0x79, 0x73, + 0x2e, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x28, + 0x6b, 0x65, 0x79, 0x2e, 0x69, 0x64, 0x29, 0x29, 0x20, 0x7b, + 0xa, 0x9, 0x9, 0x9, 0x6b, 0x65, 0x79, 0x2e, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x2e, 0x72, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x28, 0x22, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x9, + 0x7d, 0xa, 0x9, 0x7d, 0xa, 0xa, 0x9, 0x2f, 0x2f, 0x20, + 0x73, 0x68, 0x6f, 0x77, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, + 0x6b, 0x65, 0x79, 0x73, 0xa, 0x9, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, + 0x22, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x29, 0x2e, 0x69, 0x6e, + 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, 0x20, + 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6b, + 0x65, 0x79, 0x73, 0x3b, 0xa, 0x9, 0x2f, 0x2f, 0x20, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x6c, 0x65, + 0x63, 0x74, 0x65, 0x64, 0x20, 0x6b, 0x65, 0x79, 0xa, 0x9, + 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x73, + 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x29, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x2f, 0x2f, + 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x6b, 0x65, + 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x6c, 0x61, 0x79, + 0x6f, 0x75, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x61, + 0x72, 0x72, 0x61, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x6b, 0x65, + 0x79, 0x73, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4b, 0x65, + 0x79, 0x73, 0x28, 0x64, 0x69, 0x76, 0x5f, 0x69, 0x64, 0x2c, + 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x29, + 0x20, 0x7b, 0xa, 0x9, 0x6c, 0x65, 0x74, 0x20, 0x6b, 0x65, + 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x44, 0x69, 0x76, 0x20, + 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x64, 0x69, 0x76, 0x5f, + 0x69, 0x64, 0x29, 0x3b, 0xa, 0x9, 0x66, 0x6f, 0x72, 0x20, + 0x28, 0x6c, 0x65, 0x74, 0x20, 0x72, 0x6f, 0x77, 0x20, 0x6f, + 0x66, 0x20, 0x6b, 0x65, 0x79, 0x5f, 0x6c, 0x69, 0x73, 0x74, + 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x6c, 0x65, 0x74, 0x20, + 0x6e, 0x65, 0x77, 0x52, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x28, 0x22, 0x64, 0x69, 0x76, 0x22, 0x29, 0x3b, 0xa, + 0x9, 0x9, 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x44, 0x69, 0x76, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, + 0x43, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, 0x65, 0x77, 0x52, + 0x6f, 0x77, 0x29, 0x3b, 0xa, 0x9, 0x9, 0x66, 0x6f, 0x72, + 0x20, 0x28, 0x6c, 0x65, 0x74, 0x20, 0x6b, 0x65, 0x79, 0x20, + 0x6f, 0x66, 0x20, 0x72, 0x6f, 0x77, 0x29, 0x20, 0x7b, 0xa, + 0x9, 0x9, 0x9, 0x6c, 0x65, 0x74, 0x20, 0x6e, 0x65, 0x77, + 0x44, 0x69, 0x76, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x28, 0x22, + 0x64, 0x69, 0x76, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x9, 0x9, + 0x69, 0x66, 0x20, 0x28, 0x6b, 0x65, 0x79, 0x2e, 0x69, 0x64, + 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, + 0x77, 0x44, 0x69, 0x76, 0x2e, 0x69, 0x64, 0x20, 0x3d, 0x20, + 0x6b, 0x65, 0x79, 0x2e, 0x69, 0x64, 0x3b, 0xa, 0x9, 0x9, + 0x9, 0x9, 0x6e, 0x65, 0x77, 0x44, 0x69, 0x76, 0x2e, 0x69, + 0x6e, 0x6e, 0x65, 0x72, 0x48, 0x54, 0x4d, 0x4c, 0x20, 0x3d, + 0x20, 0x6b, 0x65, 0x79, 0x2e, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x3b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, 0x77, 0x44, + 0x69, 0x76, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, + 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x6b, 0x65, 0x79, 0x22, + 0x3b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, 0x77, 0x44, + 0x69, 0x76, 0x2e, 0x61, 0x64, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x65, 0x72, 0x28, + 0x22, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x22, 0x2c, 0x20, 0x66, + 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x29, + 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x9, 0x70, 0x72, + 0x65, 0x73, 0x73, 0x4b, 0x65, 0x79, 0x28, 0x6b, 0x65, 0x79, + 0x2e, 0x69, 0x64, 0x29, 0x3b, 0x20, 0x7d, 0x29, 0x3b, 0xa, + 0x9, 0x9, 0x9, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, + 0x7b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, 0x77, 0x44, + 0x69, 0x76, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x4e, 0x61, + 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x6b, 0x65, 0x79, 0x20, + 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x22, 0x3b, 0xa, 0x9, 0x9, + 0x9, 0x7d, 0xa, 0x9, 0x9, 0x9, 0x6e, 0x65, 0x77, 0x52, + 0x6f, 0x77, 0x2e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x43, + 0x68, 0x69, 0x6c, 0x64, 0x28, 0x6e, 0x65, 0x77, 0x44, 0x69, + 0x76, 0x29, 0x3b, 0xa, 0x9, 0x9, 0x9, 0x69, 0x66, 0x20, + 0x28, 0x6b, 0x65, 0x79, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, + 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, + 0x77, 0x44, 0x69, 0x76, 0x2e, 0x73, 0x74, 0x79, 0x6c, 0x65, + 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x20, 0x3d, 0x20, 0x28, + 0x6b, 0x65, 0x79, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68, 0x2a, + 0x6e, 0x65, 0x77, 0x44, 0x69, 0x76, 0x2e, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x57, 0x69, 0x64, 0x74, 0x68, 0x20, 0x29, + 0x20, 0x2b, 0x20, 0x22, 0x70, 0x78, 0x22, 0x3b, 0xa, 0x9, + 0x9, 0x9, 0x7d, 0xa, 0x9, 0x9, 0x9, 0x69, 0x66, 0x20, + 0x28, 0x6b, 0x65, 0x79, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x6e, + 0x65, 0x77, 0x44, 0x69, 0x76, 0x2e, 0x73, 0x74, 0x79, 0x6c, + 0x65, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x20, 0x3d, + 0x20, 0x28, 0x6b, 0x65, 0x79, 0x2e, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x2a, 0x6e, 0x65, 0x77, 0x44, 0x69, 0x76, 0x2e, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x20, 0x29, 0x20, 0x2b, 0x20, 0x22, 0x70, 0x78, + 0x22, 0x3b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x69, 0x66, 0x20, + 0x28, 0x6b, 0x65, 0x79, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x20, 0x3e, 0x20, 0x31, 0x29, 0x20, 0x7b, 0xa, 0x9, + 0x9, 0x9, 0x9, 0x9, 0x6e, 0x65, 0x77, 0x44, 0x69, 0x76, + 0x2e, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x2e, 0x66, 0x6c, 0x6f, + 0x61, 0x74, 0x20, 0x3d, 0x20, 0x22, 0x72, 0x69, 0x67, 0x68, + 0x74, 0x22, 0x3b, 0xa, 0x9, 0x9, 0x9, 0x9, 0x7d, 0xa, + 0x9, 0x9, 0x9, 0x7d, 0xa, 0x9, 0x9, 0x7d, 0xa, 0x9, + 0x7d, 0xa, 0x7d, 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x73, 0x65, + 0x6e, 0x64, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x52, 0x47, 0x42, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x74, 0x6f, 0x20, 0x68, + 0x6f, 0x73, 0x74, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x4b, 0x65, 0x79, + 0x73, 0x28, 0x6b, 0x65, 0x79, 0x73, 0x29, 0x20, 0x7b, 0xa, + 0x9, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x2e, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, + 0x20, 0x3e, 0x20, 0x30, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, + 0x6c, 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, + 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, + 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x72, 0x67, 0x62, + 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3b, 0xa, + 0x9, 0x9, 0x6c, 0x65, 0x74, 0x20, 0x61, 0x64, 0x61, 0x70, + 0x74, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x61, + 0x64, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x2e, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x3f, 0x20, + 0x31, 0x3a, 0x30, 0x3b, 0xa, 0x9, 0x9, 0x73, 0x65, 0x6e, + 0x64, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x22, 0x53, + 0x2c, 0x22, 0x20, 0x2b, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, + 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x28, 0x31, 0x2c, + 0x36, 0x29, 0x20, 0x2b, 0x20, 0x22, 0x2c, 0x22, 0x20, 0x2b, + 0x20, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x2b, 0x20, 0x22, + 0x2c, 0x22, 0x20, 0x2b, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x2c, + 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x28, 0x29, 0x20, 0x7b, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x4b, + 0x65, 0x79, 0x73, 0x28, 0x6b, 0x65, 0x79, 0x73, 0x29, 0x3b, + 0x20, 0x7d, 0x20, 0x29, 0x3b, 0xa, 0x9, 0x7d, 0xa, 0x7d, + 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, + 0x73, 0x61, 0x76, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x68, 0x6f, 0x73, 0x74, + 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x73, 0x61, 0x76, 0x65, 0x28, 0x29, 0x20, 0x7b, 0xa, 0x9, + 0x73, 0x65, 0x6e, 0x64, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, + 0x28, 0x22, 0x46, 0x2c, 0x22, 0x2c, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x29, 0x20, 0x7b, 0x20, 0x73, 0x61, 0x76, 0x65, + 0x28, 0x29, 0x3b, 0x20, 0x7d, 0x20, 0x29, 0x3b, 0xa, 0x7d, + 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, + 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x68, 0x6f, 0x73, 0x74, + 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x20, 0x7b, 0xa, 0x9, + 0x73, 0x65, 0x6e, 0x64, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, + 0x28, 0x22, 0x4c, 0x2c, 0x22, 0x2c, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x29, 0x20, 0x7b, 0x20, 0x6c, 0x6f, 0x61, 0x64, + 0x28, 0x29, 0x3b, 0x20, 0x7d, 0x20, 0x29, 0x3b, 0xa, 0x7d, + 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x20, 0x52, 0x47, 0x42, 0x20, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, + 0x72, 0x28, 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, + 0x22, 0x72, 0x67, 0x62, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x20, 0x3d, 0x20, 0x6e, 0x65, 0x77, 0x5f, 0x63, + 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x28, 0x30, 0x2c, 0x37, 0x29, 0x3b, + 0xa, 0x9, 0x6c, 0x65, 0x74, 0x20, 0x61, 0x64, 0x61, 0x70, + 0x74, 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x61, + 0x64, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x22, 0x29, 0x3b, + 0xa, 0x9, 0x69, 0x66, 0x20, 0x28, 0x6e, 0x65, 0x77, 0x5f, + 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x73, 0x75, 0x62, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x38, 0x2c, 0x39, 0x29, + 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x31, 0x22, 0x29, 0x20, 0x7b, + 0xa, 0x9, 0x9, 0x61, 0x64, 0x61, 0x70, 0x74, 0x2e, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x3b, 0xa, 0x9, 0x7d, 0x20, 0x65, + 0x6c, 0x73, 0x65, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x61, 0x64, + 0x61, 0x70, 0x74, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x65, + 0x64, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0xa, + 0x9, 0x7d, 0xa, 0x9, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x47, 0x42, 0x28, 0x29, 0x3b, 0xa, 0x7d, 0xa, 0xa, + 0x2f, 0x2f, 0x20, 0x73, 0x65, 0x6e, 0x64, 0x20, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x72, + 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x65, + 0x64, 0x20, 0x6b, 0x65, 0x79, 0xa, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x67, 0x65, 0x74, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x28, 0x6b, 0x65, 0x79, 0x73, 0x29, 0x20, + 0x7b, 0xa, 0x9, 0x69, 0x66, 0x20, 0x28, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x2e, 0x6c, 0x65, 0x6e, 0x67, + 0x74, 0x68, 0x20, 0x3e, 0x20, 0x30, 0x29, 0x20, 0x7b, 0xa, + 0x9, 0x9, 0x73, 0x65, 0x6e, 0x64, 0x72, 0x65, 0x6c, 0x6f, + 0x61, 0x64, 0x28, 0x22, 0x47, 0x2c, 0x22, 0x20, 0x2b, 0x20, + 0x6b, 0x65, 0x79, 0x73, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x29, 0x20, 0x7b, 0x20, + 0x67, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x6b, + 0x65, 0x79, 0x73, 0x29, 0x3b, 0x20, 0x7d, 0x20, 0x29, 0x3b, + 0xa, 0x9, 0x7d, 0xa, 0x7d, 0xa, 0xa, 0x2f, 0x2f, 0x20, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x69, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, 0x61, + 0x6c, 0x20, 0x52, 0x2c, 0x20, 0x47, 0x2c, 0x20, 0x42, 0x20, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x77, 0x68, 0x65, + 0x6e, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x69, 0x73, + 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x66, 0x72, + 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x6c, + 0x65, 0x74, 0x74, 0x65, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x47, 0x42, 0x28, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x6c, + 0x65, 0x74, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x3d, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, + 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x72, 0x67, 0x62, 0x22, + 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3b, 0xa, 0x9, + 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, + 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, + 0x79, 0x49, 0x64, 0x28, 0x22, 0x72, 0x65, 0x64, 0x22, 0x29, + 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x70, + 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x63, 0x6f, + 0x6c, 0x6f, 0x72, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x28, 0x31, 0x2c, 0x33, 0x29, 0x2c, 0x20, + 0x31, 0x36, 0x29, 0x3b, 0xa, 0x9, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, + 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, + 0x22, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x29, 0x2e, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x70, 0x61, 0x72, + 0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x63, 0x6f, 0x6c, 0x6f, + 0x72, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x28, 0x33, 0x2c, 0x35, 0x29, 0x2c, 0x20, 0x31, 0x36, + 0x29, 0x3b, 0xa, 0x9, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x62, + 0x6c, 0x75, 0x65, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x20, 0x3d, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x49, + 0x6e, 0x74, 0x28, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2e, 0x73, + 0x75, 0x62, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x35, + 0x2c, 0x37, 0x29, 0x2c, 0x20, 0x31, 0x36, 0x29, 0x3b, 0xa, + 0x7d, 0xa, 0xa, 0x2f, 0x2f, 0x20, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x20, 0x70, 0x61, 0x6c, 0x65, 0x74, 0x74, 0x65, + 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x69, + 0x76, 0x69, 0x64, 0x75, 0x61, 0x6c, 0x20, 0x52, 0x2c, 0x20, + 0x47, 0x2c, 0x20, 0x42, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0xa, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x28, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x2f, 0x2f, + 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x6d, 0x61, 0x6b, + 0x65, 0x20, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x76, 0x69, 0x64, 0x75, + 0x61, 0x6c, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x62, 0x65, 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, + 0x30, 0x2d, 0x32, 0x35, 0x35, 0x3b, 0xa, 0x9, 0x6c, 0x65, + 0x74, 0x20, 0x72, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x64, 0x6f, + 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, + 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, + 0x64, 0x28, 0x22, 0x72, 0x65, 0x64, 0x22, 0x29, 0x3b, 0xa, + 0x9, 0x6c, 0x65, 0x74, 0x20, 0x67, 0x72, 0x65, 0x65, 0x6e, + 0x20, 0x3d, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x67, 0x72, + 0x65, 0x65, 0x6e, 0x22, 0x29, 0x3b, 0xa, 0x9, 0x6c, 0x65, + 0x74, 0x20, 0x62, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x64, + 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x67, 0x65, + 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x42, 0x79, + 0x49, 0x64, 0x28, 0x22, 0x62, 0x6c, 0x75, 0x65, 0x22, 0x29, + 0x3b, 0xa, 0xa, 0x9, 0x66, 0x6f, 0x72, 0x20, 0x28, 0x6c, + 0x65, 0x74, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x20, 0x6f, 0x66, 0x20, 0x5b, 0x72, 0x65, 0x64, 0x2c, 0x20, + 0x67, 0x72, 0x65, 0x65, 0x6e, 0x2c, 0x20, 0x62, 0x6c, 0x75, + 0x65, 0x5d, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x69, 0x66, + 0x20, 0x28, 0x70, 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, + 0x28, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x29, 0x20, 0x3e, 0x20, 0x32, 0x35, + 0x35, 0x29, 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x20, 0x3d, 0x20, 0x32, 0x35, 0x35, 0x3b, 0xa, 0x9, + 0x9, 0x7d, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x20, 0x69, 0x66, + 0x20, 0x28, 0x70, 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, + 0x28, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x2e, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x29, 0x20, 0x3c, 0x20, 0x30, 0x29, + 0x20, 0x7b, 0xa, 0x9, 0x9, 0x9, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, + 0x3d, 0x20, 0x30, 0x3b, 0xa, 0x9, 0x9, 0x7d, 0xa, 0x9, + 0x7d, 0xa, 0xa, 0x9, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, + 0x6e, 0x74, 0x2e, 0x67, 0x65, 0x74, 0x45, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x42, 0x79, 0x49, 0x64, 0x28, 0x22, 0x72, + 0x67, 0x62, 0x22, 0x29, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x20, 0x3d, 0x20, 0x22, 0x23, 0x22, 0x20, 0x2b, 0x20, 0x70, + 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x72, 0x65, + 0x64, 0x2e, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x29, 0x2e, 0x74, + 0x6f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x36, + 0x29, 0x2e, 0x70, 0x61, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, + 0x28, 0x32, 0x2c, 0x22, 0x30, 0x22, 0x29, 0x20, 0x2b, 0xa, + 0x9, 0x9, 0x70, 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, + 0x28, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x2e, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x28, 0x31, 0x36, 0x29, 0x2e, 0x70, 0x61, 0x64, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x28, 0x32, 0x2c, 0x22, 0x30, + 0x22, 0x29, 0x20, 0x2b, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x49, 0x6e, 0x74, 0x28, 0x62, 0x6c, 0x75, 0x65, 0x2e, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x29, 0x2e, 0x74, 0x6f, 0x53, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x28, 0x31, 0x36, 0x29, 0x2e, 0x70, + 0x61, 0x64, 0x53, 0x74, 0x61, 0x72, 0x74, 0x28, 0x32, 0x2c, + 0x22, 0x30, 0x22, 0x29, 0x3b, 0xa, 0x7d, 0xa, 0xa, 0x3c, + 0x2f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x3e, 0xa, 0xa, + 0x3c, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x3e, 0xa, 0xa, 0x3c, + 0x62, 0x6f, 0x64, 0x79, 0x3e, 0xa, 0x3c, 0x64, 0x69, 0x76, + 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x3e, 0xa, + 0x9, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, + 0x6b, 0x65, 0x79, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x22, 0x3e, + 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, 0x3c, 0x64, + 0x69, 0x76, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x6d, 0x65, 0x6e, + 0x75, 0x22, 0x3e, 0xa, 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, + 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x22, 0x3e, 0x4c, 0x45, 0x44, 0x20, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x3c, 0x2f, 0x64, + 0x69, 0x76, 0x3e, 0xa, 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, + 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x22, 0x3e, 0x4b, 0x65, 0x79, 0x28, + 0x73, 0x29, 0x3a, 0x20, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, + 0x64, 0x3d, 0x22, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x3e, 0x3c, + 0x2f, 0x64, 0x69, 0x76, 0x3e, 0x3c, 0x2f, 0x64, 0x69, 0x76, + 0x3e, 0xa, 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x69, + 0x64, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3e, 0xa, 0x9, + 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x22, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x43, 0x6f, 0x6c, + 0x6f, 0x72, 0x3a, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, + 0x6f, 0x72, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x72, 0x67, + 0x62, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x72, + 0x67, 0x62, 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x3d, 0x22, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x47, 0x42, 0x28, 0x29, 0x22, 0x3e, 0x3c, 0x62, 0x72, + 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x52, 0x65, 0x64, 0x3a, + 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, 0x20, + 0x69, 0x64, 0x3d, 0x22, 0x72, 0x65, 0x64, 0x22, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x72, 0x65, 0x64, 0x22, 0x20, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x62, 0x79, 0x74, + 0x65, 0x22, 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, + 0x3d, 0x22, 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, + 0x32, 0x7d, 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x3d, 0x22, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x22, 0x3e, 0x3c, + 0x62, 0x72, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x47, 0x72, + 0x65, 0x65, 0x6e, 0x3a, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, + 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, + 0x78, 0x74, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x67, 0x72, + 0x65, 0x65, 0x6e, 0x22, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, + 0x22, 0x67, 0x72, 0x65, 0x65, 0x6e, 0x22, 0x20, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x3d, 0x22, 0x62, 0x79, 0x74, 0x65, 0x22, + 0x20, 0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e, 0x3d, 0x22, + 0x5b, 0x30, 0x2d, 0x39, 0x5d, 0x7b, 0x31, 0x2c, 0x32, 0x7d, + 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x3d, 0x22, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, + 0x6c, 0x6f, 0x72, 0x28, 0x29, 0x22, 0x3e, 0x3c, 0x62, 0x72, + 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x42, 0x6c, 0x75, 0x65, + 0x3a, 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x3d, 0x22, 0x74, 0x65, 0x78, 0x74, 0x22, + 0x20, 0x69, 0x64, 0x3d, 0x22, 0x62, 0x6c, 0x75, 0x65, 0x22, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x62, 0x6c, 0x75, + 0x65, 0x22, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, + 0x62, 0x79, 0x74, 0x65, 0x22, 0x20, 0x70, 0x61, 0x74, 0x74, + 0x65, 0x72, 0x6e, 0x3d, 0x22, 0x5b, 0x30, 0x2d, 0x39, 0x5d, + 0x7b, 0x31, 0x2c, 0x32, 0x7d, 0x22, 0x20, 0x6f, 0x6e, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x3d, 0x22, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x29, + 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0xa, 0x9, 0x9, 0x9, + 0x9, 0x41, 0x64, 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x3a, + 0x20, 0x3c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x3d, 0x22, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, + 0x6f, 0x78, 0x22, 0x20, 0x69, 0x64, 0x3d, 0x22, 0x61, 0x64, + 0x61, 0x70, 0x74, 0x69, 0x76, 0x65, 0x22, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x3d, 0x22, 0x61, 0x64, 0x61, 0x70, 0x74, 0x69, + 0x76, 0x65, 0x22, 0x3e, 0x3c, 0x62, 0x72, 0x3e, 0x3c, 0x62, + 0x72, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x3c, 0x62, 0x75, + 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, + 0x22, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x6f, + 0x6e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x73, 0x65, + 0x6e, 0x64, 0x4b, 0x65, 0x79, 0x73, 0x28, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x29, 0x22, 0x3e, 0x53, 0x65, + 0x74, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x3c, 0x2f, 0x62, + 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3e, 0x20, 0x3c, 0x62, 0x75, + 0x74, 0x74, 0x6f, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, + 0x22, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x6f, + 0x6e, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x67, 0x65, + 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x65, 0x64, 0x29, 0x22, 0x3e, 0x52, 0x65, + 0x73, 0x65, 0x74, 0x3c, 0x2f, 0x62, 0x75, 0x74, 0x74, 0x6f, + 0x6e, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x3c, 0x2f, 0x64, 0x69, + 0x76, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, + 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x22, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x3e, 0xa, 0x9, 0x9, 0x9, + 0x9, 0x3c, 0x64, 0x69, 0x76, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x3d, 0x22, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3e, + 0x46, 0x6c, 0x61, 0x73, 0x68, 0x20, 0x6d, 0x65, 0x6d, 0x6f, + 0x72, 0x79, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, + 0x9, 0x9, 0x9, 0x3c, 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, + 0x9, 0x9, 0x9, 0x9, 0x3c, 0x62, 0x75, 0x74, 0x74, 0x6f, + 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x62, 0x75, + 0x74, 0x74, 0x6f, 0x6e, 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x6c, + 0x69, 0x63, 0x6b, 0x3d, 0x22, 0x73, 0x61, 0x76, 0x65, 0x28, + 0x29, 0x22, 0x3e, 0x53, 0x61, 0x76, 0x65, 0x3c, 0x2f, 0x62, + 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3e, 0xa, 0x9, 0x9, 0x9, + 0x9, 0x9, 0x3c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x3d, 0x22, 0x62, 0x75, 0x74, 0x74, + 0x6f, 0x6e, 0x22, 0x20, 0x6f, 0x6e, 0x63, 0x6c, 0x69, 0x63, + 0x6b, 0x3d, 0x22, 0x6c, 0x6f, 0x61, 0x64, 0x28, 0x29, 0x22, + 0x3e, 0x4c, 0x6f, 0x61, 0x64, 0x3c, 0x2f, 0x62, 0x75, 0x74, + 0x74, 0x6f, 0x6e, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x9, 0x3c, + 0x2f, 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, 0x9, 0x9, 0x3c, + 0x2f, 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, 0x9, 0x3c, 0x2f, + 0x64, 0x69, 0x76, 0x3e, 0xa, 0x9, 0x3c, 0x2f, 0x64, 0x69, + 0x76, 0x3e, 0xa, 0x3c, 0x2f, 0x64, 0x69, 0x76, 0x3e, 0xa, + 0xa, 0x3c, 0x2f, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0xa, 0xa, + 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0xa, }; + +const struct fsdata_file file_fsdata_c[] = {{NULL, data_fsdata_c, data_fsdata_c + 10, sizeof(data_fsdata_c) - 10, FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}}; + +const struct fsdata_file file_index_html[] = {{file_fsdata_c, data_index_html, data_index_html + 12, sizeof(data_index_html) - 12, FS_FILE_FLAGS_HEADER_INCLUDED | FS_FILE_FLAGS_HEADER_PERSISTENT}}; + +#define FS_ROOT file_index_html + +#define FS_NUMFILES 2 diff --git a/tusb_config.h b/tusb_config.h index eae9eb8..31b0396 100644 --- a/tusb_config.h +++ b/tusb_config.h @@ -83,8 +83,9 @@ //-----------------------Driver configuration------------------------- -#define CFG_TUD_CDC 1 -#define CFG_TUD_HID 4 +#define CFG_TUD_CDC 1 +#define CFG_TUD_HID 4 +#define CFG_TUD_NCM 1 // CDC FIFO size of TX and RX #define CFG_TUD_CDC_RX_BUFSIZE 128 diff --git a/ui.png b/ui.png new file mode 100644 index 0000000..b72a4d2 Binary files /dev/null and b/ui.png differ diff --git a/usb_device.c b/usb_device.c index 4aa0873..d184a6a 100644 --- a/usb_device.c +++ b/usb_device.c @@ -9,6 +9,7 @@ #include "hyperx_elite2.h" #include "usb_host.h" +#include "usb_server.h" #include "usb_device.h" @@ -44,6 +45,7 @@ static tusb_desc_device_t const desc_device = .bNumConfigurations = 0x01 }; + static char const* string_desc_arr [] = { (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) @@ -51,7 +53,9 @@ static char const* string_desc_arr [] = "Pico HyperX Elite 2 RGB Controller", // 2: Product NULL, // 3: Serials, should use chip ID "Pico HyperX Elite 2 CDC", // 4: CDC - "Pico HyperX Elite 2 HID", // 5: HID + "Pico HyperX Elite 2 NCM", // 5: NCM + NULL, // 6: MAC address for NCM + "Pico HyperX Elite 2 HID", // 7: HID }; static void usb_device_init(void); @@ -65,6 +69,9 @@ void usb_device_main(void) { device_state = DEVICE_INACTIVE; usb_device_init(); + // start the web server on USB + usb_server_init(); + while (true) { switch ( device_state ) { case DEVICE_ACTIVE: @@ -116,21 +123,24 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index) { (void) index; // for multiple configurations + // set configuration descriptor and CDC descriptor memset(desc_configuration, 0, sizeof(desc_configuration)); - uint8_t desc_initial[TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+1] = { - TUD_CONFIG_DESCRIPTOR(1, 2+num_mounted, 0, TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+num_mounted*TUD_HID_DESC_LEN, 0x00, 100), - TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) + uint8_t desc_initial[TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+TUD_CDC_NCM_DESC_LEN+1] = { + TUD_CONFIG_DESCRIPTOR(1, 4+num_mounted, 0, TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+TUD_CDC_NCM_DESC_LEN+num_mounted*TUD_HID_DESC_LEN, 0x00, 100), + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), + TUD_CDC_NCM_DESCRIPTOR(ITF_NUM_NCM, 5, 6, EPNUM_NCM_NOTIF, 64, EPNUM_NCM_OUT, EPNUM_NCM_IN, CFG_TUD_NET_ENDPOINT_SIZE, CFG_TUD_NET_MTU) }; - memcpy(desc_configuration, desc_initial, TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN); + memcpy(desc_configuration, desc_initial, TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+TUD_CDC_NCM_DESC_LEN); + // add a HID descriptor for each interface mounted on host if ( descriptors != NULL) { struct report_desc *descriptor; for (uint8_t i=0; idev_addr, i); uint8_t hid_desc[TUD_HID_DESC_LEN+1] = { - TUD_HID_DESCRIPTOR(ITF_NUM_HID+i, 5, HID_ITF_PROTOCOL_NONE, descriptor->desc_len, EPNUM_HID+i, CFG_TUD_HID_EP_BUFSIZE, 1) + TUD_HID_DESCRIPTOR(ITF_NUM_HID+i, 7, HID_ITF_PROTOCOL_NONE, descriptor->desc_len, EPNUM_HID+i, CFG_TUD_HID_EP_BUFSIZE, 1) }; - memcpy(&desc_configuration[TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+i*TUD_HID_DESC_LEN], hid_desc, TUD_HID_DESC_LEN); + memcpy(&desc_configuration[TUD_CONFIG_DESC_LEN+TUD_CDC_DESC_LEN+TUD_CDC_NCM_DESC_LEN+i*TUD_HID_DESC_LEN], hid_desc, TUD_HID_DESC_LEN); } } @@ -153,6 +163,17 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) case 3: // serial chr_count = board_usb_get_serial(_desc_str+1, 32); break; + case 6: // MAC address: link-local prefix of 02 and last 10 digits from board serial + chr_count = board_usb_get_serial(_desc_str+1, 32); + if (chr_count > 12) { + _desc_str[1] = '0'; + _desc_str[2] = '2'; + for (uint8_t i=0; i<10; i++) { + _desc_str[3+i] = _desc_str[chr_count-9+i]; + } + chr_count=12; + } + break; default: // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors diff --git a/usb_device.h b/usb_device.h index 8d65b4b..9d0b662 100644 --- a/usb_device.h +++ b/usb_device.h @@ -1,7 +1,7 @@ #ifndef USB_DEVICE_H_ #define USB_DEVICE_H_ -#define DESC_CFG_MAX TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + CFG_TUD_HID*TUD_HID_DESC_LEN +#define DESC_CFG_MAX TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_CDC_NCM_DESC_LEN + CFG_TUD_HID*TUD_HID_DESC_LEN #define USB_PID 0xE2BD #define USB_VID 0xCEC0 @@ -10,12 +10,17 @@ #define EPNUM_CDC_NOTIF 0x81 #define EPNUM_CDC_OUT 0x02 #define EPNUM_CDC_IN 0x82 -#define EPNUM_HID 0x83 +#define EPNUM_NCM_NOTIF 0x83 +#define EPNUM_NCM_OUT 0x03 +#define EPNUM_NCM_IN 0x84 +#define EPNUM_HID 0x85 enum { ITF_NUM_CDC=0, ITF_NUM_CDC_DATA, + ITF_NUM_NCM, + ITF_NUM_NCM_DATA, ITF_NUM_HID }; diff --git a/usb_host.c b/usb_host.c index 4e28179..b9828c6 100644 --- a/usb_host.c +++ b/usb_host.c @@ -4,6 +4,7 @@ #include #include "pico/stdlib.h" +#include "pico/multicore.h" #include "pio_usb.h" #include "tusb.h" @@ -50,8 +51,15 @@ static void usb_host_init(void) { } void usb_host_main(void) { + // allow other core to pause host process - required for saving to flash + multicore_lockout_victim_init(); + usb_host_init(); + if (load_rgb_config()) { + tud_cdc_write_str("found previous RGB configuration\n"); + } + while (true) { switch ( host_state ) { case HOST_MOUNTED: @@ -242,7 +250,7 @@ static void report_desc_init(struct report_desc *descriptor) { descriptor->next = NULL; } -// free memory and teardown usb->bt report ID mappings for report descriptor struct +// free memory for report descriptor struct static void report_desc_free(struct report_desc *descriptor) { if (descriptor != NULL) { if (descriptors == descriptor) { diff --git a/usb_server.c b/usb_server.c new file mode 100644 index 0000000..8f4b010 --- /dev/null +++ b/usb_server.c @@ -0,0 +1,234 @@ +#include "bsp/board_api.h" +#include "tusb.h" +#include "dhserver.h" +#include "dnserver.h" +#include "lwip/apps/httpd.h" +#include "lwip/init.h" +#include "lwip/timeouts.h" + +#include "websocket.h" +#include "usb_device.h" +#include "hyperx_elite2.h" + +#include "usb_server.h" + +// ip address of the USB server and dhcp address(es) it will give out +static const ip4_addr_t usb_ip = INIT_IP4(192, 168, 226, 1); +static const ip4_addr_t usb_netmask = INIT_IP4(255, 255, 255, 0); +static const ip4_addr_t usb_gateway = INIT_IP4(0, 0, 0, 0); +static dhcp_entry_t dhcp_clients[] = { + { {0}, INIT_IP4(192, 168, 226, 2), 4*3600 }, +}; +static const dhcp_config_t dhcp_config = { + .router = INIT_IP4(0,0,0,0), + .port = 67, + .dns = usb_ip, + "usb", + TU_ARRAY_SIZE(dhcp_clients), + dhcp_clients +}; + +static struct netif netif_data; + +static err_t netif_init_cb(struct netif *netif); +static err_t ip4_output_fn(struct netif *netif, struct pbuf *p, const ip4_addr_t *addr); +static err_t linkoutput_fn(struct netif *netif, struct pbuf *p); +static void usb_server_netif_link_cb(struct netif *netif); +static bool dns_request(const char *name, ip4_addr_t *addr); + +// called to initialize the USB network and HTTP server +void usb_server_init(void) { + struct netif *netif = &netif_data; + + lwip_init(); + + // use 02 followed by last 10 digits from board serial as MAC address + uint8_t board_serial[16]; + size_t count = board_get_unique_id(board_serial, sizeof(board_serial)); + netif->hwaddr_len = 6; + memcpy(netif->hwaddr, &board_serial[count-6], 6); + netif->hwaddr[0]=0x02; + // lwip virtual MAC address msut differ from the host MAC - toggle last bit + netif->hwaddr[5] ^= 0x01; + + netif = netif_add(netif, &usb_ip, &usb_netmask, &usb_gateway, NULL, netif_init_cb, ethernet_input); + netif_set_default(netif); + +#if LWIP_NETIF_LINK_CALLBACK + netif_set_link_callback(netif, usb_server_netif_link_cb); + netif_set_link_up(netif); +#else + //tud_network_link_state(BOARD_TUD_RHPORT, true); + // unsupported in current version - add when Pico SDK updates TinyUSB version +#endif + + while (!netif_is_up(&netif_data)); + while (dhserv_init(&dhcp_config) != ERR_OK); + while (dnserv_init(IP_ADDR_ANY, 53, dns_request) != ERR_OK); + + httpd_init(); + + // start the websocket server + ws_server_init(); + ws_set_open_handler(ws_open_handler); + ws_set_receive_handler(ws_receive_handler); +} + +// callback when data is received on USB network +// return true if the packet buffer was accepted +bool tud_network_recv_cb(const uint8_t *src, uint16_t size) { + struct netif *netif = &netif_data; + + if (size) { + struct pbuf *p = pbuf_alloc(PBUF_RAW, size, PBUF_POOL); + + if (p == NULL) { + printf("ERROR: Failed to allocate pbuf of size %d\n", size); + return false; + } + + /* Copy buf to pbuf */ + pbuf_take(p, src, size); + + // Surrender ownership of our pbuf unless there was an error + // Only call pbuf_free if not Ok else it will panic with "pbuf_free: p->ref > 0" + // or steal it from whatever took ownership of it with undefined consequences. + // See: https://savannah.nongnu.org/patch/index.php?10121 + if (netif->input(p, netif) != ERR_OK) { + printf("ERROR: netif input failed\n"); + pbuf_free(p); + } + // Signal tinyusb that the current frame has been processed. + tud_network_recv_renew(); + } + + return true; +} + +// callback when network interface is initialized +// save the network configuration +static err_t netif_init_cb(struct netif *netif) { + LWIP_ASSERT("netif != NULL", (netif != NULL)); + netif->mtu = CFG_TUD_NET_MTU; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP | NETIF_FLAG_UP; + netif->state = NULL; + netif->name[0] = 'E'; + netif->name[1] = 'X'; + netif->linkoutput = linkoutput_fn; + netif->output = ip4_output_fn; + + return ERR_OK; +} + +// callback for sending data over USB network interface +// copy from network stack packet pointer to dst +uint16_t tud_network_xmit_cb(uint8_t *dst, void *ref, uint16_t arg) { + struct pbuf *p = (struct pbuf *) ref; + + (void) arg; /* unused for this example */ + + return pbuf_copy_partial(p, dst, p->tot_len, 0); +} + +static err_t ip4_output_fn(struct netif *netif, struct pbuf *p, const ip4_addr_t *addr) { + return etharp_output(netif, p, addr); +} + +static err_t linkoutput_fn(struct netif *netif, struct pbuf *p) { + (void) netif; + + for (;;) { + // if TinyUSB isn't ready, we must signal back to lwip that there is nothing we can do + if (!tud_ready()) + return ERR_USE; + + // if the network driver can accept another packet, we make it happen + if (tud_network_can_xmit(p->tot_len)) { + tud_network_xmit(p, 0 /* unused for this example */); + return ERR_OK; + } + + // transfer execution to TinyUSB in the hopes that it will finish transmitting the prior packet + tud_task(); + } +} + +// notify USB host about link state changes +static void usb_server_netif_link_cb(struct netif *netif) { + bool link_up = netif_is_link_up(netif); + //tud_network_link_state(BOARD_TUD_RHPORT, link_up); + // unsupported in current version - add when Pico SDK updates TinyUSB version +} + +// handle DNS requests and serve on designed domain +static bool dns_request(const char *name, ip4_addr_t *addr) { + if (0 == strcmp(name, "alloyelite2.usb")) { + *addr = usb_ip; + return true; + } + return false; +} + +// handler called when websocket connection is opened +const void ws_open_handler(struct ws_state * wss) { + (void) wss; + + // nothing to do +} + +// handler for data received on websocket connection +const void ws_receive_handler(uint8_t *data, uint16_t len) { + if (strncmp(data, "S,", 2) == 0) { + // set color command + parse_colors(&data[2], len-2); + } else if ( strncmp(data, "G,", 2) == 0) { + // get color comand + get_color(&data[2], len-2); + } else if ( strncmp(data, "F,", 2) == 0) { + // save to flash memory + save_rgb_config(); + } else if ( strncmp(data, "L,", 2) == 0) { + // load from flash memory + load_rgb_config(); + } +} + +// Pico specific routines needed by lwip +auto_init_mutex(lwip_mutex); +static int lwip_mutex_count = 0; + +sys_prot_t sys_arch_protect(void) +{ + uint32_t owner; + if (!mutex_try_enter(&lwip_mutex, &owner)) + { + if (owner != get_core_num()) + { + // Wait until other core releases mutex + mutex_enter_blocking(&lwip_mutex); + } + } + + lwip_mutex_count++; + + return 0; +} + +void sys_arch_unprotect(sys_prot_t pval) +{ + (void)pval; + + if (lwip_mutex_count) + { + lwip_mutex_count--; + if (!lwip_mutex_count) + { + mutex_exit(&lwip_mutex); + } + } +} + +uint32_t sys_now(void) +{ + return to_ms_since_boot( get_absolute_time() ); +} diff --git a/usb_server.h b/usb_server.h new file mode 100644 index 0000000..02c8e5f --- /dev/null +++ b/usb_server.h @@ -0,0 +1,12 @@ +#ifndef USB_SERVER_H_ +#define USB_SERVER_H_ + +#define INIT_IP4(a, b, c, d) \ + { PP_HTONL(LWIP_MAKEU32(a, b, c, d)) } + +void usb_server_init(void); +const void ws_receive_handler(uint8_t *data, uint16_t len); +const void ws_open_handler(struct ws_state * wss); + +#endif + diff --git a/websocket.c b/websocket.c new file mode 100644 index 0000000..9df358a --- /dev/null +++ b/websocket.c @@ -0,0 +1,470 @@ +#include + +#include "lwip/altcp.h" +#include "lwip/debug.h" +#include "mbedtls/base64.h" +#include "mbedtls/sha1.h" + +#include "websocket.h" + +static const char WS_GUID[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +static const char WS_RESPONSE[] = "HTTP/1.1 101 Switching Protocols\r\n" \ + "Upgrade: websocket\r\n" \ + "Connection: Upgrade\r\n" \ + "Sec-WebSocket-Accept: "; + +static uint8_t buf[WS_BUFFER_SIZE]; +static uint16_t buf_len=0; + +static tWSHandler ws_receive_cb = NULL; +static tWSOpenHandler ws_open_cb = NULL; + +static struct ws_state * ws_connections; +static uint8_t ws_num_conns = 0; + +static struct ws_state* ws_state_alloc(void); +static void ws_state_init(struct ws_state *wss); +static void ws_state_free(struct ws_state *wss); +static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port); +static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err); +static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err); +static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len); +static void ws_err (void *arg, err_t err); +static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss); +static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss, uint8_t abort_conn); +static err_t ws_poll(void *arg, struct altcp_pcb *pcb); +static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p); +static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p); +static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len); + +// allocate memory for ws_state instance +static struct ws_state * ws_state_alloc(void) { + struct ws_state *ret = WS_ALLOC_WS_STATE(); + + if ( ret != NULL) { + ws_state_init(ret); + if (ws_connections == NULL) { + ws_connections = ret; + } else { + struct ws_state *last; + for (last=ws_connections; last->next != NULL; last=last->next); + LWIP_ASSERT("last != NULL", last != NULL); + last->next = ret; + } + } + + return ret; +} + +// initiate ws_state instance +static void ws_state_init(struct ws_state *wss) { + memset(wss, 0, sizeof(struct ws_state)); + wss->active = false; +} + +// free memory from ws_state instance +static void ws_state_free(struct ws_state *wss) { + if (wss != NULL) { + if (ws_connections == wss) { + ws_connections = wss->next; + } else { + struct ws_state * last; + for (last = ws_connections; last->next != NULL; last = last->next) { + if (last->next == wss) { + last->next = wss->next; + break; + } + } + } + mem_free(wss); + } +} + +// initiate websocket server on specified pcb +static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port) { + err_t err; + + if (pcb) { + altcp_setprio(pcb, TCP_PRIO_MIN); + err = altcp_bind(pcb, IP_ANY_TYPE, port); + LWIP_UNUSED_ARG(err); + LWIP_ASSERT("ws_server_init: tcp_bind failed", err == ERR_OK); + pcb = altcp_listen(pcb); + LWIP_ASSERT("ws_server_init: tcp_listen failed", pcb != NULL); + altcp_accept(pcb, ws_accept); + } +} + +// initiate a websocket server +void ws_server_init(void) { + struct altcp_pcb *pcb = altcp_tcp_new_ip_type(IPADDR_TYPE_ANY); + LWIP_ASSERT("ws_server_init: tcp_new failed", pcb != NULL); + ws_server_init_pcb(pcb, WS_PORT); +} + +// set ws_receive_handler +void ws_set_receive_handler( tWSHandler ws_handler) +{ + ws_receive_cb = ws_handler; +} + +// set ws_open_handler +void ws_set_open_handler( tWSOpenHandler ws_handler) +{ + ws_open_cb = ws_handler; +} + +// callback for accepted websocket connection +static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err) { + struct ws_state *wss; + LWIP_UNUSED_ARG(err); + LWIP_UNUSED_ARG(arg); + LWIP_DEBUGF(WS_DEBUG, ("ws_accept %p / %p\n", (void *)pcb, arg)); + + if ((err != ERR_OK) || (pcb == NULL)) { + return ERR_VAL; + } + + // create new ws_state object + wss = ws_state_alloc(); + if (wss == NULL) { + LWIP_DEBUGF(WS_DEBUG, ("ws_accept: Out of memory, RST\n")); + return ERR_MEM; + } + wss->pcb = pcb; + + // make ws_state object the argument of callbacks + altcp_arg(pcb, wss); + + // register callbacks for tcp events + altcp_recv(pcb, ws_recv); + altcp_sent(pcb, ws_sent); + altcp_poll(pcb, ws_poll, WS_POLL_INTERVAL); + altcp_err(pcb, ws_err); + + return ERR_OK; +} + +// call when data is received +static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) { + struct ws_state *wss = (struct ws_state *) arg; + + if ((err != ERR_OK) || (p == NULL) || (wss == NULL)) { + // error or closed by client + if (p != NULL) { + // inform TCP that we have taken the data + altcp_recved(pcb, p->tot_len); + pbuf_free(p); + } + if (wss == NULL) { + // should not occur + LWIP_DEBUGF(WS_DEBUG, ("Error, ws_recv: wss is NULL, close\n")); + } + ws_close_conn(pcb, wss); + return ERR_OK; + } + + if (wss->active) { + // process websocket message + err = ws_read(pcb, wss, p); + } else { + // init websocket connection + LWIP_DEBUGF(WS_DEBUG, ("ws_recv: websocket inactive, checking for handshake\n")); + + err = ws_handshake(pcb, wss, p); + } + + // inform TCP that we have taken the data. + altcp_recved(pcb, p->tot_len); + pbuf_free(p); + + if (err == ERR_CLSD) { + ws_close_conn(pcb, wss); + } + + return ERR_OK; +} + +// called when data has been sent over the websocket +static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len) { + (void) pcb; + + struct ws_state *wss = (struct ws_state *)arg; + + LWIP_DEBUGF(WS_DEBUG | LWIP_DBG_TRACE, ("ws_sent %p\n", (void*) pcb)); + + LWIP_UNUSED_ARG(len); + + if (wss == NULL) { + return ERR_OK; + } + + wss->retries = 0; + + return ERR_OK; +} + +// called when there is a websocket error +static void ws_err (void *arg, err_t err) { + struct ws_state *wss = (struct ws_state *) arg; + LWIP_UNUSED_ARG(err); + + LWIP_DEBUGF(WS_DEBUG, ("ws_err: %s", lwip_strerr(err))); + + if (wss != NULL) { + ws_state_free(wss); + } +} + +// initiate close of connection +static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss) { + return ws_close_or_abort_conn(pcb, wss, 0); +} + +// call when closing connection or connection was aborted +static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss, + uint8_t abort_conn) { + + err_t err; + LWIP_DEBUGF(WS_DEBUG, ("Closing connection %p\n", (void *)pcb)); + + // clear callbacks + altcp_arg(pcb, NULL); + altcp_recv(pcb, NULL); + altcp_sent(pcb, NULL); + altcp_poll(pcb, NULL, 0); + altcp_err(pcb, NULL); + + // remove and free memory from ws_state object + if (wss != NULL) { + ws_state_free(wss); + } + + if (abort_conn) { + altcp_abort(pcb); + return ERR_OK; + } + err = altcp_close(pcb); + if (err != ERR_OK) { + LWIP_DEBUGF(WS_DEBUG, ("Error %d closing %p\n", err, (void *)pcb)); + // error closing, try again later in poll + altcp_poll(pcb, ws_poll, WS_POLL_INTERVAL); + } + return err; +} + +// callback for polling process +static err_t ws_poll(void *arg, struct altcp_pcb *pcb) { + struct ws_state *wss = (struct ws_state *) arg; + if (wss == NULL) { + err_t closed; + LWIP_DEBUGF(WS_DEBUG, ("ws_poll: arg is NULL, close\n")); + closed = ws_close_conn(pcb, NULL); + LWIP_UNUSED_ARG(closed); + if (closed == ERR_MEM) { + altcp_abort(pcb); + return ERR_ABRT; + } + return ERR_OK; + } else { + wss->retries++; + if (wss->retries == WS_MAX_RETRIES) { + LWIP_DEBUGF(WS_DEBUG, ("ws_poll: too may retries, close\n")); + ws_close_conn(pcb, wss); + return ERR_OK; + } + } + + return ERR_OK; +} + +// check for and complete handshake with client +static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p){ + uint8_t *data = (uint8_t *) p->payload; + uint16_t len = p->len; + + // check if client is initiating a websocket connecttion + if (strstr(data, "Upgrade: websocket")) { + LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: received websocket upgrade request\n")); + + // search for websocket security key + char *key_start = strstr(data, "Sec-WebSocket-Key: "); + + if (key_start) { + key_start += 19; + const char *key_end = strstr(key_start, "\r\n"); + if (key_end) { + char key[64]; + uint16_t key_len = key_end-key_start; + if ( (key_len>0) && (key_len + sizeof(WS_GUID) < sizeof(key)) ) { + // create response key by concatenating with websocket GUID, + // taking SHA1 hash, then encoding in base 64 + strncpy(key, key_start, key_len); + strlcpy(&key[key_len], WS_GUID, sizeof(key)-key_len); + + key_len += sizeof(WS_GUID)-1; + unsigned char key_sha1[20]; + unsigned char key_base64[29]; + size_t encoded_len; + mbedtls_sha1( (unsigned char *) key, key_len, key_sha1); + mbedtls_base64_encode( key_base64, 29, &encoded_len, key_sha1, 20); + + // create response packet with encoded response key + unsigned char response[sizeof(WS_RESPONSE) + sizeof(key_base64)+3]; + size_t count = sprintf(response, "%s%s\r\n\r\n", WS_RESPONSE, key_base64); + + // send completed data packet + LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: sending response\n")); + if(altcp_write(pcb, response, count, TCP_WRITE_FLAG_COPY) == ERR_OK) { + wss->active = true; + } + + if (ws_open_cb != NULL) { + ws_open_cb(wss); + } + + return ERR_OK; + } + } + + LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: key overflow\n")); + return ERR_MEM; + } else { + LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: key not received\n")); + return ERR_ARG; + } + } + + LWIP_DEBUGF(WS_DEBUG, ("ws_handshake: not a websocket request\n")); + return ERR_ARG; +} + +// handle reading of websocket data and pass to ws_receive_cb +static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p) { + (void) pcb; + uint8_t *data = (uint8_t *) p->payload; + uint16_t len = p->len; + + if (data != NULL && len > 1) { + // successful read, reset timeout + wss->retries = 0; + + uint8_t fin = data[0] & 0x80; + uint8_t opcode = data[0] & 0x0F; + uint8_t masked = data[1] & 0x80; + uint16_t msg_len = data[1] & 0x7F; + uint8_t *msg; + + switch (msg_len) { + case 126: // next two bytes are length + memcpy(&msg_len, &data[2], 2); + if (len >= 8) { + msg = &data[8]; + } + break; + case 127: // next four bytes are length + // lwIP's pbuf only handles 16-bit lengths, so error + LWIP_DEBUGF(WS_DEBUG, ("ws_read: received 64-bit length %u\n", msg_len)); + return ERR_MEM; + default: + if (len >= 6) { + msg = &data[6]; + } + break; + } + switch (opcode) { + case OP_CONT: + LWIP_DEBUGF(WS_DEBUG, ("ws_read: received continuation frame\n")); + case OP_TEXT: + LWIP_DEBUGF(WS_DEBUG, ("ws_read: received text data\n")); + case OP_BINARY: + LWIP_DEBUGF(WS_DEBUG, ("ws_read: decoding data, len=%u\n", msg_len)); + if (msg && ws_receive_cb != NULL) { + // unmask the data if mask bit is received + if (masked) { + uint8_t *mask = &data[2]; + + for (int i=0; i WS_BUFFER_SIZE) { + LWIP_DEBUGF(WS_DEBUG, ("ws_read: message exceeds buffer size %u+%u\n", buf_len, msg_len)); + return ERR_MEM; + } + + memcpy(&buf[buf_len], msg, msg_len); + buf_len += msg_len; + + if (fin) { // last packet in message, process completed message + ws_receive_cb(buf, buf_len); + } + } + break; + case OP_CLOSE: + LWIP_DEBUGF(WS_DEBUG, ("ws_read: close request")); + return ERR_CLSD; + case OP_PING: + // control frames cannot exceed 125 bytes in length + if (msg && msg_len <= 125) { + // send back a pong + uint8_t pong[2+msg_len]; + pong[0]=0x8A; + pong[1]=msg_len; + memcpy(&pong[2], msg, msg_len); + + return ws_send(wss, pong, msg_len+2); + } + return ERR_ARG; + case OP_PONG: // no response required for pong + return ERR_OK; + default: + LWIP_DEBUGF(WS_DEBUG, ("ws_read: invalid opcode %02X\n", opcode)); + return ERR_ARG; + } + + return ERR_OK; + } + LWIP_DEBUGF(WS_DEBUG, ("ws_read: received empty payload\n")); + return ERR_VAL; +} + +static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len) { + uint8_t buf[128]; + buf[0] = 0x81; + buf[1] = len & 0x7F; + memcpy(&buf[2], data, len); + + err_t err; + err = altcp_write(wss->pcb, buf, len+2, TCP_WRITE_FLAG_COPY); + if (err == ERR_OK) { + altcp_output(wss->pcb); + } + + return err; +} + +void ws_send_all(uint8_t *data, uint16_t len) { + // send message to all connections + if (ws_connections != NULL) { + struct ws_state *wss; + err_t err; + for (wss=ws_connections; wss != NULL; wss=wss->next) { + err = ws_send(wss, data, len); + if (err != ERR_OK ) { + LWIP_DEBUGF(WS_DEBUG, ("ws_send_all: error sending to %p\n", wss)); + } + } + } +} diff --git a/websocket.h b/websocket.h new file mode 100644 index 0000000..adeb8cc --- /dev/null +++ b/websocket.h @@ -0,0 +1,36 @@ +#ifndef WEBSOCKET_H_ +#define WEBSOCKET_H_ + +#define WS_PORT 8080 +#define WS_TIMEOUT 10 +#define WS_DEBUG LWIP_DBG_ON +#define WS_MAX_RETRIES 10 +#define WS_POLL_INTERVAL 60 // WS_POLL_INTERVAL/2 seconds +#define WS_MAX_CONN 4 +#define WS_BUFFER_SIZE 512 + +#define OP_CONT 0x00 +#define OP_TEXT 0x01 +#define OP_BINARY 0x02 +#define OP_CLOSE 0x08 +#define OP_PING 0x09 +#define OP_PONG 0x0A + +struct ws_state { + bool active; + uint8_t retries; + struct altcp_pcb *pcb; + struct ws_state *next; +}; + +#define WS_ALLOC_WS_STATE() (struct ws_state *)mem_malloc(sizeof(struct ws_state)) + +typedef void (* tWSHandler ) (uint8_t *data, uint16_t len); +typedef void (* tWSOpenHandler ) (struct ws_state * wss); + +void ws_server_init(void); +void ws_send_all(uint8_t *data, uint16_t len); +void ws_set_receive_handler( tWSHandler ws_handler); +void ws_set_open_handler( tWSOpenHandler ws_handler); + +#endif