You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

429 lines
16 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2023 Intel Corporation. All Rights Reserved.
//#cmake:dependencies rsutils
#include <unit-tests/test.h>
#include <rsutils/string/hexarray.h>
#include <rsutils/string/hexdump.h>
#include <rsutils/string/from.h>
#include <rsutils/string/slice.h>
#include <rsutils/json.h>
#include <ostream>
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( "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 ) ) == "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" )
{
// 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" );
}
SECTION( "case" )
{
int i = 0x0a0b0c0d;
CHECK( to_string( hexdump( i ) ) == "0a0b0c0d" );
CHECK( ( from() << std::uppercase << hexdump( i ) ).str() == "0A0B0C0D" );
}
}
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 ) ) == "01020304" );
CHECK( to_string( hexdump( i4 ).format( "{4}" ) ) == "04030201" );
}
SECTION( "{-#} to reverse order (big-endian)" )
{
CHECK( to_string( hexdump( i4 ).format( "{-4}" ) ) == "01020304" );
}
SECTION( "{0#} implies big-endian" )
{
// 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 ).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" );
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" )
{
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( "case insensitive" )
{
CHECK( hexarray::from_string( { "0A", 2 } ).to_string() == "0a" );
}
SECTION( "empty string is an empty array" )
{
CHECK( hexarray::from_string( { "abc", size_t(0) } ).to_string() == "" );
}
SECTION( "throws on invalid chars" )
{
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
}
SECTION( "to json" )
{
rsutils::json j;
j["blah"] = hexarray( std::move( ba ) );
CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}");
}
SECTION( "(same as using hexarray::to_string)" )
{
rsutils::json j;
j["blah"] = hexarray::to_string( ba );
CHECK( j.dump() == "{\"blah\":\"00010203040506070809\"}" );
}
SECTION( "from json" )
{
auto j = rsutils::json::parse( "{\"blah\":\"00010203040506070809\"}" );
CHECK( j["blah"].get< hexarray >().get_bytes() == ba );
}
SECTION( "from json shuld accept bytearrays, too" )
{
auto j = rsutils::json::parse( "{\"blah\":[0,1,2,3,4,5,6,7,8,9]}" );
CHECK( j["blah"].get< hexarray >().get_bytes() == ba );
j = rsutils::json::parse( "{\"blah\":[0,1,256]}" ); // out-of-range
CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() );
j = rsutils::json::parse( "{\"blah\":[0,1,2.0]}" ); // must be integer
CHECK_THROWS( j["blah"].get< hexarray >().get_bytes() );
}
}