2#include <ossia/detail/json.hpp>
3#include <ossia/network/value/format_value.hpp>
4#include <ossia/network/value/value.hpp>
6#include <QCborStreamReader>
7#include <QCborStreamWriter>
10#include <halp/callback.hpp>
11#include <halp/controls.hpp>
12#include <halp/layout.hpp>
13#include <halp/meta.hpp>
14#include <rapidjson/error/en.h>
15#include <rapidjson/memorystream.h>
16#include <rapidjson/prettywriter.h>
17#include <rapidjson/reader.h>
33#include <unordered_set>
43#if !defined(SCORE_HAS_STD_FLOAT_FROM_CHARS)
44#if defined(__cpp_lib_to_chars) && !defined(_LIBCPP_VERSION)
45#define SCORE_HAS_STD_FLOAT_FROM_CHARS 1
47#define SCORE_HAS_STD_FLOAT_FROM_CHARS 0
51#if !SCORE_HAS_STD_FLOAT_FROM_CHARS
52#include <boost/lexical_cast/try_lexical_convert.hpp>
55namespace avnd_tools::value_serialization
66inline constexpr std::size_t max_bytes = 8 * 1024 * 1024;
67inline constexpr std::size_t max_nodes = 65536;
68inline constexpr std::size_t max_depth = 64;
72 using std::runtime_error::runtime_error;
79inline bool parse_float64(std::string_view text,
double& out)
noexcept
81#if SCORE_HAS_STD_FLOAT_FROM_CHARS
82 const auto*
const last = text.data() + text.size();
83 const auto parsed = std::from_chars(text.data(), last, out);
84 return parsed.ec == std::errc{} && parsed.ptr == last;
86 if(text.starts_with(
'+'))
88 if(!boost::conversion::try_lexical_convert(text, out))
92 const auto mantissa = text.substr(0, text.find_first_of(
"eE"));
93 if(mantissa.find_first_of(
"123456789") != std::string_view::npos)
105 if(++nodes > max_nodes)
108 static void depth(std::size_t n)
115inline void utf8(std::string_view text)
117 rapidjson::MemoryStream stream{text.data(), text.size()};
118 while(stream.Tell() < text.size())
120 unsigned codepoint{};
121 if(!rapidjson::UTF8<>::Decode(stream, &codepoint))
122 throw codec_error{
"Invalid UTF-8 text"};
126inline ossia::value floating(
double number)
128 if(!std::isfinite(number) || std::abs(number) > std::numeric_limits<float>::max())
129 throw codec_error{
"Number outside finite float32 range"};
130 const float value =
static_cast<float>(number);
131 if(number != 0. && value == 0.f)
132 throw codec_error{
"Number underflows float32"};
136inline ossia::value integer(std::int64_t number)
138 if(number < std::numeric_limits<std::int32_t>::min()
139 || number > std::numeric_limits<std::int32_t>::max())
140 throw codec_error{
"Integer outside int32 range"};
141 return static_cast<std::int32_t
>(number);
144inline ossia::value unsigned_integer(std::uint64_t number)
146 if(number >
static_cast<std::uint64_t
>(std::numeric_limits<std::int32_t>::max()))
147 throw codec_error{
"Integer outside int32 range"};
148 return static_cast<std::int32_t
>(number);
152struct json_reader : rapidjson::BaseReaderHandler<rapidjson::UTF8<>, json_reader>
158 std::unordered_set<std::string> keys;
160 std::vector<frame> stack;
164 bool put(ossia::value value)
167 result = std::move(value);
168 else if(
auto* list = stack.back().value.target<std::vector<ossia::value>>())
169 list->push_back(std::move(value));
171 stack.back().value.get<ossia::value_map_type>().emplace_back(
172 std::move(stack.back().key), std::move(value));
175 bool scalar(ossia::value value)
178 return put(std::move(value));
180 bool Null() {
return scalar(ossia::impulse{}); }
181 bool Bool(
bool v) {
return scalar(v); }
182 bool RawNumber(
const char* s, rapidjson::SizeType n,
bool)
184 const std::string_view token{s, n};
185 if(token.find_first_of(
".eE") == std::string_view::npos)
187 std::int32_t number{};
188 const auto parsed = std::from_chars(s, s + n, number);
189 if(parsed.ec != std::errc{} || parsed.ptr != s + n)
190 throw codec_error{
"Integer outside int32 range"};
191 return scalar(number);
194 if(!parse_float64(token, number))
195 throw codec_error{
"Number outside finite float32 range"};
196 return scalar(floating(number));
198 bool String(
const char* s, rapidjson::SizeType n,
bool)
200 return scalar(std::string{s, n});
202 bool Key(
const char* s, rapidjson::SizeType n,
bool)
205 auto& top = stack.back();
206 top.key.assign(s, n);
207 if(!top.keys.insert(top.key).second)
208 throw codec_error{
"Duplicate map key"};
211 bool start(ossia::value value)
214 limits::depth(stack.size() + 1);
215 stack.push_back({std::move(value), {}, {}});
218 bool StartObject() {
return start(ossia::value_map_type{}); }
219 bool StartArray() {
return start(std::vector<ossia::value>{}); }
222 auto value = std::move(stack.back().value);
224 return put(std::move(value));
226 bool EndObject(rapidjson::SizeType) {
return end(); }
227 bool EndArray(rapidjson::SizeType) {
return end(); }
230inline ossia::value from_json(std::string_view input)
232 if(input.size() > max_bytes)
233 throw codec_error{
"Input byte limit exceeded"};
236 if(input.find(
'\0') != std::string_view::npos)
237 throw codec_error{
"Unescaped NUL in JSON text"};
238 rapidjson::MemoryStream stream{input.data(), input.size()};
239 rapidjson::Reader reader;
241 constexpr unsigned flags = rapidjson::kParseValidateEncodingFlag
242 | rapidjson::kParseNumbersAsStringsFlag
243 | rapidjson::kParseIterativeFlag;
244 if(!reader.Parse<flags>(stream, handler))
246 std::string{rapidjson::GetParseError_En(reader.GetParseErrorCode())}
247 +
" at byte " + std::to_string(reader.GetErrorOffset())};
248 return std::move(handler.result);
259 if(data.size() == max_bytes)
266template <
template <
typename,
typename,
typename,
typename,
unsigned>
class Writer>
271 json_output, rapidjson::UTF8<>, rapidjson::UTF8<>, rapidjson::CrtAllocator,
272 rapidjson::kWriteValidateEncodingFlag>
274 void null() { writer.Null(); }
275 void number(
int v) { writer.Int(v); }
276 void number(
float v) { writer.Double(v); }
277 void boolean(
bool v) { writer.Bool(v); }
278 void text(std::string_view v)
280 if(!writer.String(v.data(),
static_cast<rapidjson::SizeType
>(v.size())))
283 void key(std::string_view v) { text(v); }
284 void array(std::size_t) { writer.StartArray(); }
285 void map(std::size_t) { writer.StartObject(); }
286 void end_array() { writer.EndArray(); }
287 void end_map() { writer.EndObject(); }
298 qint64 readData(
char*, qint64)
override {
return -1; }
299 qint64 writeData(
const char* source, qint64 size)
override
301 if(size < 0 ||
static_cast<std::uint64_t
>(size) > max_bytes - data.size())
306 data.append(source,
static_cast<std::size_t
>(size));
314 QCborStreamWriter writer{&output};
315 void null() { writer.appendNull(); }
316 void number(
int v) { writer.append(v); }
317 void number(
float v) { writer.append(v); }
318 void boolean(
bool v) { writer.append(v); }
319 void text(std::string_view v)
322 writer.appendTextString(v.data(),
static_cast<qsizetype
>(v.size()));
324 void key(std::string_view v) { text(v); }
325 void array(std::size_t n) { writer.startArray(n); }
326 void map(std::size_t n) { writer.startMap(n); }
327 void end_array() { writer.endArray(); }
328 void end_map() { writer.endMap(); }
331template <
typename Writer>
337 void operator()()
const {
throw codec_error{
"Cannot serialize an unset value"}; }
338 void operator()(ossia::impulse)
const { writer.null(); }
339 void operator()(
int v)
const { writer.number(v); }
340 void operator()(
float v)
const
342 if(!std::isfinite(v))
343 throw codec_error{
"Cannot serialize a non-finite number"};
346 void operator()(
bool v)
const { writer.boolean(v); }
347 void operator()(
const std::string& v)
const
349 if(v.size() > max_bytes)
353 void child(
const ossia::value& v)
const
357 if constexpr(
requires { writer.output.overflow; })
358 if(writer.output.overflow)
361 template <std::
size_t N>
362 void operator()(
const std::array<float, N>& v)
const
364 limits::depth(depth + 1);
373 void operator()(
const std::vector<ossia::value>& v)
const
375 limits::depth(depth + 1);
376 writer.array(v.size());
377 for(
const auto& x : v)
381 void operator()(
const ossia::value_map_type& v)
const
383 limits::depth(depth + 1);
384 writer.map(v.size());
385 std::unordered_set<std::string_view> keys;
386 for(
const auto& [key, value] : v)
389 if(key.size() > max_bytes)
391 if(!keys.insert(key).second)
400template <
typename Writer>
401std::string encode(
const ossia::value& value)
407 if constexpr(
requires { writer.output.overflow; })
408 if(writer.output.overflow)
409 throw codec_error{
"Output byte limit exceeded"};
410 return std::move(writer.output.data);
413inline std::string cbor_text(QCborStreamReader& reader)
420 const auto size = reader.currentStringChunkSize();
421 if(size < 0 ||
static_cast<std::uint64_t
>(size) > max_bytes - text.size())
422 throw codec_error{
"String byte limit exceeded"};
423 const auto offset = text.size();
424 text.resize(offset +
static_cast<std::size_t
>(size));
425 const auto chunk = reader.readStringChunk(text.data() + offset, size);
426 if(chunk.status == QCborStreamReader::Error)
427 throw codec_error{
"Malformed CBOR text"};
428 text.resize(offset +
static_cast<std::size_t
>(chunk.data));
429 if(chunk.status == QCborStreamReader::EndOfString)
432 utf8(std::string_view{text}.substr(offset));
437cbor_value(QCborStreamReader& reader, limits& budget, std::size_t depth = 0)
441 if(reader.isContainer())
443 limits::depth(depth + 1);
444 const bool map = reader.isMap();
445 if(reader.isLengthKnown() && reader.length() > max_nodes)
446 throw codec_error{
"Value count limit exceeded"};
447 if(!reader.enterContainer())
448 throw codec_error{
"Malformed CBOR container"};
451 ossia::value_map_type values;
452 std::unordered_set<std::string> keys;
453 while(reader.hasNext())
456 if(!reader.isString())
457 throw codec_error{
"CBOR map keys must be text"};
458 auto key = cbor_text(reader);
459 if(!keys.insert(key).second)
460 throw codec_error{
"Duplicate map key"};
461 values.emplace_back(std::move(key), cbor_value(reader, budget, depth + 1));
463 result = std::move(values);
467 std::vector<ossia::value> values;
468 while(reader.hasNext())
469 values.push_back(cbor_value(reader, budget, depth + 1));
470 result = std::move(values);
472 if(!reader.leaveContainer())
473 throw codec_error{
"Malformed CBOR container"};
476 if(reader.isString())
477 return cbor_text(reader);
478 if(reader.isUnsignedInteger())
479 result = unsigned_integer(reader.toUnsignedInteger());
480 else if(reader.isNegativeInteger())
483 const auto magnitude =
static_cast<quint64
>(reader.toNegativeInteger());
484 if(magnitude == 0 || magnitude > 2147483648ULL)
485 throw codec_error{
"Integer outside int32 range"};
486 result = integer(-
static_cast<std::int64_t
>(magnitude));
488 else if(reader.isBool())
489 result = reader.toBool();
490 else if(reader.isNull())
491 result = ossia::impulse{};
492 else if(reader.isFloat16())
493 result = floating(
static_cast<float>(reader.toFloat16()));
494 else if(reader.isFloat())
495 result = floating(reader.toFloat());
496 else if(reader.isDouble())
497 result = floating(reader.toDouble());
500 "Unsupported or malformed CBOR value (tags, bytes and undefined are not "
503 throw codec_error{
"Malformed CBOR value"};
507inline ossia::value from_cbor(std::string_view input)
509 if(input.size() > max_bytes)
510 throw codec_error{
"Input byte limit exceeded"};
511 QCborStreamReader reader{input.data(),
static_cast<qsizetype
>(input.size())};
513 auto result = cbor_value(reader, budget);
514 if(reader.lastError() != QCborError::NoError)
515 throw codec_error{
"Malformed CBOR input"};
516 if(reader.currentOffset() !=
static_cast<qint64
>(input.size()))
517 throw codec_error{
"Trailing CBOR input"};
570inline void append_bytes(std::string& output, std::string_view bytes)
572 if(bytes.size() > max_bytes - output.size())
573 throw codec_error{
"Output byte limit exceeded"};
574 output.append(bytes);
577inline std::string_view line_ending(LineEnding ending)
583 case LineEnding::CRLF:
593inline ossia::value text_scalar(std::string_view text, TextType type)
595 if(type == TextType::String)
596 return std::string{text};
597 if(type == TextType::Boolean || type == TextType::Auto)
603 if(type == TextType::Boolean)
604 throw codec_error{
"Expected true or false"};
606 if(type == TextType::Integer
607 || (type == TextType::Auto && text.find_first_of(
".eE") == text.npos))
609 std::int32_t number{};
610 auto parsed = std::from_chars(text.data(), text.data() + text.size(), number);
611 if(parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size())
613 if(type == TextType::Integer || parsed.ec == std::errc::result_out_of_range)
614 throw codec_error{
"Expected int32 text"};
617 if(parse_float64(text, number))
618 return floating(number);
621 && ((text.front() >=
'0' && text.front() <=
'9') || text.front() ==
'-'
622 || text.front() ==
'+' || text.front() ==
'.');
623 if(type == TextType::Float || numeric)
624 throw codec_error{
"Invalid or out-of-range numeric text"};
625 return std::string{text};
628inline bool structured_text(std::string_view text)
630 const auto start = text.find_first_not_of(
" \t\r\n");
631 if(start == text.npos)
633 text.remove_prefix(start);
634 const char first = text.front();
635 return first ==
'[' || first ==
'{' || first ==
'"' || first ==
'-'
636 || (first >=
'0' && first <=
'9') || text ==
"true" || text ==
"false"
644 using iterator_category = std::output_iterator_tag;
645 using difference_type = std::ptrdiff_t;
646 using value_type = void;
647 using pointer = void;
648 using reference = void;
664 void number(
float) { }
665 void boolean(
bool) { }
666 void text(std::string_view text) { utf8(text); }
667 void key(std::string_view text) { utf8(text); }
668 void array(std::size_t) { }
669 void map(std::size_t) { }
674inline void validate_value(
const ossia::value& value)
682inline std::string to_pretty(
const ossia::value& value)
684 validate_value(value);
686 fmt::format_to(pretty_output_iterator{&bounded},
"{}", value);
687 return std::move(bounded.data);
690inline ossia::value from_pretty(std::string_view text)
698 for(std::size_t i = 0; i < text.size();)
700 const char c = text[i++];
703 if(c ==
'\\' && i < text.size() && text[i] ==
'"')
712 limits::depth(++depth);
716 throw codec_error{
"Unmatched pretty container"};
719 else if((c >=
'a' && c <=
'z') || (c >=
'A' && c <=
'Z'))
724 && ((text[i] >=
'a' && text[i] <=
'z') || (text[i] >=
'0' && text[i] <=
'9')))
727 else if((c >=
'0' && c <=
'9') || c ==
'-' || c ==
'+' || c ==
'.')
730 const auto start = i - 1;
731 while(i < text.size()
732 && ((text[i] >=
'0' && text[i] <=
'9') || text[i] ==
'.' || text[i] ==
'e'
733 || text[i] ==
'E' || text[i] ==
'+' || text[i] ==
'-'))
736 const auto token = text.substr(start, i - start);
737 if(!parse_float64(token, number))
738 throw codec_error{
"Invalid pretty number"};
743 throw codec_error{
"Unterminated pretty container or string"};
746 auto result = ossia::parse_pretty_value(text);
747 if(!result.valid() || to_pretty(result) != text)
748 throw codec_error{
"Expected canonical ossia pretty text (maps are display only)"};
752inline void text_delimiter(std::string_view delimiter)
754 if(delimiter.empty() || delimiter.size() > max_bytes
755 || delimiter.find_first_of(
"\"\\[]{}") != delimiter.npos)
757 "Delimiter must be nonempty, bounded and exclude JSON quotes, escapes and "
762inline std::size_t text_separator(std::string_view text, std::string_view delimiter)
765 for(std::size_t i = 0; i < text.size(); ++i)
767 if(quoted && text[i] ==
'\\')
769 else if(text[i] ==
'"')
771 else if(!quoted && text.substr(i).starts_with(delimiter))
777template <
typename Values>
778std::string delimited_text(
const Values& values, std::string_view delimiter)
780 text_delimiter(delimiter);
781 if(values.size() >= max_nodes)
782 throw codec_error{
"Value count limit exceeded"};
784 std::size_t field_start{};
786 for(
const auto& value : values)
788 auto token = encode<json_writer>(value);
789 if(text_separator(token, delimiter) != token.npos)
790 throw codec_error{
"Numeric text field contains the delimiter"};
793 const auto boundary = output.size();
794 append_bytes(output, delimiter);
795 if(text_separator(std::string_view{output}.substr(field_start), delimiter)
796 != boundary - field_start)
797 throw codec_error{
"Text field overlaps the delimiter"};
798 field_start = output.size();
800 append_bytes(output, token);
806inline ossia::value from_text(
807 std::string_view text, TextStyle style, TextType type, std::string_view delimiter,
810 if(text.size() > max_bytes)
811 throw codec_error{
"Input byte limit exceeded"};
815 if(type == TextType::String)
816 return std::string{text};
817 const auto suffix = line_ending(ending);
818 if(!suffix.empty() && text.ends_with(suffix))
819 text.remove_suffix(suffix.size());
820 if(style == TextStyle::Pretty)
821 return from_pretty(text);
822 if(type != TextType::Auto && type != TextType::List)
823 return text_scalar(text, type);
824 const auto start = text.find_first_not_of(
" \t\r\n");
825 if(start != text.npos && (text[start] ==
'[' || text[start] ==
'{'))
826 return from_json(text);
827 text_delimiter(delimiter);
828 auto separator = text_separator(text, delimiter);
829 if(type != TextType::List && separator == text.npos)
830 return structured_text(text) ? from_json(text) : text_scalar(text, TextType::Auto);
831 std::vector<ossia::value> values;
837 const auto token = text.substr(0, separator);
838 const auto token_start = token.find_first_not_of(
" \t\r\n");
839 if(token_start != token.npos
840 && (token[token_start] ==
'[' || token[token_start] ==
'{'))
841 throw codec_error{
"Nested text containers require a complete JSON representation"};
843 structured_text(token) ? from_json(token) : text_scalar(token, TextType::Auto));
844 if(separator == text.npos)
846 text.remove_prefix(separator + delimiter.size());
847 separator = text_separator(text, delimiter);
852inline std::string to_text(
853 const ossia::value& value, TextStyle style, std::string_view delimiter,
856 if(
const auto* list = value.target<std::vector<ossia::value>>();
857 list && list->size() >= max_nodes)
858 throw codec_error{
"Value count limit exceeded"};
860 if(style == TextStyle::Pretty)
861 output = to_pretty(value);
862 else if(
const auto* text = value.target<std::string>())
864 if(text->size() > max_bytes)
865 throw codec_error{
"String byte limit exceeded"};
871 const auto* list = value.target<std::vector<ossia::value>>();
872 list && !list->empty()
873 && std::all_of(list->begin(), list->end(), [](
const ossia::value& item) {
874 return item.target<int>() || item.target<float>() || item.target<bool>()
875 || item.target<std::string>() || item.target<ossia::impulse>();
877 output = delimited_text(*list, delimiter);
878 else if(
const auto* vector = value.target<ossia::vec2f>())
879 output = delimited_text(*vector, delimiter);
880 else if(
const auto* vector = value.target<ossia::vec3f>())
881 output = delimited_text(*vector, delimiter);
882 else if(
const auto* vector = value.target<ossia::vec4f>())
883 output = delimited_text(*vector, delimiter);
885 output = encode<json_writer>(value);
886 append_bytes(output, line_ending(ending));
898inline std::size_t binary_width(
char code)
920 throw codec_error{
"Unknown binary format code"};
924inline char scalar_code(ScalarType type)
932 case ScalarType::u16:
934 case ScalarType::i16:
936 case ScalarType::u32:
938 case ScalarType::i32:
940 case ScalarType::f32:
942 case ScalarType::f64:
945 throw codec_error{
"Unknown scalar type"};
956 std::vector<binary_field> fields;
958 std::size_t values{};
962 CHAR_BIT == 8 &&
sizeof(short) == 2 &&
sizeof(
int) == 4 &&
sizeof(
long long) == 8
963 &&
sizeof(
bool) == 1 &&
sizeof(float) == 4 &&
sizeof(
double) == 8
964 && std::numeric_limits<float>::is_iec559 && std::numeric_limits<double>::is_iec559);
966 std::endian::native == std::endian::little
967 || std::endian::native == std::endian::big);
969inline std::size_t native_alignment(
char code)
975 return alignof(short);
981 return alignof(
long long);
983 return alignof(float);
985 return alignof(double);
987 return alignof(bool);
993inline binary_layout compile_layout(std::string_view format, ByteOrder order)
995 if(format.size() > max_nodes)
996 throw codec_error{
"Format text limit exceeded"};
997 binary_layout layout{order, {}, 0, 0};
999 = [](
char c) {
return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r'; };
1001 while(pos < format.size() && whitespace(format[pos]))
1004 if(pos < format.size())
1006 const char prefix = format[pos];
1007 if(prefix ==
'<' || prefix ==
'>' || prefix ==
'!' || prefix ==
'=' || prefix ==
'@')
1010 native = prefix ==
'@';
1011 layout.order = prefix ==
'<' ? ByteOrder::Little
1012 : prefix ==
'>' || prefix ==
'!' ? ByteOrder::Big
1013 : std::endian::native == std::endian::little ? ByteOrder::Little
1017 while(pos < format.size())
1019 if(whitespace(format[pos]))
1024 std::size_t count{};
1025 bool explicit_count{};
1026 while(pos < format.size() && format[pos] >=
'0' && format[pos] <=
'9')
1028 explicit_count =
true;
1029 count = count * 10 + (format[pos++] -
'0');
1030 if(count > max_nodes)
1031 throw codec_error{
"Binary count limit exceeded"};
1035 if(pos == format.size())
1036 throw codec_error{
"Invalid binary count"};
1037 const char code = format[pos++];
1038 const auto width = binary_width(code);
1039 if(count == 0 && (code ==
's' || code ==
'x'))
1040 throw codec_error{
"Zero repeats require a numeric alignment code"};
1041 const auto alignment = native ? native_alignment(code) : 1;
1042 const auto padding = (alignment - layout.bytes % alignment) % alignment;
1043 if(padding > max_bytes - layout.bytes)
1044 throw codec_error{
"Binary layout resource limit exceeded"};
1047 layout.fields.push_back({
'x', padding});
1048 layout.bytes += padding;
1050 const auto bytes = count * width;
1051 const auto values = code ==
'x' ? 0 : code ==
's' ? 1 : count;
1052 if(bytes > max_bytes - layout.bytes || values >= max_nodes - layout.values)
1053 throw codec_error{
"Binary layout resource limit exceeded"};
1054 layout.bytes += bytes;
1055 layout.values += values;
1056 layout.fields.push_back({code, count});
1058 if(layout.fields.empty())
1059 throw codec_error{
"Empty binary layout"};
1070 const binary_layout& get(std::string_view format, ByteOrder requested)
1072 if(format.size() > max_nodes)
1074 if(!initialized || source != format || order != requested)
1082 layout = compile_layout(format, requested);
1096write_word(std::string& output, std::uint64_t word, std::size_t width, ByteOrder order)
1098 if(width > max_bytes - output.size())
1100 for(std::size_t i = 0; i < width; ++i)
1102 const auto shift = 8 * (order == ByteOrder::Little ? i : width - 1 - i);
1103 output.push_back(
static_cast<char>((word >> shift) & 255));
1108read_word(std::string_view input, std::size_t& pos, std::size_t width, ByteOrder order)
1110 if(width > input.size() - pos)
1111 throw codec_error{
"Truncated binary input"};
1112 std::uint64_t word{};
1113 for(std::size_t i = 0; i < width; ++i)
1115 const auto shift = 8 * (order == ByteOrder::Little ? i : width - 1 - i);
1116 word |= std::uint64_t(
static_cast<unsigned char>(input[pos++])) << shift;
1122read_scalar(std::string_view input, std::size_t& pos,
char code, ByteOrder order)
1124 const auto width = binary_width(code);
1125 const auto word = read_word(input, pos, width, order);
1129 return floating(std::bit_cast<float>(
static_cast<std::uint32_t
>(word)));
1131 return floating(std::bit_cast<double>(word));
1134 throw codec_error{
"Boolean byte must be 0 or 1"};
1140 const auto extended = width < 8 && (word & (std::uint64_t{1} << (width * 8 - 1)))
1141 ? word | (~std::uint64_t{0} << (width * 8))
1143 return integer(std::bit_cast<std::int64_t>(extended));
1146 return unsigned_integer(word);
1151write_scalar(std::string& output,
const ossia::value& value,
char code, ByteOrder order)
1153 const auto width = binary_width(code);
1154 if(code ==
'f' || code ==
'd')
1157 if(
const auto* f = value.target<
float>())
1159 else if(
const auto* i = value.target<
int>())
1162 throw codec_error{
"Floating field requires a number"};
1166 ? std::uint64_t(std::bit_cast<std::uint32_t>(
static_cast<float>(number)))
1167 :
std::bit_cast<
std::uint64_t>(number);
1168 write_word(output, word, width, order);
1171 std::int64_t number;
1172 if(
const auto* i = value.target<
int>())
1174 else if(
const auto* b = value.target<
bool>())
1177 throw codec_error{
"Integer field requires an integer or boolean"};
1178 const bool signed_type = code ==
'b' || code ==
'h' || code ==
'i' || code ==
'q';
1181 if(number < 0 || number > 1)
1182 throw codec_error{
"Boolean field must be 0 or 1"};
1184 else if(signed_type && width < 8)
1186 const std::int64_t bound = std::int64_t{1} << (width * 8 - 1);
1187 if(number < -bound || number >= bound)
1188 throw codec_error{
"Signed field overflow"};
1190 else if(!signed_type)
1193 || (width < 8 && std::uint64_t(number) >= (std::uint64_t{1} << (width * 8))))
1194 throw codec_error{
"Unsigned field overflow"};
1196 write_word(output,
static_cast<std::uint64_t
>(number), width, order);
1201 std::string& output;
1204 std::size_t depth{};
1205 void operator()()
const {
throw codec_error{
"Cannot serialize an unset value"}; }
1206 void operator()(ossia::impulse)
const
1210 void operator()(
int v)
const { write_scalar(output, v,
'i', order); }
1211 void operator()(
float v)
const { write_scalar(output, v,
'f', order); }
1212 void operator()(
bool v)
const { write_scalar(output, v,
'?', order); }
1213 void operator()(
const std::string& v)
const { append_bytes(output, v); }
1214 void operator()(
const ossia::value_map_type&)
const
1216 throw codec_error{
"Binary maps have ambiguous field order"};
1218 void operator()(
const std::vector<ossia::value>& values)
const
1220 limits::depth(depth + 1);
1221 for(
const auto& value : values)
1227 template <std::
size_t N>
1228 void operator()(
const std::array<float, N>& values)
const
1230 limits::depth(depth + 1);
1231 for(
float value : values)
1239template <
typename Values>
1240std::string to_binary_fields(
const Values& values,
const binary_layout& layout)
1243 if(values.size() != layout.values)
1244 throw codec_error{
"Binary layout input field count mismatch"};
1245 output.reserve(layout.bytes);
1246 std::size_t index{};
1247 for(
const auto& field : layout.fields)
1249 if(field.code ==
'x')
1251 output.append(field.count,
'\0');
1254 const auto repeats = field.code ==
's' ? 1 : field.count;
1255 for(std::size_t i = 0; i < repeats; ++i)
1257 const ossia::value& item = values[index++];
1258 if(field.code ==
's')
1260 const auto* text = item.target<std::string>();
1261 if(!text || text->size() != field.count)
1262 throw codec_error{
"Fixed string requires exactly its declared byte count"};
1263 append_bytes(output, *text);
1266 write_scalar(output, item, field.code, layout.order);
1273to_binary(
const ossia::value& value, ByteOrder order,
const binary_layout* layout)
1280 value.apply(free_binary_writer{output, order, budget});
1283 if(
const auto* list = value.target<std::vector<ossia::value>>())
1284 return to_binary_fields(*list, *layout);
1285 if(
const auto* vector = value.target<ossia::vec2f>())
1286 return to_binary_fields(*vector, *layout);
1287 if(
const auto* vector = value.target<ossia::vec3f>())
1288 return to_binary_fields(*vector, *layout);
1289 if(
const auto* vector = value.target<ossia::vec4f>())
1290 return to_binary_fields(*vector, *layout);
1291 return to_binary_fields(std::span{&value, 1}, *layout);
1294inline ossia::value from_binary(
1295 std::string_view input, ByteOrder order, ScalarType type,
1296 const binary_layout* layout)
1298 if(input.size() > max_bytes)
1299 throw codec_error{
"Input byte limit exceeded"};
1300 std::vector<ossia::value> values;
1304 const auto code = scalar_code(type);
1305 const auto width = binary_width(code);
1306 if(input.size() % width)
1307 throw codec_error{
"Truncated binary scalar"};
1308 if(input.size() / width >= max_nodes)
1309 throw codec_error{
"Value count limit exceeded"};
1310 values.reserve(input.size() / width);
1311 while(pos < input.size())
1312 values.push_back(read_scalar(input, pos, code, order));
1316 if(input.size() != layout->bytes)
1317 throw codec_error{
"Truncated or trailing binary input"};
1318 values.reserve(layout->values);
1319 for(
const auto& field : layout->fields)
1321 if(field.code ==
'x')
1326 if(field.code ==
's')
1328 values.emplace_back(std::string{input.substr(pos, field.count)});
1332 for(std::size_t i = 0; i < field.count; ++i)
1333 values.push_back(read_scalar(input, pos, field.code, layout->order));
1343template <
typename Output,
typename Function>
1344void publish(Output& output, Function&& function)
1346 using result_type =
decltype(function());
1351 result = function();
1353 catch(
const std::exception& e)
1357 output.error(error);
1358 output.success(error.empty());
1359 output.result(std::move(result));
1367 halp_meta(name,
"Deserialize")
1368 halp_meta(c_name,
"avnd_deserialize")
1369 halp_meta(author,
"ossia team")
1370 halp_meta(category,
"Control/Serialization")
1371 halp_meta(uuid,
"5d265cd1-014f-48bb-9c51-d594c8b58b61")
1374 "Decode Bytes as strict JSON, JSON-compatible CBOR, UTF-8 text or binary. "
1375 "Integers must fit int32; floats round to finite float32 without underflow. "
1376 "Binary decoding returns an ordered list. Limits: 8 MiB, 65536 nodes, 64 "
1377 "containers. Errors clear Value; Error is empty on success.")
1380 struct : halp::val_port<
"Bytes", std::string>
1382 void update(
Deserialize& self) { self.process(); }
1384 halp::enum_t<value_serialization::Format,
"Format"> format;
1385 struct : halp::enum_t<value_serialization::TextStyle,
"Text style">
1389 "Plain reads delimited fields or nested JSON. Pretty reads canonical ossia "
1390 "type labels only; maps are display only, floats were rounded to two "
1391 "decimals, and only quotes are escaped.")
1393 struct : halp::enum_t<value_serialization::TextType,
"Interpretation">
1397 "Auto recognizes separators, JSON and exact scalar tokens, otherwise raw "
1398 "strings. String preserves all UTF-8 bytes and line endings. List forces "
1399 "single delimited fields into a list. Numeric options require exact scalars. "
1400 "Pretty uses its own labels unless String is selected.")
1402 struct : halp::lineedit<
"Delimiter",
",">
1406 "Separator for flat Plain lists and vectors, ignored inside JSON-quoted "
1407 "strings. Must exclude quotes, backslashes and brackets. Nested containers "
1410 struct : halp::enum_t<value_serialization::LineEnding,
"Line ending">
1414 "Remove one matching suffix before parsing, except with String "
1417 struct : halp::enum_t<value_serialization::ByteOrder,
"Byte order">
1420 description,
"Raw scalar endian and the default for layouts without a prefix.")
1422 struct : halp::enum_t<value_serialization::BinaryMode,
"Binary mode">
1426 "FreeFlow reads repeated scalars; Layout reads explicitly typed fields. Both "
1429 struct : halp::enum_t<value_serialization::ScalarType,
"Scalar type">
1433 "Repeated raw binary width. Integers must fit int32; floats round to finite "
1434 "float32 without underflow.")
1436 struct : halp::lineedit<
"Layout",
"<B H I f 4s 2x">
1440 "Python struct subset: < > ! = packed, @ native endian and alignment; "
1441 "no prefix uses Byte order and packed widths. [count]b B h H i I q Q f d ? s "
1443 "Counts <=65536; zero numeric counts align only. Ns is one exact-size raw "
1444 "string; Nx skips padding. No automatic end padding. Int32 and finite float32 "
1450 halp::callback<
"Value", ossia::value> result;
1451 halp::callback<
"Success",
bool> success;
1452 halp::callback<
"Error", std::string> error;
1457 halp_meta(layout, halp::layouts::vbox)
1458 halp::item<&ins::format> format;
1461 halp_meta(layout, halp::layouts::tabs)
1462 halp_flag(hide_tabs);
1463 static constexpr auto model = &ins::format;
1466 halp_meta(name,
"JSON")
1467 halp_meta(layout, halp::layouts::vbox)
1468 halp::label types{.text =
"Strict UTF-8 JSON: int32, finite float32."};
1469 halp::label values{.text =
"null = impulse; arrays = lists; ordered maps."};
1470 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1474 halp_meta(name,
"CBOR")
1475 halp_meta(layout, halp::layouts::vbox)
1476 halp::label types{.text =
"JSON-compatible CBOR: int32, finite float32."};
1477 halp::label keys{.text =
"UTF-8 text keys; no tags, byte strings or undefined."};
1478 halp::label values{.text =
"null = impulse; arrays = lists; ordered maps."};
1479 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1483 halp_meta(name,
"Text")
1484 halp_meta(layout, halp::layouts::vbox)
1485 halp::item<&ins::text_style> style;
1486 halp::item<&ins::text_type> type;
1487 halp::item<&ins::delimiter> delimiter;
1488 halp::item<&ins::line_ending> ending;
1492 halp_meta(name,
"Binary")
1493 halp_meta(layout, halp::layouts::vbox)
1494 halp::item<&ins::byte_order> order;
1495 halp::item<&ins::binary_mode> mode;
1496 halp::item<&ins::scalar_type> scalar;
1497 halp::item<&ins::layout> format_text;
1505 using namespace value_serialization;
1506 publish(outputs, [&]() -> ossia::value {
1507 const auto& bytes = inputs.input.value;
1508 switch(inputs.format.value)
1511 return from_json(bytes);
1513 return from_cbor(bytes);
1516 bytes, inputs.text_style.value, inputs.text_type.value,
1517 inputs.delimiter.value, inputs.line_ending.value);
1518 case Format::Binary:
1520 bytes, inputs.byte_order.value, inputs.scalar_type.value,
1521 inputs.binary_mode.value == BinaryMode::Layout
1522 ? &cache.get(inputs.layout.value, inputs.byte_order.value)
1525 throw codec_error{
"Unknown format"};
1532 halp_meta(name,
"Serialize")
1533 halp_meta(c_name,
"avnd_serialize")
1534 halp_meta(author,
"ossia team")
1535 halp_meta(category,
"Control/Serialization")
1536 halp_meta(uuid,
"5d265cd1-014f-48bb-9c51-d594c8b58b62")
1539 "Encode Value as strict JSON, JSON-compatible CBOR, UTF-8 text or binary. "
1540 "Plain Text infers shape: raw strings, delimited flat lists/vectors, nested JSON; "
1541 "vectors decode as lists. Pretty Text is ossia display, not a lossless archive. "
1542 "Free-flow binary flattens lists/vectors; maps and impulses fail. "
1543 "Limits: 8 MiB, 65536 nodes, 64 containers. Errors clear Bytes.")
1546 struct : halp::val_port<
"Value", ossia::value>
1548 void update(
Serialize& self) { self.process(); }
1550 halp::enum_t<value_serialization::Format,
"Format"> format;
1551 struct : halp::toggle<
"Pretty print">
1554 description,
"Indent JSON without changing its values or numeric precision.")
1556 struct : halp::enum_t<value_serialization::TextStyle,
"Text style">
1560 "Plain infers shape: root strings are raw UTF-8, scalars are plain tokens, "
1561 "flat lists/vectors are delimited JSON tokens, nested containers use JSON. "
1562 "Pretty uses ossia type labels and two-decimal floats: lossy display; maps "
1563 "and ambiguous escaped strings cannot be decoded.")
1565 struct : halp::lineedit<
"Delimiter",
",">
1569 "Separator for nonempty flat Plain lists and vectors. Strings are "
1570 "JSON-quoted; numeric delimiter collisions fail. Must exclude quotes, "
1571 "backslashes and brackets. One-field lists require List interpretation when "
1574 struct : halp::enum_t<value_serialization::LineEnding,
"Line ending">
1578 "Append a line ending to Text, except raw Plain strings which are unchanged.")
1580 struct : halp::enum_t<value_serialization::ByteOrder,
"Byte order">
1583 description,
"Free-flow endian and the default for layouts without a prefix.")
1585 struct : halp::enum_t<value_serialization::BinaryMode,
"Binary mode">
1589 "FreeFlow writes int32, float32, bool bytes and raw strings, flattening lists "
1590 "and vectors without tags. Layout writes explicitly typed fields.")
1592 struct : halp::lineedit<
"Layout",
"<B H I f 4s 2x">
1596 "Python struct subset: < > ! = packed, @ native endian and alignment; "
1597 "no prefix uses Byte order and packed widths. [count]b B h H i I q Q f d ? s "
1599 "Counts <=65536; zero numeric counts align only. Ns consumes one exact-size "
1600 "raw string, never padded or truncated; Nx writes zeros. No automatic end "
1606 halp::callback<
"Bytes", std::string> result;
1607 halp::callback<
"Success",
bool> success;
1608 halp::callback<
"Error", std::string> error;
1613 halp_meta(layout, halp::layouts::vbox)
1614 halp::item<&ins::format> format;
1617 halp_meta(layout, halp::layouts::tabs)
1618 halp_flag(hide_tabs);
1619 static constexpr auto model = &ins::format;
1622 halp_meta(name,
"JSON")
1623 halp_meta(layout, halp::layouts::vbox)
1624 halp::item<&ins::pretty> pretty;
1628 halp_meta(name,
"CBOR")
1629 halp_meta(layout, halp::layouts::vbox)
1630 halp::label types{.text =
"JSON-compatible CBOR: int32, finite float32."};
1631 halp::label values{.text =
"Impulse = null; vectors/lists = arrays."};
1632 halp::label keys{.text =
"UTF-8 text and unique map keys required."};
1633 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1637 halp_meta(name,
"Text")
1638 halp_meta(layout, halp::layouts::vbox)
1639 halp::item<&ins::text_style> style;
1640 halp::item<&ins::delimiter> delimiter;
1641 halp::item<&ins::line_ending> ending;
1645 halp_meta(name,
"Binary")
1646 halp_meta(layout, halp::layouts::vbox)
1647 halp::item<&ins::byte_order> order;
1648 halp::item<&ins::binary_mode> mode;
1649 halp::item<&ins::layout> format_text;
1657 using namespace value_serialization;
1658 publish(outputs, [&]() -> std::string {
1659 const auto& value = inputs.input.value;
1660 switch(inputs.format.value)
1663 return inputs.pretty.value ? encode<pretty_json_writer>(value)
1664 : encode<json_writer>(value);
1666 return encode<cbor_writer>(value);
1669 value, inputs.text_style.value, inputs.delimiter.value,
1670 inputs.line_ending.value);
1671 case Format::Binary:
1673 value, inputs.byte_order.value,
1674 inputs.binary_mode.value == BinaryMode::Layout
1675 ? &cache.get(inputs.layout.value, inputs.byte_order.value)
1678 throw codec_error{
"Unknown format"};
Definition BaseScenarioSerialization.cpp:17