From 19f551a42bbb7bfd4c1ac1dca3770d8bbfa6003b Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 1 Sep 2023 09:42:27 +0300 Subject: [PATCH 1/7] add rsutils::string::hexdump (cherry picked from commit 8f01c760d7e9af50b5e1fb8acd54fd197800c9e5) --- .../rsutils/include/rsutils/string/hexdump.h | 111 ++++++ third-party/rsutils/src/hexdump.cpp | 329 ++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 third-party/rsutils/include/rsutils/string/hexdump.h create mode 100644 third-party/rsutils/src/hexdump.cpp diff --git a/third-party/rsutils/include/rsutils/string/hexdump.h b/third-party/rsutils/include/rsutils/string/hexdump.h new file mode 100644 index 0000000000..4809c7f4e9 --- /dev/null +++ b/third-party/rsutils/include/rsutils/string/hexdump.h @@ -0,0 +1,111 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. + +#pragma once + +#include +#include + + +namespace rsutils { +namespace string { + + +// Allow easy dumping of memory contents into a stream, so easily stringified +// +// E.g.: +// std::ostringstream ss; +// ss << hexdump( this ); // output pointer to this object as a hex value +// +struct hexdump +{ + uint8_t const * const _data; + size_t const _cb; + + size_t _max_bytes = 0; // no more than this number of bytes (extra will print "..."; 0=no max) + size_t _gap = 0; // pad with spaces every bytes (0=no gap) + char _gap_character = ' '; + + // Manual ctor for custom memory layout + hexdump( uint8_t const * data, size_t len ) + : _data( data ) + , _cb( len ) + { + } + + // Auto ptr for easy object dumping + template< class T > + hexdump( T const & t ) + : hexdump( reinterpret_cast< uint8_t const * >( &t ), sizeof( T ) ) + { + } + + // Allow no more than this number of bytes out. If 0 (default), there is no maximum. + // Anything past the max bytes will cause a '...' to be appended. + // + // E.g.: + // ss << hexdump( buffer, 100 ).max_bytes( 2 ); + // Will stream: + // 0001... + // + hexdump & max_bytes( size_t cb ) + { + _max_bytes = cb; + return *this; + } + + // Automatically insert a gap character (default ' ') every bytes, or none if 0. + // + // E.g.: + // ss << hexdump( int32_var ).gap( 2 ); + // Will stream: + // 0001 0203 + // + hexdump & gap( size_t cb, char character = ' ' ) + { + _gap = cb; + _gap_character = character; + return *this; + } + + struct _format + { + hexdump & _h; + char const * _fmt; + }; + + // Allow custom formatting of the memory buffer. + // + // E.g.: + // ss << hexdump( vec.data(), vec.size() ).format( "two: {2} four: {4} two: {2}" ); + // Will stream the first 6 bytes: + // two: 0102 four: 03040506 two: 0708 + // + // Syntax: + // {#} Output # bytes consecutively + // {0#} Output # bytes, but with leading 0's removed + // {-[0]#} Output # bytes, but in reverse order + // {#i} Output # bytes as a signed integral value + // {#u} Output # bytes as an unsigned integral value + // {#f} Output # bytes as a floating point value (4f=float; 8f=double) + // {+#} Skip # bytes + // \{ Output '{' (or any character following the '\') + // {repeat:#} Start a repeat sequence that repeats # times (default is 0: as many times as buffer allows) + // {:} End the current sequence and repeat it as necessary + // {:?} Same, but add '...' if there are still more bytes + // {:?} Same, but add if there are still more bytes + // E.g., a simple implementation of a gap: + // ss << hexdump(...).format( "data[{2}{repeat:} {2}{:?}]" ); + // + inline _format format( char const * fmt ) + { + return _format{ *this, fmt }; + } +}; + +std::ostream & operator<<( std::ostream & os, hexdump const & ); +std::ostream & operator<<( std::ostream & os, hexdump::_format const & ); + + +} // namespace string +} // namespace rsutils diff --git a/third-party/rsutils/src/hexdump.cpp b/third-party/rsutils/src/hexdump.cpp new file mode 100644 index 0000000000..c6838dd96a --- /dev/null +++ b/third-party/rsutils/src/hexdump.cpp @@ -0,0 +1,329 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. + +#include +#include +#include +#include +#include + + +namespace { + + +struct _stream_saver +{ + std::ostream & _os; + std::ios_base::fmtflags _flags; + char _fill; + + _stream_saver( std::ostream & os ) + : _os( os ) + , _flags( os.flags() ) + , _fill( os.fill() ) + { + } + + ~_stream_saver() + { + _os.fill( _fill ); + _os.setf( _flags ); + } +}; + + +size_t _write( std::ostream & os, uint8_t const * const data, size_t cb, bool skip_leading_0s = false ) +{ + uint8_t const * pb = data; + if( skip_leading_0s ) + while( cb > 1 && ! *pb ) + ++pb, --cb; + else + os << std::setw( 2 ); + while( cb-- > 0 ) + { + os << int( *pb++ ); + if( cb ) + os << std::setw( 2 ); + } + return pb - data; +} + + +void _write_reverse( std::ostream & os, uint8_t const * const data, size_t cb, bool skip_leading_0s = false ) +{ + if( skip_leading_0s ) + while( cb > 1 && ! data[cb - 1] ) + --cb; + else + os << std::setw( 2 ); + while( cb-- > 0 ) + { + os << int( data[cb] ); + if( cb ) + os << std::setw( 2 ); + } +} + + +unsigned _read_number( char const *& p_format, char const * const p_format_end ) +{ + unsigned n = 0; + while( p_format < p_format_end && *p_format >= '0' && *p_format <= '9' ) + n = n * 10 + (*p_format++ - '0'); + return n; +} + + +} + + +namespace rsutils { +namespace string { + + +std::ostream & operator<<( std::ostream & os, hexdump const & h ) +{ + if( ! h._cb ) + return os; + + _stream_saver state( os ); + os << std::hex; + os.fill( '0' ); + + auto pb = h._data; + size_t n_left = h._cb; + if( h._max_bytes ) + n_left = std::min( h._max_bytes, n_left ); + if( ! h._gap ) + { + _write( os, pb, n_left ); + } + else + { + auto pend = pb + n_left; + pb += _write( os, pb, std::min( h._gap, size_t( pend - pb ) ) ); + while( pb < pend ) + { + os << h._gap_character; + pb += _write( os, pb, std::min( h._gap, size_t( pend - pb ) ) ); + } + } + if( h._max_bytes && h._cb > h._max_bytes ) + os << "..."; + + return os; +} + + +std::ostream & operator<<( std::ostream & os, hexdump::_format const & f ) +{ + _stream_saver state( os ); + os << std::hex; + os.fill( '0' ); + + auto pb = f._h._data; + size_t n_left = f._h._max_bytes ? f._h._max_bytes : f._h._cb; + auto pend = pb + f._h._cb; + char const * pf = f._fmt; + char const * p_repeat = nullptr; // set when we encounter {repeat:} + unsigned c_repeats = 0; // how many times to repeat; 0=infinite + while( *pf ) + { + if( *pf == '\\' ) + { + if( pf[1] ) + ++pf; + } + else if( *pf == '{' ) + { + char const * pfe = ++pf; + int depth = 1; + while( *pfe ) + { + if( *pf == '\\' ) + { + if( pf[1] ) + ++pf; + } + else if( *pfe == '}' ) + { + if( ! --depth ) + break; + } + else if( *pfe == '{' ) + ++depth; + ++pfe; + } + if( *pfe ) // pfe now points to the closing '}' + { + size_t const directive_length = pfe - pf; + // Grouping directives end with ':' (possibly followed by more information) + if( ! p_repeat && 7 <= directive_length && 0 == strncmp( pf, "repeat:", 7 ) ) + { + pf += 7; // past the ':' + c_repeats = _read_number( pf, pfe ); + if( pf != pfe ) + os << "{error:repeat:#}"; + else + p_repeat = pfe + 1; + pf = pfe + 1; + continue; + } + // Group end is ':', possibly followed by more info + if( *pf == ':' ) + { + // Close any previous grouping directive (right now, all we know is "repeat:") + if( p_repeat ) + { + if( 1 == directive_length || pf[1] == '?' ) + { + if( pb < pend ) + { + // Haven't reached the end of the buffer + if( ! n_left ) + { + // Not allowed to output any more... + } + else if( c_repeats == 0 || --c_repeats > 0 ) + { + // Go back to the group beginning (right after the repeat:) + pf = p_repeat; + continue; + } + // done repeating + if( pf[1] == '?' ) + { + // "{:?...}" means print what follows (default to '...') if we still have more to go + // E.g.: + // "dump[{2}{repeat:2} {2}{:?}]" + // Will print the first 6 bytes of the buffer, with a gap every 2. + // If there are more bytes then: + // "dump[0001 0203 0405...]" + pf += 2; + if( pf == pfe ) + os << "..."; + else + os.write( pf, pfe - pf ); + } + } + // done repeating; move past the directive + pf = pfe + 1; + continue; + } + } + os << "{error}"; + pf = pfe + 1; + continue; + } + char const * const prefix = pf; + if( *prefix == '+' // "{+5}" means skip 5 bytes + || *prefix == '-' ) // "{-4}" -> in reverse (big-endian) byte order + ++pf; + size_t n_bytes = _read_number( pf, pfe ); + if( *pf == 'i' || *pf == 'u' ) + { + if( pf + 1 != pfe ) + { + os << "{error}"; + } + else if( n_bytes != 1 && n_bytes != 2 && n_bytes != 4 && n_bytes != 8 ) + { + os << "{error:1/2/4/8}"; + } + else if( pb + n_bytes > pend ) + { + os << "{error:not enough bytes}"; + } + else + { + if( n_bytes == 1 ) + { + if( *pf == 'i' ) + os << std::to_string( int( *reinterpret_cast< int8_t const * >( pb ) ) ); + else + os << std::to_string( int( *reinterpret_cast< uint8_t const * >( pb ) ) ); + } + else if( n_bytes == 2 ) + { + if( *pf == 'i' ) + os << std::to_string( *reinterpret_cast< int16_t const * >( pb ) ); + else + os << std::to_string( *reinterpret_cast< uint16_t const * >( pb ) ); + } + else if( n_bytes == 4 ) + { + if( *pf == 'i' ) + os << std::to_string( *reinterpret_cast< int32_t const * >( pb ) ); + else + os << std::to_string( *reinterpret_cast< uint32_t const * >( pb ) ); + } + else if( n_bytes == 8 ) + { + if( *pf == 'i' ) + os << std::to_string( *reinterpret_cast< int64_t const * >( pb ) ); + else + os << std::to_string( *reinterpret_cast< uint64_t const * >( pb ) ); + } + pb += n_bytes; + } + } + else if( *pf == 'f' ) + { + if( pf + 1 != pfe ) + { + os << "{error}"; + } + else if( n_bytes != 4 && n_bytes != 8 ) + { + os << "{error:4/8}"; + } + else if( pb + n_bytes > pend ) + { + os << "{error:not enough bytes}"; + } + else + { + if( n_bytes == 4 ) + { + os << std::to_string( *reinterpret_cast< float const * >( pb ) ); + } + else if( n_bytes == 8 ) + { + os << std::to_string( *reinterpret_cast< double const * >( pb ) ); + } + pb += n_bytes; + } + } + else if( pf != pfe || ! n_bytes ) + { + os << "{error}"; + } + else + { + n_bytes = std::min( std::min( n_bytes, size_t( pend - pb ) ), n_left ); + if( *prefix == '-' ) + { + _write_reverse( os, pb, n_bytes, '0' == prefix[1] ); + n_left -= n_bytes; // skipped bytes are not output, therefore do not affect n_left + } + else if( *prefix != '+' ) + { + _write( os, pb, n_bytes, '0' == *prefix ); + n_left -= n_bytes; // skipped bytes are not output, therefore do not affect n_left + } + pb += n_bytes; + } + pf = pfe + 1; + continue; + } + } + os << *pf++; + } + + return os; +} + + +} // namespace string +} // namespace rsutils From a443fa761945858330a70b30d5cf942ff5e3f5b5 Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 29 Sep 2023 11:08:28 +0300 Subject: [PATCH 2/7] add hexarray --- .../rsutils/include/rsutils/string/hexarray.h | 82 +++++++++++++++++ third-party/rsutils/src/hexarray.cpp | 90 +++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 third-party/rsutils/include/rsutils/string/hexarray.h create mode 100644 third-party/rsutils/src/hexarray.cpp diff --git a/third-party/rsutils/include/rsutils/string/hexarray.h b/third-party/rsutils/include/rsutils/string/hexarray.h new file mode 100644 index 0000000000..d7493cf932 --- /dev/null +++ b/third-party/rsutils/include/rsutils/string/hexarray.h @@ -0,0 +1,82 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. + +#pragma once + +#include + +#include +#include +#include +#include + + +namespace rsutils { +namespace string { + + +class slice; + + +// If a 'bytearray' is an array of bytes, then an array of hex representations is a 'hexarray'. +// +// An object wrapping a vector of bytes, for writing to/reading from either ostream or json. With ostream, usage is +// standard via operator<<. For json, the type should be implicitly compatible with the various json::get<> variants +// through the to_json() and from_json() functions provided below. +// +// The representation is like a hexdump, but here we go in both directions: either to-hex or from-hex. +// +// Since we represent data that needs to be returned, we cannot simply refer to an existing set of bytes (like hexdump, +// for example) and instead must own it. Construction is always through moves to avoid implicit data copies. Static +// functions for to_string() and to_stream() are provided to deal with such data that cannot be moved. +// +class hexarray +{ + typedef std::vector< uint8_t > bytes; + +private: + bytes _bytes; + + friend void from_json( nlohmann::json const &, hexarray & ); + +public: + hexarray() = default; + hexarray( hexarray && hexa ) = default; + hexarray( bytes && bytearray ) + : _bytes( std::move( bytearray ) ) + { + } + + // Only lower-case hex is understood: any non-lower-case hex characters will throw + static hexarray from_string( slice const & ); + + hexarray & operator=( hexarray && ) = default; + + bytes detach() { return std::move( _bytes ); } + bytes const & get_bytes() const { return _bytes; } + + bool empty() const { return _bytes.empty(); } + void clear() { _bytes.clear(); } + + std::string to_string() const { return to_string( _bytes ); } + static std::string to_string( bytes const & ); + + static std::ostream & to_stream( std::ostream &, bytes const & ); +}; + + +inline std::ostream & operator<<( std::ostream & os, hexarray const & hexa ) +{ + return hexa.to_stream( os, hexa.get_bytes() ); +} + + +// Allow j["key"] = hexarray( bytes ); +void to_json( nlohmann::json &, const hexarray & ); +// Allow j.get< hexarray >(); +void from_json( nlohmann::json const &, hexarray & ); +// See https://github.com/nlohmann/json#arbitrary-types-conversions + + +} // namespace string +} // namespace rsutils diff --git a/third-party/rsutils/src/hexarray.cpp b/third-party/rsutils/src/hexarray.cpp new file mode 100644 index 0000000000..c0b4129b7f --- /dev/null +++ b/third-party/rsutils/src/hexarray.cpp @@ -0,0 +1,90 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. + +#include +#include +#include +#include + +#include + + +namespace rsutils { +namespace string { + + +/*static*/ std::string hexarray::to_string( bytes const & bytearray ) +{ + return rsutils::string::from() << hexdump( bytearray.data(), bytearray.size() ); +} + + +/*static*/ std::ostream & hexarray::to_stream( std::ostream & os, bytes const & bytearray ) +{ + return os << hexdump( bytearray.data(), bytearray.size() ); +} + + +void to_json( nlohmann::json & j, const hexarray & hexa ) +{ + j = hexa.to_string(); +} + + +void from_json( nlohmann::json const & j, hexarray & hexa ) +{ + if( j.is_array() ) + { + hexa._bytes.resize( j.size() ); + std::transform( j.begin(), j.end(), std::begin( hexa._bytes ), + []( nlohmann::json const & elem ) + { + if( ! elem.is_number_unsigned() ) + throw nlohmann::json::type_error::create( 302, "array value not an unsigned integer", &elem ); + auto v = elem.template get< uint64_t >(); + if( v > 255 ) + throw nlohmann::json::out_of_range::create( 401, "array value out of range", &elem ); + return uint8_t( v ); + } ); + } + else if( j.is_string() ) + hexa = hexarray::from_string( j.get< std::string >() ); + else + throw nlohmann::json::type_error::create( 317, "hexarray should be a string", &j ); +} + + +hexarray hexarray::from_string( slice const & s ) +{ + if( s.length() % 2 != 0 ) + throw std::runtime_error( "odd length" ); + char const * pch = s.begin(); + hexarray hexa; + hexa._bytes.resize( s.length() / 2 ); + uint8_t * pb = hexa._bytes.data(); + while( pch < s.end() ) + { + if( *pch >= '0' && *pch <= '9' ) + *pb = ( *pch - '0' ) << 4; + else if( *pch >= 'a' && *pch <= 'f' ) + *pb = ( *pch - 'a' + 10 ) << 4; + else + throw std::runtime_error( "invalid character" ); + + ++pch; + if( *pch >= '0' && *pch <= '9' ) + *pb += ( *pch - '0' ); + else if( *pch >= 'a' && *pch <= 'f' ) + *pb += ( *pch - 'a' + 10 ); + else + throw std::runtime_error( "invalid character" ); + ++pch; + + ++pb; + } + return hexa; +} + + +} // namespace string +} // namespace rsutils \ No newline at end of file From a1ae6bb735b61150b9cda11e1a4fd9e8ef58440e Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 29 Sep 2023 11:08:49 +0300 Subject: [PATCH 3/7] add test-hexarray --- unit-tests/rsutils/string/test-hexarray.cpp | 392 ++++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 unit-tests/rsutils/string/test-hexarray.cpp diff --git a/unit-tests/rsutils/string/test-hexarray.cpp b/unit-tests/rsutils/string/test-hexarray.cpp new file mode 100644 index 0000000000..d2714601fa --- /dev/null +++ b/unit-tests/rsutils/string/test-hexarray.cpp @@ -0,0 +1,392 @@ +// License: Apache 2.0. See LICENSE file in root directory. +// Copyright(c) 2023 Intel Corporation. All Rights Reserved. + +//#cmake:dependencies rsutils + +#include +#include +#include +#include +#include +#include +#include + +using rsutils::string::hexdump; +using rsutils::string::hexarray; +using rsutils::string::from; +using byte = uint8_t; +using bytearray = std::vector< byte >; + + +namespace { + + +constexpr bool is_little_endian() +{ + // Only since C++20 + //return ( std::endian::native == std::endian::little ); + + union + { + uint32_t i; + uint8_t b[4]; + } + bint = { 0x01020304 }; + + return bint.b[0] == 4; +} + +std::string to_string( hexdump const & hex ) +{ + return from() << hex; +} +std::string to_string( hexdump::_format const & hexf ) +{ + return from() << hexf; +} + + +} // namespace + + +TEST_CASE( "hexdump", "[hexarray]" ) +{ + SECTION( "single-values" ) + { + CHECK( to_string( hexdump( 'a' ) ) == "61" ); + CHECK( to_string( hexdump( byte( 0 ) ) ) == "00" ); + CHECK( to_string( hexdump( 0 ) ) == "00000000" ); + CHECK( to_string( hexdump( 0x04030201 ) ) == "01020304" ); // depends on byte-ordering + } + SECTION( "struct" ) + { + // one extra byte at the end + struct { uint32_t i; uint16_t w; uint8_t b; } s{ 0x01020304, 0x0506, 0x78 }; + auto str = to_string( hexdump( s ) ); + CHECK( str.length() == sizeof( s ) * 2 ); + CHECK( str.substr( 0, sizeof( s ) * 2 - 2 ) == "04030201060578" ); + } + SECTION( "struct, packed" ) + { +#pragma pack( push, 1 ) + struct { uint32_t i; uint16_t w; uint8_t b; } s{ 0x01020304, 0x0506, 0x78 }; +#pragma pack( pop ) + CHECK( to_string( hexdump( s ) ) == "04030201060578" ); + } +} + +TEST_CASE( "hexdump of bytearray", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 10; ++i ) + ba.push_back( i ); + std::string ba_string = "00010203040506070809"; + + SECTION( "vector repr is not the bytearray" ) + { + CHECK_FALSE( to_string( hexdump( ba ) ) == ba_string ); + } + SECTION( "have to use buffer syntax" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ) ) == ba_string ); + } + SECTION( "zero length?" ) + { + CHECK( to_string( hexdump( ba.data(), 0 ) ) == "" ); + } +} + +TEST_CASE( "hexdump ellipsis", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 10; ++i ) + ba.push_back( i ); + + SECTION( "have to use buffer syntax" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size()-1 ) ) == "000102030405060708" ); + } + SECTION( "ellipsis only if we ended before end of buffer" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes( ba.size() - 1 ) ) == "000102030405060708..." ); + } + SECTION( "max-bytes zero means no max bytes" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes( 0 ) ) == "00010203040506070809" ); + } + SECTION( "max-bytes greater than length should have no effect" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes( ba.size()+1 ) ) == "00010203040506070809" ); + } +} + +TEST_CASE( "hexdump gap", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 5; ++i ) + ba.push_back( i ); + + SECTION( "gap of 0 is no gap" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap(0) ) == "0001020304" ); + } + SECTION( "gap >= length should have no effect" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( ba.size() ) ) == "0001020304" ); + } + SECTION( "gap of one" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 1 ) ) == "00 01 02 03 04" ); + } + SECTION( "gap of two" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 2, '-' ) ) == "0001-0203-04" ); + } + SECTION( "gap of three" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 3 ) ) == "000102 0304" ); + } + SECTION( "gap of four" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 4 ) ) == "00010203 04" ); + } + SECTION( "gap, max-bytes four" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 4 ).max_bytes( 4 ) ) == "00010203..." ); + } +} + +TEST_CASE( "hexdump format", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 5; ++i ) + ba.push_back( i ); + + SECTION( "no format == no output" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "" ) ) == "" ); + } + SECTION( "even with max-bytes" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes( 1 ).format( "" ) ) == "" ); + } + SECTION( "output just as many bytes as you ask" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{2}" ) ) == "0001" ); + } + SECTION( "but no more than there are" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{10}" ) ) == "0001020304" ); + } + SECTION( "even with max-bytes" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes(2).format( "{3}" ) ) == "0001" ); + } + SECTION( "gap doesn't matter, either" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).gap( 1 ).format( "{3}" ) ) == "000102" ); + } + SECTION( "one following the other" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{1}{2}" ) ) == "000102" ); + } + SECTION( "with extra characters" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{1} -> {2}]" ) ) == "[00 -> 0102]" ); + } + SECTION( "invalid directives show {error} and do not throw" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{hmm} {} {:}]" ) ) == "[{error} {error} {error}]" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{ {} }]" ) ) == "[{error}]" ); + } + SECTION( "skip bytes with {+#}" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{1}{+2}{5}" ) ) == "000304" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{1}{+5}{2}" ) ) == "00" ); + } + SECTION( "other syntax: \\{ etc." ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "\\{}" ) ) == "{}" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "\\" ) ) == "\\" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{1000000}" ) ) == "0001020304" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "{1000000.}" ) ) == "{error}" ); + } +} + +TEST_CASE( "hexdump format {i}", "[hexarray]" ) +{ + uint32_t i4 = 0x01020304; + int32_t negi4 = int32_t( ~0x01020304 + 1 ); + auto i8 = 0x0102030405060708ull; + + SECTION( "our platforms should be little endian" ) + { + CHECK( to_string( hexdump( i4 ) ) == "04030201" ); + } + SECTION( "{-#} to reverse order" ) + { + CHECK( to_string( hexdump( i4 ).format( "{-4}" ) ) == "01020304" ); + } + SECTION( "regular {4} notation" ) + { + CHECK( to_string( hexdump( i4 ).format( "{4}" ) ) == "04030201" ); + CHECK( to_string( hexdump( i4 ).format( "{04}" ) ) == "4030201" ); + } + SECTION( "signed integral value" ) + { + i4 = 1020304; // decimal + i8 = 1020304050607080910; + CHECK( to_string( hexdump( i4 ).format( "{-4}" ) ) == "000f9190" ); + CHECK( to_string( hexdump( i4 ).format( "{-04}" ) ) == "f9190" ); + CHECK( to_string( hexdump( i4 ).format( "{4i}" ) ) == "1020304" ); + CHECK( to_string( hexdump( i4 ).format( "{3i}" ) ) == "{error:1/2/4/8}" ); // non-integral size + CHECK( to_string( hexdump( i4 ).format( "{1}" ) ) == "90" ); + CHECK( to_string( hexdump( i4 ).format( "{1u}" ) ) == "144" ); // 9*16 + CHECK( to_string( hexdump( i4 ).format( "{1i}" ) ) == "-112" ); + CHECK( to_string( hexdump( i4 ).format( "{2u}" ) ) == "37264" ); + CHECK( to_string( hexdump( i4 ).format( "{5i}" ) ) == "{error:1/2/4/8}" ); + CHECK( to_string( hexdump( i4 ).format( "{6i}" ) ) == "{error:1/2/4/8}" ); + CHECK( to_string( hexdump( i4 ).format( "{7i}" ) ) == "{error:1/2/4/8}" ); + CHECK( to_string( hexdump( i8 ).format( "{-8}" ) ) == "0e28d920d353c5ce" ); + CHECK( to_string( hexdump( i8 ).format( "{8i}" ) ) == "1020304050607080910" ); + } + SECTION( "unsigned integral value" ) + { + CHECK( to_string( hexdump( negi4 ).format( "{4}" ) ) == "fcfcfdfe" ); + CHECK( to_string( hexdump( negi4 ).format( "{4i}" ) ) == "-16909060" ); // or -0x01020304 + CHECK( to_string( hexdump( negi4 ).format( "{4u}" ) ) == "4278058236" ); + CHECK( to_string( hexdump( negi4 ).format( "{2i}" ) ) == "-772" ); + CHECK( to_string( hexdump( negi4 ).format( "{1i}" ) ) == "-4" ); + } + SECTION( "not enough bytes" ) + { + CHECK( to_string( hexdump( i4 ).format( "{8i}" ) ) == "{error:not enough bytes}" ); + } +} + +TEST_CASE( "hexdump format {f}", "[hexarray]" ) +{ + float f = 102.03f; + double d = 12345678.91011; + + SECTION( "{4f} for floats" ) + { + CHECK( to_string( hexdump( f ) ) == "5c0fcc42" ); + CHECK( to_string( hexdump( f ).format( "{4f}" ) ) == "102.029999" ); + } + SECTION( "{8f} for doubles" ) + { + CHECK( to_string( hexdump( d ) ) == "029f1fdd298c6741" ); + CHECK( to_string( hexdump( d ).format( "{8f}" ) ) == "12345678.910110" ); + } + SECTION( "invalid size" ) + { + CHECK( to_string( hexdump( f ).format( "{3f}" ) ) == "{error:4/8}"); + } +} + +TEST_CASE( "hexdump format {repeat:}", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 10; ++i ) + ba.push_back( i ); + + SECTION( "basic repeat" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:} {2}{:}]" ) ) == "[0001 0203 0405 0607 0809]" ); + } + SECTION( "without {:}, doesn't repeat" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:} {2}]" ) ) == "[0001 0203]" ); + } + SECTION( "nothing inside repeat will repeat forever unless we add a skip or limit reps" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:}{+2}{:}]" ) ) == "[0001]" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:2}{:}]" ) ) == "[0001]" ); + } + SECTION( "only twice, no ..." ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:2} {2}{:}]" ) ) == "[0001 0203 0405]" ); + } + SECTION( "want ...? have to say it" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).format( "[{2}{repeat:2} {2}{:?}]" ) ) == "[0001 0203 0405...]" ); + } + SECTION( "but only if you don't reach the end" ) + { + CHECK( to_string( hexdump( ba.data(), 6 ).format( "[{2}{repeat:2} {2}{:?}]" ) ) == "[0001 0203 0405]" ); + } + SECTION( "with max-size" ) + { + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes(3).format( "[{2}{repeat:2} {2}{:?}]" ) ) == "[0001 02...]" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes(3).format( "[{2}{repeat:2} {2}{:}]" ) ) == "[0001 02]" ); + CHECK( to_string( hexdump( ba.data(), ba.size() ).max_bytes(3).format( "[{2}{repeat:2} {2}{:?xx}]" ) ) == "[0001 02xx]" ); + } +} + +TEST_CASE( "hexarray", "[hexarray]" ) +{ + bytearray ba; + for( byte i = 0; i < 10; ++i ) + ba.push_back( i ); + std::string const ba_string = to_string( hexdump( ba.data(), ba.size() ) ); + + SECTION( "same as hexdump" ) + { + CHECK( hexarray::to_string( ba ) == ba_string ); + } + SECTION( "but can also read hexarrays" ) + { + CHECK( hexarray::from_string( ba_string ).get_bytes() == ba ); + } + SECTION( "throws on invalid chars" ) + { + CHECK_NOTHROW( hexarray::from_string( std::string( "" ) ) ); + CHECK_THROWS( hexarray::from_string( std::string( "1" ) ) ); // invalid length + CHECK_NOTHROW( hexarray::from_string( std::string( "01" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "02" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "03" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "04" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "05" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "06" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "07" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "08" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "09" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0a" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0b" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0c" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0d" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0e" ) ) ); + CHECK_NOTHROW( hexarray::from_string( std::string( "0f" ) ) ); + CHECK_THROWS( hexarray::from_string( std::string( "0g" ) ) ); // all the rest should throw + CHECK_THROWS( hexarray::from_string( std::string( "0A" ) ) ); // upper-case isn't accepted + } + SECTION( "to json" ) + { + nlohmann::json j; + j["blah"] = hexarray( std::move( ba ) ); + CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}"); + } + SECTION( "(same as using hexarray::to_string)" ) + { + nlohmann::json j; + j["blah"] = hexarray::to_string( ba ); + CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}" ); + } + SECTION( "from json" ) + { + auto j = nlohmann::json::parse( "{\"blah\":\"00010203040506070809\"}" ); + CHECK( j["blah"].get< hexarray >().get_bytes() == ba ); + } + SECTION( "from json shuld accept bytearrays, too" ) + { + auto j = nlohmann::json::parse( "{\"blah\":[0,1,2,3,4,5,6,7,8,9]}" ); + CHECK( j["blah"].get< hexarray >().get_bytes() == ba ); + + j = nlohmann::json::parse( "{\"blah\":[0,1,256]}" ); // out-of-range + CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() ); + + j = nlohmann::json::parse( "{\"blah\":[0,1,2.0]}" ); // must be integer + CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() ); + } +} From 5073852b631345dfdca9de3ae2fa7394c37b837e Mon Sep 17 00:00:00 2001 From: Eran Date: Fri, 29 Sep 2023 09:24:22 +0300 Subject: [PATCH 4/7] revise 'hwm' with hexarray --- third-party/realdds/doc/control.md | 35 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/third-party/realdds/doc/control.md b/third-party/realdds/doc/control.md index f83859cf05..57d0421de7 100644 --- a/third-party/realdds/doc/control.md +++ b/third-party/realdds/doc/control.md @@ -114,19 +114,44 @@ A [disconnection event](discovery.md#disconnection) can be expected if the reply Can be used to send internal commands to the hardware and may brick the device if used. May or may not be implemented, and is not documented. -* `opcode` (string) is suggested +* `opcode` (string) is suggested, but optional +* `data` is required + +It is up to the server to validate and make sure everything is as expected. -Plus any additional fields necessary. It is up to the server to validate and make sure everything is as expected. ```JSON { "id": "hwm", - "opcode": "WWD", - "data": ["kaboom"] + "data": "1400abcd1000000000000000000000000000000000000000" } ``` -A reply can be expected. Attaching the control is recommended so it's clear what was done. +The above is a `GVD` HWM command. + +A reply can be expected. Attaching the control is recommended so it's clear what was done. The reply `data` should be preset: + +```JSON +{ + "id": "hwm", + "sample": ["010f9a5f64d95fd300000000.403", 1], + "control": { + "id": "hwm", + "data": "1400abcd1000000000000000000000000000000000000000" + }, + "data": "10000000011004000e01ffffff01010102000f05000000000000000064006b0000001530ffffffffffffffffffffffffffffffff0365220706600000ffff3935343031300094230500730000ffffffffffffffff0aff3939394146509281a1ffffffffffffffffff9281a1ffffffffffffffffffffffffffffffffffffffffff1f0fffffffffffffffff000027405845ffffffffffffffff908907ffffff01ffffff050aff00260000000200000001000000010000000100000001000000000600010001000100030303020200000000000100070001ffffffffffffffffffffffffffffffffffff014a34323038362d31303001550400ae810100c50004006441050011d00000388401002e0000002dc00000ff" +} +``` + +#### `hexarray` type + +A `hexarray` is a special encoding for a `bytearray`, or array of bytes, in JSON. + +Rather than representing as `[0,100,2,255]`, a `hexarray` is instead represented as a string `"006402ff"` which is a hexadecimal representation of all the consecutive bytes, from index 0 onwards. + +* Only lower-case alphabetical letters are used (`[0-9a-f]`) +* The length must be even (2 hex digits per byte) +* The first hex pair is for the first byte, the second hex pair for the second, onwards ### `dfu-start` and `dfu-apply` From 94bfb7df7c5197efd3f4a4d8db1735426ba0215a Mon Sep 17 00:00:00 2001 From: Eran Date: Mon, 2 Oct 2023 08:00:51 +0300 Subject: [PATCH 5/7] clarify big-endian handling, especially with {0#} notation --- .../rsutils/include/rsutils/string/hexdump.h | 10 +++++----- third-party/rsutils/src/hexdump.cpp | 19 +++++-------------- unit-tests/rsutils/string/test-hexarray.cpp | 16 ++++++++++++---- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/third-party/rsutils/include/rsutils/string/hexdump.h b/third-party/rsutils/include/rsutils/string/hexdump.h index 4809c7f4e9..7405b85aab 100644 --- a/third-party/rsutils/include/rsutils/string/hexdump.h +++ b/third-party/rsutils/include/rsutils/string/hexdump.h @@ -83,11 +83,11 @@ struct hexdump // // Syntax: // {#} Output # bytes consecutively - // {0#} Output # bytes, but with leading 0's removed - // {-[0]#} Output # bytes, but in reverse order - // {#i} Output # bytes as a signed integral value - // {#u} Output # bytes as an unsigned integral value - // {#f} Output # bytes as a floating point value (4f=float; 8f=double) + // {0#} Output # bytes, but with leading 0's removed (implies big-endian) + // {-[0]#} Output # bytes, but in reverse (big-endian) order + // {#i} Interpret # bytes as a signed integral value, and output decimal value + // {#u} Interpret # bytes as an unsigned integral value, and output decimal value + // {#f} Interpret # bytes as a floating point value (4f=float; 8f=double) // {+#} Skip # bytes // \{ Output '{' (or any character following the '\') // {repeat:#} Start a repeat sequence that repeats # times (default is 0: as many times as buffer allows) diff --git a/third-party/rsutils/src/hexdump.cpp b/third-party/rsutils/src/hexdump.cpp index c6838dd96a..2e027cb98a 100644 --- a/third-party/rsutils/src/hexdump.cpp +++ b/third-party/rsutils/src/hexdump.cpp @@ -32,20 +32,11 @@ struct _stream_saver }; -size_t _write( std::ostream & os, uint8_t const * const data, size_t cb, bool skip_leading_0s = false ) +size_t _write( std::ostream & os, uint8_t const * const data, size_t cb ) { uint8_t const * pb = data; - if( skip_leading_0s ) - while( cb > 1 && ! *pb ) - ++pb, --cb; - else - os << std::setw( 2 ); while( cb-- > 0 ) - { - os << int( *pb++ ); - if( cb ) - os << std::setw( 2 ); - } + os << std::setw( 2 ) << int( *pb++ ); return pb - data; } @@ -302,14 +293,14 @@ std::ostream & operator<<( std::ostream & os, hexdump::_format const & f ) else { n_bytes = std::min( std::min( n_bytes, size_t( pend - pb ) ), n_left ); - if( *prefix == '-' ) + if( *prefix == '-' || *prefix == '0' ) { - _write_reverse( os, pb, n_bytes, '0' == prefix[1] ); + _write_reverse( os, pb, n_bytes, *prefix == '0' || '0' == prefix[1] ); n_left -= n_bytes; // skipped bytes are not output, therefore do not affect n_left } else if( *prefix != '+' ) { - _write( os, pb, n_bytes, '0' == *prefix ); + _write( os, pb, n_bytes ); n_left -= n_bytes; // skipped bytes are not output, therefore do not affect n_left } pb += n_bytes; diff --git a/unit-tests/rsutils/string/test-hexarray.cpp b/unit-tests/rsutils/string/test-hexarray.cpp index d2714601fa..6aa4f287c6 100644 --- a/unit-tests/rsutils/string/test-hexarray.cpp +++ b/unit-tests/rsutils/string/test-hexarray.cpp @@ -222,15 +222,23 @@ TEST_CASE( "hexdump format {i}", "[hexarray]" ) SECTION( "our platforms should be little endian" ) { CHECK( to_string( hexdump( i4 ) ) == "04030201" ); + CHECK( to_string( hexdump( i4 ).format( "{4}" ) ) == "04030201" ); } - SECTION( "{-#} to reverse order" ) + SECTION( "{-#} to reverse order (big-endian)" ) { CHECK( to_string( hexdump( i4 ).format( "{-4}" ) ) == "01020304" ); } - SECTION( "regular {4} notation" ) + SECTION( "{0#} implies big-endian" ) { - CHECK( to_string( hexdump( i4 ).format( "{4}" ) ) == "04030201" ); - CHECK( to_string( hexdump( i4 ).format( "{04}" ) ) == "4030201" ); + // 0x100 = 00 01 00 00; removing leading 0s doesn't make sense (we get '1') so removing leading 0s should also + // imply big-endian! + CHECK( to_string( hexdump( 0x100 ) ) == "00010000" ); + CHECK( to_string( hexdump( 0x100 ).format( "{2}" ) ) == "0001" ); + CHECK( to_string( hexdump( 0x100 ).format( "{02}" ) ) == "100" ); + CHECK( to_string( hexdump( 0x1 ).format( "{01}" ) ) == "1" ); + + CHECK( to_string( hexdump( i4 ).format( "{04}" ) ) == "1020304" ); + CHECK( to_string( hexdump( i4 ).format( "{-04}" ) ) == to_string( hexdump( i4 ).format( "{04}" ) ) ); } SECTION( "signed integral value" ) { From 697e82af7373f917ce8243adf20055bedd1365b7 Mon Sep 17 00:00:00 2001 From: Eran Date: Tue, 3 Oct 2023 08:26:56 +0300 Subject: [PATCH 6/7] clarify single-value hexdump to be more usable --- .../rsutils/include/rsutils/string/hexdump.h | 35 +++++++++++++++++-- third-party/rsutils/src/hexdump.cpp | 5 ++- unit-tests/rsutils/string/test-hexarray.cpp | 24 ++++++++++--- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/third-party/rsutils/include/rsutils/string/hexdump.h b/third-party/rsutils/include/rsutils/string/hexdump.h index 7405b85aab..c8e5fd8f1f 100644 --- a/third-party/rsutils/include/rsutils/string/hexdump.h +++ b/third-party/rsutils/include/rsutils/string/hexdump.h @@ -5,6 +5,7 @@ #include #include +#include namespace rsutils { @@ -25,6 +26,7 @@ struct hexdump size_t _max_bytes = 0; // no more than this number of bytes (extra will print "..."; 0=no max) size_t _gap = 0; // pad with spaces every bytes (0=no gap) char _gap_character = ' '; + bool _big_endian = false; // Manual ctor for custom memory layout hexdump( uint8_t const * data, size_t len ) @@ -33,13 +35,42 @@ struct hexdump { } - // Auto ptr for easy object dumping + // Simplify usage with any type buffer, to avoid having to cast on the outside + template< class T > + hexdump( T const * pt, size_t len ) + : hexdump( reinterpret_cast< uint8_t const * >( pt ), sizeof( T ) ) + { + } + + + // Non-buffer usage would take the form of: + // hexdump( t ) // no length + // These are easy to translate into a buffer. + // However, if 't' is some human-legible type (integral, pointer), we make it so the values show big-endian: + // if i=0x01020304 + // hexdump( &i, sizeof( i )) would give little-endian 04030201 <- not readable + // so hexdump( i ) should give 01020304 + + template< typename T, typename = void > + struct show_in_big_endian + { + static const bool value = false; + }; + template< typename T > + struct show_in_big_endian< T, std::enable_if_t< std::is_integral< T >::value || std::is_pointer< T >::value > > + { + static const bool value = true; + }; + + // Single-value dump: BIG-endian if integral or pointer; otherwise little-endian template< class T > hexdump( T const & t ) - : hexdump( reinterpret_cast< uint8_t const * >( &t ), sizeof( T ) ) + : hexdump( &t, sizeof( T ) ) { + _big_endian = show_in_big_endian< T >::value; } + // Allow no more than this number of bytes out. If 0 (default), there is no maximum. // Anything past the max bytes will cause a '...' to be appended. // diff --git a/third-party/rsutils/src/hexdump.cpp b/third-party/rsutils/src/hexdump.cpp index 2e027cb98a..28282acf49 100644 --- a/third-party/rsutils/src/hexdump.cpp +++ b/third-party/rsutils/src/hexdump.cpp @@ -88,7 +88,10 @@ std::ostream & operator<<( std::ostream & os, hexdump const & h ) n_left = std::min( h._max_bytes, n_left ); if( ! h._gap ) { - _write( os, pb, n_left ); + if( h._big_endian ) + _write_reverse( os, pb, n_left ); + else + _write( os, pb, n_left ); } else { diff --git a/unit-tests/rsutils/string/test-hexarray.cpp b/unit-tests/rsutils/string/test-hexarray.cpp index 6aa4f287c6..7ebe3e220c 100644 --- a/unit-tests/rsutils/string/test-hexarray.cpp +++ b/unit-tests/rsutils/string/test-hexarray.cpp @@ -51,12 +51,28 @@ std::string to_string( hexdump::_format const & hexf ) TEST_CASE( "hexdump", "[hexarray]" ) { - SECTION( "single-values" ) + SECTION( "buffers get native (little-endian) byte ordering" ) + { + int i = 0x04030201; + CHECK( to_string( hexdump( reinterpret_cast< byte const * >( &i ), sizeof( i ) ) ) == "01020304" ); // little-endian + } + SECTION( "single values show the way you'd expect to read them, in big-endian" ) { CHECK( to_string( hexdump( 'a' ) ) == "61" ); CHECK( to_string( hexdump( byte( 0 ) ) ) == "00" ); CHECK( to_string( hexdump( 0 ) ) == "00000000" ); - CHECK( to_string( hexdump( 0x04030201 ) ) == "01020304" ); // depends on byte-ordering + CHECK( to_string( hexdump( 0x04030201 ) ) == "04030201" ); + CHECK( to_string( hexdump( (void *) (0x123) ) ) == "0000000000000123" ); // pointers, too + } + SECTION( "floating point values aren't readable -- they stay at native endian-ness" ) + { + union { uint32_t i; float f; byte b[4]; } u = { 0x40b570a4 }; // 5.67 + CHECK( to_string( hexdump( u ) ) == "a470b540" ); + CHECK( ! hexdump( u )._big_endian ); + CHECK( to_string( hexdump( u.b ) ) == "a470b540" ); // array + CHECK( to_string( hexdump( u.f ) ) == "a470b540" ); // float + CHECK( to_string( hexdump( u.i ) ) == "40b570a4" ); + CHECK( int( reinterpret_cast< byte const * >( &u )[0] ) == 0xa4 ); } SECTION( "struct" ) { @@ -221,7 +237,7 @@ TEST_CASE( "hexdump format {i}", "[hexarray]" ) SECTION( "our platforms should be little endian" ) { - CHECK( to_string( hexdump( i4 ) ) == "04030201" ); + CHECK( to_string( hexdump( i4 ) ) == "01020304" ); CHECK( to_string( hexdump( i4 ).format( "{4}" ) ) == "04030201" ); } SECTION( "{-#} to reverse order (big-endian)" ) @@ -232,7 +248,7 @@ TEST_CASE( "hexdump format {i}", "[hexarray]" ) { // 0x100 = 00 01 00 00; removing leading 0s doesn't make sense (we get '1') so removing leading 0s should also // imply big-endian! - CHECK( to_string( hexdump( 0x100 ) ) == "00010000" ); + CHECK( to_string( hexdump( 0x100 ).format( "{4}" ) ) == "00010000" ); CHECK( to_string( hexdump( 0x100 ).format( "{2}" ) ) == "0001" ); CHECK( to_string( hexdump( 0x100 ).format( "{02}" ) ) == "100" ); CHECK( to_string( hexdump( 0x1 ).format( "{01}" ) ) == "1" ); From b9e6036482f1033a79d9907d7feca6e26ca2bca8 Mon Sep 17 00:00:00 2001 From: Eran Date: Tue, 3 Oct 2023 08:27:13 +0300 Subject: [PATCH 7/7] sample usage in d500 --- src/ds/d500/d500-factory.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ds/d500/d500-factory.cpp b/src/ds/d500/d500-factory.cpp index e090c90372..4e352ca081 100644 --- a/src/ds/d500/d500-factory.cpp +++ b/src/ds/d500/d500-factory.cpp @@ -26,6 +26,10 @@ #include "firmware_logger_device.h" #include "device-calibration.h" +#include +using rsutils::string::hexdump; + + namespace librealsense { @@ -115,8 +119,7 @@ class d555e_device return std::make_shared< d555e_device >( dev_info ); default: - throw std::runtime_error( rsutils::string::from() << "Unsupported D500 model! 0x" << std::hex - << std::setw( 4 ) << std::setfill( '0' ) << (int)pid ); + throw std::runtime_error( rsutils::string::from() << "unsupported D500 PID 0x" << hexdump( pid ) ); } }