diff --git a/.github/workflows/notecard-binary-tests.yml b/.github/workflows/notecard-binary-tests.yml index 3d0b40e8..97559e78 100644 --- a/.github/workflows/notecard-binary-tests.yml +++ b/.github/workflows/notecard-binary-tests.yml @@ -296,6 +296,14 @@ jobs: echo "build flags $PLATFORMIO_BUILD_FLAGS" cd $GITHUB_WORKSPACE/$PIO_PROJECT_DIR + # Enable Notecard USB trace output. + printf '{"req":"card.trace","mode":"usb","trace":"req,comm,sync,mem"}\n' > "$RESERVATION_DIR/notecard_usb" + + # Stream Notecard USB output to trace.log in the background. + socat -u GOPEN:"$RESERVATION_DIR/notecard_usb",rawer - >> "$GITHUB_WORKSPACE/trace.log" & + TRACE_PID=$! + echo "Notecard trace capture started (PID: $TRACE_PID)." + # Run the tests. It's important that we provided --no-reset here. If # we don't, PlatformIO tries to fiddle with DTR and RTS, and that # causes an exception because the serial port for the Swan isn't a @@ -309,6 +317,7 @@ jobs: --json-output-path test.json \ --junit-output-path test.xml + kill $TRACE_PID 2>/dev/null kill $PID 2>/dev/null - name: Publish test report @@ -321,6 +330,22 @@ jobs: check_name: Notecard Binary HIL Tests require_tests: true + - name: Upload trace log + uses: actions/upload-artifact@v4 + if: always() + with: + name: notecard-trace + path: trace.log + if-no-files-found: ignore + + - name: Upload MD5 server log + uses: actions/upload-artifact@v4 + if: always() + with: + name: md5srv-log + path: md5srv.log + if-no-files-found: ignore + - name: Cleanup Notehub proxy route if: always() run: | diff --git a/test/hitl/scripts/md5srv.py b/test/hitl/scripts/md5srv.py index 02947f35..bf0d2043 100755 --- a/test/hitl/scripts/md5srv.py +++ b/test/hitl/scripts/md5srv.py @@ -63,6 +63,16 @@ def query_data(self): @cached_property def post_data(self): + transfer_encoding = self.headers.get("Transfer-Encoding", "") + if "chunked" in transfer_encoding.lower(): + data = b"" + while True: + chunk_size = int(self.rfile.readline().strip(), 16) + if chunk_size == 0: + break + data += self.rfile.read(chunk_size) + self.rfile.read(2) # consume trailing \r\n after chunk data + return data content_length = int(self.headers.get("Content-Length", 0)) return self.rfile.read(content_length) @@ -107,15 +117,23 @@ def send_status(self, reply_body = reply_body + '\n' self.wfile.write(reply_body.encode('utf-8')) + def log_request_headers(self): + content_length = self.headers.get("Content-Length", "") + transfer_encoding = self.headers.get("Transfer-Encoding", "") + content_type = self.headers.get("Content-Type", "") + log(f"{self.command} {self.path} | Content-Length: {content_length} | Transfer-Encoding: {transfer_encoding} | Content-Type: {content_type}") + def do(self, handler): """ Handle a request. If an exception is thrown, raises an internal server error. """ try: - # print(self.dump_request()) self.validate_token() handler() except HTTPException as e: + self.log_request_headers() + log(f"HTTP {e.status_code}: {e.message}" + (f" | {e.detail}" if e.detail else "")) self.send_status(e.status_code, e.message, e.detail) except Exception as e: + self.log_request_headers() traceback.print_exc() self.send_status(500, "Internal server error.", str(e)) @@ -207,7 +225,7 @@ def write_note(self): self._write_file(name, chunk, length, payload, md5) def write_file(self): - length = int(self.headers['Content-Length']) + length = int(self.headers['Content-Length'] or len(self.post_data)) if not length: raise HTTPException(400, "Request body is empty.") chunk = self.query_data.get("chunk", None) diff --git a/test/src/_cobsDecode_test.cpp b/test/src/_cobsDecode_test.cpp index 82f8f663..955fa921 100644 --- a/test/src/_cobsDecode_test.cpp +++ b/test/src/_cobsDecode_test.cpp @@ -195,7 +195,8 @@ SCENARIO("_cobsDecode") 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0x02, - 0xFF}; + 0xFF + }; WHEN("The array is COBS-decoded") { const uint32_t decodedLen = _cobsDecode(encoded, sizeof(encoded), eop, decoded); @@ -237,7 +238,8 @@ SCENARIO("_cobsDecode") 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, - 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}; + 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; REQUIRE(decodedLen == sizeof(expectedDecoded)); CHECK(memcmp(decoded, expectedDecoded, sizeof(expectedDecoded)) == 0); } diff --git a/test/src/_cobsEncode_test.cpp b/test/src/_cobsEncode_test.cpp index c5d86438..d8c1ea44 100644 --- a/test/src/_cobsEncode_test.cpp +++ b/test/src/_cobsEncode_test.cpp @@ -197,7 +197,8 @@ SCENARIO("_cobsEncode") 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, - 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}; + 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; WHEN("The array is COBS-encoded") { const uint32_t encodedLen = _cobsEncode(unencoded, 255, eop, encoded); @@ -248,7 +249,8 @@ SCENARIO("_cobsEncode") 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0x02, - 0xFF}; + 0xFF + }; REQUIRE(encodedLen == sizeof(expectedEncoded)); CHECK(memcmp(encoded, expectedEncoded, sizeof(expectedEncoded)) == 0); } diff --git a/test/src/_cobsEncodedLength_test.cpp b/test/src/_cobsEncodedLength_test.cpp index cda7d783..9e0d9afc 100644 --- a/test/src/_cobsEncodedLength_test.cpp +++ b/test/src/_cobsEncodedLength_test.cpp @@ -163,7 +163,8 @@ SCENARIO("_cobsEncodedLength") 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, - 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}; + 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; WHEN("The array is evaluated for its COBS-encoded length") { const uint32_t encodedLen = _cobsEncodedLength(unencoded, sizeof(unencoded)); @@ -205,7 +206,8 @@ SCENARIO("_cobsEncodedLength") 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, - 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF}; + 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + }; CHECK(memcmp(unencoded, original, sizeof(original)) == 0); } }