From d374a7bca7338f1f12c15dbc0736becb0fcf2b48 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 25 Sep 2025 18:27:56 +0200 Subject: [PATCH 01/14] feat: catching PARSING_ERR for return/error type mismatch --- src/client.h | 3 +++ src/decoder.h | 12 ++++++++++-- src/error.h | 1 + 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/client.h b/src/client.h index 8d95748..06cd8ae 100644 --- a/src/client.h +++ b/src/client.h @@ -68,6 +68,9 @@ class RPCClient { lastError.code = tmp_error.code; lastError.traceback = tmp_error.traceback; return true; + } else if (tmp_error.code == PARSING_ERR) { // catches the parsing error + lastError.code = tmp_error.code; + lastError.traceback = tmp_error.traceback; } return false; } diff --git a/src/decoder.h b/src/decoder.h index f3ab3d3..7df0e9a 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -76,9 +76,17 @@ class RpcDecoder { MsgPack::object::nil_t nil; if (unpacker.unpackable(nil)){ // No error - if (!unpacker.deserialize(nil, result)) return false; + if (!unpacker.deserialize(nil, result)) { + error.code = PARSING_ERR; + error.traceback = "Result not parsable (check type)"; + return false; + } } else { // RPC returned an error - if (!unpacker.deserialize(error, nil)) return false; + if (!unpacker.deserialize(error, nil)) { + error.code = PARSING_ERR; + error.traceback = "RPC Error not parsable (check type)"; + return false; + } } reset_packet(); diff --git a/src/error.h b/src/error.h index 925b30a..bece5d8 100644 --- a/src/error.h +++ b/src/error.h @@ -17,6 +17,7 @@ #include "MsgPack.h" #define NO_ERR 0x00 +#define PARSING_ERR 0xFC #define MALFORMED_CALL_ERR 0xFD #define FUNCTION_NOT_FOUND_ERR 0xFE #define GENERIC_ERR 0xFF From a593af1994ad514fb0c23bfd2f5f52155465928b Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 25 Sep 2025 20:28:38 +0200 Subject: [PATCH 02/14] feat: decoder handles RPC response PARSING_ERR discarding it feat: RpcDecoder.discard --- src/decoder.h | 50 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/decoder.h b/src/decoder.h index 7df0e9a..5515042 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -62,16 +62,33 @@ class RpcDecoder { MsgPack::Unpacker unpacker; unpacker.clear(); - size_t res_size = get_packet_size(); - if (!unpacker.feed(_raw_buffer, res_size)) return false; + if (!unpacker.feed(_raw_buffer, _packet_size)) return false; MsgPack::arr_size_t resp_size; int resp_type; uint32_t resp_id; - if (!unpacker.deserialize(resp_size, resp_type, resp_id)) return false; - if (resp_size.size() != RESPONSE_SIZE) return false; - if (resp_type != RESP_MSG) return false; + if (!unpacker.deserialize(resp_size, resp_type, resp_id)) { + error.code = PARSING_ERR; + error.traceback = "Non parsable RPC response headers (check type)"; + discard(); + return false; + } + if (resp_size.size() != RESPONSE_SIZE) { + error.code = PARSING_ERR; + error.traceback = "Wrong RPC response size"; + discard(); + return false; + } + if (resp_type != RESP_MSG) { + // This should never happen + error.code = GENERIC_ERR; + error.traceback = "Unexpected response type"; + discard(); + return false; + } + + // DO NOT MODIFY THIS if resp_id is not what is expected then the RPC response belongs to s/o else if (resp_id != msg_id) return false; MsgPack::object::nil_t nil; @@ -79,18 +96,20 @@ class RpcDecoder { if (!unpacker.deserialize(nil, result)) { error.code = PARSING_ERR; error.traceback = "Result not parsable (check type)"; + discard(); return false; } } else { // RPC returned an error if (!unpacker.deserialize(error, nil)) { error.code = PARSING_ERR; error.traceback = "RPC Error not parsable (check type)"; + discard(); return false; } } + consume(_packet_size); reset_packet(); - consume(res_size); return true; } @@ -111,8 +130,7 @@ class RpcDecoder { unpacker.clear(); if (!unpacker.feed(_raw_buffer, _packet_size)) { // feed should not fail at this point - consume(_packet_size); - reset_packet(); + discard(); return ""; }; @@ -121,27 +139,23 @@ class RpcDecoder { MsgPack::arr_size_t req_size; if (!unpacker.deserialize(req_size, msg_type)) { - consume(_packet_size); - reset_packet(); + discard(); return ""; // Header not unpackable } if (msg_type == CALL_MSG && req_size.size() == REQUEST_SIZE) { uint32_t msg_id; if (!unpacker.deserialize(msg_id, method)) { - consume(_packet_size); - reset_packet(); + discard(); return ""; // Method not unpackable } } else if (msg_type == NOTIFY_MSG && req_size.size() == NOTIFY_SIZE) { if (!unpacker.deserialize(method)) { - consume(_packet_size); - reset_packet(); + discard(); return ""; // Method not unpackable } } else { - consume(_packet_size); - reset_packet(); + discard(); return ""; // Invalid request size/type } @@ -260,6 +274,10 @@ class RpcDecoder { return consume(packet_size); } + void discard() { + consume(_packet_size); + reset_packet(); + } void reset_packet() { _packet_type = NO_MSG; From e2bfcd7557c7f007aa6abd0a2891641d131daeb2 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Thu, 25 Sep 2025 22:10:56 +0200 Subject: [PATCH 03/14] feat: client.get_response fills in a per-call error param fixing lastError mismatch feat: RpcError .copy --- src/client.h | 16 +++++++--------- src/error.h | 5 +++++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/client.h b/src/client.h index 06cd8ae..c133343 100644 --- a/src/client.h +++ b/src/client.h @@ -41,7 +41,8 @@ class RPCClient { } // blocking call - while (!get_response(msg_id_wait, result)){ + RpcError tmp_error; + while (!get_response(msg_id_wait, result, tmp_error)) { //delay(1); } @@ -60,17 +61,14 @@ class RPCClient { } template - bool get_response(const uint32_t wait_id, RType& result) { - RpcError tmp_error; + bool get_response(const uint32_t wait_id, RType& result, RpcError& error) { decoder->decode(); - if (decoder->get_response(wait_id, result, tmp_error)) { - lastError.code = tmp_error.code; - lastError.traceback = tmp_error.traceback; + if (decoder->get_response(wait_id, result, error)) { + lastError.copy(error); return true; - } else if (tmp_error.code == PARSING_ERR) { // catches the parsing error - lastError.code = tmp_error.code; - lastError.traceback = tmp_error.traceback; + } else if (error.code == PARSING_ERR) { // catches the parsing error + lastError.copy(error); } return false; } diff --git a/src/error.h b/src/error.h index bece5d8..c98cb7d 100644 --- a/src/error.h +++ b/src/error.h @@ -35,6 +35,11 @@ struct RpcError { RpcError(const int c, MsgPack::str_t tb) : code(c), traceback(std::move(tb)) {} + void copy(const RpcError& err) { + code = err.code; + traceback = err.traceback; + } + MSGPACK_DEFINE(code, traceback); // -> [code, traceback] }; From 7340e3b90ad40818185b5d8871e652f60f8826a1 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Fri, 26 Sep 2025 09:09:52 +0200 Subject: [PATCH 04/14] feat: get_discard_packages (client/server/decoder) mod: recoded decoder.get_response logic discarding broken packages with right ID --- src/client.h | 4 ++-- src/decoder.h | 41 +++++++++++++++++++++++------------------ src/server.h | 2 ++ 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/client.h b/src/client.h index c133343..04f26da 100644 --- a/src/client.h +++ b/src/client.h @@ -67,12 +67,12 @@ class RPCClient { if (decoder->get_response(wait_id, result, error)) { lastError.copy(error); return true; - } else if (error.code == PARSING_ERR) { // catches the parsing error - lastError.copy(error); } return false; } + uint32_t get_discarded_packets() const {return decoder->get_discarded_packets();} + }; #endif //RPCLITE_CLIENT_H diff --git a/src/decoder.h b/src/decoder.h index 5515042..883abeb 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -68,50 +68,48 @@ class RpcDecoder { int resp_type; uint32_t resp_id; - if (!unpacker.deserialize(resp_size, resp_type, resp_id)) { + if (!unpacker.deserialize(resp_size, resp_type, resp_id)) return false; + + // ReSharper disable once CppDFAUnreachableCode + if (resp_id != msg_id) return false; + + // msg_id OK packet will be consumed. + if (resp_type != RESP_MSG) { + // This should never happen error.code = PARSING_ERR; - error.traceback = "Non parsable RPC response headers (check type)"; + error.traceback = "Unexpected response type"; discard(); - return false; + return true; } + if (resp_size.size() != RESPONSE_SIZE) { - error.code = PARSING_ERR; - error.traceback = "Wrong RPC response size"; - discard(); - return false; - } - if (resp_type != RESP_MSG) { // This should never happen - error.code = GENERIC_ERR; - error.traceback = "Unexpected response type"; + error.code = PARSING_ERR; + error.traceback = "Unexpected RPC response size"; discard(); - return false; + return true; } - // DO NOT MODIFY THIS if resp_id is not what is expected then the RPC response belongs to s/o else - if (resp_id != msg_id) return false; - MsgPack::object::nil_t nil; if (unpacker.unpackable(nil)){ // No error if (!unpacker.deserialize(nil, result)) { error.code = PARSING_ERR; error.traceback = "Result not parsable (check type)"; discard(); - return false; + return true; } } else { // RPC returned an error if (!unpacker.deserialize(error, nil)) { error.code = PARSING_ERR; error.traceback = "RPC Error not parsable (check type)"; discard(); - return false; + return true; } } consume(_packet_size); reset_packet(); return true; - } bool send_response(const MsgPack::Packer& packer) const { @@ -143,6 +141,7 @@ class RpcDecoder { return ""; // Header not unpackable } + // ReSharper disable once CppDFAUnreachableCode if (msg_type == CALL_MSG && req_size.size() == REQUEST_SIZE) { uint32_t msg_id; if (!unpacker.deserialize(msg_id, method)) { @@ -205,11 +204,13 @@ class RpcDecoder { if (type != CALL_MSG && type != RESP_MSG && type != NOTIFY_MSG) { consume(bytes_checked); + _discarded_packets++; break; // Not a valid RPC type (could be type=WRONG_MSG) } if ((type == CALL_MSG && container_size != REQUEST_SIZE) || (type == RESP_MSG && container_size != RESPONSE_SIZE) || (type == NOTIFY_MSG && container_size != NOTIFY_SIZE)) { consume(bytes_checked); + _discarded_packets++; break; // Not a valid RPC format } @@ -232,6 +233,8 @@ class RpcDecoder { size_t size() const {return _bytes_stored;} + uint32_t get_discarded_packets() const {return _discarded_packets;} + friend class DecoderTester; private: @@ -241,6 +244,7 @@ class RpcDecoder { int _packet_type = NO_MSG; size_t _packet_size = 0; uint32_t _msg_id = 0; + uint32_t _discarded_packets = 0; bool buffer_full() const { return _bytes_stored == BufferSize; } @@ -277,6 +281,7 @@ class RpcDecoder { void discard() { consume(_packet_size); reset_packet(); + _discarded_packets++; } void reset_packet() { diff --git a/src/server.h b/src/server.h index 3200b40..f3a5803 100644 --- a/src/server.h +++ b/src/server.h @@ -87,6 +87,8 @@ class RPCServer { } + uint32_t get_discarded_packets() const {return decoder->get_discarded_packets();} + private: RpcDecoder<>* decoder = nullptr; RpcFunctionDispatcher dispatcher{}; From cd969e0d716e46da8def7e26a9c0c443631c7308 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Fri, 26 Sep 2025 17:24:41 +0200 Subject: [PATCH 05/14] ver: 0.2.0 --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 1fa6098..94d97ec 100644 --- a/library.json +++ b/library.json @@ -11,7 +11,7 @@ "url": "https://github.com/eigen-value", "maintainer": true }, - "version": "0.1.3", + "version": "0.2.0", "license": "MPL2.0", "frameworks": "arduino", "platforms": "*", diff --git a/library.properties b/library.properties index 8ebdfb6..1a36aeb 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino_RPClite -version=0.1.3 +version=0.2.0 author=Arduino, Lucio Rossi (eigen-value) maintainer=Arduino, Lucio Rossi (eigen-value) sentence=A MessagePack RPC library for Arduino From 6af013472ca1916083aed46954b98193cb3c39a7 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Fri, 26 Sep 2025 17:24:41 +0200 Subject: [PATCH 06/14] ver: 0.2.0 --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 1fa6098..94d97ec 100644 --- a/library.json +++ b/library.json @@ -11,7 +11,7 @@ "url": "https://github.com/eigen-value", "maintainer": true }, - "version": "0.1.3", + "version": "0.2.0", "license": "MPL2.0", "frameworks": "arduino", "platforms": "*", diff --git a/library.properties b/library.properties index 8ebdfb6..1a36aeb 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino_RPClite -version=0.1.3 +version=0.2.0 author=Arduino, Lucio Rossi (eigen-value) maintainer=Arduino, Lucio Rossi (eigen-value) sentence=A MessagePack RPC library for Arduino From fb059ea76b53485772133aaf81e61c701bb96969 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Mon, 13 Oct 2025 17:40:23 +0200 Subject: [PATCH 07/14] feat: decoder.h can pick 1st available response anywhere in the buffer + tests --- examples/decoder_tests/decoder_tester.h | 30 +++ examples/decoder_tests/decoder_tests.ino | 230 ++++++++++++++++++++++- src/decoder.h | 101 +++++++--- 3 files changed, 331 insertions(+), 30 deletions(-) diff --git a/examples/decoder_tests/decoder_tester.h b/examples/decoder_tests/decoder_tester.h index 266064f..61c00b0 100644 --- a/examples/decoder_tests/decoder_tester.h +++ b/examples/decoder_tests/decoder_tester.h @@ -21,10 +21,40 @@ class DecoderTester { DecoderTester(RpcDecoder<>& _d): decoder(_d){} + void first_response_info() { + if (!decoder.response_queued()) { + Serial.println("No response queued"); + return; + } + Serial.println("-- First response info --"); + Serial.print("RESP OFFSET: "); + Serial.println(static_cast(decoder._response_offset)); + Serial.print("RESP SIZE: "); + Serial.println(static_cast(decoder._response_size)); + } + + size_t get_response_size() { + return decoder._response_size; + } + + size_t get_response_offset() { + return decoder._response_offset; + } + + template + bool get_response(const uint32_t msg_id, RType& result, RpcError& error) { + return decoder.get_response(msg_id, result, error); + } + void crop_bytes(size_t size, size_t offset){ decoder.consume(size, offset); } + void pop_first() { + uint8_t temp_buffer[512]; + decoder.pop_packet(temp_buffer, 512); + } + void print_raw_buf(){ Serial.print("Decoder raw buffer content: "); diff --git a/examples/decoder_tests/decoder_tests.ino b/examples/decoder_tests/decoder_tests.ino index b823bf9..46e92c0 100644 --- a/examples/decoder_tests/decoder_tests.ino +++ b/examples/decoder_tests/decoder_tests.ino @@ -48,7 +48,7 @@ void runDecoderTest(const char* label) { Serial.println("-- Done --\n"); } -void runDecoderConsumeTest(const char* label, size_t second_packet_sz) { +void runDecoderConsumeTest(const char* label, size_t expected_2nd_pack_size) { Serial.println(label); print_buf(); @@ -63,20 +63,184 @@ void runDecoderConsumeTest(const char* label, size_t second_packet_sz) { delay(50); } + dt.first_response_info(); + + while (!decoder.response_queued()) { + Serial.println("1st response not ready"); + decoder.decode(); + delay(50); + } + size_t pack_size = decoder.get_packet_size(); Serial.print("1st Packet size: "); Serial.println(pack_size); + dt.first_response_info(); + + if ((dt.get_response_offset()!=pack_size)||(dt.get_response_size()!=expected_2nd_pack_size)) { + Serial.println("ERROR parsing 1st response\n"); + return; + } + + Serial.print("Consuming 2nd packet of given size: "); + Serial.println(dt.get_response_size()); + + dt.crop_bytes(dt.get_response_size(), dt.get_response_offset()); + + dt.print_raw_buf(); + + Serial.println("-- Done --\n"); +} + +void runDecoderPopFirstTest(const char* label, size_t expected_2nd_pack_size) { + Serial.println(label); + + print_buf(); + DummyTransport dummy_transport(packer.data(), packer.size()); + RpcDecoder<> decoder(dummy_transport); + + DecoderTester dt(decoder); + + while (!decoder.packet_incoming()) { + Serial.println("Packet not ready"); + decoder.decode(); + delay(50); + } + + while (!decoder.response_queued()) { + Serial.println("1st response not ready"); + decoder.decode(); + delay(50); + } + + dt.first_response_info(); + + size_t pack_size = decoder.get_packet_size(); + Serial.print("Consuming 1st Packet of size: "); + Serial.println(pack_size); + dt.pop_first(); + dt.print_raw_buf(); + + dt.first_response_info(); + + if ((dt.get_response_offset()!=0)||(dt.get_response_size()!=expected_2nd_pack_size)) { + Serial.println("ERROR moving 1st response\n"); + return; + } + Serial.print("Consuming 2nd packet of given size: "); - Serial.println(second_packet_sz); + Serial.println(dt.get_response_size()); + + dt.crop_bytes(dt.get_response_size(), dt.get_response_offset()); + + dt.print_raw_buf(); + dt.first_response_info(); + + Serial.println("-- Done --\n"); +} + +void runDecoderGetResponseTest(const char* label, size_t expected_2nd_pack_size, int _id) { + Serial.println(label); + + print_buf(); + DummyTransport dummy_transport(packer.data(), packer.size()); + RpcDecoder<> decoder(dummy_transport); + + DecoderTester dt(decoder); + + while (!decoder.packet_incoming()) { + Serial.println("Packet not ready"); + decoder.decode(); + delay(50); + } + + dt.first_response_info(); + + while (!decoder.response_queued()) { + Serial.println("1st response not ready"); + decoder.decode(); + delay(50); + } + + size_t pack_size = decoder.get_packet_size(); + Serial.print("1st Packet size: "); + Serial.println(pack_size); + + dt.first_response_info(); + + if ((dt.get_response_offset()!=pack_size)||(dt.get_response_size()!=expected_2nd_pack_size)) { + Serial.println("ERROR parsing 1st response\n"); + return; + } + + Serial.print("Getting response (2nd packet) size: "); + Serial.println(dt.get_response_size()); + + int res; + RpcError _err; + dt.get_response(_id, res, _err); + + Serial.print("Result: "); + Serial.println(res); + + dt.print_raw_buf(); + + Serial.println("-- Done --\n"); +} + + +void runDecoderGetTopResponseTest(const char* label, size_t expected_size, int _id) { + Serial.println(label); + + print_buf(); + DummyTransport dummy_transport(packer.data(), packer.size()); + RpcDecoder<> decoder(dummy_transport); + + DecoderTester dt(decoder); + + while (!decoder.packet_incoming()) { + Serial.println("Packet not ready"); + decoder.decode(); + delay(50); + } - dt.crop_bytes(second_packet_sz, pack_size); + dt.first_response_info(); + + while (!decoder.response_queued()) { + Serial.println("1st response not ready"); + decoder.decode(); + delay(50); + } + + size_t pack_size = decoder.get_packet_size(); + Serial.print("1st Packet size: "); + Serial.println(pack_size); + + dt.first_response_info(); + + if ((dt.get_response_offset()!=0)||(dt.get_response_size()!=expected_size)) { + Serial.println("ERROR parsing 1st response\n"); + return; + } + + Serial.print("Getting response size: "); + Serial.println(dt.get_response_size()); + + int res; + RpcError _err; + dt.get_response(_id, res, _err); + + Serial.print("Result: "); + Serial.println(res); dt.print_raw_buf(); + dt.first_response_info(); + Serial.println("-- Done --\n"); } + void testNestedArrayRequest() { packer.clear(); MsgPack::arr_size_t outer_arr(3); @@ -166,6 +330,63 @@ void testMultipleRpcPackets() { } +// Multiple RPCs in one buffer. Pop the 1st request and then the 2nd response +void testPopRpcPackets() { + packer.clear(); + MsgPack::arr_size_t req_sz(4); + MsgPack::arr_size_t par_sz(2); + MsgPack::arr_size_t resp_sz(4); + MsgPack::object::nil_t nil; + + // 1st request + packer.serialize(req_sz, 0, 1, "sum", par_sz, 10, 20); + // 2nd response + packer.serialize(resp_sz, 1, 1, nil, 42); + // 3rd request + packer.serialize(req_sz, 0, 2, "echo", par_sz, "Hello", true); + + runDecoderPopFirstTest("== Test: Pop-first packet ==", 5); + +} + +// Multiple RPCs in one buffer. Get the response in the buffer +void testGetResponsePacket() { + packer.clear(); + MsgPack::arr_size_t req_sz(4); + MsgPack::arr_size_t par_sz(2); + MsgPack::arr_size_t resp_sz(4); + MsgPack::object::nil_t nil; + + // 1st request + packer.serialize(req_sz, 0, 1, "sum", par_sz, 10, 20); + // 2nd response + packer.serialize(resp_sz, 1, 1, nil, 101); + // 3rd request + packer.serialize(req_sz, 0, 2, "echo", par_sz, "Hello", true); + + runDecoderGetResponseTest("== Test: Get response packet ==", 5, 1); + +} + +// Multiple RPCs in one buffer. The response is top of the buffer +void testGetTopResponsePacket() { + packer.clear(); + MsgPack::arr_size_t req_sz(4); + MsgPack::arr_size_t par_sz(2); + MsgPack::arr_size_t resp_sz(4); + MsgPack::object::nil_t nil; + + // 1st response + packer.serialize(resp_sz, 1, 1, nil, 101); + // 2nd request + packer.serialize(req_sz, 0, 2, "echo", par_sz, "Hello", true); + // 3rd request + packer.serialize(req_sz, 0, 1, "sum", par_sz, 30, 30); + + runDecoderGetTopResponseTest("== Test: Get top response packet ==", 5, 1); + +} + // Binary parameter (e.g., binary blob) void testBinaryParam() { packer.clear(); @@ -225,6 +446,9 @@ void setup() { testDeepNestedStructure(); testArrayOfMapsResponse(); testMultipleRpcPackets(); + testPopRpcPackets(); + testGetResponsePacket(); + testGetTopResponsePacket(); testBinaryParam(); testExtensionParam(); testCombinedComplexBuffer(); diff --git a/src/decoder.h b/src/decoder.h index 883abeb..3636646 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -57,12 +57,12 @@ class RpcDecoder { template bool get_response(const uint32_t msg_id, RType& result, RpcError& error) { - if (!packet_incoming() || _packet_type!=RESP_MSG) return false; + if (!response_queued()) return false; MsgPack::Unpacker unpacker; unpacker.clear(); - if (!unpacker.feed(_raw_buffer, _packet_size)) return false; + if (!unpacker.feed(_raw_buffer + _response_offset, _response_size)) return false; MsgPack::arr_size_t resp_size; int resp_type; @@ -78,7 +78,7 @@ class RpcDecoder { // This should never happen error.code = PARSING_ERR; error.traceback = "Unexpected response type"; - discard(); + crop_response(true); return true; } @@ -86,7 +86,7 @@ class RpcDecoder { // This should never happen error.code = PARSING_ERR; error.traceback = "Unexpected RPC response size"; - discard(); + crop_response(true); return true; } @@ -95,20 +95,19 @@ class RpcDecoder { if (!unpacker.deserialize(nil, result)) { error.code = PARSING_ERR; error.traceback = "Result not parsable (check type)"; - discard(); + crop_response(true); return true; } } else { // RPC returned an error if (!unpacker.deserialize(error, nil)) { error.code = PARSING_ERR; error.traceback = "RPC Error not parsable (check type)"; - discard(); + crop_response(true); return true; } } - consume(_packet_size); - reset_packet(); + crop_response(false); return true; } @@ -188,37 +187,60 @@ class RpcDecoder { void parse_packet(){ - if (packet_incoming()){return;} + size_t offset = 0; + + if (packet_incoming()) { + if (response_queued()) { + return; + } + offset = _response_offset; + } size_t bytes_checked = 0; size_t container_size; int type; MsgPack::Unpacker unpacker; - while (bytes_checked < _bytes_stored){ + while (bytes_checked + offset < _bytes_stored){ bytes_checked++; unpacker.clear(); - if (!unpacker.feed(_raw_buffer, bytes_checked)) continue; + if (!unpacker.feed(_raw_buffer + offset, bytes_checked)) continue; if (unpackTypedArray(unpacker, container_size, type)) { if (type != CALL_MSG && type != RESP_MSG && type != NOTIFY_MSG) { - consume(bytes_checked); + consume(bytes_checked, offset); _discarded_packets++; break; // Not a valid RPC type (could be type=WRONG_MSG) } if ((type == CALL_MSG && container_size != REQUEST_SIZE) || (type == RESP_MSG && container_size != RESPONSE_SIZE) || (type == NOTIFY_MSG && container_size != NOTIFY_SIZE)) { - consume(bytes_checked); + consume(bytes_checked, offset); _discarded_packets++; break; // Not a valid RPC format } - _packet_type = type; - _packet_size = bytes_checked; + if (offset == 0) { // that's the first packet + _packet_type = type; + _packet_size = bytes_checked; + if (type == RESP_MSG) { // and it is for a client + _response_offset = 0; + _response_size = bytes_checked; + } else if (!response_queued()) { + _response_offset = bytes_checked; + _response_size = 0; + } + } else { + if (type == RESP_MSG) { // we have a response packet in the queue + _response_offset = offset; + _response_size = bytes_checked; + } else { // look further + _response_offset = offset + bytes_checked; + _response_size = 0; + } + } + break; - } else { - continue; } } @@ -227,6 +249,10 @@ class RpcDecoder { bool packet_incoming() const { return _packet_size >= MIN_RPC_BYTES; } + bool response_queued() const { + return (_response_offset < _bytes_stored) && (_response_size > 0); + } + int packet_type() const { return _packet_type; } size_t get_packet_size() const { return _packet_size;} @@ -243,6 +269,8 @@ class RpcDecoder { size_t _bytes_stored = 0; int _packet_type = NO_MSG; size_t _packet_size = 0; + size_t _response_offset = 0; + size_t _response_size = 0; uint32_t _msg_id = 0; uint32_t _discarded_packets = 0; @@ -275,9 +303,23 @@ class RpcDecoder { } reset_packet(); + if (_response_offset >= packet_size) { + _response_offset -= packet_size; + } return consume(packet_size); } + void crop_response(bool discard) { + consume(_response_size, _response_offset); + if (_response_offset==0) { // the response was in the first position + reset_packet(); + } + reset_response(); + if (discard) { + _discarded_packets++; + } + } + void discard() { consume(_packet_size); reset_packet(); @@ -289,18 +331,23 @@ class RpcDecoder { _packet_size = 0; } -size_t consume(size_t size, size_t offset = 0) { - // Boundary checks - if (offset + size > _bytes_stored || size == 0) return 0; - - size_t remaining_bytes = _bytes_stored - size; - for (size_t i=offset; i _bytes_stored || size == 0) return 0; + + size_t remaining_bytes = _bytes_stored - size; + for (size_t i=offset; i Date: Tue, 21 Oct 2025 11:47:58 +0200 Subject: [PATCH 08/14] ops: quench linter warning about lib manager --- .github/workflows/arduino-lint.yml | 2 +- library.json | 2 +- library.properties | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/arduino-lint.yml b/.github/workflows/arduino-lint.yml index a85a4b5..a897cdb 100644 --- a/.github/workflows/arduino-lint.yml +++ b/.github/workflows/arduino-lint.yml @@ -24,6 +24,6 @@ jobs: uses: arduino/arduino-lint-action@v2 with: compliance: strict - library-manager: submit # remember to change to 'update' after the library is published on the libraries index + library-manager: update # remember to change to 'update' after the library is published on the libraries index # Always use this setting for official repositories. Remove for 3rd party projects. official: true \ No newline at end of file diff --git a/library.json b/library.json index 94d97ec..98abd73 100644 --- a/library.json +++ b/library.json @@ -4,7 +4,7 @@ "description": "A MessagePack RPC library for Arduino", "repository": { "type": "git", - "url": "https://github.com/bcmi-labs/Arduino_RPClite" + "url": "https://github.com/arduino-libraries/Arduino_RPCLite" }, "authors": { "name": "Lucio Rossi", diff --git a/library.properties b/library.properties index 1a36aeb..307032e 100644 --- a/library.properties +++ b/library.properties @@ -5,6 +5,6 @@ maintainer=Arduino, Lucio Rossi (eigen-value) sentence=A MessagePack RPC library for Arduino paragraph=allows to create a client/server architecture using MessagePack as the serialization format. It follows the MessagePack-RPC protocol specification. It is designed to be lightweight and easy to use, making it suitable for embedded systems and IoT applications. category=Communication -url=https://www.arduino.cc/ +url=https://github.com/arduino-libraries/Arduino_RPCLite architectures=* depends=MsgPack (>=0.4.2) From d8ba53d09455bba6464499246fd4dacf38fb7427 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Tue, 21 Oct 2025 12:36:16 +0200 Subject: [PATCH 09/14] ops: add compile target unoq --- .github/workflows/compile-examples.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml index da9e4fe..b0443ba 100644 --- a/.github/workflows/compile-examples.yml +++ b/.github/workflows/compile-examples.yml @@ -61,6 +61,8 @@ jobs: artifact-name-suffix: arduino-renesas_portenta-portenta_c33 - fqbn: arduino:renesas_uno:unor4wifi artifact-name-suffix: arduino-renesas_uno-unor4wifi + - fqbn: arduino:zephyr:unoq + artifact-name-suffix: arduino-zephyr-unoq steps: - name: Checkout From 73b70d7b24338baf558e9e2c5354a2411c938d46 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Fri, 28 Nov 2025 12:05:44 +0100 Subject: [PATCH 10/14] impr: decoder code fix: decoder not consuming resp in buffer --- src/decoder.h | 74 +++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 41 deletions(-) diff --git a/src/decoder.h b/src/decoder.h index 3636646..c969f50 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -15,11 +15,11 @@ #include "MsgPack.h" #include "transport.h" #include "rpclite_utils.h" +#include "error.h" using namespace RpcUtils::detail; #define MIN_RPC_BYTES 4 -#define CHUNK_SIZE 32 template class RpcDecoder { @@ -78,7 +78,9 @@ class RpcDecoder { // This should never happen error.code = PARSING_ERR; error.traceback = "Unexpected response type"; - crop_response(true); + consume(_response_size, _response_offset); + if (_response_offset == 0) reset_packet(); + _discarded_packets++; return true; } @@ -86,7 +88,9 @@ class RpcDecoder { // This should never happen error.code = PARSING_ERR; error.traceback = "Unexpected RPC response size"; - crop_response(true); + consume(_response_size, _response_offset); + if (_response_offset == 0) reset_packet(); + _discarded_packets++; return true; } @@ -95,19 +99,25 @@ class RpcDecoder { if (!unpacker.deserialize(nil, result)) { error.code = PARSING_ERR; error.traceback = "Result not parsable (check type)"; - crop_response(true); + consume(_response_size, _response_offset); + if (_response_offset == 0) reset_packet(); + _discarded_packets++; return true; } } else { // RPC returned an error if (!unpacker.deserialize(error, nil)) { error.code = PARSING_ERR; error.traceback = "RPC Error not parsable (check type)"; - crop_response(true); + consume(_response_size, _response_offset); + if (_response_offset == 0) reset_packet(); + _discarded_packets++; return true; } } - crop_response(false); + if (_response_offset == 0) reset_packet(); + consume(_response_size, _response_offset); + return true; } @@ -190,10 +200,8 @@ class RpcDecoder { size_t offset = 0; if (packet_incoming()) { - if (response_queued()) { - return; - } - offset = _response_offset; + if (response_queued()) return; // parsing complete + offset = _response_offset; // looking for a RESP } size_t bytes_checked = 0; @@ -220,24 +228,16 @@ class RpcDecoder { break; // Not a valid RPC format } - if (offset == 0) { // that's the first packet + if (offset == 0) { _packet_type = type; _packet_size = bytes_checked; - if (type == RESP_MSG) { // and it is for a client - _response_offset = 0; - _response_size = bytes_checked; - } else if (!response_queued()) { - _response_offset = bytes_checked; - _response_size = 0; - } + } + + if (type == RESP_MSG) { + _response_offset = offset; + _response_size = bytes_checked; // response queued } else { - if (type == RESP_MSG) { // we have a response packet in the queue - _response_offset = offset; - _response_size = bytes_checked; - } else { // look further - _response_offset = offset + bytes_checked; - _response_size = 0; - } + _response_offset = offset + bytes_checked; } break; @@ -250,7 +250,7 @@ class RpcDecoder { bool packet_incoming() const { return _packet_size >= MIN_RPC_BYTES; } bool response_queued() const { - return (_response_offset < _bytes_stored) && (_response_size > 0); + return _response_size > 0; } int packet_type() const { return _packet_type; } @@ -303,23 +303,9 @@ class RpcDecoder { } reset_packet(); - if (_response_offset >= packet_size) { - _response_offset -= packet_size; - } return consume(packet_size); } - void crop_response(bool discard) { - consume(_response_size, _response_offset); - if (_response_offset==0) { // the response was in the first position - reset_packet(); - } - reset_response(); - if (discard) { - _discarded_packets++; - } - } - void discard() { consume(_packet_size); reset_packet(); @@ -332,7 +318,7 @@ class RpcDecoder { } void reset_response() { - _response_offset = _bytes_stored; + _response_offset = 0; _response_size = 0; } @@ -345,6 +331,12 @@ class RpcDecoder { _raw_buffer[i] = _raw_buffer[i+size]; } + if (_response_offset >= offset + size) { + _response_offset -= size; + } else { + reset_response(); + } + _bytes_stored = remaining_bytes; return size; } From 2a4f8646a6ddd5f92d58330603aabdd7bea4e19a Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Mon, 15 Dec 2025 12:42:48 +0100 Subject: [PATCH 11/14] MsgPack: set default log level to WARN to avoid CI issues Not setting the default log level causes a warning on every CI run. Setting it to WARN avoids the warning and is a reasonable default. Signed-off-by: Luca Burelli --- src/decoder.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/decoder.h b/src/decoder.h index c969f50..34e5961 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -6,12 +6,14 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ #ifndef RPCLITE_DECODER_H #define RPCLITE_DECODER_H +// MsgPack log level +#define DEBUGLOG_DEFAULT_LOG_LEVEL_WARN + #include "MsgPack.h" #include "transport.h" #include "rpclite_utils.h" @@ -343,4 +345,4 @@ class RpcDecoder { }; -#endif \ No newline at end of file +#endif From fb521ebf712a390ff364249a56db99f8898af8b1 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Mon, 15 Dec 2025 12:48:19 +0100 Subject: [PATCH 12/14] decoder.h: warning cleanup Fix a compiler warning about a possibly uninitialized variable (the compiler cannot prove that the variable is always initialized before use, even though the code logic guarantees it). Signed-off-by: Luca Burelli --- src/decoder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decoder.h b/src/decoder.h index 34e5961..d0716ca 100644 --- a/src/decoder.h +++ b/src/decoder.h @@ -208,7 +208,7 @@ class RpcDecoder { size_t bytes_checked = 0; size_t container_size; - int type; + int type = NO_MSG; MsgPack::Unpacker unpacker; while (bytes_checked + offset < _bytes_stored){ From 151f39b1e9ede40c58569690ea5c224b5b783ee8 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Mon, 15 Dec 2025 12:42:48 +0100 Subject: [PATCH 13/14] MsgPack: set default log level to WARN to avoid CI issues (2) Missed this spot while setting the default log level for MsgPack to avoid a warning on every CI run. Signed-off-by: Luca Burelli --- src/error.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/error.h b/src/error.h index c98cb7d..d177c52 100644 --- a/src/error.h +++ b/src/error.h @@ -14,6 +14,9 @@ #include +// MsgPack log level +#define DEBUGLOG_DEFAULT_LOG_LEVEL_WARN + #include "MsgPack.h" #define NO_ERR 0x00 @@ -43,4 +46,4 @@ struct RpcError { MSGPACK_DEFINE(code, traceback); // -> [code, traceback] }; -#endif \ No newline at end of file +#endif From 986cb160651b12810a3a01c8c478cdd56f4c0503 Mon Sep 17 00:00:00 2001 From: Lucio Rossi Date: Mon, 15 Dec 2025 17:46:38 +0100 Subject: [PATCH 14/14] ver 0.2.1 --- library.json | 2 +- library.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library.json b/library.json index 98abd73..9666cda 100644 --- a/library.json +++ b/library.json @@ -11,7 +11,7 @@ "url": "https://github.com/eigen-value", "maintainer": true }, - "version": "0.2.0", + "version": "0.2.1", "license": "MPL2.0", "frameworks": "arduino", "platforms": "*", diff --git a/library.properties b/library.properties index 307032e..de0055a 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=Arduino_RPClite -version=0.2.0 +version=0.2.1 author=Arduino, Lucio Rossi (eigen-value) maintainer=Arduino, Lucio Rossi (eigen-value) sentence=A MessagePack RPC library for Arduino