/proc/self/cwd/source/extensions/filters/network/thrift_proxy/buffer_helper.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/extensions/filters/network/thrift_proxy/buffer_helper.h" |
2 | | |
3 | | #include "source/common/common/byte_order.h" |
4 | | #include "source/common/common/safe_memcpy.h" |
5 | | |
6 | | namespace Envoy { |
7 | | namespace Extensions { |
8 | | namespace NetworkFilters { |
9 | | namespace ThriftProxy { |
10 | | |
11 | 0 | double BufferHelper::drainBEDouble(Buffer::Instance& buffer) { |
12 | 0 | static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) != sizeof(uint64_t)"); |
13 | 0 | static_assert(std::numeric_limits<double>::is_iec559, "non-IEC559 (IEEE 754) double"); |
14 | | |
15 | | // Implementation based on: |
16 | | // https://github.com/CppCon/CppCon2017/raw/master/Presentations/Type%20Punning%20In%20C%2B%2B17%20-%20Avoiding%20Pun-defined%20Behavior/Type%20Punning%20In%20C%2B%2B17%20-%20Avoiding%20Pun-defined%20Behavior%20-%20Scott%20Schurr%20-%20CppCon%202017.pdf |
17 | | // The short version: |
18 | | // 1. Reinterpreting uint64_t* to double* falls astray of strict aliasing rules. |
19 | | // 2. Using union {uint64_t i; double d;} is undefined behavior in C++ (but not C11). |
20 | | // 3. Using memcpy may be undefined, but probably reliable, and can be optimized to the |
21 | | // same instructions as 1 and 2. |
22 | | // 4. Implementation of last resort is to manually copy from i to d via unsigned char*. |
23 | 0 | uint64_t i = buffer.drainBEInt<uint64_t>(); |
24 | 0 | double d; |
25 | 0 | safeMemcpy(&d, &i); |
26 | 0 | return d; |
27 | 0 | } |
28 | | |
29 | | // Thrift's var int encoding is described in |
30 | | // https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md |
31 | 0 | uint64_t BufferHelper::peekVarInt(Buffer::Instance& buffer, uint64_t offset, int& size) { |
32 | | // Need at least 1 byte for a var int. |
33 | 0 | if (buffer.length() <= offset) { |
34 | 0 | throw EnvoyException("buffer underflow"); |
35 | 0 | } |
36 | | |
37 | | // Need at most 10 bytes for a 64-bit var int. |
38 | 0 | const uint64_t last = std::min(buffer.length() - offset, static_cast<uint64_t>(10)); |
39 | |
|
40 | 0 | uint8_t shift = 0; |
41 | 0 | uint64_t result = 0; |
42 | 0 | for (uint64_t i = 0; i < last; i++) { |
43 | 0 | uint8_t b = buffer.peekInt<uint8_t>(offset + i); |
44 | | |
45 | | // Note: the compact protocol spec says these variable-length ints are encoded as big-endian, |
46 | | // but the Apache C++, Java, and Python implementations read and write them little-endian. |
47 | 0 | result |= static_cast<uint64_t>(b & 0x7f) << shift; |
48 | 0 | shift += 7; |
49 | |
|
50 | 0 | if ((b & 0x80) == 0) { |
51 | | // End of encoded int. |
52 | 0 | size = i + 1; |
53 | 0 | return result; |
54 | 0 | } |
55 | 0 | } |
56 | | |
57 | | // Ran out of bytes (or it's invalid). |
58 | 0 | size = -last; |
59 | 0 | return 0; |
60 | 0 | } |
61 | | |
62 | 0 | int32_t BufferHelper::peekVarIntI32(Buffer::Instance& buffer, uint64_t offset, int& size) { |
63 | 0 | int underlying_size; |
64 | 0 | uint64_t v64 = peekVarInt(buffer, offset, underlying_size); |
65 | |
|
66 | 0 | if (underlying_size <= -5 || underlying_size > 5) { |
67 | 0 | throw EnvoyException("invalid compact protocol varint i32"); |
68 | 0 | } |
69 | | |
70 | 0 | size = underlying_size; |
71 | 0 | if (size < 0) { |
72 | 0 | return 0; |
73 | 0 | } |
74 | | |
75 | 0 | return static_cast<int32_t>(v64); |
76 | 0 | } |
77 | | |
78 | | // Thrift's zig-zag int encoding is described in |
79 | | // https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md |
80 | 0 | int64_t BufferHelper::peekZigZagI64(Buffer::Instance& buffer, uint64_t offset, int& size) { |
81 | 0 | int underlying_size; |
82 | 0 | uint64_t zz64 = peekVarInt(buffer, offset, underlying_size); |
83 | |
|
84 | 0 | if (underlying_size <= -10 || underlying_size > 10) { |
85 | | // Max size is 10, so this must be an invalid encoding. |
86 | 0 | throw EnvoyException("invalid compact protocol zig-zag i64"); |
87 | 0 | } |
88 | | |
89 | 0 | size = underlying_size; |
90 | 0 | if (size < 0) { |
91 | | // Still an underflow, but it might become valid with additional data. |
92 | 0 | return 0; |
93 | 0 | } |
94 | | |
95 | 0 | return (zz64 >> 1) ^ static_cast<uint64_t>(-static_cast<int64_t>(zz64 & 1)); |
96 | 0 | } |
97 | | |
98 | | // Thrift's zig-zag int encoding is described in |
99 | | // https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md |
100 | 0 | int32_t BufferHelper::peekZigZagI32(Buffer::Instance& buffer, uint64_t offset, int& size) { |
101 | 0 | int underlying_size; |
102 | 0 | uint64_t zz64 = peekVarInt(buffer, offset, underlying_size); |
103 | |
|
104 | 0 | if (underlying_size <= -5 || underlying_size > 5) { |
105 | | // Max size is 5, so this must be an invalid encoding. |
106 | 0 | throw EnvoyException("invalid compact protocol zig-zag i32"); |
107 | 0 | } |
108 | | |
109 | 0 | size = underlying_size; |
110 | 0 | if (size < 0) { |
111 | | // Still an underflow, but it might become valid with additional data. |
112 | 0 | return 0; |
113 | 0 | } |
114 | | |
115 | 0 | uint32_t zz32 = static_cast<uint32_t>(zz64); |
116 | 0 | return (zz32 >> 1) ^ static_cast<uint32_t>(-static_cast<int32_t>(zz32 & 1)); |
117 | 0 | } |
118 | | |
119 | 0 | void BufferHelper::writeBEDouble(Buffer::Instance& buffer, double value) { |
120 | 0 | static_assert(sizeof(double) == sizeof(uint64_t), "sizeof(double) != sizeof(uint64_t)"); |
121 | 0 | static_assert(std::numeric_limits<double>::is_iec559, "non-IEC559 (IEEE 754) double"); |
122 | | |
123 | | // See drainDouble for implementation details. |
124 | 0 | uint64_t i; |
125 | 0 | safeMemcpy(&i, &value); |
126 | 0 | buffer.writeBEInt<uint64_t>(i); |
127 | 0 | } |
128 | | |
129 | | // Thrift's var int encoding is described in |
130 | | // https://github.com/apache/thrift/blob/master/doc/specs/thrift-compact-protocol.md |
131 | 0 | void BufferHelper::writeVarIntI32(Buffer::Instance& buffer, int32_t value) { |
132 | 0 | uint8_t bytes[5]; |
133 | 0 | uint32_t v = static_cast<uint32_t>(value); |
134 | 0 | int pos = 0; |
135 | 0 | while (pos < 5) { |
136 | 0 | if ((v & ~0x7F) == 0) { |
137 | 0 | bytes[pos++] = static_cast<uint8_t>(v); |
138 | 0 | break; |
139 | 0 | } |
140 | | |
141 | 0 | bytes[pos++] = static_cast<uint8_t>(v & 0x7F) | 0x80; |
142 | 0 | v >>= 7; |
143 | 0 | } |
144 | 0 | ASSERT(v < 0x80); |
145 | 0 | ASSERT(pos <= 5); |
146 | | |
147 | 0 | buffer.add(bytes, pos); |
148 | 0 | } |
149 | | |
150 | 0 | void BufferHelper::writeVarIntI64(Buffer::Instance& buffer, int64_t value) { |
151 | 0 | uint8_t bytes[10]; |
152 | 0 | uint64_t v = static_cast<uint64_t>(value); |
153 | 0 | int pos = 0; |
154 | 0 | while (pos < 10) { |
155 | 0 | if ((v & ~0x7F) == 0) { |
156 | 0 | bytes[pos++] = static_cast<uint8_t>(v); |
157 | 0 | break; |
158 | 0 | } |
159 | | |
160 | 0 | bytes[pos++] = static_cast<uint8_t>(v & 0x7F) | 0x80; |
161 | 0 | v >>= 7; |
162 | 0 | } |
163 | |
|
164 | 0 | ASSERT(v < 0x80); |
165 | 0 | ASSERT(pos <= 10); |
166 | | |
167 | 0 | buffer.add(bytes, pos); |
168 | 0 | } |
169 | | |
170 | 0 | void BufferHelper::writeZigZagI32(Buffer::Instance& buffer, int32_t value) { |
171 | 0 | uint32_t zz32 = (static_cast<uint32_t>(value) << 1) ^ (value >> 31); |
172 | 0 | writeVarIntI32(buffer, zz32); |
173 | 0 | } |
174 | | |
175 | 0 | void BufferHelper::writeZigZagI64(Buffer::Instance& buffer, int64_t value) { |
176 | 0 | uint64_t zz64 = (static_cast<uint64_t>(value) << 1) ^ (value >> 63); |
177 | 0 | writeVarIntI64(buffer, zz64); |
178 | 0 | } |
179 | | |
180 | | } // namespace ThriftProxy |
181 | | } // namespace NetworkFilters |
182 | | } // namespace Extensions |
183 | | } // namespace Envoy |