diff --git a/doc/qbk/examples.qbk b/doc/qbk/examples.qbk index aa521ae3..de805c57 100644 --- a/doc/qbk/examples.qbk +++ b/doc/qbk/examples.qbk @@ -24,5 +24,9 @@ [example_use_allocator] [endsect] +[section CBOR] +[example_cbor] +[endsect] + [endsect] diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index 9fa1999f..eef3e30e 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -122,6 +122,7 @@ [import ../../example/pretty.cpp] [import ../../example/validate.cpp] [import ../../example/use_allocator.cpp] +[import ../../example/cbor.cpp] [import ../../include/boost/json/impl/serialize.ipp] [import ../../test/doc_background.cpp] [import ../../test/doc_parsing.cpp] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 269a0659..4013a2f2 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -80,3 +80,11 @@ add_executable(use_allocator ) set_property(TARGET use_allocator PROPERTY FOLDER "example") target_link_libraries(use_allocator PRIVATE Boost::json) + +# + +add_executable(cbor + cbor.cpp +) +set_property(TARGET cbor PROPERTY FOLDER "example") +target_link_libraries(cbor PRIVATE Boost::json) diff --git a/example/Jamfile b/example/Jamfile index 24cd8b60..47239cdc 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -24,3 +24,5 @@ exe proxy : proxy.cpp ; exe validate : validate.cpp ; exe use_allocator : use_allocator.cpp ; + +exe cbor : cbor.cpp ; diff --git a/example/cbor.cpp b/example/cbor.cpp new file mode 100644 index 00000000..815290d4 --- /dev/null +++ b/example/cbor.cpp @@ -0,0 +1,473 @@ +// +// Copyright 2020 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/json +// + +//[example_cbor + +/* + This example implements simple parsing and serialization of a + subset of CBOR types that are directly supported by JSON. +*/ + +#include +#include +#include +#include +#include + +using namespace boost::json; + +void serialize_cbor_number( + unsigned char mt, std::uint64_t n, std::vector & out ) +{ + mt <<= 5; + + if( n < 24 ) + { + out.push_back( static_cast( mt + n ) ); + } + else if( n < 256 ) + { + unsigned char data[] = { static_cast( mt + 24 ), static_cast( n ) }; + out.insert( out.end(), std::begin( data ), std::end( data ) ); + } + else if( n < 65536 ) + { + unsigned char data[] = { static_cast( mt + 25 ), static_cast( n >> 8 ), static_cast( n ) }; + out.insert( out.end(), std::begin( data ), std::end( data ) ); + } + else if( n < 0x1000000ull ) + { + unsigned char data[ 5 ]; + + data[ 0 ] = static_cast( mt + 26 ); + boost::endian::endian_store( data + 1, static_cast( n ) ); + + out.insert( out.end(), std::begin( data ), std::end( data ) ); + } + else + { + unsigned char data[ 9 ]; + + data[ 0 ] = static_cast( mt + 27 ); + boost::endian::endian_store( data + 1, n ); + + out.insert( out.end(), std::begin( data ), std::end( data ) ); + } +} + +void +serialize_cbor_string( string_view sv, std::vector& out ) +{ + std::size_t n = sv.size(); + serialize_cbor_number( 3, n, out ); + + out.insert( out.end(), sv.data(), sv.data() + n ); +} + +void +serialize_cbor_value( const value& jv, std::vector& out ) +{ + switch( jv.kind() ) + { + case kind::null: + out.push_back( 224 + 22 ); + break; + + case kind::bool_: + out.push_back( 224 + 20 + jv.get_bool() ); + break; + + case kind::int64: + { + std::int64_t n = jv.get_int64(); + if( n >= 0 ) + serialize_cbor_number( 0, n, out ); + else + serialize_cbor_number( 1, ~n, out ); + } + break; + + case kind::uint64: + serialize_cbor_number( 0, jv.get_uint64(), out ); + break; + + case kind::double_: + { + unsigned char data[ 9 ]; + data[ 0 ] = 224 + 27; + boost::endian::endian_store( data + 1, jv.get_double() ); + + out.insert( out.end(), std::begin(data), std::end(data) ); + } + break; + + case kind::string: + serialize_cbor_string( jv.get_string(), out ); + break; + + case kind::array: + { + const array& ja = jv.get_array(); + std::size_t n = ja.size(); + + out.reserve( out.size() + n + 1 ); + + serialize_cbor_number( 4, n, out ); + + for( std::size_t i = 0; i < n; ++i ) + serialize_cbor_value( ja[i], out ); + } + break; + + case kind::object: + { + const object& jo = jv.get_object(); + std::size_t n = jo.size(); + + out.reserve( out.size() + 3 * n + 1 ); + + serialize_cbor_number( 5, n, out ); + + for( const key_value_pair& kv: jo ) + { + serialize_cbor_string( kv.key(), out ); + serialize_cbor_value( kv.value(), out ); + } + } + break; + } +} + +BOOST_NORETURN +void +throw_eof_error() +{ + throw std::runtime_error( "Unexpected end of input" ); +} + +BOOST_NORETURN +void +throw_format_error( char const * err ) +{ + throw std::runtime_error( err ); +} + +void +ensure( std::size_t n, const unsigned char* first, const unsigned char* last ) +{ + if( static_cast(last - first) < n ) + throw_eof_error(); +} + +const unsigned char* +parse_cbor_value( + const unsigned char* first, const unsigned char* last, value& v ); + +const unsigned char* +parse_cbor_number( + const unsigned char* first, const unsigned char* last, unsigned char ch, std::uint64_t& n ) +{ + unsigned char cv = ch & 31; + + if( cv < 24 ) + { + n = cv; + } + else if( cv == 24 ) + { + ensure( 1, first, last ); + n = *first++; + } + else if( cv == 25 ) + { + ensure( 2, first, last ); + n = boost::endian::load_big_u16( first ); + first += 2; + } + else if( cv == 26 ) + { + ensure( 4, first, last ); + n = boost::endian::load_big_u32( first ); + first += 4; + } + else if( cv == 27 ) + { + ensure( 8, first, last ); + n = boost::endian::load_big_u64( first ); + first += 8; + } + else if( cv == 31 ) + { + // infinite array/object + throw_format_error( "Infinite sequences aren't supported" ); + } + else + { + throw_format_error( "Invalid minor type" ); + } + + return first; +} + +const unsigned char* +parse_cbor_string( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + ensure( n, first, last ); + + string_view sv( reinterpret_cast( first ), n ); + first += n; + + v = sv; + return first; +} + +const unsigned char* +parse_cbor_array( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + array & a = v.emplace_array(); + + a.resize( n ); + + std::size_t i = 0; + + for( ; i < n; ++i ) // double[] fast path + { + ensure( 1, first, last ); + unsigned char ch2 = *first; + + if( ch2 != 0xFB ) break; + + ++first; + ensure( 8, first, last ); + + double w = boost::endian::endian_load( first ); + first += 8; + + a[ i ] = w; + } + + for( ; i < n; ++i ) // int[] fast path + { + ensure( 1, first, last ); + unsigned char ch2 = *first; + + if( ch2 >= 0x40 ) break; + + ++first; + + std::uint64_t m; + first = parse_cbor_number( first, last, ch2, m ); + + if( ch2 < 0x20 ) + { + a[ i ] = m; + } + else + { + a[ i ] = static_cast( ~m ); + } + } + + for( ; i < n; ++i ) + first = parse_cbor_value( first, last, a[ i ] ); + + return first; +} + +const unsigned char* +parse_cbor_object( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + object & o = v.emplace_object(); + + o.reserve( n ); + + for( std::size_t i = 0; i < n; ++i ) + { + // key string + + ensure( 1, first, last ); + unsigned char ch2 = *first++; + + if( ( ch2 >> 5 ) != 3 ) + throw_format_error( "Object keys must be strings" ); + + std::uint64_t m; + first = parse_cbor_number( first, last, ch2, m ); + + ensure( m, first, last ); + + string_view sv( reinterpret_cast( first ), m ); + first += m; + + // value + + first = parse_cbor_value( first, last, o[ sv ] ); + } + + return first; +} + +const unsigned char* +parse_cbor_unsigned( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + v = n; + return first; +} + +const unsigned char* +parse_cbor_signed( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + v = static_cast( ~n ); + return first; +} + +const unsigned char* +parse_cbor_semantic_tag( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + std::uint64_t n; + first = parse_cbor_number( first, last, ch, n ); + + // ignore semantic tags + + return parse_cbor_value( first, last, v ); +} + +const unsigned char* +parse_cbor_type7( + const unsigned char* first, const unsigned char* last, unsigned char ch, value& v ) +{ + switch( ch & 31 ) + { + case 20: + v = false; + return first; + + case 21: + v = true; + return first; + + case 22: + v = nullptr; + return first; + + case 26: // float + { + ensure( 4, first, last ); + + float w = boost::endian::endian_load( first ); + first += 4; + + v = w; + return first; + } + + case 27: // double + { + ensure( 8, first, last ); + + double w = boost::endian::endian_load( first ); + first += 8; + + v = w; + return first; + } + + default: + throw_format_error( "Invalid minor type for major type 7" ); + } +} + +const unsigned char* +parse_cbor_value( const unsigned char* first, const unsigned char* last, value& v ) +{ + ensure( 1, first, last ); + const unsigned char ch = *first++; + + switch( ch >> 5 ) + { + case 0: + return parse_cbor_unsigned( first, last, ch, v ); + + case 1: + return parse_cbor_signed( first, last, ch, v ); + + case 2: + throw_format_error( "Binary strings aren't supported" ); + + case 3: + return parse_cbor_string( first, last, ch, v ); + + case 4: + return parse_cbor_array( first, last, ch, v ); + + case 5: + return parse_cbor_object( first, last, ch, v ); + + case 6: + return parse_cbor_semantic_tag( first, last, ch, v ); + + case 7: + return parse_cbor_type7( first, last, ch, v ); + + default: + BOOST_JSON_UNREACHABLE(); + } +} + +int +main(int argc, const char** argv) +{ + if( argc != 2 ) + { + std::cerr << "Usage: cbor FILE_NAME\n"; + return EXIT_FAILURE; + } + + std::ifstream is(argv[1]); + is.exceptions(std::ios::badbit); + + const value jv = parse(is); + + std::vector out; + serialize_cbor_value( jv, out ); + + value jv2; + parse_cbor_value( out.data(), out.data() + out.size(), jv2 ); + + if( jv != jv2 ) + { + std::cerr << "Roundtrip check failed\n"; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +//]