-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathbinary.hpp
More file actions
132 lines (117 loc) · 5.5 KB
/
Copy pathbinary.hpp
File metadata and controls
132 lines (117 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#ifndef POLYWEB_BINARY_HPP_
#define POLYWEB_BINARY_HPP_
#include "Polynet/polynet.hpp"
#include <algorithm>
#include <concepts>
#include <cstddef>
#include <iterator>
#include <memory>
#include <stddef.h>
#include <string.h>
#include <type_traits>
namespace pw {
inline void reverse_memcpy(void* __restrict dest, const void* __restrict src, size_t size) {
char* __restrict dest_bytes = (char*) dest;
const char* __restrict src_bytes = (const char*) src;
for (size_t i = 0; i < size; ++i) {
dest_bytes[i] = src_bytes[size - 1 - i];
}
}
inline void reverse_memmove(void* dest, const void* src, size_t size) {
char* dest_bytes = (char*) dest;
const char* src_bytes = (const char*) src;
// Overlap in either direction defeats a reversing copy, which reads the
// far end of the source only after the near end has been overwritten.
// The straight copy is the one that survives overlap, so do that first
// and reverse in place afterwards.
if (dest_bytes < src_bytes + size && src_bytes < dest_bytes + size) {
memmove(dest, src, size);
std::reverse(dest_bytes, dest_bytes + size);
} else {
reverse_memcpy(dest, src, size);
}
}
namespace binary {
// A one-byte object whose representation is the wire byte. Not the
// types that may be aliased through — that list leaves out signed char
// — because nothing here punts a pointer at another type: reads go
// through memcpy and writes go element by element.
template <typename T>
concept ByteType =
std::same_as<std::remove_cv_t<T>, char> ||
std::same_as<std::remove_cv_t<T>, signed char> ||
std::same_as<std::remove_cv_t<T>, unsigned char> ||
std::same_as<std::remove_cv_t<T>, char8_t> ||
std::same_as<std::remove_cv_t<T>, std::byte>;
template <typename It>
concept ContiguousByteInputIterator =
std::contiguous_iterator<It> && ByteType<std::iter_value_t<It>>;
template <typename It>
concept ByteOutputIterator =
std::output_iterator<It, char> ||
std::output_iterator<It, signed char> ||
std::output_iterator<It, unsigned char> ||
std::output_iterator<It, char8_t> ||
std::output_iterator<It, std::byte>;
// What may cross the wire as a single unit. Trivially copyable is too
// weak a requirement: anything wider than a scalar has an internal
// layout, and reversing the bytes of a struct transposes its members
// rather than converting their byte order, silently. Aggregates have to
// be sent field by field.
template <typename T>
concept Packable = std::integral<T> || std::floating_point<T> || std::is_enum_v<T>;
// std::byte is the one byte type that takes no implicit conversion from
// an integer, so it is the one that needs asking about. Public because
// a protocol layering its own framing on top of write() needs to emit a
// byte without deciding this again.
template <ByteOutputIterator OutputIt>
OutputIt write_byte(OutputIt ret, unsigned char byte) {
if constexpr (requires(std::byte value) { *ret++ = value; }) {
*ret++ = (std::byte) byte;
} else {
*ret++ = byte;
}
return ret;
}
// Returns the iterator past what was read, or `first` unchanged if the
// range is too short — in which case `ret` is left alone. The return
// value is the only report of that, hence the nodiscard; callers that
// would rather branch on it want try_read.
template <Packable T, ContiguousByteInputIterator InputIt>
[[nodiscard]] InputIt read(InputIt first, InputIt last, T& ret, int byte_order = BIG_ENDIAN) {
auto byte_count = (std::iter_difference_t<InputIt>) sizeof(T);
if (std::distance(first, last) < byte_count) {
return first;
}
if constexpr (std::same_as<T, bool>) {
// A bool occupies a byte but only 0 and 1 are valid object
// representations for one. The wire is attacker-controlled, so
// copying 0x02 into a bool is reachable UB.
unsigned char byte;
memcpy(&byte, std::to_address(first), 1);
ret = byte;
} else if (byte_order == BYTE_ORDER) {
memcpy(&ret, std::to_address(first), sizeof(T));
} else {
reverse_memcpy(&ret, std::to_address(first), sizeof(T));
}
std::advance(first, byte_count);
return first;
}
template <Packable T, ContiguousByteInputIterator InputIt>
[[nodiscard]] bool try_read(InputIt& first, InputIt last, T& ret, int byte_order = BIG_ENDIAN) {
InputIt old_first = first;
first = read(first, last, ret, byte_order);
return first != old_first;
}
template <Packable T, ByteOutputIterator OutputIt>
OutputIt write(OutputIt ret, T value, int byte_order = BIG_ENDIAN) {
auto bytes = (const unsigned char*) &value;
for (size_t i = 0; i < sizeof(T); ++i) {
ret = write_byte(ret, byte_order == BYTE_ORDER ? bytes[i] : bytes[sizeof(T) - 1 - i]);
}
return ret;
}
} // namespace binary
} // namespace pw
#endif