apply astylerc

This commit is contained in:
technyon
2025-08-13 17:04:20 +02:00
parent b575f23df5
commit b6cfea25f5
35 changed files with 2613 additions and 2403 deletions

View File

@@ -5,7 +5,7 @@
#define NUKI_HUB_VERSION "9.12" #define NUKI_HUB_VERSION "9.12"
#define NUKI_HUB_VERSION_INT (uint32_t)912 #define NUKI_HUB_VERSION_INT (uint32_t)912
#define NUKI_HUB_BUILD "unknownbuildnr" #define NUKI_HUB_BUILD "unknownbuildnr"
#define NUKI_HUB_DATE "2025-07-21" #define NUKI_HUB_DATE "2025-08-13"
#define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest" #define GITHUB_LATEST_RELEASE_URL (char*)"https://github.com/technyon/nuki_hub/releases/latest"
#define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json" #define GITHUB_OTA_MANIFEST_URL (char*)"https://raw.githubusercontent.com/technyon/nuki_hub/binary/ota/manifest.json"

View File

@@ -85,28 +85,28 @@ private:
GpioAction IRAM_ATTR getGpioAction(const PinRole& role) const; GpioAction IRAM_ATTR getGpioAction(const PinRole& role) const;
static void IRAM_ATTR isrOnTimer(); static void IRAM_ATTR isrOnTimer();
#if defined(CONFIG_IDF_TARGET_ESP32C3) #if defined(CONFIG_IDF_TARGET_ESP32C3)
//Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf //Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf
const std::vector<uint8_t> _availablePins = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19, 20, 21 }; const std::vector<uint8_t> _availablePins = { 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 18, 19, 20, 21 };
#elif defined(CONFIG_IDF_TARGET_ESP32S3) #elif defined(CONFIG_IDF_TARGET_ESP32S3)
//Based on https://github.com/atomic14/esp32-s3-pinouts?tab=readme-ov-file and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf //Based on https://github.com/atomic14/esp32-s3-pinouts?tab=readme-ov-file and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf
const std::vector<uint8_t> _availablePins = { 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 38, 39, 40, 41, 42 }; const std::vector<uint8_t> _availablePins = { 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 38, 39, 40, 41, 42 };
#elif defined(CONFIG_IDF_TARGET_ESP32C5) #elif defined(CONFIG_IDF_TARGET_ESP32C5)
//Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c5_datasheet_en.pdf //Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c5/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c5_datasheet_en.pdf
const std::vector<uint8_t> _availablePins = { 0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 15, 23, 24, 26 }; const std::vector<uint8_t> _availablePins = { 0, 1, 3, 4, 5, 6, 8, 9, 10, 11, 12, 15, 23, 24, 26 };
#elif defined(CONFIG_IDF_TARGET_ESP32C6) #elif defined(CONFIG_IDF_TARGET_ESP32C6)
//Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf //Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32c6/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-c6_datasheet_en.pdf
const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23 };
#elif defined(CONFIG_IDF_TARGET_ESP32P4) #elif defined(CONFIG_IDF_TARGET_ESP32P4)
//Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/gpio.html //Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-reference/peripherals/gpio.html
const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33 }; const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33 };
#elif defined(CONFIG_IDF_TARGET_ESP32H2) #elif defined(CONFIG_IDF_TARGET_ESP32H2)
//Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf //Based on https://docs.espressif.com/projects/esp-idf/en/latest/esp32h2/api-reference/peripherals/gpio.html and https://www.espressif.com/sites/default/files/documentation/esp32-h2_datasheet_en.pdf
const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 22, 23, 24, 25, 26, 27 }; const std::vector<uint8_t> _availablePins = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 14, 22, 23, 24, 25, 26, 27 };
#else #else
//Based on https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ //Based on https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
const std::vector<uint8_t> _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 }; const std::vector<uint8_t> _availablePins = { 2, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 32, 33 };
#endif #endif
const std::vector<PinRole> _allRoles = const std::vector<PinRole> _allRoles =
{ {
PinRole::Disabled, PinRole::Disabled,

View File

@@ -25,7 +25,8 @@ HomeAssistantDiscovery::HomeAssistantDiscovery(NetworkDevice* device, Preference
char uidString[20]; char uidString[20];
itoa(_preferences->getUInt(preference_device_id_lock, 0), uidString, 10); itoa(_preferences->getUInt(preference_device_id_lock, 0), uidString, 10);
removeHASSConfig(uidString); removeHASSConfig(uidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);
@@ -41,13 +42,15 @@ HomeAssistantDiscovery::HomeAssistantDiscovery(NetworkDevice* device, Preference
char uidString[20]; char uidString[20];
itoa(_preferences->getUInt(preference_device_id_lock, 0), uidString, 10); itoa(_preferences->getUInt(preference_device_id_lock, 0), uidString, 10);
removeHASSConfig(uidString); removeHASSConfig(uidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);
itoa(savedDevId, uidString, 10); itoa(savedDevId, uidString, 10);
removeHASSConfig(uidString); removeHASSConfig(uidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);
@@ -102,7 +105,8 @@ void HomeAssistantDiscovery::setupHASS(int type, uint32_t nukiId, char* nukiName
void HomeAssistantDiscovery::disableHASS() void HomeAssistantDiscovery::disableHASS()
{ {
removeHASSConfig(_nukiHubUidString); removeHASSConfig(_nukiHubUidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);
@@ -113,7 +117,8 @@ void HomeAssistantDiscovery::disableHASS()
{ {
itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16); itoa(_preferences->getUInt(preference_nuki_id_lock, 0), uidString, 16);
removeHASSConfig(uidString); removeHASSConfig(uidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);
@@ -122,7 +127,8 @@ void HomeAssistantDiscovery::disableHASS()
{ {
itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16); itoa(_preferences->getUInt(preference_nuki_id_opener, 0), uidString, 16);
removeHASSConfig(uidString); removeHASSConfig(uidString);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(3000 / portTICK_PERIOD_MS); vTaskDelay(3000 / portTICK_PERIOD_MS);

View File

@@ -151,7 +151,8 @@ int ImportExport::checkDuoAuth(PsychicRequest *request)
} }
} }
if (request->hasParam("id")) { if (request->hasParam("id"))
{
const PsychicWebParameter* p = request->getParam("id"); const PsychicWebParameter* p = request->getParam("id");
String id = p->value(); String id = p->value();
DuoAuthLib duoAuth; DuoAuthLib duoAuth;
@@ -337,13 +338,15 @@ bool ImportExport::checkBypass(String bypass)
void ImportExport::exportHttpsJson(JsonDocument &json) void ImportExport::exportHttpsJson(JsonDocument &json)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.crt"); File file = SPIFFS.open("/http_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.crt not found"); Log->println("http_ssl.crt not found");
} }
else else
@@ -359,13 +362,15 @@ void ImportExport::exportHttpsJson(JsonDocument &json)
} }
} }
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.key"); File file = SPIFFS.open("/http_ssl.key");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.key not found"); Log->println("http_ssl.key not found");
} }
else else
@@ -384,13 +389,15 @@ void ImportExport::exportHttpsJson(JsonDocument &json)
void ImportExport::exportMqttsJson(JsonDocument &json) void ImportExport::exportMqttsJson(JsonDocument &json)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.ca"); File file = SPIFFS.open("/mqtt_ssl.ca");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.ca not found"); Log->println("mqtt_ssl.ca not found");
} }
else else
@@ -406,13 +413,15 @@ void ImportExport::exportMqttsJson(JsonDocument &json)
} }
} }
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.crt"); File file = SPIFFS.open("/mqtt_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.crt not found"); Log->println("mqtt_ssl.crt not found");
} }
else else
@@ -428,13 +437,15 @@ void ImportExport::exportMqttsJson(JsonDocument &json)
} }
} }
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.key"); File file = SPIFFS.open("/mqtt_ssl.key");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.key not found"); Log->println("mqtt_ssl.key not found");
} }
else else
@@ -909,7 +920,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
nukiBlePref.end(); nukiBlePref.end();
if(!doc["mqtt_ssl.ca"].isNull()) if(!doc["mqtt_ssl.ca"].isNull())
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
json["mqtt_ssl.ca"] = "error"; json["mqtt_ssl.ca"] = "error";
} }
@@ -918,7 +930,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
if(doc["mqtt_ssl.ca"].as<String>().length() > 0) if(doc["mqtt_ssl.ca"].as<String>().length() > 0)
{ {
File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.ca for writing"); Log->println("Failed to open /mqtt_ssl.ca for writing");
json["mqtt_ssl.ca"] = "error"; json["mqtt_ssl.ca"] = "error";
} }
@@ -938,7 +951,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.ca")) { if (!SPIFFS.remove("/mqtt_ssl.ca"))
{
Log->println("Failed to delete /mqtt_ssl.ca"); Log->println("Failed to delete /mqtt_ssl.ca");
json["mqtt_ssl.crt"] = "error"; json["mqtt_ssl.crt"] = "error";
} }
@@ -951,7 +965,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
if(!doc["mqtt_ssl.crt"].isNull()) if(!doc["mqtt_ssl.crt"].isNull())
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
json["mqtt_ssl.crt"] = "error"; json["mqtt_ssl.crt"] = "error";
} }
@@ -960,7 +975,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
if(doc["mqtt_ssl.crt"].as<String>().length() > 0) if(doc["mqtt_ssl.crt"].as<String>().length() > 0)
{ {
File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.crt for writing"); Log->println("Failed to open /mqtt_ssl.crt for writing");
json["mqtt_ssl.crt"] = "error"; json["mqtt_ssl.crt"] = "error";
} }
@@ -980,7 +996,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.crt")) { if (!SPIFFS.remove("/mqtt_ssl.crt"))
{
Log->println("Failed to delete /mqtt_ssl.crt"); Log->println("Failed to delete /mqtt_ssl.crt");
json["mqtt_ssl.crt"] = "error"; json["mqtt_ssl.crt"] = "error";
} }
@@ -993,7 +1010,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
if(!doc["mqtt_ssl.key"].isNull()) if(!doc["mqtt_ssl.key"].isNull())
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
json["mqtt_ssl.key"] = "error"; json["mqtt_ssl.key"] = "error";
} }
@@ -1002,7 +1020,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
if(doc["mqtt_ssl.key"].as<String>().length() > 0) if(doc["mqtt_ssl.key"].as<String>().length() > 0)
{ {
File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.key for writing"); Log->println("Failed to open /mqtt_ssl.key for writing");
json["mqtt_ssl.key"] = "error"; json["mqtt_ssl.key"] = "error";
} }
@@ -1022,7 +1041,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.key")) { if (!SPIFFS.remove("/mqtt_ssl.key"))
{
Log->println("Failed to delete /mqtt_ssl.key"); Log->println("Failed to delete /mqtt_ssl.key");
} }
else else
@@ -1034,7 +1054,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
if(!doc["http_ssl.crt"].isNull()) if(!doc["http_ssl.crt"].isNull())
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
json["http_ssl.crt"] = "error"; json["http_ssl.crt"] = "error";
} }
@@ -1043,7 +1064,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
if(doc["http_ssl.crt"].as<String>().length() > 0) if(doc["http_ssl.crt"].as<String>().length() > 0)
{ {
File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /http_ssl.crt for writing"); Log->println("Failed to open /http_ssl.crt for writing");
json["http_ssl.crt"] = "error"; json["http_ssl.crt"] = "error";
} }
@@ -1063,7 +1085,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
else else
{ {
if (!SPIFFS.remove("/http_ssl.crt")) { if (!SPIFFS.remove("/http_ssl.crt"))
{
Log->println("Failed to delete /http_ssl.crt"); Log->println("Failed to delete /http_ssl.crt");
json["http_ssl.crt"] = "error"; json["http_ssl.crt"] = "error";
} }
@@ -1076,7 +1099,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
if(!doc["http_ssl.key"].isNull()) if(!doc["http_ssl.key"].isNull())
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
json["http_ssl.key"] = "error"; json["http_ssl.key"] = "error";
} }
@@ -1085,7 +1109,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
if(doc["http_ssl.key"].as<String>().length() > 0) if(doc["http_ssl.key"].as<String>().length() > 0)
{ {
File file = SPIFFS.open("/http_ssl.key", FILE_WRITE); File file = SPIFFS.open("/http_ssl.key", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /http_ssl.key for writing"); Log->println("Failed to open /http_ssl.key for writing");
json["http_ssl.key"] = "error"; json["http_ssl.key"] = "error";
} }
@@ -1105,7 +1130,8 @@ JsonDocument ImportExport::importJson(JsonDocument &doc)
} }
else else
{ {
if (!SPIFFS.remove("/http_ssl.key")) { if (!SPIFFS.remove("/http_ssl.key"))
{
Log->println("Failed to delete /http_ssl.key"); Log->println("Failed to delete /http_ssl.key");
json["http_ssl.key"] = "error"; json["http_ssl.key"] = "error";
} }

View File

@@ -49,9 +49,9 @@ void NukiNetwork::setupDevice()
if(hardwareDetect == 0) if(hardwareDetect == 0)
{ {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
hardwareDetect = 1; hardwareDetect = 1;
#else #else
hardwareDetect = 11; hardwareDetect = 11;
_preferences->putInt(preference_network_custom_addr, 1); _preferences->putInt(preference_network_custom_addr, 1);
_preferences->putInt(preference_network_custom_cs, 8); _preferences->putInt(preference_network_custom_cs, 8);
@@ -61,13 +61,13 @@ void NukiNetwork::setupDevice()
_preferences->putInt(preference_network_custom_miso, 12); _preferences->putInt(preference_network_custom_miso, 12);
_preferences->putInt(preference_network_custom_mosi, 13); _preferences->putInt(preference_network_custom_mosi, 13);
_preferences->putBool(preference_ntw_reconfigure, true); _preferences->putBool(preference_ntw_reconfigure, true);
#endif #endif
_preferences->putInt(preference_network_hardware, hardwareDetect); _preferences->putInt(preference_network_hardware, hardwareDetect);
} }
if(wifiFallback == true) if(wifiFallback == true)
{ {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_firstBootAfterDeviceChange) if(!_firstBootAfterDeviceChange)
{ {
Log->println("Failed to connect to network. Wi-Fi fallback is disabled, rebooting."); Log->println("Failed to connect to network. Wi-Fi fallback is disabled, rebooting.");
@@ -78,7 +78,7 @@ void NukiNetwork::setupDevice()
Log->println("Switching to Wi-Fi device as fallback."); Log->println("Switching to Wi-Fi device as fallback.");
_networkDeviceType = NetworkDeviceType::WiFi; _networkDeviceType = NetworkDeviceType::WiFi;
#else #else
int custEth = _preferences->getInt(preference_network_custom_phy, 0); int custEth = _preferences->getInt(preference_network_custom_phy, 0);
if(custEth<3) if(custEth<3)
@@ -91,7 +91,7 @@ void NukiNetwork::setupDevice()
} }
_preferences->putInt(preference_network_custom_phy, custEth); _preferences->putInt(preference_network_custom_phy, custEth);
_preferences->putBool(preference_ntw_reconfigure, true); _preferences->putBool(preference_ntw_reconfigure, true);
#endif #endif
} }
else else
{ {
@@ -103,9 +103,9 @@ void NukiNetwork::setupDevice()
Log->print("Network device: "); Log->print("Network device: ");
Log->println(_device->deviceName()); Log->println(_device->deviceName());
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
_hadiscovery = new HomeAssistantDiscovery(_device, _preferences, _buffer, _bufferSize); _hadiscovery = new HomeAssistantDiscovery(_device, _preferences, _buffer, _bufferSize);
#endif #endif
} }
void NukiNetwork::reconfigureDevice() void NukiNetwork::reconfigureDevice()
@@ -156,11 +156,11 @@ bool NukiNetwork::isConnected()
bool NukiNetwork::mqttConnected() bool NukiNetwork::mqttConnected()
{ {
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
return _device->mqttConnected(); return _device->mqttConnected();
#else #else
return false; return false;
#endif #endif
} }
bool NukiNetwork::wifiConnected() bool NukiNetwork::wifiConnected()
@@ -387,7 +387,7 @@ void NukiNetwork::setMQTTConnectionSettings()
Log->print(":"); Log->print(":");
Log->println(_mqttPort); Log->println(_mqttPort);
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
_device->mqttOnConnect([&](bool sessionPresent) _device->mqttOnConnect([&](bool sessionPresent)
{ {
onMqttConnect(sessionPresent); onMqttConnect(sessionPresent);
@@ -396,7 +396,7 @@ void NukiNetwork::setMQTTConnectionSettings()
{ {
onMqttDisconnect(reason); onMqttDisconnect(reason);
}); });
#endif #endif
} }
int NukiNetwork::getRestartServices() int NukiNetwork::getRestartServices()
@@ -476,7 +476,8 @@ bool NukiNetwork::update()
bool success = reconnect(); bool success = reconnect();
if(!success) if(!success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -488,7 +489,8 @@ bool NukiNetwork::update()
if(forceEnableWebServer && !_webEnabled) if(forceEnableWebServer && !_webEnabled)
{ {
forceEnableWebServer = false; forceEnableWebServer = false;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -498,7 +500,8 @@ bool NukiNetwork::update()
{ {
forceEnableWebServer = false; forceEnableWebServer = false;
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -513,13 +516,15 @@ bool NukiNetwork::update()
forceEnableWebServer = true; forceEnableWebServer = true;
} }
Log->println("Network timeout has been reached, restarting ..."); Log->println("Network timeout has been reached, restarting ...");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
restartEsp(RestartReason::NetworkTimeoutWatchdog); restartEsp(RestartReason::NetworkTimeoutWatchdog);
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -751,7 +756,8 @@ bool NukiNetwork::reconnect(bool force)
while(!_connectReplyReceived && espMillis() < timeout) while(!_connectReplyReceived && espMillis() < timeout)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -767,7 +773,8 @@ bool NukiNetwork::reconnect(bool force)
Log->println("MQTT connected"); Log->println("MQTT connected");
_mqttConnectedTs = millis(); _mqttConnectedTs = millis();
_mqttConnectionState = 1; _mqttConnectionState = 1;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -1015,7 +1022,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
{ {
Log->println("Restart requested via MQTT."); Log->println("Restart requested via MQTT.");
clearWifiFallback(); clearWifiFallback();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1071,7 +1079,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
Log->println("Updating to latest release version."); Log->println("Updating to latest release version.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1089,7 +1098,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
_preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL); _preferences->putString(preference_ota_updater_url, GITHUB_BETA_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_BETA_RELEASE_BINARY_URL);
Log->println("Updating to latest beta version."); Log->println("Updating to latest beta version.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1107,7 +1117,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
_preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL); _preferences->putString(preference_ota_updater_url, GITHUB_MASTER_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL);
Log->println("Updating to latest developmemt version."); Log->println("Updating to latest developmemt version.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1125,7 +1136,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL);
Log->println("Updating to latest release version."); Log->println("Updating to latest release version.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1165,7 +1177,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
_preferences->putBool(preference_webserver_enabled, false); _preferences->putBool(preference_webserver_enabled, false);
} }
clearWifiFallback(); clearWifiFallback();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1197,7 +1210,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
{ {
String jsonTotp = doc["totp"]; String jsonTotp = doc["totp"];
if (!_importExport->checkTOTP(&jsonTotp)) { if (!_importExport->checkTOTP(&jsonTotp))
{
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action_command_result, "{\"error\": \"totpIncorrect\"}", false); publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action_command_result, "{\"error\": \"totpIncorrect\"}", false);
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true); publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
return; return;
@@ -1219,7 +1233,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
while (duoResult == 2) while (duoResult == 2)
{ {
duoResult = _importExport->checkDuoApprove(); duoResult = _importExport->checkDuoApprove();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -1339,7 +1354,8 @@ void NukiNetwork::onMqttDataReceived(const char* topic, byte* payload, const uns
serializeJson(json, _buffer, _bufferSize); serializeJson(json, _buffer, _bufferSize);
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_json, _buffer, false); publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_json, _buffer, false);
publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true); publishString(_maintenancePathPrefix, mqtt_topic_nuki_hub_config_action, "--", true);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -1479,10 +1495,10 @@ void NukiNetwork::removeTopic(const String& mqttPath, const String& mqttTopic)
path.concat(mqttTopic); path.concat(mqttTopic);
publish(path.c_str(), "", true); publish(path.c_str(), "", true);
#ifdef DEBUG_NUKIHUB #ifdef DEBUG_NUKIHUB
Log->print("Removing MQTT topic: "); Log->print("Removing MQTT topic: ");
Log->println(path.c_str()); Log->println(path.c_str());
#endif #endif
} }
void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad) void NukiNetwork::setupHASS(int type, uint32_t nukiId, char* nukiName, const char* firmwareVersion, const char* hardwareVersion, bool hasDoorSensor, bool hasKeypad)

View File

@@ -43,9 +43,9 @@ public:
NetworkDevice* device(); NetworkDevice* device();
#ifdef NUKI_HUB_UPDATER #ifdef NUKI_HUB_UPDATER
explicit NukiNetwork(Preferences* preferences); explicit NukiNetwork(Preferences* preferences);
#else #else
explicit NukiNetwork(Preferences* preferences, Gpio* gpio, char* buffer, size_t bufferSize, ImportExport* importExport); explicit NukiNetwork(Preferences* preferences, Gpio* gpio, char* buffer, size_t bufferSize, ImportExport* importExport);
void registerMqttReceiver(MqttReceiver* receiver); void registerMqttReceiver(MqttReceiver* receiver);
@@ -93,7 +93,7 @@ public:
bool pathEquals(const char* prefix, const char* path, const char* referencePath); bool pathEquals(const char* prefix, const char* path, const char* referencePath);
uint16_t subscribe(const char* topic, uint8_t qos); uint16_t subscribe(const char* topic, uint8_t qos);
void addReconnectedCallback(std::function<void()> reconnectedCallback); void addReconnectedCallback(std::function<void()> reconnectedCallback);
#endif #endif
private: private:
void setupDevice(); void setupDevice();
void setMQTTConnectionSettings(); void setMQTTConnectionSettings();
@@ -115,7 +115,7 @@ private:
bool _firstBootAfterDeviceChange = false; bool _firstBootAfterDeviceChange = false;
bool _webEnabled = true; bool _webEnabled = true;
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total); static void onMqttDataReceivedCallback(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total); void onMqttDataReceived(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t& len, size_t& index, size_t& total);
void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length); void onMqttDataReceived(const char* topic, byte* payload, const unsigned int length);
@@ -173,5 +173,5 @@ private:
const size_t _bufferSize; const size_t _bufferSize;
int8_t _lastRssi = 127; int8_t _lastRssi = 127;
#endif #endif
}; };

