Permalink
Cannot retrieve contributors at this time
3972 lines (3357 sloc)
106 KB
| #include "particle_core.h" | |
| //#include <Arduino.h> | |
| #include "../ESP8266WiFi/src/ESP8266WiFi.h" | |
| #include "handshake.h" | |
| #include "messages.h" | |
| #include "coap.h" | |
| #include "aes.h" | |
| #include "rsa.h" | |
| #include "dsakeygen.h" | |
| #include "append_list.h" | |
| #include "appender.h" | |
| #include "file_transfer.h" | |
| #include "crc32.h" | |
| #ifdef __cplusplus | |
| extern "C" { | |
| #endif | |
| #include <c_types.h> | |
| #include <user_interface.h> | |
| #include <mem.h> | |
| #include <osapi.h> | |
| #include "espmissingincludes.h" | |
| #include "oakboot.h" | |
| #ifdef __cplusplus | |
| } | |
| #endif | |
| namespace particle_core { | |
| #define PROTOCOL_BUFFER_SIZE 800 | |
| #define QUEUE_SIZE 800 | |
| uint8_t spark_failed_connects = 0; | |
| bool spark_ok_to_connect = false; | |
| //this has to be aligned | |
| uint8_t chunk_buffer[512]; | |
| WiFiClient pClient; | |
| static System_Mode_TypeDef system_mode = DEFAULT_MODE; | |
| typedef unsigned short uint16_t; | |
| typedef uint16_t chunk_index_t; | |
| unsigned char queue[PROTOCOL_BUFFER_SIZE]; | |
| typedef struct { | |
| //can cut off here if needed | |
| char device_id[25]; //device id in hex | |
| char claim_code[65]; // server public key | |
| uint8 claimed; // server public key | |
| uint8 device_private_key[1216]; // device private key | |
| uint8 device_public_key[384]; // device public key | |
| uint8 server_public_key[768]; //also contains the server address at offset 384 | |
| uint8 server_address_type; //domain or ip of cloud server | |
| uint8 server_address_length; //domain or ip of cloud server | |
| char server_address_domain[254]; //domain or ip of cloud server | |
| uint8 ota_success; | |
| uint32 server_address_ip; //[4]//domain or ip of cloud server | |
| unsigned short firmware_version; | |
| unsigned short system_version; // | |
| char version_string[33]; // | |
| uint8 reserved_flags[32]; // | |
| uint8 reserved1[32]; | |
| uint8 product_store[24]; | |
| char ssid[33]; //ssid and terminator | |
| char passcode[65]; //passcode and terminator | |
| uint8 channel; //channel number | |
| int32 third_party_id; // | |
| char third_party_data[256]; // | |
| char first_update_domain[65]; | |
| char first_update_url[65]; | |
| char first_update_fingerprint[60]; | |
| uint8 current_rom_scheme[1]; | |
| uint8 system_update_pending; | |
| uint8 magic; | |
| uint8 chksum; | |
| //uint8 reserved2[698]; | |
| } oak_config; | |
| struct msg { | |
| uint8_t token; | |
| size_t len; | |
| uint8_t* response; | |
| size_t response_len; | |
| }; | |
| #define SPARK_SERVER_PORT 5683 | |
| #define USER_VAR_MAX_COUNT 10 | |
| #define USER_VAR_KEY_LENGTH 12 | |
| #define USER_FUNC_MAX_COUNT 4 | |
| #define USER_FUNC_KEY_LENGTH 12 | |
| #define USER_FUNC_ARG_LENGTH 64 | |
| #define USER_EVENT_NAME_LENGTH 64 | |
| #define USER_EVENT_DATA_LENGTH 64 | |
| #define SECTOR_SIZE 0x1000 | |
| #define DEVICE_CONFIG_SECTOR 256 | |
| #define DEVICE_BACKUP_CONFIG_SECTOR 512 | |
| #define DEVICE_CHKSUM_INIT 0xee | |
| #define DEVICE_MAGIC 0xf0 | |
| #define DEVICE_CONFIG_SIZE 3398 | |
| #define PRIVATE_KEY_LENGTH (612) | |
| #define PUBLIC_KEY_LENGTH (162) | |
| /* Length in bytes of DER-encoded 2048-bit RSA public key */ | |
| #define SERVER_PUBLIC_KEY_LENGTH (294) | |
| #define SERVER_DOMAIN_LENGTH (253) | |
| const CloudVariableTypeBool BOOLEAN; | |
| const CloudVariableTypeInt INT; | |
| const CloudVariableTypeString STRING; | |
| const CloudVariableTypeDouble DOUBLE; | |
| uint8 config_buffer[DEVICE_CONFIG_SIZE]; | |
| oak_config *deviceConfig = (oak_config*)config_buffer; | |
| uint8 boot_buffer[BOOT_CONFIG_SIZE]; | |
| oakboot_config *bootConfig = (oakboot_config*)boot_buffer; | |
| volatile bool spark_connect_pending = false; | |
| byte device_id[12]; | |
| bool spark_initialized = false; | |
| #ifdef DEBUG_SETUP | |
| void ERROR(String out){ | |
| Serial.println(out); | |
| } | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| void INFO(String out){ | |
| Serial.println(out); | |
| } | |
| #endif | |
| aes_context aes; | |
| unsigned char key[16]; | |
| unsigned char iv_send[16]; | |
| unsigned char iv_receive[16]; | |
| unsigned char salt[8]; | |
| unsigned short _message_id; | |
| unsigned char _token; | |
| uint32_t last_message_millis; | |
| uint32_t last_chunk_millis; // NB: also used to synchronize time | |
| unsigned short chunk_index; | |
| unsigned short chunk_size; | |
| bool expecting_ping_ack; | |
| bool initialized; | |
| uint8_t updating; | |
| struct User_Var_Lookup_Table_t | |
| { | |
| const void *userVar; | |
| Spark_Data_TypeDef userVarType; | |
| char userVarKey[USER_VAR_KEY_LENGTH+1]; | |
| const void* (*update)(const char* name, Spark_Data_TypeDef varType, const void* var, void* reserved); | |
| }; | |
| struct User_Func_Lookup_Table_t | |
| { | |
| void* pUserFuncData; | |
| cloud_function_t pUserFunc; | |
| char userFuncKey[USER_FUNC_KEY_LENGTH]; | |
| }; | |
| User_Var_Lookup_Table_t* find_var_by_key_or_add(const char* varKey); | |
| User_Func_Lookup_Table_t* find_func_by_key_or_add(const char* funcKey); | |
| static append_list<User_Var_Lookup_Table_t> vars(5); | |
| static append_list<User_Func_Lookup_Table_t> funcs(5); | |
| FilteringEventHandler event_handlers[5]; | |
| User_Var_Lookup_Table_t* find_var_by_key(const char* varKey) | |
| { | |
| for (int i = vars.size(); i-->0; ) | |
| { | |
| if (0 == strncmp(vars[i].userVarKey, varKey, USER_VAR_KEY_LENGTH)) | |
| { | |
| return &vars[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| User_Var_Lookup_Table_t* find_var_by_key_or_add(const char* varKey) | |
| { | |
| User_Var_Lookup_Table_t* result = find_var_by_key(varKey); | |
| return result ? result : vars.add(); | |
| } | |
| User_Func_Lookup_Table_t* find_func_by_key(const char* funcKey) | |
| { | |
| for (int i = funcs.size(); i-->0; ) | |
| { | |
| if (0 == strncmp(funcs[i].userFuncKey, funcKey, USER_FUNC_KEY_LENGTH)) | |
| { | |
| return &funcs[i]; | |
| } | |
| } | |
| return NULL; | |
| } | |
| User_Func_Lookup_Table_t* find_func_by_key_or_add(const char* funcKey) | |
| { | |
| User_Func_Lookup_Table_t* result = find_func_by_key(funcKey); | |
| return result ? result : funcs.add(); | |
| } | |
| int call_raw_user_function(void* data, const char* param, void* reserved) | |
| { | |
| user_function_int_str_t* fn = (user_function_int_str_t*)(data); | |
| String p(param); | |
| return (*fn)(p); | |
| } | |
| int call_std_user_function(void* data, const char* param, void* reserved) | |
| { | |
| user_std_function_int_str_t* fn = (user_std_function_int_str_t*)(data); | |
| return (*fn)(String(param)); | |
| } | |
| void call_wiring_event_handler(const void* handler_data, const char *event_name, const char *data) | |
| { | |
| wiring_event_handler_t* fn = (wiring_event_handler_t*)(handler_data); | |
| (*fn)(event_name, data); | |
| } | |
| bool spark_connected() | |
| { | |
| return pClient.connected(); | |
| } | |
| unsigned short next_message_id() | |
| { | |
| return ++_message_id; | |
| } | |
| static uint8 calc_device_chksum(uint8 *start, uint8 *end) { | |
| uint8 chksum = DEVICE_CHKSUM_INIT; | |
| while(start < end) { | |
| chksum ^= *start; | |
| start++; | |
| } | |
| return chksum; | |
| } | |
| void writeDeviceConfig(){ | |
| deviceConfig->chksum = calc_device_chksum((uint8*)deviceConfig,(uint8*)&deviceConfig->chksum); | |
| noInterrupts(); | |
| spi_flash_erase_sector(DEVICE_CONFIG_SECTOR); | |
| spi_flash_write(DEVICE_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(config_buffer), DEVICE_CONFIG_SIZE); | |
| spi_flash_erase_sector(DEVICE_BACKUP_CONFIG_SECTOR); | |
| spi_flash_write(DEVICE_BACKUP_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(config_buffer), DEVICE_CONFIG_SIZE); | |
| interrupts(); | |
| } | |
| uint8_t hex_nibble(unsigned char c) { | |
| if (c<'0') | |
| return 0; | |
| if (c<='9') | |
| return c-'0'; | |
| if (c<='Z') | |
| return c-'A'+10; | |
| if (c<='z') | |
| return c-'a'+10; | |
| return 0; | |
| } | |
| size_t hex_decode(uint8_t* buf, size_t len, const char* hex) { | |
| unsigned char c = '0'; // any non-null character | |
| size_t i; | |
| for (i=0; i<len && c; i++) { | |
| uint8_t b; | |
| if (!(c = *hex++)) | |
| break; | |
| b = hex_nibble(c)<<4; | |
| if (c) { | |
| c = *hex++; | |
| b |= hex_nibble(c); | |
| } | |
| *buf++ = b; | |
| } | |
| return i; | |
| } | |
| // Returns bytes received or -1 on error | |
| int blocking_send(const unsigned char *buf, int length) | |
| { | |
| if(!spark_connected()) | |
| return -1; | |
| #ifdef DEBUG_SETUP | |
| Serial.println("BLSEND"); | |
| #endif | |
| pClient.setTimeout(100); | |
| #ifdef DEBUG_SETUP | |
| uint32_t start = millis(); | |
| #endif | |
| int byte_count = pClient.write(buf, length); | |
| yield(); | |
| #ifdef DEBUG_SETUP | |
| Serial.println(byte_count); | |
| Serial.println((millis()-start)/1000); | |
| #endif | |
| //Maybe this should have been if(byte_count ==0 && length > 0) | |
| //Removing seemed to not have ill effect | |
| // if(byte_count==0) | |
| // byte_count = -1; | |
| return byte_count; | |
| } | |
| // Returns bytes received or -1 on error | |
| int receive(unsigned char *buf, int length) | |
| { | |
| pClient.setTimeout(2000); | |
| int available = pClient.available(); | |
| if(available >= length){ | |
| return pClient.readBytes(buf, length); | |
| } | |
| else if(available > 0){ | |
| return pClient.readBytes(buf, available); | |
| } | |
| else{ | |
| if(!spark_connected()) | |
| return -1; | |
| else | |
| return 0; | |
| } | |
| } | |
| // Returns bytes received or -1 on error | |
| int blocking_receive(unsigned char *buf, int length) | |
| { | |
| if(!spark_connected()) | |
| return -1; | |
| yield(); | |
| #ifdef DEBUG_SETUP | |
| Serial.println("BLRECV"); | |
| #endif | |
| pClient.setTimeout(2000); | |
| int byte_count = pClient.readBytes(buf, length); | |
| if(byte_count==0) | |
| byte_count = -1; | |
| return byte_count; | |
| } | |
| int set_key(const unsigned char *signed_encrypted_credentials) | |
| { | |
| unsigned char credentials[40]; | |
| unsigned char hmac[20]; | |
| if (0 != decipher_aes_credentials(deviceConfig->device_private_key, | |
| signed_encrypted_credentials, | |
| credentials)) | |
| return 1;//decrypt error | |
| calculate_ciphertext_hmac(signed_encrypted_credentials, credentials, hmac); | |
| if (0 == verify_signature(signed_encrypted_credentials + 128, | |
| deviceConfig->server_public_key, | |
| hmac)) | |
| { | |
| memcpy(key, credentials, 16); | |
| memcpy(iv_send, credentials + 16, 16); | |
| memcpy(iv_receive, credentials + 16, 16); | |
| memcpy(salt, credentials + 32, 8); | |
| _message_id = *(credentials + 32) << 8 | *(credentials + 33); | |
| _token = *(credentials + 34); | |
| unsigned int seed; | |
| memcpy(&seed, credentials + 35, 4); | |
| randomSeed(seed); | |
| return 0; | |
| } | |
| else return 1;//auth error | |
| } | |
| void encrypt(unsigned char *buf, int length) | |
| { | |
| aes_setkey_enc(&aes, key, 128); | |
| aes_crypt_cbc(&aes, AES_ENCRYPT, length, iv_send, buf, buf); | |
| memcpy(iv_send, buf, 16); | |
| } | |
| void ping(unsigned char *buf) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| buf[0] = 0x40; // Confirmable, no token | |
| buf[1] = 0x00; // code signifying empty message | |
| buf[2] = message_id >> 8; | |
| buf[3] = message_id & 0xff; | |
| memset(buf + 4, 12, 12); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| size_t wrap(unsigned char *buf, size_t msglen) | |
| { | |
| size_t buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(buf + 2 + msglen, pad, pad); // PKCS #7 padding | |
| encrypt(buf + 2, buflen); | |
| buf[0] = (buflen >> 8) & 0xff; | |
| buf[1] = buflen & 0xff; | |
| return buflen + 2; | |
| } | |
| void hello(unsigned char *buf, bool newly_upgraded) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| size_t len = Messages::hello(buf+2, message_id, newly_upgraded, PLATFORM_ID, PRODUCT_ID, deviceConfig->firmware_version, false, nullptr, 0); | |
| wrap(buf, len); | |
| } | |
| inline void coded_ack(unsigned char *buf, | |
| unsigned char code, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb | |
| ) | |
| { | |
| buf[0] = 0x60; // acknowledgment, no token | |
| buf[1] = code; | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| memset(buf + 4, 12, 12); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| inline void coded_ack(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char code, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = code; | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| memset(buf + 5, 11, 11); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| void variable_value(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb, | |
| bool return_value) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| buf[6] = return_value ? 1 : 0; | |
| memset(buf + 7, 9, 9); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| void variable_value(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb, | |
| int return_value) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| buf[6] = return_value >> 24; | |
| buf[7] = return_value >> 16 & 0xff; | |
| buf[8] = return_value >> 8 & 0xff; | |
| buf[9] = return_value & 0xff; | |
| memset(buf + 10, 6, 6); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| void variable_value(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb, | |
| double return_value) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| memcpy(buf + 6, &return_value, 8); | |
| memset(buf + 14, 2, 2); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| // Returns the length of the buffer to send | |
| int variable_value(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb, | |
| const void *return_value, | |
| int length) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| memcpy(buf + 6, return_value, length); | |
| int msglen = 6 + length; | |
| int buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(buf + msglen, pad, pad); // PKCS #7 padding | |
| encrypt(buf, buflen); | |
| return buflen; | |
| } | |
| uint32_t timestamp_offset; | |
| uint32_t last_time_offset; | |
| void set_time(uint32_t time){ | |
| timestamp_offset = time - (millis()/1000); | |
| last_time_offset = millis()/1000; | |
| } | |
| uint32_t get_time(){ | |
| //as long as we get time once every 98 days this should be OK | |
| if(millis()/1000<last_time_offset){ | |
| timestamp_offset += 4294968; | |
| } | |
| last_time_offset = millis()/1000; | |
| return timestamp_offset+last_time_offset; | |
| } | |
| void handle_time_response(uint32_t time) | |
| { | |
| // deduct latency | |
| uint32_t latency = last_chunk_millis ? (millis()-last_chunk_millis)/2000 : 0; | |
| last_chunk_millis = 0; | |
| set_time(time-latency); | |
| } | |
| int numUserFunctions(void) | |
| { | |
| return funcs.size(); | |
| } | |
| const char* getUserFunctionKey(int function_index) | |
| { | |
| return funcs[function_index].userFuncKey; | |
| } | |
| int numUserVariables(void) | |
| { | |
| return vars.size(); | |
| } | |
| const char* getUserVariableKey(int variable_index) | |
| { | |
| return vars[variable_index].userVarKey; | |
| } | |
| int userVarType(const char *varKey) | |
| { | |
| User_Var_Lookup_Table_t* item = find_var_by_key(varKey); | |
| return item ? item->userVarType : -1; | |
| } | |
| SparkReturnType::Enum wrapVarTypeInEnum(const char *varKey) | |
| { | |
| switch (userVarType(varKey)) | |
| { | |
| case 1: | |
| return SparkReturnType::BOOLEAN; | |
| case 4: | |
| return SparkReturnType::STRING; | |
| case 9: | |
| return SparkReturnType::DOUBLE; | |
| case 2: | |
| default: | |
| return SparkReturnType::INT; | |
| } | |
| } | |
| bool send_subscription(const char *event_name, const char *device_id) | |
| { | |
| uint16_t msg_id = next_message_id(); | |
| size_t msglen = subscription(queue + 2, msg_id, event_name, device_id); | |
| size_t buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(queue + 2 + msglen, pad, pad); // PKCS #7 padding | |
| encrypt(queue + 2, buflen); | |
| queue[0] = (buflen >> 8) & 0xff; | |
| queue[1] = buflen & 0xff; | |
| return (0 <= blocking_send(queue, buflen + 2)); | |
| } | |
| bool send_subscription(const char *event_name, | |
| SubscriptionScope::Enum scope) | |
| { | |
| uint16_t msg_id = next_message_id(); | |
| size_t msglen = subscription(queue + 2, msg_id, event_name, scope); | |
| size_t buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(queue + 2 + msglen, pad, pad); // PKCS #7 padding | |
| encrypt(queue + 2, buflen); | |
| queue[0] = (buflen >> 8) & 0xff; | |
| queue[1] = buflen & 0xff; | |
| return (0 <= blocking_send(queue, buflen + 2)); | |
| } | |
| void send_subscriptions() | |
| { | |
| const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); | |
| for (int i = 0; i < NUM_HANDLERS; i++) | |
| { | |
| if (NULL != event_handlers[i].handler) | |
| { | |
| if (event_handlers[i].device_id[0]) | |
| { | |
| send_subscription(event_handlers[i].filter, event_handlers[i].device_id); | |
| } | |
| else | |
| { | |
| send_subscription(event_handlers[i].filter, event_handlers[i].scope); | |
| } | |
| } | |
| } | |
| } | |
| bool event_handler_exists(const char *event_name, EventHandler handler, | |
| void *handler_data, SubscriptionScope::Enum scope, const char* id) | |
| { | |
| const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); | |
| for (int i = 0; i < NUM_HANDLERS; i++) | |
| { | |
| if (event_handlers[i].handler==handler && | |
| event_handlers[i].handler_data==handler_data && | |
| event_handlers[i].scope==scope) { | |
| const size_t MAX_FILTER_LEN = sizeof(event_handlers[i].filter); | |
| const size_t FILTER_LEN = strnlen(event_name, MAX_FILTER_LEN); | |
| if (!strncmp(event_handlers[i].filter, event_name, FILTER_LEN)) { | |
| const size_t MAX_ID_LEN = sizeof(event_handlers[i].device_id)-1; | |
| const size_t id_len = id ? strnlen(id, MAX_ID_LEN) : 0; | |
| if (id_len) | |
| return !strncmp(event_handlers[i].device_id, id, id_len); | |
| else | |
| return !event_handlers[i].device_id[0]; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool add_event_handler(const char *event_name, EventHandler handler, | |
| void *handler_data, SubscriptionScope::Enum scope, const char* id) | |
| { | |
| if (event_handler_exists(event_name, handler, handler_data, scope, id)) | |
| return true; | |
| const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); | |
| for (int i = 0; i < NUM_HANDLERS; i++) | |
| { | |
| if (NULL == event_handlers[i].handler) | |
| { | |
| const size_t MAX_FILTER_LEN = sizeof(event_handlers[i].filter); | |
| const size_t FILTER_LEN = strnlen(event_name, MAX_FILTER_LEN); | |
| memcpy(event_handlers[i].filter, event_name, FILTER_LEN); | |
| memset(event_handlers[i].filter + FILTER_LEN, 0, MAX_FILTER_LEN - FILTER_LEN); | |
| event_handlers[i].handler = handler; | |
| event_handlers[i].handler_data = handler_data; | |
| event_handlers[i].device_id[0] = 0; | |
| const size_t MAX_ID_LEN = sizeof(event_handlers[i].device_id)-1; | |
| const size_t id_len = id ? strnlen(id, MAX_ID_LEN) : 0; | |
| memcpy(event_handlers[i].device_id, id, id_len); | |
| event_handlers[i].device_id[id_len] = 0; | |
| event_handlers[i].scope = scope; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| const void *getUserVar(const char *varKey) | |
| { | |
| User_Var_Lookup_Table_t* item = find_var_by_key(varKey); | |
| const void* result = nullptr; | |
| if (item) { | |
| if (item->update) | |
| result = item->update(item->userVarKey, item->userVarType, item->userVar, nullptr); | |
| else | |
| result = item->userVar; | |
| } | |
| return result; | |
| } | |
| void userFuncScheduleImpl(User_Func_Lookup_Table_t* item, const char* paramString, bool freeParamString, FunctionResultCallback callback) | |
| { | |
| int result = item->pUserFunc(item->pUserFuncData, paramString, NULL); | |
| if (freeParamString) | |
| delete paramString; | |
| callback((const void*)long(result), SparkReturnType::INT); | |
| } | |
| int userFuncSchedule(const char *funcKey, const char *paramString, FunctionResultCallback callback, void* reserved) | |
| { | |
| // for now, we invoke the function directly and return the result via the callback | |
| User_Func_Lookup_Table_t* item = find_func_by_key(funcKey); | |
| if (!item) | |
| return -1; | |
| userFuncScheduleImpl(item, paramString, false, callback); | |
| return 0; | |
| } | |
| SubscriptionScope::Enum convert(Spark_Subscription_Scope_TypeDef subscription_type) | |
| { | |
| return(subscription_type==MY_DEVICES) ? SubscriptionScope::MY_DEVICES : SubscriptionScope::FIREHOSE; | |
| } | |
| bool register_event(const char* eventName, SubscriptionScope::Enum event_scope, const char* deviceID) | |
| { | |
| bool success; | |
| if (deviceID) | |
| success = send_subscription(eventName, deviceID); | |
| else | |
| success = send_subscription(eventName, event_scope); | |
| return success; | |
| } | |
| bool spark_subscribe(const char *eventName, EventHandler handler, void* handler_data, | |
| Spark_Subscription_Scope_TypeDef scope, const char* deviceID, void* reserved) | |
| { | |
| //SYSTEM_THREAD_CONTEXT_SYNC(spark_subscribe(eventName, handler, handler_data, scope, deviceID, reserved)); | |
| auto event_scope = convert(scope); | |
| bool success = add_event_handler(eventName, handler, handler_data, event_scope, deviceID); | |
| if (success && spark_connected()) | |
| { | |
| register_event(eventName, event_scope, deviceID); | |
| } | |
| return success; | |
| } | |
| inline EventType::Enum convert(Spark_Event_TypeDef eventType) { | |
| return eventType==PUBLIC ? EventType::PUBLIC : EventType::PRIVATE; | |
| } | |
| inline bool is_system(const char* event_name) { | |
| // if there were a strncmpi this would be easier! | |
| char prefix[6]; | |
| if (!*event_name || strlen(event_name)<5) | |
| return false; | |
| memcpy(prefix, event_name, 5); | |
| prefix[5] = '\0'; | |
| return !strcasecmp(prefix, "spark"); | |
| } | |
| // Returns true on success, false on sending timeout or rate-limiting failure | |
| bool send_event(const char *event_name, const char *data, | |
| int ttl, EventType::Enum event_type) | |
| { | |
| if (updating) | |
| { | |
| return false; | |
| } | |
| bool is_system_event = is_system(event_name); | |
| if (is_system_event) { | |
| static uint16_t lastMinute = 0; | |
| static uint8_t eventsThisMinute = 0; | |
| uint16_t currentMinute = uint16_t(millis()>>16); | |
| if (currentMinute==lastMinute) { // == handles millis() overflow | |
| if (eventsThisMinute==255) | |
| return false; | |
| } | |
| else { | |
| lastMinute = currentMinute; | |
| eventsThisMinute = 0; | |
| } | |
| eventsThisMinute++; | |
| } | |
| else { | |
| static uint32_t recent_event_ticks[5] = { | |
| (uint32_t) -1000, (uint32_t) -1000, | |
| (uint32_t) -1000, (uint32_t) -1000, | |
| (uint32_t) -1000 }; | |
| static int evt_tick_idx = 0; | |
| uint32_t now = recent_event_ticks[evt_tick_idx] = millis(); | |
| evt_tick_idx++; | |
| evt_tick_idx %= 5; | |
| if (now - recent_event_ticks[evt_tick_idx] < 1000) | |
| { | |
| // exceeded allowable burst of 4 events per second | |
| return false; | |
| } | |
| } | |
| uint16_t msg_id = next_message_id(); | |
| size_t msglen = Messages::event(queue + 2, msg_id, event_name, data, ttl, event_type, false); | |
| size_t wrapped_len = wrap(queue, msglen); | |
| return (0 <= blocking_send(queue, wrapped_len)); | |
| } | |
| bool spark_send_event(const char* name, const char* data, int ttl, Spark_Event_TypeDef eventType, void* reserved) | |
| { | |
| //SYSTEM_THREAD_CONTEXT_SYNC(spark_send_event(name, data, ttl, eventType, reserved)); | |
| //return spark_protocol_send_event(sp, name, data, ttl, convert(eventType), NULL); | |
| return send_event(name, data, ttl, convert(eventType)); | |
| } | |
| bool spark_variable(const char *varKey, const void *userVar, Spark_Data_TypeDef userVarType, spark_variable_t* extra) | |
| { | |
| //SYSTEM_THREAD_CONTEXT_SYNC(spark_variable(varKey, userVar, userVarType, extra)); | |
| User_Var_Lookup_Table_t* item = NULL; | |
| if (NULL != userVar && NULL != varKey && strlen(varKey)<=USER_VAR_KEY_LENGTH) | |
| { | |
| if ((item=find_var_by_key_or_add(varKey))!=NULL) | |
| { | |
| item->userVar = userVar; | |
| item->userVarType = userVarType; | |
| if (extra) { | |
| item->update = extra->update; | |
| } | |
| memset(item->userVarKey, 0, USER_VAR_KEY_LENGTH); | |
| memcpy(item->userVarKey, varKey, USER_VAR_KEY_LENGTH); | |
| } | |
| } | |
| return item!=NULL; | |
| } | |
| void function_return(unsigned char *buf, | |
| unsigned char token, | |
| int return_value) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| buf[0] = 0x51; // non-confirmable, one-byte token | |
| buf[1] = 0x44; // response code 2.04 CHANGED | |
| buf[2] = message_id >> 8; | |
| buf[3] = message_id & 0xff; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| buf[6] = return_value >> 24; | |
| buf[7] = return_value >> 16 & 0xff; | |
| buf[8] = return_value >> 8 & 0xff; | |
| buf[9] = return_value & 0xff; | |
| memset(buf + 10, 6, 6); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| bool spark_function_internal(const cloud_function_descriptor* desc, void* reserved) | |
| { | |
| User_Func_Lookup_Table_t* item = NULL; | |
| if (NULL != desc->fn && NULL != desc->funcKey && strlen(desc->funcKey)<=USER_FUNC_KEY_LENGTH) | |
| { | |
| if ((item=find_func_by_key(desc->funcKey)) || (item = funcs.add())) | |
| { | |
| item->pUserFunc = desc->fn; | |
| item->pUserFuncData = desc->data; | |
| memset(item->userFuncKey, 0, USER_FUNC_KEY_LENGTH); | |
| memcpy(item->userFuncKey, desc->funcKey, USER_FUNC_KEY_LENGTH); | |
| } | |
| } | |
| return item!=NULL; | |
| } | |
| /** | |
| * This is the original released signature for firmware version 0 and needs to remain like this. | |
| * (The original returned void - we can safely change to bool.) | |
| */ | |
| bool spark_function(const char *funcKey, p_user_function_int_str_t pFunc, void* reserved) | |
| { | |
| //SYSTEM_THREAD_CONTEXT_SYNC(spark_function(funcKey, pFunc, reserved)); | |
| bool result; | |
| if (funcKey) { // old call, with funcKey != NULL | |
| cloud_function_descriptor desc; | |
| desc.funcKey = funcKey; | |
| desc.fn = call_raw_user_function; | |
| desc.data = (void*)pFunc; | |
| result = spark_function_internal(&desc, NULL); | |
| } | |
| else { // new call - pFunc is actually a pointer to a descriptor | |
| result = spark_function_internal((cloud_function_descriptor*)pFunc, reserved); | |
| } | |
| return result; | |
| } | |
| bool register_function(cloud_function_t fn, void* data, const char* funcKey) | |
| { | |
| cloud_function_descriptor desc; | |
| memset(&desc, 0, sizeof(desc)); | |
| desc.size = sizeof(desc); | |
| desc.fn = fn; | |
| desc.data = (void*)data; | |
| desc.funcKey = funcKey; | |
| return spark_function(NULL, (user_function_int_str_t*)&desc, NULL); | |
| } | |
| String buffer_to_string(const uint8_t *buf,size_t length){ | |
| String result = ""; | |
| for(uint8_t i = 0; i<length; i++){ | |
| result += buf[i]; | |
| } | |
| return result; | |
| } | |
| bool spark_describe_called = false; | |
| int description(unsigned char *buf, unsigned char token, | |
| unsigned char message_id_msb, unsigned char message_id_lsb, int desc_flags) | |
| { | |
| spark_describe_called = true; | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| BufferAppender appender(buf+6, QUEUE_SIZE-8); | |
| appender.append("{"); | |
| bool has_content = false; | |
| if (desc_flags && DESCRIBE_APPLICATION) { | |
| has_content = true; | |
| appender.append("\"f\":["); | |
| int num_keys = numUserFunctions(); | |
| int i; | |
| for (i = 0; i < num_keys; ++i) | |
| { | |
| if (i) | |
| { | |
| appender.append(','); | |
| } | |
| appender.append('"'); | |
| const char* key = getUserFunctionKey(i); | |
| size_t function_name_length = strlen(key); | |
| if (MAX_FUNCTION_KEY_LENGTH < function_name_length) | |
| { | |
| function_name_length = MAX_FUNCTION_KEY_LENGTH; | |
| } | |
| appender.append((const uint8_t*)key, function_name_length); | |
| appender.append('"'); | |
| } | |
| appender.append("],\"v\":{"); | |
| num_keys = numUserVariables(); | |
| for (i = 0; i < num_keys; ++i) | |
| { | |
| if (i) | |
| { | |
| appender.append(','); | |
| } | |
| appender.append('"'); | |
| const char* key = getUserVariableKey(i); | |
| size_t variable_name_length = strlen(key); | |
| SparkReturnType::Enum t = wrapVarTypeInEnum(key); | |
| if (MAX_VARIABLE_KEY_LENGTH < variable_name_length) | |
| { | |
| variable_name_length = MAX_VARIABLE_KEY_LENGTH; | |
| } | |
| appender.append((const uint8_t*)key, variable_name_length); | |
| appender.append("\":"); | |
| appender.append('0' + (char)t); | |
| } | |
| appender.append('}'); | |
| } | |
| if ((desc_flags&DESCRIBE_SYSTEM)) { | |
| //if (descriptor.append_system_info && (desc_flags&DESCRIBE_SYSTEM)) { | |
| if (has_content) | |
| appender.append(','); | |
| //descriptor.append_system_info(append_instance, &appender, NULL); | |
| char number_buffer[4]; | |
| appender.append("\"p\":82,\"m\":[{\"s\":1040368,\"l\":\"m\",\"vc\":30,\"vv\":30,\"f\":\"s\",\"n\":\"1\",\"v\":"); | |
| sprintf(number_buffer,"%d",deviceConfig->system_version); | |
| appender.append(number_buffer); | |
| appender.append(",\"d\":[]},{\"s\":1040368,\"l\":\"m\",\"vc\":30,\"vv\":30,\"u\":\"0\",\"f\":\"u\",\"n\":\"1\",\"v\":1,\"d\":[{\"f\":\"s\",\"n\":\"1\",\"v\":"); | |
| sprintf(number_buffer,"%d",deviceConfig->system_version); | |
| appender.append(number_buffer); | |
| appender.append(",\"_\":\"\"}]}]"); | |
| } | |
| appender.append('}'); | |
| int msglen = appender.next() - (uint8_t *)buf; | |
| int buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(buf+msglen, pad, pad); // PKCS #7 padding | |
| encrypt(buf, buflen); | |
| return buflen; | |
| } | |
| /* | |
| int description(unsigned char *buf, unsigned char token, | |
| unsigned char message_id_msb, unsigned char message_id_lsb, int desc_flags) | |
| { | |
| buf[0] = 0x61; // acknowledgment, one-byte token | |
| buf[1] = 0x45; // response code 2.05 CONTENT | |
| buf[2] = message_id_msb; | |
| buf[3] = message_id_lsb; | |
| buf[4] = token; | |
| buf[5] = 0xff; // payload marker | |
| String content = ""; | |
| //BufferAppender appender(buf+6, QUEUE_SIZE-8); | |
| content += "{"; | |
| bool has_content = false; | |
| if (desc_flags && DESCRIBE_APPLICATION) { | |
| has_content = true; | |
| content += "\"f\":["; | |
| int num_keys = numUserFunctions(); | |
| int i; | |
| for (i = 0; i < num_keys; ++i) | |
| { | |
| if (i) | |
| { | |
| content += ","; | |
| } | |
| content += "\""; | |
| const char* key = getUserFunctionKey(i); | |
| size_t function_name_length = strlen(key); | |
| if (MAX_FUNCTION_KEY_LENGTH < function_name_length) | |
| { | |
| function_name_length = MAX_FUNCTION_KEY_LENGTH; | |
| } | |
| content += buffer_to_string((const uint8_t*)key, function_name_length); | |
| content += "\""; | |
| } | |
| content += "],\"v\":{"; | |
| num_keys = numUserVariables(); | |
| for (i = 0; i < num_keys; ++i) | |
| { | |
| if (i) | |
| { | |
| content += ","; | |
| } | |
| content += "\""; | |
| const char* key = getUserVariableKey(i); | |
| size_t variable_name_length = strlen(key); | |
| SparkReturnType::Enum t = wrapVarTypeInEnum(key); | |
| if (MAX_VARIABLE_KEY_LENGTH < variable_name_length) | |
| { | |
| variable_name_length = MAX_VARIABLE_KEY_LENGTH; | |
| } | |
| content += buffer_to_string((const uint8_t*)key, variable_name_length); | |
| content += "\":"; | |
| content += String('0' + (char)t); | |
| } | |
| content += "}"; | |
| } | |
| if (desc_flags&DESCRIBE_SYSTEM) { | |
| if (has_content) | |
| content += ","; | |
| //descriptor.append_system_info(append_instance, &appender, NULL); | |
| content += "\"p\":82,\"m\":[]"; | |
| } | |
| content += "}"; | |
| //truncate if too long | |
| if(content.length() > QUEUE_SIZE-8){ | |
| content = content.substring(0,QUEUE_SIZE-8); | |
| } | |
| int msglen = ((uint8_t *)buf+6 + content.length()) - (uint8_t *)buf; | |
| int buflen = (msglen & ~15) + 16; | |
| char pad = buflen - msglen; | |
| memset(buf+msglen, pad, pad); // PKCS #7 padding | |
| encrypt(buf, buflen); | |
| return buflen; | |
| } | |
| */ | |
| bool function_result(const void* result, SparkReturnType::Enum, uint8_t token) | |
| { | |
| // send return value | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| function_return(queue + 2, token, long(result)); | |
| if (0 > blocking_send(queue, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| return true; | |
| } | |
| char function_arg[MAX_FUNCTION_ARG_LENGTH]; | |
| bool handle_function_call(msg& message) | |
| { | |
| // copy the function key | |
| char function_key[13]; | |
| memset(function_key, 0, 13); | |
| int function_key_length = queue[7] & 0x0F; | |
| memcpy(function_key, queue + 8, function_key_length); | |
| // How long is the argument? | |
| size_t q_index = 8 + function_key_length; | |
| size_t query_length = queue[q_index] & 0x0F; | |
| if (13 == query_length) | |
| { | |
| ++q_index; | |
| query_length = 13 + queue[q_index]; | |
| } | |
| else if (14 == query_length) | |
| { | |
| ++q_index; | |
| query_length = queue[q_index] << 8; | |
| ++q_index; | |
| query_length |= queue[q_index]; | |
| query_length += 269; | |
| } | |
| bool has_function = false; | |
| // allocated memory bounds check | |
| if (MAX_FUNCTION_ARG_LENGTH > query_length) | |
| { | |
| // save a copy of the argument | |
| memcpy(function_arg, queue + q_index + 1, query_length); | |
| function_arg[query_length] = 0; // null terminate string | |
| has_function = true; | |
| } | |
| uint8_t* msg_to_send = message.response; | |
| // send ACK | |
| msg_to_send[0] = 0; | |
| msg_to_send[1] = 16; | |
| coded_ack(msg_to_send + 2, has_function ? 0x00 : RESPONSE_CODE(4,00), queue[2], queue[3]); | |
| if (0 > blocking_send(msg_to_send, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| // call the given user function | |
| auto callback = [=] (const void* result, SparkReturnType::Enum resultType ) { return function_result(result, resultType, message.token); }; | |
| userFuncSchedule(function_key, function_arg, callback, NULL); | |
| return true; | |
| } | |
| void invokeEventHandlerInternal(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, | |
| const char* event_name, const char* data, void* reserved) | |
| { | |
| if(handlerInfo->handler_data) | |
| { | |
| EventHandlerWithData handler = (EventHandlerWithData) handlerInfo->handler; | |
| handler(handlerInfo->handler_data, event_name, data); | |
| } | |
| else | |
| { | |
| handlerInfo->handler(event_name, data); | |
| } | |
| } | |
| void invokeEventHandlerString(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, | |
| const String& name, const String& data, void* reserved) | |
| { | |
| invokeEventHandlerInternal(handlerInfoSize, handlerInfo, name.c_str(), data.c_str(), reserved); | |
| } | |
| void invokeEventHandler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, | |
| const char* event_name, const char* event_data, void* reserved) | |
| { | |
| invokeEventHandlerInternal(handlerInfoSize, handlerInfo, event_name, event_data, reserved); | |
| } | |
| volatile uint32_t lastCloudEvent = 0; | |
| void handle_event(msg& message) | |
| { | |
| const unsigned len = message.len; | |
| // fist decode the event data before looking for a handler | |
| unsigned char pad = queue[len - 1]; | |
| if (0 == pad || 16 < pad) | |
| { | |
| // ignore bad message, PKCS #7 padding must be 1-16 | |
| return; | |
| } | |
| // end of CoAP message | |
| unsigned char *end = queue + len - pad; | |
| unsigned char *event_name = queue + 6; | |
| size_t event_name_length = CoAP::option_decode(&event_name); | |
| if (0 == event_name_length) | |
| { | |
| // error, malformed CoAP option | |
| return; | |
| } | |
| unsigned char *next_src = event_name + event_name_length; | |
| unsigned char *next_dst = next_src; | |
| while (next_src < end && 0x00 == (*next_src & 0xf0)) | |
| { | |
| // there's another Uri-Path option, i.e., event name with slashes | |
| size_t option_len = CoAP::option_decode(&next_src); | |
| *next_dst++ = '/'; | |
| if (next_dst != next_src) | |
| { | |
| // at least one extra byte has been used to encode a CoAP Uri-Path option length | |
| memmove(next_dst, next_src, option_len); | |
| } | |
| next_src += option_len; | |
| next_dst += option_len; | |
| } | |
| event_name_length = next_dst - event_name; | |
| if (next_src < end && 0x30 == (*next_src & 0xf0)) | |
| { | |
| // Max-Age option is next, which we ignore | |
| size_t next_len = CoAP::option_decode(&next_src); | |
| next_src += next_len; | |
| } | |
| unsigned char *data = NULL; | |
| if (next_src < end && 0xff == *next_src) | |
| { | |
| // payload is next | |
| data = next_src + 1; | |
| // null terminate data string | |
| *end = 0; | |
| } | |
| // null terminate event name string | |
| event_name[event_name_length] = 0; | |
| const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); | |
| for (int i = 0; i < NUM_HANDLERS; i++) | |
| { | |
| if (NULL == event_handlers[i].handler) | |
| { | |
| break; | |
| } | |
| const size_t MAX_FILTER_LENGTH = sizeof(event_handlers[i].filter); | |
| const size_t filter_length = strnlen(event_handlers[i].filter, MAX_FILTER_LENGTH); | |
| if (event_name_length < filter_length) | |
| { | |
| // does not match this filter, try the next event handler | |
| continue; | |
| } | |
| const int cmp = memcmp(event_handlers[i].filter, event_name, filter_length); | |
| if (0 == cmp) | |
| { | |
| // don't call the handler directly, use a callback for it. | |
| if (!invokeEventHandler) | |
| { | |
| if(event_handlers[i].handler_data) | |
| { | |
| EventHandlerWithData handler = (EventHandlerWithData) event_handlers[i].handler; | |
| handler(event_handlers[i].handler_data, (char *)event_name, (char *)data); | |
| } | |
| else | |
| { | |
| event_handlers[i].handler((char *)event_name, (char *)data); | |
| } | |
| } | |
| else | |
| { | |
| invokeEventHandler(sizeof(FilteringEventHandler), &event_handlers[i], (const char*)event_name, (const char*)data, NULL); | |
| } | |
| } | |
| // else continue the for loop to try the next handler | |
| } | |
| } | |
| bool send_description(int description_flags, msg& message) | |
| { | |
| int desc_len = description(queue + 2, message.token, queue[2], queue[3], description_flags); | |
| queue[0] = (desc_len >> 8) & 0xff; | |
| queue[1] = desc_len & 0xff; | |
| return blocking_send(queue, desc_len + 2)>=0; | |
| } | |
| void empty_ack(unsigned char *buf, | |
| unsigned char message_id_msb, | |
| unsigned char message_id_lsb) { | |
| coded_ack(buf, 0, message_id_msb, message_id_lsb); | |
| }; | |
| FileTransfer::Descriptor file; | |
| unsigned chunk_bitmap_size() | |
| { | |
| return (file.chunk_count(chunk_size)+7)/8; | |
| } | |
| uint8_t* chunk_bitmap() | |
| { | |
| return &queue[QUEUE_SIZE-chunk_bitmap_size()]; | |
| } | |
| chunk_index_t missed_chunk_index; | |
| void separate_response_with_payload(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char code, | |
| unsigned char* payload, | |
| unsigned payload_len) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| buf[0] = 0x51; // non-confirmable, one-byte token | |
| buf[1] = code; | |
| buf[2] = message_id >> 8; | |
| buf[3] = message_id & 0xff; | |
| buf[4] = token; | |
| unsigned len = 5; | |
| // for now, assume the payload is less than 9 | |
| if (payload && payload_len) { | |
| buf[5] = 0xFF; | |
| memcpy(buf+6, payload, payload_len); | |
| len += 1 + payload_len; | |
| } | |
| memset(buf + len, 16-len, 16-len); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| inline bool is_chunk_received(chunk_index_t idx) | |
| { | |
| return (chunk_bitmap()[idx>>3] & uint8_t(1<<(idx&7))); | |
| } | |
| void separate_response(unsigned char *buf, | |
| unsigned char token, | |
| unsigned char code) | |
| { | |
| separate_response_with_payload(buf, token, code, NULL, 0); | |
| } | |
| chunk_index_t next_chunk_missing(chunk_index_t start) | |
| { | |
| chunk_index_t chunk = NO_CHUNKS_MISSING; | |
| chunk_index_t chunks = file.chunk_count(chunk_size); | |
| chunk_index_t idx = start; | |
| for (;idx<chunks; idx++) | |
| { | |
| if (!is_chunk_received(idx)) | |
| { | |
| //serial_dump("next missing chunk %d from %d", idx, start); | |
| chunk = idx; | |
| break; | |
| } | |
| } | |
| return chunk; | |
| } | |
| void chunk_received(unsigned char *buf, | |
| unsigned char token, | |
| ChunkReceivedCode::Enum code) | |
| { | |
| separate_response(buf, token, code); | |
| } | |
| int send_missing_chunks(int count) | |
| { | |
| int sent = 0; | |
| chunk_index_t idx = 0; | |
| uint8_t* buf = queue+2; | |
| unsigned short message_id = next_message_id(); | |
| buf[0] = 0x40; // confirmable, no token | |
| buf[1] = 0x01; // code 0.01 GET | |
| buf[2] = message_id >> 8; | |
| buf[3] = message_id & 0xff; | |
| buf[4] = 0xb1; // one-byte Uri-Path option | |
| buf[5] = 'c'; | |
| buf[6] = 0xff; // payload marker | |
| while ((idx=next_chunk_missing(chunk_index_t(idx)))!=NO_CHUNKS_MISSING && sent<count) | |
| { | |
| buf[(sent*2)+7] = idx >> 8; | |
| buf[(sent*2)+8] = idx & 0xFF; | |
| missed_chunk_index = idx; | |
| idx++; | |
| sent++; | |
| } | |
| if (sent>0) { | |
| //serial_dump("Sent %d missing chunks", sent); | |
| size_t message_size = 7+(sent*2); | |
| message_size = wrap(queue, message_size); | |
| if (0 > blocking_send(queue, message_size)) | |
| return -1; | |
| } | |
| return sent; | |
| } | |
| void chunk_missed(unsigned char *buf, unsigned short chunk_index) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| buf[0] = 0x40; // confirmable, no token | |
| buf[1] = 0x01; // code 0.01 GET | |
| buf[2] = message_id >> 8; | |
| buf[3] = message_id & 0xff; | |
| buf[4] = 0xb1; // one-byte Uri-Path option | |
| buf[5] = 'c'; | |
| buf[6] = 0xff; // payload marker | |
| buf[7] = chunk_index >> 8; | |
| buf[8] = chunk_index & 0xff; | |
| memset(buf + 9, 7, 7); // PKCS #7 padding | |
| encrypt(buf, 16); | |
| } | |
| void update_ready(unsigned char *buf, unsigned char token) | |
| { | |
| separate_response_with_payload(buf, token, 0x44, NULL, 0); | |
| } | |
| void update_ready(unsigned char *buf, unsigned char token, uint8_t flags) | |
| { | |
| separate_response_with_payload(buf, token, 0x44, &flags, 1); | |
| } | |
| static uint8 calc_boot_chksum(uint8 *start, uint8 *end) { | |
| uint8 chksum = CHKSUM_INIT; | |
| while(start < end) { | |
| chksum ^= *start; | |
| start++; | |
| } | |
| return chksum; | |
| } | |
| bool readBootConfig(){ | |
| noInterrupts(); | |
| spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(boot_buffer), BOOT_CONFIG_SIZE); | |
| if(bootConfig->magic != BOOT_CONFIG_MAGIC || bootConfig->chksum != calc_boot_chksum((uint8*)bootConfig, (uint8*)&bootConfig->chksum)){ | |
| //load the backup and copy to main | |
| spi_flash_read(BOOT_BACKUP_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(boot_buffer), BOOT_CONFIG_SIZE); | |
| spi_flash_erase_sector(BOOT_CONFIG_SECTOR); | |
| spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(boot_buffer), BOOT_CONFIG_SIZE); | |
| } | |
| interrupts(); | |
| if(bootConfig->magic != BOOT_CONFIG_MAGIC || bootConfig->chksum != calc_boot_chksum((uint8*)bootConfig, (uint8*)&bootConfig->chksum)){ | |
| return false; | |
| } | |
| return true; | |
| } | |
| void writeBootConfig(){ | |
| noInterrupts(); | |
| bootConfig->chksum = calc_boot_chksum((uint8*)bootConfig,(uint8*)&bootConfig->chksum); | |
| spi_flash_erase_sector(BOOT_CONFIG_SECTOR); | |
| spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(boot_buffer), BOOT_CONFIG_SIZE); | |
| spi_flash_erase_sector(BOOT_BACKUP_CONFIG_SECTOR); | |
| spi_flash_write(BOOT_BACKUP_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(boot_buffer), BOOT_CONFIG_SIZE); | |
| interrupts(); | |
| } | |
| #define FLASH_MAX_SIZE (unsigned long)(0x100000 - 0x2000) | |
| #define OTA_CHUNK_SIZE (unsigned long)512 | |
| uint8_t getOTAFlashSlot(){ | |
| if(bootConfig->program_rom != 0 && bootConfig->config_rom != 0) | |
| return 0; | |
| else if(bootConfig->program_rom != 4 && bootConfig->config_rom != 4) | |
| return 4; | |
| else | |
| return 8; | |
| } | |
| bool status_led_state = false; | |
| #define STATUS_LED 1 | |
| void LED_Toggle(){ | |
| status_led_state = !status_led_state; | |
| digitalWrite(STATUS_LED, status_led_state); | |
| } | |
| int prepare_for_firmware_update(FileTransfer::Descriptor& file, uint32_t flags, void* reserved) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("FIRMWARE"); | |
| #endif | |
| file.file_address = bootConfig->roms[getOTAFlashSlot()];// + file.chunk_address; | |
| // chunk_size 0 indicates defaults. | |
| if (file.chunk_size==0) { | |
| file.chunk_size = OTA_CHUNK_SIZE; | |
| file.file_length = FLASH_MAX_SIZE; | |
| } | |
| int result = 0; | |
| if (flags & 1) { | |
| // only check address | |
| } | |
| else { | |
| pinMode(STATUS_LED, OUTPUT); | |
| if (file.store!=FileTransfer::Store::FIRMWARE) | |
| return 1; | |
| } | |
| return result; | |
| } | |
| void set_chunks_received(uint8_t value) | |
| { | |
| size_t bytes = chunk_bitmap_size(); | |
| if (bytes) | |
| memset(queue+QUEUE_SIZE-bytes, value, bytes); | |
| } | |
| uint16_t last_chunk = 0; | |
| bool handle_update_begin(msg& message) | |
| { | |
| //event here will send | |
| // spark_send_event("oak/device/stderr","OTA Update Starting", 60, PRIVATE, NULL); | |
| // send ACK | |
| uint8_t* msg_to_send = message.response; | |
| *msg_to_send = 0; | |
| *(msg_to_send + 1) = 16; | |
| uint8_t flags = 0; | |
| int actual_len = message.len - queue[message.len-1]; | |
| if (actual_len>=20 && queue[7]==0xFF) { | |
| flags = decode_uint8(queue+8); | |
| file.chunk_size = decode_uint16(queue+9); | |
| file.file_length = decode_uint32(queue+11); | |
| file.store = FileTransfer::Store::Enum(decode_uint8(queue+15)); | |
| file.file_address = decode_uint32(queue+16); | |
| file.chunk_address = file.file_address; | |
| } | |
| else { | |
| file.chunk_size = 0; | |
| file.file_length = 0; | |
| file.store = FileTransfer::Store::FIRMWARE; | |
| file.file_address = 0; | |
| file.chunk_address = 0; | |
| } | |
| // check the parameters only | |
| bool success = !prepare_for_firmware_update(file, 1, NULL); | |
| if (success) { | |
| success = file.chunk_count(file.chunk_size) < MAX_CHUNKS; | |
| } | |
| last_chunk = file.chunk_count(OTA_CHUNK_SIZE)-1; | |
| coded_ack(msg_to_send+2, success ? 0x00 : RESPONSE_CODE(4,00), queue[2], queue[3]); | |
| if (0 > blocking_send(msg_to_send, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| if (success) | |
| { | |
| if (!prepare_for_firmware_update(file, 0, NULL)) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("F1"); | |
| Serial.println("LASTCHUNKNUM"); | |
| Serial.println(last_chunk); | |
| #endif | |
| last_chunk_millis = millis(); | |
| chunk_index = 0; | |
| chunk_size = file.chunk_size; // save chunk size since the descriptor size is overwritten | |
| updating = 1; | |
| // when not in fast OTA mode, the chunk missing buffer is set to 1 since the protocol | |
| // handles missing chunks one by one. Also we don't know the actual size of the file to | |
| // know the correct size of the bitmap. | |
| set_chunks_received(flags & 1 ? 0 : 0xFF); | |
| // send update_reaady - use fast OTA if available | |
| #ifdef DEBUG_SETUP | |
| Serial.println("F2"); | |
| #endif | |
| update_ready(msg_to_send + 2, message.token, (flags & 0x1)); | |
| //Event does not send may be issue on particle end | |
| spark_send_event("oak/device/stderr","OTA Update Started", 60, PRIVATE, NULL); | |
| delay(100); | |
| if (0 > blocking_send(msg_to_send, 18)) | |
| { | |
| // error | |
| #ifdef DEBUG_SETUP | |
| Serial.println("F3"); | |
| #endif | |
| return false; | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println("F4"); | |
| #endif | |
| /*if (deviceConfig->system_version < OAK_SYSTEM_VERSION_INTEGER || deviceConfig->system_update_pending > 0){ | |
| if(deviceConfig->system_update_pending == 0){ | |
| deviceConfig->system_update_pending = 1; | |
| writeDeviceConfig(); | |
| } | |
| }*/ | |
| //else if(deviceConfig->system_update_pending == 1){ | |
| // set_oakboot_defaults(0); | |
| // deviceConfig->system_update_pending = 2; | |
| // writeDeviceConfig(); | |
| //} | |
| /*else{ | |
| reboot_to_fallback_updater(); | |
| }*/ | |
| // } | |
| } | |
| } | |
| //PUMP ONLY CLOUD WHIE UPDATING | |
| uint32_t update_timeout = millis()+360000;//todo need to find a max for this? | |
| uint32_t last_blink = millis(); | |
| while(updating>0 && millis()<update_timeout){ | |
| spark_process(false); | |
| if(millis() - last_blink >= 100){ | |
| last_blink = millis(); | |
| LED_Toggle(); | |
| } | |
| } | |
| //should never get here | |
| ESP.restart(); | |
| return true; | |
| } | |
| void spark_disconnect(){ | |
| if(pClient.connected()){ | |
| pClient.stop(); | |
| set_system_mode(SEMI_AUTOMATIC); | |
| spark_ok_to_connect = false; | |
| } | |
| } | |
| void spark_delay(uint32_t ms){ | |
| uint32_t start = millis(); | |
| if(ms>6000) | |
| spark_process(false,true); | |
| while((millis()-start)<ms){ | |
| spark_process(false,false); | |
| } | |
| } | |
| int finish_firmware_update(FileTransfer::Descriptor& file, uint32_t flags, void* reserved) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("UPDATE FINISHED - REBOOT ME"); | |
| #endif | |
| if (flags & 1) { // update successful | |
| if((file.chunk_address/SECTOR_SIZE)<((file.file_address+FLASH_MAX_SIZE)/SECTOR_SIZE)-1){ | |
| //check if sector of final chunk is less then the max sector | |
| noInterrupts(); | |
| spi_flash_erase_sector((file.chunk_address/SECTOR_SIZE)+1); | |
| interrupts(); | |
| } | |
| // check CRC and fall through if it fails | |
| if(check_image(getOTAFlashSlot())){ | |
| deviceConfig->ota_success = 1; | |
| writeDeviceConfig(); | |
| spark_send_event("oak/device/stderr","OTA Update Complete", 60, PRIVATE, NULL); | |
| delay(500); | |
| #ifdef DEBUG_SETUP | |
| Serial.println("DONE - RESTART"); | |
| #endif | |
| //set program rom equal to config rom, so that we return to failsafe on failure | |
| bootConfig->current_rom = getOTAFlashSlot(); | |
| bootConfig->ota_reboot = 1; | |
| writeBootConfig(); | |
| spark_disconnect(); | |
| internal_delay(100); | |
| ESP.restart(); | |
| while(1); | |
| } | |
| } | |
| spark_send_event("oak/device/stderr","OTA Update Failed", 60, PRIVATE, NULL); | |
| delay(500); | |
| spark_disconnect(); | |
| internal_delay(100); | |
| ESP.restart(); | |
| return 0; | |
| } | |
| bool flash_erase_sector(uint32_t sector){ | |
| return (spi_flash_erase_sector(sector) == SPI_FLASH_RESULT_OK); | |
| } | |
| bool is_claimed(void){ | |
| return deviceConfig->claimed == 1; | |
| } | |
| uint8_t current_rom(void){ | |
| return bootConfig->current_rom; | |
| } | |
| uint8_t config_rom(void){ | |
| return bootConfig->config_rom; | |
| } | |
| uint8_t user_rom(void){ | |
| return bootConfig->program_rom; | |
| } | |
| uint8_t update_rom(void){ | |
| return bootConfig->update_rom; | |
| } | |
| uint8_t ota_reboot(void){ | |
| return bootConfig->ota_reboot; | |
| } | |
| void save_firmware_chunk(FileTransfer::Descriptor& file, uint8_t* chunk, void* reserved) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("S1"); | |
| #endif | |
| //todo ensure chunk is 512, else pad to 512 to ensure alignment | |
| //noInterrupts(); | |
| if(file.chunk_address%SECTOR_SIZE == 0){ | |
| if(spi_flash_erase_sector(file.chunk_address/SECTOR_SIZE) != SPI_FLASH_RESULT_OK){ | |
| #ifdef DEBUG_SETUP | |
| Serial.println("SF"); | |
| #endif | |
| } | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println(file.chunk_address); | |
| #endif | |
| memcpy(chunk_buffer,chunk,512); | |
| spi_flash_write(file.chunk_address, reinterpret_cast<uint32_t*>(chunk_buffer), OTA_CHUNK_SIZE); | |
| //interrupts(); | |
| #ifdef DEBUG_SETUP | |
| Serial.println("S2"); | |
| #endif | |
| return; | |
| } | |
| inline void flag_chunk_received(chunk_index_t idx) | |
| { | |
| // serial_dump("flagged chunk %d", idx); | |
| chunk_bitmap()[idx>>3] |= uint8_t(1<<(idx&7)); | |
| } | |
| void notify_update_done(uint8_t* buf) | |
| { | |
| unsigned short message_id = next_message_id(); | |
| size_t size = Messages::update_done(buf+2, message_id, false); | |
| wrap(buf, size); | |
| } | |
| bool handle_chunk(msg& message) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("CHUNK"); | |
| #endif | |
| last_chunk_millis = millis(); | |
| //Faults without this. | |
| internal_delay(10); | |
| //serial_dump("chunk"); | |
| if (!updating) { | |
| //serial_dump("got chunk when not updating"); | |
| return true; | |
| } | |
| bool fast_ota = false; | |
| uint8_t payload = 7; | |
| unsigned option = 0; | |
| uint32_t given_crc = 0; | |
| while (queue[payload]!=0xFF) { | |
| switch (option) { | |
| case 0: | |
| given_crc = decode_uint32(queue+payload+1); | |
| break; | |
| case 1: | |
| chunk_index = decode_uint16(queue+payload+1); | |
| fast_ota = true; | |
| break; | |
| } | |
| option++; | |
| payload += (queue[payload]&0xF)+1; // increase by the size. todo handle > 11 | |
| } | |
| uint8_t* msg_to_send = message.response; | |
| //Move this down here to match Spark, also only send if not fast ota | |
| if(!fast_ota){ | |
| // send ACK | |
| *msg_to_send = 0; | |
| *(msg_to_send + 1) = 16; | |
| empty_ack(msg_to_send + 2, queue[2], queue[3]); | |
| if (0 > blocking_send(msg_to_send, 18)) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("CE1"); | |
| #endif | |
| return false; | |
| } | |
| } | |
| if (0xFF==queue[payload]) | |
| { | |
| payload++; | |
| uint8_t* chunk = queue+payload; | |
| file.chunk_size = message.len - payload - queue[message.len - 1]; // remove length added due to pkcs #7 padding? | |
| file.chunk_address = file.file_address + (chunk_index * chunk_size); | |
| if (chunk_index>=MAX_CHUNKS) { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("invalid chunk index"); | |
| #endif | |
| return false; | |
| } | |
| uint32_t crc = crc32(chunk, file.chunk_size); | |
| bool has_response = false; | |
| bool crc_valid = (crc == given_crc); | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C0"); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| Serial.printf("chunk idx=%d crc=%d fast=%d updating=%d", chunk_index, crc_valid, fast_ota, updating); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C00"); | |
| #endif | |
| if (crc_valid) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C1"); | |
| #endif | |
| save_firmware_chunk(file, chunk, NULL); | |
| if (!fast_ota || (updating!=2 && (true || (chunk_index & 32)==0))) { | |
| chunk_received(msg_to_send + 2, message.token, ChunkReceivedCode::OK); | |
| has_response = true; | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C2"); | |
| #endif | |
| flag_chunk_received(chunk_index); | |
| if (updating==2) { // clearing up missed chunks at the end of fast OTA | |
| chunk_index_t next_missed = next_chunk_missing(0); | |
| if (next_missed==NO_CHUNKS_MISSING) { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("NO MISS"); | |
| #endif | |
| notify_update_done(msg_to_send); | |
| finish_firmware_update(file, 1, NULL); | |
| has_response = true; | |
| } | |
| else { | |
| if (has_response && 0 > blocking_send(msg_to_send, 18)) { | |
| //serial_dump("send chunk response failed"); | |
| return false; | |
| } | |
| has_response = false; | |
| if (next_missed>missed_chunk_index) | |
| send_missing_chunks(MISSED_CHUNKS_TO_SEND); | |
| } | |
| } | |
| chunk_index++; | |
| } | |
| else if (!fast_ota) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C3"); | |
| #endif | |
| chunk_received(msg_to_send + 2, message.token, ChunkReceivedCode::BAD); | |
| has_response = true; | |
| //serial_dump("chunk bad %d", chunk_index); | |
| } | |
| // fast OTA will request the chunk later | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C4"); | |
| #endif | |
| if (!fast_ota && has_response && 0 > blocking_send(msg_to_send, 18)) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("C5"); | |
| #endif | |
| // error | |
| return false; | |
| } | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println("ChunkFIN"); | |
| #endif | |
| return true; | |
| } | |
| bool handle_update_done(msg& message) | |
| { | |
| // send ACK 2.04 | |
| uint8_t* msg_to_send = message.response; | |
| *msg_to_send = 0; | |
| *(msg_to_send + 1) = 16; | |
| #ifdef DEBUG_SETUP | |
| Serial.println("update done received"); | |
| #endif | |
| chunk_index_t index = next_chunk_missing(0); | |
| bool missing = index!=NO_CHUNKS_MISSING; | |
| coded_ack(msg_to_send + 2, message.token, missing ? ChunkReceivedCode::BAD : ChunkReceivedCode::OK, queue[2], queue[3]); | |
| if (0 > blocking_send(msg_to_send, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| if (!missing) { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("update done - all done!"); | |
| #endif | |
| finish_firmware_update(file, 1, NULL); | |
| } | |
| else { | |
| updating = 2; // flag that we are sending missing chunks. | |
| #ifdef DEBUG_SETUP | |
| Serial.println("update done - missing chunks"); | |
| #endif | |
| send_missing_chunks(MISSED_CHUNKS_TO_SEND); | |
| last_chunk_millis = millis(); | |
| } | |
| return true; | |
| } | |
| bool handle_message(msg& message, token_t token, CoAPMessageType::Enum message_type) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("HM1"); | |
| Serial.println(message_type); | |
| #endif | |
| switch (message_type) | |
| { | |
| case CoAPMessageType::DESCRIBE: | |
| { | |
| if (!send_description(DESCRIBE_SYSTEM, message) || !send_description(DESCRIBE_APPLICATION, message)) { | |
| return false; | |
| } | |
| break; | |
| } | |
| case CoAPMessageType::FUNCTION_CALL: | |
| if (!handle_function_call(message)) | |
| return false; | |
| break; | |
| case CoAPMessageType::VARIABLE_REQUEST: | |
| { | |
| // copy the variable key | |
| int variable_key_length = queue[7] & 0x0F; | |
| if (12 < variable_key_length) | |
| variable_key_length = 12; | |
| char variable_key[13]; | |
| memcpy(variable_key, queue + 8, variable_key_length); | |
| memset(variable_key + variable_key_length, 0, 13 - variable_key_length); | |
| queue[0] = 0; | |
| queue[1] = 16; // default buffer length | |
| // get variable value according to type using the descriptor | |
| SparkReturnType::Enum var_type = wrapVarTypeInEnum(variable_key); | |
| if(SparkReturnType::BOOLEAN == var_type) | |
| { | |
| bool *bool_val = (bool *)getUserVar(variable_key); | |
| variable_value(queue + 2, token, queue[2], queue[3], *bool_val); | |
| } | |
| else if(SparkReturnType::INT == var_type) | |
| { | |
| int *int_val = (int *)getUserVar(variable_key); | |
| variable_value(queue + 2, token, queue[2], queue[3], *int_val); | |
| } | |
| else if(SparkReturnType::STRING == var_type) | |
| { | |
| char *str_val = (char *)getUserVar(variable_key); | |
| // 2-byte leading length, 16 potential padding bytes | |
| int max_length = QUEUE_SIZE - 2 - 16; | |
| int str_length = strlen(str_val); | |
| if (str_length > max_length) { | |
| str_length = max_length; | |
| } | |
| int buf_size = variable_value(queue + 2, token, queue[2], queue[3], str_val, str_length); | |
| queue[1] = buf_size & 0xff; | |
| queue[0] = (buf_size >> 8) & 0xff; | |
| } | |
| else if(SparkReturnType::DOUBLE == var_type) | |
| { | |
| double *double_val = (double *)getUserVar(variable_key); | |
| variable_value(queue + 2, token, queue[2], queue[3], *double_val); | |
| } | |
| // buffer length may have changed if variable is a long string | |
| if (0 > blocking_send(queue, (queue[0] << 8) + queue[1] + 2)) | |
| { | |
| // error | |
| return false; | |
| } | |
| break; | |
| } | |
| case CoAPMessageType::SAVE_BEGIN: | |
| // fall through | |
| case CoAPMessageType::UPDATE_BEGIN: | |
| return handle_update_begin(message); | |
| case CoAPMessageType::CHUNK: | |
| return handle_chunk(message); | |
| case CoAPMessageType::UPDATE_DONE: | |
| return handle_update_done(message); | |
| case CoAPMessageType::EVENT: | |
| handle_event(message); | |
| break; | |
| case CoAPMessageType::KEY_CHANGE: | |
| // TODO | |
| break; | |
| case CoAPMessageType::SIGNAL_START: | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| coded_ack(queue + 2, token, ChunkReceivedCode::OK, queue[2], queue[3]); | |
| if (0 > blocking_send(queue, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| //callbacks.signal(true, 0, NULL); | |
| break; | |
| case CoAPMessageType::SIGNAL_STOP: | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| coded_ack(queue + 2, token, ChunkReceivedCode::OK, queue[2], queue[3]); | |
| if (0 > blocking_send(queue, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| //callbacks.signal(false, 0, NULL); | |
| break; | |
| case CoAPMessageType::HELLO: | |
| if(deviceConfig->ota_success == 1){ | |
| deviceConfig->ota_success = 0; | |
| writeDeviceConfig(); | |
| } | |
| break; | |
| case CoAPMessageType::TIME: | |
| handle_time_response(queue[6] << 24 | queue[7] << 16 | queue[8] << 8 | queue[9]); | |
| break; | |
| case CoAPMessageType::PING: | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| empty_ack(queue + 2, queue[2], queue[3]); | |
| if (0 > blocking_send(queue, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| break; | |
| case CoAPMessageType::EMPTY_ACK: | |
| case CoAPMessageType::ERROR: | |
| default: | |
| ; // drop it on the floor | |
| } | |
| // all's well | |
| return true; | |
| } | |
| CoAPMessageType::Enum received_message(unsigned char *buf, size_t length) | |
| { | |
| unsigned char next_iv[16]; | |
| memcpy(next_iv, buf, 16); | |
| aes_setkey_dec(&aes, key, 128); | |
| aes_crypt_cbc(&aes, AES_DECRYPT, length, iv_receive, buf, buf); | |
| memcpy(iv_receive, next_iv, 16); | |
| return Messages::decodeType(buf, length); | |
| } | |
| CoAPMessageType::Enum handle_received_message(void) | |
| { | |
| #ifdef DEBUG_SETUP | |
| Serial.println("HRM1"); | |
| #endif | |
| last_message_millis = millis(); | |
| expecting_ping_ack = false; | |
| size_t len = queue[0] << 8 | queue[1]; | |
| if (len > QUEUE_SIZE) { // TODO add sanity check on data, e.g. CRC | |
| return CoAPMessageType::ERROR; | |
| } | |
| if (0 > blocking_receive(queue, len)) | |
| { | |
| // error | |
| return CoAPMessageType::ERROR; | |
| } | |
| CoAPMessageType::Enum message_type = received_message(queue, len); | |
| unsigned char token = queue[4]; | |
| unsigned char *msg_to_send = queue + len; | |
| msg message; | |
| message.len = len; | |
| message.token = queue[4]; | |
| message.response = msg_to_send; | |
| message.response_len = QUEUE_SIZE-len; | |
| return handle_message(message, token, message_type) | |
| ? message_type : CoAPMessageType::ERROR; | |
| } | |
| // Returns true if no errors and still connected. | |
| // Returns false if there was an error, and we are probably disconnected. | |
| bool event_loop(CoAPMessageType::Enum& message_type) | |
| { | |
| message_type = CoAPMessageType::NONE; | |
| int bytes_received = receive(queue, 2); | |
| if (2 <= bytes_received) | |
| { | |
| message_type = handle_received_message(); | |
| if (message_type==CoAPMessageType::ERROR) | |
| { | |
| if (updating) { // was updating but had an error, inform the client | |
| #ifdef DEBUG_SETUP | |
| Serial.println("up error"); | |
| #endif | |
| finish_firmware_update(file, 0, NULL); | |
| updating = false; | |
| } | |
| // bail if and only if there was an error | |
| #ifdef DEBUG_SETUP | |
| INFO("UPDATE ERROR"); | |
| #endif | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| if (0 > bytes_received) | |
| { | |
| // error, disconnected | |
| #ifdef DEBUG_SETUP | |
| INFO("DISCONNECT"); | |
| #endif | |
| return false; | |
| } | |
| if (updating) | |
| { | |
| uint32_t millis_since_last_chunk = millis() - last_chunk_millis; | |
| if (3000 < millis_since_last_chunk) | |
| { | |
| if (updating==2) { // send missing chunks | |
| //serial_dump("timeout - resending missing chunks"); | |
| if (!send_missing_chunks(MISSED_CHUNKS_TO_SEND)){ | |
| #ifdef DEBUG_SETUP | |
| INFO("CHUNK ERROR"); | |
| #endif | |
| return false; | |
| } | |
| } | |
| /* Do not resend chunks since this can cause duplicates on the server. | |
| else | |
| { | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| chunk_missed(queue + 2, chunk_index); | |
| if (0 > blocking_send(queue, 18)) | |
| { | |
| // error | |
| return false; | |
| } | |
| } | |
| */ | |
| last_chunk_millis = millis(); | |
| } | |
| } | |
| else | |
| { | |
| uint32_t millis_since_last_message = millis() - last_message_millis; | |
| if (expecting_ping_ack) | |
| { | |
| if (10000 < millis_since_last_message) | |
| { | |
| // timed out, disconnect | |
| expecting_ping_ack = false; | |
| last_message_millis = millis(); | |
| #ifdef DEBUG_SETUP | |
| ERROR("FAILED4"); | |
| #endif | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| if (15000 < millis_since_last_message) | |
| { | |
| queue[0] = 0; | |
| queue[1] = 16; | |
| ping(queue + 2); | |
| blocking_send(queue, 18); | |
| expecting_ping_ack = true; | |
| last_message_millis = millis(); | |
| } | |
| } | |
| } | |
| } | |
| // no errors, still connected | |
| return true; | |
| } | |
| bool event_loop(CoAPMessageType::Enum message_type, uint32_t timeout) | |
| { | |
| uint32_t start = millis(); | |
| do | |
| { | |
| CoAPMessageType::Enum msgtype; | |
| if (!event_loop(msgtype)) | |
| return false; | |
| if (msgtype==message_type) | |
| return true; | |
| yield(); | |
| } | |
| while ((millis()-start) < timeout); | |
| return false; | |
| } | |
| bool event_loop() | |
| { | |
| CoAPMessageType::Enum message; | |
| bool res; | |
| uint32_t start = millis(); | |
| do { | |
| res = event_loop(message); | |
| yield(); | |
| }while(res && pClient.available() >= 2 && (millis()-start) < 20); | |
| return res; | |
| } | |
| int handshake(){ | |
| #ifdef DEBUG_SETUP | |
| INFO("SHAKE"); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| Serial.println(pClient.status()); | |
| #endif | |
| memcpy(queue + 40, device_id, 12); | |
| int err = blocking_receive(queue, 40);; | |
| #ifdef DEBUG_SETUP | |
| Serial.println(err); | |
| #endif | |
| if (0 > err) { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: could not receive nonce"); | |
| #endif | |
| return err; | |
| } | |
| memcpy(queue+52, deviceConfig->device_public_key,PUBLIC_KEY_LENGTH); | |
| #ifdef DEBUG_SETUP | |
| INFO("SHAKE1"); | |
| #endif | |
| rsa_context rsa; | |
| init_rsa_context_with_public_key(&rsa, deviceConfig->server_public_key); | |
| const int len = 52+PUBLIC_KEY_LENGTH; | |
| err = rsa_pkcs1_encrypt(&rsa, RSA_PUBLIC, len, queue, queue + len); | |
| rsa_free(&rsa); | |
| if (err) { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: rsa encrypt error"); | |
| #endif | |
| return err; } | |
| #ifdef DEBUG_SETUP | |
| Serial.println(pClient.status()); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| INFO("SHAKE2"); | |
| #endif | |
| err = blocking_send(queue + len, 256); | |
| #ifdef DEBUG_SETUP | |
| INFO("SHAKE3"); | |
| #endif | |
| if (0 > err) { | |
| #ifdef DEBUG_SETUP | |
| Serial.println(pClient.status()); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: Unable to send key"); | |
| #endif | |
| return err; | |
| } | |
| #ifdef DEBUG_SETUP | |
| INFO("SHAKE4"); | |
| #endif | |
| err = blocking_receive(queue, 384); | |
| if (0 > err) { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: Unable to receive key"); | |
| #endif | |
| return err; } | |
| #ifdef DEBUG_SETUP | |
| INFO("SET KEY"); | |
| #endif | |
| err = set_key(queue); | |
| if (err) { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: could not set key"); | |
| #endif | |
| return err; } | |
| #ifdef DEBUG_SETUP | |
| INFO("SEND HELLO"); | |
| #endif | |
| //gets reset on response in handle message | |
| hello(queue, deviceConfig->ota_success); | |
| #ifdef DEBUG_SETUP | |
| INFO("GET HELLO RESPONSE"); | |
| #endif | |
| err = blocking_send(queue, 18); | |
| if (0 > err) { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Hanshake: could not send hello message"); | |
| #endif | |
| return err; } | |
| #ifdef DEBUG_SETUP | |
| INFO("WAIT FOR SERVER HELLO"); | |
| #endif | |
| if (!event_loop(CoAPMessageType::HELLO, 2000)) // read the hello message from the server | |
| { | |
| #ifdef DEBUG_SETUP | |
| ERROR("Handshake: could not receive hello response"); | |
| #endif | |
| return -1; | |
| } | |
| #ifdef DEBUG_SETUP | |
| INFO("Hanshake: completed"); | |
| #endif | |
| return 0; | |
| } | |
| void remove_event_handlers(const char* event_name) | |
| { | |
| if (NULL == event_name) | |
| { | |
| memset(event_handlers, 0, sizeof(event_handlers)); | |
| } | |
| else | |
| { | |
| const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); | |
| int dest = 0; | |
| for (int i = 0; i < NUM_HANDLERS; i++) | |
| { | |
| if (!strcmp(event_name, event_handlers[i].filter)) | |
| { | |
| memset(&event_handlers[i], 0, sizeof(event_handlers[i])); | |
| } | |
| else | |
| { | |
| if (dest!=i) { | |
| memcpy(event_handlers+dest, event_handlers+i, sizeof(event_handlers[i])); | |
| memset(event_handlers+i, 0, sizeof(event_handlers[i])); | |
| } | |
| dest++; | |
| } | |
| } | |
| } | |
| } | |
| bool particleConnect(){ | |
| uint8_t max_connect_tries = 3; | |
| // Generate a random local port number, to avoid issues with | |
| // previous connections left hanging on the server | |
| // register used is apparently a RNG, see: | |
| // http://esp8266-re.foogod.com/wiki/Random_Number_Generator | |
| uint16_t local_port = ESP8266_DREG(0x20E44); | |
| // Avoid using low port numbers, 4k is probably overkill, but it doesn't matter. | |
| if(local_port < 4096) local_port += 4096; | |
| pClient.setLocalPortStart((uint16_t)local_port); | |
| if(deviceConfig->server_address_type == 1){ | |
| while(!pClient.connect(deviceConfig->server_address_domain,SPARK_SERVER_PORT) && max_connect_tries > 0){ | |
| max_connect_tries--; | |
| } | |
| //return pClient.connect("staging-device.spark.io",SPARK_SERVER_PORT); | |
| //return pClient.connect(IPAddress(192,168,0,111),SPARK_SERVER_PORT); | |
| } | |
| else{ | |
| while(!pClient.connect(IPAddress(deviceConfig->server_address_ip),SPARK_SERVER_PORT) && max_connect_tries > 0){ | |
| max_connect_tries--; | |
| } | |
| } | |
| if(max_connect_tries>0) | |
| return true; | |
| else | |
| return false; | |
| } | |
| //this connects to the configured wifi | |
| bool wifiConnect(){ | |
| WiFi.softAPdisconnect(false); | |
| WiFi.mode_internal(WIFI_STA); | |
| if(deviceConfig->passcode[0] != '\0' && deviceConfig->channel > 0){ | |
| WiFi.begin_internal(deviceConfig->ssid,deviceConfig->passcode, deviceConfig->channel); | |
| } | |
| else if(deviceConfig->passcode[0] != '\0'){ | |
| WiFi.begin_internal(deviceConfig->ssid,deviceConfig->passcode); | |
| } | |
| else if(deviceConfig->channel > 0){ | |
| WiFi.begin_internal(deviceConfig->ssid, NULL, deviceConfig->channel); | |
| } | |
| else if (deviceConfig->ssid[0] != '\0'){ | |
| WiFi.begin_internal(deviceConfig->ssid); | |
| } | |
| else{ | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool wifiConnected(){ | |
| return (WiFi.status() == WL_CONNECTED); | |
| } | |
| bool wifiWaitForConnection(){ //returns false if it times out - I will later add code to jump to config rom in this case | |
| uint32_t timeoutTime = millis() + 15000; | |
| while (WiFi.status() != WL_CONNECTED) | |
| { | |
| yield(); | |
| //timeout after 15 seconds | |
| if(millis() > timeoutTime){ | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool readDeviceConfig(bool isSystem){ | |
| noInterrupts(); | |
| spi_flash_read(DEVICE_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(config_buffer), DEVICE_CONFIG_SIZE); | |
| if(deviceConfig->magic != DEVICE_MAGIC || deviceConfig->chksum != calc_device_chksum((uint8*)deviceConfig, (uint8*)&deviceConfig->chksum)){ | |
| //load the backup and copy to main | |
| spi_flash_read(DEVICE_BACKUP_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(config_buffer), DEVICE_CONFIG_SIZE); | |
| spi_flash_erase_sector(DEVICE_CONFIG_SECTOR); | |
| spi_flash_write(DEVICE_CONFIG_SECTOR * SECTOR_SIZE, reinterpret_cast<uint32_t*>(config_buffer), DEVICE_CONFIG_SIZE); | |
| } | |
| interrupts(); | |
| if(deviceConfig->magic != DEVICE_MAGIC || deviceConfig->chksum != calc_device_chksum((uint8*)deviceConfig, (uint8*)&deviceConfig->chksum)){ | |
| if(isSystem){ | |
| ets_memset(deviceConfig, 0x00, sizeof(oak_config)); | |
| deviceConfig->magic = DEVICE_MAGIC; | |
| deviceConfig->chksum = calc_device_chksum((uint8*)deviceConfig, (uint8*)&deviceConfig->chksum); | |
| writeDeviceConfig(); | |
| } | |
| else{ | |
| reboot_to_config(); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| unsigned char next_token() | |
| { | |
| return ++_token; | |
| } | |
| size_t time_request(unsigned char *buf) | |
| { | |
| uint16_t msg_id = next_message_id(); | |
| uint8_t token = next_token(); | |
| return Messages::time_request(buf, msg_id, token); | |
| } | |
| bool send_time_request(void) | |
| { | |
| if (updating) | |
| { | |
| return false; | |
| } | |
| size_t msglen = time_request(queue + 2); | |
| size_t wrapped_len = wrap(queue, msglen); | |
| last_chunk_millis = millis(); | |
| return (0 <= blocking_send(queue, wrapped_len)); | |
| } | |
| bool particle_handshake(){ | |
| char buf[65]; | |
| #ifdef DEBUG_SETUP | |
| INFO("START HANDSHAKE"); | |
| #endif | |
| if(handshake()<0) | |
| return false; | |
| #ifdef DEBUG_SETUP | |
| INFO("END HANDSHAKE"); | |
| #endif | |
| #ifdef DEBUG_SETUP | |
| INFO("SEND EVENTS"); | |
| #endif | |
| if(deviceConfig->claim_code[0] != '\0') | |
| spark_send_event("spark/device/claim/code", deviceConfig->claim_code, 60, PRIVATE, NULL); | |
| //send max size of rom | |
| ultoa(FLASH_MAX_SIZE, buf, 10); | |
| spark_send_event("spark/hardware/max_binary", buf, 60, PRIVATE, NULL); | |
| //send ota chunk size | |
| ultoa(OTA_CHUNK_SIZE, buf, 10); | |
| spark_send_event("spark/hardware/ota_chunk_size", buf, 60, PRIVATE, NULL); | |
| ///if we want to be able to get a system update we need to send that we are in safe more right now | |
| /* | |
| if (deviceConfig->system_version < OAK_SYSTEM_VERSION_INTEGER || deviceConfig->system_update_pending > 0){ | |
| spark_send_event("spark/device/safemode" "", "", 60, PRIVATE, NULL); | |
| } | |
| */ | |
| /* | |
| #if defined(SPARK_SUBSYSTEM_EVENT_NAME) | |
| if (!HAL_core_subsystem_version(buf, sizeof (buf)) && *buf) | |
| { | |
| spark_send_event("spark/" SPARK_SUBSYSTEM_EVENT_NAME, buf, 60, PRIVATE, NULL); | |
| } | |
| #endif | |
| */ | |
| #ifdef DEBUG_SETUP | |
| INFO("SEND SUBS"); | |
| #endif | |
| send_subscriptions(); | |
| // important this comes at the end since it requires a response from the cloud. | |
| #ifdef DEBUG_SETUP | |
| INFO("SEND TIME REQ"); | |
| #endif | |
| send_time_request(); | |
| #ifdef DEBUG_SETUP | |
| INFO("LOOP"); | |
| #endif | |
| if(!event_loop()){ | |
| #ifdef DEBUG_SETUP | |
| ERROR("SHAKE LOOP FAIL"); | |
| #endif | |
| } | |
| return true; | |
| } | |
| void reboot_to_user(){ | |
| if(bootConfig->current_rom != bootConfig->program_rom){ | |
| bootConfig->current_rom = bootConfig->program_rom; | |
| writeBootConfig(); | |
| } | |
| ESP.restart(); | |
| while(1); | |
| } | |
| void reboot_to_config(){ | |
| if(bootConfig->current_rom != bootConfig->config_rom){ | |
| bootConfig->current_rom = bootConfig->config_rom; | |
| writeBootConfig(); | |
| } | |
| ESP.restart(); | |
| while(1); | |
| } | |
| void reboot_to_fallback_updater(){ | |
| if(bootConfig->current_rom != bootConfig->update_rom){ | |
| bootConfig->current_rom = bootConfig->update_rom; | |
| writeBootConfig(); | |
| } | |
| ESP.restart(); | |
| while(1); | |
| } | |
| String spark_deviceID(){ | |
| return String(deviceConfig->device_id); | |
| } | |
| const char* CLAIM_EVENTS = "spark/device/claim/"; | |
| const char* RESET_EVENT = "spark/device/reset"; | |
| const char* OAK_RESET_EVENT = "oak/device/reset"; | |
| const char* OAK_RX_EVENT = "oak/device/stdin"; | |
| #define OAK_RESET_EVENT_LEN 16 | |
| #define OAK_RX_EVENT_LEN 16 | |
| void SystemEvents(const char* name, const char* data) | |
| { | |
| if (!strncmp(name, CLAIM_EVENTS, strlen(CLAIM_EVENTS))) { | |
| //mark as claimed | |
| deviceConfig->claim_code[0] = '\0'; | |
| deviceConfig->claimed = 1; | |
| writeDeviceConfig(); | |
| } | |
| if (!strcmp(name, RESET_EVENT)) { | |
| if (data && *data) { | |
| if (!strcmp("safe mode", data)) | |
| reboot_to_config(); | |
| else if (!strcmp("dfu", data)) | |
| return; | |
| else if (!strcmp("reboot", data)) | |
| ESP.reset(); | |
| } | |
| } | |
| if (!strncmp(name, OAK_RESET_EVENT, OAK_RESET_EVENT_LEN) && | |
| (name[OAK_RESET_EVENT_LEN] == 0 || | |
| (name[OAK_RESET_EVENT_LEN] == '/' && | |
| !strcmp((name+OAK_RESET_EVENT_LEN+1),deviceConfig->device_id)))) { | |
| if (data && *data) { | |
| if (!strcmp("config mode", data)) | |
| reboot_to_config(); | |
| else if (!strcmp("user mode", data)) | |
| reboot_to_user(); | |
| else if (!strcmp("update mode", data)) | |
| reboot_to_fallback_updater(); | |
| else if (!strcmp("reboot", data)) | |
| ESP.reset(); | |
| else if (!strcmp("mode",data)) | |
| if (bootConfig->current_rom == bootConfig->config_rom) | |
| spark_send_event("oak/device/mode/config", "", 60, PRIVATE, NULL); | |
| else if (bootConfig->current_rom == bootConfig->program_rom) | |
| spark_send_event("oak/device/mode/user", "", 60, PRIVATE, NULL); | |
| else if (bootConfig->current_rom == bootConfig->update_rom) | |
| spark_send_event("oak/device/mode/update", "", 60, PRIVATE, NULL); | |
| } | |
| } | |
| if (!strcmp(name, OAK_RX_EVENT)) { | |
| if (data && *data) { | |
| /* | |
| while(*data != '\0'){ | |
| // if buffer full, set the overflow flag and return | |
| uint8_t next = (spark_receive_buffer_tail + 1) % MAX_BUFF; | |
| if (next != spark_receive_buffer_head) | |
| { | |
| // save new data in buffer: tail points to where byte goes | |
| spark_receive_buffer[spark_receive_buffer_tail] = *data; // save new byte | |
| data++; | |
| spark_receive_buffer_tail = next; | |
| } | |
| else | |
| { | |
| spark_buffer_overflow = true; | |
| return; | |
| } | |
| } | |
| */ | |
| } | |
| } | |
| } | |
| bool oak_rom_inited = false; | |
| void oak_rom_init(){ | |
| if(oak_rom_inited) | |
| return; | |
| oak_rom_inited = true; | |
| #ifndef OAK_SYSTEM_ROM_4F616B | |
| #pragma message "SYSTEM DEFINE NOT SET, DEFAULTING TO USER ROM" | |
| #define OAK_SYSTEM_ROM_4F616B 0 | |
| #else | |
| #pragma message "SYSTEM DEFINE SET" | |
| #endif | |
| #ifdef OAK_SYSTEM_ROM_4F616B //DO NOT DEFINE THIS IN YOUR FILE OR IT MAY CORRUPT YOUR DEVICE | |
| if(OAK_SYSTEM_ROM_4F616B == 82 && deviceConfig->system_version < OAK_SYSTEM_VERSION_INTEGER){ | |
| //TODO WHAT ABOUT BOOTING TO THIS DO TO FAILURE AFTER UPDATE | |
| //this is a new system rom that we just booted into | |
| deviceConfig->system_version = OAK_SYSTEM_VERSION_INTEGER; | |
| sprintf(deviceConfig->version_string, "%d.%d.%d", OAK_SYSTEM_VERSION_MAJOR, OAK_SYSTEM_VERSION_MINOR, OAK_SYSTEM_VERSION_RELEASE); | |
| #ifdef DEBUG_SETUP | |
| Serial.println(deviceConfig->version_string); | |
| #endif | |
| //memcpy(deviceConfig->version_string,OAK_SYSTEM_VERSION_STRING,sizeof(OAK_SYSTEM_VERSION_STRING)); | |
| if(bootConfig->config_rom != bootConfig->current_rom){ | |
| bootConfig->ota_reboot = 0; | |
| bootConfig->config_rom = bootConfig->current_rom; | |
| bootConfig->update_rom = bootConfig->current_rom+2; | |
| writeBootConfig(); | |
| //Serial.println("INIT"); | |
| } | |
| deviceConfig->system_update_pending = 0; | |
| init_bootloader_flags(); | |
| writeDeviceConfig(); | |
| //go back to the user application | |
| if(bootConfig->program_rom != bootConfig->current_rom) | |
| reboot_to_user(); | |
| } | |
| else if(OAK_SYSTEM_ROM_4F616B != 82){ | |
| //dont do this until we reach the end of setup? the end of loop? | |
| //this is a new user rom, we have booted so set user rom to this | |
| if(bootConfig->program_rom != bootConfig->current_rom){ //if not already set | |
| bootConfig->ota_reboot = 0; | |
| bootConfig->program_rom = bootConfig->current_rom; | |
| writeBootConfig(); | |
| } | |
| init_bootloader_flags(); | |
| } | |
| #endif | |
| } | |
| //this should be called when the Particle library is inited | |
| void spark_initConfig(bool isSystem){ | |
| if(spark_initialized) | |
| return; | |
| spark_initialized = true; | |
| #ifdef DEBUG_SETUP | |
| Serial.println("INIT CONFIG"); | |
| #endif | |
| readDeviceConfig(isSystem); //will not return if valid device config does not exist, will reboot to config ROM, unless isSytem in which case it will create a new one | |
| readBootConfig(); | |
| hex_decode(device_id,12,deviceConfig->device_id); | |
| spark_subscribe("spark", SystemEvents, NULL, ALL_DEVICES, NULL, NULL); | |
| spark_subscribe("oak", SystemEvents, NULL, MY_DEVICES, NULL, NULL); | |
| } | |
| uint8_t wifi_connect_failed = 0; | |
| bool spark_internal_connect(){ | |
| if(!spark_initialized) | |
| spark_initConfig(false); | |
| spark_connect_pending = true; | |
| if(!wifiConnected()){ | |
| if(!wifiConnect()){ | |
| //the wifi info is just bad | |
| reboot_to_config(); | |
| #ifdef DEBUG_SETUP | |
| Serial.println("WIFI"); | |
| #endif | |
| spark_connect_pending = false; | |
| return false; | |
| } | |
| if(!wifiWaitForConnection()){ | |
| #ifdef DEBUG_SETUP | |
| Serial.println("WAIT"); | |
| #endif | |
| spark_connect_pending = false; | |
| wifi_connect_failed++; | |
| #ifndef DISABLE_AUTO_CONFIG | |
| if(wifi_connect_failed>5) | |
| reboot_to_config(); | |
| #endif | |
| return false; | |
| } | |
| wifi_connect_failed = 0; | |
| } | |
| if(!pClient.connected()){ | |
| if(!particleConnect()){ | |
| #ifdef DEBUG_SETUP | |
| Serial.println("Particle"); | |
| #endif | |
| spark_connect_pending = false; | |
| return false; | |
| } | |
| else{ | |
| //we just connected | |
| spark_describe_called = false; | |
| } | |
| if(!particle_handshake()){ | |
| #ifdef DEBUG_SETUP | |
| Serial.println("SHAKE"); | |
| #endif | |
| pClient.stop(); | |
| spark_connect_pending = false; | |
| return false; | |
| } | |
| } | |
| spark_connect_pending = false; | |
| return true; | |
| } | |
| uint32_t spark_last_failed_connect = 0; | |
| bool spark_auto_connect(bool internal){ | |
| if(internal) | |
| oak_rom_init(); | |
| if(system_mode>1 && internal) | |
| return false; | |
| spark_ok_to_connect = true; | |
| #ifdef DEBUG_SETUP | |
| Serial.println("AUTO CONNECT"); | |
| #endif | |
| while(!spark_connect() && spark_failed_connects < 3){yield();}; | |
| #ifdef DEBUG_SETUP | |
| Serial.println("END AUTO CONNECT"); | |
| #endif | |
| if(spark_failed_connects == 3) | |
| return false; | |
| else{ | |
| //pump events until describe | |
| //uint32_t desc_start = millis(); | |
| /*while(!spark_describe_called && millis()-desc_start<3000){ | |
| #ifdef DEBUG_SETUP | |
| Serial.println("DESC PUMP"); | |
| #endif | |
| spark_process(); | |
| }*/ | |
| //pump 5 times | |
| uint8_t pumpLoop = 5; | |
| while(pumpLoop-->0){ | |
| spark_process(); | |
| } | |
| return true; | |
| } | |
| } | |
| bool spark_connect(){ | |
| //connect with automatic back off | |
| // | |
| if(spark_failed_connects < 2 || | |
| (spark_failed_connects < 5 && millis()-spark_last_failed_connect > 5000) || | |
| millis()-spark_last_failed_connect > 30000 ){ | |
| if(!spark_internal_connect()){ | |
| spark_failed_connects++; | |
| spark_last_failed_connect = millis(); | |
| return false; | |
| } | |
| else{ | |
| spark_failed_connects = 0; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| void spark_process(bool internal, bool allow_connect) | |
| { | |
| if(!internal) | |
| yield(); | |
| else if(system_mode == 3) | |
| return; | |
| if(spark_connect_pending){ | |
| // #ifdef DEBUG_SETUP | |
| // ERROR("ALREADY PENDING"); | |
| // #endif | |
| return; | |
| } | |
| if(spark_connected()){ | |
| if(millis() - lastCloudEvent > 1000){ | |
| if(spark_send_tx()>0) | |
| lastCloudEvent = millis(); | |
| } | |
| if(!event_loop()){ | |
| if(pClient.connected()){ | |
| pClient.stop(); | |
| } | |
| #ifdef DEBUG_SETUP | |
| ERROR("EVENT LOOP FAIL!"); | |
| #endif | |
| return; | |
| } | |
| } | |
| else{ | |
| if((system_mode < 2 || spark_ok_to_connect) && allow_connect){ | |
| #ifdef DEBUG_SETUP | |
| ERROR("NO CONNECT"); | |
| #endif | |
| spark_connect(); | |
| } | |
| else | |
| return; | |
| } | |
| } | |
| #define MAX_SERIAL_BUFF 255 | |
| char* spark_receive_buffer = NULL; | |
| char* spark_transmit_buffer = NULL; | |
| volatile uint8_t spark_receive_buffer_count = 0; | |
| volatile uint8_t spark_receive_buffer_head = 0; | |
| volatile uint8_t spark_transmit_buffer_count = 0; | |
| volatile uint8_t spark_transmit_buffer_head = 0; | |
| volatile uint8_t spark_listening; | |
| volatile uint8_t spark_buffer_overflow; | |
| volatile uint8_t spark_serial_state = 0; | |
| void spark_serial_begin(){ | |
| //don't allocate buffers until this is called | |
| spark_receive_buffer = new char[MAX_SERIAL_BUFF]; | |
| spark_transmit_buffer = new char[MAX_SERIAL_BUFF]; | |
| if(spark_serial_state == 0){ | |
| spark_subscribe("oak/device/stdin", spark_get_rx, NULL, MY_DEVICES, NULL, NULL); | |
| } | |
| spark_serial_state = 2; | |
| } | |
| void spark_serial_end() | |
| { | |
| //de-allocate buffers here | |
| delete[] spark_receive_buffer; | |
| spark_receive_buffer = NULL; | |
| spark_receive_buffer_count = 0; | |
| delete[] spark_transmit_buffer; | |
| spark_transmit_buffer = NULL; | |
| spark_transmit_buffer_count = 0; | |
| spark_serial_state = 1; | |
| } | |
| // Read data from buffer | |
| int spark_serial_read() | |
| { | |
| // Return if cloud serial not initialized | |
| if (spark_serial_state < 2) | |
| return -1; | |
| // Empty buffer? | |
| if (spark_receive_buffer_count == 0) | |
| return -1; | |
| // Read from "head" | |
| uint8_t d = spark_receive_buffer[spark_receive_buffer_head]; // grab next byte | |
| spark_receive_buffer_head = (spark_receive_buffer_head + 1) % MAX_SERIAL_BUFF; | |
| spark_receive_buffer_count--; | |
| return d; | |
| } | |
| int spark_serial_available() | |
| { | |
| return spark_receive_buffer_count; | |
| } | |
| size_t spark_serial_write(uint8_t b) | |
| { | |
| // Return if cloud serial not initialized | |
| if (spark_serial_state < 2) | |
| return -1; | |
| // if buffer full, set the overflow flag and return | |
| if (spark_transmit_buffer_count < MAX_SERIAL_BUFF) | |
| { | |
| // save new data in buffer: tail points to where byte goes | |
| uint8_t tail=(spark_transmit_buffer_head + spark_transmit_buffer_count) % MAX_SERIAL_BUFF; | |
| spark_transmit_buffer[tail] = b; // save new byte | |
| spark_transmit_buffer_count++; | |
| return 1; | |
| } | |
| else | |
| { | |
| spark_buffer_overflow = true; | |
| return 0; | |
| } | |
| } | |
| void spark_serial_flush() | |
| { | |
| spark_transmit_buffer_count = 0; | |
| } | |
| int spark_serial_peek() | |
| { | |
| // Return if cloud serial not initialized | |
| if (spark_serial_state < 2) | |
| return -1; | |
| // Empty buffer? | |
| if (spark_receive_buffer_count == 0) | |
| return -1; | |
| // Read from "head" | |
| return spark_receive_buffer[spark_receive_buffer_head]; | |
| } | |
| void spark_get_rx(const char* name, const char* data){ //this is automatically called when new data comes from the cloud | |
| if(spark_serial_state < 2){ | |
| return; | |
| } | |
| if ((name[OAK_RX_EVENT_LEN] != 0 && | |
| (name[OAK_RX_EVENT_LEN] != '/' || | |
| strcmp((name+OAK_RX_EVENT_LEN+1),deviceConfig->device_id)))) { | |
| return; | |
| } | |
| if (data && *data) { | |
| while(*data != '\0'){ | |
| // if buffer full, set the overflow flag and return | |
| uint8_t tail = (spark_receive_buffer_head + | |
| spark_receive_buffer_count) % MAX_SERIAL_BUFF; | |
| if (spark_receive_buffer_count < MAX_SERIAL_BUFF) | |
| { | |
| // save new data in buffer: tail points to where byte goes | |
| spark_receive_buffer[tail] = *data; // save new byte | |
| data++; | |
| spark_receive_buffer_count++; | |
| } | |
| else | |
| { | |
| spark_buffer_overflow = true; | |
| return; | |
| } | |
| } | |
| } | |
| } | |
| int spark_send_tx(){ | |
| if(spark_transmit_buffer_count == 0)//nothing buffer | |
| return 0; | |
| char buff[spark_transmit_buffer_count+1]; | |
| uint8_t b; | |
| for(b=0;b<spark_transmit_buffer_count;b++){ | |
| // Read from "head" | |
| buff[b] = spark_transmit_buffer[spark_transmit_buffer_head]; // grab next byte | |
| spark_transmit_buffer_head = (spark_transmit_buffer_head + 1) % MAX_SERIAL_BUFF; | |
| } | |
| // Need to null terminate the buffer so that spark_send_event knows where | |
| // the end is | |
| buff[spark_transmit_buffer_count]=0; | |
| // Zero the buffer count since we've taken its contents | |
| spark_transmit_buffer_count=0; | |
| spark_send_event("oak/device/stdout", buff, 60, PRIVATE, NULL); | |
| return b; | |
| } | |
| #define NOINLINE __attribute__ ((noinline)) | |
| #define ROM_MAGIC 0xe9 | |
| #define ROM_MAGIC_NEW1 0xea | |
| #define ROM_MAGIC_NEW2 0x04 | |
| // buffer size, must be at least 0x10 (size of rom_header_new structure) | |
| #define BUFFER_SIZE 0x100 | |
| // functions we'll call by address | |
| typedef void stage2a(uint32); | |
| typedef void usercode(void); | |
| // standard rom header | |
| typedef struct { | |
| // general rom header | |
| uint8 magic; | |
| uint8 count; | |
| uint8 flags1; | |
| uint8 flags2; | |
| usercode* entry; | |
| } rom_header; | |
| typedef struct { | |
| uint8* address; | |
| uint32 length; | |
| } section_header; | |
| // new rom header (irom section first) there is | |
| // another 8 byte header straight afterward the | |
| // standard header | |
| typedef struct { | |
| // general rom header | |
| uint8 magic; | |
| uint8 count; // second magic for new header | |
| uint8 flags1; | |
| uint8 flags2; | |
| uint32 entry; | |
| // new type rom, lib header | |
| uint32 add; // zero | |
| uint32 len; // length of irom section | |
| } rom_header_new; | |
| bool check_image(uint8_t rom_number) { | |
| uint32 readpos = bootConfig->roms[rom_number]; | |
| uint8 buffer[BUFFER_SIZE]; | |
| uint8 sectcount; | |
| uint8 sectcurrent; | |
| uint8 *writepos; | |
| uint8 chksum = CHKSUM_INIT; | |
| uint32 loop; | |
| uint32 remaining; | |
| uint32 romaddr; | |
| rom_header_new *header = (rom_header_new*)buffer; | |
| section_header *section = (section_header*)buffer; | |
| if (readpos == 0 || readpos == 0xffffffff) { | |
| //ets_printf("EMPTY"); | |
| return 0; | |
| } | |
| // read rom header | |
| //if (SPIRead(readpos, header, sizeof(rom_header_new)) != 0) { | |
| if (spi_flash_read(readpos, reinterpret_cast<uint32_t*>(header), sizeof(rom_header_new)) != SPI_FLASH_RESULT_OK) { | |
| //ets_printf("NO_HEADER"); | |
| return 0; | |
| } | |
| // check header type | |
| if (header->magic == ROM_MAGIC) { | |
| // old type, no extra header or irom section to skip over | |
| romaddr = readpos; | |
| readpos += sizeof(rom_header); | |
| sectcount = header->count; | |
| } else if (header->magic == ROM_MAGIC_NEW1 && header->count == ROM_MAGIC_NEW2) { | |
| // new type, has extra header and irom section first | |
| romaddr = readpos + header->len + sizeof(rom_header_new); | |
| // we will set the real section count later, when we read the header | |
| sectcount = 0xff; | |
| // just skip the first part of the header | |
| // rest is processed for the chksum | |
| readpos += sizeof(rom_header); | |
| /* | |
| // skip the extra header and irom section | |
| readpos = romaddr; | |
| // read the normal header that follows | |
| if (SPIRead(readpos, header, sizeof(rom_header)) != 0) { | |
| //ets_printf("NNH"); | |
| return 0; | |
| } | |
| sectcount = header->count; | |
| readpos += sizeof(rom_header); | |
| */ | |
| } else { | |
| //ets_printf("BH"); | |
| return 0; | |
| } | |
| // test each section | |
| for (sectcurrent = 0; sectcurrent < sectcount; sectcurrent++) { | |
| //ets_printf("ST"); | |
| // read section header | |
| if (spi_flash_read(readpos, reinterpret_cast<uint32_t*>(section), sizeof(section_header)) != SPI_FLASH_RESULT_OK) { | |
| return 0; | |
| } | |
| readpos += sizeof(section_header); | |
| // get section address and length | |
| writepos = section->address; | |
| remaining = section->length; | |
| while (remaining > 0) { | |
| // work out how much to read, up to BUFFER_SIZE | |
| uint32 readlen = (remaining < BUFFER_SIZE) ? remaining : BUFFER_SIZE; | |
| // read the block | |
| if (spi_flash_read(readpos, reinterpret_cast<uint32_t*>(buffer), readlen) != SPI_FLASH_RESULT_OK) { | |
| return 0; | |
| } | |
| // increment next read and write positions | |
| readpos += readlen; | |
| writepos += readlen; | |
| // decrement remaining count | |
| remaining -= readlen; | |
| // add to chksum | |
| for (loop = 0; loop < readlen; loop++) { | |
| chksum ^= buffer[loop]; | |
| } | |
| } | |
| //#ifdef BOOT_IROM_CHKSUM | |
| if (sectcount == 0xff) { | |
| // just processed the irom section, now | |
| // read the normal header that follows | |
| if (spi_flash_read(readpos, reinterpret_cast<uint32_t*>(header), sizeof(rom_header)) != SPI_FLASH_RESULT_OK) { | |
| //ets_printf("SPI"); | |
| return 0; | |
| } | |
| sectcount = header->count + 1; | |
| readpos += sizeof(rom_header); | |
| } | |
| //#endif | |
| } | |
| // round up to next 16 and get checksum | |
| readpos = readpos | 0x0f; | |
| if (spi_flash_read(readpos, reinterpret_cast<uint32_t*>(buffer), 1) != SPI_FLASH_RESULT_OK) { | |
| //ets_printf("CK"); | |
| return 0; | |
| } | |
| // compare calculated and stored checksums | |
| if (buffer[0] != chksum) { | |
| //ets_printf("CKF"); | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| int decrypt_rsa(const uint8_t* ciphertext, const uint8_t* private_key, uint8_t* plaintext, int plaintext_len) | |
| { | |
| rsa_context rsa; | |
| init_rsa_context_with_private_key(&rsa, private_key); | |
| int err = rsa_pkcs1_decrypt(&rsa, RSA_PRIVATE, &plaintext_len, ciphertext, plaintext, plaintext_len); | |
| rsa_free(&rsa); | |
| return err ? -abs(err) : plaintext_len; | |
| } | |
| int decrypt(char* plaintext, int max_plaintext_len, char* hex_encoded_ciphertext) { | |
| const size_t len = 256; | |
| uint8_t buf[len]; | |
| hex_decode(buf, len, hex_encoded_ciphertext); | |
| // reuse the hex encoded buffer | |
| int plaintext_len = decrypt_rsa(buf, deviceConfig->device_private_key, (uint8_t*)plaintext, max_plaintext_len); | |
| return plaintext_len; | |
| } | |
| /** | |
| * Reads and generates the device's private key. | |
| * @param keyBuffer | |
| * @return | |
| */ | |
| bool generatePrivateKey(uint8_t *keyBuffer)//, bool force) | |
| { | |
| if(*keyBuffer!=0xFF && *keyBuffer!=0x00){// && !force){ | |
| return false; | |
| } | |
| else{ | |
| ESP.wdtDisable(); | |
| if (!gen_rsa_key(keyBuffer, PRIVATE_KEY_LENGTH, rsa_random, NULL)) { | |
| //keyBuffer + PRIVATE_KEY_LENGTH = '\0'; | |
| ESP.wdtEnable(WDTO_8S); | |
| return true; | |
| } | |
| ESP.wdtEnable(WDTO_8S); | |
| } | |
| return false; | |
| } | |
| static char ascii_nibble(uint8_t nibble) { | |
| char hex_digit = nibble + 48; | |
| if (57 < hex_digit) | |
| hex_digit += 7; | |
| return hex_digit; | |
| } | |
| int rsa_random(void* p) | |
| { | |
| byte randBytes[4]; | |
| os_get_random(randBytes, 4); | |
| return *((long *)randBytes); | |
| } | |
| bool IPAddressFromString(IPAddress &ipaddress, const char *address) | |
| { | |
| uint16_t acc = 0; // Accumulator | |
| uint8_t dots = 0; | |
| while (*address) | |
| { | |
| char c = *address++; | |
| if (c >= '0' && c <= '9') | |
| { | |
| acc = acc * 10 + (c - '0'); | |
| if (acc > 255) { | |
| // Value out of [0..255] range | |
| return false; | |
| } | |
| } | |
| else if (c == '.') | |
| { | |
| if (dots == 3) { | |
| // Too much dots (there must be 3 dots) | |
| return false; | |
| } | |
| ipaddress[dots++] = acc; | |
| acc = 0; | |
| } | |
| else | |
| { | |
| // Invalid char | |
| return false; | |
| } | |
| } | |
| if (dots != 3) { | |
| // Too few dots (there must be 3 dots) | |
| return false; | |
| } | |
| ipaddress[3] = acc; | |
| return true; | |
| } | |
| String info_response(void){ | |
| String response = "{\"id\":\""; | |
| response += deviceConfig->device_id; | |
| response += "\",\"claimed\":"; | |
| if(deviceConfig->claimed != 1) | |
| response += "0,"; | |
| else | |
| response += "1,"; | |
| response += "\"claim_code\":\""; | |
| response += deviceConfig->claim_code; | |
| response += "\",\"server_address_type\":"; | |
| if(deviceConfig->server_address_type != 1){ | |
| response += "0,"; | |
| response += "\"server_address_ip\":\""; | |
| IPAddress server_ip = IPAddress(deviceConfig->server_address_ip); | |
| response += String(server_ip[0]); | |
| response += "."; | |
| response += String(server_ip[1]); | |
| response += "."; | |
| response += String(server_ip[2]); | |
| response += "."; | |
| response += String(server_ip[3]); | |
| response += "\""; | |
| } | |
| else if(deviceConfig->server_address_type == 1){ | |
| response += "1,"; | |
| response += "\"server_address_domain\":\""; | |
| response += deviceConfig->server_address_domain; | |
| response += "\""; | |
| } | |
| else | |
| response += "-1,"; | |
| response += ",\"system_version\":"; | |
| response += deviceConfig->system_version; | |
| response += ",\"version_string\":\""; | |
| response += deviceConfig->version_string; | |
| response += "\",\"meta_id\":"; | |
| response += deviceConfig->third_party_id; | |
| response += ",\"meta_data\":\""; | |
| response += deviceConfig->third_party_data; | |
| response += "\",\"first_update_domain\":\""; | |
| response += deviceConfig->first_update_domain; | |
| response += "\",\"first_update_url\":\""; | |
| response += deviceConfig->first_update_url; | |
| response += "\",\"first_update_fingerprint\":\""; | |
| response += deviceConfig->first_update_fingerprint; | |
| response += "\"}"; | |
| return response; | |
| } | |
| String set_config_from_JSON(String json){ | |
| String valueString; | |
| uint8_t gotSomething = false; | |
| int16_t valueStart = json.indexOf("\"cc\":\""); | |
| int16_t valueEnd; | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+6); | |
| valueString = json.substring(valueStart+6,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()<63){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->claim_code,65); | |
| deviceConfig->claim_code[64] = '\0'; | |
| } | |
| valueStart = json.indexOf("\"device-id\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+13); | |
| valueString = json.substring(valueStart+13,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()!=24){ | |
| return String("{\"r\":-1}"); | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.print("="); | |
| Serial.print(valueString); | |
| Serial.println("="); | |
| #endif | |
| //deviceIdSet = true; | |
| //bootConfig->first_boot = 1; | |
| //writeBootConfig(); | |
| //LEDFlip.attach(0.5, FlipLED); | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->device_id,25); | |
| #ifdef DEBUG_SETUP | |
| Serial.print("="); | |
| Serial.print(deviceConfig->device_id); | |
| Serial.println("="); | |
| #endif | |
| deviceConfig->device_id[24] = '\0'; | |
| #ifdef DEBUG_SETUP | |
| Serial.print("="); | |
| Serial.print(deviceConfig->device_id); | |
| Serial.println("="); | |
| #endif | |
| } | |
| valueStart = json.indexOf("\"meta-id\":"); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf(',',valueStart+10); | |
| valueString = json.substring(valueStart+10,valueEnd); | |
| gotSomething = true; | |
| deviceConfig->third_party_id = valueString.toInt(); | |
| } | |
| valueStart = json.indexOf("\"first-update-domain\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+23); | |
| valueString = json.substring(valueStart+23,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()>64){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->first_update_domain,valueString.length()+1); | |
| deviceConfig->first_update_domain[valueString.length()] = '\0'; | |
| } | |
| valueStart = json.indexOf("\"first-update-url\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+20); | |
| valueString = json.substring(valueStart+20,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()>64){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->first_update_url,valueString.length()+1); | |
| deviceConfig->first_update_url[valueString.length()] = '\0'; | |
| } | |
| valueStart = json.indexOf("\"first-update-fingerprint\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+28); | |
| valueString = json.substring(valueStart+28,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()>59){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->first_update_fingerprint,valueString.length()+1); | |
| deviceConfig->first_update_fingerprint[valueString.length()] = '\0'; | |
| } | |
| valueStart = json.indexOf("\"meta-data\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+13); | |
| valueString = json.substring(valueStart+13,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()>255){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->third_party_data,valueString.length()+1); | |
| deviceConfig->third_party_data[valueString.length()] = '\0'; | |
| } | |
| valueStart = json.indexOf("\"server-address\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+18); | |
| valueString = json.substring(valueStart+18,valueEnd); | |
| valueString.trim(); | |
| if(valueString.length()>253){ | |
| return String("{\"r\":-1}"); | |
| } | |
| //get server address type | |
| valueStart = json.indexOf("\"server-address-type\":"); | |
| if(valueStart<0){ | |
| //server-address-type not set | |
| return String("{\"r\":-1}"); | |
| } | |
| char server_address_type = json.charAt(valueStart+22); | |
| if(server_address_type == '0'){ | |
| gotSomething = true; | |
| IPAddress bufIP; | |
| IPAddressFromString(bufIP,valueString.c_str()); | |
| deviceConfig->server_address_ip = bufIP; | |
| deviceConfig->server_address_length = '\0'; | |
| } | |
| else if(server_address_type == '1'){ | |
| gotSomething = true; | |
| valueString.toCharArray(deviceConfig->server_address_domain,valueString.length()+1); | |
| deviceConfig->server_address_domain[valueString.length()] = '\0'; | |
| deviceConfig->server_address_domain[253] = '\0'; | |
| } | |
| else | |
| return String("{\"r\":-1}"); | |
| } | |
| valueStart = json.indexOf("\"server-public-key\":\""); | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf('"',valueStart+21); | |
| valueString = json.substring(valueStart+21,valueEnd); | |
| if(valueString.length()>2046){ | |
| return String("{\"r\":-1}"); | |
| } | |
| gotSomething = true; | |
| const size_t len = (valueString.length()+1)/2; | |
| uint8_t buf[len]; | |
| hex_decode(buf, len, valueString.c_str()); | |
| memcpy(deviceConfig->server_public_key,buf,SERVER_PUBLIC_KEY_LENGTH); | |
| if(len>386){ | |
| uint8_t domainLength; | |
| domainLength = buf[385]; | |
| deviceConfig->server_address_type = buf[384]; | |
| if(deviceConfig->server_address_type == IP_ADDRESS){ | |
| uint8_t ipBuf[4]; | |
| memcpy(ipBuf,buf+386,4); | |
| deviceConfig->server_address_ip = (ipBuf[3] << 24) | (ipBuf[2] << 16) | (ipBuf[1] << 8) | ipBuf[0]; | |
| deviceConfig->server_address_length = '\0'; | |
| } | |
| else if(deviceConfig->server_address_type == DOMAIN_NAME && domainLength < 254){ | |
| memcpy(deviceConfig->server_address_domain,buf+386,domainLength); | |
| deviceConfig->server_address_domain[domainLength] = '\0'; | |
| deviceConfig->server_address_domain[253] = '\0'; | |
| deviceConfig->server_address_length = domainLength; | |
| } | |
| else{ | |
| return String("{\"r\":-1}"); | |
| } | |
| } | |
| } | |
| if(!gotSomething) | |
| return String("{\"r\":-1}"); | |
| else{ | |
| //write config | |
| writeDeviceConfig(); | |
| return String("{\"r\":0}"); | |
| } | |
| } | |
| String configure_ap_from_JSON(String json){ | |
| int16_t valueStart = json.indexOf("\"ssid\":\""); | |
| if(valueStart<0) | |
| return String("{\"r\":-1}"); | |
| int16_t valueEnd = json.indexOf('"',valueStart+8); | |
| if(valueEnd-valueStart < 1) | |
| return String("{\"r\":-1}"); | |
| String ssid = json.substring(valueStart+8,valueEnd); | |
| ssid.trim(); | |
| if(ssid.length()>64) | |
| return String("{\"r\":-1}"); | |
| ssid.toCharArray(deviceConfig->ssid,ssid.length()+1); | |
| deviceConfig->ssid[ssid.length()] = '\0'; | |
| valueStart = json.indexOf("\"ch\":"); | |
| uint8_t ch = 0; | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf(',',valueStart+5); | |
| ch = json.substring(valueStart+5,valueEnd).toInt(); | |
| } | |
| deviceConfig->channel = ch; | |
| valueStart = json.indexOf("\"sec\":"); | |
| uint8_t sec = false; | |
| if(valueStart>=0){ | |
| valueEnd = json.indexOf(',',valueStart+6); | |
| if(json.substring(valueStart+6,valueEnd).equals(String("0"))) | |
| sec = false; | |
| else | |
| sec = true; | |
| } | |
| char passcode[65]; | |
| if(sec == true){ | |
| valueStart = json.indexOf("\"pwd\":\""); | |
| if(valueStart<0) | |
| return String("{\"r\":-1}"); | |
| valueEnd = json.indexOf('"',valueStart+7); | |
| if(valueEnd-(valueStart+7) != 256) | |
| return String("{\"r\":-1}"); | |
| char encodedPasscode[257]; | |
| String passcodeString = json.substring(valueStart+7,valueEnd); | |
| passcodeString.trim(); | |
| passcodeString.toCharArray(encodedPasscode,257); | |
| int decodeLength = decrypt((char*)deviceConfig->passcode, 65, encodedPasscode); | |
| deviceConfig->passcode[decodeLength] = '\0'; | |
| } | |
| else{ | |
| deviceConfig->passcode[0] = '\0'; | |
| } | |
| writeDeviceConfig(); | |
| return String("{\"r\":0}"); | |
| } | |
| String pub_key(){ | |
| String response; | |
| const int length = 162; | |
| const uint8_t* data = deviceConfig->device_public_key; | |
| for (unsigned i=length; i-->0; ) { | |
| uint8_t v = *data++; | |
| response += ascii_nibble(v>>4); | |
| response += ascii_nibble(v&0xF); | |
| } | |
| return response; | |
| } | |
| bool provision_keys(bool force){//(bool force){ | |
| if(deviceConfig->device_private_key[0] != 0x00 && deviceConfig->device_private_key[0] != 0xFF && !force){ | |
| return true; | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println("Provision Keys"); | |
| #endif | |
| if(generatePrivateKey(deviceConfig->device_private_key)){ | |
| parse_device_pubkey_from_privkey(deviceConfig->device_public_key,deviceConfig->device_private_key); | |
| #ifdef DEBUG_SETUP | |
| const int length = 612; | |
| const uint8_t* data = deviceConfig->device_private_key; | |
| for (unsigned i=length; i-->0; ) { | |
| uint8_t v = *data++; | |
| Serial.write(ascii_nibble(v>>4)); | |
| Serial.write(ascii_nibble(v&0xF)); | |
| } | |
| Serial.write('\n'); | |
| #endif | |
| writeDeviceConfig(); | |
| return true;//generated | |
| } | |
| #ifdef DEBUG_SETUP | |
| Serial.println("Provision Keys FAILED"); | |
| #endif | |
| return false; //not generate | |
| } | |
| void set_system_mode(System_Mode_TypeDef mode){ | |
| if(system_mode == DEFAULT_MODE && mode == DEFAULT_MODE) | |
| system_mode = AUTOMATIC; | |
| else if(mode == DEFAULT_MODE) | |
| return; | |
| else | |
| system_mode = mode; | |
| } | |
| System_Mode_TypeDef get_system_mode(void){ | |
| return system_mode; | |
| } | |
| void set_oakboot_defaults(uint8_t failure_rom){ //0 = update rom, 1 = config rom, 2 = user rom | |
| bool changed = false; | |
| if(failure_rom != bootConfig->rom_on_swdt){ | |
| bootConfig->rom_on_swdt = failure_rom; | |
| changed = true; | |
| } | |
| if(failure_rom != bootConfig->rom_on_hwdt){ | |
| bootConfig->rom_on_hwdt = failure_rom; | |
| changed = true; | |
| } | |
| if(failure_rom != bootConfig->rom_on_exception){ | |
| bootConfig->rom_on_exception = failure_rom; | |
| changed = true; | |
| } | |
| if(failure_rom != bootConfig->rom_on_gpio){ | |
| bootConfig->rom_on_gpio = failure_rom; | |
| changed = true; | |
| } | |
| if(failure_rom != bootConfig->rom_on_invalid){ | |
| bootConfig->rom_on_invalid = failure_rom; | |
| changed = true; | |
| } | |
| if(failure_rom != bootConfig->rom_on_reinit){ | |
| bootConfig->rom_on_reinit = failure_rom; | |
| changed = true; | |
| } | |
| if(0 != bootConfig->mode){ //don't allow gpio16 boot | |
| bootConfig->mode = 0; | |
| changed = true; | |
| } | |
| if(changed){ | |
| writeBootConfig(); | |
| } | |
| } | |
| void set_bootloader_reason_write_skip(void){ | |
| if(bootConfig->reset_write_skip != 1){ | |
| bootConfig->reset_write_skip = 1; | |
| writeBootConfig(); | |
| } | |
| } | |
| void init_bootloader_flags(void){ | |
| set_bootloader_reason_write_skip(); | |
| set_oakboot_defaults(1); | |
| } | |
| void check_safe_mode(void){ | |
| #ifdef SAFE_MODE_PIN | |
| if(OAK_SYSTEM_ROM_4F616B == 82) | |
| return; | |
| pinMode(SAFE_MODE_PIN,INPUT_PULLUP); | |
| if(digitalRead(SAFE_MODE_PIN) == LOW){ | |
| uint32_t startHold = millis(); | |
| while(millis() - startHold < 50){ | |
| if(digitalRead(SAFE_MODE_PIN) == HIGH) | |
| return; | |
| } | |
| reboot_to_config(); | |
| } | |
| pinMode(SAFE_MODE_PIN,INPUT); | |
| #endif | |
| } | |
| uint8_t read_factory_reason(){ | |
| return bootConfig->factory_reason; | |
| } | |
| void clear_factory_reason(){ | |
| if(bootConfig->factory_reason != 'N'){ | |
| bootConfig->factory_reason = 'N'; | |
| writeBootConfig(); | |
| } | |
| } | |
| }; // particle_core |