mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
125 lines
4.6 KiB
C++
125 lines
4.6 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
#include <development/protocol.h>
|
|
|
|
#include <cJSON.h>
|
|
|
|
#include <cstring>
|
|
|
|
namespace {
|
|
|
|
constexpr uint16_t DEFAULT_COLUMNS = 80;
|
|
constexpr uint16_t DEFAULT_ROWS = 24;
|
|
|
|
bool copyString(const cJSON* item, char* out, size_t outSize) {
|
|
if (!cJSON_IsString(item) || item->valuestring == nullptr) {
|
|
return false;
|
|
}
|
|
const size_t length = strlen(item->valuestring);
|
|
if (length >= outSize) {
|
|
return false;
|
|
}
|
|
memcpy(out, item->valuestring, length + 1);
|
|
return true;
|
|
}
|
|
|
|
uint16_t readDimension(const cJSON* item, uint16_t fallback) {
|
|
if (!cJSON_IsNumber(item) || item->valuedouble < 1 || item->valuedouble > UINT16_MAX) {
|
|
return fallback;
|
|
}
|
|
return static_cast<uint16_t>(item->valuedouble);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
DevelopmentHelloError development_protocol_parse_hello(const char* json, size_t length, DevelopmentHello* out_hello, const char** out_message) {
|
|
cJSON* root = cJSON_ParseWithLength(json, length);
|
|
if (root == nullptr || !cJSON_IsObject(root)) {
|
|
cJSON_Delete(root);
|
|
*out_message = "malformed hello";
|
|
return DevelopmentHelloError::Malformed;
|
|
}
|
|
|
|
DevelopmentHello hello {};
|
|
const cJSON* protocol = cJSON_GetObjectItemCaseSensitive(root, "protocol");
|
|
if (!cJSON_IsNumber(protocol) ||
|
|
!copyString(cJSON_GetObjectItemCaseSensitive(root, "mode"), hello.mode, sizeof(hello.mode)) ||
|
|
!copyString(cJSON_GetObjectItemCaseSensitive(root, "client"), hello.client, sizeof(hello.client))) {
|
|
cJSON_Delete(root);
|
|
*out_message = "malformed hello";
|
|
return DevelopmentHelloError::Malformed;
|
|
}
|
|
hello.protocol = protocol->valueint;
|
|
hello.columns = readDimension(cJSON_GetObjectItemCaseSensitive(root, "columns"), DEFAULT_COLUMNS);
|
|
hello.rows = readDimension(cJSON_GetObjectItemCaseSensitive(root, "rows"), DEFAULT_ROWS);
|
|
const cJSON* command = cJSON_GetObjectItemCaseSensitive(root, "command");
|
|
const bool hasCommand = command != nullptr;
|
|
if (hasCommand && !copyString(command, hello.command, sizeof(hello.command))) {
|
|
cJSON_Delete(root);
|
|
*out_message = "malformed hello";
|
|
return DevelopmentHelloError::Malformed;
|
|
}
|
|
cJSON_Delete(root);
|
|
|
|
if (hello.protocol != DEVELOPMENT_PROTOCOL_VERSION) {
|
|
*out_message = "unsupported protocol version";
|
|
return DevelopmentHelloError::UnsupportedProtocol;
|
|
}
|
|
if (strcmp(hello.mode, "shell") != 0 && strcmp(hello.mode, "sync") != 0 && strcmp(hello.mode, "app") != 0) {
|
|
*out_message = "unsupported mode";
|
|
return DevelopmentHelloError::UnsupportedMode;
|
|
}
|
|
if (hasCommand && strcmp(hello.mode, "shell") != 0) {
|
|
*out_message = "malformed hello";
|
|
return DevelopmentHelloError::Malformed;
|
|
}
|
|
|
|
*out_hello = hello;
|
|
*out_message = nullptr;
|
|
return DevelopmentHelloError::None;
|
|
}
|
|
|
|
size_t development_protocol_format_status(const char* status, const char* message, char* out_line, size_t out_size) {
|
|
cJSON* root = cJSON_CreateObject();
|
|
if (root == nullptr) {
|
|
return 0;
|
|
}
|
|
cJSON_AddStringToObject(root, "status", status);
|
|
if (message != nullptr) {
|
|
cJSON_AddStringToObject(root, "message", message);
|
|
}
|
|
// The newline is appended below, so one byte is held back for it
|
|
const bool printed = out_size >= 2 && cJSON_PrintPreallocated(root, out_line, static_cast<int>(out_size - 1), false);
|
|
cJSON_Delete(root);
|
|
if (!printed) {
|
|
return 0;
|
|
}
|
|
const size_t length = strlen(out_line);
|
|
out_line[length] = '\n';
|
|
out_line[length + 1] = '\0';
|
|
return length + 1;
|
|
}
|
|
|
|
DevelopmentFrameParseResult development_frame_parse(const uint8_t* data, size_t size, DevelopmentFrame* out_frame, size_t* out_consumed) {
|
|
if (size < DEVELOPMENT_FRAME_HEADER_SIZE) {
|
|
return DevelopmentFrameParseResult::Incomplete;
|
|
}
|
|
const uint16_t length = static_cast<uint16_t>((data[1] << 8) | data[2]);
|
|
if (length > DEVELOPMENT_FRAME_PAYLOAD_MAX) {
|
|
return DevelopmentFrameParseResult::Invalid;
|
|
}
|
|
if (size < DEVELOPMENT_FRAME_HEADER_SIZE + length) {
|
|
return DevelopmentFrameParseResult::Incomplete;
|
|
}
|
|
out_frame->type = static_cast<DevelopmentFrameType>(data[0]);
|
|
out_frame->payload = data + DEVELOPMENT_FRAME_HEADER_SIZE;
|
|
out_frame->length = length;
|
|
*out_consumed = DEVELOPMENT_FRAME_HEADER_SIZE + length;
|
|
return DevelopmentFrameParseResult::Ok;
|
|
}
|
|
|
|
void development_frame_write_header(DevelopmentFrameType type, uint16_t length, uint8_t out_header[DEVELOPMENT_FRAME_HEADER_SIZE]) {
|
|
out_header[0] = type;
|
|
out_header[1] = static_cast<uint8_t>(length >> 8);
|
|
out_header[2] = static_cast<uint8_t>(length & 0xFF);
|
|
}
|