View File

@@ -80,9 +80,9 @@ void NukiOpenerWrapper::readSettings()
if(pwrLvl >= 9) if(pwrLvl >= 9)
{ {
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
powerLevel = ESP_PWR_LVL_P9; powerLevel = ESP_PWR_LVL_P9;
#else #else
if(pwrLvl >= 20) if(pwrLvl >= 20)
{ {
powerLevel = ESP_PWR_LVL_P20; powerLevel = ESP_PWR_LVL_P20;
@@ -103,7 +103,7 @@ void NukiOpenerWrapper::readSettings()
{ {
powerLevel = ESP_PWR_LVL_P9; powerLevel = ESP_PWR_LVL_P9;
} }
#endif #endif
} }
else if(pwrLvl >= 6) else if(pwrLvl >= 6)
{ {
@@ -247,7 +247,8 @@ void NukiOpenerWrapper::update()
} }
else else
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -268,7 +269,8 @@ void NukiOpenerWrapper::update()
Log->print("No BLE beacon received from the opener for "); Log->print("No BLE beacon received from the opener for ");
Log->print((ts - lastReceivedBeaconTs) / 1000); Log->print((ts - lastReceivedBeaconTs) / 1000);
Log->println(" seconds, signalling to restart BLE controller."); Log->println(" seconds, signalling to restart BLE controller.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -304,7 +306,8 @@ void NukiOpenerWrapper::update()
_network->publishRetry(std::to_string(retryCount + 1)); _network->publishRetry(std::to_string(retryCount + 1));
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(_retryDelay / portTICK_PERIOD_MS); vTaskDelay(_retryDelay / portTICK_PERIOD_MS);
@@ -343,7 +346,8 @@ void NukiOpenerWrapper::update()
if(_statusUpdated) if(_statusUpdated)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -615,7 +619,8 @@ void NukiOpenerWrapper::updateBatteryState()
{ {
Log->print("Querying opener battery state: "); Log->print("Querying opener battery state: ");
result = _nukiOpener.requestBatteryReport(&_batteryReport); result = _nukiOpener.requestBatteryReport(&_batteryReport);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -790,7 +795,8 @@ void NukiOpenerWrapper::updateAuthData(bool retrieved)
if(result == Nuki::CmdResult::Success) if(result == Nuki::CmdResult::Success)
{ {
_waitAuthLogUpdateTs = espMillis() + 5000; _waitAuthLogUpdateTs = espMillis() + 5000;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -1020,7 +1026,8 @@ void NukiOpenerWrapper::updateAuth(bool retrieved)
{ {
Log->print("Querying opener authorization: "); Log->print("Querying opener authorization: ");
result = _nukiOpener.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH)); result = _nukiOpener.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -3062,7 +3069,8 @@ void NukiOpenerWrapper::onKeypadJsonCommandReceived(const char *value)
if(resultKp == Nuki::CmdResult::Success) if(resultKp == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -3431,7 +3439,8 @@ void NukiOpenerWrapper::onTimeControlCommandReceived(const char *value)
if(resultTc == Nuki::CmdResult::Success) if(resultTc == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -3890,7 +3899,8 @@ void NukiOpenerWrapper::onAuthCommandReceived(const char *value)
if(resultAuth == Nuki::CmdResult::Success) if(resultAuth == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);

View File

@@ -85,9 +85,9 @@ void NukiWrapper::readSettings()
if(pwrLvl >= 9) if(pwrLvl >= 9)
{ {
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
powerLevel = ESP_PWR_LVL_P9; powerLevel = ESP_PWR_LVL_P9;
#else #else
if(pwrLvl >= 20) if(pwrLvl >= 20)
{ {
powerLevel = ESP_PWR_LVL_P20; powerLevel = ESP_PWR_LVL_P20;
@@ -108,7 +108,7 @@ void NukiWrapper::readSettings()
{ {
powerLevel = ESP_PWR_LVL_P9; powerLevel = ESP_PWR_LVL_P9;
} }
#endif #endif
} }
else if(pwrLvl >= 6) else if(pwrLvl >= 6)
{ {
@@ -263,7 +263,8 @@ void NukiWrapper::update(bool reboot)
} }
else else
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -284,7 +285,8 @@ void NukiWrapper::update(bool reboot)
Log->print("No BLE beacon received from the lock for "); Log->print("No BLE beacon received from the lock for ");
Log->print((ts - lastReceivedBeaconTs) / 1000); Log->print((ts - lastReceivedBeaconTs) / 1000);
Log->println(" seconds, signalling to restart BLE controller."); Log->println(" seconds, signalling to restart BLE controller.");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -324,7 +326,8 @@ void NukiWrapper::update(bool reboot)
_network->publishRetry(std::to_string(retryCount + 1)); _network->publishRetry(std::to_string(retryCount + 1));
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(_retryDelay / portTICK_PERIOD_MS); vTaskDelay(_retryDelay / portTICK_PERIOD_MS);
@@ -367,7 +370,8 @@ void NukiWrapper::update(bool reboot)
if(_statusUpdated) if(_statusUpdated)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -821,8 +825,10 @@ void NukiWrapper::updateDebug()
Log->print("Result: "); Log->print("Result: ");
Log->println(result); Log->println(result);
count = 0; count = 0;
while (count < 5) { while (count < 5)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -843,8 +849,10 @@ void NukiWrapper::updateDebug()
Log->println(result); Log->println(result);
count = 0; count = 0;
while (count < 15) { while (count < 15)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -865,8 +873,10 @@ void NukiWrapper::updateDebug()
Log->println(result); Log->println(result);
count = 0; count = 0;
while (count < 20) { while (count < 20)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -948,7 +958,8 @@ void NukiWrapper::updateAuthData(bool retrieved)
if(result == Nuki::CmdResult::Success) if(result == Nuki::CmdResult::Success)
{ {
_waitAuthLogUpdateTs = espMillis() + 5000; _waitAuthLogUpdateTs = espMillis() + 5000;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -1176,7 +1187,8 @@ void NukiWrapper::updateAuth(bool retrieved)
{ {
Log->print("Querying lock authorization: "); Log->print("Querying lock authorization: ");
result = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH)); result = _nukiLock.retrieveAuthorizationEntries(0, _preferences->getInt(preference_auth_max_entries, MAX_AUTH));
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -3312,7 +3324,8 @@ void NukiWrapper::onKeypadJsonCommandReceived(const char *value)
if(resultKp == Nuki::CmdResult::Success) if(resultKp == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -3682,7 +3695,8 @@ void NukiWrapper::onTimeControlCommandReceived(const char *value)
if(resultTc == Nuki::CmdResult::Success) if(resultTc == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -3901,7 +3915,8 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
if(idExists) if(idExists)
{ {
result = _nukiLock.deleteAuthorizationEntry(authId); result = _nukiLock.deleteAuthorizationEntry(authId);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -4124,7 +4139,8 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
} }
result = _nukiLock.addAuthorizationEntry(entry); result = _nukiLock.addAuthorizationEntry(entry);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -4150,7 +4166,8 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
if(resultAuth == Nuki::CmdResult::Success) if(resultAuth == Nuki::CmdResult::Success)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -4296,7 +4313,8 @@ void NukiWrapper::onAuthCommandReceived(const char *value)
} }
result = _nukiLock.updateAuthorizationEntry(entry); result = _nukiLock.updateAuthorizationEntry(entry);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -4411,7 +4429,8 @@ void NukiWrapper::readConfig()
{ {
++retryCount; ++retryCount;
Log->println("Failed to retrieve lock config, retrying in 1s"); Log->println("Failed to retrieve lock config, retrying in 1s");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -4442,7 +4461,8 @@ void NukiWrapper::readAdvancedConfig()
{ {
++retryCount; ++retryCount;
Log->println("Failed to retrieve lock advanced config, retrying in 1s"); Log->println("Failed to retrieve lock advanced config, retrying in 1s");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);

View File

@@ -189,9 +189,9 @@
inline void initPreferences(Preferences* preferences) inline void initPreferences(Preferences* preferences)
{ {
#ifdef NUKI_HUB_UPDATER #ifdef NUKI_HUB_UPDATER
return; return;
#else #else
bool firstStart = !preferences->getBool(preference_started_before); bool firstStart = !preferences->getBool(preference_started_before);
if(firstStart) if(firstStart)
@@ -300,7 +300,10 @@ inline void initPreferences(Preferences* preferences)
Log->print("Current config version: "); Log->print("Current config version: ");
Log->println(NUKI_HUB_VERSION_INT); Log->println(NUKI_HUB_VERSION_INT);
if(lastConfigVer >= NUKI_HUB_VERSION_INT && lastConfigVer < 20000) return; if(lastConfigVer >= NUKI_HUB_VERSION_INT && lastConfigVer < 20000)
{
return;
}
if (lastConfigVer < 834) if (lastConfigVer < 834)
{ {
@@ -398,12 +401,12 @@ inline void initPreferences(Preferences* preferences)
{ {
Log->println("Migration 9.01"); Log->println("Migration 9.01");
#if defined(CONFIG_IDF_TARGET_ESP32S3) #if defined(CONFIG_IDF_TARGET_ESP32S3)
if (preferences->getInt(preference_network_hardware) == 3) if (preferences->getInt(preference_network_hardware) == 3)
{ {
preferences->putInt(preference_network_hardware, 10); preferences->putInt(preference_network_hardware, 10);
} }
#endif #endif
if (preferences->getInt(preference_network_hardware) == 2) if (preferences->getInt(preference_network_hardware) == 2)
{ {
preferences->putInt(preference_network_hardware, 3); preferences->putInt(preference_network_hardware, 3);
@@ -429,13 +432,15 @@ inline void initPreferences(Preferences* preferences)
if (caLength > 1) if (caLength > 1)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.ca for writing"); Log->println("Failed to open /mqtt_ssl.ca for writing");
} }
else else
@@ -455,13 +460,15 @@ inline void initPreferences(Preferences* preferences)
if (crtLength > 1) if (crtLength > 1)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.crt for writing"); Log->println("Failed to open /mqtt_ssl.crt for writing");
} }
else else
@@ -480,13 +487,15 @@ inline void initPreferences(Preferences* preferences)
if (keyLength > 1) if (keyLength > 1)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.key for writing"); Log->println("Failed to open /mqtt_ssl.key for writing");
} }
else else
@@ -510,7 +519,7 @@ inline void initPreferences(Preferences* preferences)
restartEsp(RestartReason::OTACompleted); restartEsp(RestartReason::OTACompleted);
} }
} }
#endif #endif
} }
class DebugPreferences class DebugPreferences

View File

@@ -4,7 +4,7 @@
#include "hal/wdt_hal.h" #include "hal/wdt_hal.h"
SerialReader::SerialReader(ImportExport *importExport, NukiNetwork* network) SerialReader::SerialReader(ImportExport *importExport, NukiNetwork* network)
: _importExport(importExport), : _importExport(importExport),
_network(network) _network(network)
{ {
} }

View File

@@ -30,7 +30,8 @@ extern bool timeSynced;
#include "ArduinoJson.h" #include "ArduinoJson.h"
#include <freertos/queue.h> #include <freertos/queue.h>
typedef struct { typedef struct
{
int socket; int socket;
char *buffer; char *buffer;
size_t len; size_t len;
@@ -183,7 +184,8 @@ esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse*
resp->setCookie("duoId", "", 0, "Secure; HttpOnly"); resp->setCookie("duoId", "", 0, "Secure; HttpOnly");
} }
if (request->hasCookie("duoId")) { if (request->hasCookie("duoId"))
{
String cookie2 = request->getCookie("duoId"); String cookie2 = request->getCookie("duoId");
_importExport->_duoSessions.remove(cookie2); _importExport->_duoSessions.remove(cookie2);
saveSessions(1); saveSessions(1);
@@ -205,7 +207,8 @@ esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse*
resp->setCookie("totpId", "", 0, "Secure; HttpOnly"); resp->setCookie("totpId", "", 0, "Secure; HttpOnly");
} }
if (request->hasCookie("totpId")) { if (request->hasCookie("totpId"))
{
String cookie2 = request->getCookie("totpId"); String cookie2 = request->getCookie("totpId");
_importExport->_totpSessions.remove(cookie2); _importExport->_totpSessions.remove(cookie2);
saveSessions(2); saveSessions(2);
@@ -227,7 +230,8 @@ esp_err_t WebCfgServer::logoutSession(PsychicRequest *request, PsychicResponse*
resp->setCookie("bypassId", "", 0, "Secure; HttpOnly"); resp->setCookie("bypassId", "", 0, "Secure; HttpOnly");
} }
if (request->hasCookie("bypassId")) { if (request->hasCookie("bypassId"))
{
String cookie2 = request->getCookie("bypassId"); String cookie2 = request->getCookie("bypassId");
_importExport->_bypassSessions.remove(cookie2); _importExport->_bypassSessions.remove(cookie2);
} }
@@ -284,7 +288,8 @@ void WebCfgServer::loadSessions(int type)
{ {
file = SPIFFS.open("/sessions.json", "r"); file = SPIFFS.open("/sessions.json", "r");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("sessions.json not found"); Log->println("sessions.json not found");
} }
else else
@@ -296,7 +301,8 @@ void WebCfgServer::loadSessions(int type)
{ {
file = SPIFFS.open("/duosessions.json", "r"); file = SPIFFS.open("/duosessions.json", "r");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("duosessions.json not found"); Log->println("duosessions.json not found");
} }
else else
@@ -308,7 +314,8 @@ void WebCfgServer::loadSessions(int type)
{ {
file = SPIFFS.open("/totpsessions.json", "r"); file = SPIFFS.open("/totpsessions.json", "r");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("totpsessions.json not found"); Log->println("totpsessions.json not found");
} }
else else
@@ -441,7 +448,7 @@ void WebCfgServer::initialize()
if(_preferences->getBool(preference_webserial_enabled, false)) if(_preferences->getBool(preference_webserial_enabled, false))
{ {
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
if (websocketHandler == nullptr) if (websocketHandler == nullptr)
{ {
websocketHandler = new PsychicWebSocketHandler; websocketHandler = new PsychicWebSocketHandler;
@@ -476,11 +483,13 @@ void WebCfgServer::initialize()
//prepare our message queue of 10 messages //prepare our message queue of 10 messages
wsMessages = xQueueCreate(10, sizeof(WebsocketMessage)); wsMessages = xQueueCreate(10, sizeof(WebsocketMessage));
if (wsMessages == 0) { if (wsMessages == 0)
{
Log->printf("Failed to create queue= %p\n", wsMessages); Log->printf("Failed to create queue= %p\n", wsMessages);
} }
websocketHandler->onOpen([](PsychicWebSocketClient *client) { websocketHandler->onOpen([](PsychicWebSocketClient *client)
{
Log->printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString()); Log->printf("[socket] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
client->sendMessage("NukiHub WebSerial started"); client->sendMessage("NukiHub WebSerial started");
}); });
@@ -518,12 +527,13 @@ void WebCfgServer::initialize()
} }
return ESP_OK; return ESP_OK;
}); });
websocketHandler->onClose([](PsychicWebSocketClient *client) { websocketHandler->onClose([](PsychicWebSocketClient *client)
{
Log->printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString()); Log->printf("[socket] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
}); });
_psychicServer->on("/ws", websocketHandler); _psychicServer->on("/ws", websocketHandler);
#endif #endif
} }
@@ -879,7 +889,7 @@ void WebCfgServer::initialize()
restartEsp(RestartReason::RequestedViaWebServer); restartEsp(RestartReason::RequestedViaWebServer);
return res; return res;
} }
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
else if (value == "restartservices") else if (value == "restartservices")
{ {
String value2 = ""; String value2 = "";
@@ -1026,7 +1036,7 @@ void WebCfgServer::initialize()
{ {
return buildGpioConfigHtml(request, resp); return buildGpioConfigHtml(request, resp);
} }
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
else if (value == "wifi") else if (value == "wifi")
{ {
return buildConfigureWifiHtml(request, resp); return buildConfigureWifiHtml(request, resp);
@@ -1061,8 +1071,8 @@ void WebCfgServer::initialize()
_network->reconfigureDevice(); _network->reconfigureDevice();
return res; return res;
} }
#endif #endif
#endif #endif
else if (value == "ota") else if (value == "ota")
{ {
return buildOtaHtml(request, resp); return buildOtaHtml(request, resp);
@@ -1102,13 +1112,13 @@ void WebCfgServer::initialize()
} }
else if (value == "autoupdate") else if (value == "autoupdate")
{ {
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
return processUpdate(request, resp); return processUpdate(request, resp);
#else #else
resp->setCode(302); resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache"); resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/"); return resp->redirect("/");
#endif #endif
} }
else else
{ {
@@ -1288,7 +1298,7 @@ void WebCfgServer::initialize()
return resp->redirect("/"); return resp->redirect("/");
} }
} }
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
else if (value == "savecfg") else if (value == "savecfg")
{ {
String message = ""; String message = "";
@@ -1349,25 +1359,25 @@ void WebCfgServer::initialize()
return buildConfirmHtml(request, resp, message, 3, true); return buildConfirmHtml(request, resp, message, 3, true);
} }
} }
#endif #endif
else else
{ {
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
if(!_network->isApOpen()) if(!_network->isApOpen())
{ {
#endif #endif
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
return buildHtml(request, resp); return buildHtml(request, resp);
#else #else
return buildOtaHtml(request, resp); return buildOtaHtml(request, resp);
#endif #endif
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
} }
else else
{ {
return buildWifiConnectHtml(request, resp); return buildWifiConnectHtml(request, resp);
} }
#endif #endif
} }
}); });
@@ -1721,7 +1731,8 @@ bool WebCfgServer::processWiFi(PsychicRequest *request, PsychicResponse* resp, S
int loop = 0; int loop = 0;
while(!_network->isConnected() && loop < 150) while(!_network->isConnected() && loop < 150)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -1855,16 +1866,16 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, PsychicResponse* r
response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('latestver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest release?'); } \" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: green\" value=\"Update to latest release\"></form>"); response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('latestver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest release?'); } \" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: green\" value=\"Update to latest release\"></form>");
response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('betaver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest beta? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"beta\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"color: black; background: yellow\" value=\"Update to latest beta\"></form>"); response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('betaver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest beta? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"beta\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"color: black; background: yellow\" value=\"Update to latest beta\"></form>");
response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('devver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest development version? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"master\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: red\" value=\"Update to latest development version\"></form>"); response.print("<form onsubmit=\"if(document.getElementById('currentver').innerHTML == document.getElementById('devver').innerHTML && '" + release_type + "' == '" + build_type + "') { alert('You are already on this version, build and build type'); return false; } else { return confirm('Do you really want to update to the latest development version? This version could contain breaking bugs and necessitate downgrading to the latest release version using USB/Serial'); }\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"master\" value=\"1\" /><input type=\"hidden\" name=\"" + release_type + "\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: red\" value=\"Update to latest development version\"></form>");
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_SPIRAM) #if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(CONFIG_SPIRAM)
if(esp_psram_get_size() <= 0) if(esp_psram_get_size() <= 0)
{ {
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to other PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to other PSRAM release version\"></form>");
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to No PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to No PSRAM release version\"></form>");
} }
#elif defined(CONFIG_IDF_TARGET_ESP32S3) #elif defined(CONFIG_IDF_TARGET_ESP32S3)
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Quad PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Quad PSRAM release version\"></form>");
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Octal PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Octal PSRAM release version\"></form>");
#elif defined(CONFIG_IDF_TARGET_ESP32) && defined(CONFIG_SPIRAM) && !defined(NUKI_TARGET_GL_S10) #elif defined(CONFIG_IDF_TARGET_ESP32) && defined(CONFIG_SPIRAM) && !defined(NUKI_TARGET_GL_S10)
if(_preferences->getInt(preference_network_hardware) == 8) if(_preferences->getInt(preference_network_hardware) == 8)
{ {
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to specific GL-S10 release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to specific GL-S10 release version\"></form>");
@@ -1873,13 +1884,13 @@ esp_err_t WebCfgServer::buildOtaHtml(PsychicRequest *request, PsychicResponse* r
{ {
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to No PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to No PSRAM release version\"></form>");
} }
#elif defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_SPIRAM) #elif defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_SPIRAM)
if(_preferences->getInt(preference_network_hardware) == 8) if(_preferences->getInt(preference_network_hardware) == 8)
{ {
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to specific GL-S10 release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"1\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to specific GL-S10 release version\"></form>");
} }
response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Quad PSRAM release version\"></form>"); response.print("<form onsubmit=\"return confirm('Do you really want to update to the latest release?');\" action=\"/get\" method=\"get\" style=\"float: left; margin-right: 10px\"><input type=\"hidden\" name=\"page\" value=\"autoupdate\"><input type=\"hidden\" name=\"other\" value=\"2\" /><input type=\"hidden\" name=\"release\" value=\"1\" /><input type=\"hidden\" name=\"CONFIRMTOKEN\" value=\"" + _confirmCode + "\" /><br><input type=\"submit\" style=\"background: blue\" value=\"Update to Quad PSRAM release version\"></form>");
#endif #endif
response.print("<div style=\"clear: both\"></div><br>"); response.print("<div style=\"clear: both\"></div><br>");
response.print("<b>Current version: </b><span id=\"currentver\">"); response.print("<b>Current version: </b><span id=\"currentver\">");
@@ -2272,14 +2283,17 @@ esp_err_t WebCfgServer::buildLoginHtml(PsychicRequest *request, PsychicResponse*
response.print("<label for=\"password\"><b>Password</b></label><input type=\"password\" placeholder=\"Enter Password\" name=\"password\" required>"); response.print("<label for=\"password\"><b>Password</b></label><input type=\"password\" placeholder=\"Enter Password\" name=\"password\" required>");
if (_importExport->getTOTPEnabled() || _duoEnabled) if (_importExport->getTOTPEnabled() || _duoEnabled)
{ {
if (_importExport->getTOTPEnabled()) { if (_importExport->getTOTPEnabled())
{
response.print("<button name=\"totp\" type=\"submit\" formaction=\"/post?page=login&totp=1\">Login with TOTP</button>"); response.print("<button name=\"totp\" type=\"submit\" formaction=\"/post?page=login&totp=1\">Login with TOTP</button>");
} }
if (_duoEnabled) { if (_duoEnabled)
{
response.print("<button name=\"duo\" type=\"submit\">Login with Duo</button>"); response.print("<button name=\"duo\" type=\"submit\">Login with Duo</button>");
} }
} }
else { else
{
response.print("<button type=\"submit\">Login</button>"); response.print("<button type=\"submit\">Login</button>");
} }
response.print("<label><input type=\"checkbox\" name=\"remember\"> Remember me</label></div>"); response.print("<label><input type=\"checkbox\" name=\"remember\"> Remember me</label></div>");
@@ -2398,7 +2412,8 @@ esp_err_t WebCfgServer::buildCoredumpHtml(PsychicRequest *request, PsychicRespon
{ {
File file = SPIFFS.open("/coredump.hex", "r"); File file = SPIFFS.open("/coredump.hex", "r");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("coredump.hex not found"); Log->println("coredump.hex not found");
} }
else else
@@ -2454,7 +2469,8 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
PsychicStreamResponse response(resp, "text/html"); PsychicStreamResponse response(resp, "text/html");
char buffer[33]; char buffer[33];
int i; int i;
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++)
{
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random()); sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
} }
@@ -2535,10 +2551,12 @@ bool WebCfgServer::processLogin(PsychicRequest *request, PsychicResponse* resp)
char buffer[33]; char buffer[33];
int i; int i;
int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_remember, 720); int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_remember, 720);
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++)
{
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random()); sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
} }
if(!request->hasParam("remember")) { if(!request->hasParam("remember"))
{
durationLength = _preferences->getInt(preference_cred_session_lifetime, 3600); durationLength = _preferences->getInt(preference_cred_session_lifetime, 3600);
} }
@@ -2580,7 +2598,8 @@ bool WebCfgServer::processBypass(PsychicRequest *request, PsychicResponse* resp)
{ {
char buffer[33]; char buffer[33];
int i; int i;
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++)
{
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random()); sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
} }
@@ -2601,7 +2620,8 @@ bool WebCfgServer::processBypass(PsychicRequest *request, PsychicResponse* resp)
char randomstr2[33]; char randomstr2[33];
randomSeed(esp_random()); randomSeed(esp_random());
char chars[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'}; char chars[] = {'1', '2', '3','4', '5', '6','7', '8', '9', '0', 'A', 'B', 'C', 'D','E', 'F', 'G','H', 'I', 'J','K', 'L', 'M', 'N', 'O','P', 'Q','R', 'S', 'T','U', 'V', 'W','X', 'Y', 'Z'};
for(int i = 0;i < 32; i++){ for(int i = 0; i < 32; i++)
{
randomstr2[i] = chars[random(36)]; randomstr2[i] = chars[random(36)];
} }
randomstr2[32] = '\0'; randomstr2[32] = '\0';
@@ -2627,7 +2647,8 @@ bool WebCfgServer::processTOTP(PsychicRequest *request, PsychicResponse* resp)
char buffer[33]; char buffer[33];
int i; int i;
int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_totp_remember, 720); int64_t durationLength = 60*60*_preferences->getInt(preference_cred_session_lifetime_totp_remember, 720);
for (i = 0; i < 4; i++) { for (i = 0; i < 4; i++)
{
sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random()); sprintf(buffer + (i * 8), "%08lx", (unsigned long int)esp_random());
} }
@@ -2670,12 +2691,14 @@ esp_err_t WebCfgServer::sendWebSerial(PsychicRequest* request, PsychicResponse*
void WebCfgServer::updateWebSerial() void WebCfgServer::updateWebSerial()
{ {
if (websocketHandler != nullptr) { if (websocketHandler != nullptr)
{
WebsocketMessage message; WebsocketMessage message;
while (xQueueReceive(wsMessages, &message, 0) == pdTRUE) while (xQueueReceive(wsMessages, &message, 0) == pdTRUE)
{ {
PsychicWebSocketClient *client = websocketHandler->getClient(message.socket); PsychicWebSocketClient *client = websocketHandler->getClient(message.socket);
if (client == NULL) { if (client == NULL)
{
Log->printf("[socket] client #%d bad, bailing\n", message.socket); Log->printf("[socket] client #%d bad, bailing\n", message.socket);
return; return;
} }
@@ -2856,7 +2879,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "MQTTCA") else if(key == "MQTTCA")
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
@@ -2866,7 +2890,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(value != "") if(value != "")
{ {
File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.ca", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.ca for writing"); Log->println("Failed to open /mqtt_ssl.ca for writing");
} }
else else
@@ -2880,7 +2905,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.ca")) { if (!SPIFFS.remove("/mqtt_ssl.ca"))
{
Log->println("Failed to delete /mqtt_ssl.ca"); Log->println("Failed to delete /mqtt_ssl.ca");
} }
} }
@@ -2892,7 +2918,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "MQTTCRT") else if(key == "MQTTCRT")
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
@@ -2902,7 +2929,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(value != "") if(value != "")
{ {
File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.crt for writing"); Log->println("Failed to open /mqtt_ssl.crt for writing");
} }
else else
@@ -2916,7 +2944,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.crt")) { if (!SPIFFS.remove("/mqtt_ssl.crt"))
{
Log->println("Failed to delete /mqtt_ssl.crt"); Log->println("Failed to delete /mqtt_ssl.crt");
} }
} }
@@ -2928,7 +2957,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "MQTTKEY") else if(key == "MQTTKEY")
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
@@ -2938,7 +2968,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(value != "") if(value != "")
{ {
File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE); File file = SPIFFS.open("/mqtt_ssl.key", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /mqtt_ssl.key for writing"); Log->println("Failed to open /mqtt_ssl.key for writing");
} }
else else
@@ -2952,7 +2983,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else else
{ {
if (!SPIFFS.remove("/mqtt_ssl.key")) { if (!SPIFFS.remove("/mqtt_ssl.key"))
{
Log->println("Failed to delete /mqtt_ssl.key"); Log->println("Failed to delete /mqtt_ssl.key");
} }
} }
@@ -2964,7 +2996,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "HTTPCRT" && nuki_hub_https_server_enabled) else if(key == "HTTPCRT" && nuki_hub_https_server_enabled)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
@@ -2974,7 +3007,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(value != "") if(value != "")
{ {
File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /http_ssl.crt for writing"); Log->println("Failed to open /http_ssl.crt for writing");
} }
else else
@@ -2988,7 +3022,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else else
{ {
if (!SPIFFS.remove("/http_ssl.crt")) { if (!SPIFFS.remove("/http_ssl.crt"))
{
Log->println("Failed to delete /http_ssl.crt"); Log->println("Failed to delete /http_ssl.crt");
} }
} }
@@ -3000,7 +3035,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "HTTPKEY" && nuki_hub_https_server_enabled) else if(key == "HTTPKEY" && nuki_hub_https_server_enabled)
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
@@ -3010,7 +3046,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(value != "") if(value != "")
{ {
File file = SPIFFS.open("/http_ssl.key", FILE_WRITE); File file = SPIFFS.open("/http_ssl.key", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /http_ssl.key for writing"); Log->println("Failed to open /http_ssl.key for writing");
} }
else else
@@ -3024,7 +3061,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else else
{ {
if (!SPIFFS.remove("/http_ssl.key")) { if (!SPIFFS.remove("/http_ssl.key"))
{
Log->println("Failed to delete /http_ssl.key"); Log->println("Failed to delete /http_ssl.key");
} }
} }
@@ -3036,9 +3074,9 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "HTTPGEN" && nuki_hub_https_server_enabled) else if(key == "HTTPGEN" && nuki_hub_https_server_enabled)
{ {
#ifdef NUKI_HUB_HTTPS_SERVER #ifdef NUKI_HUB_HTTPS_SERVER
createSSLCertificate(); createSSLCertificate();
#endif #endif
Log->print("Setting changed: "); Log->print("Setting changed: ");
Log->println(key); Log->println(key);
configChanged = true; configChanged = true;
@@ -3297,7 +3335,8 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
if(_preferences->getBool(preference_cred_duo_enabled, false) != (value == "1")) if(_preferences->getBool(preference_cred_duo_enabled, false) != (value == "1"))
{ {
_preferences->putBool(preference_cred_duo_enabled, (value == "1")); _preferences->putBool(preference_cred_duo_enabled, (value == "1"));
if (value == "1") { if (value == "1")
{
_preferences->putBool(preference_update_time, true); _preferences->putBool(preference_update_time, true);
} }
Log->print("Setting changed: "); Log->print("Setting changed: ");
@@ -3722,11 +3761,11 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
} }
else if(key == "TXPWR") else if(key == "TXPWR")
{ {
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
if(value.toInt() >= -12 && value.toInt() <= 9) if(value.toInt() >= -12 && value.toInt() <= 9)
#else #else
if(value.toInt() >= -12 && value.toInt() <= 20) if(value.toInt() >= -12 && value.toInt() <= 20)
#endif #endif
{ {
if(_preferences->getInt(preference_ble_tx_power, 9) != value.toInt()) if(_preferences->getInt(preference_ble_tx_power, 9) != value.toInt())
{ {
@@ -5219,19 +5258,22 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
char randomstr[17]; char randomstr[17];
randomSeed(esp_random()); randomSeed(esp_random());
for(int i = 0;i < 16; i++){ for(int i = 0; i < 16; i++)
{
randomstr[i] = chars[random(32)]; randomstr[i] = chars[random(32)];
} }
randomstr[16] = '\0'; randomstr[16] = '\0';
char randomstr2[33]; char randomstr2[33];
randomSeed(esp_random()); randomSeed(esp_random());
for(int i = 0;i < 32; i++){ for(int i = 0; i < 32; i++)
{
randomstr2[i] = chars2[random(36)]; randomstr2[i] = chars2[random(36)];
} }
randomstr2[32] = '\0'; randomstr2[32] = '\0';
char randomstr3[33]; char randomstr3[33];
randomSeed(esp_random()); randomSeed(esp_random());
for(int i = 0;i < 32; i++){ for(int i = 0; i < 32; i++)
{
randomstr3[i] = chars2[random(36)]; randomstr3[i] = chars2[random(36)];
} }
randomstr3[32] = '\0'; randomstr3[32] = '\0';
@@ -5455,13 +5497,15 @@ esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, PsychicR
{ {
bool found = false; bool found = false;
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.ca"); File file = SPIFFS.open("/mqtt_ssl.ca");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.ca not found"); Log->println("mqtt_ssl.ca not found");
} }
else else
@@ -5488,13 +5532,15 @@ esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, PsychicR
{ {
bool found = false; bool found = false;
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.crt"); File file = SPIFFS.open("/mqtt_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.crt not found"); Log->println("mqtt_ssl.crt not found");
} }
else else
@@ -5521,13 +5567,15 @@ esp_err_t WebCfgServer::buildMqttSSLConfigHtml(PsychicRequest *request, PsychicR
{ {
bool found = false; bool found = false;
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.key"); File file = SPIFFS.open("/mqtt_ssl.key");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.key not found"); Log->println("mqtt_ssl.key not found");
} }
else else
@@ -5573,13 +5621,15 @@ esp_err_t WebCfgServer::buildHttpSSLConfigHtml(PsychicRequest *request, PsychicR
{ {
bool found = false; bool found = false;
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.crt"); File file = SPIFFS.open("/http_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.crt not found"); Log->println("http_ssl.crt not found");
} }
else else
@@ -5606,13 +5656,15 @@ esp_err_t WebCfgServer::buildHttpSSLConfigHtml(PsychicRequest *request, PsychicR
{ {
bool found = false; bool found = false;
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.key"); File file = SPIFFS.open("/http_ssl.key");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.key not found"); Log->println("http_ssl.key not found");
} }
else else
@@ -6059,11 +6111,11 @@ esp_err_t WebCfgServer::buildNukiConfigHtml(PsychicRequest *request, PsychicResp
printCheckBox(&response, "REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), ""); printCheckBox(&response, "REGAPPOPN", "Opener: Nuki Bridge is running alongside Nuki Hub (needs re-pairing if changed)", _preferences->getBool(preference_register_opener_as_app), "");
} }
printInputField(&response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, ""); printInputField(&response, "RSBC", "Restart if bluetooth beacons not received (seconds; -1 to disable)", _preferences->getInt(preference_restart_ble_beacon_lost), 10, "");
#if defined(CONFIG_IDF_TARGET_ESP32) #if defined(CONFIG_IDF_TARGET_ESP32)
printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 9)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 9)", _preferences->getInt(preference_ble_tx_power, 9), 10, "");
#else #else
printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 20)", _preferences->getInt(preference_ble_tx_power, 9), 10, ""); printInputField(&response, "TXPWR", "BLE transmit power in dB (minimum -12, maximum 20)", _preferences->getInt(preference_ble_tx_power, 9), 10, "");
#endif #endif
printCheckBox(&response, "UPTIME", "Update Nuki Hub and Lock/Opener time using NTP", _preferences->getBool(preference_update_time, false), ""); printCheckBox(&response, "UPTIME", "Update Nuki Hub and Lock/Opener time using NTP", _preferences->getBool(preference_update_time, false), "");
printInputField(&response, "TIMESRV", "NTP server", _preferences->getString(preference_time_server, "pool.ntp.org").c_str(), 255, ""); printInputField(&response, "TIMESRV", "NTP server", _preferences->getString(preference_time_server, "pool.ntp.org").c_str(), 255, "");
@@ -6185,7 +6237,7 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
response.print(ESP.getFreeHeap()); response.print(ESP.getFreeHeap());
response.print("\nTotal internal heap: "); response.print("\nTotal internal heap: ");
response.print(ESP.getHeapSize()); response.print(ESP.getHeapSize());
#if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM) #if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM)
if(esp_psram_get_size() > 0) if(esp_psram_get_size() > 0)
{ {
response.print("\nPSRAM Available: Yes"); response.print("\nPSRAM Available: Yes");
@@ -6202,9 +6254,9 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
{ {
response.print("\nPSRAM Available: No"); response.print("\nPSRAM Available: No");
} }
#else #else
response.print("\nPSRAM Available: No"); response.print("\nPSRAM Available: No");
#endif #endif
response.print("\nNetwork task stack high watermark: "); response.print("\nNetwork task stack high watermark: ");
response.print(uxTaskGetStackHighWaterMark(networkTaskHandle)); response.print(uxTaskGetStackHighWaterMark(networkTaskHandle));
response.print("\nNuki task stack high watermark: "); response.print("\nNuki task stack high watermark: ");
@@ -6272,7 +6324,8 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
response.print("\nWeb configurator enabled: "); response.print("\nWeb configurator enabled: ");
response.print(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No"); response.print(_preferences->getBool(preference_webserver_enabled, true) ? "Yes" : "No");
response.print("\nHTTP SSL: "); response.print("\nHTTP SSL: ");
if (!SPIFFS.begin(true) || !nuki_hub_https_server_enabled) { if (!SPIFFS.begin(true) || !nuki_hub_https_server_enabled)
{
response.print("Disabled"); response.print("Disabled");
} }
else else
@@ -6401,13 +6454,15 @@ esp_err_t WebCfgServer::buildInfoHtml(PsychicRequest *request, PsychicResponse*
response.print("\nMQTT SSL: "); response.print("\nMQTT SSL: ");
if(_preferences->getBool(preference_mqtt_ssl_enabled, false)) if(_preferences->getBool(preference_mqtt_ssl_enabled, false))
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
response.print("Disabled"); response.print("Disabled");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.ca"); File file = SPIFFS.open("/mqtt_ssl.ca");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
response.print("Disabled"); response.print("Disabled");
} }
else else
@@ -6977,14 +7032,14 @@ esp_err_t WebCfgServer::processUpdate(PsychicRequest *request, PsychicResponse*
_preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL); _preferences->putString(preference_ota_main_url, GITHUB_MASTER_RELEASE_BINARY_URL);
//} //}
} }
#if (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32)) && !defined(CONFIG_FREERTOS_UNICORE) #if (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32)) && !defined(CONFIG_FREERTOS_UNICORE)
else if(request->hasParam("other")) else if(request->hasParam("other"))
{ {
res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest RELEASE version", 2, true); res = buildConfirmHtml(request, resp, "Rebooting to update Nuki Hub and Nuki Hub updater<br/>Updating to latest RELEASE version", 2, true);
_preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL_OTHER); _preferences->putString(preference_ota_updater_url, GITHUB_LATEST_UPDATER_BINARY_URL_OTHER);
_preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL_OTHER); _preferences->putString(preference_ota_main_url, GITHUB_LATEST_RELEASE_BINARY_URL_OTHER);
} }
#endif #endif
else else
{ {
/* /*
@@ -7296,14 +7351,17 @@ void WebCfgServer::createSSLCertificate()
"20350101000000" "20350101000000"
); );
if (createCertResult == 0) { if (createCertResult == 0)
if (!SPIFFS.begin(true)) { {
if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE); File file = SPIFFS.open("/http_ssl.crt", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /http_ssl.crt for writing"); Log->println("Failed to open /http_ssl.crt for writing");
} }
else else
@@ -7316,7 +7374,8 @@ void WebCfgServer::createSSLCertificate()
} }
File file2 = SPIFFS.open("/http_ssl.key", FILE_WRITE); File file2 = SPIFFS.open("/http_ssl.key", FILE_WRITE);
if (!file2) { if (!file2)
{
Log->println("Failed to open /http_ssl.key for writing"); Log->println("Failed to open /http_ssl.key for writing");
} }
else else

View File

@@ -42,18 +42,18 @@ extern TaskHandle_t networkTaskHandle;
class WebCfgServer class WebCfgServer
{ {
public: public:
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, PsychicHttpServer* psychicServer, ImportExport* importExport); WebCfgServer(NukiWrapper* nuki, NukiOpenerWrapper* nukiOpener, NukiNetwork* network, Gpio* gpio, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, PsychicHttpServer* psychicServer, ImportExport* importExport);
void updateWebSerial(); void updateWebSerial();
#else #else
WebCfgServer(NukiNetwork* network, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, PsychicHttpServer* psychicServer, ImportExport* importExport); WebCfgServer(NukiNetwork* network, Preferences* preferences, bool allowRestartToPortal, uint8_t partitionType, PsychicHttpServer* psychicServer, ImportExport* importExport);
#endif #endif
~WebCfgServer() = default; ~WebCfgServer() = default;
void initialize(); void initialize();
private: private:
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
esp_err_t sendSettings(PsychicRequest *request, PsychicResponse* resp, bool adminKey = false); esp_err_t sendSettings(PsychicRequest *request, PsychicResponse* resp, bool adminKey = false);
bool processArgs(PsychicRequest *request, PsychicResponse* resp, String& message); bool processArgs(PsychicRequest *request, PsychicResponse* resp, String& message);
bool processImport(PsychicRequest *request, PsychicResponse* resp, String& message); bool processImport(PsychicRequest *request, PsychicResponse* resp, String& message);
@@ -70,9 +70,9 @@ private:
esp_err_t buildAdvancedConfigHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildAdvancedConfigHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildNukiConfigHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildNukiConfigHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildGpioConfigHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildGpioConfigHtml(PsychicRequest *request, PsychicResponse* resp);
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
esp_err_t buildConfigureWifiHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildConfigureWifiHtml(PsychicRequest *request, PsychicResponse* resp);
#endif #endif
esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildInfoHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildCustomNetworkConfigHtml(PsychicRequest *request, PsychicResponse* resp);
esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener); esp_err_t processUnpair(PsychicRequest *request, PsychicResponse* resp, bool opener);
@@ -85,12 +85,12 @@ private:
const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const; const std::vector<std::pair<String, String>> getNetworkDetectionOptions() const;
const std::vector<std::pair<String, String>> getGpioOptions() const; const std::vector<std::pair<String, String>> getGpioOptions() const;
const std::vector<std::pair<String, String>> getNetworkCustomPHYOptions() const; const std::vector<std::pair<String, String>> getNetworkCustomPHYOptions() const;
#if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32P4)
const std::vector<std::pair<String, String>> getNetworkCustomCLKOptions() const; const std::vector<std::pair<String, String>> getNetworkCustomCLKOptions() const;
#endif #endif
#ifdef NUKI_HUB_HTTPS_SERVER #ifdef NUKI_HUB_HTTPS_SERVER
void createSSLCertificate(); void createSSLCertificate();
#endif #endif
const String getPreselectionForGpio(const uint8_t& pin) const; const String getPreselectionForGpio(const uint8_t& pin) const;
const String pinStateToString(const NukiPinState& value) const; const String pinStateToString(const NukiPinState& value) const;
@@ -102,7 +102,7 @@ private:
bool _brokerConfigured = false; bool _brokerConfigured = false;
bool _rebootRequired = false; bool _rebootRequired = false;
int _restartServicesRequired = 0; int _restartServicesRequired = 0;
#endif #endif
std::vector<String> _ssidList; std::vector<String> _ssidList;
std::vector<int> _rssiList; std::vector<int> _rssiList;
@@ -135,11 +135,11 @@ private:
void waitAndProcess(const bool blocking, const uint32_t duration); void waitAndProcess(const bool blocking, const uint32_t duration);
esp_err_t handleOtaUpload(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final); esp_err_t handleOtaUpload(PsychicRequest *request, const String& filename, uint64_t index, uint8_t *data, size_t len, bool final);
void printCheckBox(PsychicStreamResponse *response, const char* token, const char* description, const bool value, const char* htmlClass); void printCheckBox(PsychicStreamResponse *response, const char* token, const char* description, const bool value, const char* htmlClass);
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
esp_err_t buildWifiConnectHtml(PsychicRequest *request, PsychicResponse* resp); esp_err_t buildWifiConnectHtml(PsychicRequest *request, PsychicResponse* resp);
bool processWiFi(PsychicRequest *request, PsychicResponse* resp, String& message); bool processWiFi(PsychicRequest *request, PsychicResponse* resp, String& message);
#endif #endif
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* args, const bool& isPassword = false, const bool& showLengthRestriction = false); void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const char* value, const size_t& maxLength, const char* args, const bool& isPassword = false, const bool& showLengthRestriction = false);
void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const int value, size_t maxLength, const char* args); void printInputField(PsychicStreamResponse *response, const char* token, const char* description, const int value, size_t maxLength, const char* args);

View File

@@ -1,17 +1,20 @@
#pragma once #pragma once
// converted to char array by bin2array // converted to char array by bin2array
const char stylecss[] = { const char stylecss[] =
#include "webServerConstants/style.h" {
#include "webServerConstants/style.h"
}; };
// converted to char array by bin2array // converted to char array by bin2array
const uint8_t favicon_32x32[] = { const uint8_t favicon_32x32[] =
#include "webServerConstants/favicon-32x32.h" {
#include "webServerConstants/favicon-32x32.h"
}; };
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
const uint8_t WEBSERIAL_HTML[] = { const uint8_t WEBSERIAL_HTML[] =
#include "webServerConstants/webSerial.h" {
#include "webServerConstants/webSerial.h"
}; };
#endif #endif

View File

@@ -157,11 +157,11 @@ void setReroute()
{ {
esp_log_set_vprintf(_log_vprintf); esp_log_set_vprintf(_log_vprintf);
#ifdef DEBUG_NUKIHUB #ifdef DEBUG_NUKIHUB
esp_log_level_set("*", ESP_LOG_DEBUG); esp_log_level_set("*", ESP_LOG_DEBUG);
esp_log_level_set("nvs", ESP_LOG_INFO); esp_log_level_set("nvs", ESP_LOG_INFO);
esp_log_level_set("wifi", ESP_LOG_INFO); esp_log_level_set("wifi", ESP_LOG_INFO);
#else #else
/* /*
esp_log_level_set("*", ESP_LOG_NONE); esp_log_level_set("*", ESP_LOG_NONE);
esp_log_level_set("httpd", ESP_LOG_ERROR); esp_log_level_set("httpd", ESP_LOG_ERROR);
@@ -175,7 +175,7 @@ void setReroute()
esp_log_level_set("nvs", ESP_LOG_ERROR); esp_log_level_set("nvs", ESP_LOG_ERROR);
esp_log_level_set("wifi", ESP_LOG_ERROR); esp_log_level_set("wifi", ESP_LOG_ERROR);
*/ */
#endif #endif
if(preferences->getBool(preference_mqtt_log_enabled)) if(preferences->getBool(preference_mqtt_log_enabled))
{ {
@@ -193,12 +193,12 @@ uint8_t checkPartition()
Log->println(running_partition->subtype); Log->println(running_partition->subtype);
#if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32P4) #if !defined(CONFIG_IDF_TARGET_ESP32C5) && !defined(CONFIG_IDF_TARGET_ESP32P4)
if(running_partition->size == 1966080) if(running_partition->size == 1966080)
{ {
return 0; //OLD PARTITION TABLE return 0; //OLD PARTITION TABLE
} }
#endif #endif
if(running_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) if(running_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0)
{ {
@@ -210,28 +210,36 @@ uint8_t checkPartition()
} }
} }
void listDir(fs::FS &fs, const char *dirname, uint8_t levels) { void listDir(fs::FS &fs, const char *dirname, uint8_t levels)
{
Serial.printf("Listing directory: %s\r\n", dirname); Serial.printf("Listing directory: %s\r\n", dirname);
File root = fs.open(dirname); File root = fs.open(dirname);
if (!root) { if (!root)
{
Serial.println("- failed to open directory"); Serial.println("- failed to open directory");
return; return;
} }
if (!root.isDirectory()) { if (!root.isDirectory())
{
Serial.println(" - not a directory"); Serial.println(" - not a directory");
return; return;
} }
File file = root.openNextFile(); File file = root.openNextFile();
while (file) { while (file)
if (file.isDirectory()) { {
if (file.isDirectory())
{
Serial.print(" DIR : "); Serial.print(" DIR : ");
Serial.println(file.name()); Serial.println(file.name());
if (levels) { if (levels)
{
listDir(fs, file.path(), levels - 1); listDir(fs, file.path(), levels - 1);
} }
} else { }
else
{
Serial.print(" FILE: "); Serial.print(" FILE: ");
Serial.print(file.name()); Serial.print(file.name());
Serial.print("\tSIZE: "); Serial.print("\tSIZE: ");
@@ -247,7 +255,8 @@ void listDir(fs::FS &fs, const char *dirname, uint8_t levels) {
} }
} }
void cbSyncTime(struct timeval *tv) { void cbSyncTime(struct timeval *tv)
{
Log->println("NTP time synced"); Log->println("NTP time synced");
timeSynced = true; timeSynced = true;
} }
@@ -272,7 +281,8 @@ void startWebServer()
else else
{ {
File file = SPIFFS.open("/http_ssl.crt"); File file = SPIFFS.open("/http_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.crt not found"); Log->println("http_ssl.crt not found");
} }
else else
@@ -302,7 +312,8 @@ void startWebServer()
psychicServerRedirect = new PsychicHttpServer(); psychicServerRedirect = new PsychicHttpServer();
psychicServerRedirect->config.ctrl_port = 20424; psychicServerRedirect->config.ctrl_port = 20424;
psychicServerRedirect->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicServerRedirect->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
String url = "https://" + request->host() + request->url(); String url = "https://" + request->host() + request->url();
if (preferences->getString(preference_https_fqdn, "") != "") if (preferences->getString(preference_https_fqdn, "") != "")
{ {
@@ -320,7 +331,8 @@ void startWebServer()
psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE; psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE;
webCfgServerSSL = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer, importExport); webCfgServerSSL = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer, importExport);
webCfgServerSSL->initialize(); webCfgServerSSL->initialize();
psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
return response->redirect("/"); return response->redirect("/");
}); });
psychicSSLServer->begin(); psychicSSLServer->begin();
@@ -337,7 +349,8 @@ void startWebServer()
psychicServer->config.stack_size = HTTPD_TASK_SIZE; psychicServer->config.stack_size = HTTPD_TASK_SIZE;
webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer, importExport); webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer, importExport);
webCfgServer->initialize(); webCfgServer->initialize();
psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
return response->redirect("/"); return response->redirect("/");
}); });
psychicServer->begin(); psychicServer->begin();
@@ -456,13 +469,15 @@ void restartServices(bool reconnect)
Log->println("Scanner nulled from main"); Log->println("Scanner nulled from main");
} }
if (BLEDevice::isInitialized()) { if (BLEDevice::isInitialized())
{
Log->println("Deinit BLE device"); Log->println("Deinit BLE device");
BLEDevice::deinit(false); BLEDevice::deinit(false);
Log->println("Deinit BLE device done"); Log->println("Deinit BLE device done");
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -508,7 +523,8 @@ void restartServices(bool reconnect)
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
@@ -559,7 +575,8 @@ void networkTask(void *pvParameters)
} }
#endif #endif
network->update(); network->update();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -606,7 +623,8 @@ void networkTask(void *pvParameters)
if(connected && webSerialEnabled && (webSSLStarted || webStarted)) if(connected && webSerialEnabled && (webSSLStarted || webStarted))
{ {
webCfgServerSSL->updateWebSerial(); webCfgServerSSL->updateWebSerial();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -615,7 +633,8 @@ void networkTask(void *pvParameters)
if(connected && lockStarted) if(connected && lockStarted)
{ {
rebootLock = networkLock->update(); rebootLock = networkLock->update();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -624,7 +643,8 @@ void networkTask(void *pvParameters)
if(connected && openerStarted) if(connected && openerStarted)
{ {
networkOpener->update(); networkOpener->update();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -650,7 +670,8 @@ void networkTask(void *pvParameters)
restartEsp(RestartReason::RestartTimer); restartEsp(RestartReason::RestartTimer);
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -674,14 +695,16 @@ static void print_all_tasks_info(void)
Log->printf("\n--------------------------------------------------------------------------------\n"); Log->printf("\n--------------------------------------------------------------------------------\n");
Log->printf("PRINTING ALL TASKS INFO\n"); Log->printf("PRINTING ALL TASKS INFO\n");
Log->printf("--------------------------------------------------------------------------------\n"); Log->printf("--------------------------------------------------------------------------------\n");
for (size_t task_idx = 0; task_idx < tasks_stat.task_count; task_idx++) { for (size_t task_idx = 0; task_idx < tasks_stat.task_count; task_idx++)
{
task_stat_t task_stat = tasks_stat.stat_arr[task_idx]; task_stat_t task_stat = tasks_stat.stat_arr[task_idx];
Log->printf("%s: %s: Peak Usage %" PRIu16 ", Current Usage %" PRIu16 "\n", task_stat.name, Log->printf("%s: %s: Peak Usage %" PRIu16 ", Current Usage %" PRIu16 "\n", task_stat.name,
task_stat.is_alive ? "ALIVE " : "DELETED", task_stat.is_alive ? "ALIVE " : "DELETED",
task_stat.overall_peak_usage, task_stat.overall_peak_usage,
task_stat.overall_current_usage); task_stat.overall_current_usage);
for (size_t heap_idx = 0; heap_idx < task_stat.heap_count; heap_idx++) { for (size_t heap_idx = 0; heap_idx < task_stat.heap_count; heap_idx++)
{
heap_stat_t heap_stat = task_stat.heap_stat[heap_idx]; heap_stat_t heap_stat = task_stat.heap_stat[heap_idx];
Log->printf(" %s: Caps: %" PRIu32 ". Size %" PRIu16 ", Current Usage %" PRIu16 ", Peak Usage %" PRIu16 ", alloc count %" PRIu16 "\n", heap_stat.name, Log->printf(" %s: Caps: %" PRIu32 ". Size %" PRIu16 ", Current Usage %" PRIu16 ", Peak Usage %" PRIu16 ", alloc count %" PRIu16 "\n", heap_stat.name,
heap_stat.caps, heap_stat.caps,
@@ -690,7 +713,8 @@ static void print_all_tasks_info(void)
heap_stat.peak_usage, heap_stat.peak_usage,
heap_stat.alloc_count); heap_stat.alloc_count);
for (size_t alloc_idx = 0; alloc_idx < heap_stat.alloc_count; alloc_idx++) { for (size_t alloc_idx = 0; alloc_idx < heap_stat.alloc_count; alloc_idx++)
{
heap_task_block_t alloc_stat = heap_stat.alloc_stat[alloc_idx]; heap_task_block_t alloc_stat = heap_stat.alloc_stat[alloc_idx];
Log->printf(" %p: Size: %" PRIu32 "\n", alloc_stat.address, alloc_stat.size); Log->printf(" %p: Size: %" PRIu32 "\n", alloc_stat.address, alloc_stat.size);
} }
@@ -707,22 +731,24 @@ void nukiTask(void *pvParameters)
if (preferences->getBool(preference_mqtt_ssl_enabled, false)) if (preferences->getBool(preference_mqtt_ssl_enabled, false))
{ {
#if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM) #if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM)
if (esp_psram_get_size() <= 0) if (esp_psram_get_size() <= 0)
{ {
Log->println("Waiting 20 seconds to start BLE because of MQTT SSL"); Log->println("Waiting 20 seconds to start BLE because of MQTT SSL");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(20000 / portTICK_PERIOD_MS); vTaskDelay(20000 / portTICK_PERIOD_MS);
} }
#else #else
Log->println("Waiting 20 seconds to start BLE because of MQTT SSL"); Log->println("Waiting 20 seconds to start BLE because of MQTT SSL");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(20000 / portTICK_PERIOD_MS); vTaskDelay(20000 / portTICK_PERIOD_MS);
#endif #endif
} }
int64_t nukiLoopTs = 0; int64_t nukiLoopTs = 0;
bool whiteListed = false; bool whiteListed = false;
@@ -733,7 +759,8 @@ void nukiTask(void *pvParameters)
if(bleScannerStarted) if(bleScannerStarted)
{ {
bleScanner->update(); bleScanner->update();
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(20 / portTICK_PERIOD_MS); vTaskDelay(20 / portTICK_PERIOD_MS);
@@ -743,7 +770,8 @@ void nukiTask(void *pvParameters)
if (needsPairing) if (needsPairing)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(2500 / portTICK_PERIOD_MS); vTaskDelay(2500 / portTICK_PERIOD_MS);
@@ -833,7 +861,8 @@ void nukiTask(void *pvParameters)
Log->println("nukiTask is running"); Log->println("nukiTask is running");
nukiLoopTs = espMillis(); nukiLoopTs = espMillis();
} }
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(50 / portTICK_PERIOD_MS); vTaskDelay(50 / portTICK_PERIOD_MS);
@@ -982,7 +1011,8 @@ void otaTask(void *pvParameter)
{ {
Log->println("Firmware upgrade failed, retrying in 5 seconds"); Log->println("Firmware upgrade failed, retrying in 5 seconds");
retryCount++; retryCount++;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(5000 / portTICK_PERIOD_MS); vTaskDelay(5000 / portTICK_PERIOD_MS);
@@ -990,7 +1020,8 @@ void otaTask(void *pvParameter)
} }
while (1) while (1)
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
@@ -1040,7 +1071,8 @@ void setupTasks(bool ota)
void logCoreDump() void logCoreDump()
{ {
coredumpPrinted = false; coredumpPrinted = false;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -1066,7 +1098,8 @@ void logCoreDump()
else else
{ {
file = SPIFFS.open("/coredump.hex", FILE_WRITE); file = SPIFFS.open("/coredump.hex", FILE_WRITE);
if (!file) { if (!file)
{
Log->println("Failed to open /coredump.hex for writing"); Log->println("Failed to open /coredump.hex for writing");
} }
else else
@@ -1106,14 +1139,16 @@ void logCoreDump()
} }
Serial.printf("%s", str_dst); Serial.printf("%s", str_dst);
if (file) { if (file)
{
file.printf("%s", str_dst); file.printf("%s", str_dst);
} }
} }
Serial.println(""); Serial.println("");
if (file) { if (file)
{
file.println(""); file.println("");
file.close(); file.close();
} }
@@ -1132,14 +1167,14 @@ void logCoreDump()
void setup() void setup()
{ {
#if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM) #if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM)
#ifndef FORCE_NUKI_HUB_HTTPS_SERVER #ifndef FORCE_NUKI_HUB_HTTPS_SERVER
if(esp_psram_get_size() <= 0) if(esp_psram_get_size() <= 0)
{ {
nuki_hub_https_server_enabled = false; nuki_hub_https_server_enabled = false;
} }
#endif #endif
#endif #endif
//Set Log level to error for all TAGS //Set Log level to error for all TAGS
esp_log_level_set("*", ESP_LOG_ERROR); esp_log_level_set("*", ESP_LOG_ERROR);
@@ -1149,13 +1184,13 @@ void setup()
Serial.begin(115200); Serial.begin(115200);
Log = &Serial; Log = &Serial;
#if !defined(NUKI_HUB_UPDATER) && !defined(CONFIG_IDF_TARGET_ESP32C5) #if !defined(NUKI_HUB_UPDATER) && !defined(CONFIG_IDF_TARGET_ESP32C5)
stdout = funopen(NULL, NULL, &write_fn, NULL, NULL); stdout = funopen(NULL, NULL, &write_fn, NULL, NULL);
static char linebuf[1024]; static char linebuf[1024];
setvbuf(stdout, linebuf, _IOLBF, sizeof(linebuf)); setvbuf(stdout, linebuf, _IOLBF, sizeof(linebuf));
esp_rom_install_channel_putc(1, &ets_putc_handler); esp_rom_install_channel_putc(1, &ets_putc_handler);
//ets_install_putc1(&ets_putc_handler); //ets_install_putc1(&ets_putc_handler);
#endif #endif
preferences = new Preferences(); preferences = new Preferences();
preferences->begin("nukihub", false); preferences->begin("nukihub", false);
@@ -1221,18 +1256,21 @@ void setup()
{ {
bool failed = true; bool failed = true;
if (!nuki_hub_https_server_enabled) { if (!nuki_hub_https_server_enabled)
{
Log->println("Not running on HTTPS server enabled device"); Log->println("Not running on HTTPS server enabled device");
} }
else else
{ {
if (!SPIFFS.begin(true)) { if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/http_ssl.crt"); File file = SPIFFS.open("/http_ssl.crt");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("http_ssl.crt not found"); Log->println("http_ssl.crt not found");
} }
else else
@@ -1246,7 +1284,8 @@ void setup()
cert[filesize] = '\0'; cert[filesize] = '\0';
File file2 = SPIFFS.open("/http_ssl.key"); File file2 = SPIFFS.open("/http_ssl.key");
if (!file2 || file2.isDirectory()) { if (!file2 || file2.isDirectory())
{
Log->println("http_ssl.key not found"); Log->println("http_ssl.key not found");
} }
else else
@@ -1261,7 +1300,8 @@ void setup()
psychicServer = new PsychicHttpServer(); psychicServer = new PsychicHttpServer();
psychicServer->config.ctrl_port = 20424; psychicServer->config.ctrl_port = 20424;
psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
String url = "https://" + request->host() + request->url(); String url = "https://" + request->host() + request->url();
if (preferences->getString(preference_https_fqdn, "") != "") if (preferences->getString(preference_https_fqdn, "") != "")
{ {
@@ -1279,7 +1319,8 @@ void setup()
psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE; psychicSSLServer->config.stack_size = HTTPD_TASK_SIZE;
webCfgServerSSL = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer, importExport); webCfgServerSSL = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicSSLServer, importExport);
webCfgServerSSL->initialize(); webCfgServerSSL->initialize();
psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicSSLServer->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
return response->redirect("/"); return response->redirect("/");
}); });
psychicSSLServer->begin(); psychicSSLServer->begin();
@@ -1296,7 +1337,8 @@ void setup()
psychicServer->config.stack_size = HTTPD_TASK_SIZE; psychicServer->config.stack_size = HTTPD_TASK_SIZE;
webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer, importExport); webCfgServer = new WebCfgServer(network, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType, psychicServer, importExport);
webCfgServer->initialize(); webCfgServer->initialize();
psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response) { psychicServer->onNotFound([](PsychicRequest* request, PsychicResponse* response)
{
return response->redirect("/"); return response->redirect("/");
}); });
psychicServer->begin(); psychicServer->begin();

View File

@@ -69,7 +69,8 @@ const String EthernetDevice::deviceName() const
void EthernetDevice::initialize() void EthernetDevice::initialize()
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(250 / portTICK_PERIOD_MS); vTaskDelay(250 / portTICK_PERIOD_MS);
@@ -79,7 +80,8 @@ void EthernetDevice::initialize()
Log->println("Failed to initialize ethernet hardware"); Log->println("Failed to initialize ethernet hardware");
Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot."); Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot.");
wifiFallback = true; wifiFallback = true;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -108,11 +110,11 @@ void EthernetDevice::initialize()
// https://github.com/arendst/Tasmota/commit/f8fbe153000591727e40b5007e0de78c33833131 // https://github.com/arendst/Tasmota/commit/f8fbe153000591727e40b5007e0de78c33833131
// https://github.com/arendst/Tasmota/commit/f8fbe153000591727e40b5007e0de78c33833131#diff-32fc0eefbf488dd507b3bef52189bbe37158737aba6f96fe98a8746dc5021955R417 // https://github.com/arendst/Tasmota/commit/f8fbe153000591727e40b5007e0de78c33833131#diff-32fc0eefbf488dd507b3bef52189bbe37158737aba6f96fe98a8746dc5021955R417
uint32_t pkg_version = bootloader_common_get_chip_ver_pkg(); uint32_t pkg_version = bootloader_common_get_chip_ver_pkg();
#if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM) #if defined(CONFIG_SOC_SPIRAM_SUPPORTED) && defined(CONFIG_SPIRAM)
if(esp_psram_get_size() <= 0 && pkg_version <= 3) if(esp_psram_get_size() <= 0 && pkg_version <= 3)
#else #else
if(pkg_version <= 3) if(pkg_version <= 3)
#endif #endif
{ {
esp_gpio_revoke(0xFFFFFFFFFFFFFFFF); esp_gpio_revoke(0xFFFFFFFFFFFFFFFF);
} }
@@ -147,7 +149,8 @@ void EthernetDevice::initialize()
Log->println("Failed to initialize ethernet hardware"); Log->println("Failed to initialize ethernet hardware");
Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot."); Log->println("Network device has a critical failure, enable fallback to Wi-Fi and reboot.");
wifiFallback = true; wifiFallback = true;
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -232,7 +235,8 @@ void EthernetDevice::onNetworkEvent(arduino_event_id_t event, arduino_event_info
void EthernetDevice::reconfigure() void EthernetDevice::reconfigure()
{ {
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);

View File

@@ -1,7 +1,8 @@
#pragma once #pragma once
#if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32P4) #if !defined(CONFIG_IDF_TARGET_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32P4)
typedef enum { typedef enum
{
ETH_CLOCK_GPIO0_IN = 0, ETH_CLOCK_GPIO0_IN = 0,
ETH_CLOCK_GPIO0_OUT = 1, ETH_CLOCK_GPIO0_OUT = 1,
ETH_CLOCK_GPIO16_OUT = 2, ETH_CLOCK_GPIO16_OUT = 2,

View File

@@ -12,14 +12,17 @@ void NetworkDevice::init()
{ {
_useEncryption = false; _useEncryption = false;
if(_preferences->getBool(preference_mqtt_ssl_enabled, false)) { if(_preferences->getBool(preference_mqtt_ssl_enabled, false))
if (!SPIFFS.begin(true)) { {
if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed"); Log->println("SPIFFS Mount Failed");
} }
else else
{ {
File file = SPIFFS.open("/mqtt_ssl.ca"); File file = SPIFFS.open("/mqtt_ssl.ca");
if (!file || file.isDirectory()) { if (!file || file.isDirectory())
{
Log->println("mqtt_ssl.ca not found"); Log->println("mqtt_ssl.ca not found");
} }
else else
@@ -47,7 +50,8 @@ void NetworkDevice::init()
File file2 = SPIFFS.open("/mqtt_ssl.crt"); File file2 = SPIFFS.open("/mqtt_ssl.crt");
File file3 = SPIFFS.open("/mqtt_ssl.key"); File file3 = SPIFFS.open("/mqtt_ssl.key");
if (!file2 || file2.isDirectory() || !file3 || file3.isDirectory()) { if (!file2 || file2.isDirectory() || !file3 || file3.isDirectory())
{
Log->println("mqtt_ssl.crt or mqtt_ssl.key not found"); Log->println("mqtt_ssl.crt or mqtt_ssl.key not found");
} }
else else

View File

@@ -28,7 +28,7 @@ public:
virtual String localIP() = 0; virtual String localIP() = 0;
virtual String BSSIDstr() = 0; virtual String BSSIDstr() = 0;
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
virtual bool isEncrypted(); virtual bool isEncrypted();
virtual bool mqttConnect(); virtual bool mqttConnect();
virtual bool mqttDisconnect(bool force); virtual bool mqttDisconnect(bool force);
@@ -50,12 +50,12 @@ public:
virtual void mqttOnMessage(espMqttClientTypes::OnMessageCallback callback); virtual void mqttOnMessage(espMqttClientTypes::OnMessageCallback callback);
virtual void mqttOnConnect(espMqttClientTypes::OnConnectCallback callback); virtual void mqttOnConnect(espMqttClientTypes::OnConnectCallback callback);
virtual void mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback); virtual void mqttOnDisconnect(espMqttClientTypes::OnDisconnectCallback callback);
#endif #endif
protected: protected:
const IPConfiguration* _ipConfiguration = nullptr; const IPConfiguration* _ipConfiguration = nullptr;
Preferences* _preferences = nullptr; Preferences* _preferences = nullptr;
#ifndef NUKI_HUB_UPDATER #ifndef NUKI_HUB_UPDATER
espMqttClient *_mqttClient = nullptr; espMqttClient *_mqttClient = nullptr;
espMqttClientSecure *_mqttClientSecure = nullptr; espMqttClientSecure *_mqttClientSecure = nullptr;
@@ -67,7 +67,7 @@ protected:
bool _mqttEnabled = true; bool _mqttEnabled = true;
bool _mqttInternal = false; bool _mqttInternal = false;
char* _path; char* _path;
#endif #endif
const String _hostname; const String _hostname;
}; };

View File

@@ -21,7 +21,8 @@ const String WifiDevice::deviceName() const
void WifiDevice::initialize() void WifiDevice::initialize()
{ {
if (_hostname != "fakep4forhosted") { if (_hostname != "fakep4forhosted")
{
ssid = _preferences->getString(preference_wifi_ssid, ""); ssid = _preferences->getString(preference_wifi_ssid, "");
ssid.trim(); ssid.trim();
pass = _preferences->getString(preference_wifi_pass, ""); pass = _preferences->getString(preference_wifi_pass, "");
@@ -61,8 +62,10 @@ void WifiDevice::initialize()
WiFi.disconnect(); WiFi.disconnect();
int loop = 0; int loop = 0;
while (!_wifiClientStarted && loop < 50) { while (!_wifiClientStarted && loop < 50)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -85,8 +88,10 @@ void WifiDevice::scan(bool passive, bool async)
} }
int loop = 0; int loop = 0;
while (!_wifiClientStarted && loop < 50) { while (!_wifiClientStarted && loop < 50)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -122,12 +127,14 @@ void WifiDevice::openAP()
Log->println("Starting AP with SSID NukiHub and Password NukiHubESP32"); Log->println("Starting AP with SSID NukiHub and Password NukiHubESP32");
_startAP = false; _startAP = false;
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_AP);
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
WiFi.softAPsetHostname(_hostname.c_str()); WiFi.softAPsetHostname(_hostname.c_str());
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(500 / portTICK_PERIOD_MS); vTaskDelay(500 / portTICK_PERIOD_MS);
@@ -142,8 +149,10 @@ void WifiDevice::openAP()
bool WifiDevice::connect() bool WifiDevice::connect()
{ {
int loop = 0; int loop = 0;
while (!_wifiClientStarted && loop < 50) { while (!_wifiClientStarted && loop < 50)
if (esp_task_wdt_status(NULL) == ESP_OK) { {
if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -211,7 +220,8 @@ bool WifiDevice::connect()
while(!isConnected() && loop < 600) while(!isConnected() && loop < 600)
{ {
Log->print("."); Log->print(".");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(25 / portTICK_PERIOD_MS); vTaskDelay(25 / portTICK_PERIOD_MS);
@@ -226,7 +236,8 @@ bool WifiDevice::connect()
if(_preferences->getBool(preference_restart_on_disconnect, false) && (espMillis() > 60000)) if(_preferences->getBool(preference_restart_on_disconnect, false) && (espMillis() > 60000))
{ {
Log->println("Restart on disconnect watchdog triggered, rebooting"); Log->println("Restart on disconnect watchdog triggered, rebooting");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(100 / portTICK_PERIOD_MS); vTaskDelay(100 / portTICK_PERIOD_MS);
@@ -253,7 +264,8 @@ void WifiDevice::reconfigure()
{ {
_preferences->putString(preference_wifi_ssid, ""); _preferences->putString(preference_wifi_ssid, "");
_preferences->putString(preference_wifi_pass, ""); _preferences->putString(preference_wifi_pass, "");
if (esp_task_wdt_status(NULL) == ESP_OK) { if (esp_task_wdt_status(NULL) == ESP_OK)
{
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
vTaskDelay(200 / portTICK_PERIOD_MS); vTaskDelay(200 / portTICK_PERIOD_MS);
@@ -307,7 +319,8 @@ void WifiDevice::onWifiEvent(const WiFiEvent_t &event, const WiFiEventInfo_t &in
{ {
Log->printf("[WiFi-event] event: %d\n", event); Log->printf("[WiFi-event] event: %d\n", event);
switch (event) { switch (event)
{
case ARDUINO_EVENT_WIFI_READY: case ARDUINO_EVENT_WIFI_READY:
Log->println("WiFi interface ready"); Log->println("WiFi interface ready");
break; break;

View File

@@ -11,9 +11,9 @@
NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDeviceType, String hostname, Preferences *preferences, IPConfiguration *ipConfiguration) NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDeviceType, String hostname, Preferences *preferences, IPConfiguration *ipConfiguration)
{ {
NetworkDevice* device = nullptr; NetworkDevice* device = nullptr;
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
bool fakedevice = true; bool fakedevice = true;
#endif #endif
switch (networkDeviceType) switch (networkDeviceType)
{ {
@@ -94,7 +94,7 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
ETH_PHY_SPI_MOSI_ETHLITES3_W5500, ETH_PHY_SPI_MOSI_ETHLITES3_W5500,
ETH_PHY_W5500); ETH_PHY_W5500);
break; break;
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
case NetworkDeviceType::Waveshare_ESP32_P4_NANO: case NetworkDeviceType::Waveshare_ESP32_P4_NANO:
device = new EthernetDevice(hostname, preferences, ipConfiguration, "Waveshare ESP32-P4-NANO", device = new EthernetDevice(hostname, preferences, ipConfiguration, "Waveshare ESP32-P4-NANO",
1, 1,
@@ -122,7 +122,7 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
ETH_PHY_IP101, ETH_PHY_IP101,
ETH_CLOCK_GPIO0_IN); ETH_CLOCK_GPIO0_IN);
break; break;
#endif #endif
case NetworkDeviceType::CUSTOM: case NetworkDeviceType::CUSTOM:
{ {
int custPHY = preferences->getInt(preference_network_custom_phy, 0); int custPHY = preferences->getInt(preference_network_custom_phy, 0);
@@ -184,9 +184,9 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
else else
{ {
device = new WifiDevice(hostname, preferences, ipConfiguration); device = new WifiDevice(hostname, preferences, ipConfiguration);
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
fakedevice = false; fakedevice = false;
#endif #endif
} }
#endif #endif
} }
@@ -217,15 +217,15 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
#ifndef CONFIG_IDF_TARGET_ESP32H2 #ifndef CONFIG_IDF_TARGET_ESP32H2
case NetworkDeviceType::WiFi: case NetworkDeviceType::WiFi:
device = new WifiDevice(hostname, preferences, ipConfiguration); device = new WifiDevice(hostname, preferences, ipConfiguration);
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
fakedevice = false; fakedevice = false;
#endif #endif
break; break;
default: default:
device = new WifiDevice(hostname, preferences, ipConfiguration); device = new WifiDevice(hostname, preferences, ipConfiguration);
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
fakedevice = false; fakedevice = false;
#endif #endif
break; break;
#else #else
default: default:
@@ -242,8 +242,9 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
#endif #endif
} }
#if defined(CONFIG_IDF_TARGET_ESP32P4) #if defined(CONFIG_IDF_TARGET_ESP32P4)
if (fakedevice) { if (fakedevice)
{
Log->println("Create dummy WiFi device for Hosted on P4"); Log->println("Create dummy WiFi device for Hosted on P4");
NetworkDevice* device2 = nullptr; NetworkDevice* device2 = nullptr;
device2 = new WifiDevice("fakep4forhosted", preferences, ipConfiguration); device2 = new WifiDevice("fakep4forhosted", preferences, ipConfiguration);
@@ -251,7 +252,7 @@ NetworkDevice *NetworkDeviceInstantiator::Create(NetworkDeviceType networkDevice
delete device2; delete device2;
device2 = NULL; device2 = NULL;
} }
#endif #endif
return device; return device;
} }

View File

@@ -28,42 +28,51 @@ SSLCert::SSLCert(uint16_t certLength, uint16_t pkLength, String keyPEM, String c
_certLength(certLength), _certLength(certLength),
_pkLength(pkLength), _pkLength(pkLength),
_keyPEM(keyPEM), _keyPEM(keyPEM),
_certPEM(certPEM) { _certPEM(certPEM)
{
} }
SSLCert::~SSLCert() { SSLCert::~SSLCert()
{
// TODO Auto-generated destructor stub // TODO Auto-generated destructor stub
} }
uint16_t SSLCert::getCertLength() { uint16_t SSLCert::getCertLength()
{
return _certLength; return _certLength;
} }
uint16_t SSLCert::getPKLength() { uint16_t SSLCert::getPKLength()
{
return _pkLength; return _pkLength;
} }
String SSLCert::getKeyPEM() { String SSLCert::getKeyPEM()
{
return _keyPEM; return _keyPEM;
} }
String SSLCert::getCertPEM() { String SSLCert::getCertPEM()
{
return _certPEM; return _certPEM;
} }
void SSLCert::setPK(String keyPEM) { void SSLCert::setPK(String keyPEM)
{
_keyPEM = keyPEM; _keyPEM = keyPEM;
_pkLength = keyPEM.length(); _pkLength = keyPEM.length();
} }
void SSLCert::setCert(String certPEM) { void SSLCert::setCert(String certPEM)
{
_certPEM = certPEM; _certPEM = certPEM;
_certLength = certPEM.length(); _certLength = certPEM.length();
} }
void SSLCert::clear() { void SSLCert::clear()
{
_certLength = 0; _certLength = 0;
_pkLength = 0; _pkLength = 0;
@@ -74,14 +83,17 @@ void SSLCert::clear() {
/** /**
* Returns the CN value from a DN, or "" if it cannot be found * Returns the CN value from a DN, or "" if it cannot be found
*/ */
static std::string get_cn(std::string dn) { static std::string get_cn(std::string dn)
{
size_t cnStart = dn.find("CN="); size_t cnStart = dn.find("CN=");
if (cnStart == std::string::npos) { if (cnStart == std::string::npos)
{
return ""; return "";
} }
cnStart += 3; cnStart += 3;
size_t cnStop = dn.find(",", cnStart); size_t cnStop = dn.find(",", cnStart);
if (cnStop == std::string::npos) { if (cnStop == std::string::npos)
{
cnStop = dn.length(); cnStop = dn.length();
} }
return dn.substr(cnStart, cnStop - cnStart); return dn.substr(cnStart, cnStop - cnStart);
@@ -90,7 +102,8 @@ static std::string get_cn(std::string dn) {
/** /**
* Sets the DN as subjectAltName extension in the certificate * Sets the DN as subjectAltName extension in the certificate
*/ */
static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn) { static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn)
{
size_t bufsize = cn.length() + 8; // some additional space for tags and length fields size_t bufsize = cn.length() + 8; // some additional space for tags and length fields
uint8_t buf[bufsize]; uint8_t buf[bufsize];
uint8_t *p = &buf[bufsize - 1]; uint8_t *p = &buf[bufsize - 1];
@@ -128,7 +141,8 @@ static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn) {
* *
* Based on programs/pkey/gen_key.c * Based on programs/pkey/gen_key.c
*/ */
static int gen_key(SSLCert &certCtx, SSLKeySize keySize) { static int gen_key(SSLCert &certCtx, SSLKeySize keySize)
{
// Initialize the entropy source // Initialize the entropy source
mbedtls_entropy_context entropy; mbedtls_entropy_context entropy;
@@ -141,7 +155,8 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
&ctr_drbg, mbedtls_entropy_func, &entropy, &ctr_drbg, mbedtls_entropy_func, &entropy,
NULL, 0 NULL, 0
); );
if (rngRes != 0) { if (rngRes != 0)
{
mbedtls_entropy_free( &entropy ); mbedtls_entropy_free( &entropy );
return HTTPS_SERVER_ERROR_KEYGEN_RNG; return HTTPS_SERVER_ERROR_KEYGEN_RNG;
} }
@@ -150,7 +165,8 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
mbedtls_pk_context key; mbedtls_pk_context key;
mbedtls_pk_init( &key ); mbedtls_pk_init( &key );
int resPkSetup = mbedtls_pk_setup( &key, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) ); int resPkSetup = mbedtls_pk_setup( &key, mbedtls_pk_info_from_type( MBEDTLS_PK_RSA ) );
if ( resPkSetup != 0) { if ( resPkSetup != 0)
{
mbedtls_ctr_drbg_free( &ctr_drbg ); mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy ); mbedtls_entropy_free( &entropy );
return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK; return HTTPS_SERVER_ERROR_KEYGEN_SETUP_PK;
@@ -164,7 +180,8 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
keySize, keySize,
65537 65537
); );
if ( resPkGen != 0) { if ( resPkGen != 0)
{
mbedtls_pk_free( &key ); mbedtls_pk_free( &key );
mbedtls_ctr_drbg_free( &ctr_drbg ); mbedtls_ctr_drbg_free( &ctr_drbg );
mbedtls_entropy_free( &entropy ); mbedtls_entropy_free( &entropy );
@@ -177,7 +194,8 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
// Allocate the space on the heap, as stack size is quite limited // Allocate the space on the heap, as stack size is quite limited
unsigned char * output_buf = new unsigned char[4096]; unsigned char * output_buf = new unsigned char[4096];
if (output_buf == NULL) { if (output_buf == NULL)
{
mbedtls_pk_free( &key ); mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM; return HTTPS_SERVER_ERROR_KEY_OUT_OF_MEM;
} }
@@ -185,7 +203,8 @@ static int gen_key(SSLCert &certCtx, SSLKeySize keySize) {
// Write the key to the temporary buffer and determine its length // Write the key to the temporary buffer and determine its length
int resPkWrite = mbedtls_pk_write_key_pem( &key, output_buf, 4096 ); int resPkWrite = mbedtls_pk_write_key_pem( &key, output_buf, 4096 );
if (resPkWrite < 0) { if (resPkWrite < 0)
{
delete[] output_buf; delete[] output_buf;
mbedtls_pk_free( &key ); mbedtls_pk_free( &key );
return HTTPS_SERVER_ERROR_KEY_WRITE_PK; return HTTPS_SERVER_ERROR_KEY_WRITE_PK;
@@ -214,21 +233,25 @@ static int parse_serial_decimal_format(unsigned char *obuf, size_t obufmax,
errno = 0; errno = 0;
dec = strtoull(ibuf, &end_ptr, 10); dec = strtoull(ibuf, &end_ptr, 10);
if ((errno != 0) || (end_ptr == ibuf)) { if ((errno != 0) || (end_ptr == ibuf))
{
return -1; return -1;
} }
*len = 0; *len = 0;
while (remaining_bytes > 0) { while (remaining_bytes > 0)
if (obufmax < (*len + 1)) { {
if (obufmax < (*len + 1))
{
return -1; return -1;
} }
val = (dec >> ((remaining_bytes - 1) * 8)) & 0xFF; val = (dec >> ((remaining_bytes - 1) * 8)) & 0xFF;
/* Skip leading zeros */ /* Skip leading zeros */
if ((val != 0) || (*len != 0)) { if ((val != 0) || (*len != 0))
{
*p = val; *p = val;
(*len)++; (*len)++;
p++; p++;
@@ -248,7 +271,8 @@ static int parse_serial_decimal_format(unsigned char *obuf, size_t obufmax,
* Based on programs/x509/cert_write.c * Based on programs/x509/cert_write.c
*/ */
static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo) { static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom, std::string validityTo)
{
int funcRes = 0; int funcRes = 0;
int stepRes = 0; int stepRes = 0;
@@ -268,7 +292,8 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
strcpy(dn_cstr, dn.c_str()); strcpy(dn_cstr, dn.c_str());
std::string cn = get_cn(dn); std::string cn = get_cn(dn);
if (cn == "") { if (cn == "")
{
return HTTPS_SERVER_ERROR_CERTGEN_CN; return HTTPS_SERVER_ERROR_CERTGEN_CN;
} }
@@ -278,7 +303,8 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
// Initialize the RNG // Initialize the RNG
mbedtls_ctr_drbg_init( &ctr_drbg ); mbedtls_ctr_drbg_init( &ctr_drbg );
stepRes = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0 ); stepRes = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, NULL, 0 );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_RNG; funcRes = HTTPS_SERVER_ERROR_CERTGEN_RNG;
goto error_after_entropy; goto error_after_entropy;
} }
@@ -286,7 +312,8 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
mbedtls_pk_init( &key ); mbedtls_pk_init( &key );
stepRes = mbedtls_pk_parse_key( &key, (const unsigned char *)certCtx.getKeyPEM().c_str(), certCtx.getPKLength() + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg); stepRes = mbedtls_pk_parse_key( &key, (const unsigned char *)certCtx.getKeyPEM().c_str(), certCtx.getPKLength() + 1, NULL, 0, mbedtls_ctr_drbg_random, &ctr_drbg);
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY; funcRes = HTTPS_SERVER_ERROR_CERTGEN_READKEY;
goto error_after_key; goto error_after_key;
} }
@@ -303,53 +330,61 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom
// Set issuer and subject (same, as we self-sign) // Set issuer and subject (same, as we self-sign)
stepRes = mbedtls_x509write_crt_set_subject_name( &crt, dn_cstr ); stepRes = mbedtls_x509write_crt_set_subject_name( &crt, dn_cstr );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME; funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert; goto error_after_cert;
} }
stepRes = mbedtls_x509write_crt_set_issuer_name( &crt, dn_cstr ); stepRes = mbedtls_x509write_crt_set_issuer_name( &crt, dn_cstr );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME; funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert; goto error_after_cert;
} }
// Set the validity of the certificate. At the moment, it's fixed from 2019 to end of 2029. // Set the validity of the certificate. At the moment, it's fixed from 2019 to end of 2029.
stepRes = mbedtls_x509write_crt_set_validity( &crt, validityFrom.c_str(), validityTo.c_str()); stepRes = mbedtls_x509write_crt_set_validity( &crt, validityFrom.c_str(), validityTo.c_str());
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY; funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
goto error_after_cert; goto error_after_cert;
} }
// Make this a CA certificate // Make this a CA certificate
stepRes = mbedtls_x509write_crt_set_basic_constraints( &crt, 1, 0 ); stepRes = mbedtls_x509write_crt_set_basic_constraints( &crt, 1, 0 );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY; funcRes = HTTPS_SERVER_ERROR_CERTGEN_VALIDITY;
goto error_after_cert; goto error_after_cert;
} }
stepRes = add_subject_alt_name( &crt, cn ); stepRes = add_subject_alt_name( &crt, cn );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME; funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME;
goto error_after_cert; goto error_after_cert;
} }
// Initialize the serial number // Initialize the serial number
stepRes = mbedtls_x509write_crt_set_serial_raw( &crt, (unsigned char *)serial, strlen(serial) ); stepRes = mbedtls_x509write_crt_set_serial_raw( &crt, (unsigned char *)serial, strlen(serial) );
if (stepRes != 0) { if (stepRes != 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL; funcRes = HTTPS_SERVER_ERROR_CERTGEN_SERIAL;
goto error_after_cert_serial; goto error_after_cert_serial;
} }
// Create buffer to write the certificate // Create buffer to write the certificate
primary_buffer = new unsigned char[4096]; primary_buffer = new unsigned char[4096];
if (primary_buffer == NULL) { if (primary_buffer == NULL)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM; funcRes = HTTPS_SERVER_ERROR_CERTGEN_OUT_OF_MEM;
goto error_after_cert_serial; goto error_after_cert_serial;
} }
// Write the actual certificate // Write the actual certificate
stepRes = mbedtls_x509write_crt_pem(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg ); stepRes = mbedtls_x509write_crt_pem(&crt, primary_buffer, 4096, mbedtls_ctr_drbg_random, &ctr_drbg );
if (stepRes < 0) { if (stepRes < 0)
{
funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE; funcRes = HTTPS_SERVER_ERROR_CERTGEN_WRITE;
goto error_after_primary_buffer; goto error_after_primary_buffer;
} }
@@ -375,18 +410,21 @@ error_after_entropy:
return funcRes; return funcRes;
} }
int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom, std::string validUntil) { int createSelfSignedCert(SSLCert &certCtx, SSLKeySize keySize, std::string dn, std::string validFrom, std::string validUntil)
{
// Add the private key // Add the private key
int keyRes = gen_key(certCtx, keySize); int keyRes = gen_key(certCtx, keySize);
if (keyRes != 0) { if (keyRes != 0)
{
// Key-generation failed, return the failure code // Key-generation failed, return the failure code
return keyRes; return keyRes;
} }
// Add the self-signed certificate // Add the self-signed certificate
int certRes = cert_write(certCtx, dn, validFrom, validUntil); int certRes = cert_write(certCtx, dn, validFrom, validUntil);
if (certRes != 0) { if (certRes != 0)
{
// Cert writing failed, reset the pk and return failure code // Cert writing failed, reset the pk and return failure code
certCtx.setPK(""); certCtx.setPK("");
return certRes; return certRes;

View File

@@ -1,363 +1,344 @@
0x2f, 0x2a, 0x0d, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x2f, 0x2a, 0x0a, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x68, 0x74,
0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x63, 0x65, 0x73, 0x63, 0x61, 0x70,
0x70, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0d, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x65, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x20,
0x3a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x63, 0x64, 0x6e, 0x2e, 0x6a, 0x73, 0x64, 0x65,
0x64, 0x65, 0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40, 0x6c, 0x69, 0x76, 0x72, 0x2e, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x70, 0x6d, 0x2f, 0x40, 0x65, 0x78,
0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x64, 0x65, 0x76, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x63, 0x73, 0x73,
0x73, 0x73, 0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e, 0x40, 0x31, 0x2e, 0x31, 0x2e, 0x32, 0x2f, 0x6e, 0x65, 0x77, 0x2e, 0x6d, 0x69, 0x6e, 0x2e, 0x63,
0x2e, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d, 0x0a, 0x0d, 0x0a, 0x2f, 0x2a, 0x0d, 0x0a, 0x73, 0x73, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x2f, 0x2a, 0x0a, 0x20, 0x2a, 0x20, 0x55, 0x73, 0x61,
0x20, 0x2a, 0x20, 0x55, 0x73, 0x61, 0x67, 0x65, 0x3a, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x67, 0x65, 0x3a, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f,
0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x2f, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x66, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64,
0x68, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0a,
0x79, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20, 0x20, 0x2a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65,
0x6f, 0x6e, 0x65, 0x2d, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57,
0x74, 0x6f, 0x20, 0x73, 0x72, 0x63, 0x2f, 0x57, 0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72, 0x65, 0x62, 0x43, 0x66, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74,
0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61, 0x61, 0x6e, 0x74, 0x73, 0x2e, 0x68, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63,
0x73, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x63, 0x73, 0x73, 0x0d, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x73, 0x73, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f,
0x4f, 0x44, 0x4f, 0x3a, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x6d, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x73, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20,
0x62, 0x75, 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x3a, 0x29, 0x0d, 0x0a, 0x2a, 0x2f, 0x0d, 0x3a, 0x29, 0x0a, 0x2a, 0x2f, 0x0a, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x0a, 0x0d, 0x0a, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x3a, 0x20, 0x73, 0x3a, 0x20, 0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c,
0x27, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x27, 0x2c, 0x2d, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2d, 0x73, 0x65, 0x2d, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61,
0x79, 0x73, 0x74, 0x65, 0x6d, 0x2c, 0x42, 0x6c, 0x69, 0x6e, 0x6b, 0x4d, 0x61, 0x63, 0x53, 0x79, 0x63, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67,
0x73, 0x74, 0x65, 0x6d, 0x46, 0x6f, 0x6e, 0x74, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78,
0x55, 0x49, 0x27, 0x2c, 0x52, 0x6f, 0x62, 0x6f, 0x74, 0x6f, 0x2c, 0x4f, 0x78, 0x79, 0x67, 0x65, 0x79, 0x67, 0x65, 0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74,
0x6e, 0x2c, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x2c, 0x43, 0x61, 0x6e, 0x74, 0x61, 0x72, 0x65, 0x61, 0x72, 0x65, 0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73,
0x6c, 0x6c, 0x2c, 0x27, 0x4f, 0x70, 0x65, 0x6e, 0x20, 0x53, 0x61, 0x6e, 0x73, 0x27, 0x2c, 0x27, 0x27, 0x2c, 0x27, 0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75,
0x48, 0x65, 0x6c, 0x76, 0x65, 0x74, 0x69, 0x63, 0x61, 0x20, 0x4e, 0x65, 0x75, 0x65, 0x27, 0x2c, 0x65, 0x27, 0x2c, 0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41,
0x73, 0x61, 0x6e, 0x73, 0x2d, 0x73, 0x65, 0x72, 0x69, 0x66, 0x2c, 0x27, 0x41, 0x70, 0x70, 0x6c, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69,
0x65, 0x20, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c, 0x27, 0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a,
0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x45, 0x6d, 0x6f, 0x6a, 0x69, 0x27, 0x2c, 0x69, 0x27, 0x2c, 0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d,
0x27, 0x53, 0x65, 0x67, 0x6f, 0x65, 0x20, 0x55, 0x49, 0x20, 0x53, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x62, 0x6f, 0x6c, 0x27, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x27, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x6f, 0x6e, 0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c,
0x74, 0x2d, 0x6d, 0x6f, 0x6e, 0x6f, 0x3a, 0x20, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x61, 0x73, 0x61, 0x73, 0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74,
0x2c, 0x6d, 0x6f, 0x6e, 0x61, 0x63, 0x6f, 0x2c, 0x27, 0x55, 0x62, 0x75, 0x6e, 0x74, 0x75, 0x20, 0x75, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74,
0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x4c, 0x69, 0x62, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x69, 0x6f, 0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69,
0x6e, 0x20, 0x4d, 0x6f, 0x6e, 0x6f, 0x27, 0x2c, 0x27, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x65, 0x72, 0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c,
0x20, 0x4e, 0x65, 0x77, 0x27, 0x2c, 0x43, 0x6f, 0x75, 0x72, 0x69, 0x65, 0x72, 0x2c, 0x6d, 0x6f, 0x6d, 0x6f, 0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x6e, 0x6f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0a,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x23,
0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x31, 0x61, 0x31, 0x61, 0x31, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63,
0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x66, 0x36, 0x66,
0x36, 0x66, 0x38, 0x66, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x38, 0x66, 0x61, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0d, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x65, 0x35, 0x65, 0x37, 0x65, 0x62, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37,
0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b,
0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x33, 0x36, 0x36, 0x64, 0x36, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31,
0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x66, 0x66, 0x65, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30,
0x78, 0x3a, 0x20, 0x23, 0x30, 0x63, 0x34, 0x30, 0x34, 0x37, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x34, 0x37, 0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65,
0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x28, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x73, 0x2d, 0x66, 0x65, 0x72, 0x73, 0x2d, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d,
0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x2d, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x3a, 0x20, 0x64, 0x61, 0x65, 0x3a, 0x20, 0x64, 0x61, 0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a,
0x72, 0x6b, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x3a, 0x72, 0x6f, 0x6f, 0x74, 0x72, 0x6f, 0x6f, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x2d, 0x74, 0x78, 0x2d, 0x31, 0x3a, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32,
0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x3a, 0x20, 0x3a, 0x20, 0x23, 0x65, 0x65, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x23, 0x65, 0x65, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b,
0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x30, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x31, 0x31, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x3a, 0x20, 0x23, 0x32, 0x32,
0x32, 0x32, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x32, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b, 0x6c, 0x6b, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x33, 0x32, 0x39, 0x31, 0x66, 0x66, 0x3b, 0x0a, 0x20,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32,
0x6b, 0x2d, 0x32, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0d, 0x0a, 0x20, 0x3a, 0x20, 0x23, 0x30, 0x30, 0x37, 0x30, 0x66, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23,
0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x66, 0x66, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e,
0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x3a, 0x20, 0x23, 0x37, 0x39, 0x32, 0x38, 0x63, 0x61, 0x3b,
0x32, 0x38, 0x63, 0x61, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63,
0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0d, 0x2d, 0x74, 0x78, 0x3a, 0x20, 0x23, 0x66, 0x66, 0x66, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2a, 0x20, 0x7b, 0x7d, 0x0a, 0x0a, 0x2a, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x6e, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x30, 0x67, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x74, 0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65,
0x2c, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x70, 0x2c, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2c, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0a, 0x20,
0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x75, 0x6c, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x75, 0x74, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e,
0x74, 0x6f, 0x6e, 0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x2c, 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65,
0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x66, 0x61,
0x74, 0x2d, 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6d, 0x69, 0x6c, 0x79, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x66,
0x6e, 0x63, 0x2d, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0d, 0x0a, 0x7d, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x61, 0x6e, 0x73, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x62, 0x6f, 0x64,
0x0d, 0x0a, 0x0d, 0x0a, 0x62, 0x6f, 0x64, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0d, 0x30, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20,
0x37, 0x35, 0x30, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b,
0x69, 0x6e, 0x67, 0x3a, 0x20, 0x32, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69,
0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x36, 0x75, 0x73, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x6c, 0x6f, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b,
0x77, 0x2d, 0x78, 0x3a, 0x20, 0x68, 0x69, 0x64, 0x64, 0x65, 0x6e, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a,
0x20, 0x20, 0x77, 0x6f, 0x72, 0x64, 0x2d, 0x62, 0x72, 0x65, 0x61, 0x6b, 0x3a, 0x20, 0x6e, 0x6f, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65,
0x72, 0x6d, 0x61, 0x6c, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x66, 0x72, 0x66, 0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77,
0x6c, 0x6f, 0x77, 0x2d, 0x77, 0x72, 0x61, 0x70, 0x3a, 0x20, 0x61, 0x6e, 0x79, 0x77, 0x68, 0x65, 0x68, 0x65, 0x72, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72,
0x72, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x67, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20,
0x20, 0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x31, 0x2e, 0x30, 0x33, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e,
0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0d, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x0a, 0x7d, 0x0a,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x0a, 0x3a, 0x3a, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20,
0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76,
0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20,
0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c,
0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x2e, 0x38,
0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a,
0x20, 0x38, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f,
0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d,
0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68,
0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d,
0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31,
0x2e, 0x35, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x68, 0x34, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65,
0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x35, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a,
0x68, 0x36, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31,
0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20,
0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61,
0x62, 0x62, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x61, 0x62,
0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0d, 0x0a, 0x7d, 0x0d,
0x0a, 0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74,
0x6f, 0x6e, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65,
0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78,
0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f,
0x77, 0x72, 0x61, 0x70, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74,
0x78, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a,
0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d,
0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62,
0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61,
0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x73, 0x65, 0x74, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69,
0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74,
0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75,
0x6c, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79,
0x3a, 0x20, 0x2e, 0x35, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f,
0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0d, 0x0a,
0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63,
0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72,
0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75,
0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f,
0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62,
0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66,
0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70,
0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65,
0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29,
0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x7b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c, 0x6c, 0x61,
0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b, 0x0d, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73,
0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67,
0x2d, 0x33, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0d,
0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x68, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d,
0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x28, 0x65, 0x76,
0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x62, 0x67, 0x2d, 0x32, 0x29, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a,
0x0d, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74,
0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70,
0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f,
0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61,
0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6e, 0x63, 0x2d, 0x61, 0x63, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c,
0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x68, 0x32, 0x2c, 0x68, 0x33, 0x2c, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b,
0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0a,
0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f, 0x70, 0x3a,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x2c, 0x68,
0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x0d, 0x0a, 0x7d, 0x0d, 0x32, 0x2c, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x0a, 0x0d, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f,
0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d,
0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x38, 0x70,
0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f,
0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20,
0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x0a, 0x68, 0x34, 0x2c, 0x68, 0x35, 0x2c, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x74, 0x64, 0x3e, 0x2e, 0x33, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x31, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x2e, 0x32,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x32, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x38, 0x35,
0x20, 0x30, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x33, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x67, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x35, 0x72,
0x72, 0x65, 0x64, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x34, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66,
0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x2e, 0x32, 0x35, 0x72, 0x65,
0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x68, 0x35, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x70, 0x78, 0x29, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a,
0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x0a, 0x68, 0x36, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73,
0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x20, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x2e, 0x38, 0x37, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76,
0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x74, 0x65, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x0a, 0x7d, 0x0a,
0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x0a, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5d, 0x2c, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c,
0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x6b, 0x2d, 0x32, 0x29, 0x20, 0x21, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x3b,
0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x0a, 0x7d, 0x0a, 0x0a, 0x61, 0x62, 0x62, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x62, 0x72, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x68, 0x65, 0x6c, 0x70, 0x0a, 0x7d, 0x0a, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x2c,
0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x29, 0x6e, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x73, 0x65, 0x74, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0d, 0x0a, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20,
0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69,
0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61,
0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a,
0x74, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78,
0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f,
0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x69, 0x74, 0x65, 0x2d, 0x73, 0x70,
0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x61, 0x63, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x77, 0x72, 0x61, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0d, 0x0a, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72,
0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x6c, 0x61, 0x73, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72,
0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23, 0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20,
0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0d, 0x0a, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x3b,
0x20, 0x20, 0x7d, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28,
0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x6c, 0x6b, 0x2d, 0x74, 0x78, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x61,
0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x20, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x5d, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
0x6c, 0x69, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75,
0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c,
0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5d, 0x5b, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x5b, 0x64,
0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d, 0x20, 0x30, 0x3b, 0x0d, 0x0a, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63,
0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3b, 0x0a,
0x20, 0x31, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x3a, 0x20, 0x2e, 0x35, 0x3b,
0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x3a, 0x20, 0x6e, 0x6f, 0x74,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f, 0x72, 0x61, 0x2d, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x62, 0x75, 0x74,
0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x6e, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x2e, 0x62, 0x75, 0x74, 0x74, 0x6f,
0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x66,
0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x3a, 0x68, 0x6f, 0x76, 0x65,
0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74,
0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74,
0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x5d, 0x3a, 0x68, 0x6f,
0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70, 0x75,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x72, 0x65, 0x73, 0x65, 0x74, 0x5d, 0x3a, 0x68, 0x6f,
0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73, 0x76, 0x65, 0x72, 0x2c, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a, 0x66, 0x6f, 0x63, 0x75, 0x73, 0x2c, 0x69, 0x6e, 0x70,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x3a,
0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2c, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20, 0x2d, 0x6c, 0x6b, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20,
0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x63, 0x6f, 0x6c,
0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3a, 0x20, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x3b,
0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x2c, 0x74, 0x68, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29,
0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64,
0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x68,
0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e,
0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61, 0x63, 0x74, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32,
0x69, 0x76, 0x65, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x72, 0x3a, 0x6e, 0x74, 0x68, 0x2d, 0x63, 0x68, 0x69, 0x6c,
0x64, 0x28, 0x65, 0x76, 0x65, 0x6e, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61,
0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d,
0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74,
0x61, 0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6e,
0x70, 0x75, 0x74, 0x2c, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x61,
0x72, 0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x20, 0x36, 0x70, 0x78, 0x20, 0x31, 0x32, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67,
0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d,
0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x32, 0x29,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x31, 0x70,
0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x62, 0x67, 0x2d, 0x33, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64,
0x65, 0x72, 0x2d, 0x72, 0x61, 0x64, 0x69, 0x75, 0x73, 0x3a, 0x20, 0x34, 0x70, 0x78, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x68, 0x61, 0x64, 0x6f, 0x77, 0x3a, 0x20,
0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x78, 0x2d, 0x73, 0x69,
0x7a, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x78,
0x0a, 0x7d, 0x0a, 0x0a, 0x69, 0x6d, 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a,
0x0a, 0x74, 0x64, 0x3e, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x74, 0x64, 0x3e, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72,
0x65, 0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d,
0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67,
0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a,
0x74, 0x64, 0x3e, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x74, 0x6f, 0x70, 0x3a, 0x20, 0x30, 0x3b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x3a, 0x20, 0x30, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x20,
0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x72, 0x65, 0x64,
0x0a, 0x7d, 0x0a, 0x0a, 0x40, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20,
0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x28, 0x6d, 0x61, 0x78, 0x2d,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x36, 0x30, 0x30, 0x70, 0x78, 0x29, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x20, 0x7b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79,
0x70, 0x65, 0x3d, 0x74, 0x65, 0x78, 0x74, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20,
0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x70, 0x61, 0x73, 0x73, 0x77,
0x6f, 0x72, 0x64, 0x5d, 0x2c, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x73, 0x75, 0x62, 0x6d, 0x69, 0x74, 0x5d, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x61, 0x72, 0x65, 0x61, 0x2c, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x20, 0x7b, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30,
0x30, 0x25, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61,
0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x64, 0x3a, 0x68, 0x61, 0x73, 0x28, 0x69, 0x6e, 0x70, 0x75,
0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x62, 0x6f, 0x78, 0x5d,
0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x0a, 0x20,
0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74,
0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5b, 0x74, 0x79, 0x70, 0x65, 0x3d, 0x63, 0x68, 0x65, 0x63,
0x6b, 0x62, 0x6f, 0x78, 0x5d, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x2e, 0x35, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x2e,
0x35, 0x65, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2e,
0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64, 0x3a, 0x66,
0x69, 0x72, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74,
0x6f, 0x6d, 0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20,
0x20, 0x2e, 0x61, 0x64, 0x61, 0x70, 0x74, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x64,
0x3a, 0x6c, 0x61, 0x73, 0x74, 0x2d, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x74, 0x6f, 0x70,
0x3a, 0x20, 0x30, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x23,
0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e,
0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77,
0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x34, 0x30, 0x70, 0x78, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x7d, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x7b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x20, 0x30, 0x3b, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f,
0x6d, 0x3a, 0x20, 0x31, 0x70, 0x78, 0x20, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b,
0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a,
0x20, 0x31, 0x72, 0x65, 0x6d, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d,
0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x62, 0x6f, 0x6c, 0x64, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x36, 0x72, 0x65, 0x6d,
0x20, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f,
0x72, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x74, 0x78, 0x2d, 0x31,
0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x64, 0x65, 0x63, 0x6f,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69,
0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69, 0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f,
0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x61, 0x72, 0x65, 0x6e,
0x74, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72, 0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32,
0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29,
0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30,
0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x32, 0x73, 0x20,
0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20,
0x61, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
0x6e, 0x64, 0x3a, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x2d, 0x67, 0x72, 0x61, 0x64, 0x69,
0x65, 0x6e, 0x74, 0x28, 0x74, 0x6f, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x2c, 0x76, 0x61, 0x72, 0x28,
0x2d, 0x2d, 0x6e, 0x63, 0x2d, 0x62, 0x67, 0x2d, 0x32, 0x29, 0x20, 0x35, 0x30, 0x25, 0x2c, 0x72,
0x67, 0x62, 0x61, 0x28, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c, 0x32, 0x35, 0x35, 0x2c,
0x30, 0x2e, 0x34, 0x29, 0x20, 0x35, 0x30, 0x25, 0x29, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2d,
0x73, 0x69, 0x7a, 0x65, 0x3a, 0x20, 0x32, 0x30, 0x30, 0x25, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a,
0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x68, 0x6f, 0x76,
0x65, 0x72, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f,
0x75, 0x6e, 0x64, 0x2d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x6c, 0x65,
0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69,
0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x34, 0x35, 0x73, 0x20, 0x65, 0x61, 0x73,
0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x3a, 0x61,
0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x62, 0x61, 0x63, 0x6b,
0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x3a, 0x20, 0x76, 0x61, 0x72, 0x28, 0x2d, 0x2d, 0x6e, 0x63,
0x2d, 0x6c, 0x6b, 0x2d, 0x31, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x31, 0x35, 0x73, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x2e, 0x31, 0x35, 0x73,
0x20, 0x65, 0x61, 0x73, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x20, 0x65, 0x61, 0x73, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76,
0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x69, 0x73, 0x74,
0x6c, 0x69, 0x73, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x6e, 0x6f, 0x6e, 0x65, 0x3b, 0x0a, 0x20, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x20, 0x20, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x3a, 0x20, 0x2e, 0x35, 0x72, 0x65, 0x6d,
0x2e, 0x35, 0x72, 0x65, 0x6d, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69,
0x6c, 0x61, 0x79, 0x3a, 0x20, 0x69, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6e, 0x6c, 0x69, 0x6e, 0x65, 0x2d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x6b, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x20, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d, 0x0a, 0x0a,
0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x23, 0x74, 0x62, 0x6c, 0x6e, 0x61, 0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61,
0x76, 0x20, 0x61, 0x20, 0x6c, 0x69, 0x3e, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61,
0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20,
0x3a, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x20, 0x6d, 0x61, 0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31,
0x72, 0x67, 0x69, 0x6e, 0x2d, 0x72, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x70, 0x78, 0x30, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20,
0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x3a, 0x20, 0x23, 0x66, 0x23, 0x66, 0x37, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77,
0x37, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x77, 0x65, 0x65, 0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x69, 0x67, 0x68, 0x74, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x74, 0x61, 0x6c, 0x66, 0x6f, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3a, 0x20, 0x69, 0x74, 0x61, 0x6c,
0x69, 0x63, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x63, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x3a,
0x3a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x0d, 0x0a, 0x2e, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x74, 0x64, 0x62, 0x74, 0x6e,
0x64, 0x62, 0x74, 0x6e, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x74, 0x65, 0x78, 0x74, 0x2d, 0x61, 0x6c, 0x69, 0x67,
0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0d, 0x6e, 0x3a, 0x20, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x76,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x65, 0x72, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x2d, 0x61, 0x6c, 0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d,
0x69, 0x67, 0x6e, 0x3a, 0x20, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x0d, 0x0a, 0x7d, 0x0d, 0x0a, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x0a, 0x7d, 0x0a, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74,
0x0d, 0x0a, 0x2e, 0x6e, 0x61, 0x76, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x7b, 0x0d, 0x0a, 0x20, 0x72, 0x79, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20,
0x20, 0x20, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x3a, 0x20, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0d, 0x6c, 0x65, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69,
0x0a, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x78, 0x2d, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20,
0x33, 0x37, 0x35, 0x70, 0x78, 0x3b, 0x0d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x77, 0x69, 0x64, 0x74, 0x77, 0x69, 0x64, 0x74, 0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0a, 0x7d,
0x68, 0x3a, 0x20, 0x31, 0x30, 0x30, 0x25, 0x0d, 0x0a, 0x7d,

File diff suppressed because it is too large Load Diff