fix: make router socket non-blocking to prevent poll loop stalls
Validate and Test / validate-patches (push) Failing after 13s
Validate and Test / validate-patches (push) Failing after 13s
This commit is contained in:
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
This file is part of the Arduino_RPClite library.
|
||||
|
||||
Copyright (C) Arduino s.r.l. and/or its affiliated companies
|
||||
|
||||
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"
|
||||
#include "error.h"
|
||||
|
||||
using namespace RpcUtils::detail;
|
||||
|
||||
#define MIN_RPC_BYTES 4
|
||||
|
||||
template<size_t BufferSize = DECODER_BUFFER_SIZE>
|
||||
class RpcDecoder {
|
||||
|
||||
explicit RpcDecoder(ITransport& transport) : _transport(&transport) {}
|
||||
|
||||
public:
|
||||
|
||||
RpcDecoder(const RpcDecoder&) = delete;
|
||||
RpcDecoder& operator=(const RpcDecoder&) = delete;
|
||||
|
||||
template<typename... Args>
|
||||
bool send_call(const int call_type, const MsgPack::str_t& method, uint32_t& msg_id, Args&&... args) {
|
||||
|
||||
if (call_type!=CALL_MSG && call_type!=NOTIFY_MSG) return false;
|
||||
|
||||
MsgPack::Packer packer;
|
||||
packer.clear();
|
||||
|
||||
if (call_type == CALL_MSG){
|
||||
msg_id = _msg_id;
|
||||
MsgPack::arr_size_t call_size(REQUEST_SIZE);
|
||||
packer.serialize(call_size, call_type, msg_id, method);
|
||||
} else {
|
||||
MsgPack::arr_size_t call_size(NOTIFY_SIZE);
|
||||
packer.serialize(call_size, call_type, method);
|
||||
}
|
||||
|
||||
MsgPack::arr_size_t arg_size(sizeof...(args));
|
||||
packer.serialize(arg_size, std::forward<Args>(args)...);
|
||||
|
||||
if (send(reinterpret_cast<const uint8_t*>(packer.data()), packer.size()) == packer.size()){
|
||||
_msg_id++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename RType>
|
||||
bool get_response(const uint32_t msg_id, RType& result, RpcError& error) {
|
||||
|
||||
if (!response_queued()) return false;
|
||||
|
||||
MsgPack::Unpacker unpacker;
|
||||
unpacker.clear();
|
||||
|
||||
if (!unpacker.feed(_raw_buffer + _response_offset, _response_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;
|
||||
|
||||
// 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 = "Unexpected response type";
|
||||
consume(_response_size, _response_offset);
|
||||
if (_response_offset == 0) reset_packet();
|
||||
_discarded_packets++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resp_size.size() != RESPONSE_SIZE) {
|
||||
// This should never happen
|
||||
error.code = PARSING_ERR;
|
||||
error.traceback = "Unexpected RPC response size";
|
||||
consume(_response_size, _response_offset);
|
||||
if (_response_offset == 0) reset_packet();
|
||||
_discarded_packets++;
|
||||
return true;
|
||||
}
|
||||
|
||||
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)";
|
||||
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)";
|
||||
consume(_response_size, _response_offset);
|
||||
if (_response_offset == 0) reset_packet();
|
||||
_discarded_packets++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_response_offset == 0) reset_packet();
|
||||
consume(_response_size, _response_offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool send_response(const MsgPack::Packer& packer) const {
|
||||
return send(reinterpret_cast<const uint8_t*>(packer.data()), packer.size()) == packer.size();
|
||||
}
|
||||
|
||||
MsgPack::str_t fetch_rpc_method(){
|
||||
|
||||
if (!packet_incoming()){return "";}
|
||||
|
||||
if (_packet_type != CALL_MSG && _packet_type != NOTIFY_MSG) {
|
||||
return ""; // No RPC
|
||||
}
|
||||
|
||||
MsgPack::Unpacker unpacker;
|
||||
|
||||
unpacker.clear();
|
||||
if (!unpacker.feed(_raw_buffer, _packet_size)) { // feed should not fail at this point
|
||||
discard();
|
||||
return "";
|
||||
};
|
||||
|
||||
int msg_type;
|
||||
MsgPack::str_t method;
|
||||
MsgPack::arr_size_t req_size;
|
||||
|
||||
if (!unpacker.deserialize(req_size, msg_type)) {
|
||||
discard();
|
||||
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)) {
|
||||
discard();
|
||||
return ""; // Method not unpackable
|
||||
}
|
||||
} else if (msg_type == NOTIFY_MSG && req_size.size() == NOTIFY_SIZE) {
|
||||
if (!unpacker.deserialize(method)) {
|
||||
discard();
|
||||
return ""; // Method not unpackable
|
||||
}
|
||||
} else {
|
||||
discard();
|
||||
return ""; // Invalid request size/type
|
||||
}
|
||||
|
||||
return method;
|
||||
|
||||
}
|
||||
|
||||
size_t get_request(uint8_t* buffer, size_t buffer_size) {
|
||||
|
||||
if (_packet_type != CALL_MSG && _packet_type != NOTIFY_MSG) {
|
||||
return 0; // No RPC
|
||||
}
|
||||
|
||||
return pop_packet(buffer, buffer_size);
|
||||
}
|
||||
|
||||
void decode(){
|
||||
advance();
|
||||
parse_packet();
|
||||
}
|
||||
|
||||
// Fill the raw buffer to its capacity
|
||||
void advance() {
|
||||
|
||||
if (_transport->available() && !buffer_full()) {
|
||||
size_t bytes_read = _transport->read(_raw_buffer + _bytes_stored, BufferSize - _bytes_stored);
|
||||
_bytes_stored += bytes_read;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void parse_packet(){
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
if (packet_incoming()) {
|
||||
if (response_queued()) return; // parsing complete
|
||||
offset = _response_offset; // looking for a RESP
|
||||
}
|
||||
|
||||
size_t bytes_checked = 0;
|
||||
size_t container_size;
|
||||
int type = NO_MSG;
|
||||
MsgPack::Unpacker unpacker;
|
||||
|
||||
while (bytes_checked + offset < _bytes_stored){
|
||||
bytes_checked++;
|
||||
unpacker.clear();
|
||||
if (!unpacker.feed(_raw_buffer + offset, bytes_checked)) continue;
|
||||
|
||||
if (unpacker.isUInt7()) { // let the ascii pass
|
||||
consume(1, offset);
|
||||
bytes_checked--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unpackTypedArray(unpacker, container_size, type)) {
|
||||
|
||||
if (!isValidRpc(type, container_size)) {
|
||||
consume(bytes_checked, offset);
|
||||
_discarded_packets++;
|
||||
break; // Not a valid rpclib format
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
_packet_type = type;
|
||||
_packet_size = bytes_checked;
|
||||
}
|
||||
|
||||
if (type == RESP_MSG) {
|
||||
_response_offset = offset;
|
||||
_response_size = bytes_checked; // response queued
|
||||
} else {
|
||||
_response_offset = offset + bytes_checked; // keep on looking for a RESP_MSG
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool packet_incoming() const { return _packet_size >= MIN_RPC_BYTES; }
|
||||
|
||||
bool response_queued() const {
|
||||
return _response_size > 0;
|
||||
}
|
||||
|
||||
int packet_type() const { return _packet_type; }
|
||||
|
||||
size_t get_packet_size() const { return _packet_size;}
|
||||
|
||||
size_t size() const {return _bytes_stored;}
|
||||
|
||||
uint32_t get_discarded_packets() const {return _discarded_packets;}
|
||||
|
||||
friend class RpcDecoderManager;
|
||||
friend class DecoderTester;
|
||||
|
||||
private:
|
||||
ITransport* _transport;
|
||||
uint8_t _raw_buffer[BufferSize]{};
|
||||
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;
|
||||
|
||||
bool buffer_full() const { return _bytes_stored == BufferSize; }
|
||||
|
||||
bool buffer_empty() const { return _bytes_stored == 0;}
|
||||
|
||||
// This is a blocking send, under the assumption _transport->write will always succeed eventually
|
||||
size_t send(const uint8_t* data, const size_t size) const {
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < size) {
|
||||
size_t bytes_written = _transport->write(data + offset, size - offset);
|
||||
offset += bytes_written;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
size_t pop_packet(uint8_t* buffer, size_t buffer_size) {
|
||||
|
||||
if (!packet_incoming()) return 0;
|
||||
|
||||
size_t packet_size = get_packet_size();
|
||||
if (packet_size > buffer_size) return 0;
|
||||
|
||||
for (size_t i = 0; i < packet_size; i++) {
|
||||
buffer[i] = _raw_buffer[i];
|
||||
}
|
||||
|
||||
reset_packet();
|
||||
return consume(packet_size);
|
||||
}
|
||||
|
||||
void discard() {
|
||||
consume(_packet_size);
|
||||
reset_packet();
|
||||
_discarded_packets++;
|
||||
}
|
||||
|
||||
void reset_packet() {
|
||||
_packet_type = NO_MSG;
|
||||
_packet_size = 0;
|
||||
}
|
||||
|
||||
void reset_response() {
|
||||
_response_offset = 0;
|
||||
_response_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<remaining_bytes; i++){
|
||||
_raw_buffer[i] = _raw_buffer[i+size];
|
||||
}
|
||||
|
||||
if (_response_offset >= offset + size) {
|
||||
_response_offset -= size;
|
||||
} else {
|
||||
reset_response();
|
||||
}
|
||||
|
||||
_bytes_stored = remaining_bytes;
|
||||
return size;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user