Commit e5cd9e56 authored by 尹佳钦's avatar 尹佳钦

2020 0930

parent e5950adc
...@@ -156,6 +156,9 @@ void emberAfPluginDeviceTableDeviceLeftCallback(EmberEUI64 nodeEui64) ...@@ -156,6 +156,9 @@ void emberAfPluginDeviceTableDeviceLeftCallback(EmberEUI64 nodeEui64)
rpc_printfJSON("left",nodeJson); rpc_printfJSON("left",nodeJson);
kk_msg_report_dev_leave(deviceTable[deviceTableIndex].eui64); kk_msg_report_dev_leave(deviceTable[deviceTableIndex].eui64);
kk_device_table_delete(deviceTable[deviceTableIndex].eui64);
kk_device_db_delete(deviceTable[deviceTableIndex].eui64);
} }
......
...@@ -266,7 +266,7 @@ int _init_param(struct jrpc_server *server) { ...@@ -266,7 +266,7 @@ int _init_param(struct jrpc_server *server) {
//kk_zlog_init("paltform"); //kk_zlog_init("paltform");
printf("getenv\r\n"); printf("getenv\r\n");
char * debug_level_env = getenv("HOME"); char * debug_level_env = getenv("JRPC_DEBUG");
printf("getenv(JRPC_DEBUG):%s\n", server->debug_level); printf("getenv(JRPC_DEBUG):%s\n", server->debug_level);
if (debug_level_env == NULL) if (debug_level_env == NULL)
server->debug_level = 0; server->debug_level = 0;
......
...@@ -47,13 +47,13 @@ void halReboot(void) ...@@ -47,13 +47,13 @@ void halReboot(void)
uint8_t halGetResetInfo(void) uint8_t halGetResetInfo(void)
{ {
return RESET_SOFTWARE; return RESET_SOFTWARE;
} }
PGM_P halGetResetString(void) PGM_P halGetResetString(void)
{ {
static PGM_P resetString = "SOFTWARE"; static PGM_P resetString = "SOFTWARE";
return (resetString); return (resetString);
} }
// Ideally this should not be necessary, but the serial code references // Ideally this should not be necessary, but the serial code references
......
...@@ -36,9 +36,10 @@ ...@@ -36,9 +36,10 @@
static const char options[] = "b:f:hv::i:n:o:p:r:s:t:x:"; static const char options[] = "b:f:hv::i:n:o:p:r:s:t:x:";
bool checkSerialPort(const char* portString); bool checkSerialPort(const char* portString);
bool ezspInternalProcessCommandOptions(int argc, char *argv[], char *errStr) bool ezspInternalProcessCommandOptions(int argc, char *argv[], char *errStr)
{ {
int c; int c;
char port[ASH_PORT_LEN]; char port[ASH_PORT_LEN];
char devport[ASH_PORT_LEN]; char devport[ASH_PORT_LEN];
uint32_t baud; uint32_t baud;
...@@ -48,181 +49,181 @@ bool ezspInternalProcessCommandOptions(int argc, char *argv[], char *errStr) ...@@ -48,181 +49,181 @@ bool ezspInternalProcessCommandOptions(int argc, char *argv[], char *errStr)
uint8_t portnum; uint8_t portnum;
uint8_t cfg; uint8_t cfg;
int blksize; int blksize;
int optionCount = 0; int optionCount = 0;
if (!argv || !errStr) { if (!argv || !errStr) {
snprintf(errStr, ERR_LEN, "Error: argv[] is %s and errStr is %s.\n", snprintf(errStr, ERR_LEN, "Error: argv[] is %s and errStr is %s.\n",
argv ? "valid" : "NULL", argv ? "valid" : "NULL",
errStr ? "valid" : "NULL"); errStr ? "valid" : "NULL");
return false; return false;
} }
while (true) { while (true) {
c = getopt(argc, argv, options); c = getopt(argc, argv, options);
if (c == -1) { if (c == -1) {
if (optind != argc ) { if (optind != argc ) {
snprintf(errStr, ERR_LEN, "Invalid option %s.\n", argv[optind]); snprintf(errStr, ERR_LEN, "Invalid option %s.\n", argv[optind]);
} }
break; break;
} }
optionCount++; optionCount++;
switch (c) { switch (c) {
case 'b': case 'b':
if (!optarg || (sscanf(optarg, "%u", &baud) != 1)) { if (!optarg || (sscanf(optarg, "%u", &baud) != 1)) {
snprintf(errStr, ERR_LEN, "Invalid baud rate %s.\n", snprintf(errStr, ERR_LEN, "Invalid baud rate %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
ashWriteConfig(baudRate, baud); ashWriteConfig(baudRate, baud);
} }
break; break;
case 'f': case 'f':
if (!optarg) { if (!optarg) {
snprintf(errStr, ERR_LEN, "Invalid flow control choice NULL.\n"); snprintf(errStr, ERR_LEN, "Invalid flow control choice NULL.\n");
break; break;
} }
switch (*optarg) { switch (*optarg) {
case 'r': case 'r':
ashWriteConfig(rtsCts, true); ashWriteConfig(rtsCts, true);
break; break;
case 'x': case 'x':
ashWriteConfig(rtsCts, false); ashWriteConfig(rtsCts, false);
break; break;
default: default:
snprintf(errStr, ERR_LEN, "Invalid flow control choice %s.\n", optarg); snprintf(errStr, ERR_LEN, "Invalid flow control choice %s.\n", optarg);
} }
break; break;
case 'h': case 'h':
case '?': case '?':
snprintf(errStr, ERR_LEN, "\n"); snprintf(errStr, ERR_LEN, "\n");
break; break;
case 'i': case 'i':
if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) { if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
snprintf(errStr, ERR_LEN, "Invalid input buffer choice %s.\n", snprintf(errStr, ERR_LEN, "Invalid input buffer choice %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
blksize = enable ? 256 : 1; blksize = enable ? 256 : 1;
ashWriteConfig(inBlockLen, blksize); ashWriteConfig(inBlockLen, blksize);
} }
break; break;
case 'n': case 'n':
if (optionCount != 1) { if (optionCount != 1) {
snprintf(errStr, ERR_LEN, "NCP option, if present, must be first.\n"); snprintf(errStr, ERR_LEN, "NCP option, if present, must be first.\n");
} else if (!optarg || (sscanf(optarg, "%hhu", &cfg) != 1) || (cfg > 1)) { } else if (!optarg || (sscanf(optarg, "%hhu", &cfg) != 1) || (cfg > 1)) {
snprintf(errStr, ERR_LEN, "Invalid NCP config choice %s.\n", snprintf(errStr, ERR_LEN, "Invalid NCP config choice %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
ashSelectHostConfig(cfg); ashSelectHostConfig(cfg);
} }
break; break;
case 'o': case 'o':
if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) { if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
snprintf(errStr, ERR_LEN, "Invalid output buffer choice %s.\n", snprintf(errStr, ERR_LEN, "Invalid output buffer choice %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
blksize = enable ? 256 : 1; blksize = enable ? 256 : 1;
ashWriteConfig(outBlockLen, blksize); ashWriteConfig(outBlockLen, blksize);
} }
break; break;
case 'p': case 'p':
if (!optarg || (sscanf(optarg, "%39s", port) <= 0)) { if (!optarg || (sscanf(optarg, "%39s", port) <= 0)) {
snprintf(errStr, ERR_LEN, "Invalid serial port name %s.\n", snprintf(errStr, ERR_LEN, "Invalid serial port name %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else if (strlen(port) >= ASH_PORT_LEN - 1) { } else if (strlen(port) >= ASH_PORT_LEN - 1) {
snprintf(errStr, ERR_LEN, "Serial port name %s too long.\n", port); snprintf(errStr, ERR_LEN, "Serial port name %s too long.\n", port);
} else { } else {
// Handle some common variations specifying a serial port // Handle some common variations specifying a serial port
strncpy(devport, "/dev/", 5); strncpy(devport, "/dev/", 5);
devport[5] = '\0'; devport[5] = '\0';
if ( strncmp(devport, port, 5) == 0) { if ( strncmp(devport, port, 5) == 0) {
strncat(devport, port + 5, ASH_PORT_LEN - 5 - 1); strncat(devport, port + 5, ASH_PORT_LEN - 5 - 1);
#ifdef __CYGWIN__ #ifdef __CYGWIN__
} else if ( ((strncmp("COM", port, 3) == 0) } else if ( ((strncmp("COM", port, 3) == 0)
|| (strncmp("com", port, 3) == 0) ) || (strncmp("com", port, 3) == 0) )
&& (sscanf(port + 3, "%hhu", &portnum) == 1) && (sscanf(port + 3, "%hhu", &portnum) == 1)
&& (portnum > 0) ) { && (portnum > 0) ) {
snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1); snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1);
} else if ( (sscanf(port, "%hhu", &portnum) == 1) && portnum ) { } else if ( (sscanf(port, "%hhu", &portnum) == 1) && portnum ) {
snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1); snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum - 1);
#else #else
} else if (sscanf(port, "%hhu", &portnum) == 1) { } else if (sscanf(port, "%hhu", &portnum) == 1) {
snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum); snprintf(devport, ASH_PORT_LEN, "/dev/ttyS%hhu", portnum);
#endif #endif
} else { } else {
strncat(devport, port, ASH_PORT_LEN - 1); strncat(devport, port, ASH_PORT_LEN - 1);
} }
strncpy(ashHostConfig.serialPort, devport, ASH_PORT_LEN - 1); strncpy(ashHostConfig.serialPort, devport, ASH_PORT_LEN - 1);
ashHostConfig.serialPort[ASH_PORT_LEN - 1] = '\0'; ashHostConfig.serialPort[ASH_PORT_LEN - 1] = '\0';
if (!checkSerialPort(ashHostConfig.serialPort)) { if (!checkSerialPort(ashHostConfig.serialPort)) {
return false; return false;
} }
} }
break; break;
case 'r': case 'r':
if (!optarg) { if (!optarg) {
snprintf(errStr, ERR_LEN, "Invalid reset method NULL.\n"); snprintf(errStr, ERR_LEN, "Invalid reset method NULL.\n");
break; break;
} }
switch (*optarg) { switch (*optarg) {
case 'r': case 'r':
ashWriteConfig(resetMethod, ASH_RESET_METHOD_RST); ashWriteConfig(resetMethod, ASH_RESET_METHOD_RST);
break; break;
case 'd': case 'd':
ashWriteConfig(resetMethod, ASH_RESET_METHOD_DTR); ashWriteConfig(resetMethod, ASH_RESET_METHOD_DTR);
break; break;
case 'c': case 'c':
ashWriteConfig(resetMethod, ASH_RESET_METHOD_CUSTOM); ashWriteConfig(resetMethod, ASH_RESET_METHOD_CUSTOM);
break; break;
default: default:
snprintf(errStr, ERR_LEN, "Invalid reset method %s.\n", optarg); snprintf(errStr, ERR_LEN, "Invalid reset method %s.\n", optarg);
} }
break; break;
case 's': case 's':
if (!optarg || (sscanf(optarg, "%hhu", &stops) != 1) || (stops < 1) if (!optarg || (sscanf(optarg, "%hhu", &stops) != 1) || (stops < 1)
|| (stops > 2)) { || (stops > 2)) {
snprintf(errStr, ERR_LEN, "Invalid number of stop bits %s.\n", snprintf(errStr, ERR_LEN, "Invalid number of stop bits %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
ashWriteConfig(stopBits, stops); ashWriteConfig(stopBits, stops);
} }
break; break;
case 't': case 't':
if (!optarg || (sscanf(optarg, "%hhu", &trace) != 1)) { if (!optarg || (sscanf(optarg, "%hhu", &trace) != 1)) {
snprintf(errStr, ERR_LEN, "Invalid trace flag value %s.\n", snprintf(errStr, ERR_LEN, "Invalid trace flag value %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
ashWriteConfig(traceFlags, trace); ashWriteConfig(traceFlags, trace);
} }
break; break;
case 'v': case 'v':
if (!backchannelSupported) { if (!backchannelSupported) {
fprintf(stderr, "Error: Backchannel support not compiled into this application.\n"); fprintf(stderr, "Error: Backchannel support not compiled into this application.\n");
exit(1); exit(1);
} }
backchannelEnable = true; backchannelEnable = true;
if (optarg) { if (optarg) {
int port = atoi(optarg); int port = atoi(optarg);
if (port == 0 || port > 65535) { if (port == 0 || port > 65535) {
snprintf(errStr, ERR_LEN, "Invalid virtual ISA port number '%d'.\n", port); snprintf(errStr, ERR_LEN, "Invalid virtual ISA port number '%d'.\n", port);
} }
backchannelSerialPortOffset = port; backchannelSerialPortOffset = port;
} }
break; break;
case 'x': case 'x':
if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) { if (!optarg || (sscanf(optarg, "%hhu", &enable) != 1) || (enable > 1)) {
snprintf(errStr, ERR_LEN, "Invalid randomization choice %s.\n", snprintf(errStr, ERR_LEN, "Invalid randomization choice %s.\n",
optarg ? optarg : "NULL"); optarg ? optarg : "NULL");
} else { } else {
ashWriteConfig(randomize, enable); ashWriteConfig(randomize, enable);
} }
break; break;
default: default:
assert(1); assert(1);
break; break;
} // end of switch (c) } // end of switch (c)
} //end while } //end while
return true; return true;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
......
...@@ -31,31 +31,31 @@ void ashPrintCounters(AshCount *counters, bool clear); ...@@ -31,31 +31,31 @@ void ashPrintCounters(AshCount *counters, bool clear);
void ashClearCounters(AshCount *counters); void ashClearCounters(AshCount *counters);
static const char usage[] = static const char usage[] =
" {ncp type} {options}\n" " {ncp type} {options}\n"
" ncp type:\n" " ncp type:\n"
" -n 0,1 0=EM2xx/EM3xx @ 115200 bps, RTS/CTS\n" " -n 0,1 0=EM2xx/EM3xx @ 115200 bps, RTS/CTS\n"
" 1=EM2xx/EM3xx @ 57600 bps, XON/XOFF\n" " 1=EM2xx/EM3xx @ 57600 bps, XON/XOFF\n"
" (if present must be the first option)\n" " (if present must be the first option)\n"
" options:\n" " options:\n"
" -b <baud rate> 9600, 19200, 38400, 57600, 115200, etc.\n" " -b <baud rate> 9600, 19200, 38400, 57600, 115200, etc.\n"
" -f r,x flow control: r=RST/CTS, x=XON/XOFF\n" " -f r,x flow control: r=RST/CTS, x=XON/XOFF\n"
" -h display usage information\n" " -h display usage information\n"
" -i 0,1 enable/disable input buffering\n" " -i 0,1 enable/disable input buffering\n"
" -o 0,1 enable/disable output buffering\n" " -o 0,1 enable/disable output buffering\n"
" -p <port> serial port name or number (eg, COM1, ttyS0, or 1)\n" " -p <port> serial port name or number (eg, COM1, ttyS0, or 1)\n"
" -r d,r,c ncp reset method: d=DTR, r=RST frame, c=custom\n" " -r d,r,c ncp reset method: d=DTR, r=RST frame, c=custom\n"
" -s 1,2 stop bits\n" " -s 1,2 stop bits\n"
" -t <trace flags> trace B0=frames, B1=verbose frames, B2=events, B3=EZSP\n" " -t <trace flags> trace B0=frames, B1=verbose frames, B2=events, B3=EZSP\n"
" -v[base-port] enables virtual ISA support. The [base-port] argument\n" " -v[base-port] enables virtual ISA support. The [base-port] argument\n"
" is optional. Both serial ports are available via telnet\n" " is optional. Both serial ports are available via telnet\n"
" instead of local console. RAW serial port is available\n" " instead of local console. RAW serial port is available\n"
" on the first port (offset 0 from base port), and CLI is\n" " on the first port (offset 0 from base port), and CLI is\n"
" available on the second port (offset 1 from base port).\n" " available on the second port (offset 1 from base port).\n"
" By default, 4900 is the base-port, therefore RAW access\n" " By default, 4900 is the base-port, therefore RAW access\n"
" is available from port 4900, and CLI access is available\n" " is available from port 4900, and CLI access is available\n"
" on port 4901.\n" " on port 4901.\n"
" NOTE: No space is allowed between '-v' and [base-port].\n" " NOTE: No space is allowed between '-v' and [base-port].\n"
" -x 0,1 enable/disable data randomization\n"; " -x 0,1 enable/disable data randomization\n";
static const AshCount zeroAshCount = { 0 }; static const AshCount zeroAshCount = { 0 };
......
...@@ -559,6 +559,7 @@ bool ezspSerialPortRegisterCallback(EzspSerialPortCallbackFunction callback) ...@@ -559,6 +559,7 @@ bool ezspSerialPortRegisterCallback(EzspSerialPortCallbackFunction callback)
void ezspSerialWriteByte(uint8_t byte) void ezspSerialWriteByte(uint8_t byte)
{ {
BUMP_HOST_COUNTER(txBytes); BUMP_HOST_COUNTER(txBytes);
#ifdef IO_LOG #ifdef IO_LOG
{ {
......
...@@ -66,10 +66,10 @@ ...@@ -66,10 +66,10 @@
void ezspPrintUsage(char *name) void ezspPrintUsage(char *name)
{ {
char *shortName = strrchr(name, '/'); char *shortName = strrchr(name, '/');
shortName = shortName ? shortName + 1 : name; shortName = shortName ? shortName + 1 : name;
fprintf(stderr, "Usage: %s", shortName); fprintf(stderr, "Usage: %s", shortName);
fprintf(stderr, usage); fprintf(stderr, usage);
} }
#ifdef __CYGWIN__ #ifdef __CYGWIN__
...@@ -103,16 +103,16 @@ bool checkSerialPort(const char* portString, bool silent) ...@@ -103,16 +103,16 @@ bool checkSerialPort(const char* portString, bool silent)
bool ezspProcessCommandOptions(int argc, char *argv[]) bool ezspProcessCommandOptions(int argc, char *argv[])
{ {
char errStr[ERR_LEN] = ""; char errStr[ERR_LEN] = "";
if (!ezspInternalProcessCommandOptions(argc, argv, errStr)) { if (!ezspInternalProcessCommandOptions(argc, argv, errStr)) {
return false; return false;
} }
if (*errStr != '\0') { if (*errStr != '\0') {
fprintf(stderr, "%s", errStr); fprintf(stderr, "%s", errStr);
ezspPrintUsage(argv[0]); ezspPrintUsage(argv[0]);
} }
return (*errStr == '\0'); return (*errStr == '\0');
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
......
...@@ -1122,26 +1122,26 @@ typedef struct { ...@@ -1122,26 +1122,26 @@ typedef struct {
* an upgrade file. * an upgrade file.
*/ */
typedef struct { typedef struct {
uint16_t manufacturerId; uint16_t manufacturerId;
uint16_t imageTypeId; uint16_t imageTypeId;
uint32_t firmwareVersion; uint32_t firmwareVersion;
/** /**
* This is only used for device specific files. * This is only used for device specific files.
* It will be set to all 0's when the image does not * It will be set to all 0's when the image does not
* have an upgrade destination field in it. * have an upgrade destination field in it.
* Little endian format. * Little endian format.
*/ */
uint8_t deviceSpecificFileEui64[EUI64_SIZE]; uint8_t deviceSpecificFileEui64[EUI64_SIZE];
} EmberAfOtaImageId; } EmberAfOtaImageId;
/** /**
* @brief The list of options possible for the image block request/response. * @brief The list of options possible for the image block request/response.
*/ */
enum { enum {
EMBER_AF_IMAGE_BLOCK_REQUEST_OPTIONS_NONE = 0, EMBER_AF_IMAGE_BLOCK_REQUEST_OPTIONS_NONE = 0,
EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT = 1, EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT = 1,
EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER = 2, EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER = 2,
}; };
typedef uint8_t EmberAfImageBlockRequestOptions; typedef uint8_t EmberAfImageBlockRequestOptions;
...@@ -1150,18 +1150,18 @@ typedef uint8_t EmberAfImageBlockRequestOptions; ...@@ -1150,18 +1150,18 @@ typedef uint8_t EmberAfImageBlockRequestOptions;
* emberAfImageBlockRequestCallback() to let the application decide what to do. * emberAfImageBlockRequestCallback() to let the application decide what to do.
*/ */
typedef struct { typedef struct {
const EmberAfOtaImageId* id; const EmberAfOtaImageId* id;
uint32_t offset; uint32_t offset;
uint32_t waitTimeSecondsResponse; uint32_t waitTimeSecondsResponse;
EmberNodeId source; EmberNodeId source;
EmberEUI64 sourceEui; // optionally present in messages EmberEUI64 sourceEui; // optionally present in messages
// The minBlockRequestPeriod can be treated as milliseconds or seconds on the // The minBlockRequestPeriod can be treated as milliseconds or seconds on the
// client. The OTA server plugin has optional support to probe clients and // client. The OTA server plugin has optional support to probe clients and
// treat this field with appropriate units (ms or sec) // treat this field with appropriate units (ms or sec)
uint16_t minBlockRequestPeriod; // optionally present in messages uint16_t minBlockRequestPeriod; // optionally present in messages
uint8_t maxDataSize; uint8_t maxDataSize;
uint8_t clientEndpoint; uint8_t clientEndpoint;
EmberAfImageBlockRequestOptions bitmask; EmberAfImageBlockRequestOptions bitmask;
} EmberAfImageBlockRequestCallbackStruct; } EmberAfImageBlockRequestCallbackStruct;
/** /**
...@@ -1169,11 +1169,11 @@ typedef struct { ...@@ -1169,11 +1169,11 @@ typedef struct {
* device operation. * device operation.
*/ */
typedef enum { typedef enum {
EMBER_AF_OTA_STORAGE_SUCCESS = 0, EMBER_AF_OTA_STORAGE_SUCCESS = 0,
EMBER_AF_OTA_STORAGE_ERROR = 1, EMBER_AF_OTA_STORAGE_ERROR = 1,
EMBER_AF_OTA_STORAGE_RETURN_DATA_TOO_LONG = 2, EMBER_AF_OTA_STORAGE_RETURN_DATA_TOO_LONG = 2,
EMBER_AF_OTA_STORAGE_PARTIAL_FILE_FOUND = 3, EMBER_AF_OTA_STORAGE_PARTIAL_FILE_FOUND = 3,
EMBER_AF_OTA_STORAGE_OPERATION_IN_PROGRESS = 4, EMBER_AF_OTA_STORAGE_OPERATION_IN_PROGRESS = 4,
} EmberAfOtaStorageStatus; } EmberAfOtaStorageStatus;
/** /**
...@@ -1203,41 +1203,41 @@ typedef uint8_t EmberAfOtaDownloadResult; ...@@ -1203,41 +1203,41 @@ typedef uint8_t EmberAfOtaDownloadResult;
* It is not a byte-for-byte copy. * It is not a byte-for-byte copy.
*/ */
typedef struct { typedef struct {
// Magic Number omitted since it is always the same. // Magic Number omitted since it is always the same.
uint16_t headerVersion; uint16_t headerVersion;
uint16_t headerLength; uint16_t headerLength;
uint16_t fieldControl; uint16_t fieldControl;
uint16_t manufacturerId; uint16_t manufacturerId;
uint16_t imageTypeId; // a.k.a. Device ID uint16_t imageTypeId; // a.k.a. Device ID
uint32_t firmwareVersion; uint32_t firmwareVersion;
uint16_t zigbeeStackVersion; uint16_t zigbeeStackVersion;
/** /**
* @brief The spec. does NOT require that the string be NULL terminated in the * @brief The spec. does NOT require that the string be NULL terminated in the
* header stored on disk. Therefore we make sure we can support a * header stored on disk. Therefore we make sure we can support a
* 32-character string without a NULL terminator by adding +1 in the data * 32-character string without a NULL terminator by adding +1 in the data
* structure. * structure.
*/ */
uint8_t headerString[EMBER_AF_OTA_MAX_HEADER_STRING_LENGTH + 1]; uint8_t headerString[EMBER_AF_OTA_MAX_HEADER_STRING_LENGTH + 1];
/** /**
* @brief When reading the header this will be the complete length of * @brief When reading the header this will be the complete length of
* the file. When writing the header, this must be set to * the file. When writing the header, this must be set to
* the length of the MFG image data portion including all tags. * the length of the MFG image data portion including all tags.
*/ */
uint32_t imageSize; uint32_t imageSize;
/** /**
* @brief The remaining four fields are optional. The field control should be checked * @brief The remaining four fields are optional. The field control should be checked
* to determine if their values are valid. * to determine if their values are valid.
*/ */
uint8_t securityCredentials; uint8_t securityCredentials;
union { union {
uint8_t EUI64[EUI64_SIZE]; uint8_t EUI64[EUI64_SIZE];
uint8_t UID[UID_SIZE]; uint8_t UID[UID_SIZE];
} upgradeFileDestination; } upgradeFileDestination;
uint16_t minimumHardwareVersion; uint16_t minimumHardwareVersion;
uint16_t maximumHardwareVersion; uint16_t maximumHardwareVersion;
} EmberAfOtaHeader; } EmberAfOtaHeader;
/** /**
......
...@@ -89,94 +89,94 @@ static int myVprintf(int fd, const char* formatString, va_list ap); ...@@ -89,94 +89,94 @@ static int myVprintf(int fd, const char* formatString, va_list ap);
EmberStatus backchannelStartServer(uint8_t port) EmberStatus backchannelStartServer(uint8_t port)
{ {
struct sockaddr_in serverAddress; struct sockaddr_in serverAddress;
int flags; int flags;
if (!backchannelEnable) { if (!backchannelEnable) {
return EMBER_INVALID_CALL; return EMBER_INVALID_CALL;
} }
if (port > 1) { if (port > 1) {
return EMBER_SERIAL_INVALID_PORT; return EMBER_SERIAL_INVALID_PORT;
} }
if (socketFd[port] != -1) { if (socketFd[port] != -1) {
return EMBER_INVALID_CALL; return EMBER_INVALID_CALL;
} }
socketFd[port] = socket(AF_INET, SOCK_STREAM, 0); socketFd[port] = socket(AF_INET, SOCK_STREAM, 0);
if (socketFd[port] < 0) { if (socketFd[port] < 0) {
unixError("Error: Could not open socket"); unixError("Error: Could not open socket");
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} }
flags = 1; // Enable SO_REUSEADDR to reduce bind() complaints flags = 1; // Enable SO_REUSEADDR to reduce bind() complaints
(void) setsockopt(socketFd[port], SOL_SOCKET, SO_REUSEADDR, &flags, (void) setsockopt(socketFd[port], SOL_SOCKET, SO_REUSEADDR, &flags,
sizeof(flags)); sizeof(flags));
bzero(&(clientConnections[port]), sizeof(struct sockaddr_in)); bzero(&(clientConnections[port]), sizeof(struct sockaddr_in));
bzero((char *) &serverAddress, sizeof(serverAddress)); bzero((char *) &serverAddress, sizeof(serverAddress));
serverAddress.sin_family = AF_INET; serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY; serverAddress.sin_addr.s_addr = INADDR_ANY;
serverAddress.sin_port = htons(SERVER_PORT_OFFSET + port); serverAddress.sin_port = htons(SERVER_PORT_OFFSET + port);
if (bind(socketFd[port], if (bind(socketFd[port],
(struct sockaddr *) &serverAddress, (struct sockaddr *) &serverAddress,
sizeof(serverAddress)) < 0) { sizeof(serverAddress)) < 0) {
unixError("Error: Could not bind socket to %u", SERVER_PORT_OFFSET + port); unixError("Error: Could not bind socket to %u", SERVER_PORT_OFFSET + port);
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} }
if (0 > listen(socketFd[port], 0)) { if (0 > listen(socketFd[port], 0)) {
unixError("Error: Could not mark socket as listening"); unixError("Error: Could not mark socket as listening");
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} }
infoPrint("Listening for connections on port %u", SERVER_PORT_OFFSET + port); infoPrint("Listening for connections on port %u", SERVER_PORT_OFFSET + port);
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
EmberStatus backchannelClientConnectionCleanup(uint8_t port) EmberStatus backchannelClientConnectionCleanup(uint8_t port)
{ {
if (!backchannelEnable) { if (!backchannelEnable) {
return EMBER_INVALID_CALL; return EMBER_INVALID_CALL;
} }
if (port > 1) { if (port > 1) {
return EMBER_SERIAL_INVALID_PORT; return EMBER_SERIAL_INVALID_PORT;
} }
if (clientFd[port] != INVALID_FD) { if (clientFd[port] != INVALID_FD) {
myPrintf(REMOTE_STDOUT, myPrintf(REMOTE_STDOUT,
"Server closing client connection from %s:%u on port %u\n", "Server closing client connection from %s:%u on port %u\n",
inet_ntoa(clientConnections[port].sin_addr), inet_ntoa(clientConnections[port].sin_addr),
ntohs(clientConnections[port].sin_port), ntohs(clientConnections[port].sin_port),
SERVER_PORT_OFFSET + port); SERVER_PORT_OFFSET + port);
infoPrint("Server closing client connection from %s:%u on port %u\n", infoPrint("Server closing client connection from %s:%u on port %u\n",
inet_ntoa(clientConnections[port].sin_addr), inet_ntoa(clientConnections[port].sin_addr),
ntohs(clientConnections[port].sin_port), ntohs(clientConnections[port].sin_port),
SERVER_PORT_OFFSET + port); SERVER_PORT_OFFSET + port);
close(clientFd[port]); close(clientFd[port]);
clientFd[port] = INVALID_FD; clientFd[port] = INVALID_FD;
bzero(&(clientConnections[port]), sizeof(struct sockaddr_in)); bzero(&(clientConnections[port]), sizeof(struct sockaddr_in));
} }
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
EmberStatus backchannelStopServer(uint8_t port) EmberStatus backchannelStopServer(uint8_t port)
{ {
if (!backchannelEnable) { if (!backchannelEnable) {
return EMBER_INVALID_CALL; return EMBER_INVALID_CALL;
} }
if (port > 1) { if (port > 1) {
return EMBER_SERIAL_INVALID_PORT; return EMBER_SERIAL_INVALID_PORT;
} }
backchannelClientConnectionCleanup(port); backchannelClientConnectionCleanup(port);
if (socketFd[port] != INVALID_FD) { if (socketFd[port] != INVALID_FD) {
myPrintf(LOCAL_STDOUT, "Server closing socket connection %u\n", myPrintf(LOCAL_STDOUT, "Server closing socket connection %u\n",
SERVER_PORT_OFFSET + port); SERVER_PORT_OFFSET + port);
close(socketFd[port]); close(socketFd[port]);
socketFd[port] = INVALID_FD; socketFd[port] = INVALID_FD;
} }
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
// Retrieves a single byte from the client connection. Returns the number // Retrieves a single byte from the client connection. Returns the number
...@@ -226,23 +226,23 @@ EmberStatus backchannelSend(uint8_t port, uint8_t * data, uint8_t length) ...@@ -226,23 +226,23 @@ EmberStatus backchannelSend(uint8_t port, uint8_t * data, uint8_t length)
// If one doesn't exist, it can wait for a new connection and return // If one doesn't exist, it can wait for a new connection and return
// the result. // the result.
BackchannelState backchannelCheckConnection(uint8_t port, BackchannelState backchannelCheckConnection(uint8_t port,
bool waitForConnection) bool waitForConnection)
{ {
if (!backchannelEnable) { if (!backchannelEnable) {
return CONNECTION_ERROR; return CONNECTION_ERROR;
} else if (clientFd[port] >= 0) { } else if (clientFd[port] >= 0) {
return CONNECTION_EXISTS; return CONNECTION_EXISTS;
} }
bool getConnection = false; bool getConnection = false;
if (waitForConnection) { if (waitForConnection) {
infoPrint("Waiting for client connection on port %u", infoPrint("Waiting for client connection on port %u",
port + SERVER_PORT_OFFSET); port + SERVER_PORT_OFFSET);
getConnection = true; getConnection = true;
} else if (!willServerConnectionBlock(port)) { } else if (!willServerConnectionBlock(port)) {
getConnection = true; getConnection = true;
} }
if (getConnection) { if (getConnection) {
return (getNewConnection(port) return (getNewConnection(port)
...@@ -450,32 +450,32 @@ static bool getNewConnection(uint8_t port) ...@@ -450,32 +450,32 @@ static bool getNewConnection(uint8_t port)
// Print an error message plus the associated 'errno' string // Print an error message plus the associated 'errno' string
static void unixError(const char* format, ...) static void unixError(const char* format, ...)
{ {
va_list ap = { 0 }; va_list ap = { 0 };
va_start(ap, format); va_start(ap, format);
myVprintf(LOCAL_STDERR, format, ap); myVprintf(LOCAL_STDERR, format, ap);
va_end(ap); va_end(ap);
myPrintf(LOCAL_STDERR, ": %s\n", strerror(errno)); myPrintf(LOCAL_STDERR, ": %s\n", strerror(errno));
} }
static void debugPrint(const char* formatString, ...) static void debugPrint(const char* formatString, ...)
{ {
if (debugOn) { if (debugOn) {
va_list ap = { 0 }; va_list ap = { 0 };
myPrintf(LOCAL_STDERR, "[%s] ", debugString); myPrintf(LOCAL_STDERR, "[%s] ", debugString);
va_start(ap, formatString); va_start(ap, formatString);
myVprintf(LOCAL_STDERR, formatString, ap); myVprintf(LOCAL_STDERR, formatString, ap);
va_end(ap); va_end(ap);
myPrintf(LOCAL_STDERR, "\n"); myPrintf(LOCAL_STDERR, "\n");
} }
} }
static void infoPrint(const char* formatString, ...) static void infoPrint(const char* formatString, ...)
{ {
va_list ap = { 0 }; va_list ap = { 0 };
va_start(ap, formatString); va_start(ap, formatString);
myVprintf(LOCAL_STDOUT, formatString, ap); myVprintf(LOCAL_STDOUT, formatString, ap);
va_end(ap); va_end(ap);
myPrintf(LOCAL_STDOUT, "\n"); myPrintf(LOCAL_STDOUT, "\n");
} }
// Because we mess around with FDs, we need our own printing routine // Because we mess around with FDs, we need our own printing routine
...@@ -483,20 +483,20 @@ static void infoPrint(const char* formatString, ...) ...@@ -483,20 +483,20 @@ static void infoPrint(const char* formatString, ...)
static int myPrintf(int fd, const char* formatString, ...) static int myPrintf(int fd, const char* formatString, ...)
{ {
va_list ap = { 0 }; va_list ap = { 0 };
int returnCode; int returnCode;
va_start(ap, formatString); va_start(ap, formatString);
returnCode = myVprintf(fd, formatString, ap); returnCode = myVprintf(fd, formatString, ap);
va_end(ap); va_end(ap);
return returnCode; return returnCode;
} }
// Returns 0 on success, 1 on error // Returns 0 on success, 1 on error
static int myVprintf(int fd, const char* formatString, va_list ap) static int myVprintf(int fd, const char* formatString, va_list ap)
{ {
int length; int length;
char string[MAX_STRING_LENGTH]; char string[MAX_STRING_LENGTH];
length = vsnprintf(string, MAX_STRING_LENGTH - 1, formatString, ap); length = vsnprintf(string, MAX_STRING_LENGTH - 1, formatString, ap);
string[length] = '\0'; string[length] = '\0';
return (length != write(fd, string, length)); return (length != write(fd, string, length));
} }
...@@ -65,28 +65,28 @@ static void ezspSerialPortCallback(EzspSerialPortEvent event, int fileDescriptor ...@@ -65,28 +65,28 @@ static void ezspSerialPortCallback(EzspSerialPortEvent event, int fileDescriptor
static EmberStatus gatewayBackchannelStart(void) static EmberStatus gatewayBackchannelStart(void)
{ {
if (backchannelEnable) { if (backchannelEnable) {
if (EMBER_SUCCESS != backchannelStartServer(SERIAL_PORT_CLI)) { if (EMBER_SUCCESS != backchannelStartServer(SERIAL_PORT_CLI)) {
fprintf(stderr, fprintf(stderr,
"Fatal: Failed to start backchannel services for CLI.\n"); "Fatal: Failed to start backchannel services for CLI.\n");
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} }
if (EMBER_SUCCESS != backchannelStartServer(SERIAL_PORT_RAW)) { if (EMBER_SUCCESS != backchannelStartServer(SERIAL_PORT_RAW)) {
fprintf(stderr, fprintf(stderr,
"Fatal: Failed to start backchannel services for RAW data.\n"); "Fatal: Failed to start backchannel services for RAW data.\n");
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} }
} }
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
void gatewayBackchannelStop(void) void gatewayBackchannelStop(void)
{ {
if (backchannelEnable) { if (backchannelEnable) {
backchannelStopServer(SERIAL_PORT_CLI); backchannelStopServer(SERIAL_PORT_CLI);
backchannelStopServer(SERIAL_PORT_RAW); backchannelStopServer(SERIAL_PORT_RAW);
} }
} }
bool emberAfMainStartCallback(int* returnCode, bool emberAfMainStartCallback(int* returnCode,
...@@ -95,21 +95,21 @@ bool emberAfMainStartCallback(int* returnCode, ...@@ -95,21 +95,21 @@ bool emberAfMainStartCallback(int* returnCode,
{ {
debugPrint("gatewaitInit()"); debugPrint("gatewaitInit()");
// This will process EZSP command-line options as well as determine // This will process EZSP command-line options as well as determine
// whether the backchannel should be turned on. // whether the backchannel should be turned on.
if (!ezspProcessCommandOptions(argc, argv)) { if (!ezspProcessCommandOptions(argc, argv)) {
*returnCode = EMBER_ERR_FATAL; *returnCode = EMBER_ERR_FATAL;
return true; return true;
} }
*returnCode = gatewayBackchannelStart(); *returnCode = gatewayBackchannelStart();
if (*returnCode != EMBER_SUCCESS) { if (*returnCode != EMBER_SUCCESS) {
return true; return true;
} }
emberSerialSetPrompt(cliPrompt); emberSerialSetPrompt(cliPrompt);
emberSerialCommandCompletionInit(emberCommandTable); emberSerialCommandCompletionInit(emberCommandTable);
return false; return false;
} }
......
...@@ -3,10 +3,10 @@ void gatewayBackchannelStop(void); ...@@ -3,10 +3,10 @@ void gatewayBackchannelStop(void);
typedef uint8_t BackchannelState; typedef uint8_t BackchannelState;
enum { enum {
NO_CONNECTION = 0, NO_CONNECTION = 0,
CONNECTION_EXISTS = 1, CONNECTION_EXISTS = 1,
NEW_CONNECTION = 2, NEW_CONNECTION = 2,
CONNECTION_ERROR = 3, CONNECTION_ERROR = 3,
}; };
extern const bool backchannelSupported; extern const bool backchannelSupported;
......
...@@ -38,7 +38,7 @@ void emAfOtaImageDelete(void); ...@@ -38,7 +38,7 @@ void emAfOtaImageDelete(void);
// Server CLI interface // Server CLI interface
#if !defined (EMBER_AF_PLUGIN_OTA_SERVER) #if !defined (EMBER_AF_PLUGIN_OTA_SERVER)
#define OTA_SERVER_COMMANDS #define OTA_SERVER_COMMANDS
#endif #endif
void otaImageNotifyCommand(void); void otaImageNotifyCommand(void);
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
// This relates all OTA command IDs in app/framework/gen/command-id.h // This relates all OTA command IDs in app/framework/gen/command-id.h
// to minimum message lengths (does not include EMBER_AF_ZCL_OVERHEAD) // to minimum message lengths (does not include EMBER_AF_ZCL_OVERHEAD)
PGM uint8_t emAfOtaMinMessageLengths[] = { PGM uint8_t emAfOtaMinMessageLengths[] = {
2, // Image Notify 2, // Image Notify
8, // Query Next Image Request 8, // Query Next Image Request
1, // Query Next Image Response 1, // Query Next Image Response
13, // Image Block Request 13, // Image Block Request
13, // Image Page Request 13, // Image Page Request
1, // Image Block Response (abort is shortest) 1, // Image Block Response (abort is shortest)
...@@ -48,14 +48,14 @@ EmberAfOtaImageId emAfOtaCreateEmberAfOtaImageIdStruct(uint16_t manufacturerId, ...@@ -48,14 +48,14 @@ EmberAfOtaImageId emAfOtaCreateEmberAfOtaImageIdStruct(uint16_t manufacturerId,
// This assumes the message has already been validated for its length // This assumes the message has already been validated for its length
uint8_t emAfOtaParseImageIdFromMessage(EmberAfOtaImageId* returnId, uint8_t emAfOtaParseImageIdFromMessage(EmberAfOtaImageId* returnId,
const uint8_t* buffer, const uint8_t* buffer,
uint8_t length) uint8_t length)
{ {
returnId->manufacturerId = emberAfGetInt16u(buffer, 0, length); returnId->manufacturerId = emberAfGetInt16u(buffer, 0, length);
returnId->imageTypeId = emberAfGetInt16u(buffer, 2, length); returnId->imageTypeId = emberAfGetInt16u(buffer, 2, length);
returnId->firmwareVersion = emberAfGetInt32u(buffer, 4, length); returnId->firmwareVersion = emberAfGetInt32u(buffer, 4, length);
MEMSET(returnId->deviceSpecificFileEui64, 0, EUI64_SIZE); MEMSET(returnId->deviceSpecificFileEui64, 0, EUI64_SIZE);
return 8; return 8;
} }
#if defined(EMBER_AF_PRINT_CORE) #if defined(EMBER_AF_PRINT_CORE)
......
...@@ -22,19 +22,19 @@ ...@@ -22,19 +22,19 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef enum { typedef enum {
UPGRADE_IF_SERVER_HAS_NEWER = 0, UPGRADE_IF_SERVER_HAS_NEWER = 0,
DOWNGRADE_IF_SERVER_HAS_OLDER = 1, DOWNGRADE_IF_SERVER_HAS_OLDER = 1,
REINSTALL_IF_SERVER_HAS_SAME = 2, REINSTALL_IF_SERVER_HAS_SAME = 2,
NO_NEXT_VERSION = 3, NO_NEXT_VERSION = 3,
} NextVersionPolicy; } NextVersionPolicy;
#define QUERY_POLICY_MAX NO_NEXT_VERSION #define QUERY_POLICY_MAX NO_NEXT_VERSION
#ifdef EMBER_COMMAND_INTEPRETER_HAS_DESCRIPTION_FIELD #ifdef EMBER_COMMAND_INTEPRETER_HAS_DESCRIPTION_FIELD
static PGM_P nextVersionPolicyStrings[] = { static PGM_P nextVersionPolicyStrings[] = {
"Upgrade if server has newer", "Upgrade if server has newer",
"Downgrade if server has older", "Downgrade if server has older",
"Reinstall if server has same", "Reinstall if server has same",
"No next version", "No next version",
}; };
#endif // EMBER_COMMAND_INTEPRETER_HAS_DESCRIPTION_FIELD #endif // EMBER_COMMAND_INTEPRETER_HAS_DESCRIPTION_FIELD
...@@ -70,10 +70,10 @@ PGM_P imageBlockRequestPolicyStrings[] = { ...@@ -70,10 +70,10 @@ PGM_P imageBlockRequestPolicyStrings[] = {
#endif #endif
typedef enum { typedef enum {
UPGRADE_NOW = 0, UPGRADE_NOW = 0,
UPGRADE_SOON = 1, UPGRADE_SOON = 1,
UPGRADE_ASK_ME_LATER = 2, UPGRADE_ASK_ME_LATER = 2,
UPGRADE_ABORT = 3, UPGRADE_ABORT = 3,
} UpgradePolicy; } UpgradePolicy;
#define UPGRADE_POLICY_MAX UPGRADE_ABORT #define UPGRADE_POLICY_MAX UPGRADE_ABORT
...@@ -81,18 +81,18 @@ UpgradePolicy upgradePolicy = UPGRADE_NOW; ...@@ -81,18 +81,18 @@ UpgradePolicy upgradePolicy = UPGRADE_NOW;
#define UPGRADE_SOON_TIME_SECONDS (2 * 60) #define UPGRADE_SOON_TIME_SECONDS (2 * 60)
PGM_P upgradePolicyStrings[] = { PGM_P upgradePolicyStrings[] = {
"Upgrade Now", "Upgrade Now",
"Upgrade In a few minutes", "Upgrade In a few minutes",
"Ask me later to upgrade", "Ask me later to upgrade",
"Abort upgrade", "Abort upgrade",
}; };
// This corresponds to the enumerated UpgradePolicy list. // This corresponds to the enumerated UpgradePolicy list.
PGM uint32_t upgradeTimes[] = { PGM uint32_t upgradeTimes[] = {
0, // Now 0, // Now
UPGRADE_SOON_TIME_SECONDS, // in a little while UPGRADE_SOON_TIME_SECONDS, // in a little while
0xFFFFFFFFL, // go ask your father (wait forever) 0xFFFFFFFFL, // go ask your father (wait forever)
0, // unused 0, // unused
}; };
#if defined EM_AF_TEST_HARNESS_CODE #if defined EM_AF_TEST_HARNESS_CODE
...@@ -112,8 +112,8 @@ static uint8_t pageRequestStatus = PAGE_REQUEST_STATUS_CODE; ...@@ -112,8 +112,8 @@ static uint8_t pageRequestStatus = PAGE_REQUEST_STATUS_CODE;
static uint16_t otaMinimumBlockPeriodMs = 0; static uint16_t otaMinimumBlockPeriodMs = 0;
#define SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST \ #define SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST \
(EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT \ (EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT \
| EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER) | EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER)
#ifdef EMBER_TEST #ifdef EMBER_TEST
uint8_t testClientDelayUnit = OTA_SERVER_DO_NOT_OVERRIDE_CLIENT_DELAY_UNITS; uint8_t testClientDelayUnit = OTA_SERVER_DO_NOT_OVERRIDE_CLIENT_DELAY_UNITS;
...@@ -164,135 +164,140 @@ void emAfOtaServerPolicyPrint(void) ...@@ -164,135 +164,140 @@ void emAfOtaServerPolicyPrint(void)
} }
static bool determineNextSoftwareVersion(uint32_t versionServerHas, static bool determineNextSoftwareVersion(uint32_t versionServerHas,
uint32_t versionClientHas) uint32_t versionClientHas)
{ {
// Our system here controls whether we tell the client to // Our system here controls whether we tell the client to
// (A) upgrade, because we have a newer version // (A) upgrade, because we have a newer version
// (B) downgrade, because we have an older version we want to install // (B) downgrade, because we have an older version we want to install
// (C) reinstall, because we have the same version you have currently // (C) reinstall, because we have the same version you have currently
// (D) do nothing (no 'next' image is avaiable) // (D) do nothing (no 'next' image is avaiable)
switch (nextVersionPolicy) { switch (nextVersionPolicy) {
case UPGRADE_IF_SERVER_HAS_NEWER: case UPGRADE_IF_SERVER_HAS_NEWER:
if (versionServerHas > versionClientHas) { if (versionServerHas > versionClientHas) {
return true; return true;
} }
break; break;
case DOWNGRADE_IF_SERVER_HAS_OLDER: case DOWNGRADE_IF_SERVER_HAS_OLDER:
if (versionServerHas < versionClientHas) { if (versionServerHas < versionClientHas) {
return true; return true;
} }
break; break;
case REINSTALL_IF_SERVER_HAS_SAME: case REINSTALL_IF_SERVER_HAS_SAME:
if (versionServerHas == versionClientHas) { if (versionServerHas == versionClientHas) {
return true; return true;
} }
break; break;
case NO_NEXT_VERSION: case NO_NEXT_VERSION:
default: default:
break; break;
} }
return false; return false;
} }
uint8_t emberAfOtaServerQueryCallback(const EmberAfOtaImageId* currentImageId, uint8_t emberAfOtaServerQueryCallback(const EmberAfOtaImageId* currentImageId,
uint16_t* hardwareVersion, uint16_t* hardwareVersion,
EmberAfOtaImageId* nextUpgradeImageId) EmberAfOtaImageId* nextUpgradeImageId)
{ {
// This function is called by the OTA cluster server to determine what // This function is called by the OTA cluster server to determine what
// the 'next' version of software is for a particular device requesting // the 'next' version of software is for a particular device requesting
// a new download image. The server returns a status code indicating // a new download image. The server returns a status code indicating
// EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE, or EMBER_ZCL_STATUS_SUCCESS // EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE, or EMBER_ZCL_STATUS_SUCCESS
// (new image is available). It then also fills in the 'nextUpgradeImageId' // (new image is available). It then also fills in the 'nextUpgradeImageId'
// structure with the appropriate version. // structure with the appropriate version.
// The server can use whatever criteria it wants to dictate what // The server can use whatever criteria it wants to dictate what
// the 'next' version is and if it is currently available. // the 'next' version is and if it is currently available.
// This sample does this based on a global policy value. // This sample does this based on a global policy value.
uint8_t status = EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE; uint8_t status = EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE;
bool hardwareVersionMismatch = false; bool hardwareVersionMismatch = false;
otaPrintln("QueryNextImageRequest mfgId:0x%2x imageTypeId:0x%2x, fw:0x%4x", otaPrintln("QueryNextImageRequest mfgId:0x%2x imageTypeId:0x%2x, fw:0x%4x",
currentImageId->manufacturerId, currentImageId->manufacturerId,
currentImageId->imageTypeId, currentImageId->imageTypeId,
currentImageId->firmwareVersion); currentImageId->firmwareVersion);
*nextUpgradeImageId *nextUpgradeImageId
= emberAfOtaStorageSearchCallback(currentImageId->manufacturerId, = emberAfOtaStorageSearchCallback(currentImageId->manufacturerId,
currentImageId->imageTypeId, currentImageId->imageTypeId,
hardwareVersion); hardwareVersion);
if (emberAfIsOtaImageIdValid(nextUpgradeImageId)) { if (emberAfIsOtaImageIdValid(nextUpgradeImageId)) {
// We only perform a check if both the query and the // We only perform a check if both the query and the
// file have hardware version(s). If one or the other doesn't // file have hardware version(s). If one or the other doesn't
// have them, we assume a match is still possible. // have them, we assume a match is still possible.
if (hardwareVersion) { otaPrintln("[OTA]2222,hardwareVersion=%d",hardwareVersion);
EmberAfOtaHeader header; if (hardwareVersion) {
emberAfOtaStorageGetFullHeaderCallback(nextUpgradeImageId, EmberAfOtaHeader header;
&header); emberAfOtaStorageGetFullHeaderCallback(nextUpgradeImageId,
if (header.fieldControl & HARDWARE_VERSIONS_PRESENT_MASK) { &header);
if (*hardwareVersion < header.minimumHardwareVersion if (header.fieldControl & HARDWARE_VERSIONS_PRESENT_MASK) {
|| header.maximumHardwareVersion < *hardwareVersion) { otaPrintln("[OTA]3333");
otaPrintln("Hardware version 0x%02X does not fall within the min (0x%02X) and max (0x%02X) hardware versions in the file.", if (*hardwareVersion < header.minimumHardwareVersion
*hardwareVersion, || header.maximumHardwareVersion < *hardwareVersion) {
header.minimumHardwareVersion, otaPrintln("Hardware version 0x%02X does not fall within the min (0x%02X) and max (0x%02X) hardware versions in the file.",
header.maximumHardwareVersion); *hardwareVersion,
hardwareVersionMismatch = true; header.minimumHardwareVersion,
} header.maximumHardwareVersion);
} hardwareVersionMismatch = true;
} }
// "!hardwareVersionMismatch" does not mean the hardware }
// versions match. It just means we don't *disqualify* the image }
// as a potential upgrade candidate because the hardware is out // "!hardwareVersionMismatch" does not mean the hardware
// of range. // versions match. It just means we don't *disqualify* the image
if (!hardwareVersionMismatch) { // as a potential upgrade candidate because the hardware is out
status = (determineNextSoftwareVersion(nextUpgradeImageId->firmwareVersion, // of range.
currentImageId->firmwareVersion) if (!hardwareVersionMismatch) {
? EMBER_ZCL_STATUS_SUCCESS otaPrintln("[OTA]4444,%x,%x",nextUpgradeImageId->firmwareVersion,currentImageId->firmwareVersion);
: EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE); status = (determineNextSoftwareVersion(nextUpgradeImageId->firmwareVersion,
if (status == EMBER_ZCL_STATUS_SUCCESS) { currentImageId->firmwareVersion)
otaPrintln("Next fw version is: 0x%4X", ? EMBER_ZCL_STATUS_SUCCESS
nextUpgradeImageId->firmwareVersion); : EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE);
} otaPrintln("[OTA]555,status=%x",status);
} if (status == EMBER_ZCL_STATUS_SUCCESS) {
} otaPrintln("Next fw version is: 0x%4X",
return status; nextUpgradeImageId->firmwareVersion);
}
}
}
otaPrintln("[OTA]11111");
return status;
} }
uint8_t emberAfOtaServerBlockSizeCallback(EmberNodeId clientNodeId) uint8_t emberAfOtaServerBlockSizeCallback(EmberNodeId clientNodeId)
{ {
// This function provides a way for the server to potentially // This function provides a way for the server to potentially
// adjust the block size based on the client who is requesting. // adjust the block size based on the client who is requesting.
// In other words if we are using source routing we will limit // In other words if we are using source routing we will limit
// data returned by enough to put a source route into the message. // data returned by enough to put a source route into the message.
EmberApsFrame apsFrame; EmberApsFrame apsFrame;
uint8_t maxSize; uint8_t maxSize;
apsFrame.options = EMBER_APS_OPTION_NONE; apsFrame.options = EMBER_APS_OPTION_NONE;
if (emberAfIsCurrentSecurityProfileSmartEnergy()) { if (emberAfIsCurrentSecurityProfileSmartEnergy()) {
apsFrame.options |= EMBER_APS_OPTION_ENCRYPTION; apsFrame.options |= EMBER_APS_OPTION_ENCRYPTION;
} }
maxSize = emberAfMaximumApsPayloadLength(EMBER_OUTGOING_DIRECT, maxSize = emberAfMaximumApsPayloadLength(EMBER_OUTGOING_DIRECT,
clientNodeId, clientNodeId,
&apsFrame); &apsFrame);
maxSize -= IMAGE_BLOCK_RESPONSE_OVERHEAD; maxSize -= IMAGE_BLOCK_RESPONSE_OVERHEAD;
return maxSize; return maxSize;
} }
uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallbackStruct* data) uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallbackStruct* data)
{ {
uint8_t status; uint8_t status;
uint16_t serverBlockPeriodValue = otaMinimumBlockPeriodMs; uint16_t serverBlockPeriodValue = otaMinimumBlockPeriodMs;
uint8_t clientDelayUnit; uint8_t clientDelayUnit;
bool useSecondsDelay = false; bool useSecondsDelay = false;
if (SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST if (SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST
== (data->bitmask & SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST)) { == (data->bitmask & SERVER_AND_CLIENT_SUPPORT_MIN_BLOCK_REQUEST)) {
clientDelayUnit = emberAfPluginOtaServerPolicyGetClientDelayUnits( clientDelayUnit = emberAfPluginOtaServerPolicyGetClientDelayUnits(
data->source, data->source,
data->sourceEui); data->sourceEui);
#ifdef EMBER_TEST #ifdef EMBER_TEST
if (OTA_SERVER_DO_NOT_OVERRIDE_CLIENT_DELAY_UNITS != testClientDelayUnit) { if (OTA_SERVER_DO_NOT_OVERRIDE_CLIENT_DELAY_UNITS != testClientDelayUnit) {
...@@ -300,46 +305,46 @@ uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallba ...@@ -300,46 +305,46 @@ uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallba
} }
#endif // EMBER_TEST #endif // EMBER_TEST
switch (clientDelayUnit) { switch (clientDelayUnit) {
case OTA_SERVER_NO_RATE_LIMITING_FOR_CLIENT: case OTA_SERVER_NO_RATE_LIMITING_FOR_CLIENT:
return EMBER_ZCL_STATUS_SUCCESS; return EMBER_ZCL_STATUS_SUCCESS;
break; break;
case OTA_SERVER_CLIENT_USES_MILLISECONDS: case OTA_SERVER_CLIENT_USES_MILLISECONDS:
break; break;
case OTA_SERVER_CLIENT_USES_SECONDS: case OTA_SERVER_CLIENT_USES_SECONDS:
useSecondsDelay = true; useSecondsDelay = true;
break; break;
case OTA_SERVER_DISCOVER_CLIENT_DELAY_UNITS: case OTA_SERVER_DISCOVER_CLIENT_DELAY_UNITS:
{ {
// If we support dynamic block request, check to see if we know how the // If we support dynamic block request, check to see if we know how the
// client treats the field. If we haven't tested the client yet or don't // client treats the field. If we haven't tested the client yet or don't
// have a spot for it as an active OTA session, we tell it to // have a spot for it as an active OTA session, we tell it to
// WAIT_FOR_DATA // WAIT_FOR_DATA
status = emAfOtaServerCheckDynamicBlockPeriodDownload(data); status = emAfOtaServerCheckDynamicBlockPeriodDownload(data);
if (EMBER_ZCL_STATUS_WAIT_FOR_DATA == status) { if (EMBER_ZCL_STATUS_WAIT_FOR_DATA == status) {
return EMBER_ZCL_STATUS_WAIT_FOR_DATA; return EMBER_ZCL_STATUS_WAIT_FOR_DATA;
} }
useSecondsDelay = emAfOtaServerDynamicBlockPeriodClientUsesSeconds(data->source); useSecondsDelay = emAfOtaServerDynamicBlockPeriodClientUsesSeconds(data->source);
} }
break; break;
default: default:
break; break;
} }
if (useSecondsDelay) { if (useSecondsDelay) {
serverBlockPeriodValue /= 1000; serverBlockPeriodValue /= 1000;
} }
if (data->minBlockRequestPeriod != serverBlockPeriodValue) { if (data->minBlockRequestPeriod != serverBlockPeriodValue) {
data->minBlockRequestPeriod = serverBlockPeriodValue; data->minBlockRequestPeriod = serverBlockPeriodValue;
return EMBER_ZCL_STATUS_WAIT_FOR_DATA; return EMBER_ZCL_STATUS_WAIT_FOR_DATA;
} }
} }
#if defined EM_AF_TEST_HARNESS_CODE #if defined EM_AF_TEST_HARNESS_CODE
// TEST Harness code // TEST Harness code
...@@ -357,41 +362,41 @@ uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallba ...@@ -357,41 +362,41 @@ uint8_t emberAfOtaServerImageBlockRequestCallback(EmberAfImageBlockRequestCallba
} }
#endif #endif
return EMBER_ZCL_STATUS_SUCCESS; return EMBER_ZCL_STATUS_SUCCESS;
} }
bool emberAfOtaServerUpgradeEndRequestCallback(EmberNodeId source, bool emberAfOtaServerUpgradeEndRequestCallback(EmberNodeId source,
uint8_t status, uint8_t status,
uint32_t* returnValue, uint32_t* returnValue,
const EmberAfOtaImageId* imageId) const EmberAfOtaImageId* imageId)
{ {
otaPrintln("Client 0x%2X indicated upgrade status: 0x%X", otaPrintln("Client 0x%2X indicated upgrade status: 0x%X",
source, source,
status); status);
emAfOtaServerCompleteDynamicBlockPeriodDownload(source); emAfOtaServerCompleteDynamicBlockPeriodDownload(source);
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
// If status != EMBER_ZCL_STATUS_SUCCESS then this callback is // If status != EMBER_ZCL_STATUS_SUCCESS then this callback is
// only informative. Return code will be ignored. // only informative. Return code will be ignored.
return false; return false;
} }
otaPrintln("Upgrade End Response: %p", upgradePolicyStrings[upgradePolicy]); otaPrintln("Upgrade End Response: %p", upgradePolicyStrings[upgradePolicy]);
if (upgradePolicy == UPGRADE_ABORT) { if (upgradePolicy == UPGRADE_ABORT) {
return false; return false;
} }
*returnValue = upgradeTimes[upgradePolicy]; *returnValue = upgradeTimes[upgradePolicy];
return true; return true;
} }
void emAfOtaServerSetQueryPolicy(uint8_t value) void emAfOtaServerSetQueryPolicy(uint8_t value)
{ {
if (value <= QUERY_POLICY_MAX) { if (value <= QUERY_POLICY_MAX) {
nextVersionPolicy = (NextVersionPolicy)value; nextVersionPolicy = (NextVersionPolicy)value;
} }
} }
void emAfOtaServerSetBlockRequestPolicy(uint8_t value) void emAfOtaServerSetBlockRequestPolicy(uint8_t value)
...@@ -401,7 +406,7 @@ void emAfOtaServerSetBlockRequestPolicy(uint8_t value) ...@@ -401,7 +406,7 @@ void emAfOtaServerSetBlockRequestPolicy(uint8_t value)
imageBlockRequestPolicy = (ImageBlockRequestPolicy)value; imageBlockRequestPolicy = (ImageBlockRequestPolicy)value;
} }
#else #else
otaPrintln("Unsupported."); otaPrintln("Unsupported.");
#endif #endif
} }
......
...@@ -149,9 +149,9 @@ static PGM_P notifyArguments[] = { ...@@ -149,9 +149,9 @@ static PGM_P notifyArguments[] = {
emberCommandEntryTerminator(), emberCommandEntryTerminator(),
EmberCommandEntry emberAfPluginOtaServerCommands[] = { EmberCommandEntry emberAfPluginOtaServerCommands[] = {
OTA_SERVER_COMMANDS OTA_SERVER_COMMANDS
emberCommandEntryTerminator(), emberCommandEntryTerminator(),
}; };
#endif // EMBER_AF_GENERATE_CLI #endif // EMBER_AF_GENERATE_CLI
......
...@@ -79,13 +79,13 @@ uint8_t getIndexForDownloadingNodeId(EmberNodeId nodeId) ...@@ -79,13 +79,13 @@ uint8_t getIndexForDownloadingNodeId(EmberNodeId nodeId)
void emAfOtaServerDynamicBlockPeriodInit() void emAfOtaServerDynamicBlockPeriodInit()
{ {
// Initialize array to have no current OTA downloads // Initialize array to have no current OTA downloads
uint8_t nodeIndex; uint8_t nodeIndex;
for (nodeIndex = 0; nodeIndex < MAX_DOWNLOADS; nodeIndex++) { for (nodeIndex = 0; nodeIndex < MAX_DOWNLOADS; nodeIndex++) {
downloadingNodes[nodeIndex].nodeId = EMBER_NULL_NODE_ID; downloadingNodes[nodeIndex].nodeId = EMBER_NULL_NODE_ID;
downloadingNodes[nodeIndex].unitDiscoveryState = STATE_NONE; downloadingNodes[nodeIndex].unitDiscoveryState = STATE_NONE;
downloadingNodes[nodeIndex].msTickWhenLastSeen = 0; downloadingNodes[nodeIndex].msTickWhenLastSeen = 0;
} }
} }
void emAfOtaServerDynamicBlockPeriodTick() void emAfOtaServerDynamicBlockPeriodTick()
......
...@@ -111,11 +111,11 @@ uint8_t emAfOtaPageRequestHandler(uint8_t clientEndpoint, ...@@ -111,11 +111,11 @@ uint8_t emAfOtaPageRequestHandler(uint8_t clientEndpoint,
void emAfOtaPageRequestTick(uint8_t endpoint) void emAfOtaPageRequestTick(uint8_t endpoint)
{ {
if (requesterNodeId == EMBER_NULL_NODE_ID) { if (requesterNodeId == EMBER_NULL_NODE_ID) {
return; return;
} }
sendBlockRequest(); sendBlockRequest();
emberAfScheduleServerTickExtended(endpoint, emberAfScheduleServerTickExtended(endpoint,
ZCL_OTA_BOOTLOAD_CLUSTER_ID, ZCL_OTA_BOOTLOAD_CLUSTER_ID,
requesterResponseSpacing, requesterResponseSpacing,
......
...@@ -46,9 +46,9 @@ uint8_t otaServerEndpoint = 0; // invalid endpoint ...@@ -46,9 +46,9 @@ uint8_t otaServerEndpoint = 0; // invalid endpoint
#define MAX_POSSIBLE_SERVER_BLOCK_SIZE 63 #define MAX_POSSIBLE_SERVER_BLOCK_SIZE 63
#if defined(EMBER_AF_PLUGIN_OTA_SERVER_MIN_BLOCK_REQUEST_SUPPORT) #if defined(EMBER_AF_PLUGIN_OTA_SERVER_MIN_BLOCK_REQUEST_SUPPORT)
#define MIN_BLOCK_REQUEST_SERVER_SUPPORT EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER #define MIN_BLOCK_REQUEST_SERVER_SUPPORT EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_SERVER
#else #else
#define MIN_BLOCK_REQUEST_SERVER_SUPPORT 0 #define MIN_BLOCK_REQUEST_SERVER_SUPPORT 0
#endif #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
...@@ -58,7 +58,7 @@ uint8_t otaServerEndpoint = 0; // invalid endpoint ...@@ -58,7 +58,7 @@ uint8_t otaServerEndpoint = 0; // invalid endpoint
static bool commandParse(EmberAfClusterCommand* command); static bool commandParse(EmberAfClusterCommand* command);
#define prepareClusterResponse(commandId, status) \ #define prepareClusterResponse(commandId, status) \
emAfOtaServerPrepareResponse(false, \ emAfOtaServerPrepareResponse(false, \
(commandId), \ (commandId), \
(status), \ (status), \
0) // defaultResponsePayloadCommandId 0) // defaultResponsePayloadCommandId
...@@ -77,14 +77,14 @@ static void addEmberAfOtaImageIdIntoResponse(const EmberAfOtaImageId* id); ...@@ -77,14 +77,14 @@ static void addEmberAfOtaImageIdIntoResponse(const EmberAfOtaImageId* id);
void emberAfOtaBootloadClusterServerInitCallback(uint8_t endpoint) void emberAfOtaBootloadClusterServerInitCallback(uint8_t endpoint)
{ {
emberAfOtaStorageInitCallback(); emberAfOtaStorageInitCallback();
otaServerEndpoint = endpoint; otaServerEndpoint = endpoint;
emAfOtaServerDynamicBlockPeriodInit(); emAfOtaServerDynamicBlockPeriodInit();
} }
void emberAfOtaBootloadClusterServerTickCallback(uint8_t endpoint) void emberAfOtaBootloadClusterServerTickCallback(uint8_t endpoint)
{ {
emAfOtaPageRequestTick(endpoint); emAfOtaPageRequestTick(endpoint);
} }
// This tick is endpointless and declared differently in plugin.properties // This tick is endpointless and declared differently in plugin.properties
...@@ -98,11 +98,11 @@ void emberAfOtaServerTick(void) ...@@ -98,11 +98,11 @@ void emberAfOtaServerTick(void)
// ------------------------------------------------------- // -------------------------------------------------------
bool emberAfOtaServerIncomingMessageRawCallback(EmberAfClusterCommand* command) bool emberAfOtaServerIncomingMessageRawCallback(EmberAfClusterCommand* command)
{ {
EmberStatus status; EmberStatus status;
if (!commandParse(command)) { if (!commandParse(command)) {
emberAfOtaBootloadClusterPrintln("ClusterError: failed parsing cmd 0x%x", emberAfOtaBootloadClusterPrintln("ClusterError: failed parsing cmd 0x%x",
command->commandId); command->commandId);
emberAfOtaBootloadClusterFlush(); emberAfOtaBootloadClusterFlush();
status = emberAfSendDefaultResponse(command, EMBER_ZCL_STATUS_INVALID_FIELD); status = emberAfSendDefaultResponse(command, EMBER_ZCL_STATUS_INVALID_FIELD);
} else { } else {
status = emberAfSendResponse(); status = emberAfSendResponse();
...@@ -115,29 +115,29 @@ bool emberAfOtaServerIncomingMessageRawCallback(EmberAfClusterCommand* command) ...@@ -115,29 +115,29 @@ bool emberAfOtaServerIncomingMessageRawCallback(EmberAfClusterCommand* command)
status); status);
} }
// Always return true to indicate we processed the message. // Always return true to indicate we processed the message.
return true; return true;
} }
static uint8_t queryNextImageRequestHandler(const EmberAfOtaImageId* currentImageId, static uint8_t queryNextImageRequestHandler(const EmberAfOtaImageId* currentImageId,
uint16_t* hardwareVersion) uint16_t* hardwareVersion)
{ {
uint8_t status; uint8_t status;
EmberAfOtaImageId upgradeImageId; EmberAfOtaImageId upgradeImageId;
status = emberAfOtaServerQueryCallback(currentImageId, status = emberAfOtaServerQueryCallback(currentImageId,
hardwareVersion, hardwareVersion,
&upgradeImageId); &upgradeImageId);
prepareClusterResponse(ZCL_QUERY_NEXT_IMAGE_RESPONSE_COMMAND_ID, prepareClusterResponse(ZCL_QUERY_NEXT_IMAGE_RESPONSE_COMMAND_ID,
status); status);
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
return status; return status;
} }
addEmberAfOtaImageIdIntoResponse(&upgradeImageId); addEmberAfOtaImageIdIntoResponse(&upgradeImageId);
emberAfPutInt32uInResp(emberAfOtaStorageGetTotalImageSizeCallback(&upgradeImageId)); emberAfPutInt32uInResp(emberAfOtaStorageGetTotalImageSizeCallback(&upgradeImageId));
return status; return status;
} }
bool emberAfOtaServerSendImageNotifyCallback(EmberNodeId dest, bool emberAfOtaServerSendImageNotifyCallback(EmberNodeId dest,
...@@ -185,28 +185,28 @@ bool emberAfOtaServerSendImageNotifyCallback(EmberNodeId dest, ...@@ -185,28 +185,28 @@ bool emberAfOtaServerSendImageNotifyCallback(EmberNodeId dest,
} }
static void printBlockRequestInfo(const EmberAfOtaImageId* id, static void printBlockRequestInfo(const EmberAfOtaImageId* id,
uint8_t maxDataSize, uint8_t maxDataSize,
uint32_t offset) uint32_t offset)
{ {
// To reduce the redundant data printed by the server, it will only print // To reduce the redundant data printed by the server, it will only print
// a request for a different image than the last one. To change this // a request for a different image than the last one. To change this
// behavior update the bool below. // behavior update the bool below.
const bool printAllRequests = false; const bool printAllRequests = false;
static EmberAfOtaImageId lastImageId = INVALID_OTA_IMAGE_ID; static EmberAfOtaImageId lastImageId = INVALID_OTA_IMAGE_ID;
if (!printAllRequests if (!printAllRequests
&& (0 == MEMCOMPARE(id, &lastImageId, sizeof(EmberAfOtaImageId)))) { && (0 == MEMCOMPARE(id, &lastImageId, sizeof(EmberAfOtaImageId)))) {
return; return;
} }
MEMMOVE(&lastImageId, id, sizeof(EmberAfOtaImageId)); MEMMOVE(&lastImageId, id, sizeof(EmberAfOtaImageId));
emberAfOtaBootloadClusterPrintln("NEW ImageBlockReq mfgId:%2x imageTypeId:%2x, file:%4x, maxDataSize:%d, offset:0x%4x", emberAfOtaBootloadClusterPrintln("NEW ImageBlockReq mfgId:%2x imageTypeId:%2x, file:%4x, maxDataSize:%d, offset:0x%4x",
id->manufacturerId, id->manufacturerId,
id->imageTypeId, id->imageTypeId,
id->firmwareVersion, id->firmwareVersion,
maxDataSize, maxDataSize,
offset); offset);
//emberAfPluginOtaServerUpdateStartedCallback(id->manufacturerId, //emberAfPluginOtaServerUpdateStartedCallback(id->manufacturerId,
//id->imageTypeId, //id->imageTypeId,
// id->firmwareVersion, // id->firmwareVersion,
...@@ -218,28 +218,28 @@ static void printBlockRequestInfo(const EmberAfOtaImageId* id, ...@@ -218,28 +218,28 @@ static void printBlockRequestInfo(const EmberAfOtaImageId* id,
// It returns 0 on error, or the number of bytes sent on success. // It returns 0 on error, or the number of bytes sent on success.
uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct* callbackData) uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct* callbackData)
{ {
uint8_t data[MAX_POSSIBLE_SERVER_BLOCK_SIZE]; uint8_t data[MAX_POSSIBLE_SERVER_BLOCK_SIZE];
uint32_t actualLength; uint32_t actualLength;
uint8_t status = EMBER_ZCL_STATUS_SUCCESS; uint8_t status = EMBER_ZCL_STATUS_SUCCESS;
uint8_t serverBlockSize = emberAfOtaServerBlockSizeCallback(callbackData->source); uint8_t serverBlockSize = emberAfOtaServerBlockSizeCallback(callbackData->source);
#ifdef EMBER_AF_PLUGIN_SUB_GHZ_SERVER #ifdef EMBER_AF_PLUGIN_SUB_GHZ_SERVER
EmberDutyCycleState dcState; EmberDutyCycleState dcState;
#endif #endif
if (serverBlockSize > MAX_POSSIBLE_SERVER_BLOCK_SIZE) { if (serverBlockSize > MAX_POSSIBLE_SERVER_BLOCK_SIZE) {
serverBlockSize = MAX_POSSIBLE_SERVER_BLOCK_SIZE; serverBlockSize = MAX_POSSIBLE_SERVER_BLOCK_SIZE;
} }
// Delay checks // Delay checks
// We delay the OTA client if the following conditions happen, listed by order // We delay the OTA client if the following conditions happen, listed by order
// 1. The duty cycle state has moved to Critical state (for SE1.4 subghz) // 1. The duty cycle state has moved to Critical state (for SE1.4 subghz)
// 2. We support a dynamically-treated Minimum Block Period, which means we // 2. We support a dynamically-treated Minimum Block Period, which means we
// can only support so many clients downloading at once. When we're at that // can only support so many clients downloading at once. When we're at that
// max, we tell new clients to delay and come back later // max, we tell new clients to delay and come back later
// 3. The OTA client indicates that it supports the Minimum Block Period, and // 3. The OTA client indicates that it supports the Minimum Block Period, and
// its value differs from the value the server holds. We tell it to wait // its value differs from the value the server holds. We tell it to wait
// for data with the updated block period value // for data with the updated block period value
// 4. Test code desires a delay // 4. Test code desires a delay
#ifdef EMBER_AF_PLUGIN_SUB_GHZ_SERVER #ifdef EMBER_AF_PLUGIN_SUB_GHZ_SERVER
// SE1.4 says that when in Critical Duty Cycle state... "If active, the OTA server // SE1.4 says that when in Critical Duty Cycle state... "If active, the OTA server
...@@ -251,67 +251,67 @@ uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct* ...@@ -251,67 +251,67 @@ uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct*
status = EMBER_ZCL_STATUS_WAIT_FOR_DATA; status = EMBER_ZCL_STATUS_WAIT_FOR_DATA;
} else } else
#endif #endif
status = emberAfOtaServerImageBlockRequestCallback(callbackData); status = emberAfOtaServerImageBlockRequestCallback(callbackData);
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
prepareClusterResponse(ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID, prepareClusterResponse(ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID,
status); status);
if (status == EMBER_ZCL_STATUS_WAIT_FOR_DATA) { if (status == EMBER_ZCL_STATUS_WAIT_FOR_DATA) {
// Current time (0 = use relative time, not UTC) // Current time (0 = use relative time, not UTC)
emberAfPutInt32uInResp(0); emberAfPutInt32uInResp(0);
emberAfPutInt32uInResp(callbackData->waitTimeSecondsResponse); emberAfPutInt32uInResp(callbackData->waitTimeSecondsResponse);
#if defined(EMBER_AF_PLUGIN_OTA_SERVER_MIN_BLOCK_REQUEST_SUPPORT) #if defined(EMBER_AF_PLUGIN_OTA_SERVER_MIN_BLOCK_REQUEST_SUPPORT)
// The min block request period is in milliseconds as defined in af-types.h // The min block request period is in milliseconds as defined in af-types.h
// This attribute, BockRequestDelay, used to be in milliseconds // This attribute, BockRequestDelay, used to be in milliseconds
// (09-5264-23, section 6.7.10) // (09-5264-23, section 6.7.10)
// It now (15-0324-02) is called MinimumBlockPeriod and is defined to be // It now (15-0324-02) is called MinimumBlockPeriod and is defined to be
// in seconds (section 11.10.10) // in seconds (section 11.10.10)
emberAfPutInt16uInResp(callbackData->minBlockRequestPeriod); emberAfPutInt16uInResp(callbackData->minBlockRequestPeriod);
#endif #endif
} }
return 0; return 0;
} }
MEMSET(data, 0, MAX_POSSIBLE_SERVER_BLOCK_SIZE); MEMSET(data, 0, MAX_POSSIBLE_SERVER_BLOCK_SIZE);
printBlockRequestInfo(callbackData->id, printBlockRequestInfo(callbackData->id,
callbackData->maxDataSize, callbackData->maxDataSize,
callbackData->offset); callbackData->offset);
callbackData->maxDataSize = (callbackData->maxDataSize < serverBlockSize callbackData->maxDataSize = (callbackData->maxDataSize < serverBlockSize
? callbackData->maxDataSize ? callbackData->maxDataSize
: serverBlockSize); : serverBlockSize);
if (EMBER_AF_OTA_STORAGE_SUCCESS if (EMBER_AF_OTA_STORAGE_SUCCESS
!= emberAfOtaStorageReadImageDataCallback(callbackData->id, != emberAfOtaStorageReadImageDataCallback(callbackData->id,
callbackData->offset, callbackData->offset,
callbackData->maxDataSize, callbackData->maxDataSize,
data, data,
&actualLength) &actualLength)
|| actualLength == 0) { || actualLength == 0) {
status = EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE; status = EMBER_ZCL_STATUS_NO_IMAGE_AVAILABLE;
// emberAfPluginOtaServerUpdateCompleteCallback(callbackData->id->manufacturerId, // emberAfPluginOtaServerUpdateCompleteCallback(callbackData->id->manufacturerId,
// callbackData->id->imageTypeId, // callbackData->id->imageTypeId,
// callbackData->id->firmwareVersion, // callbackData->id->firmwareVersion,
// callbackData->source, // callbackData->source,
// status); // status);
} }
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
if (!emAfOtaPageRequestErrorHandler()) { if (!emAfOtaPageRequestErrorHandler()) {
// If the page request code didn't handle the error (because this code // If the page request code didn't handle the error (because this code
// wasn't called due to a page request) then we send a normal // wasn't called due to a page request) then we send a normal
// response. We don't generate an error message because in that case // response. We don't generate an error message because in that case
// we were sending an unsolicited image block response. // we were sending an unsolicited image block response.
prepareDefaultResponse(status, ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID); prepareDefaultResponse(status, ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID);
} }
return 0; return 0;
} }
prepareClusterResponse(ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID, status); prepareClusterResponse(ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID, status);
addEmberAfOtaImageIdIntoResponse(callbackData->id); addEmberAfOtaImageIdIntoResponse(callbackData->id);
emberAfPutInt32uInResp(callbackData->offset); emberAfPutInt32uInResp(callbackData->offset);
emberAfPutInt8uInResp((uint8_t)actualLength); emberAfPutInt8uInResp((uint8_t)actualLength);
emberAfPutBlockInResp(data, actualLength); emberAfPutBlockInResp(data, actualLength);
emberAfSetCommandEndpoints(otaServerEndpoint, callbackData->clientEndpoint); emberAfSetCommandEndpoints(otaServerEndpoint, callbackData->clientEndpoint);
//emberAfPluginOtaServerBlockSentCallback((uint8_t)actualLength, //emberAfPluginOtaServerBlockSentCallback((uint8_t)actualLength,
// callbackData->id->manufacturerId, // callbackData->id->manufacturerId,
...@@ -319,85 +319,85 @@ uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct* ...@@ -319,85 +319,85 @@ uint8_t emAfOtaImageBlockRequestHandler(EmberAfImageBlockRequestCallbackStruct*
// callbackData->id->firmwareVersion); // callbackData->id->firmwareVersion);
// We can't send more than 128 bytes in a packet so we can safely cast this // We can't send more than 128 bytes in a packet so we can safely cast this
// to a 1-byte number. // to a 1-byte number.
return (uint8_t)actualLength; return (uint8_t)actualLength;
} }
static void constructUpgradeEndResponse(const EmberAfOtaImageId* imageId, static void constructUpgradeEndResponse(const EmberAfOtaImageId* imageId,
uint32_t upgradeTime) uint32_t upgradeTime)
{ {
prepareClusterResponse(ZCL_UPGRADE_END_RESPONSE_COMMAND_ID, prepareClusterResponse(ZCL_UPGRADE_END_RESPONSE_COMMAND_ID,
0); // status code (will ignore) 0); // status code (will ignore)
appResponseLength--; // The above function wrote an extra byte which we appResponseLength--; // The above function wrote an extra byte which we
// don't want because there is no status code for this // don't want because there is no status code for this
// message // message
addEmberAfOtaImageIdIntoResponse(imageId); addEmberAfOtaImageIdIntoResponse(imageId);
// We always use relative time. There is no benefit in using // We always use relative time. There is no benefit in using
// UTC time since the client has to support both. // UTC time since the client has to support both.
emberAfPutInt32uInResp(0); // current time emberAfPutInt32uInResp(0); // current time
emberAfPutInt32uInResp(upgradeTime); emberAfPutInt32uInResp(upgradeTime);
} }
static void upgradeEndRequestHandler(EmberNodeId source, static void upgradeEndRequestHandler(EmberNodeId source,
uint8_t status, uint8_t status,
const EmberAfOtaImageId* imageId) const EmberAfOtaImageId* imageId)
{ {
uint32_t upgradeTime; uint32_t upgradeTime;
bool goAhead; bool goAhead;
EmberAfStatus defaultRespStatus = EMBER_ZCL_STATUS_SUCCESS; EmberAfStatus defaultRespStatus = EMBER_ZCL_STATUS_SUCCESS;
emberAfOtaBootloadClusterPrintln("RX UpgradeEndReq status:%x", emberAfOtaBootloadClusterPrintln("RX UpgradeEndReq status:%x",
status); status);
// This callback is considered only informative when the status // This callback is considered only informative when the status
// is a failure. // is a failure.
goAhead = emberAfOtaServerUpgradeEndRequestCallback(source, goAhead = emberAfOtaServerUpgradeEndRequestCallback(source,
status, status,
&upgradeTime, &upgradeTime,
imageId); imageId);
if (status == EMBER_ZCL_STATUS_SUCCESS) { if (status == EMBER_ZCL_STATUS_SUCCESS) {
if (goAhead) { if (goAhead) {
constructUpgradeEndResponse(imageId, upgradeTime); constructUpgradeEndResponse(imageId, upgradeTime);
//emberAfPluginOtaServerUpdateCompleteCallback(imageId->manufacturerId, //emberAfPluginOtaServerUpdateCompleteCallback(imageId->manufacturerId,
// imageId->imageTypeId, // imageId->imageTypeId,
// imageId->firmwareVersion, // imageId->firmwareVersion,
// source, // source,
// status); // status);
return; return;
} else { } else {
defaultRespStatus = EMBER_ZCL_STATUS_ABORT; defaultRespStatus = EMBER_ZCL_STATUS_ABORT;
} }
} }
prepareDefaultResponse(defaultRespStatus, prepareDefaultResponse(defaultRespStatus,
ZCL_UPGRADE_END_REQUEST_COMMAND_ID); ZCL_UPGRADE_END_REQUEST_COMMAND_ID);
} }
static void querySpecificFileRequestHandler(uint8_t* requestNodeAddress, static void querySpecificFileRequestHandler(uint8_t* requestNodeAddress,
const EmberAfOtaImageId* imageId, const EmberAfOtaImageId* imageId,
uint16_t currentStackVersion) uint16_t currentStackVersion)
{ {
// Not supported yet. // Not supported yet.
prepareDefaultResponse(EMBER_ZCL_STATUS_UNSUP_CLUSTER_COMMAND, prepareDefaultResponse(EMBER_ZCL_STATUS_UNSUP_CLUSTER_COMMAND,
ZCL_QUERY_SPECIFIC_FILE_REQUEST_COMMAND_ID); ZCL_QUERY_SPECIFIC_FILE_REQUEST_COMMAND_ID);
} }
void emAfOtaServerPrepareResponse(bool useDefaultResponse, void emAfOtaServerPrepareResponse(bool useDefaultResponse,
uint8_t commandId, uint8_t commandId,
uint8_t status, uint8_t status,
uint8_t defaultResponsePayloadCommandId) uint8_t defaultResponsePayloadCommandId)
{ {
emberAfResponseApsFrame.sourceEndpoint = otaServerEndpoint; emberAfResponseApsFrame.sourceEndpoint = otaServerEndpoint;
appResponseLength = 0; appResponseLength = 0;
emberAfResponseApsFrame.clusterId = ZCL_OTA_BOOTLOAD_CLUSTER_ID; emberAfResponseApsFrame.clusterId = ZCL_OTA_BOOTLOAD_CLUSTER_ID;
emberAfResponseApsFrame.options = emberAfResponseApsFrame.options =
((emAfOtaServerHandlingPageRequest() ((emAfOtaServerHandlingPageRequest()
&& commandId == ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID) && commandId == ZCL_IMAGE_BLOCK_RESPONSE_COMMAND_ID)
? EMBER_APS_OPTION_NONE ? EMBER_APS_OPTION_NONE
: EMBER_APS_OPTION_RETRY); : EMBER_APS_OPTION_RETRY);
// Assume emberAfResponseApsFrame.destinationEndpoint has already // Assume emberAfResponseApsFrame.destinationEndpoint has already
// been set based on the framework. In most cases it is as simple // been set based on the framework. In most cases it is as simple
...@@ -423,189 +423,236 @@ void emAfOtaServerPrepareResponse(bool useDefaultResponse, ...@@ -423,189 +423,236 @@ void emAfOtaServerPrepareResponse(bool useDefaultResponse,
static void addEmberAfOtaImageIdIntoResponse(const EmberAfOtaImageId* id) static void addEmberAfOtaImageIdIntoResponse(const EmberAfOtaImageId* id)
{ {
emberAfPutInt16uInResp(id->manufacturerId); emberAfPutInt16uInResp(id->manufacturerId);
emberAfPutInt16uInResp(id->imageTypeId); emberAfPutInt16uInResp(id->imageTypeId);
emberAfPutInt32uInResp(id->firmwareVersion); emberAfPutInt32uInResp(id->firmwareVersion);
} }
static bool commandParse(EmberAfClusterCommand* command) static bool commandParse(EmberAfClusterCommand* command)
{ {
uint8_t commandId = command->commandId; uint8_t commandId = command->commandId;
uint8_t* buffer = command->buffer; uint8_t* buffer = command->buffer;
uint8_t length = command->bufLen; uint8_t length = command->bufLen;
uint8_t index = EMBER_AF_ZCL_OVERHEAD; uint8_t index = EMBER_AF_ZCL_OVERHEAD;
EmberAfOtaImageId imageId = INVALID_OTA_IMAGE_ID; EmberAfOtaImageId imageId = INVALID_OTA_IMAGE_ID;
if (commandId > LAST_MESSAGE_ID if (commandId > LAST_MESSAGE_ID
|| (length || (length
< (EMBER_AF_ZCL_OVERHEAD + emAfOtaMinMessageLengths[commandId]))) { < (EMBER_AF_ZCL_OVERHEAD + emAfOtaMinMessageLengths[commandId]))) {
return false; return false;
} }
switch (commandId) { switch (commandId) {
case ZCL_QUERY_NEXT_IMAGE_REQUEST_COMMAND_ID: case ZCL_QUERY_NEXT_IMAGE_REQUEST_COMMAND_ID:
case ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID: case ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID:
case ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID: { case ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID: {
uint8_t fieldControl; uint8_t fieldControl;
uint32_t offset; uint32_t offset;
uint8_t maxDataSize; uint8_t maxDataSize;
uint16_t hardwareVersion; uint16_t hardwareVersion;
bool hardwareVersionPresent = false; bool hardwareVersionPresent = false;
fieldControl = emberAfGetInt8u(buffer, index, length); fieldControl = emberAfGetInt8u(buffer, index, length);
index++; index++;
index += emAfOtaParseImageIdFromMessage(&imageId, index += emAfOtaParseImageIdFromMessage(&imageId,
&(buffer[index]), &(buffer[index]),
length); length);
if (commandId == ZCL_QUERY_NEXT_IMAGE_REQUEST_COMMAND_ID) { if (commandId == ZCL_QUERY_NEXT_IMAGE_REQUEST_COMMAND_ID) {
if (fieldControl & QUERY_NEXT_IMAGE_HW_VER_PRESENT_MASK) { if (fieldControl & QUERY_NEXT_IMAGE_HW_VER_PRESENT_MASK) {
hardwareVersionPresent = true; hardwareVersionPresent = true;
hardwareVersion = emberAfGetInt16u(buffer, index, length); hardwareVersion = emberAfGetInt16u(buffer, index, length);
index += 2; index += 2;
} }
queryNextImageRequestHandler(&imageId, queryNextImageRequestHandler(&imageId,
(hardwareVersionPresent (hardwareVersionPresent
? &hardwareVersion ? &hardwareVersion
: NULL)); : NULL));
return true; return true;
} // else // ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID } // else // ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID
// or ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID // or ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID
offset = emberAfGetInt32u(buffer, index, length); offset = emberAfGetInt32u(buffer, index, length);
index += 4; index += 4;
maxDataSize = emberAfGetInt8u(buffer, index, length); maxDataSize = emberAfGetInt8u(buffer, index, length);
index++; index++;
if (commandId == ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID) { if (commandId == ZCL_IMAGE_BLOCK_REQUEST_COMMAND_ID) {
EmberAfImageBlockRequestCallbackStruct callbackStruct; EmberAfImageBlockRequestCallbackStruct callbackStruct;
MEMSET(&callbackStruct, 0, sizeof(EmberAfImageBlockRequestCallbackStruct)); MEMSET(&callbackStruct, 0, sizeof(EmberAfImageBlockRequestCallbackStruct));
if (fieldControl & OTA_FIELD_CONTROL_NODE_EUI64_PRESENT_BIT) { if (fieldControl & OTA_FIELD_CONTROL_NODE_EUI64_PRESENT_BIT) {
MEMCOPY(callbackStruct.sourceEui, &(buffer[index]), EUI64_SIZE); MEMCOPY(callbackStruct.sourceEui, &(buffer[index]), EUI64_SIZE);
index += EUI64_SIZE; index += EUI64_SIZE;
} }
if (fieldControl & OTA_FIELD_CONTROL_MIN_BLOCK_REQUEST_PRESENT_BIT) { if (fieldControl & OTA_FIELD_CONTROL_MIN_BLOCK_REQUEST_PRESENT_BIT) {
callbackStruct.minBlockRequestPeriod = emberAfGetInt16u(buffer, index, length); callbackStruct.minBlockRequestPeriod = emberAfGetInt16u(buffer, index, length);
callbackStruct.bitmask |= EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT; callbackStruct.bitmask |= EMBER_AF_IMAGE_BLOCK_REQUEST_MIN_BLOCK_REQUEST_SUPPORTED_BY_CLIENT;
index += 2; index += 2;
} }
callbackStruct.clientEndpoint = command->apsFrame->sourceEndpoint; callbackStruct.clientEndpoint = command->apsFrame->sourceEndpoint;
callbackStruct.source = command->source; callbackStruct.source = command->source;
callbackStruct.id = &imageId; callbackStruct.id = &imageId;
callbackStruct.offset = offset; callbackStruct.offset = offset;
callbackStruct.maxDataSize = maxDataSize; callbackStruct.maxDataSize = maxDataSize;
callbackStruct.bitmask |= MIN_BLOCK_REQUEST_SERVER_SUPPORT; callbackStruct.bitmask |= MIN_BLOCK_REQUEST_SERVER_SUPPORT;
emAfOtaImageBlockRequestHandler(&callbackStruct); emAfOtaImageBlockRequestHandler(&callbackStruct);
} else { // ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID } else { // ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID
uint16_t pageSize; uint16_t pageSize;
uint16_t responseSpacing; uint16_t responseSpacing;
uint8_t status; uint8_t status;
pageSize = emberAfGetInt16u(buffer, index, length); pageSize = emberAfGetInt16u(buffer, index, length);
index += 2; index += 2;
responseSpacing = emberAfGetInt16u(buffer, index, length); responseSpacing = emberAfGetInt16u(buffer, index, length);
index += 2; index += 2;
status = emAfOtaPageRequestHandler(command->apsFrame->sourceEndpoint, status = emAfOtaPageRequestHandler(command->apsFrame->sourceEndpoint,
otaServerEndpoint, otaServerEndpoint,
&imageId, &imageId,
offset, offset,
maxDataSize, maxDataSize,
pageSize, pageSize,
responseSpacing); responseSpacing);
if (status != EMBER_ZCL_STATUS_SUCCESS) { if (status != EMBER_ZCL_STATUS_SUCCESS) {
prepareDefaultResponse(status, prepareDefaultResponse(status,
ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID); ZCL_IMAGE_PAGE_REQUEST_COMMAND_ID);
} }
} }
return true; return true;
} }
case ZCL_UPGRADE_END_REQUEST_COMMAND_ID: { case ZCL_UPGRADE_END_REQUEST_COMMAND_ID: {
uint8_t status = emberAfGetInt8u(buffer, index, length); uint8_t status = emberAfGetInt8u(buffer, index, length);
index++; index++;
if (status == EMBER_ZCL_STATUS_SUCCESS) { if (status == EMBER_ZCL_STATUS_SUCCESS) {
index += emAfOtaParseImageIdFromMessage(&imageId, index += emAfOtaParseImageIdFromMessage(&imageId,
&(buffer[index]), &(buffer[index]),
length); length);
} }
upgradeEndRequestHandler(command->source, status, &imageId); upgradeEndRequestHandler(command->source, status, &imageId);
return true; return true;
} }
case ZCL_QUERY_SPECIFIC_FILE_REQUEST_COMMAND_ID: { case ZCL_QUERY_SPECIFIC_FILE_REQUEST_COMMAND_ID: {
uint8_t* requestNodeAddress = &(buffer[index]); uint8_t* requestNodeAddress = &(buffer[index]);
uint16_t currentStackVersion; uint16_t currentStackVersion;
index += 8; // add 8 to jump over the requestNodeAddress index += 8; // add 8 to jump over the requestNodeAddress
index += emAfOtaParseImageIdFromMessage(&imageId, index += emAfOtaParseImageIdFromMessage(&imageId,
&(buffer[index]), &(buffer[index]),
length); length);
currentStackVersion = emberAfGetInt16u(buffer, index, length); currentStackVersion = emberAfGetInt16u(buffer, index, length);
index += 2; index += 2;
querySpecificFileRequestHandler(requestNodeAddress, querySpecificFileRequestHandler(requestNodeAddress,
&imageId, &imageId,
currentStackVersion); currentStackVersion);
return true; return true;
} }
} }
return false; return false;
} }
void emberAfOtaServerSendUpgradeCommandCallback(EmberNodeId dest, void emberAfOtaServerSendUpgradeCommandCallback(EmberNodeId dest,
uint8_t endpoint, uint8_t endpoint,
const EmberAfOtaImageId* id) const EmberAfOtaImageId* id)
{ {
EmberStatus status; EmberStatus status;
emberAfResponseDestination = dest; emberAfResponseDestination = dest;
emberAfResponseApsFrame.destinationEndpoint = endpoint; emberAfResponseApsFrame.destinationEndpoint = endpoint;
constructUpgradeEndResponse(id, constructUpgradeEndResponse(id,
0); // upgrade time (0 = now) 0); // upgrade time (0 = now)
status = emberAfSendResponse(); status = emberAfSendResponse();
if (EMBER_SUCCESS != status) { if (EMBER_SUCCESS != status) {
emberAfOtaBootloadClusterPrintln("OTA: failed sending upgrade response: " emberAfOtaBootloadClusterPrintln("OTA: failed sending upgrade response: "
"error 0x%x", status); "error 0x%x", status);
} }
}
void kk_ota_test123123123()
{
EmberAfOtaImageId id;
id.manufacturerId = 0x1268;
id.imageTypeId = 0x2904;
id.firmwareVersion = 15;
emberAfOtaServerSendImageNotifyCallback(0xffff,
1,
0x03,
0,
&id);
} }
void kk_ota_test111()
{
/*zclBufferSetup(ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT|ZCL_DISABLE_DEFAULT_RESPONSE_MASK, \
(ZCL_OTA_BOOTLOAD_CLUSTER_ID), \
(ZCL_IMAGE_NOTIFY_COMMAND_ID));
zclBufferAddByte(0x03);
zclBufferAddByte(0x00);
zclBufferAddWord(0x1234);
zclBufferAddWord(0x5678);
zclBufferAddInt32(0x11223344);
rpcSendCommand(0xffff,1,1,false);
emberAfCorePrintln("[kk_ota_test111] \r\n");*/
emberAfCorePrintln("[kk_ota_test111] \r\n");
static int cnt;
EmberAfOtaImageId id = emberAfOtaStorageIteratorFirstCallback();
cnt = 0;
do {
emberAfCorePrintln("**********%d********\r\n",++cnt);
emberAfCorePrintln("manufacturerId=0x%4X \r\n",id.manufacturerId);
emberAfCorePrintln("imageTypeId=0x%4X \r\n",id.imageTypeId);
emberAfCorePrintln("firmwareVersion=0x%8X \r\n",id.firmwareVersion);
id = emberAfOtaStorageIteratorNextCallback();
} while (emberAfIsOtaImageIdValid(&id));
}
static int s_ota_server_init = 0; static int s_ota_server_init = 0;
EmberAfOtaImageId kk_get_imageId(char* filename){ EmberAfOtaImageId kk_get_imageId(char* filename){
char* oatfile = NULL; char* oatfile = NULL;
EmberAfOtaImageId id = emberAfOtaStorageIteratorFirstCallback(); EmberAfOtaImageId id = emberAfOtaStorageIteratorFirstCallback();
do { do {
oatfile = emAfOtaStorageGetFilepath(&id); oatfile = emAfOtaStorageGetFilepath(&id);
if (oatfile != NULL && strcmp(oatfile, filename) == 0){ if (oatfile != NULL && strcmp(oatfile, filename) == 0){
return id; return id;
} }
id = emberAfOtaStorageIteratorNextCallback(); id = emberAfOtaStorageIteratorNextCallback();
} while (emberAfIsOtaImageIdValid(&id)); } while (emberAfIsOtaImageIdValid(&id));
return emberAfInvalidImageId; return emberAfInvalidImageId;
} }
void kk_ota_notify(char* filepath){ void kk_ota_notify(char* filepath){
if (s_ota_server_init == 0){ if (s_ota_server_init == 0){
s_ota_server_init = 1; s_ota_server_init = 1;
emberAfOtaBootloadClusterServerInitCallback(0x00); emberAfOtaBootloadClusterServerInitCallback(0x00);
} }
//add file //add file
emAfOtaReloadStorageDevice(); emAfOtaReloadStorageDevice();
//TODO:开启通知, 通过mac地址获取dest,endpoint //TODO:开启通知, 通过mac地址获取dest,endpoint
EmberNodeId dest; EmberNodeId dest;
uint8_t endpoint; uint8_t endpoint;
EmberAfOtaImageId id; EmberAfOtaImageId id;
id = kk_get_imageId(filepath); id = kk_get_imageId(filepath);
emberAfOtaServerSendUpgradeCommandCallback(dest, endpoint, &id); emberAfOtaServerSendUpgradeCommandCallback(dest, endpoint, &id);
} }
...@@ -142,10 +142,10 @@ void emAfOtaImageDelete(void) ...@@ -142,10 +142,10 @@ void emAfOtaImageDelete(void)
void emAfOtaReloadStorageDevice(void) void emAfOtaReloadStorageDevice(void)
{ {
#if defined (EMBER_AF_PLUGIN_OTA_STORAGE_POSIX_FILESYSTEM) #if defined (EMBER_AF_PLUGIN_OTA_STORAGE_POSIX_FILESYSTEM)
emAfOtaStorageClose(); emAfOtaStorageClose();
#endif #endif
emberAfOtaStorageInitCallback(); emberAfOtaStorageInitCallback();
} }
#define PRINT_BLOCK_LENGTH 64 #define PRINT_BLOCK_LENGTH 64
......
...@@ -39,18 +39,18 @@ ...@@ -39,18 +39,18 @@
EmberAfOtaImageId emAfOtaStorageGetImageIdFromHeader(const EmberAfOtaHeader* header) EmberAfOtaImageId emAfOtaStorageGetImageIdFromHeader(const EmberAfOtaHeader* header)
{ {
EmberAfOtaImageId id = INVALID_OTA_IMAGE_ID; EmberAfOtaImageId id = INVALID_OTA_IMAGE_ID;
id.manufacturerId = header->manufacturerId; id.manufacturerId = header->manufacturerId;
id.imageTypeId = header->imageTypeId; id.imageTypeId = header->imageTypeId;
id.firmwareVersion = header->firmwareVersion; id.firmwareVersion = header->firmwareVersion;
if (headerHasUpgradeFileDest(header)) { if (headerHasUpgradeFileDest(header)) {
MEMCOPY(id.deviceSpecificFileEui64, MEMCOPY(id.deviceSpecificFileEui64,
&header->upgradeFileDestination, &header->upgradeFileDestination,
EUI64_SIZE); EUI64_SIZE);
} }
return id; return id;
} }
uint16_t emGetUpgradeFileDestinationLength(uint16_t headerVersion) uint16_t emGetUpgradeFileDestinationLength(uint16_t headerVersion)
...@@ -249,7 +249,7 @@ EmberAfOtaStorageStatus emAfOtaStorageReadAllTagInfo(const EmberAfOtaImageId* id ...@@ -249,7 +249,7 @@ EmberAfOtaStorageStatus emAfOtaStorageReadAllTagInfo(const EmberAfOtaImageId* id
bool emberAfIsOtaImageIdValid(const EmberAfOtaImageId* idToCompare) bool emberAfIsOtaImageIdValid(const EmberAfOtaImageId* idToCompare)
{ {
return (0 != MEMPGMCOMPARE(idToCompare, return (0 != MEMPGMCOMPARE(idToCompare,
&emberAfInvalidImageId, &emberAfInvalidImageId,
sizeof(EmberAfOtaImageId))); sizeof(EmberAfOtaImageId)));
} }
...@@ -40,18 +40,18 @@ ...@@ -40,18 +40,18 @@
#define TAG_OVERHEAD (2 + 4) // 2 bytes for the tag ID, 4 bytes for the length #define TAG_OVERHEAD (2 + 4) // 2 bytes for the tag ID, 4 bytes for the length
#define isValidHeaderVersion(headerVersion) \ #define isValidHeaderVersion(headerVersion) \
((headerVersion == OTA_HEADER_VERSION_ZIGBEE) || (headerVersion == OTA_HEADER_VERSION_THREAD)) ((headerVersion == OTA_HEADER_VERSION_ZIGBEE) || (headerVersion == OTA_HEADER_VERSION_THREAD))
#define SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK 0x0001 #define SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK 0x0001
#define DEVICE_SPECIFIC_FILE_PRESENT_MASK 0x0002 #define DEVICE_SPECIFIC_FILE_PRESENT_MASK 0x0002
#define HARDWARE_VERSIONS_PRESENT_MASK 0x0004 #define HARDWARE_VERSIONS_PRESENT_MASK 0x0004
#define headerHasSecurityCredentials(header) \ #define headerHasSecurityCredentials(header) \
((header)->fieldControl & SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK) ((header)->fieldControl & SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK)
#define headerHasUpgradeFileDest(header) \ #define headerHasUpgradeFileDest(header) \
((header)->fieldControl & DEVICE_SPECIFIC_FILE_PRESENT_MASK) ((header)->fieldControl & DEVICE_SPECIFIC_FILE_PRESENT_MASK)
#define headerHasHardwareVersions(header) \ #define headerHasHardwareVersions(header) \
((header)->fieldControl & HARDWARE_VERSIONS_PRESENT_MASK) ((header)->fieldControl & HARDWARE_VERSIONS_PRESENT_MASK)
// This size does NOT include the tag overhead. // This size does NOT include the tag overhead.
#define SIGNATURE_TAG_DATA_SIZE (EUI64_SIZE + EMBER_SIGNATURE_SIZE) #define SIGNATURE_TAG_DATA_SIZE (EUI64_SIZE + EMBER_SIGNATURE_SIZE)
...@@ -63,11 +63,11 @@ ...@@ -63,11 +63,11 @@
#define INVALID_EUI64 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } #define INVALID_EUI64 { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
#define INVALID_OTA_IMAGE_ID \ #define INVALID_OTA_IMAGE_ID \
{ INVALID_MANUFACTURER_ID, \ { INVALID_MANUFACTURER_ID, \
INVALID_DEVICE_ID, \ INVALID_DEVICE_ID, \
INVALID_FIRMWARE_VERSION, \ INVALID_FIRMWARE_VERSION, \
INVALID_EUI64, \ INVALID_EUI64, \
} }
#define INVALID_SLOT (uint32_t)-1 #define INVALID_SLOT (uint32_t)-1
......
...@@ -64,12 +64,12 @@ static char* tempStorageFilepath = NULL; ...@@ -64,12 +64,12 @@ static char* tempStorageFilepath = NULL;
static const char* tempStorageFile = "temporary-storage.ota"; static const char* tempStorageFile = "temporary-storage.ota";
typedef struct { typedef struct {
EmberAfOtaHeader* header; EmberAfOtaHeader* header;
char* filepath; char* filepath;
const char* filenameStart; // ptr to data in 'filepath' const char* filenameStart; // ptr to data in 'filepath'
struct OtaImage* next; struct OtaImage* next;
struct OtaImage* prev; struct OtaImage* prev;
off_t fileSize; off_t fileSize;
} OtaImage; } OtaImage;
static OtaImage* imageListFirst = NULL; static OtaImage* imageListFirst = NULL;
...@@ -79,26 +79,26 @@ static uint8_t imageCount = 0; ...@@ -79,26 +79,26 @@ static uint8_t imageCount = 0;
#define OTA_MAX_FILENAME_LENGTH 1000 #define OTA_MAX_FILENAME_LENGTH 1000
static const uint8_t otaFileMagicNumberBytes[] = { static const uint8_t otaFileMagicNumberBytes[] = {
(uint8_t)(OTA_FILE_MAGIC_NUMBER), (uint8_t)(OTA_FILE_MAGIC_NUMBER),
(uint8_t)(OTA_FILE_MAGIC_NUMBER >> 8), (uint8_t)(OTA_FILE_MAGIC_NUMBER >> 8),
(uint8_t)(OTA_FILE_MAGIC_NUMBER >> 16), (uint8_t)(OTA_FILE_MAGIC_NUMBER >> 16),
(uint8_t)(OTA_FILE_MAGIC_NUMBER >> 24), (uint8_t)(OTA_FILE_MAGIC_NUMBER >> 24),
}; };
#define ALWAYS_PRESENT_MASK 0xFFFF #define ALWAYS_PRESENT_MASK 0xFFFF
enum FieldType{ enum FieldType{
INVALID_FIELD = 0, INVALID_FIELD = 0,
INTEGER_FIELD = 1, INTEGER_FIELD = 1,
BYTE_ARRAY_FIELD = 2, BYTE_ARRAY_FIELD = 2,
STRING_FIELD = 3, STRING_FIELD = 3,
}; };
typedef struct { typedef struct {
const char* name; const char* name;
const enum FieldType type; const enum FieldType type;
const uint16_t length; const uint16_t length;
const uint16_t maskForOptionalField; const uint16_t maskForOptionalField;
} EmberAfOtaHeaderFieldDefinition; } EmberAfOtaHeaderFieldDefinition;
// This global is used to define what is in the OTA header and help map that // This global is used to define what is in the OTA header and help map that
...@@ -106,54 +106,54 @@ typedef struct { ...@@ -106,54 +106,54 @@ typedef struct {
// emGetOtaHeaderFieldDefinition() to get the field definitions. Do not use this // emGetOtaHeaderFieldDefinition() to get the field definitions. Do not use this
// array directly. // array directly.
const static EmberAfOtaHeaderFieldDefinition otaHeaderFieldDefinitions[] = { const static EmberAfOtaHeaderFieldDefinition otaHeaderFieldDefinitions[] = {
{ "Magic Number", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK }, { "Magic Number", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK },
{ "Header Version", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Header Version", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
// The fields returned above SHALL not depend on otaHeaderVersion, because // The fields returned above SHALL not depend on otaHeaderVersion, because
// when reading in the header, we must be able to determine the location of // when reading in the header, we must be able to determine the location of
// the header version // the header version
{ "Header Length", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Header Length", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
{ "Field Control", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Field Control", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
{ "Manufacturer ID", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Manufacturer ID", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
{ "Image Type", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Image Type", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
{ "Firmware Version", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK }, { "Firmware Version", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK },
{ "Zigbee Stack Version", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK }, { "Zigbee Stack Version", INTEGER_FIELD, 2, ALWAYS_PRESENT_MASK },
{ "Header String", STRING_FIELD, 32, ALWAYS_PRESENT_MASK }, { "Header String", STRING_FIELD, 32, ALWAYS_PRESENT_MASK },
{ "Total Image Size", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK }, { "Total Image Size", INTEGER_FIELD, 4, ALWAYS_PRESENT_MASK },
{ "Security Credentials", INTEGER_FIELD, 1, SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK }, { "Security Credentials", INTEGER_FIELD, 1, SECURITY_CREDENTIAL_VERSION_FIELD_PRESENT_MASK },
{ "Upgrade File Destination", BYTE_ARRAY_FIELD, EUI64_SIZE, DEVICE_SPECIFIC_FILE_PRESENT_MASK }, { "Upgrade File Destination", BYTE_ARRAY_FIELD, EUI64_SIZE, DEVICE_SPECIFIC_FILE_PRESENT_MASK },
{ "Minimum Hardware Version", INTEGER_FIELD, 2, HARDWARE_VERSIONS_PRESENT_MASK }, { "Minimum Hardware Version", INTEGER_FIELD, 2, HARDWARE_VERSIONS_PRESENT_MASK },
{ "Maximum Hardware Version", INTEGER_FIELD, 2, HARDWARE_VERSIONS_PRESENT_MASK }, { "Maximum Hardware Version", INTEGER_FIELD, 2, HARDWARE_VERSIONS_PRESENT_MASK },
// Tag Data information omitted // Tag Data information omitted
{ NULL, INVALID_FIELD, 0, 0 }, { NULL, INVALID_FIELD, 0, 0 },
}; };
typedef enum { typedef enum {
// Don't define all index numbers so the compiler // Don't define all index numbers so the compiler
// automatically numbers them. We just define the first. // automatically numbers them. We just define the first.
MAGIC_NUMBER_INDEX = 0, MAGIC_NUMBER_INDEX = 0,
HEADER_VERSION_INDEX, HEADER_VERSION_INDEX,
HEADER_LENGTH_INDEX, HEADER_LENGTH_INDEX,
FIELD_CONTROL_INDEX, FIELD_CONTROL_INDEX,
MANUFACTURER_ID_INDEX, MANUFACTURER_ID_INDEX,
IMAGE_TYPE_INDEX, IMAGE_TYPE_INDEX,
FIRMWARE_VERSION_INDEX, FIRMWARE_VERSION_INDEX,
ZIGBEE_STACK_VERSION_INDEX, ZIGBEE_STACK_VERSION_INDEX,
HEADER_STRING_INDEX, HEADER_STRING_INDEX,
TOTAL_IMAGE_SIZE_INDEX, TOTAL_IMAGE_SIZE_INDEX,
SECURITY_CREDENTIALS_INDEX, SECURITY_CREDENTIALS_INDEX,
UPGRADE_FILE_DESTINATION_INDEX, UPGRADE_FILE_DESTINATION_INDEX,
MINIMUM_HARDWARE_VERSION_INDEX, MINIMUM_HARDWARE_VERSION_INDEX,
MAXIMUM_HARDWARE_VERSION_INDEX, MAXIMUM_HARDWARE_VERSION_INDEX,
// This defines the maximum number of entries and refers to the invalid entry // This defines the maximum number of entries and refers to the invalid entry
INVALID_FIELD_INDEX, INVALID_FIELD_INDEX,
} EmberAfOtaBootloadFileHeaderFieldIndex_t; } EmberAfOtaBootloadFileHeaderFieldIndex_t;
#define FIELD_INDEX_MAX INVALID_FIELD_INDEX #define FIELD_INDEX_MAX INVALID_FIELD_INDEX
typedef struct { typedef struct {
void* location; void* location;
bool found; bool found;
} EmberAfOtaHeaderFieldLocation; } EmberAfOtaHeaderFieldLocation;
static EmberAfOtaHeaderFieldLocation otaHeaderFieldLocations[FIELD_INDEX_MAX]; static EmberAfOtaHeaderFieldLocation otaHeaderFieldLocations[FIELD_INDEX_MAX];
...@@ -170,9 +170,9 @@ static OtaImage* iterator = NULL; ...@@ -170,9 +170,9 @@ static OtaImage* iterator = NULL;
#endif #endif
static EmAfOtaStorageLinuxConfig config = { static EmAfOtaStorageLinuxConfig config = {
false, // memoryDebug true, // memoryDebug
false, // fileDebug true, // fileDebug
false, // fieldDebug true, // fieldDebug
true, // ignoreFilesWithUnderscorePrefix true, // ignoreFilesWithUnderscorePrefix
true, // printFileDiscoveryOrRemoval true, // printFileDiscoveryOrRemoval
NULL, // fileAddedHandler NULL, // fileAddedHandler
...@@ -235,493 +235,493 @@ static bool createStorageDirectory = true; ...@@ -235,493 +235,493 @@ static bool createStorageDirectory = true;
// loads a single file into its header cache. // loads a single file into its header cache.
EmberAfOtaStorageStatus emAfOtaSetStorageDevice(const void* device) EmberAfOtaStorageStatus emAfOtaSetStorageDevice(const void* device)
{ {
if (initDone) { if (initDone) {
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (device == NULL) { if (device == NULL) {
// We interpet this to mean, don't use a storage directory. // We interpet this to mean, don't use a storage directory.
// This option is useful when creating files via the PC // This option is useful when creating files via the PC
// tool. // tool.
createStorageDirectory = false; createStorageDirectory = false;
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
const char* directoryOrFile = (const char*)device; const char* directoryOrFile = (const char*)device;
int length = strnlen(directoryOrFile, MAX_FILEPATH_LENGTH + 1); int length = strnlen(directoryOrFile, MAX_FILEPATH_LENGTH + 1);
if (MAX_FILEPATH_LENGTH < length) { if (MAX_FILEPATH_LENGTH < length) {
error("Storage directory path too long (max = %d)!\n", error("Storage directory path too long (max = %d)!\n",
MAX_FILEPATH_LENGTH); MAX_FILEPATH_LENGTH);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
// Add 1 for '\0' and 1 for '/'. This may or may not be necessary // Add 1 for '\0' and 1 for '/'. This may or may not be necessary
// because the path already has it or it is only a file. // because the path already has it or it is only a file.
storageDevice = myMalloc(length + 2, storageDevice = myMalloc(length + 2,
"emAfSetStorageDevice(): storageDevice"); "emAfSetStorageDevice(): storageDevice");
if (storageDevice == NULL) { if (storageDevice == NULL) {
error("Could not allocate %d bytes!\n", length); error("Could not allocate %d bytes!\n", length);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (directoryOrFile[length - 1] == '/') { if (directoryOrFile[length - 1] == '/') {
// We don't want to copy the '/' yet, since stat() will // We don't want to copy the '/' yet, since stat() will
// complain if we pass it in. // complain if we pass it in.
length--; length--;
} }
MEMSET(storageDevice, 0, length + 2); MEMSET(storageDevice, 0, length + 2);
strncpy(storageDevice, directoryOrFile, length); strncpy(storageDevice, directoryOrFile, length);
struct stat statInfo; struct stat statInfo;
int returnValue = stat(storageDevice, &statInfo); int returnValue = stat(storageDevice, &statInfo);
debug(config.fileDebug, debug(config.fileDebug,
"Checking for existence of '%s'\n", "Checking for existence of '%s'\n",
storageDevice); storageDevice);
if (returnValue != 0) { if (returnValue != 0) {
error("Could not read storage device '%s'. %s\n", error("Could not read storage device '%s'. %s\n",
directoryOrFile, directoryOrFile,
strerror(errno)); strerror(errno));
myFree(storageDevice); myFree(storageDevice);
storageDevice = NULL; storageDevice = NULL;
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (S_ISDIR(statInfo.st_mode)) { if (S_ISDIR(statInfo.st_mode)) {
storageDeviceIsDirectory = true; storageDeviceIsDirectory = true;
storageDevice[length] = '/'; storageDevice[length] = '/';
} }
debug(config.fileDebug, "Storage device set to '%s'.\n", storageDevice); debug(config.fileDebug, "Storage device set to '%s'.\n", storageDevice);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
void emAfOtaStorageGetConfig(EmAfOtaStorageLinuxConfig* currentConfig) void emAfOtaStorageGetConfig(EmAfOtaStorageLinuxConfig* currentConfig)
{ {
memcpy(currentConfig, &config, sizeof(EmAfOtaStorageLinuxConfig)); memcpy(currentConfig, &config, sizeof(EmAfOtaStorageLinuxConfig));
} }
void emAfOtaStorageSetConfig(const EmAfOtaStorageLinuxConfig* newConfig) void emAfOtaStorageSetConfig(const EmAfOtaStorageLinuxConfig* newConfig)
{ {
memcpy(&config, newConfig, sizeof(EmAfOtaStorageLinuxConfig)); memcpy(&config, newConfig, sizeof(EmAfOtaStorageLinuxConfig));
} }
EmberAfOtaStorageStatus emberAfOtaStorageInitCallback(void) EmberAfOtaStorageStatus emberAfOtaStorageInitCallback(void)
{ {
if (initDone) { if (initDone) {
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
if (config.fileDebug) { if (config.fileDebug) {
char cwd[MAX_FILEPATH_LENGTH]; char cwd[MAX_FILEPATH_LENGTH];
debug(config.fileDebug, "Current Working Directory: %s\n", debug(config.fileDebug, "Current Working Directory: %s\n",
(NULL == getcwd(cwd, MAX_FILEPATH_LENGTH) (NULL == getcwd(cwd, MAX_FILEPATH_LENGTH)
? "UNKNOWN!" ? "UNKNOWN!"
: cwd)); : cwd));
} }
EmberAfOtaStorageStatus status; EmberAfOtaStorageStatus status;
if (storageDevice == NULL) { if (storageDevice == NULL) {
if (createStorageDirectory) { if (createStorageDirectory) {
status = createDefaultStorageDirectory(); status = createDefaultStorageDirectory();
if (status != EMBER_AF_OTA_STORAGE_SUCCESS) { if (status != EMBER_AF_OTA_STORAGE_SUCCESS) {
return status; return status;
} }
} else { } else {
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
} }
if (storageDeviceIsDirectory) { if (storageDeviceIsDirectory) {
status = initImageDirectory(); status = initImageDirectory();
} else { } else {
OtaImage* newImage = addImageFileToList(storageDevice, true); OtaImage* newImage = addImageFileToList(storageDevice, true);
if (config.fileAddedHandler != NULL if (config.fileAddedHandler != NULL
&& newImage != NULL) { && newImage != NULL) {
(config.fileAddedHandler)(newImage->header); (config.fileAddedHandler)(newImage->header);
} }
status = (NULL == newImage status = (NULL == newImage
? EMBER_AF_OTA_STORAGE_ERROR ? EMBER_AF_OTA_STORAGE_ERROR
: EMBER_AF_OTA_STORAGE_SUCCESS); : EMBER_AF_OTA_STORAGE_SUCCESS);
} }
iterator = NULL; // Must be initialized via iterator = NULL; // Must be initialized via
// otaStorageIteratorReset() // otaStorageIteratorReset()
if (status == EMBER_AF_OTA_STORAGE_SUCCESS) { if (status == EMBER_AF_OTA_STORAGE_SUCCESS) {
initDone = true; initDone = true;
} }
return status; return status;
} }
void emAfOtaStorageClose(void) void emAfOtaStorageClose(void)
{ {
OtaImage* ptr = imageListLast; OtaImage* ptr = imageListLast;
while (ptr != NULL) { while (ptr != NULL) {
OtaImage* current = ptr; OtaImage* current = ptr;
ptr = (OtaImage*)ptr->prev; ptr = (OtaImage*)ptr->prev;
freeOtaImage(current); freeOtaImage(current);
} }
imageListLast = NULL; imageListLast = NULL;
imageListFirst = NULL; imageListFirst = NULL;
if (storageDevice != NULL) { if (storageDevice != NULL) {
myFree(storageDevice); myFree(storageDevice);
storageDevice = NULL; storageDevice = NULL;
} }
if (tempStorageFilepath != NULL) { if (tempStorageFilepath != NULL) {
myFree(tempStorageFilepath); myFree(tempStorageFilepath);
tempStorageFilepath = NULL; tempStorageFilepath = NULL;
} }
initDone = false; initDone = false;
imageCount = 0; imageCount = 0;
} }
uint8_t emberAfOtaStorageGetCountCallback(void) uint8_t emberAfOtaStorageGetCountCallback(void)
{ {
return imageCount; return imageCount;
} }
EmberAfOtaImageId emberAfOtaStorageSearchCallback(uint16_t manufacturerId, EmberAfOtaImageId emberAfOtaStorageSearchCallback(uint16_t manufacturerId,
uint16_t manufacturerDeviceId, uint16_t manufacturerDeviceId,
const uint16_t* hardwareVersion) const uint16_t* hardwareVersion)
{ {
EmberAfOtaImageId id = { EmberAfOtaImageId id = {
manufacturerId, manufacturerId,
manufacturerDeviceId, manufacturerDeviceId,
INVALID_FIRMWARE_VERSION, INVALID_FIRMWARE_VERSION,
INVALID_EUI64, INVALID_EUI64,
}; };
OtaImage* image = imageSearchInternal(&id); OtaImage* image = imageSearchInternal(&id);
if (image == NULL) { if (image == NULL) {
return emberAfInvalidImageId; return emberAfInvalidImageId;
} }
// We assume that if there is no hardware version information in the header // We assume that if there is no hardware version information in the header
// then it can be used on ANY hardware version. The behavior of the server // then it can be used on ANY hardware version. The behavior of the server
// in this case is not spelled out by the specification. // in this case is not spelled out by the specification.
if (hardwareVersion != NULL if (hardwareVersion != NULL
&& headerHasHardwareVersions(image->header) && headerHasHardwareVersions(image->header)
&& !(*hardwareVersion >= image->header->minimumHardwareVersion && !(*hardwareVersion >= image->header->minimumHardwareVersion
&& *hardwareVersion <= image->header->maximumHardwareVersion)) { && *hardwareVersion <= image->header->maximumHardwareVersion)) {
return emberAfInvalidImageId; return emberAfInvalidImageId;
} }
id.firmwareVersion = image->header->firmwareVersion; id.firmwareVersion = image->header->firmwareVersion;
return id; return id;
} }
const char* emAfOtaStorageGetFilepath(const EmberAfOtaImageId* id) const char* emAfOtaStorageGetFilepath(const EmberAfOtaImageId* id)
{ {
OtaImage* image = findImageById(id); OtaImage* image = findImageById(id);
if (image == NULL) { if (image == NULL) {
return NULL; return NULL;
} }
return image->filepath; return image->filepath;
} }
EmberAfOtaImageId emberAfOtaStorageIteratorFirstCallback(void) EmberAfOtaImageId emberAfOtaStorageIteratorFirstCallback(void)
{ {
iterator = imageListFirst; iterator = imageListFirst;
return getIteratorImageId(); return getIteratorImageId();
} }
EmberAfOtaImageId emberAfOtaStorageIteratorNextCallback(void) EmberAfOtaImageId emberAfOtaStorageIteratorNextCallback(void)
{ {
if (iterator != NULL) { if (iterator != NULL) {
iterator = (OtaImage*)iterator->next; iterator = (OtaImage*)iterator->next;
} }
return getIteratorImageId(); return getIteratorImageId();
} }
EmberAfOtaStorageStatus emberAfOtaStorageReadImageDataCallback(const EmberAfOtaImageId* id, EmberAfOtaStorageStatus emberAfOtaStorageReadImageDataCallback(const EmberAfOtaImageId* id,
uint32_t offset, uint32_t offset,
uint32_t length, uint32_t length,
uint8_t* returnData, uint8_t* returnData,
uint32_t* returnedLength) uint32_t* returnedLength)
{ {
OtaImage* image = imageSearchInternal(id); OtaImage* image = imageSearchInternal(id);
if (image == NULL) { if (image == NULL) {
error("No such Image (Mfg ID: 0x%04X, Image ID: 0x%04X, Version: 0x%08X\n", error("No such Image (Mfg ID: 0x%04X, Image ID: 0x%04X, Version: 0x%08X\n",
id->manufacturerId, id->manufacturerId,
id->imageTypeId, id->imageTypeId,
id->firmwareVersion); id->firmwareVersion);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
FILE* fileHandle = fopen(image->filepath, "rb"); FILE* fileHandle = fopen(image->filepath, "rb");
if (fileHandle == NULL) { if (fileHandle == NULL) {
error("Failed to open file '%s' for reading: %s\n", error("Failed to open file '%s' for reading: %s\n",
image->filenameStart, image->filenameStart,
strerror(errno)); strerror(errno));
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (0 != fseek(fileHandle, offset, SEEK_SET)) { if (0 != fseek(fileHandle, offset, SEEK_SET)) {
error("Failed to seek in file '%s' to offset %d\n", error("Failed to seek in file '%s' to offset %d\n",
image->filenameStart, image->filenameStart,
offset); offset);
fclose(fileHandle); fclose(fileHandle);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
*returnedLength = fread(returnData, 1, length, fileHandle); *returnedLength = fread(returnData, 1, length, fileHandle);
fclose(fileHandle); fclose(fileHandle);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
EmberAfOtaStorageStatus emAfOtaStorageCreateImage(EmberAfOtaHeader* header, EmberAfOtaStorageStatus emAfOtaStorageCreateImage(EmberAfOtaHeader* header,
const char* filename) const char* filename)
{ {
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
FILE* fileHandle = fopen(filename, "wb"); FILE* fileHandle = fopen(filename, "wb");
if (fileHandle == NULL) { if (fileHandle == NULL) {
error("Could not open file '%s' for writing: %s\n", error("Could not open file '%s' for writing: %s\n",
filename, filename,
strerror(errno)); strerror(errno));
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
uint8_t buffer[OTA_MAXIMUM_HEADER_LENGTH]; uint8_t buffer[OTA_MAXIMUM_HEADER_LENGTH];
uint8_t* bufferPtr = buffer; uint8_t* bufferPtr = buffer;
memcpy(bufferPtr, otaFileMagicNumberBytes, 4); memcpy(bufferPtr, otaFileMagicNumberBytes, 4);
bufferPtr += 4; bufferPtr += 4;
bufferPtr[0] = (uint8_t)(header->headerVersion); bufferPtr[0] = (uint8_t)(header->headerVersion);
bufferPtr[1] = (uint8_t)(header->headerVersion >> 8); bufferPtr[1] = (uint8_t)(header->headerVersion >> 8);
bufferPtr += 2; bufferPtr += 2;
mapHeaderFieldDefinitionToDataStruct(header); mapHeaderFieldDefinitionToDataStruct(header);
header->headerLength = calculateOtaFileHeaderLength(header); header->headerLength = calculateOtaFileHeaderLength(header);
header->imageSize += header->headerLength; header->imageSize += header->headerLength;
EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex = HEADER_LENGTH_INDEX; EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex = HEADER_LENGTH_INDEX;
while (fieldIndex < FIELD_INDEX_MAX) { while (fieldIndex < FIELD_INDEX_MAX) {
const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(header->headerVersion, fieldIndex); const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(header->headerVersion, fieldIndex);
debug(config.memoryDebug, debug(config.memoryDebug,
"Writing Header Field: %s, bufferPtr: 0x%08X\n", "Writing Header Field: %s, bufferPtr: 0x%08X\n",
definition->name, definition->name,
(unsigned int)bufferPtr); (unsigned int)bufferPtr);
bufferPtr = writeHeaderDataToBuffer(fieldIndex, bufferPtr = writeHeaderDataToBuffer(fieldIndex,
header->headerVersion, header->headerVersion,
bufferPtr); bufferPtr);
// I have run into stack corruption bugs in the past so this // I have run into stack corruption bugs in the past so this
// is to try and catch any new issues. // is to try and catch any new issues.
assert(bufferPtr - buffer <= OTA_MAXIMUM_HEADER_LENGTH); assert(bufferPtr - buffer <= OTA_MAXIMUM_HEADER_LENGTH);
fieldIndex++; fieldIndex++;
} }
unmapHeaderFieldDefinitions(); unmapHeaderFieldDefinitions();
size_t written = fwrite(buffer, 1, header->headerLength, fileHandle); size_t written = fwrite(buffer, 1, header->headerLength, fileHandle);
if (written != header->headerLength) { if (written != header->headerLength) {
error("Wrote only %d bytes but expected to write %d.\n", error("Wrote only %d bytes but expected to write %d.\n",
written, written,
header->headerLength); header->headerLength);
fclose(fileHandle); fclose(fileHandle);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
fflush(fileHandle); fflush(fileHandle);
fclose(fileHandle); fclose(fileHandle);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
EmberAfOtaStorageStatus emAfOtaStorageAddImageFile(const char* filename) EmberAfOtaStorageStatus emAfOtaStorageAddImageFile(const char* filename)
{ {
return (NULL == addImageFileToList(filename, return (NULL == addImageFileToList(filename,
false) // print image info? false) // print image info?
? EMBER_AF_OTA_STORAGE_ERROR ? EMBER_AF_OTA_STORAGE_ERROR
: EMBER_AF_OTA_STORAGE_SUCCESS); : EMBER_AF_OTA_STORAGE_SUCCESS);
} }
EmberAfOtaStorageStatus emberAfOtaStorageDeleteImageCallback(const EmberAfOtaImageId* id) EmberAfOtaStorageStatus emberAfOtaStorageDeleteImageCallback(const EmberAfOtaImageId* id)
{ {
OtaImage* image = imageSearchInternal(id); OtaImage* image = imageSearchInternal(id);
// EMAPPFWKV2-1169: if we find the image, we delete it and return success. // EMAPPFWKV2-1169: if we find the image, we delete it and return success.
// If we don't have the image, then we are ready to receive another one, // If we don't have the image, then we are ready to receive another one,
// so we should return success. // so we should return success.
if (image == NULL) { if (image == NULL) {
if (config.printFileDiscoveryOrRemoval) { if (config.printFileDiscoveryOrRemoval) {
note("No such Image (Mfg ID: 0x%04X, Image ID: 0x%04X, Version: 0x%08X\n", note("No such Image (Mfg ID: 0x%04X, Image ID: 0x%04X, Version: 0x%08X\n",
id->manufacturerId, id->manufacturerId,
id->imageTypeId, id->imageTypeId,
id->firmwareVersion); id->firmwareVersion);
} }
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
if (config.printFileDiscoveryOrRemoval) { if (config.printFileDiscoveryOrRemoval) {
note("Image '%s' removed from storage list. NOT deleted from disk.\n", note("Image '%s' removed from storage list. NOT deleted from disk.\n",
image->filenameStart); image->filenameStart);
} }
removeImage(image); removeImage(image);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
// Although the call is to "clear" temp data we actually end up allocating // Although the call is to "clear" temp data we actually end up allocating
// space for the filename and creating an empty file. // space for the filename and creating an empty file.
EmberAfOtaStorageStatus emberAfOtaStorageClearTempDataCallback(void) EmberAfOtaStorageStatus emberAfOtaStorageClearTempDataCallback(void)
{ {
EmberAfOtaStorageStatus status = EMBER_AF_OTA_STORAGE_ERROR; EmberAfOtaStorageStatus status = EMBER_AF_OTA_STORAGE_ERROR;
FILE* fileHandle = NULL; FILE* fileHandle = NULL;
if (!storageDeviceIsDirectory) { if (!storageDeviceIsDirectory) {
error("Cannot create temp. OTA data because storage device is a file, not a directory.\n"); error("Cannot create temp. OTA data because storage device is a file, not a directory.\n");
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (storageDevice == NULL) { if (storageDevice == NULL) {
error("No storage device defined!"); error("No storage device defined!");
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (tempStorageFilepath == NULL) { if (tempStorageFilepath == NULL) {
// Add 1 to make sure we have room for a NULL terminating character // Add 1 to make sure we have room for a NULL terminating character
int tempFilepathLength = (strlen(storageDevice) int tempFilepathLength = (strlen(storageDevice)
+ strlen(tempStorageFile) + 1); + strlen(tempStorageFile) + 1);
if (tempFilepathLength > MAX_FILEPATH_LENGTH) { if (tempFilepathLength > MAX_FILEPATH_LENGTH) {
goto clearTempDataCallbackDone; goto clearTempDataCallbackDone;
} }
tempStorageFilepath = myMalloc(tempFilepathLength, tempStorageFilepath = myMalloc(tempFilepathLength,
"otaStorageCreateTempData(): tempFilepath"); "otaStorageCreateTempData(): tempFilepath");
if (tempStorageFilepath == NULL) { if (tempStorageFilepath == NULL) {
goto clearTempDataCallbackDone; goto clearTempDataCallbackDone;
} }
snprintf(tempStorageFilepath, snprintf(tempStorageFilepath,
tempFilepathLength, tempFilepathLength,
"%s%s", "%s%s",
storageDevice, storageDevice,
tempStorageFile); tempStorageFile);
} }
OtaImage* image = findImageByFilename(tempStorageFilepath); OtaImage* image = findImageByFilename(tempStorageFilepath);
if (image) { if (image) {
removeImage(image); removeImage(image);
} }
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
fileHandle = fopen(tempStorageFilepath, fileHandle = fopen(tempStorageFilepath,
"wb"); // truncate the file to zero length "wb"); // truncate the file to zero length
if (fileHandle == NULL) { if (fileHandle == NULL) {
error("Could not open temporary file '%s' for writing: %s\n", error("Could not open temporary file '%s' for writing: %s\n",
tempStorageFilepath, tempStorageFilepath,
strerror(errno)); strerror(errno));
} else { } else {
status = EMBER_AF_OTA_STORAGE_SUCCESS; status = EMBER_AF_OTA_STORAGE_SUCCESS;
} }
clearTempDataCallbackDone: clearTempDataCallbackDone:
// tempStorageFilepath is used throughout the life of the storage // tempStorageFilepath is used throughout the life of the storage
// device, so we only need to free it if there was an error, // device, so we only need to free it if there was an error,
// or when the storage device changed. We expect we can // or when the storage device changed. We expect we can
// only change the storage device if we first call emAfOtaStorageClose() // only change the storage device if we first call emAfOtaStorageClose()
// which frees the tempStorageFilepath data as well. // which frees the tempStorageFilepath data as well.
if ((status != EMBER_AF_OTA_STORAGE_SUCCESS) if ((status != EMBER_AF_OTA_STORAGE_SUCCESS)
&& tempStorageFilepath) { && tempStorageFilepath) {
myFree(tempStorageFilepath); myFree(tempStorageFilepath);
tempStorageFilepath = NULL; tempStorageFilepath = NULL;
} }
if (fileHandle) { if (fileHandle) {
fclose(fileHandle); fclose(fileHandle);
} }
return status; return status;
} }
EmberAfOtaStorageStatus emberAfOtaStorageWriteTempDataCallback(uint32_t offset, EmberAfOtaStorageStatus emberAfOtaStorageWriteTempDataCallback(uint32_t offset,
uint32_t length, uint32_t length,
const uint8_t* data) const uint8_t* data)
{ {
if (tempStorageFilepath == NULL) { if (tempStorageFilepath == NULL) {
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
return writeRawData(offset, tempStorageFilepath, length, data); return writeRawData(offset, tempStorageFilepath, length, data);
} }
EmberAfOtaStorageStatus emberAfOtaStorageCheckTempDataCallback(uint32_t* returnOffset, EmberAfOtaStorageStatus emberAfOtaStorageCheckTempDataCallback(uint32_t* returnOffset,
uint32_t* returnTotalSize, uint32_t* returnTotalSize,
EmberAfOtaImageId* returnOtaImageId) EmberAfOtaImageId* returnOtaImageId)
{ {
OtaImage* image; OtaImage* image;
if (tempStorageFilepath == NULL) { if (tempStorageFilepath == NULL) {
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
image = addImageFileToList(tempStorageFilepath, true); image = addImageFileToList(tempStorageFilepath, true);
if (image == NULL) { if (image == NULL) {
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
*returnTotalSize = image->header->imageSize; *returnTotalSize = image->header->imageSize;
*returnOffset = image->fileSize; *returnOffset = image->fileSize;
MEMSET(returnOtaImageId, 0, sizeof(EmberAfOtaImageId)); MEMSET(returnOtaImageId, 0, sizeof(EmberAfOtaImageId));
*returnOtaImageId = emAfOtaStorageGetImageIdFromHeader(image->header); *returnOtaImageId = emAfOtaStorageGetImageIdFromHeader(image->header);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
EmberAfOtaStorageStatus emAfOtaStorageAppendImageData(const char* filename, EmberAfOtaStorageStatus emAfOtaStorageAppendImageData(const char* filename,
uint32_t length, uint32_t length,
const uint8_t* data) const uint8_t* data)
{ {
OtaImage* image = findImageByFilename(filename); OtaImage* image = findImageByFilename(filename);
if (image != NULL) { if (image != NULL) {
// The file is already in use by the storage device, so it cannot // The file is already in use by the storage device, so it cannot
// be modified. // be modified.
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
return writeRawData(APPEND_OFFSET, filename, length, data); return writeRawData(APPEND_OFFSET, filename, length, data);
} }
int remainingAllocations(void) int remainingAllocations(void)
{ {
return allocations; return allocations;
} }
EmberAfOtaStorageStatus emberAfOtaStorageGetFullHeaderCallback(const EmberAfOtaImageId* id, EmberAfOtaStorageStatus emberAfOtaStorageGetFullHeaderCallback(const EmberAfOtaImageId* id,
EmberAfOtaHeader* returnData) EmberAfOtaHeader* returnData)
{ {
OtaImage* image = imageSearchInternal(id); OtaImage* image = imageSearchInternal(id);
if (image == NULL) { if (image == NULL) {
return false; return false;
} }
MEMCOPY(returnData, image->header, sizeof(EmberAfOtaHeader)); MEMCOPY(returnData, image->header, sizeof(EmberAfOtaHeader));
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
uint32_t emberAfOtaStorageGetTotalImageSizeCallback(const EmberAfOtaImageId* id) uint32_t emberAfOtaStorageGetTotalImageSizeCallback(const EmberAfOtaImageId* id)
{ {
OtaImage* image = imageSearchInternal(id); OtaImage* image = imageSearchInternal(id);
if (image == NULL) { if (image == NULL) {
return 0; return 0;
} }
return image->fileSize; return image->fileSize;
} }
EmberAfOtaStorageStatus emberAfOtaStorageFinishDownloadCallback(uint32_t offset) EmberAfOtaStorageStatus emberAfOtaStorageFinishDownloadCallback(uint32_t offset)
{ {
// Not used. The OS filesystem keeps track of the latest download offset. // Not used. The OS filesystem keeps track of the latest download offset.
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
void emAfOtaStorageInfoPrint(void) void emAfOtaStorageInfoPrint(void)
{ {
note("Storage Module: OTA POSIX Filesystem Storage Module\n"); note("Storage Module: OTA POSIX Filesystem Storage Module\n");
note("Storage Directory: %s\n", defaultStorageDirectory); note("Storage Directory: %s\n", defaultStorageDirectory);
} }
uint32_t emberAfOtaStorageDriverMaxDownloadSizeCallback(void) uint32_t emberAfOtaStorageDriverMaxDownloadSizeCallback(void)
{ {
// In theory we are limited by the local disk space, but for now // In theory we are limited by the local disk space, but for now
// assume there is no limit. // assume there is no limit.
return 0xFFFFFFFFUL; return 0xFFFFFFFFUL;
} }
EmberAfOtaStorageStatus emberAfOtaStorageDriverPrepareToResumeDownloadCallback(void) EmberAfOtaStorageStatus emberAfOtaStorageDriverPrepareToResumeDownloadCallback(void)
{ {
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
//============================================================================== //==============================================================================
...@@ -730,193 +730,185 @@ EmberAfOtaStorageStatus emberAfOtaStorageDriverPrepareToResumeDownloadCallback(v ...@@ -730,193 +730,185 @@ EmberAfOtaStorageStatus emberAfOtaStorageDriverPrepareToResumeDownloadCallback(v
static EmberAfOtaStorageStatus createDefaultStorageDirectory(void) static EmberAfOtaStorageStatus createDefaultStorageDirectory(void)
{ {
struct stat statInfo; struct stat statInfo;
int returnValue = stat(defaultStorageDirectory, &statInfo); int returnValue = stat(defaultStorageDirectory, &statInfo);
if (returnValue == 0) { if (returnValue == 0) {
if (!S_ISDIR(statInfo.st_mode)) { if (!S_ISDIR(statInfo.st_mode)) {
error("Default storage directory '%s' is not a directory!\n", error("Default storage directory '%s' is not a directory!\n",
defaultStorageDirectory); defaultStorageDirectory);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
debug(config.fileDebug, debug(config.fileDebug,
"Default storage directory already exists '%s'\n", "Default storage directory already exists '%s'\n",
defaultStorageDirectory); defaultStorageDirectory);
} else { } else {
// Does not exist, therefore we must create it. // Does not exist, therefore we must create it.
debug(config.fileDebug, debug(config.fileDebug,
"Creating default storage directory '%s'\n", "Creating default storage directory '%s'\n",
defaultStorageDirectory); defaultStorageDirectory);
int status = portableMkdir(defaultStorageDirectory); int status = portableMkdir(defaultStorageDirectory);
if (status != 0) { if (status != 0) {
error("Could not create default directory '%s': %s\n", error("Could not create default directory '%s': %s\n",
defaultStorageDirectory, defaultStorageDirectory,
strerror(errno)); strerror(errno));
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
} }
return emAfOtaSetStorageDevice(defaultStorageDirectory); return emAfOtaSetStorageDevice(defaultStorageDirectory);
} }
static bool doEui64sMatch(const uint8_t* firstEui64, static bool doEui64sMatch(const uint8_t* firstEui64,
const uint8_t* secondEui64) const uint8_t* secondEui64)
{ {
return (0 == MEMCOMPARE(firstEui64, secondEui64, EUI64_SIZE)); return (0 == MEMCOMPARE(firstEui64, secondEui64, EUI64_SIZE));
} }
static OtaImage* findImageByFilename(const char* tempFilepath) static OtaImage* findImageByFilename(const char* tempFilepath)
{ {
OtaImage* ptr = imageListFirst; OtaImage* ptr = imageListFirst;
while (ptr != NULL) { while (ptr != NULL) {
if (0 == strcmp(ptr->filepath, tempFilepath)) { if (0 == strcmp(ptr->filepath, tempFilepath)) {
return ptr; return ptr;
} }
ptr = (OtaImage*)ptr->next; ptr = (OtaImage*)ptr->next;
} }
return NULL; return NULL;
} }
static EmberAfOtaStorageStatus writeRawData(uint32_t offset, static EmberAfOtaStorageStatus writeRawData(uint32_t offset,
const char* filepath, const char* filepath,
uint32_t length, uint32_t length,
const uint8_t* data) const uint8_t* data)
{ {
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
FILE* fileHandle = fopen(filepath, FILE* fileHandle = fopen(filepath,
"r+b"); "r+b");
EmberAfOtaStorageStatus status = EMBER_AF_OTA_STORAGE_ERROR; EmberAfOtaStorageStatus status = EMBER_AF_OTA_STORAGE_ERROR;
int whence = SEEK_SET; int whence = SEEK_SET;
if (fileHandle == NULL) { if (fileHandle == NULL) {
error("Could not open file '%s' for writing: %s\n", error("Could not open file '%s' for writing: %s\n",
filepath, filepath,
strerror(errno)); strerror(errno));
goto writeEnd; goto writeEnd;
} }
if (offset == APPEND_OFFSET) { if (offset == APPEND_OFFSET) {
offset = 0; offset = 0;
whence = SEEK_END; whence = SEEK_END;
} }
if (0 != fseek(fileHandle, offset, whence)) { if (0 != fseek(fileHandle, offset, whence)) {
error("Could not seek to offset 0x%08X (%s) in file '%s': %s\n", error("Could not seek to offset 0x%08X (%s) in file '%s': %s\n",
offset, offset,
(whence == SEEK_END ? "SEEK_END" : "SEEK_SET"), (whence == SEEK_END ? "SEEK_END" : "SEEK_SET"),
filepath, filepath,
strerror(errno)); strerror(errno));
goto writeEnd; goto writeEnd;
} }
size_t written = fwrite(data, 1, length, fileHandle); size_t written = fwrite(data, 1, length, fileHandle);
if (written != length) { if (written != length) {
error("Tried to write %d bytes but wrote %d\n", length, written); error("Tried to write %d bytes but wrote %d\n", length, written);
goto writeEnd; goto writeEnd;
} }
status = EMBER_AF_OTA_STORAGE_SUCCESS; status = EMBER_AF_OTA_STORAGE_SUCCESS;
writeEnd: writeEnd:
if (fileHandle) { if (fileHandle) {
fclose(fileHandle); fclose(fileHandle);
} }
return status; return status;
} }
static void removeImage(OtaImage* image) static void removeImage(OtaImage* image)
{ {
OtaImage* before = (OtaImage*)image->prev; OtaImage* before = (OtaImage*)image->prev;
OtaImage* after = (OtaImage*)image->next; OtaImage* after = (OtaImage*)image->next;
if (before) { if (before) {
before->next = (struct OtaImage*)after; before->next = (struct OtaImage*)after;
} }
if (after) { if (after) {
after->prev = (struct OtaImage*)before; after->prev = (struct OtaImage*)before;
} }
if (image == imageListFirst) { if (image == imageListFirst) {
imageListFirst = after; imageListFirst = after;
} }
if (image == imageListLast) { if (image == imageListLast) {
imageListLast = before; imageListLast = before;
} }
freeOtaImage(image); freeOtaImage(image);
imageCount--; imageCount--;
} }
static void printEui64(uint8_t* eui64) static void printEui64(uint8_t* eui64)
{ {
note("%02X%02X%02X%02X%02X%02X%02X%02X", note("%02X%02X%02X%02X%02X%02X%02X%02X",
eui64[0], eui64[0],
eui64[1], eui64[1],
eui64[2], eui64[2],
eui64[3], eui64[3],
eui64[4], eui64[4],
eui64[5], eui64[5],
eui64[6], eui64[6],
eui64[7]); eui64[7]);
} }
static void printHeaderInfo(const EmberAfOtaHeader* header) static void printHeaderInfo(const EmberAfOtaHeader* header)
{ {
if (!config.printFileDiscoveryOrRemoval) { if (!config.printFileDiscoveryOrRemoval) {
return; return;
} }
note(" Manufacturer ID: 0x%04X\n",
// printf(" Header Version: 0x%04X\n", header->manufacturerId);
// header->headerVersion); note(" Image Type ID: 0x%04X\n",
// printf(" Header Length: 0x%04X\n", header->imageTypeId);
// header->headerLength); note(" Version: 0x%08X\n",
// printf(" Field Control: 0x%04X\n", header->firmwareVersion);
// header->fieldControl); note(" Header String: %s\n",
note(" Manufacturer ID: 0x%04X\n", header->headerString);
header->manufacturerId);
note(" Image Type ID: 0x%04X\n",
header->imageTypeId);
note(" Version: 0x%08X\n",
header->firmwareVersion);
note(" Header String: %s\n",
header->headerString);
// printf("\n");
} }
static OtaImage* addImageFileToList(const char* filename, static OtaImage* addImageFileToList(const char* filename,
bool printImageInfo) bool printImageInfo)
{ {
OtaImage* newImage = (OtaImage*)myMalloc(sizeof(OtaImage), OtaImage* newImage = (OtaImage*)myMalloc(sizeof(OtaImage),
"addImageFileToList():OtaImage"); "addImageFileToList():OtaImage");
if (newImage == NULL) { if (newImage == NULL) {
return NULL; return NULL;
} }
memset(newImage, 0, sizeof(OtaImage)); memset(newImage, 0, sizeof(OtaImage));
struct stat statInfo; struct stat statInfo;
if (0 != stat(filename, &statInfo)) { if (0 != stat(filename, &statInfo)) {
myFree(newImage); myFree(newImage);
return NULL; return NULL;
} }
newImage->fileSize = statInfo.st_size; newImage->fileSize = statInfo.st_size;
int length = 1 + strnlen(filename, OTA_MAX_FILENAME_LENGTH); int length = 1 + strnlen(filename, OTA_MAX_FILENAME_LENGTH);
newImage->filepath = myMalloc(length, "filename"); newImage->filepath = myMalloc(length, "filename");
if (newImage->filepath == NULL) { if (newImage->filepath == NULL) {
goto dontAdd; goto dontAdd;
} }
strncpy(newImage->filepath, filename, length); strncpy(newImage->filepath, filename, length);
newImage->filepath[length - 1] = '\0'; newImage->filepath[length - 1] = '\0';
newImage->filenameStart = strrchr(newImage->filepath, newImage->filenameStart = strrchr(newImage->filepath,
'/'); '/');
if (newImage->filenameStart == NULL) { if (newImage->filenameStart == NULL) {
newImage->filenameStart = newImage->filepath; newImage->filenameStart = newImage->filepath;
} else { } else {
newImage->filenameStart++; // +1 for the '/' character newImage->filenameStart++; // +1 for the '/' character
} }
newImage->header = readImageHeader(filename); newImage->header = readImageHeader(filename);
if (newImage->header == NULL) { if (newImage->header == NULL) {
goto dontAdd; goto dontAdd;
} }
EmberAfOtaImageId new = { EmberAfOtaImageId new = {
newImage->header->manufacturerId, newImage->header->manufacturerId,
...@@ -924,7 +916,7 @@ static OtaImage* addImageFileToList(const char* filename, ...@@ -924,7 +916,7 @@ static OtaImage* addImageFileToList(const char* filename,
INVALID_FIRMWARE_VERSION, INVALID_FIRMWARE_VERSION,
INVALID_EUI64, INVALID_EUI64,
}; };
OtaImage* test; OtaImage* test;
if (headerHasUpgradeFileDest(newImage->header)) { if (headerHasUpgradeFileDest(newImage->header)) {
MEMCOPY(new.deviceSpecificFileEui64, MEMCOPY(new.deviceSpecificFileEui64,
&newImage->header->upgradeFileDestination, &newImage->header->upgradeFileDestination,
...@@ -990,241 +982,241 @@ static OtaImage* addImageFileToList(const char* filename, ...@@ -990,241 +982,241 @@ static OtaImage* addImageFileToList(const char* filename,
imageCount++; imageCount++;
return newImage; return newImage;
dontAdd: dontAdd:
freeOtaImage(newImage); freeOtaImage(newImage);
return NULL; return NULL;
} }
// This function assumes that the file pointer is at the start of the // This function assumes that the file pointer is at the start of the
// file. It will modify the pointer in the passed FILE* // file. It will modify the pointer in the passed FILE*
static bool checkMagicNumber(FILE* fileHandle, bool printError) static bool checkMagicNumber(FILE* fileHandle, bool printError)
{ {
uint8_t magicNumber[4]; uint8_t magicNumber[4];
if (1 != fread((void*)&magicNumber, 4, 1, fileHandle)) { if (1 != fread((void*)&magicNumber, 4, 1, fileHandle)) {
return false; return false;
} }
if (0 != memcmp(magicNumber, otaFileMagicNumberBytes, 4)) { if (0 != memcmp(magicNumber, otaFileMagicNumberBytes, 4)) {
if (printError) { if (printError) {
error("File has bad magic number.\n"); error("File has bad magic number.\n");
} }
return false; return false;
} }
return true; return true;
} }
static EmberAfOtaStorageStatus initImageDirectory(void) static EmberAfOtaStorageStatus initImageDirectory(void)
{ {
DIR* dir = opendir(storageDevice); DIR* dir = opendir(storageDevice);
if (dir == NULL) { if (dir == NULL) {
error("Could not open directory: %s\n", strerror(errno)); error("Could not open directory: %s\n", strerror(errno));
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
debug(config.fileDebug, "Opened Storage Directory: %s\n", storageDevice); debug(config.fileDebug, "Opened Storage Directory: %s\n", storageDevice);
struct dirent* dirEntry = readdir(dir); struct dirent* dirEntry = readdir(dir);
while (dirEntry != NULL) { while (dirEntry != NULL) {
FILE* fileHandle = NULL; FILE* fileHandle = NULL;
debug(config.fileDebug, "Considering file '%s'\n", dirEntry->d_name); debug(config.fileDebug, "Considering file '%s'\n", dirEntry->d_name);
// +2 for trailing '/' and '\0' // +2 for trailing '/' and '\0'
int pathLength = strlen(storageDevice) + strlen(dirEntry->d_name) + 2; int pathLength = strlen(storageDevice) + strlen(dirEntry->d_name) + 2;
if (pathLength > MAX_FILEPATH_LENGTH) { if (pathLength > MAX_FILEPATH_LENGTH) {
error("Filepath too long (max: %d) skipping file '%s'", error("Filepath too long (max: %d) skipping file '%s'",
MAX_FILEPATH_LENGTH, MAX_FILEPATH_LENGTH,
dirEntry->d_name); dirEntry->d_name);
goto continueReadingDir; goto continueReadingDir;
} }
char* filePath = myMalloc(pathLength, "initImageDirectory(): filepath"); char* filePath = myMalloc(pathLength, "initImageDirectory(): filepath");
if (filePath == NULL) { if (filePath == NULL) {
error("Failed to allocate memory for filepath.\n"); error("Failed to allocate memory for filepath.\n");
goto continueReadingDir; goto continueReadingDir;
} }
sprintf(filePath, "%s%s", sprintf(filePath, "%s%s",
storageDevice, storageDevice,
dirEntry->d_name); dirEntry->d_name);
debug(config.fileDebug, "Full filepath: '%s'\n", filePath); debug(config.fileDebug, "Full filepath: '%s'\n", filePath);
struct stat buffer; struct stat buffer;
if (0 != stat(filePath, &buffer)) { if (0 != stat(filePath, &buffer)) {
fprintf(stderr, fprintf(stderr,
"Error: Could not stat file '%s': %s\n", "Error: Could not stat file '%s': %s\n",
filePath, filePath,
strerror(errno)); strerror(errno));
goto continueReadingDir; goto continueReadingDir;
} else if (S_ISDIR(buffer.st_mode) || !S_ISREG(buffer.st_mode)) { } else if (S_ISDIR(buffer.st_mode) || !S_ISREG(buffer.st_mode)) {
debug(config.fileDebug, debug(config.fileDebug,
"Ignoring '%s' because it is not a regular file.\n", "Ignoring '%s' because it is not a regular file.\n",
dirEntry->d_name); dirEntry->d_name);
goto continueReadingDir; goto continueReadingDir;
} }
// NOTE: dirent.d_name may have a limited length due to POSIX compliance. // NOTE: dirent.d_name may have a limited length due to POSIX compliance.
// Not sure if it will work for all possible filenames. However // Not sure if it will work for all possible filenames. However
// the length does NOT include the directory portion, so it should be // the length does NOT include the directory portion, so it should be
// able to store most all filenames (<256 characters). // able to store most all filenames (<256 characters).
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
fileHandle = fopen(filePath, "rb"); fileHandle = fopen(filePath, "rb");
if (fileHandle == NULL) { if (fileHandle == NULL) {
error("Could not open file '%s' for reading: %s\n", error("Could not open file '%s' for reading: %s\n",
filePath, filePath,
strerror(errno)); strerror(errno));
goto continueReadingDir; goto continueReadingDir;
} }
if (!checkMagicNumber(fileHandle, false)) { if (!checkMagicNumber(fileHandle, false)) {
goto continueReadingDir; goto continueReadingDir;
} }
if (config.ignoreFilesWithUnderscorePrefix if (config.ignoreFilesWithUnderscorePrefix
&& dirEntry->d_name[0] == '_') { && dirEntry->d_name[0] == '_') {
// As a means of making this program omit certain OTA files from // As a means of making this program omit certain OTA files from
// processing, we arbitrarily choose to ignore files starting with '_'. // processing, we arbitrarily choose to ignore files starting with '_'.
// This is done in part to be able to store multiple OTA files in // This is done in part to be able to store multiple OTA files in
// the same directory that have the same unique manufacturer and image // the same directory that have the same unique manufacturer and image
// type ID. Normally when this code finds a second image with the // type ID. Normally when this code finds a second image with the
// same manufacturer and image type ID it picks the one with latest // same manufacturer and image type ID it picks the one with latest
// version number. // version number.
// By changing the file name we can keep the file intact and // By changing the file name we can keep the file intact and
// have this code just skip it. // have this code just skip it.
printf("Ignoring OTA file '%s' since it starts with '_'.\n", printf("Ignoring OTA file '%s' since it starts with '_'.\n",
dirEntry->d_name); dirEntry->d_name);
goto continueReadingDir; goto continueReadingDir;
} }
if (config.printFileDiscoveryOrRemoval) { if (config.printFileDiscoveryOrRemoval) {
note("Found OTA file '%s'\n", dirEntry->d_name); note("Found OTA file '%s'\n", dirEntry->d_name);
} }
// We don't really care about the return code because we want to keep trying // We don't really care about the return code because we want to keep trying
// to add files. // to add files.
OtaImage* newImage = addImageFileToList(filePath, true); OtaImage* newImage = addImageFileToList(filePath, true);
if (config.fileAddedHandler != NULL if (config.fileAddedHandler != NULL
&& newImage != NULL) { && newImage != NULL) {
(config.fileAddedHandler)(newImage->header); (config.fileAddedHandler)(newImage->header);
} }
continueReadingDir: continueReadingDir:
if (fileHandle) { if (fileHandle) {
fclose(fileHandle); fclose(fileHandle);
fileHandle = NULL; fileHandle = NULL;
} }
if (filePath) { if (filePath) {
myFree(filePath); myFree(filePath);
filePath = NULL; filePath = NULL;
} }
dirEntry = readdir(dir); dirEntry = readdir(dir);
} }
if (config.printFileDiscoveryOrRemoval) { if (config.printFileDiscoveryOrRemoval) {
printf("Found %d files\n\n", imageCount); printf("Found %d files\n\n", imageCount);
} }
closedir(dir); closedir(dir);
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
static void freeIfNotNull(void** ptr) static void freeIfNotNull(void** ptr)
{ {
if (*ptr != NULL) { if (*ptr != NULL) {
myFree(*ptr); myFree(*ptr);
*ptr = NULL; *ptr = NULL;
} }
} }
static void freeOtaImage(OtaImage* image) static void freeOtaImage(OtaImage* image)
{ {
if (image == NULL) { if (image == NULL) {
return; return;
} }
freeIfNotNull((void**)&(image->header)); freeIfNotNull((void**)&(image->header));
freeIfNotNull((void**)&(image->filepath)); freeIfNotNull((void**)&(image->filepath));
myFree(image); myFree(image);
} }
const EmberAfOtaHeaderFieldDefinition *emGetOtaHeaderFieldDefinition(uint16_t headerVersion, EmberAfOtaBootloadFileHeaderFieldIndex_t headerIndex) const EmberAfOtaHeaderFieldDefinition *emGetOtaHeaderFieldDefinition(uint16_t headerVersion, EmberAfOtaBootloadFileHeaderFieldIndex_t headerIndex)
{ {
const EmberAfOtaHeaderFieldDefinition invalidField = { NULL, INVALID_FIELD, 0, 0 }; const EmberAfOtaHeaderFieldDefinition invalidField = { NULL, INVALID_FIELD, 0, 0 };
if ((!isValidHeaderVersion(headerVersion)) && (headerIndex != MAGIC_NUMBER_INDEX) && (headerIndex != HEADER_VERSION_INDEX)) { if ((!isValidHeaderVersion(headerVersion)) && (headerIndex != MAGIC_NUMBER_INDEX) && (headerIndex != HEADER_VERSION_INDEX)) {
return &otaHeaderFieldDefinitions[INVALID_FIELD_INDEX]; return &otaHeaderFieldDefinitions[INVALID_FIELD_INDEX];
} }
if ((headerIndex == UPGRADE_FILE_DESTINATION_INDEX) && (headerVersion == OTA_HEADER_VERSION_THREAD)) { if ((headerIndex == UPGRADE_FILE_DESTINATION_INDEX) && (headerVersion == OTA_HEADER_VERSION_THREAD)) {
static const EmberAfOtaHeaderFieldDefinition threadUpgradeFileDestination = { "Upgrade File Destination", BYTE_ARRAY_FIELD, UID_SIZE, DEVICE_SPECIFIC_FILE_PRESENT_MASK }; static const EmberAfOtaHeaderFieldDefinition threadUpgradeFileDestination = { "Upgrade File Destination", BYTE_ARRAY_FIELD, UID_SIZE, DEVICE_SPECIFIC_FILE_PRESENT_MASK };
return &threadUpgradeFileDestination; return &threadUpgradeFileDestination;
} }
return &otaHeaderFieldDefinitions[headerIndex]; return &otaHeaderFieldDefinitions[headerIndex];
} }
static EmberAfOtaHeader* readImageHeader(const char* filename) static EmberAfOtaHeader* readImageHeader(const char* filename)
{ {
EmberAfOtaHeader* header = NULL; EmberAfOtaHeader* header = NULL;
// Windows requires the 'b' (binary) as part of the mode so that line endings // Windows requires the 'b' (binary) as part of the mode so that line endings
// are not truncated. POSIX ignores this. // are not truncated. POSIX ignores this.
FILE* fileHandle = fopen(filename, "rb"); FILE* fileHandle = fopen(filename, "rb");
if (fileHandle == NULL) { if (fileHandle == NULL) {
goto imageReadError; goto imageReadError;
} }
header = (EmberAfOtaHeader*)myMalloc(sizeof(EmberAfOtaHeader), "readImageHeader():OtaImage"); header = (EmberAfOtaHeader*)myMalloc(sizeof(EmberAfOtaHeader), "readImageHeader():OtaImage");
if (header == NULL) { if (header == NULL) {
goto imageReadError; goto imageReadError;
} }
memset(header, 0, sizeof(EmberAfOtaHeader)); memset(header, 0, sizeof(EmberAfOtaHeader));
if (!checkMagicNumber(fileHandle, true)) { if (!checkMagicNumber(fileHandle, true)) {
goto imageReadError; goto imageReadError;
} }
// In all the following code, we subtract 4 from the lengths because we have // In all the following code, we subtract 4 from the lengths because we have
// already read the magic number and therefore do not need to include it in // already read the magic number and therefore do not need to include it in
// our calculations. // our calculations.
uint8_t buffer[OTA_MAXIMUM_HEADER_LENGTH - 4]; uint8_t buffer[OTA_MAXIMUM_HEADER_LENGTH - 4];
uint8_t* bufferPtr = buffer; uint8_t* bufferPtr = buffer;
int dataRead = fread((void*)bufferPtr, int dataRead = fread((void*)bufferPtr,
1, // block size (bytes) 1, // block size (bytes)
OTA_MAXIMUM_HEADER_LENGTH - 4, // count OTA_MAXIMUM_HEADER_LENGTH - 4, // count
fileHandle); fileHandle);
if (dataRead < (OTA_MINIMUM_HEADER_LENGTH - 4)) { if (dataRead < (OTA_MINIMUM_HEADER_LENGTH - 4)) {
error("OTA header is too short (length = %d but should be a minimum of %d)\n", error("OTA header is too short (length = %d but should be a minimum of %d)\n",
dataRead, dataRead,
OTA_MINIMUM_HEADER_LENGTH - 4); OTA_MINIMUM_HEADER_LENGTH - 4);
goto imageReadError; goto imageReadError;
} }
mapHeaderFieldDefinitionToDataStruct(header); mapHeaderFieldDefinitionToDataStruct(header);
// Read the Version and length first so we can use those to validate the rest // Read the Version and length first so we can use those to validate the rest
// of the image. // of the image.
EmberAfOtaStorageStatus status; EmberAfOtaStorageStatus status;
uint16_t headerVersionLength = emGetOtaHeaderFieldDefinition(0, HEADER_VERSION_INDEX)->length; uint16_t headerVersionLength = emGetOtaHeaderFieldDefinition(0, HEADER_VERSION_INDEX)->length;
status = readHeaderDataFromBuffer(HEADER_VERSION_INDEX, status = readHeaderDataFromBuffer(HEADER_VERSION_INDEX,
0, // we don't know the version yet 0, // we don't know the version yet
bufferPtr, bufferPtr,
headerVersionLength, headerVersionLength,
headerVersionLength); headerVersionLength);
if (status != EMBER_AF_OTA_STORAGE_SUCCESS) { if (status != EMBER_AF_OTA_STORAGE_SUCCESS) {
goto imageReadError; goto imageReadError;
} }
if (!isValidHeaderVersion(header->headerVersion)) { if (!isValidHeaderVersion(header->headerVersion)) {
error("Unknown header version number 0x%04X in file, cannot parse.\n", header->headerVersion); error("Unknown header version number 0x%04X in file, cannot parse.\n", header->headerVersion);
goto imageReadError; goto imageReadError;
} }
bufferPtr += 2; // header version field length bufferPtr += 2; // header version field length
uint16_t headerLengthLength = emGetOtaHeaderFieldDefinition(header->headerVersion, HEADER_LENGTH_INDEX)->length; uint16_t headerLengthLength = emGetOtaHeaderFieldDefinition(header->headerVersion, HEADER_LENGTH_INDEX)->length;
status = readHeaderDataFromBuffer(HEADER_LENGTH_INDEX, status = readHeaderDataFromBuffer(HEADER_LENGTH_INDEX,
header->headerVersion, header->headerVersion,
bufferPtr, bufferPtr,
headerLengthLength, headerLengthLength,
headerLengthLength); headerLengthLength);
if (status != EMBER_AF_OTA_STORAGE_SUCCESS) { if (status != EMBER_AF_OTA_STORAGE_SUCCESS) {
goto imageReadError; goto imageReadError;
} }
bufferPtr += 2; // header length field length bufferPtr += 2; // header length field length
// subtract 4 for length of "header length" and "header version" fields. // subtract 4 for length of "header length" and "header version" fields.
int32_t lengthRemaining = header->headerLength - 4; int32_t lengthRemaining = header->headerLength - 4;
dataRead -= 4; dataRead -= 4;
int fieldIndex = FIELD_CONTROL_INDEX; int fieldIndex = FIELD_CONTROL_INDEX;
while (fieldIndex < FIELD_INDEX_MAX && dataRead > 0 && lengthRemaining > 0) { while (fieldIndex < FIELD_INDEX_MAX && dataRead > 0 && lengthRemaining > 0) {
EmberAfOtaStorageStatus status = readHeaderDataFromBuffer(fieldIndex, EmberAfOtaStorageStatus status = readHeaderDataFromBuffer(fieldIndex,
header->headerVersion, header->headerVersion,
...@@ -1264,230 +1256,230 @@ static EmberAfOtaHeader* readImageHeader(const char* filename) ...@@ -1264,230 +1256,230 @@ static EmberAfOtaHeader* readImageHeader(const char* filename)
return header; return header;
imageReadError: imageReadError:
unmapHeaderFieldDefinitions(); unmapHeaderFieldDefinitions();
freeIfNotNull((void**)&header); freeIfNotNull((void**)&header);
fclose(fileHandle); fclose(fileHandle);
return NULL; return NULL;
} }
static EmberAfOtaStorageStatus readHeaderDataFromBuffer(EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex, static EmberAfOtaStorageStatus readHeaderDataFromBuffer(EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex,
uint16_t headerVersion, uint16_t headerVersion,
uint8_t* bufferPtr, uint8_t* bufferPtr,
int32_t headerLengthRemaining, int32_t headerLengthRemaining,
int32_t actualBufferDataRemaining) int32_t actualBufferDataRemaining)
{ {
const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(headerVersion, fieldIndex); const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(headerVersion, fieldIndex);
if (definition->maskForOptionalField != ALWAYS_PRESENT_MASK) { if (definition->maskForOptionalField != ALWAYS_PRESENT_MASK) {
uint16_t fieldControl = *(uint16_t*)(otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location); uint16_t fieldControl = *(uint16_t*)(otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location);
if (!(fieldControl & definition->maskForOptionalField)) { if (!(fieldControl & definition->maskForOptionalField)) {
// No more processing. // No more processing.
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
} }
if (headerLengthRemaining < definition->length if (headerLengthRemaining < definition->length
|| actualBufferDataRemaining < definition->length) { || actualBufferDataRemaining < definition->length) {
error("OTA Header does not contain enough data for field '%s'\n", error("OTA Header does not contain enough data for field '%s'\n",
definition->name); definition->name);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
if (definition->type == INTEGER_FIELD) { if (definition->type == INTEGER_FIELD) {
// Unfortunately we have to break up parsing of different integer lengths // Unfortunately we have to break up parsing of different integer lengths
// into separate pieces of code because of the way the data // into separate pieces of code because of the way the data
// may be packed in the data structure. // may be packed in the data structure.
// Previously we tried to just use a generic 'uint32_t*' to point // Previously we tried to just use a generic 'uint32_t*' to point
// to either the location of a 32-bit, 16-bit, or 8-bit value and then write // to either the location of a 32-bit, 16-bit, or 8-bit value and then write
// the data appropriately. However if the location could only store a 16-bit // the data appropriately. However if the location could only store a 16-bit
// value and we are referring to it as an uint32_t*, then we may write to the // value and we are referring to it as an uint32_t*, then we may write to the
// wrong bytes depending on the how the data is packed. // wrong bytes depending on the how the data is packed.
// Using the correct pointer based on the length sidesteps that problem. // Using the correct pointer based on the length sidesteps that problem.
if (1 == definition->length) { if (1 == definition->length) {
uint8_t *value = (uint8_t*)(otaHeaderFieldLocations[fieldIndex].location); uint8_t *value = (uint8_t*)(otaHeaderFieldLocations[fieldIndex].location);
*value = bufferPtr[0]; *value = bufferPtr[0];
} else if (2 == definition->length) { } else if (2 == definition->length) {
uint16_t *value = (uint16_t*)(otaHeaderFieldLocations[fieldIndex].location); uint16_t *value = (uint16_t*)(otaHeaderFieldLocations[fieldIndex].location);
*value = (bufferPtr[0] *value = (bufferPtr[0]
+ (bufferPtr[1] << 8)); + (bufferPtr[1] << 8));
} else if (4 == definition->length) { } else if (4 == definition->length) {
uint32_t *value = (uint32_t*)(otaHeaderFieldLocations[fieldIndex].location); uint32_t *value = (uint32_t*)(otaHeaderFieldLocations[fieldIndex].location);
*value = (bufferPtr[0] *value = (bufferPtr[0]
+ (bufferPtr[1] << 8) + (bufferPtr[1] << 8)
+ (bufferPtr[2] << 16) + (bufferPtr[2] << 16)
+ (bufferPtr[3] << 24)); + (bufferPtr[3] << 24));
} else { } else {
error("Unsupported data value length '%d' for type '%s'.\n", error("Unsupported data value length '%d' for type '%s'.\n",
definition->length, definition->length,
definition->name); definition->name);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
} else if (definition->type == BYTE_ARRAY_FIELD } else if (definition->type == BYTE_ARRAY_FIELD
|| definition->type == STRING_FIELD) { || definition->type == STRING_FIELD) {
memcpy(otaHeaderFieldLocations[fieldIndex].location, bufferPtr, definition->length); memcpy(otaHeaderFieldLocations[fieldIndex].location, bufferPtr, definition->length);
} else { } else {
// Programatic error // Programatic error
error("Unkown field type '%d'\n", definition->type); error("Unkown field type '%d'\n", definition->type);
return EMBER_AF_OTA_STORAGE_ERROR; return EMBER_AF_OTA_STORAGE_ERROR;
} }
otaHeaderFieldLocations[fieldIndex].found = true; otaHeaderFieldLocations[fieldIndex].found = true;
return EMBER_AF_OTA_STORAGE_SUCCESS; return EMBER_AF_OTA_STORAGE_SUCCESS;
} }
static void mapHeaderFieldDefinitionToDataStruct(EmberAfOtaHeader* header) static void mapHeaderFieldDefinitionToDataStruct(EmberAfOtaHeader* header)
{ {
otaHeaderFieldLocations[HEADER_VERSION_INDEX].location = &(header->headerVersion); otaHeaderFieldLocations[HEADER_VERSION_INDEX].location = &(header->headerVersion);
otaHeaderFieldLocations[HEADER_LENGTH_INDEX].location = &(header->headerLength); otaHeaderFieldLocations[HEADER_LENGTH_INDEX].location = &(header->headerLength);
otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location = &(header->fieldControl); otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location = &(header->fieldControl);
otaHeaderFieldLocations[MANUFACTURER_ID_INDEX].location = &(header->manufacturerId); otaHeaderFieldLocations[MANUFACTURER_ID_INDEX].location = &(header->manufacturerId);
otaHeaderFieldLocations[IMAGE_TYPE_INDEX].location = &(header->imageTypeId); otaHeaderFieldLocations[IMAGE_TYPE_INDEX].location = &(header->imageTypeId);
otaHeaderFieldLocations[FIRMWARE_VERSION_INDEX].location = &(header->firmwareVersion); otaHeaderFieldLocations[FIRMWARE_VERSION_INDEX].location = &(header->firmwareVersion);
otaHeaderFieldLocations[ZIGBEE_STACK_VERSION_INDEX].location = &(header->zigbeeStackVersion); otaHeaderFieldLocations[ZIGBEE_STACK_VERSION_INDEX].location = &(header->zigbeeStackVersion);
otaHeaderFieldLocations[TOTAL_IMAGE_SIZE_INDEX].location = &(header->imageSize); otaHeaderFieldLocations[TOTAL_IMAGE_SIZE_INDEX].location = &(header->imageSize);
otaHeaderFieldLocations[SECURITY_CREDENTIALS_INDEX].location = &(header->securityCredentials); otaHeaderFieldLocations[SECURITY_CREDENTIALS_INDEX].location = &(header->securityCredentials);
otaHeaderFieldLocations[MINIMUM_HARDWARE_VERSION_INDEX].location = &(header->minimumHardwareVersion); otaHeaderFieldLocations[MINIMUM_HARDWARE_VERSION_INDEX].location = &(header->minimumHardwareVersion);
otaHeaderFieldLocations[MAXIMUM_HARDWARE_VERSION_INDEX].location = &(header->maximumHardwareVersion); otaHeaderFieldLocations[MAXIMUM_HARDWARE_VERSION_INDEX].location = &(header->maximumHardwareVersion);
// For byte arrays and strings, those are already pointers and we do not want // For byte arrays and strings, those are already pointers and we do not want
// with another layer of indirection // with another layer of indirection
otaHeaderFieldLocations[HEADER_STRING_INDEX].location = header->headerString; otaHeaderFieldLocations[HEADER_STRING_INDEX].location = header->headerString;
otaHeaderFieldLocations[UPGRADE_FILE_DESTINATION_INDEX].location = &header->upgradeFileDestination; otaHeaderFieldLocations[UPGRADE_FILE_DESTINATION_INDEX].location = &header->upgradeFileDestination;
} }
static void unmapHeaderFieldDefinitions(void) static void unmapHeaderFieldDefinitions(void)
{ {
for (int i = 0; i < FIELD_INDEX_MAX; i++) { for (int i = 0; i < FIELD_INDEX_MAX; i++) {
otaHeaderFieldLocations[i].location = NULL; otaHeaderFieldLocations[i].location = NULL;
otaHeaderFieldLocations[i].found = false; otaHeaderFieldLocations[i].found = false;
} }
} }
static OtaImage* findImageById(const EmberAfOtaImageId* id) static OtaImage* findImageById(const EmberAfOtaImageId* id)
{ {
OtaImage* ptr = imageListFirst; OtaImage* ptr = imageListFirst;
while (ptr != NULL) { while (ptr != NULL) {
if (id->manufacturerId == ptr->header->manufacturerId if (id->manufacturerId == ptr->header->manufacturerId
&& id->imageTypeId == ptr->header->imageTypeId && id->imageTypeId == ptr->header->imageTypeId
&& id->firmwareVersion == ptr->header->firmwareVersion) { && id->firmwareVersion == ptr->header->firmwareVersion) {
return ptr; return ptr;
} }
ptr = (OtaImage*)ptr->next; ptr = (OtaImage*)ptr->next;
} }
return NULL; return NULL;
} }
static uint8_t* writeHeaderDataToBuffer(EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex, static uint8_t* writeHeaderDataToBuffer(EmberAfOtaBootloadFileHeaderFieldIndex_t fieldIndex,
uint16_t headerVersion, uint16_t headerVersion,
uint8_t* bufferPtr) uint8_t* bufferPtr)
{ {
const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(headerVersion, fieldIndex); const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(headerVersion, fieldIndex);
if (definition->maskForOptionalField != ALWAYS_PRESENT_MASK) { if (definition->maskForOptionalField != ALWAYS_PRESENT_MASK) {
uint16_t fieldControl = *(uint16_t*)(otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location); uint16_t fieldControl = *(uint16_t*)(otaHeaderFieldLocations[FIELD_CONTROL_INDEX].location);
if (!(fieldControl & definition->maskForOptionalField)) { if (!(fieldControl & definition->maskForOptionalField)) {
debug(config.fieldDebug, "Skipping field %s\n", definition->name); debug(config.fieldDebug, "Skipping field %s\n", definition->name);
// No more processing. // No more processing.
return bufferPtr; return bufferPtr;
} }
} }
debug(config.fieldDebug, debug(config.fieldDebug,
"Writing field %s, type %d, length %d\n", "Writing field %s, type %d, length %d\n",
definition->name, definition->name,
definition->type, definition->type,
definition->length); definition->length);
if (definition->type == BYTE_ARRAY_FIELD if (definition->type == BYTE_ARRAY_FIELD
|| definition->type == STRING_FIELD) { || definition->type == STRING_FIELD) {
memcpy(bufferPtr, otaHeaderFieldLocations[fieldIndex].location, definition->length); memcpy(bufferPtr, otaHeaderFieldLocations[fieldIndex].location, definition->length);
} else if (definition->type == INTEGER_FIELD) { } else if (definition->type == INTEGER_FIELD) {
if (definition->length == 1) { if (definition->length == 1) {
uint8_t *value = otaHeaderFieldLocations[fieldIndex].location; uint8_t *value = otaHeaderFieldLocations[fieldIndex].location;
bufferPtr[0] = *value; bufferPtr[0] = *value;
} else if (definition->length == 2) { } else if (definition->length == 2) {
uint16_t *value = otaHeaderFieldLocations[fieldIndex].location; uint16_t *value = otaHeaderFieldLocations[fieldIndex].location;
bufferPtr[0] = (uint8_t)(*value); bufferPtr[0] = (uint8_t)(*value);
bufferPtr[1] = (uint8_t)(*value >> 8); bufferPtr[1] = (uint8_t)(*value >> 8);
} else if (definition->length == 4) { } else if (definition->length == 4) {
uint32_t *value = otaHeaderFieldLocations[fieldIndex].location; uint32_t *value = otaHeaderFieldLocations[fieldIndex].location;
bufferPtr[0] = (uint8_t)(*value); bufferPtr[0] = (uint8_t)(*value);
bufferPtr[1] = (uint8_t)(*value >> 8); bufferPtr[1] = (uint8_t)(*value >> 8);
bufferPtr[2] = (uint8_t)(*value >> 16); bufferPtr[2] = (uint8_t)(*value >> 16);
bufferPtr[3] = (uint8_t)(*value >> 24); bufferPtr[3] = (uint8_t)(*value >> 24);
} else { } else {
assert(0); assert(0);
} }
} }
return (bufferPtr + definition->length); return (bufferPtr + definition->length);
} }
static uint16_t calculateOtaFileHeaderLength(EmberAfOtaHeader* header) static uint16_t calculateOtaFileHeaderLength(EmberAfOtaHeader* header)
{ {
uint16_t length = 4; // the size of the magic number uint16_t length = 4; // the size of the magic number
int fieldIndex = HEADER_VERSION_INDEX; int fieldIndex = HEADER_VERSION_INDEX;
while (fieldIndex < FIELD_INDEX_MAX) { while (fieldIndex < FIELD_INDEX_MAX) {
const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(header->headerVersion, fieldIndex); const EmberAfOtaHeaderFieldDefinition *definition = emGetOtaHeaderFieldDefinition(header->headerVersion, fieldIndex);
if (definition->maskForOptionalField == ALWAYS_PRESENT_MASK if (definition->maskForOptionalField == ALWAYS_PRESENT_MASK
|| (header->fieldControl & definition->maskForOptionalField)) { || (header->fieldControl & definition->maskForOptionalField)) {
length += definition->length; length += definition->length;
} }
fieldIndex++; fieldIndex++;
} }
return length; return length;
} }
static OtaImage* imageSearchInternal(const EmberAfOtaImageId* id) static OtaImage* imageSearchInternal(const EmberAfOtaImageId* id)
{ {
OtaImage* ptr = imageListFirst; OtaImage* ptr = imageListFirst;
OtaImage* newest = NULL; OtaImage* newest = NULL;
while (ptr != NULL) { while (ptr != NULL) {
/* /*
note("imageSearchInternal: Considering file '%s' (MFG: 0x%04X, Image: 0x%04X)\n", note("imageSearchInternal: Considering file '%s' (MFG: 0x%04X, Image: 0x%04X)\n",
ptr->filenameStart, ptr->filenameStart,
ptr->header->manufacturerId, ptr->header->manufacturerId,
ptr->header->imageTypeId); ptr->header->imageTypeId);
*/ */
if (ptr->header->manufacturerId == id->manufacturerId if (ptr->header->manufacturerId == id->manufacturerId
&& (ptr->header->imageTypeId == id->imageTypeId)) { && (ptr->header->imageTypeId == id->imageTypeId)) {
if ((id->firmwareVersion == INVALID_FIRMWARE_VERSION) if ((id->firmwareVersion == INVALID_FIRMWARE_VERSION)
|| (ptr->header->firmwareVersion == id->firmwareVersion)) { || (ptr->header->firmwareVersion == id->firmwareVersion)) {
if (headerHasUpgradeFileDest(ptr->header)) { if (headerHasUpgradeFileDest(ptr->header)) {
if ((ptr->header->headerVersion == OTA_HEADER_VERSION_ZIGBEE) if ((ptr->header->headerVersion == OTA_HEADER_VERSION_ZIGBEE)
&& (doEui64sMatch(id->deviceSpecificFileEui64, && (doEui64sMatch(id->deviceSpecificFileEui64,
&ptr->header->upgradeFileDestination))) { &ptr->header->upgradeFileDestination))) {
// Because there is an exact match on the EUI64, we know // Because there is an exact match on the EUI64, we know
// this is the only one that can match the request and can // this is the only one that can match the request and can
// return that now. // return that now.
return ptr; return ptr;
} else { } else {
assert(0); // TODO: We need to handle Thread OTA headers assert(0); // TODO: We need to handle Thread OTA headers
} }
} else { } else {
if (doEui64sMatch(emberAfInvalidImageId.deviceSpecificFileEui64, if (doEui64sMatch(emberAfInvalidImageId.deviceSpecificFileEui64,
id->deviceSpecificFileEui64)) { id->deviceSpecificFileEui64)) {
// Save this entry. There is no upgrade file dest in either the // Save this entry. There is no upgrade file dest in either the
// search criteria or the file. This match may or may not be // search criteria or the file. This match may or may not be
// the latest version number. // the latest version number.
newest = ptr; newest = ptr;
} }
} }
} }
} }
ptr = (OtaImage*)ptr->next; ptr = (OtaImage*)ptr->next;
} }
return newest; return newest;
} }
static EmberAfOtaImageId getIteratorImageId(void) static EmberAfOtaImageId getIteratorImageId(void)
{ {
if (iterator == NULL) { if (iterator == NULL) {
return emberAfInvalidImageId; return emberAfInvalidImageId;
} }
return emAfOtaStorageGetImageIdFromHeader(iterator->header); return emAfOtaStorageGetImageIdFromHeader(iterator->header);
} }
uint32_t emAfOtaStorageGetSlot(void) uint32_t emAfOtaStorageGetSlot(void)
{ {
return INVALID_SLOT; return INVALID_SLOT;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
...@@ -1495,65 +1487,65 @@ uint32_t emAfOtaStorageGetSlot(void) ...@@ -1495,65 +1487,65 @@ uint32_t emAfOtaStorageGetSlot(void)
static void* myMalloc(size_t size, const char* allocName) static void* myMalloc(size_t size, const char* allocName)
{ {
void* returnValue = malloc(size); void* returnValue = malloc(size);
if (returnValue != NULL) { if (returnValue != NULL) {
allocations++; allocations++;
debug(config.memoryDebug, debug(config.memoryDebug,
"[myMalloc] %s, %d bytes (0x%08X)\n", "[myMalloc] %s, %d bytes (0x%08X)\n",
allocName, size, returnValue); allocName, size, returnValue);
} }
return returnValue; return returnValue;
} }
static void myFree(void* ptr) static void myFree(void* ptr)
{ {
debug(config.memoryDebug, "[myFree] 0x%08X\n", ptr); debug(config.memoryDebug, "[myFree] 0x%08X\n", ptr);
free(ptr); free(ptr);
allocations--; allocations--;
} }
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Print routines. // Print routines.
static void message(FILE* stream, static void message(FILE* stream,
bool error, bool error,
const char* formatString, const char* formatString,
va_list ap) va_list ap)
{ {
if (messagePrefix) { if (messagePrefix) {
fprintf(stream, "[%s] ", messagePrefix); fprintf(stream, "[%s] ", messagePrefix);
} }
if (error) { if (error) {
fprintf(stream, "Error: "); fprintf(stream, "Error: ");
} }
vfprintf(stream, formatString, ap); vfprintf(stream, formatString, ap);
fflush(stream); fflush(stream);
} }
static void note(const char* formatString, ...) static void note(const char* formatString, ...)
{ {
va_list ap = { 0 }; va_list ap = { 0 };
va_start(ap, formatString); va_start(ap, formatString);
message(stdout, false, formatString, ap); message(stdout, false, formatString, ap);
va_end(ap); va_end(ap);
} }
static void debug(bool debugOn, const char* formatString, ...) static void debug(bool debugOn, const char* formatString, ...)
{ {
if (debugOn) { if (debugOn) {
va_list ap = { 0 }; va_list ap = { 0 };
va_start(ap, formatString); va_start(ap, formatString);
message(stdout, false, formatString, ap); message(stdout, false, formatString, ap);
va_end(ap); va_end(ap);
} }
} }
static void error(const char* formatString, ...) static void error(const char* formatString, ...)
{ {
va_list ap = { 0 }; va_list ap = { 0 };
va_start(ap, formatString); va_start(ap, formatString);
message(stderr, true, formatString, ap); message(stderr, true, formatString, ap);
va_end(ap); va_end(ap);
} }
#endif // defined(GATEWAY_APP) #endif // defined(GATEWAY_APP)
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
typedef void (EmAfOtaStorageFileAddedHandler)(const EmberAfOtaHeader*); typedef void (EmAfOtaStorageFileAddedHandler)(const EmberAfOtaHeader*);
typedef struct { typedef struct {
bool memoryDebug; bool memoryDebug;
bool fileDebug; bool fileDebug;
bool fieldDebug; bool fieldDebug;
bool ignoreFilesWithUnderscorePrefix; bool ignoreFilesWithUnderscorePrefix;
bool printFileDiscoveryOrRemoval; bool printFileDiscoveryOrRemoval;
EmAfOtaStorageFileAddedHandler* fileAddedHandler; EmAfOtaStorageFileAddedHandler* fileAddedHandler;
} EmAfOtaStorageLinuxConfig; } EmAfOtaStorageLinuxConfig;
void emAfOtaStorageGetConfig(EmAfOtaStorageLinuxConfig* currentConfig); void emAfOtaStorageGetConfig(EmAfOtaStorageLinuxConfig* currentConfig);
......
...@@ -77,6 +77,6 @@ bool emberAfIsCurrentSecurityProfileSmartEnergy(void) ...@@ -77,6 +77,6 @@ bool emberAfIsCurrentSecurityProfileSmartEnergy(void)
|| (emAfCurrentZigbeeProNetwork->securityProfile || (emAfCurrentZigbeeProNetwork->securityProfile
== EMBER_AF_SECURITY_PROFILE_SE_FULL))); == EMBER_AF_SECURITY_PROFILE_SE_FULL)));
#else #else
return false; return false;
#endif #endif
} }
...@@ -545,16 +545,12 @@ int emberAfMain(MAIN_FUNCTION_PARAMETERS) ...@@ -545,16 +545,12 @@ int emberAfMain(MAIN_FUNCTION_PARAMETERS)
halInit(); halInit();
INTERRUPTS_ON(); // Safe to enable interrupts at this point INTERRUPTS_ON(); // Safe to enable interrupts at this point
{
int returnCode; int returnCode;
if (emberAfMainStartCallback(&returnCode, if (emberAfMainStartCallback(&returnCode,
APP_FRAMEWORK_MAIN_ARGUMENTS)) { //get serial port info APP_FRAMEWORK_MAIN_ARGUMENTS)) { //get serial port info
return returnCode; return returnCode;
} }
}
kk_print("*******************123****************\r\n");
kk_print_info("\r\n-----hello world![%s:%s]-----\r\n",__DATE__,__TIME__);
kk_print_version();
emberSerialInit(APP_SERIAL, BAUD_RATE, PARITY_NONE, 1); //fock child process emberSerialInit(APP_SERIAL, BAUD_RATE, PARITY_NONE, 1); //fock child process
......
...@@ -379,6 +379,6 @@ EmberApsFrame *emberAfGetCommandApsFrame(void) ...@@ -379,6 +379,6 @@ EmberApsFrame *emberAfGetCommandApsFrame(void)
void emberAfSetCommandEndpoints(uint8_t sourceEndpoint, uint8_t destinationEndpoint) void emberAfSetCommandEndpoints(uint8_t sourceEndpoint, uint8_t destinationEndpoint)
{ {
emAfCommandApsFrame->sourceEndpoint = sourceEndpoint; emAfCommandApsFrame->sourceEndpoint = sourceEndpoint;
emAfCommandApsFrame->destinationEndpoint = destinationEndpoint; emAfCommandApsFrame->destinationEndpoint = destinationEndpoint;
} }
...@@ -65,11 +65,11 @@ bool emAfProcessClusterSpecificCommand(EmberAfClusterCommand *cmd) ...@@ -65,11 +65,11 @@ bool emAfProcessClusterSpecificCommand(EmberAfClusterCommand *cmd)
} }
#endif #endif
#ifdef ZCL_USING_OTA_BOOTLOAD_CLUSTER_SERVER #ifdef ZCL_USING_OTA_BOOTLOAD_CLUSTER_SERVER
if (cmd->apsFrame->clusterId == ZCL_OTA_BOOTLOAD_CLUSTER_ID if (cmd->apsFrame->clusterId == ZCL_OTA_BOOTLOAD_CLUSTER_ID
&& cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER && cmd->direction == ZCL_DIRECTION_CLIENT_TO_SERVER
&& emberAfOtaServerIncomingMessageRawCallback(cmd)) { && emberAfOtaServerIncomingMessageRawCallback(cmd)) {
return true; return true;
} }
#endif #endif
// Pass the command to the generated command parser for processing // Pass the command to the generated command parser for processing
......
...@@ -688,7 +688,7 @@ EmberStatus emberAfSendResponseWithCallback(EmberAfMessageSentFunction callback) ...@@ -688,7 +688,7 @@ EmberStatus emberAfSendResponseWithCallback(EmberAfMessageSentFunction callback)
EmberStatus emberAfSendResponse(void) EmberStatus emberAfSendResponse(void)
{ {
return emberAfSendResponseWithCallback(NULL); return emberAfSendResponseWithCallback(NULL);
} }
EmberStatus emberAfSendImmediateDefaultResponseWithCallback(EmberAfStatus status, EmberStatus emberAfSendImmediateDefaultResponseWithCallback(EmberAfStatus status,
......
...@@ -162,52 +162,52 @@ static bool sendGoAhead = true; ...@@ -162,52 +162,52 @@ static bool sendGoAhead = true;
// loop executing ezspTick() and other functionality. // loop executing ezspTick() and other functionality.
EmberStatus emberSerialInit(uint8_t port, EmberStatus emberSerialInit(uint8_t port,
SerialBaudRate rate, SerialBaudRate rate,
SerialParity parity, SerialParity parity,
uint8_t stopBits) uint8_t stopBits)
{ {
static bool emberSerialInitCalled = false; static bool emberSerialInitCalled = false;
debugPrint("emberSerialInit()\n"); debugPrint("emberSerialInit()\n");
if (port > 1) { if (port > 1) {
return EMBER_SERIAL_INVALID_PORT; return EMBER_SERIAL_INVALID_PORT;
} }
if (childPid[port] != INVALID_PID) { if (childPid[port] != INVALID_PID) {
debugPrint("Serial port %d already initialized.\n", port); debugPrint("Serial port %d already initialized.\n", port);
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
// Without the backchannel, there is only one serial port available (STDIN). // Without the backchannel, there is only one serial port available (STDIN).
if (!backchannelEnable if (!backchannelEnable
&& emberSerialInitCalled) { && emberSerialInitCalled) {
return EMBER_SERIAL_INVALID_PORT; return EMBER_SERIAL_INVALID_PORT;
} }
if (backchannelEnable) { if (backchannelEnable) {
// For the CLI, wait here until a new client connects for the first time. // For the CLI, wait here until a new client connects for the first time.
BackchannelState state = BackchannelState state =
backchannelCheckConnection(port, backchannelCheckConnection(port,
(port // waitForConnection? (port // waitForConnection?
== SERIAL_PORT_CLI)); == SERIAL_PORT_CLI));
if (port == SERIAL_PORT_CLI && state != NEW_CONNECTION) { if (port == SERIAL_PORT_CLI && state != NEW_CONNECTION) {
debugPrint("Failed to get new backchannel connection.\n"); debugPrint("Failed to get new backchannel connection.\n");
return EMBER_ERR_FATAL; return EMBER_ERR_FATAL;
} else if ( !(state == NEW_CONNECTION || state == CONNECTION_EXISTS) ) { } else if ( !(state == NEW_CONNECTION || state == CONNECTION_EXISTS) ) {
// We will defer initializing the RAW serial port (spawning the child // We will defer initializing the RAW serial port (spawning the child
// and that jazz) until we actually have a new client connection. // and that jazz) until we actually have a new client connection.
return EMBER_SUCCESS; return EMBER_SUCCESS;
} }
} }
EmberStatus status = serialInitInternal(port); EmberStatus status = serialInitInternal(port);
if (status == EMBER_SUCCESS) { if (status == EMBER_SUCCESS) {
installSignalHandler(); installSignalHandler();
emberSerialInitCalled = true; emberSerialInitCalled = true;
} }
return status; return status;
} }
static EmberStatus serialInitInternal(uint8_t port) static EmberStatus serialInitInternal(uint8_t port)
...@@ -263,7 +263,7 @@ static EmberStatus serialInitInternal(uint8_t port) ...@@ -263,7 +263,7 @@ static EmberStatus serialInitInternal(uint8_t port)
// block indefinitely waiting for debugger to attach // block indefinitely waiting for debugger to attach
sleep(1); sleep(1);
} }
childRun(port); childRun(port);
return EMBER_ERR_FATAL; // should never get here return EMBER_ERR_FATAL; // should never get here
} else if (pid == -1) { } else if (pid == -1) {
fprintf(stderr, "FATAL: Could not fork! (%d): %s\n", fprintf(stderr, "FATAL: Could not fork! (%d): %s\n",
...@@ -311,13 +311,13 @@ static bool handleRemoteConnection(uint8_t port) ...@@ -311,13 +311,13 @@ static bool handleRemoteConnection(uint8_t port)
static void handleBackchannelConnection(uint8_t port) static void handleBackchannelConnection(uint8_t port)
{ {
// BugzId:12928 Close out sockets used by other children but not this one // BugzId:12928 Close out sockets used by other children but not this one
int i; int i;
for (i = 0; i < NUM_PORTS; i++) { for (i = 0; i < NUM_PORTS; i++) {
if (i != port) { if (i != port) {
backchannelCloseConnection(i); backchannelCloseConnection(i);
} }
} }
if (EMBER_SUCCESS if (EMBER_SUCCESS
== backchannelMapStandardInputOutputToRemoteConnection(port)) { == backchannelMapStandardInputOutputToRemoteConnection(port)) {
...@@ -333,10 +333,10 @@ static void handleBackchannelConnection(uint8_t port) ...@@ -333,10 +333,10 @@ static void handleBackchannelConnection(uint8_t port)
static void childRun(uint8_t port) static void childRun(uint8_t port)
{ {
childProcessPort = port; childProcessPort = port;
close(DATA_READER(port)); close(DATA_READER(port));
close(CONTROL_WRITER(port)); close(CONTROL_WRITER(port));
if (backchannelEnable) { if (backchannelEnable) {
handleBackchannelConnection(port); handleBackchannelConnection(port);
...@@ -364,23 +364,23 @@ static void childRun(uint8_t port) ...@@ -364,23 +364,23 @@ static void childRun(uint8_t port)
installSignalHandler(); installSignalHandler();
processSerialInput(port); processSerialInput(port);
// Normally the above function NEVER returns // Normally the above function NEVER returns
// If we get here it is an error. // If we get here it is an error.
assert(0); assert(0);
} }
void emberSerialSetPrompt(const char* thePrompt) void emberSerialSetPrompt(const char* thePrompt)
{ {
if (thePrompt == NULL) { if (thePrompt == NULL) {
promptSet = false; promptSet = false;
return; return;
} }
// Substract one for the '>' // Substract one for the '>'
snprintf(prompt, snprintf(prompt,
MAX_PROMPT_LENGTH - 1, MAX_PROMPT_LENGTH - 1,
"%s>", "%s>",
thePrompt); thePrompt);
} }
static void setNonBlockingFD(int fd) static void setNonBlockingFD(int fd)
...@@ -449,18 +449,18 @@ static void parentCleanupAfterChildDied(void) ...@@ -449,18 +449,18 @@ static void parentCleanupAfterChildDied(void)
static void childCleanupAndExit(void) static void childCleanupAndExit(void)
{ {
if (childProcessPort == SERIAL_PORT_CLI) { if (childProcessPort == SERIAL_PORT_CLI) {
writeHistory(); writeHistory();
} }
if (childProcessPort != -1) { if (childProcessPort != -1) {
if (backchannelEnable) { if (backchannelEnable) {
backchannelStopServer(childProcessPort); backchannelStopServer(childProcessPort);
} }
close(DATA_WRITER(childProcessPort)); close(DATA_WRITER(childProcessPort));
close(CONTROL_READER(childProcessPort)); close(CONTROL_READER(childProcessPort));
} }
exit(0); exit(0);
} }
// This works only for the command interpreter. // This works only for the command interpreter.
...@@ -468,11 +468,11 @@ static void childCleanupAndExit(void) ...@@ -468,11 +468,11 @@ static void childCleanupAndExit(void)
void emberSerialCommandCompletionInit(EmberCommandEntry listOfCommands[]) void emberSerialCommandCompletionInit(EmberCommandEntry listOfCommands[])
{ {
#if READLINE_SUPPORT #if READLINE_SUPPORT
allCommands = listOfCommands; allCommands = listOfCommands;
rl_attempted_completion_function = commandCompletion; rl_attempted_completion_function = commandCompletion;
rl_completion_entry_function = filenameCompletion; rl_completion_entry_function = filenameCompletion;
#endif #endif
usingCommandInterpreter = true; usingCommandInterpreter = true;
} }
// This is for QA's cli // This is for QA's cli
...@@ -708,9 +708,9 @@ static void processSerialInput(uint8_t port) ...@@ -708,9 +708,9 @@ static void processSerialInput(uint8_t port)
char goAhead; char goAhead;
char singleByte[2]; char singleByte[2];
if (port == SERIAL_PORT_CLI) { if (port == SERIAL_PORT_CLI) {
initializeHistory(); initializeHistory();
} }
debugPrint("Processing input for port %d.\n", port); debugPrint("Processing input for port %d.\n", port);
...@@ -932,18 +932,18 @@ static const char* commandArgumentPtr = NULL; ...@@ -932,18 +932,18 @@ static const char* commandArgumentPtr = NULL;
static const char* findNextNonSpaceChar(const char* line) static const char* findNextNonSpaceChar(const char* line)
{ {
while (*line != '\0' && *line == ' ') { while (*line != '\0' && *line == ' ') {
line++; line++;
} }
return line; return line;
} }
static const char* findNextSpaceChar(const char* line) static const char* findNextSpaceChar(const char* line)
{ {
while (*line != '\0' && *line != ' ') { while (*line != '\0' && *line != ' ') {
line++; line++;
} }
return line; return line;
} }
// Recursive function. Traverses the command-tree // Recursive function. Traverses the command-tree
...@@ -1147,57 +1147,57 @@ static char* duplicateString(const char* source) ...@@ -1147,57 +1147,57 @@ static char* duplicateString(const char* source)
static void initializeHistory(void) static void initializeHistory(void)
{ {
int myErrno; int myErrno;
char* homeDirectory; char* homeDirectory;
readlineHistoryPath[0] = '\0'; readlineHistoryPath[0] = '\0';
using_history(); // initialize readline() history. using_history(); // initialize readline() history.
homeDirectory = getenv("HOME"); homeDirectory = getenv("HOME");
if (homeDirectory == NULL) { if (homeDirectory == NULL) {
debugPrint("Error: HOME directory env variable is not defined.\n"); debugPrint("Error: HOME directory env variable is not defined.\n");
return; return;
} else { } else {
debugPrint("Home Directory: %s\n", homeDirectory); debugPrint("Home Directory: %s\n", homeDirectory);
} }
snprintf(readlineHistoryPath, snprintf(readlineHistoryPath,
MAX_STRING_LENGTH - 1, MAX_STRING_LENGTH - 1,
"%s/%s", "%s/%s",
homeDirectory, homeDirectory,
readlineHistoryFilename); readlineHistoryFilename);
readlineHistoryPath[MAX_STRING_LENGTH - 1] = '\0'; readlineHistoryPath[MAX_STRING_LENGTH - 1] = '\0';
myErrno = read_history(readlineHistoryPath); myErrno = read_history(readlineHistoryPath);
if (myErrno != 0) { if (myErrno != 0) {
debugPrint("Could not open history file '%s': %s\n", debugPrint("Could not open history file '%s': %s\n",
readlineHistoryPath, readlineHistoryPath,
strerror(myErrno)); strerror(myErrno));
} else { } else {
debugPrint("%d history entries read from file '%s'\n", debugPrint("%d history entries read from file '%s'\n",
history_length, // readline global history_length, // readline global
readlineHistoryPath); readlineHistoryPath);
} }
} }
static void writeHistory(void) static void writeHistory(void)
{ {
int myErrno; int myErrno;
if (readlineHistoryPath[0] == '\0') { if (readlineHistoryPath[0] == '\0') {
debugPrint("Readline history path is empty, not writing history.\n"); debugPrint("Readline history path is empty, not writing history.\n");
return; return;
} }
myErrno = write_history(readlineHistoryPath); myErrno = write_history(readlineHistoryPath);
if (myErrno != 0) { if (myErrno != 0) {
debugPrint("Failed to write history file '%s': %s\n", debugPrint("Failed to write history file '%s': %s\n",
readlineHistoryPath, readlineHistoryPath,
strerror(myErrno)); strerror(myErrno));
} else { } else {
debugPrint("Wrote %d entries to history file '%s'\n", debugPrint("Wrote %d entries to history file '%s'\n",
history_length, // readline global history_length, // readline global
readlineHistoryPath); readlineHistoryPath);
} }
} }
#endif // #if READLINE_SUPPORT #endif // #if READLINE_SUPPORT
...@@ -1227,20 +1227,20 @@ static void writeHistory(void) ...@@ -1227,20 +1227,20 @@ static void writeHistory(void)
*/ */
static void signalHandler(int signal) static void signalHandler(int signal)
{ {
static bool reportSigttin = true; static bool reportSigttin = true;
const char* signalName = strsignal(signal); const char* signalName = strsignal(signal);
if (signal == SIGTTOU) { if (signal == SIGTTOU) {
debugOn = false; debugOn = false;
return; return;
} }
if (signal != SIGTTIN || reportSigttin) { if (signal != SIGTTIN || reportSigttin) {
debugPrint("%s caught signal %s (%d)\n", debugPrint("%s caught signal %s (%d)\n",
(amParent ? "Parent" : "Child"), (amParent ? "Parent" : "Child"),
(signalName == NULL ? "???" : signalName), (signalName == NULL ? "???" : signalName),
signal); signal);
} }
if (signal == SIGPIPE) { if (signal == SIGPIPE) {
// Ignore this. // Ignore this.
...@@ -1253,11 +1253,11 @@ static void signalHandler(int signal) ...@@ -1253,11 +1253,11 @@ static void signalHandler(int signal)
return; return;
} }
if (signal == SIGTTIN) { if (signal == SIGTTIN) {
reportSigttin = false; reportSigttin = false;
return; return;
} }
/* /*
if (signal == SIGCHLD) { if (signal == SIGCHLD) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment