Skip to content

Commit 5b2be38

Browse files
author
Christoph M. Wintersteiger
authored
Make serialisation endianness agnostic (#5)
1 parent 438a047 commit 5b2be38

File tree

5 files changed

+134
-48
lines changed

5 files changed

+134
-48
lines changed

CMakeLists.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ if(TESTS)
7676

7777
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
7878
target_compile_options(
79-
${NAME} PRIVATE -fsanitize=address -fno-omit-frame-pointer
79+
${NAME} PRIVATE -fsanitize=undefined,address -fno-omit-frame-pointer
8080
)
81-
target_link_options(${NAME} PRIVATE -fsanitize=address)
81+
target_link_options(${NAME} PRIVATE -fsanitize=undefined,address)
8282
endif()
8383

8484
add_test(${NAME} ${NAME})

merklecpp.h

+51-44
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,6 @@
1616
#include <stack>
1717
#include <vector>
1818

19-
#ifdef WIN32
20-
# include <stdlib.h>
21-
# if BYTE_ORDER == LITTLE_ENDIAN
22-
# define htobe32(X) _byteswap_ulong(X)
23-
# define be32toh(X) _byteswap_ulong(X)
24-
# define htobe64(X) _byteswap_uint64(X)
25-
# define be64toh(X) _byteswap_uint64(X)
26-
# else
27-
# define htobe32(X) (X)
28-
# define be32toh(X) (X)
29-
# define htobe64(X) (X)
30-
# define be64toh(X) (X)
31-
# endif
32-
# undef max
33-
#endif
34-
3519
#ifdef HAVE_OPENSSL
3620
# include <openssl/sha.h>
3721
#endif
@@ -63,22 +47,43 @@
6347

6448
namespace merkle
6549
{
66-
inline void serialise_size_t(size_t size, std::vector<uint8_t>& bytes)
50+
static inline uint32_t convert_endianness(uint32_t n)
51+
{
52+
const uint32_t sz = sizeof(uint32_t);
53+
#if defined(htobe32)
54+
// If htobe32 happens to be a macro, use it.
55+
return htobe32(n);
56+
#elif defined(__LITTLE_ENDIAN__) || defined(__LITTLE_ENDIAN)
57+
// Just as fast.
58+
uint32_t r = 0;
59+
for (size_t i = 0; i < sz; i++)
60+
r |= ((n >> (8 * ((sz - 1) - i))) & 0xFF) << (8 * i);
61+
return *reinterpret_cast<uint32_t*>(&r);
62+
#else
63+
// A little slower, but works for both endiannesses.
64+
uint8_t r[8];
65+
for (size_t i = 0; i < sz; i++)
66+
r[i] = (n >> (8 * ((sz - 1) - i))) & 0xFF;
67+
return *reinterpret_cast<uint32_t*>(&r);
68+
#endif
69+
}
70+
71+
static inline void serialise_uint64_t(uint64_t n, std::vector<uint8_t>& bytes)
6772
{
68-
size_t size_be = htobe64(size);
69-
uint8_t* size_bytes = (uint8_t*)&size_be;
70-
for (size_t i = 0; i < sizeof(size_t); i++)
71-
bytes.push_back(size_bytes[i]);
73+
size_t sz = sizeof(uint64_t);
74+
bytes.reserve(bytes.size() + sz);
75+
for (uint64_t i = 0; i < sz; i++)
76+
bytes.push_back((n >> (8 * (sz - i - 1))) & 0xFF);
7277
}
7378

74-
inline size_t deserialise_size_t(
79+
static inline uint64_t deserialise_uint64_t(
7580
const std::vector<uint8_t>& bytes, size_t& index)
7681
{
77-
size_t result = 0;
78-
uint8_t* result_bytes = (uint8_t*)&result;
79-
for (size_t i = 0; i < sizeof(size_t); i++)
80-
result_bytes[i] = bytes[index++];
81-
return be64toh(result);
82+
uint64_t r = 0;
83+
uint64_t sz = sizeof(uint64_t);
84+
for (uint64_t i = 0; i < sz; i++)
85+
r |= static_cast<uint64_t>(bytes[index++]) << (8 * (sz - i - 1));
86+
return r;
8287
}
8388

8489
/// @brief Template for fixed-size hashes
@@ -163,7 +168,7 @@ namespace merkle
163168
std::string r(num_chars, '_');
164169
for (size_t i = 0; i < num_bytes; i++)
165170
snprintf(
166-
r.data() + 2 * i,
171+
const_cast<char*>(r.data() + 2 * i),
167172
num_chars + 1 - 2 * i,
168173
lower_case ? "%02x" : "%02X",
169174
bytes[i]);
@@ -354,9 +359,9 @@ namespace merkle
354359
{
355360
MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::serialise " << std::endl);
356361
_leaf.serialise(bytes);
357-
serialise_size_t(_leaf_index, bytes);
358-
serialise_size_t(_max_index, bytes);
359-
serialise_size_t(elements.size(), bytes);
362+
serialise_uint64_t(_leaf_index, bytes);
363+
serialise_uint64_t(_max_index, bytes);
364+
serialise_uint64_t(elements.size(), bytes);
360365
for (auto& e : elements)
361366
{
362367
e.hash.serialise(bytes);
@@ -372,9 +377,9 @@ namespace merkle
372377
MERKLECPP_TRACE(MERKLECPP_TOUT << "> PathT::deserialise " << std::endl);
373378
elements.clear();
374379
_leaf.deserialise(bytes, position);
375-
_leaf_index = deserialise_size_t(bytes, position);
376-
_max_index = deserialise_size_t(bytes, position);
377-
size_t num_elements = deserialise_size_t(bytes, position);
380+
_leaf_index = deserialise_uint64_t(bytes, position);
381+
_max_index = deserialise_uint64_t(bytes, position);
382+
size_t num_elements = deserialise_uint64_t(bytes, position);
378383
for (size_t i = 0; i < num_elements; i++)
379384
{
380385
HashT<HASH_SIZE> hash(bytes, position);
@@ -452,7 +457,8 @@ namespace merkle
452457
/// @brief Convert a path to a string
453458
/// @param num_bytes The maximum number of bytes to convert
454459
/// @param lower_case Enables lower-case hex characters
455-
std::string to_string(size_t num_bytes = HASH_SIZE, bool lower_case = true) const
460+
std::string to_string(
461+
size_t num_bytes = HASH_SIZE, bool lower_case = true) const
456462
{
457463
std::stringstream stream;
458464
stream << _leaf.to_string(num_bytes);
@@ -1201,8 +1207,9 @@ namespace merkle
12011207
{
12021208
MERKLECPP_TRACE(MERKLECPP_TOUT << "> serialise " << std::endl;);
12031209

1204-
serialise_size_t(leaf_nodes.size() + uninserted_leaf_nodes.size(), bytes);
1205-
serialise_size_t(num_flushed, bytes);
1210+
serialise_uint64_t(
1211+
leaf_nodes.size() + uninserted_leaf_nodes.size(), bytes);
1212+
serialise_uint64_t(num_flushed, bytes);
12061213
for (auto& n : leaf_nodes)
12071214
n->hash.serialise(bytes);
12081215
for (auto& n : uninserted_leaf_nodes)
@@ -1243,8 +1250,8 @@ namespace merkle
12431250
(to < min_index() || max_index() < to) || from > to)
12441251
throw std::runtime_error("invalid leaf indices");
12451252

1246-
serialise_size_t(to - from + 1, bytes);
1247-
serialise_size_t(from, bytes);
1253+
serialise_uint64_t(to - from + 1, bytes);
1254+
serialise_uint64_t(from, bytes);
12481255
for (size_t i = from; i <= to; i++)
12491256
leaf(i).serialise(bytes);
12501257

@@ -1294,8 +1301,8 @@ namespace merkle
12941301
walk_stack.clear();
12951302
_root = nullptr;
12961303

1297-
size_t num_leaf_nodes = deserialise_size_t(bytes, position);
1298-
num_flushed = deserialise_size_t(bytes, position);
1304+
size_t num_leaf_nodes = deserialise_uint64_t(bytes, position);
1305+
num_flushed = deserialise_uint64_t(bytes, position);
12991306

13001307
leaf_nodes.reserve(num_leaf_nodes);
13011308
for (size_t i = 0; i < num_leaf_nodes; i++)
@@ -1832,7 +1839,7 @@ namespace merkle
18321839
uint32_t cws[64] = {0};
18331840

18341841
for (int i=0; i < 16; i++)
1835-
cws[i] = be32toh(((int32_t *)block)[i]);
1842+
cws[i] = convert_endianness(((int32_t *)block)[i]);
18361843

18371844
for (int i = 16; i < 64; i++) {
18381845
uint32_t t16 = cws[i - 16];
@@ -1864,7 +1871,7 @@ namespace merkle
18641871
}
18651872

18661873
for (int i=0; i < 8; i++)
1867-
((uint32_t*)out.bytes)[i] = htobe32(s[i] + h[i]);
1874+
((uint32_t*)out.bytes)[i] = convert_endianness(s[i] + h[i]);
18681875
}
18691876
// clang-format on
18701877

@@ -1887,7 +1894,7 @@ namespace merkle
18871894
SHA256_Transform(&ctx, &block[0]);
18881895

18891896
for (int i = 0; i < 8; i++)
1890-
((uint32_t*)out.bytes)[i] = htobe32(((uint32_t*)ctx.h)[i]);
1897+
((uint32_t*)out.bytes)[i] = convert_endianness(((uint32_t*)ctx.h)[i]);
18911898
}
18921899

18931900
/// @brief OpenSSL SHA256

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ add_merklecpp_test(past_root past_root.cpp)
2727
add_merklecpp_test(past_paths past_paths.cpp)
2828
add_merklecpp_test(serialisation serialisation.cpp)
2929
add_merklecpp_test(partial_serialisation partial_serialisation.cpp)
30+
add_merklecpp_test(serialise_to_file serialise_to_file.cpp)
3031

3132
if(TARGET evercrypt.host)
3233
add_merklecpp_test(compare_evercrypt compare_evercrypt.cpp)

test/past_paths.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ std::shared_ptr<merkle::Path> past_root_spec(
2828
return result;
2929
}
3030

31-
extern char** environ;
32-
3331
int main(int argc, char** argv)
3432
{
3533
auto test_start_time = std::chrono::high_resolution_clock::now();

test/serialise_to_file.cpp

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
#include <chrono>
5+
#include <fstream>
6+
#include <iomanip>
7+
#include <iostream>
8+
9+
#ifdef HAVE_EVERCRYPT
10+
# include <MerkleTree.h>
11+
#endif
12+
13+
#include "util.h"
14+
15+
#include <merklecpp.h>
16+
17+
#define PRINT_HASH_SIZE 3
18+
19+
int main()
20+
{
21+
try
22+
{
23+
const size_t num_leaves = 11;
24+
{
25+
auto hashes = make_hashes(num_leaves);
26+
merkle::Tree tree1;
27+
for (auto h : hashes)
28+
tree1.insert(h);
29+
auto root1 = tree1.root();
30+
std::cout << "ROOT1=" << root1.to_string() << std::endl;
31+
32+
{
33+
// Write a new file if it doesn't exist.
34+
std::ifstream fi("tree.bytes", std::ifstream::binary);
35+
if (!fi.good())
36+
{
37+
std::vector<uint8_t> bytes;
38+
tree1.serialise(bytes);
39+
std::ofstream f("tree.bytes", std::ofstream::binary);
40+
for (char b : bytes)
41+
f.write(&b, 1);
42+
f.close();
43+
fi.close();
44+
}
45+
}
46+
47+
// Read file if it exists
48+
std::ifstream f("tree.bytes", std::ifstream::binary);
49+
if (f.good())
50+
{
51+
merkle::Tree tree2;
52+
std::vector<uint8_t> bytes;
53+
char t;
54+
while (!f.eof())
55+
{
56+
f.read(&t, 1);
57+
bytes.push_back(t);
58+
}
59+
tree2.deserialise(bytes);
60+
f.close();
61+
auto root2 = tree2.root();
62+
std::cout << "ROOT2=" << root2.to_string() << std::endl;
63+
if (root1 != root2)
64+
throw std::runtime_error("root hash mismatch");
65+
}
66+
}
67+
}
68+
catch (std::exception& ex)
69+
{
70+
std::cout << "Error: " << ex.what() << std::endl;
71+
return 1;
72+
}
73+
catch (...)
74+
{
75+
std::cout << "Error" << std::endl;
76+
return 1;
77+
}
78+
79+
return 0;
80+
}

0 commit comments

Comments
 (0)