diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c1dba13..36fb4659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,8 @@ target_compile_options( -Wextra -Wpedantic -Werror + -Og + -ggdb ) target_include_directories( note_c diff --git a/n_helpers.c b/n_helpers.c index 28ef2178..777739c7 100644 --- a/n_helpers.c +++ b/n_helpers.c @@ -91,7 +91,7 @@ static const char BINARY_EOP = '\n'; @brief Get the length of the data stored on the Notecard. If there's no data stored on the Notecard, then `*len` will return 0. - @param len [out] the length of the decoded contents of the Notecard's binary + @param len [out] The length of the decoded contents of the Notecard's binary data store. @returns An error string on error and NULL on success. @@ -135,9 +135,9 @@ const char * NoteBinaryDataDecodedLength(uint32_t *len) @brief Get the required buffer length to receive the entire binary object stored on the Notecard. - @param len [out] the length required to hold the entire contents of the + @param len [out] The length required to hold the entire contents of the Notecard's binary data store. If there's no data stored on the - Notecard, then `*len` will return 0. + Notecard, then `len` will return 0. @returns An error string on error and NULL on success. */ @@ -226,26 +226,30 @@ const char * NoteBinaryDataReset(void) @param inLen The length of the binary payload. @param outBuf The buffer to write the decoded payload to. This can be the same address as inBuf, allowing for in-place decoding. - @param outLen On input, holds the length of outBuf. On output, holds the - length of the decoded data. + @param outLen The length of outBuf. + @param decLen [out] The length of the decoded data. @returns NULL on success, else an error string pointer. + + @note When in-place decoding, the overall length shrinks and the + decoded data will be left-justified in the buffer. */ /**************************************************************************/ const char * NoteBinaryDecode(const uint8_t *inBuf, uint32_t inLen, - uint8_t *outBuf, uint32_t *outLen) + uint8_t *outBuf, uint32_t outLen, + uint32_t *decLen) { - if (inBuf == NULL || outBuf == NULL || outLen == NULL) { + if (inBuf == NULL || outBuf == NULL || decLen == NULL) { NOTE_C_LOG_ERROR("NULL parameter"); return ERRSTR("NULL parameter", c_err); } - if (*outLen < cobsGuaranteedFit(inLen)) { + if (outLen < cobsGuaranteedFit(inLen)) { NOTE_C_LOG_ERROR("output buffer too small"); return ERRSTR("output buffer too small", c_err); } - *outLen = cobsDecode((uint8_t *)inBuf, inLen, BINARY_EOP, outBuf); + *decLen = cobsDecode((uint8_t *)inBuf, inLen, BINARY_EOP, outBuf); return NULL; } @@ -259,28 +263,33 @@ const char * NoteBinaryDecode(const uint8_t *inBuf, uint32_t inLen, @param inLen The length of the data to encode. @param outBuf The buffer to write the encoded data to. This can be the same address as inBuf, allowing for in-place encoding. - @param outLen On input, holds the length of outBuf. On output, holds the - length of the encoded data. + @param outLen The length of outBuf. + @param encLen [out] The length of the encoded data. @returns NULL on success, else an error string pointer. + + @note When in-place encoding, the overall length expands. Therefore, the + unencoded data should first be right-justified in the buffer, then the + value of `outBuf` should be set to the beginning of the buffer. */ /**************************************************************************/ const char * NoteBinaryEncode(const uint8_t *inBuf, uint32_t inLen, - uint8_t *outBuf, uint32_t *outLen) + uint8_t *outBuf, uint32_t outLen, + uint32_t *encLen) { - if (inBuf == NULL || outBuf == NULL || outLen == NULL) { + if (inBuf == NULL || outBuf == NULL || encLen == NULL) { NOTE_C_LOG_ERROR("NULL parameter"); return ERRSTR("NULL parameter", c_err); } - if (*outLen < cobsEncodedMaxLength(inLen)) { - if (*outLen < cobsEncodedLength(inBuf, inLen)) { + if (outLen < cobsEncodedMaxLength(inLen)) { + if (outLen < cobsEncodedLength(inBuf, inLen)) { NOTE_C_LOG_ERROR("output buffer too small"); return ERRSTR("output buffer too small", c_err); } } - *outLen = cobsEncode((uint8_t *)inBuf, inLen, BINARY_EOP, outBuf); + *encLen = cobsEncode((uint8_t *)inBuf, inLen, BINARY_EOP, outBuf); return NULL; } @@ -335,15 +344,61 @@ uint32_t NoteBinaryMaxEncodedLength(uint32_t unencodedLength) /*! @brief Receive a large binary object from the Notecard's binary buffer - @param buffer A buffer to hold the binary object - @param bufLen The total length of the provided buffer + @param buffer A buffer to hold the binary object + @param bufLen The total length of the provided buffer + @param dataLen [out] The length of the decoded data received from the + Notecard. + + @returns NULL on success, else an error string pointer. + + @note The buffer must be large enough to hold the encoded value of the + data store contents from the requested offset for the specified length. + To determine the necessary buffer size the Notecard's binary data, use + `NoteBinaryDataEncodedLength()`. + */ +/**************************************************************************/ +const char * NoteBinaryReceive(uint8_t *buffer, uint32_t bufLen, + uint32_t *dataLen) +{ + const char *err = NULL; + uint32_t decodedLen = 0; + + // Validate parameter(s) + if (!buffer) { + err = ERRSTR("NULL buffer", c_bad); + NOTE_C_LOG_ERROR(err); + } + else if (!dataLen) { + err = ERRSTR("NULL dataLen not allowed", c_bad); + NOTE_C_LOG_ERROR(err); + } + else { + // Calculate the data length available on the Notecard + if ((err = NoteBinaryDataDecodedLength(&decodedLen))) { + decodedLen = 0; + } + // Request entire binary data store from Notecard + else if ((err = NoteBinaryReceiveRange(buffer, bufLen, 0, decodedLen))) { + decodedLen = 0; + } + + // Populate `dataLen` if available + *dataLen = decodedLen; + } + + return err; +} + +//**************************************************************************/ +/*! + @brief Receive a large binary range from the Notecard's binary buffer + + @param buffer A buffer to hold the binary range + @param bufLen The total length of the provided buffer @param decodedOffset The offset to the decoded binary data already residing on the Notecard - @param decodedLen [in/out] The length of the decoded data to fetch from the - Notecard. If you wish to fetch the entire buffer from the - given offset, set this value to `NOTE_C_BINARY_RX_ALL`. - This parameter will return the bytes actually received from - the Notecard. + @param decodedLen The length of the decoded data to fetch from the + Notecard. @returns NULL on success, else an error string pointer. @@ -354,21 +409,25 @@ uint32_t NoteBinaryMaxEncodedLength(uint32_t unencodedLength) entire buffer use `NoteBinaryDataEncodedLength()` instead. */ /**************************************************************************/ -const char * NoteBinaryReceive(uint8_t * buffer, uint32_t bufLen, - uint32_t decodedOffset, uint32_t * decodedLen) +const char * NoteBinaryReceiveRange(uint8_t *buffer, uint32_t bufLen, + uint32_t decodedOffset, + uint32_t decodedLen) { // Validate parameter(s) if (!buffer) { - NOTE_C_LOG_ERROR("NULL buffer"); - return ERRSTR("NULL buffer", c_err); + const char *err = ERRSTR("NULL buffer", c_bad); + NOTE_C_LOG_ERROR(err); + return err; } - if (!decodedLen) { - NOTE_C_LOG_ERROR("decodedLen cannot be NULL"); - return ERRSTR("decodedLen cannot be NULL", c_err); + if (bufLen < (cobsEncodedMaxLength(decodedLen) + 1)) { + const char *err = ERRSTR("insufficient buffer size", c_bad); + NOTE_C_LOG_ERROR(err); + return err; } - if (bufLen < (cobsEncodedMaxLength(*decodedLen) + 1)) { - NOTE_C_LOG_ERROR("insufficient buffer size"); - return ERRSTR("insufficient buffer size", c_err); + if (decodedLen == 0) { + const char *err = ERRSTR("decodedLen cannot be zero (0)", c_bad); + NOTE_C_LOG_ERROR(err); + return err; } // Claim Notecard Mutex @@ -379,25 +438,27 @@ const char * NoteBinaryReceive(uint8_t * buffer, uint32_t bufLen, J *req = NoteNewRequest("card.binary.get"); if (req) { JAddIntToObject(req, "offset", decodedOffset); - JAddIntToObject(req, "length", *decodedLen); + JAddIntToObject(req, "length", decodedLen); // Ensure the transaction doesn't return an error. J *rsp = NoteRequestResponse(req); if (NoteResponseError(rsp)) { NOTE_C_LOG_ERROR(JGetString(rsp,"err")); JDelete(rsp); - NOTE_C_LOG_ERROR("failed to initialize binary transaction"); + const char *err = ERRSTR("failed to initialize binary transaction", c_err); + NOTE_C_LOG_ERROR(err); _UnlockNote(); - return ERRSTR("failed to initialize binary transaction", c_err); + return err; } // Examine "status" from the response to evaluate the MD5 checksum. strlcpy(status, JGetString(rsp,"status"), NOTE_MD5_HASH_STRING_SIZE); JDelete(rsp); } else { - NOTE_C_LOG_ERROR("unable to allocate request"); + const char *err = ERRSTR("unable to allocate request", c_mem); + NOTE_C_LOG_ERROR(err); _UnlockNote(); - return ERRSTR("unable to allocate request", c_mem); + return err; } // Read raw bytes from the active interface into a predefined buffer @@ -414,8 +475,9 @@ const char * NoteBinaryReceive(uint8_t * buffer, uint32_t bufLen, // Check buffer overflow condition if (available) { - NOTE_C_LOG_ERROR("unexpected data available"); - return ERRSTR("unexpected data available", c_err); + const char *err = ERRSTR("unexpected data available", c_err); + NOTE_C_LOG_ERROR(err); + return err; } // _ChunkedReceive returns the raw bytes that came off the wire, which @@ -423,14 +485,18 @@ const char * NoteBinaryReceive(uint8_t * buffer, uint32_t bufLen, // part of the binary payload, so we decrement the length by 1 to remove it. --bufLen; - uint32_t decLen = bufLen; + uint32_t decLen = 0; // Decode it in place, which is safe because decoding shrinks - err = NoteBinaryDecode(buffer, bufLen, buffer, &decLen); + err = NoteBinaryDecode(buffer, bufLen, buffer, bufLen, &decLen); if (err) { return err; } - // Return the decoded length in the decodedLen out parameter. - *decodedLen = decLen; + // Ensure the decoded length matches the caller's expectations. + if (decodedLen != decLen) { + const char *err = ERRSTR("length mismatch after decoding", c_err); + NOTE_C_LOG_ERROR(err); + return err; + } // Put a hard marker at the end of the decoded portion of the buffer. This // enables easier human reasoning when interrogating the buffer, if the @@ -441,8 +507,9 @@ const char * NoteBinaryReceive(uint8_t * buffer, uint32_t bufLen, char hashString[NOTE_MD5_HASH_STRING_SIZE] = {0}; NoteMD5HashString(buffer, decLen, hashString, NOTE_MD5_HASH_STRING_SIZE); if (strncmp(hashString, status, NOTE_MD5_HASH_STRING_SIZE)) { - NOTE_C_LOG_ERROR("computed MD5 does not match received MD5"); - return ERRSTR("computed MD5 does not match received MD5", c_err); + const char *err = ERRSTR("computed MD5 does not match received MD5", c_err); + NOTE_C_LOG_ERROR(err); + return err; } // Return `NULL` if success, else error string pointer @@ -534,13 +601,16 @@ const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, const uint32_t dataShift = (bufLen - unencodedLen); memmove(unencodedData + dataShift, unencodedData, unencodedLen); - // `encLen` holds the buffer size available for encoding. The `- 1` accounts - // for one byte of space we need to save for a newline to mark the end of - // the packet. When `NoteBinaryEncode()` returns, `encLen` will hold the - // encoded length. - uint32_t encLen = (bufLen - 1); + // Create an alias to help reason about the buffer after in-place encoding. uint8_t * const encodedData = unencodedData; - err = NoteBinaryEncode(unencodedData + dataShift, unencodedLen, encodedData, &encLen); + + // Update unencoded data pointer + unencodedData += dataShift; + uint32_t encLen = 0; + + // `(bufLen - 1)` accounts for one byte of space we need to save for a + // newline to mark the end of the packet. + err = NoteBinaryEncode(unencodedData, unencodedLen, encodedData, (bufLen - 1), &encLen); if (err) { return err; } @@ -569,13 +639,13 @@ const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, // On errors, we restore the caller's input buffer by COBS // decoding it. The caller is then able to retry transmission // with their original pointer to this buffer. - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR("failed to initialize binary transaction", c_err); } } else { NOTE_C_LOG_ERROR("unable to allocate request"); _UnlockNote(); - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR("unable to allocate request", c_mem); } @@ -587,7 +657,7 @@ const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, // Ensure transaction was successful if (err) { - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR(err, c_err); } @@ -595,7 +665,7 @@ const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, rsp = NoteRequestResponse(NoteNewRequest("card.binary")); if (!rsp) { NOTE_C_LOG_ERROR("unable to validate request"); - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR("unable to validate request", c_err); } @@ -611,13 +681,13 @@ const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, continue; } NOTE_C_LOG_ERROR("binary data invalid"); - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR("binary data invalid", c_bad); } else { JDelete(rsp); NOTE_C_LOG_ERROR("unexpected error received during " "confirmation"); - NoteBinaryDecode(encodedData, encLen, unencodedData, &bufLen); + NoteBinaryDecode(encodedData, encLen, encodedData, bufLen, &encLen); return ERRSTR("unexpected error received during confirmation", c_bad); } diff --git a/note.h b/note.h index e78757c9..0e1c9192 100644 --- a/note.h +++ b/note.h @@ -317,18 +317,22 @@ void NoteMD5HashToString(unsigned char *hash, char *strbuf, unsigned long buflen // High-level helper functions that are both useful and serve to show developers // how to call the API -#define NOTE_C_BINARY_RX_ALL 0 const char * NoteBinaryDataDecodedLength(uint32_t *len); const char * NoteBinaryDataEncodedLength(uint32_t *len); const char * NoteBinaryDataReset(void); const char * NoteBinaryDecode(const uint8_t *inBuf, uint32_t inLen, - uint8_t *outBuf, uint32_t *outLen); + uint8_t *outBuf, uint32_t outLen, + uint32_t *decLen); const char * NoteBinaryEncode(const uint8_t *inBuf, uint32_t inLen, - uint8_t *outBuf, uint32_t *outLen); + uint8_t *outBuf, uint32_t outLen, + uint32_t *encLen); uint32_t NoteBinaryMaxDecodedLength(uint32_t bufferSize); uint32_t NoteBinaryMaxEncodedLength(uint32_t unencodedLength); const char * NoteBinaryReceive(uint8_t *buffer, uint32_t bufLen, - uint32_t decodedOffset, uint32_t *decodedLen); + uint32_t *dataLen); +const char * NoteBinaryReceiveRange(uint8_t *buffer, uint32_t bufLen, + uint32_t decodedOffset, + uint32_t decodedLen); const char * NoteBinaryTransmit(uint8_t *unencodedData, uint32_t unencodedLen, uint32_t bufLen, uint32_t notecardOffset); uint32_t NoteSetSTSecs(uint32_t secs); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3b621ef1..f3e6023b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -46,6 +46,10 @@ macro(add_test TEST_NAME) PRIVATE note_c PRIVATE Catch2::Catch2WithMain ) + target_compile_options(${TEST_NAME} + PRIVATE -Og + PRIVATE -ggdb + ) list(APPEND TEST_TARGETS ${TEST_NAME}) @@ -152,6 +156,7 @@ add_test(NoteBinaryEncode_test) add_test(NoteBinaryMaxEncodedLength_test) add_test(NoteBinaryMaxDecodedLength_test) add_test(NoteBinaryReceive_test) +add_test(NoteBinaryReceiveRange_test) add_test(NoteBinaryTransmit_test) if(NOTE_C_COVERAGE) diff --git a/test/src/NoteBinaryDecode_test.cpp b/test/src/NoteBinaryDecode_test.cpp index 0dee3060..0453a6d5 100644 --- a/test/src/NoteBinaryDecode_test.cpp +++ b/test/src/NoteBinaryDecode_test.cpp @@ -22,9 +22,10 @@ DEFINE_FFF_GLOBALS FAKE_VALUE_FUNC(uint32_t, cobsDecode, uint8_t *, uint32_t, uint8_t, uint8_t *) uint8_t inBuf[12]; -uint32_t inLen; +const uint32_t inLen = sizeof(inBuf); uint8_t outBuf[10]; -uint32_t outLen; +const uint32_t outLen = sizeof(outBuf); +uint32_t decLen; namespace { @@ -32,26 +33,25 @@ namespace SCENARIO("NoteBinaryDecode") { RESET_FAKE(cobsDecode); - uint32_t inLen = sizeof(inBuf); - uint32_t outLen = sizeof(outBuf); + decLen = 0; GIVEN("Bad parameters are supplied") { WHEN("inBuf is NULL") { - const char *err = NoteBinaryDecode(NULL, inLen, outBuf, &outLen); + const char *err = NoteBinaryDecode(NULL, inLen, outBuf, outLen, &decLen); THEN("An error is returned") { CHECK(err != NULL); } } WHEN("outBuf is NULL") { - const char *err = NoteBinaryDecode(inBuf, inLen, NULL, &outLen); + const char *err = NoteBinaryDecode(inBuf, inLen, NULL, outLen, &decLen); THEN("An error is returned") { CHECK(err != NULL); } } - WHEN("outLen is NULL") { - const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, NULL); + WHEN("decLen is NULL") { + const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, outLen, NULL); THEN("An error is returned") { CHECK(err != NULL); @@ -59,7 +59,7 @@ SCENARIO("NoteBinaryDecode") } WHEN("outLen is less than the size required for the worst-case decoding") { uint32_t badOutLen = (cobsGuaranteedFit(inLen) - 1); - const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, &badOutLen); + const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, badOutLen, &decLen); THEN("An error is returned") { CHECK(err != NULL); @@ -70,7 +70,7 @@ SCENARIO("NoteBinaryDecode") GIVEN("Parameters are in order") { const uint32_t EXPECTED_RESULT = 79; cobsDecode_fake.return_val = EXPECTED_RESULT; - const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, &outLen); + const char *err = NoteBinaryDecode(inBuf, inLen, outBuf, outLen, &decLen); THEN("cobsDecode is invoked") { CHECK(cobsDecode_fake.call_count > 0); @@ -84,7 +84,7 @@ SCENARIO("NoteBinaryDecode") } THEN("The result is returned without modification") { - CHECK(EXPECTED_RESULT == outLen); + CHECK(EXPECTED_RESULT == decLen); } } } diff --git a/test/src/NoteBinaryEncode_test.cpp b/test/src/NoteBinaryEncode_test.cpp index cc29822f..f400952b 100644 --- a/test/src/NoteBinaryEncode_test.cpp +++ b/test/src/NoteBinaryEncode_test.cpp @@ -24,9 +24,10 @@ DEFINE_FFF_GLOBALS FAKE_VALUE_FUNC(uint32_t, cobsEncode, uint8_t *, uint32_t, uint8_t, uint8_t *) uint8_t inBuf[10] = "Hi there!"; -uint32_t inLen; +uint32_t inLen = strlen((const char *)inBuf); uint8_t outBuf[12]; -uint32_t outLen; +uint32_t outLen = sizeof(outBuf); +uint32_t encLen; namespace { @@ -34,26 +35,25 @@ namespace SCENARIO("NoteBinaryEncode") { RESET_FAKE(cobsEncode); - uint32_t inLen = strlen((const char *)inBuf); - uint32_t outLen = sizeof(outBuf); + encLen = 0; GIVEN("Bad parameters are supplied") { WHEN("inBuf is NULL") { - const char *err = NoteBinaryEncode(NULL, inLen, outBuf, &outLen); + const char *err = NoteBinaryEncode(NULL, inLen, outBuf, outLen, &encLen); THEN("An error is returned") { CHECK(err != NULL); } } WHEN("outBuf is NULL") { - const char *err = NoteBinaryEncode(inBuf, inLen, NULL, &outLen); + const char *err = NoteBinaryEncode(inBuf, inLen, NULL, outLen, &encLen); THEN("An error is returned") { CHECK(err != NULL); } } - WHEN("outLen is NULL") { - const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, NULL); + WHEN("encLen is NULL") { + const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, outLen, NULL); THEN("An error is returned") { CHECK(err != NULL); @@ -61,7 +61,7 @@ SCENARIO("NoteBinaryEncode") } WHEN("outLen is less than the size required for in place encoding") { uint32_t badOutLen = (cobsEncodedLength(inBuf, inLen) - 1); - const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, &badOutLen); + const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, badOutLen, &encLen); THEN("An error is returned") { CHECK(err != NULL); @@ -72,7 +72,7 @@ SCENARIO("NoteBinaryEncode") GIVEN("Parameters are in order") { const uint32_t EXPECTED_RESULT = 79; cobsEncode_fake.return_val = EXPECTED_RESULT; - const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, &outLen); + const char *err = NoteBinaryEncode(inBuf, inLen, outBuf, outLen, &encLen); THEN("cobsEncode is invoked") { CHECK(cobsEncode_fake.call_count > 0); @@ -86,7 +86,7 @@ SCENARIO("NoteBinaryEncode") } THEN("The result is returned without modification") { - CHECK(EXPECTED_RESULT == outLen); + CHECK(EXPECTED_RESULT == encLen); } } } diff --git a/test/src/NoteBinaryReceiveRange_test.cpp b/test/src/NoteBinaryReceiveRange_test.cpp new file mode 100644 index 00000000..501e4aae --- /dev/null +++ b/test/src/NoteBinaryReceiveRange_test.cpp @@ -0,0 +1,202 @@ +/*! + * @file NoteBinaryReceiveRange_test.cpp + * + * Written by the Blues Inc. team. + * + * Copyright (c) 2023 Blues Inc. MIT License. Use of this source code is + * governed by licenses granted by the copyright holder including that found in + * the + * LICENSE + * file. + * + */ + +#ifdef NOTE_C_TEST + +#include +#include "fff.h" + +#include "n_lib.h" + +DEFINE_FFF_GLOBALS +FAKE_VALUE_FUNC(J *, NoteNewRequest, const char *) +FAKE_VALUE_FUNC(const char *, NoteBinaryDataEncodedLength, uint32_t *) +FAKE_VALUE_FUNC(J *, NoteRequestResponse, J *) +FAKE_VALUE_FUNC(const char *, NoteChunkedReceive, uint8_t *, uint32_t *, bool, + size_t, uint32_t *) +FAKE_VOID_FUNC(NoteLockNote) +FAKE_VOID_FUNC(NoteUnlockNote) + +// Most of these variables have to be global because they're accessed in +// lambda functions used as fakes for various note-c functions. They can't be +// captured by the lambdas because the lambdas need to be convertible to plain +// old function pointers in order to be used by the fff mocking/fake framework. +// If a lambda captures anything, it can't be converted in this way, and you get +// a compiler error. +uint8_t buf[32]; +uint32_t bufLen = sizeof(buf); +uint32_t dataLen = 17; + +char rawMsg[] = "Hello Blues!"; +uint32_t rawMsgLen = strlen(rawMsg); + +namespace +{ + +SCENARIO("NoteBinaryReceiveRange") +{ + RESET_FAKE(NoteNewRequest); + RESET_FAKE(NoteBinaryDataEncodedLength); + RESET_FAKE(NoteRequestResponse); + RESET_FAKE(NoteChunkedReceive); + RESET_FAKE(NoteLockNote); + RESET_FAKE(NoteUnlockNote); + + const uint32_t OFFSET_ZERO = 0; + + NoteSetFnDefault(malloc, free, NULL, NULL); + + // These fakes are the default. Tests below may override them to exercise + // different scenarios. + NoteNewRequest_fake.custom_fake = [](const char *req) -> J* { + return JCreateObject(); + }; + NoteBinaryDataEncodedLength_fake.custom_fake = [](uint32_t *size) + -> const char * { + *size = bufLen; + + return NULL; + }; + NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { + JDelete(req); + J *rsp = JCreateObject(); + char hash[NOTE_MD5_HASH_STRING_SIZE] = {0}; + NoteMD5HashString((unsigned char *)rawMsg, rawMsgLen, hash, + NOTE_MD5_HASH_STRING_SIZE); + JAddStringToObject(rsp, "status", hash); + + return rsp; + }; + + GIVEN("Allocating the card.binary.get request fails") { + NoteNewRequest_fake.custom_fake = NULL; + NoteNewRequest_fake.return_val = NULL; + + WHEN("NoteBinaryReceiveRange is called") { + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteNewRequest_fake.call_count > 0); + THEN("An error is returned") { + CHECK(err != NULL); + } + } + } + + GIVEN("The response to the card.binary.get request has an error") { + NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { + JDelete(req); + J *rsp = JCreateObject(); + JAddStringToObject(rsp, "err", "some error"); + + return rsp; + }; + + WHEN("NoteBinaryReceiveRange is called") { + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteRequestResponse_fake.call_count > 0); + THEN("An error is returned") { + CHECK(err != NULL); + } + } + } + + GIVEN("NoteChunkedReceive returns an error") { + NoteChunkedReceive_fake.return_val = "some error"; + + WHEN("NoteBinaryReceiveRange is called") { + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteChunkedReceive_fake.call_count > 0); + THEN("An error is returned") { + CHECK(err != NULL); + } + } + } + + GIVEN("NoteChunkedReceive indicates there's unexpectedly more data " + "available") { + NoteChunkedReceive_fake.custom_fake = [](uint8_t *, uint32_t *, bool, + size_t, uint32_t *available) -> const char* { + *available = 1; + + return NULL; + }; + + WHEN("NoteBinaryReceiveRange is called") { + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteChunkedReceive_fake.call_count > 0); + THEN("An error is returned") { + CHECK(err != NULL); + } + } + } + + GIVEN("The binary payload is received") { + NoteChunkedReceive_fake.custom_fake = [](uint8_t *buffer, uint32_t *size, + bool, size_t, uint32_t *available) -> const char* { + uint32_t outLen = 0; + NoteBinaryEncode((uint8_t *)rawMsg, rawMsgLen, buffer, *size, &outLen); + + buffer[outLen] = '\n'; + *size = outLen + 1; + *available = 0; + + return NULL; + }; + + AND_GIVEN("The computed MD5 hash doesn't match the status field") { + NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { + JDelete(req); + J *rsp = JCreateObject(); + JAddStringToObject(rsp, "status", "garbage"); + + return rsp; + }; + + WHEN("NoteBinaryReceiveRange is called") { + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteChunkedReceive_fake.call_count > 0); + REQUIRE(NoteRequestResponse_fake.call_count > 0); + THEN("An error is returned") { + CHECK(err != NULL); + } + } + } + + AND_GIVEN("The computed MD5 matches the status field") { + WHEN("NoteBinaryReceiveRange is called") { + uint32_t dataLen = rawMsgLen; + const char *err = NoteBinaryReceiveRange(buf, bufLen, OFFSET_ZERO, dataLen); + + REQUIRE(NoteChunkedReceive_fake.call_count > 0); + THEN("No error is returned") { + CHECK(err == NULL); + } + + THEN("The decoded payload is as expected, with no trailing " + "newline") { + CHECK(memcmp(buf, rawMsg, dataLen) == 0); + } + } + } + } + CHECK(NoteLockNote_fake.call_count > 0); + CHECK(NoteLockNote_fake.call_count == NoteUnlockNote_fake.call_count); +} + +} + +#endif // NOTE_C_TEST diff --git a/test/src/NoteBinaryReceive_test.cpp b/test/src/NoteBinaryReceive_test.cpp index e85edf72..6714fd88 100644 --- a/test/src/NoteBinaryReceive_test.cpp +++ b/test/src/NoteBinaryReceive_test.cpp @@ -19,186 +19,104 @@ #include "n_lib.h" DEFINE_FFF_GLOBALS -FAKE_VALUE_FUNC(J *, NoteNewRequest, const char *) -FAKE_VALUE_FUNC(const char *, NoteBinaryDataEncodedLength, uint32_t *) -FAKE_VALUE_FUNC(J *, NoteRequestResponse, J *) -FAKE_VALUE_FUNC(const char *, NoteChunkedReceive, uint8_t *, uint32_t *, bool, - size_t, uint32_t *) -FAKE_VOID_FUNC(NoteLockNote) -FAKE_VOID_FUNC(NoteUnlockNote) - -// Most of these variables have to be global because they're accessed in -// lambda functions used as fakes for various note-c functions. They can't be -// captured by the lambdas because the lambdas need to be convertible to plain -// old function pointers in order to be used by the fff mocking/fake framework. -// If a lambda captures anything, it can't be converted in this way, and you get -// a compiler error. -uint8_t buf[32]; -uint32_t bufLen = sizeof(buf); -uint32_t dataLen = 0; +FAKE_VALUE_FUNC(const char *, NoteBinaryDataDecodedLength, uint32_t *) +FAKE_VALUE_FUNC(const char *, NoteBinaryReceiveRange, uint8_t *, uint32_t, uint32_t, uint32_t) -char rawMsg[] = "Hello Blues!"; -uint32_t rawMsgLen = strlen(rawMsg); +uint8_t buffer[12]; +const uint32_t bufLen = sizeof(buffer); +uint32_t dataLen = 0; namespace { SCENARIO("NoteBinaryReceive") { - RESET_FAKE(NoteNewRequest); - RESET_FAKE(NoteBinaryDataEncodedLength); - RESET_FAKE(NoteRequestResponse); - RESET_FAKE(NoteChunkedReceive); - RESET_FAKE(NoteLockNote); - RESET_FAKE(NoteUnlockNote); - - const uint32_t OFFSET_ZERO = 0; - - NoteSetFnDefault(malloc, free, NULL, NULL); - - // These fakes are the default. Tests below may override them to exercise - // different scenarios. - NoteNewRequest_fake.custom_fake = [](const char *req) -> J* { - return JCreateObject(); - }; - NoteBinaryDataEncodedLength_fake.custom_fake = [](uint32_t *size) - -> const char * { - *size = bufLen; - - return NULL; - }; - NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { - JDelete(req); - J *rsp = JCreateObject(); - char hash[NOTE_MD5_HASH_STRING_SIZE] = {0}; - NoteMD5HashString((unsigned char *)rawMsg, rawMsgLen, hash, - NOTE_MD5_HASH_STRING_SIZE); - JAddStringToObject(rsp, "status", hash); - - return rsp; - }; - - GIVEN("Allocating the card.binary.get request fails") { - NoteNewRequest_fake.custom_fake = NULL; - NoteNewRequest_fake.return_val = NULL; - - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); - - REQUIRE(NoteNewRequest_fake.call_count > 0); - THEN("An error is returned") { - CHECK(err != NULL); - } - } - } - - GIVEN("The response to the card.binary.get request has an error") { - NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { - JDelete(req); - J *rsp = JCreateObject(); - JAddStringToObject(rsp, "err", "some error"); - - return rsp; - }; + RESET_FAKE(NoteBinaryDataDecodedLength); + RESET_FAKE(NoteBinaryReceiveRange); + dataLen = 17; - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); + GIVEN("Bad parameters are supplied") { + WHEN("buffer is NULL") { + const char *err = NoteBinaryReceive(NULL, bufLen, &dataLen); - REQUIRE(NoteRequestResponse_fake.call_count > 0); THEN("An error is returned") { CHECK(err != NULL); } } - } - - GIVEN("NoteChunkedReceive returns an error") { - NoteChunkedReceive_fake.return_val = "some error"; - - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); + WHEN("dataLen is NULL") { + const char *err = NoteBinaryReceive(buffer, bufLen, NULL); - REQUIRE(NoteChunkedReceive_fake.call_count > 0); THEN("An error is returned") { CHECK(err != NULL); } } } - GIVEN("NoteChunkedReceive indicates there's unexpectedly more data " - "available") { - NoteChunkedReceive_fake.custom_fake = [](uint8_t *, uint32_t *, bool, - size_t, uint32_t *available) -> const char* { - *available = 1; + GIVEN("NoteBinaryDataDecodedLength() is invoked") { + WHEN("An error is encountered") { + const char *errMsg = "ERROR! Hacking too much time!"; + NoteBinaryDataDecodedLength_fake.return_val = errMsg; + const char *err = NoteBinaryReceive(buffer, bufLen, &dataLen); - return NULL; - }; - - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); + REQUIRE(NoteBinaryDataDecodedLength_fake.call_count > 0); + THEN("NoteBinaryReceiveRange() is not invoked") { + CHECK(NoteBinaryReceiveRange_fake.call_count == 0); + } + THEN("The dataLen is set to zero") { + CHECK(dataLen == 0); + } + THEN("The error is returned") { + CHECK(!strcmp(err,errMsg)); + } + } + WHEN("No error is encountered") { + const uint32_t DECODED_LEN = 79; + NoteBinaryDataDecodedLength_fake.custom_fake = [](uint32_t *len) -> const char * { + *len = DECODED_LEN; + return NULL; + }; + const char *err = NoteBinaryReceive(buffer, bufLen, &dataLen); - REQUIRE(NoteChunkedReceive_fake.call_count > 0); - THEN("An error is returned") { - CHECK(err != NULL); + REQUIRE(NoteBinaryDataDecodedLength_fake.call_count > 0); + THEN("NoteBinaryReceiveRange() is invoked") { + CHECK(NoteBinaryReceiveRange_fake.call_count > 0); + } + THEN("The decoded length is passed to NoteBinaryReceiveRange()") { + CHECK(NoteBinaryReceiveRange_fake.arg3_history[0] == DECODED_LEN); } } } - - GIVEN("The binary payload is received") { - NoteChunkedReceive_fake.custom_fake = [](uint8_t *buffer, uint32_t *size, - bool, size_t, uint32_t *available) -> const char* { - uint32_t outLen = *size; - NoteBinaryEncode((uint8_t *)rawMsg, rawMsgLen, buffer, &outLen); - - buffer[outLen] = '\n'; - *size = outLen + 1; - *available = 0; - + GIVEN("NoteBinaryReceiveRange() is invoked") { + const uint32_t DECODED_LEN = 79; + NoteBinaryDataDecodedLength_fake.custom_fake = [](uint32_t *len) -> const char * { + *len = DECODED_LEN; return NULL; }; - - AND_GIVEN("The computed MD5 hash doesn't match the status field") { - NoteRequestResponse_fake.custom_fake = [](J *req) -> J * { - JDelete(req); - J *rsp = JCreateObject(); - JAddStringToObject(rsp, "status", "garbage"); - - return rsp; - }; - - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); - - REQUIRE(NoteChunkedReceive_fake.call_count > 0); - REQUIRE(NoteRequestResponse_fake.call_count > 0); - THEN("An error is returned") { - CHECK(err != NULL); - } + WHEN("An error is encountered") { + const char *errMsg = "ERROR! Hacking too much time!"; + NoteBinaryReceiveRange_fake.return_val = errMsg; + const char *err = NoteBinaryReceive(buffer, bufLen, &dataLen); + + REQUIRE(NoteBinaryReceiveRange_fake.call_count > 0); + THEN("The dataLen is set to zero") { + CHECK(dataLen == 0); + } + THEN("The error is returned") { + CHECK(!strcmp(err,errMsg)); } } + WHEN("No error is encountered") { + const char *err = NoteBinaryReceive(buffer, bufLen, &dataLen); - AND_GIVEN("The computed MD5 matches the status field") { - WHEN("NoteBinaryReceive is called") { - const char *err = NoteBinaryReceive(buf, bufLen, OFFSET_ZERO, &dataLen); - - REQUIRE(NoteChunkedReceive_fake.call_count > 0); - THEN("No error is returned") { - CHECK(err == NULL); - } - - THEN("The length of the payload is returned in the dataLen out" - " parameter") { - CHECK(dataLen == rawMsgLen); - } - - THEN("The decoded payload is as expected, with no trailing " - "newline") { - CHECK(memcmp(buf, rawMsg, dataLen) == 0); - } + REQUIRE(NoteBinaryReceiveRange_fake.call_count > 0); + THEN("The decoded length is returned") { + CHECK(dataLen == DECODED_LEN); + } + THEN("The return value is NULL") { + CHECK(err == NULL); } } } - CHECK(NoteLockNote_fake.call_count > 0); - CHECK(NoteLockNote_fake.call_count == NoteUnlockNote_fake.call_count); } } diff --git a/test/src/NoteBinaryTransmit_test.cpp b/test/src/NoteBinaryTransmit_test.cpp index 701e0929..df6eb129 100644 --- a/test/src/NoteBinaryTransmit_test.cpp +++ b/test/src/NoteBinaryTransmit_test.cpp @@ -184,8 +184,8 @@ SCENARIO("NoteBinaryTransmit") // Discover the actual encoded length of the data const uint32_t tempBufLen = cobsEncodedMaxLength(dataLen); uint8_t *tempBuf = (uint8_t *)malloc(tempBufLen); - uint32_t newBufLen = tempBufLen; - REQUIRE(!NoteBinaryEncode(buf, dataLen, tempBuf, &newBufLen)); + uint32_t newBufLen = 0; + REQUIRE(!NoteBinaryEncode(buf, dataLen, tempBuf, tempBufLen, &newBufLen)); free(tempBuf); WHEN("NoteBinaryTransmit is called") {