Files
MeshCore/src/helpers/RoomWebJson.h
T

118 lines
4.8 KiB
C++

#pragma once
#include <ArduinoJson.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
namespace mesh {
// JsonDocument v7 is dynamically allocated. Bound actual allocations rather
// than relying on the deprecated StaticJsonDocument capacity shim.
class RoomJsonAllocator : public ArduinoJson::Allocator {
union Header { max_align_t alignment; size_t bytes; };
size_t used_ = 0;
public:
static constexpr size_t BUDGET = 8192;
void* allocate(size_t bytes) override {
if (bytes > BUDGET - used_ || sizeof(Header) > BUDGET - used_ - bytes) return nullptr;
auto* header = static_cast<Header*>(malloc(sizeof(Header) + bytes));
if (!header) return nullptr;
header->bytes = bytes; used_ += sizeof(Header) + bytes; return header + 1;
}
void deallocate(void* ptr) override {
if (!ptr) return;
auto* header = static_cast<Header*>(ptr) - 1;
used_ -= sizeof(Header) + header->bytes; free(header);
}
void* reallocate(void* ptr, size_t bytes) override {
if (!ptr) return allocate(bytes);
auto* header = static_cast<Header*>(ptr) - 1;
const size_t remaining = used_ - sizeof(Header) - header->bytes;
if (bytes > BUDGET - remaining || sizeof(Header) > BUDGET - remaining - bytes) return nullptr;
header = static_cast<Header*>(realloc(header, sizeof(Header) + bytes));
if (!header) return nullptr;
header->bytes = bytes; used_ = remaining + sizeof(Header) + bytes; return header + 1;
}
};
// Replies use their fixed mailbox buffer, with no second JSON heap pool.
class RoomJsonWriter {
char* output_;
size_t capacity_, used_ = 0;
bool valid_ = true;
public:
RoomJsonWriter(char* output, size_t capacity) : output_(output), capacity_(capacity) {
if (!output || !capacity) valid_ = false;
else output[0] = 0;
}
void reset() {
used_ = 0; valid_ = output_ && capacity_;
if (valid_) output_[0] = 0;
}
size_t capacity() const { return capacity_; }
void raw(const char* text) {
const size_t length = strlen(text);
if (!valid_ || length >= capacity_ - used_) { valid_ = false; return; }
memcpy(output_ + used_, text, length + 1); used_ += length;
}
void number(uint32_t value) { char text[12]; snprintf(text, sizeof(text), "%lu", (unsigned long)value); raw(text); }
void string(const char* text) {
raw("\"");
for (const auto* p = reinterpret_cast<const uint8_t*>(text); valid_ && *p; ++p) {
char escaped[7] = {};
if (*p == '"' || *p == '\\') { escaped[0] = '\\'; escaped[1] = *p; }
else if (*p < 0x20) snprintf(escaped, sizeof(escaped), "\\u%04x", unsigned(*p));
else escaped[0] = *p;
raw(escaped);
}
raw("\"");
}
bool finish() {
if (!valid_ && output_ && capacity_ >= 29) strcpy(output_, "{\"error\":\"reply too large\"}");
return valid_;
}
};
inline void roomEncodeBase64(const uint8_t* bytes, size_t length, char* output) {
static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t used = 0;
for (size_t i = 0; i < length; i += 3) {
const uint32_t value = (uint32_t(bytes[i]) << 16)
| (i + 1 < length ? uint32_t(bytes[i + 1]) << 8 : 0)
| (i + 2 < length ? bytes[i + 2] : 0);
output[used++] = alphabet[(value >> 18) & 63]; output[used++] = alphabet[(value >> 12) & 63];
output[used++] = i + 1 < length ? alphabet[(value >> 6) & 63] : '=';
output[used++] = i + 2 < length ? alphabet[value & 63] : '=';
}
output[used] = 0;
}
inline bool roomDecodeBase64(const char* text, uint8_t* bytes, size_t capacity, size_t& length) {
length = 0;
const size_t encoded = strlen(text);
if (encoded % 4 != 0 || encoded > ((capacity + 2) / 3) * 4) return false;
auto digit = [](char c) -> int {
if (c >= 'A' && c <= 'Z') return c - 'A'; if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52; if (c == '+') return 62; if (c == '/') return 63;
return -1;
};
for (size_t i = 0; i < encoded; i += 4) {
int values[4];
for (size_t j = 0; j < 4; ++j) values[j] = text[i + j] == '=' ? 0 : digit(text[i + j]);
if (values[0] < 0 || values[1] < 0 || values[2] < 0 || values[3] < 0
|| text[i] == '=' || text[i + 1] == '='
|| (text[i + 2] == '=' && text[i + 3] != '=')
|| (i + 4 != encoded && (text[i + 2] == '=' || text[i + 3] == '='))) return false;
const size_t n = text[i + 2] == '=' ? 1 : (text[i + 3] == '=' ? 2 : 3);
if (length + n > capacity || (n == 1 && (values[1] & 15)) || (n == 2 && (values[2] & 3))) return false;
const uint32_t value = (uint32_t(values[0]) << 18) | (uint32_t(values[1]) << 12)
| (uint32_t(values[2]) << 6) | uint32_t(values[3]);
for (size_t j = 0; j < n; ++j) bytes[length++] = uint8_t(value >> (16 - 8 * j));
}
return true;
}
} // namespace mesh