From dd0f1ab4b313c2d02a3c268a6d2f74d6c7d05907 Mon Sep 17 00:00:00 2001 From: Brandon Satrom Date: Sat, 5 Mar 2022 07:08:08 -0600 Subject: [PATCH] chore:update note-c (#79) --- library.properties | 2 +- src/note-c/n_helpers.c | 288 +++++++++++++++++++++++++++++++++++++---- src/note-c/n_hooks.c | 19 ++- src/note-c/n_request.c | 7 +- src/note-c/note.h | 18 +++ 5 files changed, 303 insertions(+), 31 deletions(-) diff --git a/library.properties b/library.properties index ac0c48f..1d5976c 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Blues Wireless Notecard -version=1.3.12 +version=1.3.13 author=Blues Wireless maintainer=Blues Wireless sentence=An easy to use Notecard Library for Arduino. diff --git a/src/note-c/n_helpers.c b/src/note-c/n_helpers.c index 7b51420..6080c4b 100644 --- a/src/note-c/n_helpers.c +++ b/src/note-c/n_helpers.c @@ -105,8 +105,8 @@ JTIME NoteTime() //**************************************************************************/ /*! @brief Set the number of minutes between refreshes of the time - from the Notecard, to help minimize clock drift on this host. - Set this to 0 for no auto-refresh; it defaults to daily. + from the Notecard, to help minimize clock drift on this host. + Set this to 0 for no auto-refresh; it defaults to daily. @returns Nothing */ /**************************************************************************/ @@ -1043,13 +1043,57 @@ bool NoteGetStatusST(char *statusBuf, int statusBufLen, JTIME *bootTime, bool *r return success; } +//**************************************************************************/ +/*! + @brief Save the current state and use `card.attn` to set a host + sleep interval. + @param payload An optional binary payload to keep in memory while the host sleeps. + @param seconds The duration to sleep. + @param modes Optional list of additional `card.attn` modes. + @returns boolean. `true` if request was successful. +*/ +/**************************************************************************/ +bool NotePayloadSaveAndSleep(NotePayloadDesc *desc, uint32_t seconds, const char *modes) +{ + char *stateB64 = NULL; + + // If specified, encode + if (desc->data != NULL && desc->length != 0) { + + // Get length that it will be when converted to base64 + unsigned b64Len = JB64EncodeLen(desc->length); + + // Convert the state to base64 + stateB64 = _Malloc(b64Len); + if (stateB64 == NULL) { + return false; + } + + // Encode the state buffer into a string + JB64Encode(stateB64, (const char *) desc->data, desc->length); + + } + + // Sleep + bool success = NoteSleep(stateB64, seconds, modes); + + // Free the temp buffer + if (stateB64 != NULL) { + _Free(stateB64); + } + + // Done + return success; + +} + //**************************************************************************/ /*! @brief Save the current state and use `card.attn` to set a host sleep interval. @param stateb64 A base64 payload to keep in memory while the host sleeps. @param seconds The duration to sleep. - @param modes A list of `card.attn` modes. + @param modes Optional list of additional `card.attn` modes. @returns boolean. `true` if request was successful. */ /**************************************************************************/ @@ -1057,15 +1101,20 @@ bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes) { bool success = false; + // Trace + _Debug("ABOUT TO SLEEP\n"); + // Use a Command rather than a Request so that the Notecard doesn't try to send // a response back to us, which would cause a communications error on that end. _Debug("requesting sleep\n"); J *req = NoteNewCommand("card.attn"); if (req != NULL) { // Add the base64 item in a wonderful way that doesn't strdup the huge string - J *stringReferenceItem = JCreateStringReference(stateb64); - if (stringReferenceItem != NULL) { - JAddItemToObject(req, "payload", stringReferenceItem); + if (stateb64 != NULL) { + J *stringReferenceItem = JCreateStringReference(stateb64); + if (stringReferenceItem != NULL) { + JAddItemToObject(req, "payload", stringReferenceItem); + } } char modestr[64]; strlcpy(modestr, "sleep", sizeof(modestr)); @@ -1078,6 +1127,9 @@ bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes) success = NoteRequest(req); } + // Trace + _Debug("DIDN'T SLEEP\n"); + // Done return success; } @@ -1094,13 +1146,42 @@ bool NoteSleep(char *stateb64, uint32_t seconds, const char *modes) /**************************************************************************/ bool NoteWake(int stateLen, void *state) { + NotePayloadDesc desc; + bool success = NotePayloadRetrieveAfterSleep(&desc); + if (!success) { + return false; + } + if (desc.length != (uint32_t)stateLen) { + NotePayloadFree(&desc); + return false; + } + memcpy(state, desc.data, stateLen); + NotePayloadFree(&desc); + return true; +} +//**************************************************************************/ +/*! + @brief Wake the module by restoring state into a state buffer, returning + its length, and fail if it isn't available. + @param payloadLen (out) Optional place to receive the length of the returned buffer + @param payload (out) The place to store address of returned in-memory payload + @returns boolean. `true` if request was successful. +*/ +/**************************************************************************/ +bool NotePayloadRetrieveAfterSleep(NotePayloadDesc *desc) +{ + + // Initialize payload descriptor + if (desc != NULL) { + memset(desc, 0, sizeof(NotePayloadDesc)); + } + + // Send the Notecard a request to retrieve the saved state J *req = NoteNewRequest("card.attn"); if (req == NULL) { return false; } - - // Send it a command to request the saved state JAddBoolToObject(req, "start", true); J *rsp = NoteRequestResponse(req); if (rsp == NULL) { @@ -1117,39 +1198,36 @@ bool NoteWake(int stateLen, void *state) setTime(seconds); } - // Exit if no payload - char *payload = JGetString(rsp, "payload"); - if (payload[0] == '\0') { + // If we didn't expect any state to be restored, we're done + if (desc == NULL) { NoteDeleteResponse(rsp); - return false; + return true; } - // Exit if the purpose of the call was to intentionally discard saved state - if (state == NULL) { + // Exit if no payload, knowing that we expected one + char *payload = JGetString(rsp, "payload"); + if (payload[0] == '\0') { NoteDeleteResponse(rsp); - return true; + return false; } // Allocate a buffer for the payload. (We can't decode in-place because we can't // risk overwriting memory if the actual payload is even slightly different.) - char *p = (char *) _Malloc(JB64DecodeLen(payload)); + uint32_t allocLen = JB64DecodeLen(payload); + uint8_t *p = (uint8_t *) _Malloc(allocLen); if (p == NULL) { NoteDeleteResponse(rsp); return false; } - int actualLen = JB64Decode(p, payload); - if (actualLen != stateLen) { - _Free(p); - _Debug("*** discarding saved state\n"); - NoteDeleteResponse(rsp); - return false; - } - memcpy(state, p, stateLen); - _Free(p); + uint32_t actualLen = (uint32_t) JB64Decode((char *)p, payload); - // State restored - _Debug("STATE RESTORED\n"); + // Fill out the payload descriptor + desc->data = p; + desc->alloc = allocLen; + desc->length = actualLen; + // State restored + _Debug("AWAKENED SUCCESSFULLY\n"); NoteDeleteResponse(rsp); return true; } @@ -1692,3 +1770,159 @@ uint32_t NoteMemAvailable() return total; } + +//**************************************************************************/ +/*! + @brief Create a desc from a buffer, or initialize a new to-be-allocated desc + if the buf is null + @param desc Pointer to the payload descriptor + @param buf Pointer to the buffer to initialize the desc with (or NULL) + @param buflen Length of the buffer to initialize the desc with (or 0) +*/ +/**************************************************************************/ +void NotePayloadSet(NotePayloadDesc *desc, uint8_t *buf, uint32_t buflen) +{ + desc->data = buf; + desc->alloc = buflen; + desc->length = buflen; +} + +//**************************************************************************/ +/*! + @brief Free the payload pointed to by the descriptor + @param desc Pointer to the payload descriptor +*/ +/**************************************************************************/ +void NotePayloadFree(NotePayloadDesc *desc) +{ + if (desc->data != NULL) { + _Free(desc->data); + } + desc->data = NULL; + desc->alloc = 0; + desc->length = 0; +} + +//**************************************************************************/ +/*! + @brief Add a segment to the specified binary with 4-character type code + @param desc Pointer to the payload descriptor + @param segtype Pointer to the 4-character payload identifier + @param data Pointer to the data segment to be appended + @param len Length of data segment + @returns boolean. `true` if named segment is appended successfully +*/ +/**************************************************************************/ +bool NotePayloadAddSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *data, uint32_t len) +{ + uint32_t alloc = 512; + uint32_t hlen = len + NP_SEGHDR_LEN; + if (hlen > alloc) { + alloc += hlen; + } + if (desc->data == NULL) { + uint8_t *base = _Malloc(alloc); + if (base == NULL) { + return false; + } + uint8_t *p = base; + memcpy(p, segtype, NP_SEGTYPE_LEN); + p += NP_SEGTYPE_LEN; + memcpy(p, &len, NP_SEGLEN_LEN); + p += NP_SEGLEN_LEN; + memcpy(p, data, len); + desc->data = base; + desc->alloc = alloc; + desc->length = hlen; + } else if ((desc->alloc - desc->length) < hlen) { + uint8_t *base = _Malloc(desc->alloc + alloc); + if (base == NULL) { + return false; + } + uint8_t *p = base; + memcpy(p, desc->data, desc->length); + p += desc->length; + memcpy(p, segtype, NP_SEGTYPE_LEN); + p += NP_SEGTYPE_LEN; + memcpy(p, &len, NP_SEGLEN_LEN); + p += NP_SEGLEN_LEN; + memcpy(p, data, len); + _Free(desc->data); + desc->data = base; + desc->alloc = desc->alloc + alloc; + desc->length += hlen; + } else { + uint8_t *p = desc->data + desc->length; + memcpy(p, segtype, NP_SEGTYPE_LEN); + p += NP_SEGTYPE_LEN; + memcpy(p, &len, NP_SEGLEN_LEN); + p += NP_SEGLEN_LEN; + memcpy(p, data, len); + desc->length += hlen; + } + return true; +} + +//**************************************************************************/ +/*! + @brief Find and copy a named segment from a segmented payload + @param desc Pointer to the payload descriptor + @param segtype Pointer to the 4-character payload identifier + @param pdata Pointer to the found segment if return is true + @param len The expected length of the returned segment + @returns boolean. `true` if named segment is restored successfully +*/ +/**************************************************************************/ +bool NotePayloadGetSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t len) +{ + uint8_t *data; + uint32_t datalen; + bool success = NotePayloadFindSegment(desc, segtype, &data, &datalen); + if (success && datalen == len) { + memcpy(pdata, data, len); + return true; + } + return false; +} + +//**************************************************************************/ +/*! + @brief Find a named segment within a segmented payload + @param desc Pointer to the payload descriptor + @param segtype Pointer to the 4-character payload identifier + @param pdata Pointer to the found segment if return is true + @param plen Pointer to the returned segment length if return is true + @returns boolean. `true` if named segment is found +*/ +/**************************************************************************/ +bool NotePayloadFindSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t *plen) +{ + + // Preset returns + * (uint8_t **) pdata = NULL; + *plen = 0; + + // Locate the segment + uint8_t *p = desc->data; + uint32_t left = desc->length; + if (p == NULL) { + return false; + } + while (left >= NP_SEGHDR_LEN) { + uint32_t len; + memcpy(&len, p + NP_SEGTYPE_LEN, sizeof(len)); + if (memcmp(p, segtype, NP_SEGTYPE_LEN) == 0) { + *plen = len; + * (uint8_t **) pdata = p + NP_SEGHDR_LEN; + return true; + } + len += NP_SEGHDR_LEN; + p += len; + if (len > left) { + left = 0; + } else { + left -= len; + } + } + return false; +} diff --git a/src/note-c/n_hooks.c b/src/note-c/n_hooks.c index 1e9ad5a..d25497a 100644 --- a/src/note-c/n_hooks.c +++ b/src/note-c/n_hooks.c @@ -323,7 +323,24 @@ void NoteSetFnDisabled() //**************************************************************************/ /*! - @brief Write a to the debug stream and output a newline. + @brief Write a number to the debug stream and output a newline. + @param line A debug string for output. +*/ +/**************************************************************************/ +void NoteDebugIntln(const char *line, int n) +{ + if (line != NULL) { + NoteDebug(line); + } + char str[16]; + JItoA(n, str); + NoteDebug(str); + NoteDebug(c_newline); +} + +//**************************************************************************/ +/*! + @brief Write text to the debug stream and output a newline. @param line A debug string for output. */ /**************************************************************************/ diff --git a/src/note-c/n_request.c b/src/note-c/n_request.c index c8f6c39..493a344 100644 --- a/src/note-c/n_request.c +++ b/src/note-c/n_request.c @@ -335,9 +335,12 @@ J *NoteTransaction(J *req) const char *reqType = JGetString(req, "req"); const char *cmdType = JGetString(req, "cmd"); - // Add the user agent object if appropriate + // Add the user agent object only when we're doing a hub.set and only when we're + // specifying the product UID. The intent is that we only piggyback user agent + // data when the host is initializing the Notecard, as opposed to every time + // the host does a hub.set to change mode. #ifndef NOTE_DISABLE_USER_AGENT - if (!JIsPresent(req, "body") && (strcmp(reqType, "hub.set") == 0)) { + if (!JIsPresent(req, "body") && (strcmp(reqType, "hub.set") == 0) && JIsPresent(req, "product")) { J *body = NoteUserAgent(); if (body != NULL) { JAddItemToObject(req, "body", body); diff --git a/src/note-c/note.h b/src/note-c/note.h index 7db9fd3..70b4f16 100644 --- a/src/note-c/note.h +++ b/src/note-c/note.h @@ -147,6 +147,7 @@ void NoteSetUserAgentCPU(int cpu_mem, int cpu_mhz, int cpu_cores, char *cpu_vend // Calls to the functions set above void NoteDebug(const char *message); void NoteDebugln(const char *message); +void NoteDebugIntln(const char *message, int n); void NoteDebugf(const char *format, ...); void *NoteMalloc(size_t size); void NoteFree(void *); @@ -261,6 +262,23 @@ bool NoteGetTemperature(JNUMBER *temp); bool NoteGetContact(char *nameBuf, int nameBufLen, char *orgBuf, int orgBufLen, char *roleBuf, int roleBufLen, char *emailBuf, int emailBufLen); bool NoteSetContact(char *nameBuf, char *orgBuf, char *roleBuf, char *emailBuf); +// Definitions necessary for payload descriptor +#define NP_SEGTYPE_LEN 4 +#define NP_SEGLEN_LEN sizeof(uint32_t) +#define NP_SEGHDR_LEN (NP_SEGTYPE_LEN + NP_SEGLEN_LEN) +typedef struct { + uint8_t *data; + uint32_t alloc; + uint32_t length; +} NotePayloadDesc; +bool NotePayloadSaveAndSleep(NotePayloadDesc *desc, uint32_t seconds, const char *modes); +bool NotePayloadRetrieveAfterSleep(NotePayloadDesc *desc); +void NotePayloadSet(NotePayloadDesc *desc, uint8_t *buf, uint32_t buflen); +void NotePayloadFree(NotePayloadDesc *desc); +bool NotePayloadAddSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t plen); +bool NotePayloadFindSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t *plen); +bool NotePayloadGetSegment(NotePayloadDesc *desc, const char segtype[NP_SEGTYPE_LEN], void *pdata, uint32_t len); + // C macro to convert a number to a string for use below #define _tstring(x) #x