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)
443 if(reader.lastError() != QCborError::NoError)
444 throw codec_error{
"Malformed CBOR input"};
446 if(reader.isContainer())
448 limits::depth(depth + 1);
449 const bool map = reader.isMap();
450 if(reader.isLengthKnown() && reader.length() > max_nodes)
451 throw codec_error{
"Value count limit exceeded"};
452 if(!reader.enterContainer())
453 throw codec_error{
"Malformed CBOR container"};
456 ossia::value_map_type values;
457 std::unordered_set<std::string> keys;
458 while(reader.hasNext())
461 if(reader.lastError() != QCborError::NoError)
462 throw codec_error{
"Malformed CBOR input"};
463 if(!reader.isString())
464 throw codec_error{
"CBOR map keys must be text"};
465 auto key = cbor_text(reader);
466 if(!keys.insert(key).second)
467 throw codec_error{
"Duplicate map key"};
468 values.emplace_back(std::move(key), cbor_value(reader, budget, depth + 1));
470 result = std::move(values);
474 std::vector<ossia::value> values;
475 while(reader.hasNext())
476 values.push_back(cbor_value(reader, budget, depth + 1));
477 result = std::move(values);
479 if(!reader.leaveContainer())
480 throw codec_error{
"Malformed CBOR container"};
483 if(reader.isString())
484 return cbor_text(reader);
485 if(reader.isUnsignedInteger())
486 result = unsigned_integer(reader.toUnsignedInteger());
487 else if(reader.isNegativeInteger())
490 const auto magnitude =
static_cast<quint64
>(reader.toNegativeInteger());
491 if(magnitude == 0 || magnitude > 2147483648ULL)
492 throw codec_error{
"Integer outside int32 range"};
493 result = integer(-
static_cast<std::int64_t
>(magnitude));
495 else if(reader.isBool())
496 result = reader.toBool();
497 else if(reader.isNull())
498 result = ossia::impulse{};
499 else if(reader.isFloat16())
500 result = floating(
static_cast<float>(reader.toFloat16()));
501 else if(reader.isFloat())
502 result = floating(reader.toFloat());
503 else if(reader.isDouble())
504 result = floating(reader.toDouble());
507 "Unsupported or malformed CBOR value (tags, bytes and undefined are not "
510 throw codec_error{
"Malformed CBOR value"};
514inline ossia::value from_cbor(std::string_view input)
516 if(input.size() > max_bytes)
517 throw codec_error{
"Input byte limit exceeded"};
518 QCborStreamReader reader{input.data(),
static_cast<qsizetype
>(input.size())};
520 auto result = cbor_value(reader, budget);
521 if(reader.lastError() != QCborError::NoError)
522 throw codec_error{
"Malformed CBOR input"};
523 if(reader.currentOffset() !=
static_cast<qint64
>(input.size()))
524 throw codec_error{
"Trailing CBOR input"};
577inline void append_bytes(std::string& output, std::string_view bytes)
579 if(bytes.size() > max_bytes - output.size())
580 throw codec_error{
"Output byte limit exceeded"};
581 output.append(bytes);
584inline std::string_view line_ending(LineEnding ending)
590 case LineEnding::CRLF:
600inline ossia::value text_scalar(std::string_view text, TextType type)
602 if(type == TextType::String)
603 return std::string{text};
604 if(type == TextType::Boolean || type == TextType::Auto)
610 if(type == TextType::Boolean)
611 throw codec_error{
"Expected true or false"};
613 if(type == TextType::Integer
614 || (type == TextType::Auto && text.find_first_of(
".eE") == text.npos))
616 std::int32_t number{};
617 auto parsed = std::from_chars(text.data(), text.data() + text.size(), number);
618 if(parsed.ec == std::errc{} && parsed.ptr == text.data() + text.size())
620 if(type == TextType::Integer || parsed.ec == std::errc::result_out_of_range)
621 throw codec_error{
"Expected int32 text"};
624 if(parse_float64(text, number))
625 return floating(number);
628 && ((text.front() >=
'0' && text.front() <=
'9') || text.front() ==
'-'
629 || text.front() ==
'+' || text.front() ==
'.');
630 if(type == TextType::Float || numeric)
631 throw codec_error{
"Invalid or out-of-range numeric text"};
632 return std::string{text};
635inline bool structured_text(std::string_view text)
637 const auto start = text.find_first_not_of(
" \t\r\n");
638 if(start == text.npos)
640 text.remove_prefix(start);
641 const char first = text.front();
642 return first ==
'[' || first ==
'{' || first ==
'"' || first ==
'-'
643 || (first >=
'0' && first <=
'9') || text ==
"true" || text ==
"false"
651 using iterator_category = std::output_iterator_tag;
652 using difference_type = std::ptrdiff_t;
653 using value_type = void;
654 using pointer = void;
655 using reference = void;
671 void number(
float) { }
672 void boolean(
bool) { }
673 void text(std::string_view text) { utf8(text); }
674 void key(std::string_view text) { utf8(text); }
675 void array(std::size_t) { }
676 void map(std::size_t) { }
681inline void validate_value(
const ossia::value& value)
689inline std::string to_pretty(
const ossia::value& value)
691 validate_value(value);
693 fmt::format_to(pretty_output_iterator{&bounded},
"{}", value);
694 return std::move(bounded.data);
697inline ossia::value from_pretty(std::string_view text)
705 for(std::size_t i = 0; i < text.size();)
707 const char c = text[i++];
710 if(c ==
'\\' && i < text.size() && text[i] ==
'"')
719 limits::depth(++depth);
723 throw codec_error{
"Unmatched pretty container"};
726 else if((c >=
'a' && c <=
'z') || (c >=
'A' && c <=
'Z'))
731 && ((text[i] >=
'a' && text[i] <=
'z') || (text[i] >=
'0' && text[i] <=
'9')))
734 else if((c >=
'0' && c <=
'9') || c ==
'-' || c ==
'+' || c ==
'.')
737 const auto start = i - 1;
738 while(i < text.size()
739 && ((text[i] >=
'0' && text[i] <=
'9') || text[i] ==
'.' || text[i] ==
'e'
740 || text[i] ==
'E' || text[i] ==
'+' || text[i] ==
'-'))
743 const auto token = text.substr(start, i - start);
744 if(!parse_float64(token, number))
745 throw codec_error{
"Invalid pretty number"};
750 throw codec_error{
"Unterminated pretty container or string"};
753 auto result = ossia::parse_pretty_value(text);
754 if(!result.valid() || to_pretty(result) != text)
755 throw codec_error{
"Expected canonical ossia pretty text (maps are display only)"};
759inline void text_delimiter(std::string_view delimiter)
761 if(delimiter.empty() || delimiter.size() > max_bytes
762 || delimiter.find_first_of(
"\"\\[]{}") != delimiter.npos)
764 "Delimiter must be nonempty, bounded and exclude JSON quotes, escapes and "
769inline std::size_t text_separator(std::string_view text, std::string_view delimiter)
772 for(std::size_t i = 0; i < text.size(); ++i)
774 if(quoted && text[i] ==
'\\')
776 else if(text[i] ==
'"')
778 else if(!quoted && text.substr(i).starts_with(delimiter))
784template <
typename Values>
785std::string delimited_text(
const Values& values, std::string_view delimiter)
787 text_delimiter(delimiter);
788 if(values.size() >= max_nodes)
789 throw codec_error{
"Value count limit exceeded"};
791 std::size_t field_start{};
793 for(
const auto& value : values)
795 auto token = encode<json_writer>(value);
796 if(text_separator(token, delimiter) != token.npos)
797 throw codec_error{
"Numeric text field contains the delimiter"};
800 const auto boundary = output.size();
801 append_bytes(output, delimiter);
802 if(text_separator(std::string_view{output}.substr(field_start), delimiter)
803 != boundary - field_start)
804 throw codec_error{
"Text field overlaps the delimiter"};
805 field_start = output.size();
807 append_bytes(output, token);
813inline ossia::value from_text(
814 std::string_view text, TextStyle style, TextType type, std::string_view delimiter,
817 if(text.size() > max_bytes)
818 throw codec_error{
"Input byte limit exceeded"};
822 if(type == TextType::String)
823 return std::string{text};
824 const auto suffix = line_ending(ending);
825 if(!suffix.empty() && text.ends_with(suffix))
826 text.remove_suffix(suffix.size());
827 if(style == TextStyle::Pretty)
828 return from_pretty(text);
829 if(type != TextType::Auto && type != TextType::List)
830 return text_scalar(text, type);
831 const auto start = text.find_first_not_of(
" \t\r\n");
832 if(start != text.npos && (text[start] ==
'[' || text[start] ==
'{'))
833 return from_json(text);
834 text_delimiter(delimiter);
835 auto separator = text_separator(text, delimiter);
836 if(type != TextType::List && separator == text.npos)
837 return structured_text(text) ? from_json(text) : text_scalar(text, TextType::Auto);
838 std::vector<ossia::value> values;
844 const auto token = text.substr(0, separator);
845 const auto token_start = token.find_first_not_of(
" \t\r\n");
846 if(token_start != token.npos
847 && (token[token_start] ==
'[' || token[token_start] ==
'{'))
848 throw codec_error{
"Nested text containers require a complete JSON representation"};
850 structured_text(token) ? from_json(token) : text_scalar(token, TextType::Auto));
851 if(separator == text.npos)
853 text.remove_prefix(separator + delimiter.size());
854 separator = text_separator(text, delimiter);
859inline std::string to_text(
860 const ossia::value& value, TextStyle style, std::string_view delimiter,
863 if(
const auto* list = value.target<std::vector<ossia::value>>();
864 list && list->size() >= max_nodes)
865 throw codec_error{
"Value count limit exceeded"};
867 if(style == TextStyle::Pretty)
868 output = to_pretty(value);
869 else if(
const auto* text = value.target<std::string>())
871 if(text->size() > max_bytes)
872 throw codec_error{
"String byte limit exceeded"};
878 const auto* list = value.target<std::vector<ossia::value>>();
879 list && !list->empty()
880 && std::all_of(list->begin(), list->end(), [](
const ossia::value& item) {
881 return item.target<int>() || item.target<float>() || item.target<bool>()
882 || item.target<std::string>() || item.target<ossia::impulse>();
884 output = delimited_text(*list, delimiter);
885 else if(
const auto* vector = value.target<ossia::vec2f>())
886 output = delimited_text(*vector, delimiter);
887 else if(
const auto* vector = value.target<ossia::vec3f>())
888 output = delimited_text(*vector, delimiter);
889 else if(
const auto* vector = value.target<ossia::vec4f>())
890 output = delimited_text(*vector, delimiter);
892 output = encode<json_writer>(value);
893 append_bytes(output, line_ending(ending));
905inline std::size_t binary_width(
char code)
927 throw codec_error{
"Unknown binary format code"};
931inline char scalar_code(ScalarType type)
939 case ScalarType::u16:
941 case ScalarType::i16:
943 case ScalarType::u32:
945 case ScalarType::i32:
947 case ScalarType::f32:
949 case ScalarType::f64:
952 throw codec_error{
"Unknown scalar type"};
963 std::vector<binary_field> fields;
965 std::size_t values{};
969 CHAR_BIT == 8 &&
sizeof(short) == 2 &&
sizeof(
int) == 4 &&
sizeof(
long long) == 8
970 &&
sizeof(
bool) == 1 &&
sizeof(float) == 4 &&
sizeof(
double) == 8
971 && std::numeric_limits<float>::is_iec559 && std::numeric_limits<double>::is_iec559);
973 std::endian::native == std::endian::little
974 || std::endian::native == std::endian::big);
976inline std::size_t native_alignment(
char code)
982 return alignof(short);
988 return alignof(
long long);
990 return alignof(float);
992 return alignof(double);
994 return alignof(bool);
1000inline binary_layout compile_layout(std::string_view format, ByteOrder order)
1002 if(format.size() > max_nodes)
1003 throw codec_error{
"Format text limit exceeded"};
1004 binary_layout layout{order, {}, 0, 0};
1006 = [](
char c) {
return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r'; };
1008 while(pos < format.size() && whitespace(format[pos]))
1011 if(pos < format.size())
1013 const char prefix = format[pos];
1014 if(prefix ==
'<' || prefix ==
'>' || prefix ==
'!' || prefix ==
'=' || prefix ==
'@')
1017 native = prefix ==
'@';
1018 layout.order = prefix ==
'<' ? ByteOrder::Little
1019 : prefix ==
'>' || prefix ==
'!' ? ByteOrder::Big
1020 : std::endian::native == std::endian::little ? ByteOrder::Little
1024 while(pos < format.size())
1026 if(whitespace(format[pos]))
1031 std::size_t count{};
1032 bool explicit_count{};
1033 while(pos < format.size() && format[pos] >=
'0' && format[pos] <=
'9')
1035 explicit_count =
true;
1036 count = count * 10 + (format[pos++] -
'0');
1037 if(count > max_nodes)
1038 throw codec_error{
"Binary count limit exceeded"};
1042 if(pos == format.size())
1043 throw codec_error{
"Invalid binary count"};
1044 const char code = format[pos++];
1045 const auto width = binary_width(code);
1046 if(count == 0 && (code ==
's' || code ==
'x'))
1047 throw codec_error{
"Zero repeats require a numeric alignment code"};
1048 const auto alignment = native ? native_alignment(code) : 1;
1049 const auto padding = (alignment - layout.bytes % alignment) % alignment;
1050 if(padding > max_bytes - layout.bytes)
1051 throw codec_error{
"Binary layout resource limit exceeded"};
1054 layout.fields.push_back({
'x', padding});
1055 layout.bytes += padding;
1057 const auto bytes = count * width;
1058 const auto values = code ==
'x' ? 0 : code ==
's' ? 1 : count;
1059 if(bytes > max_bytes - layout.bytes || values >= max_nodes - layout.values)
1060 throw codec_error{
"Binary layout resource limit exceeded"};
1061 layout.bytes += bytes;
1062 layout.values += values;
1063 layout.fields.push_back({code, count});
1065 if(layout.fields.empty())
1066 throw codec_error{
"Empty binary layout"};
1077 const binary_layout& get(std::string_view format, ByteOrder requested)
1079 if(format.size() > max_nodes)
1081 if(!initialized || source != format || order != requested)
1089 layout = compile_layout(format, requested);
1103write_word(std::string& output, std::uint64_t word, std::size_t width, ByteOrder order)
1105 if(width > max_bytes - output.size())
1107 for(std::size_t i = 0; i < width; ++i)
1109 const auto shift = 8 * (order == ByteOrder::Little ? i : width - 1 - i);
1110 output.push_back(
static_cast<char>((word >> shift) & 255));
1115read_word(std::string_view input, std::size_t& pos, std::size_t width, ByteOrder order)
1117 if(width > input.size() - pos)
1118 throw codec_error{
"Truncated binary input"};
1119 std::uint64_t word{};
1120 for(std::size_t i = 0; i < width; ++i)
1122 const auto shift = 8 * (order == ByteOrder::Little ? i : width - 1 - i);
1123 word |= std::uint64_t(
static_cast<unsigned char>(input[pos++])) << shift;
1129read_scalar(std::string_view input, std::size_t& pos,
char code, ByteOrder order)
1131 const auto width = binary_width(code);
1132 const auto word = read_word(input, pos, width, order);
1136 return floating(std::bit_cast<float>(
static_cast<std::uint32_t
>(word)));
1138 return floating(std::bit_cast<double>(word));
1141 throw codec_error{
"Boolean byte must be 0 or 1"};
1147 const auto extended = width < 8 && (word & (std::uint64_t{1} << (width * 8 - 1)))
1148 ? word | (~std::uint64_t{0} << (width * 8))
1150 return integer(std::bit_cast<std::int64_t>(extended));
1153 return unsigned_integer(word);
1158write_scalar(std::string& output,
const ossia::value& value,
char code, ByteOrder order)
1160 const auto width = binary_width(code);
1161 if(code ==
'f' || code ==
'd')
1164 if(
const auto* f = value.target<
float>())
1166 else if(
const auto* i = value.target<
int>())
1169 throw codec_error{
"Floating field requires a number"};
1173 ? std::uint64_t(std::bit_cast<std::uint32_t>(
static_cast<float>(number)))
1174 :
std::bit_cast<
std::uint64_t>(number);
1175 write_word(output, word, width, order);
1178 std::int64_t number;
1179 if(
const auto* i = value.target<
int>())
1181 else if(
const auto* b = value.target<
bool>())
1184 throw codec_error{
"Integer field requires an integer or boolean"};
1185 const bool signed_type = code ==
'b' || code ==
'h' || code ==
'i' || code ==
'q';
1188 if(number < 0 || number > 1)
1189 throw codec_error{
"Boolean field must be 0 or 1"};
1191 else if(signed_type && width < 8)
1193 const std::int64_t bound = std::int64_t{1} << (width * 8 - 1);
1194 if(number < -bound || number >= bound)
1195 throw codec_error{
"Signed field overflow"};
1197 else if(!signed_type)
1200 || (width < 8 && std::uint64_t(number) >= (std::uint64_t{1} << (width * 8))))
1201 throw codec_error{
"Unsigned field overflow"};
1203 write_word(output,
static_cast<std::uint64_t
>(number), width, order);
1208 std::string& output;
1211 std::size_t depth{};
1212 void operator()()
const {
throw codec_error{
"Cannot serialize an unset value"}; }
1213 void operator()(ossia::impulse)
const
1217 void operator()(
int v)
const { write_scalar(output, v,
'i', order); }
1218 void operator()(
float v)
const { write_scalar(output, v,
'f', order); }
1219 void operator()(
bool v)
const { write_scalar(output, v,
'?', order); }
1220 void operator()(
const std::string& v)
const { append_bytes(output, v); }
1221 void operator()(
const ossia::value_map_type&)
const
1223 throw codec_error{
"Binary maps have ambiguous field order"};
1225 void operator()(
const std::vector<ossia::value>& values)
const
1227 limits::depth(depth + 1);
1228 for(
const auto& value : values)
1234 template <std::
size_t N>
1235 void operator()(
const std::array<float, N>& values)
const
1237 limits::depth(depth + 1);
1238 for(
float value : values)
1246template <
typename Values>
1247std::string to_binary_fields(
const Values& values,
const binary_layout& layout)
1250 if(values.size() != layout.values)
1251 throw codec_error{
"Binary layout input field count mismatch"};
1252 output.reserve(layout.bytes);
1253 std::size_t index{};
1254 for(
const auto& field : layout.fields)
1256 if(field.code ==
'x')
1258 output.append(field.count,
'\0');
1261 const auto repeats = field.code ==
's' ? 1 : field.count;
1262 for(std::size_t i = 0; i < repeats; ++i)
1264 const ossia::value& item = values[index++];
1265 if(field.code ==
's')
1267 const auto* text = item.target<std::string>();
1268 if(!text || text->size() != field.count)
1269 throw codec_error{
"Fixed string requires exactly its declared byte count"};
1270 append_bytes(output, *text);
1273 write_scalar(output, item, field.code, layout.order);
1280to_binary(
const ossia::value& value, ByteOrder order,
const binary_layout* layout)
1287 value.apply(free_binary_writer{output, order, budget});
1290 if(
const auto* list = value.target<std::vector<ossia::value>>())
1291 return to_binary_fields(*list, *layout);
1292 if(
const auto* vector = value.target<ossia::vec2f>())
1293 return to_binary_fields(*vector, *layout);
1294 if(
const auto* vector = value.target<ossia::vec3f>())
1295 return to_binary_fields(*vector, *layout);
1296 if(
const auto* vector = value.target<ossia::vec4f>())
1297 return to_binary_fields(*vector, *layout);
1298 return to_binary_fields(std::span{&value, 1}, *layout);
1301inline ossia::value from_binary(
1302 std::string_view input, ByteOrder order, ScalarType type,
1303 const binary_layout* layout)
1305 if(input.size() > max_bytes)
1306 throw codec_error{
"Input byte limit exceeded"};
1307 std::vector<ossia::value> values;
1311 const auto code = scalar_code(type);
1312 const auto width = binary_width(code);
1313 if(input.size() % width)
1314 throw codec_error{
"Truncated binary scalar"};
1315 if(input.size() / width >= max_nodes)
1316 throw codec_error{
"Value count limit exceeded"};
1317 values.reserve(input.size() / width);
1318 while(pos < input.size())
1319 values.push_back(read_scalar(input, pos, code, order));
1323 if(input.size() != layout->bytes)
1324 throw codec_error{
"Truncated or trailing binary input"};
1325 values.reserve(layout->values);
1326 for(
const auto& field : layout->fields)
1328 if(field.code ==
'x')
1333 if(field.code ==
's')
1335 values.emplace_back(std::string{input.substr(pos, field.count)});
1339 for(std::size_t i = 0; i < field.count; ++i)
1340 values.push_back(read_scalar(input, pos, field.code, layout->order));
1350template <
typename Output,
typename Function>
1351void publish(Output& output, Function&& function)
1353 using result_type =
decltype(function());
1358 result = function();
1360 catch(
const std::exception& e)
1364 output.error(error);
1365 output.success(error.empty());
1366 output.result(std::move(result));
1374 halp_meta(name,
"Deserialize")
1375 halp_meta(c_name,
"avnd_deserialize")
1376 halp_meta(author,
"ossia team")
1377 halp_meta(category,
"Control/Serialization")
1378 halp_meta(uuid,
"5d265cd1-014f-48bb-9c51-d594c8b58b61")
1381 "Decode Bytes as strict JSON, JSON-compatible CBOR, UTF-8 text or binary. "
1382 "Integers must fit int32; floats round to finite float32 without underflow. "
1383 "Binary decoding returns an ordered list. Limits: 8 MiB, 65536 nodes, 64 "
1384 "containers. Errors clear Value; Error is empty on success.")
1387 struct : halp::val_port<
"Bytes", std::string>
1389 void update(
Deserialize& self) { self.process(); }
1391 halp::enum_t<value_serialization::Format,
"Format"> format;
1392 struct : halp::enum_t<value_serialization::TextStyle,
"Text style">
1396 "Plain reads delimited fields or nested JSON. Pretty reads canonical ossia "
1397 "type labels only; maps are display only, floats were rounded to two "
1398 "decimals, and only quotes are escaped.")
1400 struct : halp::enum_t<value_serialization::TextType,
"Interpretation">
1404 "Auto recognizes separators, JSON and exact scalar tokens, otherwise raw "
1405 "strings. String preserves all UTF-8 bytes and line endings. List forces "
1406 "single delimited fields into a list. Numeric options require exact scalars. "
1407 "Pretty uses its own labels unless String is selected.")
1409 struct : halp::lineedit<
"Delimiter",
",">
1413 "Separator for flat Plain lists and vectors, ignored inside JSON-quoted "
1414 "strings. Must exclude quotes, backslashes and brackets. Nested containers "
1417 struct : halp::enum_t<value_serialization::LineEnding,
"Line ending">
1421 "Remove one matching suffix before parsing, except with String "
1424 struct : halp::enum_t<value_serialization::ByteOrder,
"Byte order">
1427 description,
"Raw scalar endian and the default for layouts without a prefix.")
1429 struct : halp::enum_t<value_serialization::BinaryMode,
"Binary mode">
1433 "FreeFlow reads repeated scalars; Layout reads explicitly typed fields. Both "
1436 struct : halp::enum_t<value_serialization::ScalarType,
"Scalar type">
1440 "Repeated raw binary width. Integers must fit int32; floats round to finite "
1441 "float32 without underflow.")
1443 struct : halp::lineedit<
"Layout",
"<B H I f 4s 2x">
1447 "Python struct subset: < > ! = packed, @ native endian and alignment; "
1448 "no prefix uses Byte order and packed widths. [count]b B h H i I q Q f d ? s "
1450 "Counts <=65536; zero numeric counts align only. Ns is one exact-size raw "
1451 "string; Nx skips padding. No automatic end padding. Int32 and finite float32 "
1457 halp::callback<
"Value", ossia::value> result;
1458 halp::callback<
"Success",
bool> success;
1459 halp::callback<
"Error", std::string> error;
1464 halp_meta(layout, halp::layouts::vbox)
1465 halp::item<&ins::format> format;
1468 halp_meta(layout, halp::layouts::tabs)
1469 halp_flag(hide_tabs);
1470 static constexpr auto model = &ins::format;
1473 halp_meta(name,
"JSON")
1474 halp_meta(layout, halp::layouts::vbox)
1475 halp::label types{.text =
"Strict UTF-8 JSON: int32, finite float32."};
1476 halp::label values{.text =
"null = impulse; arrays = lists; ordered maps."};
1477 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1481 halp_meta(name,
"CBOR")
1482 halp_meta(layout, halp::layouts::vbox)
1483 halp::label types{.text =
"JSON-compatible CBOR: int32, finite float32."};
1484 halp::label keys{.text =
"UTF-8 text keys; no tags, byte strings or undefined."};
1485 halp::label values{.text =
"null = impulse; arrays = lists; ordered maps."};
1486 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1490 halp_meta(name,
"Text")
1491 halp_meta(layout, halp::layouts::vbox)
1492 halp::item<&ins::text_style> style;
1493 halp::item<&ins::text_type> type;
1494 halp::item<&ins::delimiter> delimiter;
1495 halp::item<&ins::line_ending> ending;
1499 halp_meta(name,
"Binary")
1500 halp_meta(layout, halp::layouts::vbox)
1501 halp::item<&ins::byte_order> order;
1502 halp::item<&ins::binary_mode> mode;
1503 halp::item<&ins::scalar_type> scalar;
1504 halp::item<&ins::layout> format_text;
1512 using namespace value_serialization;
1513 publish(outputs, [&]() -> ossia::value {
1514 const auto& bytes = inputs.input.value;
1515 switch(inputs.format.value)
1518 return from_json(bytes);
1520 return from_cbor(bytes);
1523 bytes, inputs.text_style.value, inputs.text_type.value,
1524 inputs.delimiter.value, inputs.line_ending.value);
1525 case Format::Binary:
1527 bytes, inputs.byte_order.value, inputs.scalar_type.value,
1528 inputs.binary_mode.value == BinaryMode::Layout
1529 ? &cache.get(inputs.layout.value, inputs.byte_order.value)
1532 throw codec_error{
"Unknown format"};
1539 halp_meta(name,
"Serialize")
1540 halp_meta(c_name,
"avnd_serialize")
1541 halp_meta(author,
"ossia team")
1542 halp_meta(category,
"Control/Serialization")
1543 halp_meta(uuid,
"5d265cd1-014f-48bb-9c51-d594c8b58b62")
1546 "Encode Value as strict JSON, JSON-compatible CBOR, UTF-8 text or binary. "
1547 "Plain Text infers shape: raw strings, delimited flat lists/vectors, nested JSON; "
1548 "vectors decode as lists. Pretty Text is ossia display, not a lossless archive. "
1549 "Free-flow binary flattens lists/vectors; maps and impulses fail. "
1550 "Limits: 8 MiB, 65536 nodes, 64 containers. Errors clear Bytes.")
1553 struct : halp::val_port<
"Value", ossia::value>
1555 void update(
Serialize& self) { self.process(); }
1557 halp::enum_t<value_serialization::Format,
"Format"> format;
1558 struct : halp::toggle<
"Pretty print">
1561 description,
"Indent JSON without changing its values or numeric precision.")
1563 struct : halp::enum_t<value_serialization::TextStyle,
"Text style">
1567 "Plain infers shape: root strings are raw UTF-8, scalars are plain tokens, "
1568 "flat lists/vectors are delimited JSON tokens, nested containers use JSON. "
1569 "Pretty uses ossia type labels and two-decimal floats: lossy display; maps "
1570 "and ambiguous escaped strings cannot be decoded.")
1572 struct : halp::lineedit<
"Delimiter",
",">
1576 "Separator for nonempty flat Plain lists and vectors. Strings are "
1577 "JSON-quoted; numeric delimiter collisions fail. Must exclude quotes, "
1578 "backslashes and brackets. One-field lists require List interpretation when "
1581 struct : halp::enum_t<value_serialization::LineEnding,
"Line ending">
1585 "Append a line ending to Text, except raw Plain strings which are unchanged.")
1587 struct : halp::enum_t<value_serialization::ByteOrder,
"Byte order">
1590 description,
"Free-flow endian and the default for layouts without a prefix.")
1592 struct : halp::enum_t<value_serialization::BinaryMode,
"Binary mode">
1596 "FreeFlow writes int32, float32, bool bytes and raw strings, flattening lists "
1597 "and vectors without tags. Layout writes explicitly typed fields.")
1599 struct : halp::lineedit<
"Layout",
"<B H I f 4s 2x">
1603 "Python struct subset: < > ! = packed, @ native endian and alignment; "
1604 "no prefix uses Byte order and packed widths. [count]b B h H i I q Q f d ? s "
1606 "Counts <=65536; zero numeric counts align only. Ns consumes one exact-size "
1607 "raw string, never padded or truncated; Nx writes zeros. No automatic end "
1613 halp::callback<
"Bytes", std::string> result;
1614 halp::callback<
"Success",
bool> success;
1615 halp::callback<
"Error", std::string> error;
1620 halp_meta(layout, halp::layouts::vbox)
1621 halp::item<&ins::format> format;
1624 halp_meta(layout, halp::layouts::tabs)
1625 halp_flag(hide_tabs);
1626 static constexpr auto model = &ins::format;
1629 halp_meta(name,
"JSON")
1630 halp_meta(layout, halp::layouts::vbox)
1631 halp::item<&ins::pretty> pretty;
1635 halp_meta(name,
"CBOR")
1636 halp_meta(layout, halp::layouts::vbox)
1637 halp::label types{.text =
"JSON-compatible CBOR: int32, finite float32."};
1638 halp::label values{.text =
"Impulse = null; vectors/lists = arrays."};
1639 halp::label keys{.text =
"UTF-8 text and unique map keys required."};
1640 halp::label limits{.text =
"8 MiB, 65536 values/keys, 64 containers."};
1644 halp_meta(name,
"Text")
1645 halp_meta(layout, halp::layouts::vbox)
1646 halp::item<&ins::text_style> style;
1647 halp::item<&ins::delimiter> delimiter;
1648 halp::item<&ins::line_ending> ending;
1652 halp_meta(name,
"Binary")
1653 halp_meta(layout, halp::layouts::vbox)
1654 halp::item<&ins::byte_order> order;
1655 halp::item<&ins::binary_mode> mode;
1656 halp::item<&ins::layout> format_text;
1664 using namespace value_serialization;
1665 publish(outputs, [&]() -> std::string {
1666 const auto& value = inputs.input.value;
1667 switch(inputs.format.value)
1670 return inputs.pretty.value ? encode<pretty_json_writer>(value)
1671 : encode<json_writer>(value);
1673 return encode<cbor_writer>(value);
1676 value, inputs.text_style.value, inputs.delimiter.value,
1677 inputs.line_ending.value);
1678 case Format::Binary:
1680 value, inputs.byte_order.value,
1681 inputs.binary_mode.value == BinaryMode::Layout
1682 ? &cache.get(inputs.layout.value, inputs.byte_order.value)
1685 throw codec_error{
"Unknown format"};
Definition BaseScenarioSerialization.cpp:17