Line data Source code
1 : #include "fd_gossip_private.h"
2 : #include "../../ballet/txn/fd_compact_u16.h"
3 :
4 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L22-L23 */
5 : #define WALLCLOCK_MAX_MILLIS (1000000000000000UL)
6 : #define MAX_SLOT (1000000000000000UL)
7 :
8 : /* https://github.com/anza-xyz/agave/blob/master/gossip/src/epoch_slots.rs#L15 */
9 : #define MAX_SLOTS_PER_EPOCH_SLOT (2048UL*8UL)
10 :
11 : struct __attribute__((packed)) slot_hash_pair {
12 : ulong slot;
13 : uchar hash[ 32UL ];
14 : };
15 :
16 : typedef struct slot_hash_pair slot_hash_pair_t;
17 :
18 : /* Adapted from fd_txn_parse.c */
19 : #define CHECK_INIT( payload, payload_sz, offset ) \
20 12 : uchar const * _payload = (payload); \
21 12 : ulong const _payload_sz = (payload_sz); \
22 12 : ulong const _offset = (offset); \
23 12 : ulong _i = (offset); \
24 12 : (void) _payload; \
25 12 : (void) _offset; \
26 :
27 45 : #define CHECK( cond ) do { \
28 45 : if( FD_UNLIKELY( !(cond) ) ) { \
29 0 : return 0; \
30 0 : } \
31 45 : } while( 0 )
32 :
33 24 : #define CHECK_LEFT( n ) CHECK( (n)<=(_payload_sz-_i) )
34 :
35 36 : #define INC( n ) (_i += (ulong)(n))
36 :
37 0 : #define CHECKED_INC( n ) do { \
38 0 : CHECK_LEFT( n ); \
39 0 : INC( n ); \
40 0 : } while( 0 )
41 :
42 : #define READ_CHECKED_COMPACT_U16( out_sz, var_name, where ) \
43 0 : do { \
44 0 : ulong _where = (where); \
45 0 : ulong _out_sz = fd_cu16_dec_sz( _payload+_where, _payload_sz-_where ); \
46 0 : CHECK( _out_sz ); \
47 0 : (var_name) = fd_cu16_dec_fixed( _payload+_where, _out_sz ); \
48 0 : (out_sz) = _out_sz; \
49 0 : } while( 0 )
50 15 : #define CUR_OFFSET ((ushort)_i)
51 0 : #define CURSOR (_payload+_i)
52 12 : #define BYTES_CONSUMED (_i-_offset)
53 : #define BYTES_REMAINING (_payload_sz-_i)
54 :
55 3 : #define CHECKED_WALLCLOCK_LOAD( var_name ) do { \
56 3 : CHECK_LEFT( 8U ); \
57 3 : ulong _wallclock_ms = FD_LOAD( ulong, CURSOR ); \
58 3 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L490-L497 */ \
59 3 : CHECK( _wallclock_ms<WALLCLOCK_MAX_MILLIS ); \
60 3 : (var_name) = FD_MILLI_TO_NANOSEC( _wallclock_ms ); \
61 3 : INC( 8U ); \
62 3 : } while( 0 )
63 :
64 : static ulong
65 : decode_u64_varint( uchar const * payload,
66 : ulong payload_sz,
67 : ulong start_offset,
68 0 : ulong * out_value ) {
69 0 : CHECK_INIT( payload, payload_sz, start_offset );
70 0 : ulong value = 0UL;
71 0 : ulong shift = 0U;
72 0 : while( FD_LIKELY( _i < _payload_sz ) ) {
73 0 : uchar byte = FD_LOAD( uchar, CURSOR ); INC( 1U );
74 0 : value |= (ulong)(byte & 0x7F) << shift;
75 0 : if( !(byte & 0x80) ) break;
76 0 : shift += 7U;
77 0 : if( FD_UNLIKELY( shift >= 64U ) ) return 0;
78 0 : }
79 0 : *out_value = value;
80 0 : return BYTES_CONSUMED;
81 0 : }
82 :
83 : /* Returns bytes_consumed for valid bitvec, 0 otherwise (should be dropped) */
84 : static inline ulong
85 : decode_bitvec_impl( uchar const * payload,
86 : ulong payload_sz,
87 : ulong start_offset,
88 : ulong * out_bits_offset,
89 : ulong * out_bits_cap,
90 : ulong * out_bits_cnt,
91 0 : ulong bits_per_element ) {
92 0 : CHECK_INIT( payload, payload_sz, start_offset );
93 :
94 0 : uchar has_bits = 0;
95 0 : CHECK_LEFT( 1U ); has_bits = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
96 0 : if( FD_UNLIKELY( !has_bits ) ) {
97 0 : *out_bits_offset = 0UL;
98 0 : *out_bits_cap = 0UL;
99 0 : *out_bits_cnt = 0UL;
100 0 : return BYTES_CONSUMED;
101 0 : }
102 :
103 0 : ulong elem_sz = bits_per_element/8U;
104 :
105 0 : CHECK_LEFT( 8U ); ulong bits_cap = FD_LOAD( ulong, CURSOR ); INC( 8U );
106 : /* elem_sz*bits_len doesn't overflow */
107 0 : CHECK( bits_cap<=(ULONG_MAX-(elem_sz-1U))/elem_sz );
108 :
109 0 : CHECK_LEFT( bits_cap*elem_sz ); ulong bits_offset = CUR_OFFSET ; INC( bits_cap*elem_sz );
110 0 : CHECK_LEFT( 8U ); ulong bits_cnt = FD_LOAD( ulong, CURSOR ); INC( 8U );
111 :
112 : /* https://github.com/tov/bv-rs/blob/de155853ff8b69d7e9e7f7dcfdf4061242f6eaff/src/bit_vec/mod.rs#L86-L88 */
113 0 : CHECK( bits_cnt<=bits_cap*bits_per_element );
114 :
115 0 : *out_bits_offset = bits_offset;
116 0 : *out_bits_cap = bits_cap;
117 0 : *out_bits_cnt = bits_cnt;
118 0 : return BYTES_CONSUMED;
119 0 : }
120 :
121 : static inline ulong
122 : decode_bitvec_u64( uchar const * payload,
123 : ulong payload_sz,
124 : ulong start_offset,
125 : ulong * out_bits_offset,
126 : ulong * out_bits_cap,
127 0 : ulong * out_bits_cnt ) {
128 0 : return decode_bitvec_impl( payload, payload_sz, start_offset, out_bits_offset, out_bits_cap, out_bits_cnt, 64U );
129 0 : }
130 :
131 : static inline ulong
132 : decode_bitvec_u8( uchar const * payload,
133 : ulong payload_sz,
134 : ulong start_offset,
135 : ulong * out_bits_offset,
136 : ulong * out_bits_cap,
137 0 : ulong * out_bits_cnt ) {
138 0 : return decode_bitvec_impl( payload, payload_sz, start_offset, out_bits_offset, out_bits_cap, out_bits_cnt, 8U );
139 0 : }
140 :
141 : static ulong
142 : fd_gossip_msg_crds_legacy_contact_info_parse( fd_gossip_view_crds_value_t * crds_val,
143 : uchar const * payload,
144 : ulong payload_sz,
145 0 : ulong start_offset ) {
146 0 : CHECK_INIT( payload, payload_sz, start_offset );
147 : /* https://github.com/anza-xyz/agave/blob/540d5bc56cd44e3cc61b179bd52e9a782a2c99e4/gossip/src/legacy_contact_info.rs#L13 */
148 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
149 0 : for( ulong i=0UL; i<10; i++ ) {
150 0 : CHECK_LEFT( 4U ); uint is_ip6 = FD_LOAD( uint, CURSOR ) ; INC( 4U );
151 0 : if( !is_ip6 ){
152 0 : CHECKED_INC( 4U+2U ); /* ip4 + port */
153 0 : } else {
154 0 : CHECKED_INC( 16U+2U+4U+4U ); /* ip6 + port + flowinfo + scope_id */
155 0 : }
156 0 : }
157 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
158 0 : CHECKED_INC( 2U ); /* shred_version */
159 0 : return BYTES_CONSUMED;
160 0 : }
161 :
162 : static ulong
163 : fd_gossip_msg_crds_vote_parse( fd_gossip_view_crds_value_t * crds_val,
164 : uchar const * payload,
165 : ulong payload_sz,
166 3 : ulong start_offset ) {
167 3 : CHECK_INIT( payload, payload_sz, start_offset );
168 3 : CHECK_LEFT( 1U ); crds_val->vote->index = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
169 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L67-L107 */
170 3 : CHECK( crds_val->vote->index<FD_GOSSIP_VOTE_IDX_MAX );
171 3 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
172 3 : ulong transaction_sz;
173 3 : CHECK( fd_txn_parse_core( CURSOR, BYTES_REMAINING, NULL, NULL, &transaction_sz, FD_TXN_INSTR_MAX )!=0UL );
174 3 : crds_val->vote->txn_off = CUR_OFFSET;
175 3 : crds_val->vote->txn_sz = transaction_sz;
176 3 : INC( transaction_sz );
177 3 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
178 3 : return BYTES_CONSUMED;
179 3 : }
180 :
181 : static ulong
182 : fd_gossip_msg_crds_lowest_slot_parse( fd_gossip_view_crds_value_t * crds_val,
183 : uchar const * payload,
184 : ulong payload_sz,
185 0 : ulong start_offset ) {
186 0 : CHECK_INIT( payload, payload_sz, start_offset );
187 0 : CHECK_LEFT( 1U ); uchar ix = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
188 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L67-L107 */
189 0 : CHECK( !ix );
190 :
191 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
192 :
193 0 : CHECK_LEFT( 8U ); uchar root = FD_LOAD( uchar, CURSOR ) ; INC( 8U );
194 0 : CHECK( !root );
195 :
196 0 : CHECK_LEFT( 8U ); crds_val->lowest_slot = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
197 0 : CHECK( crds_val->lowest_slot<MAX_SLOT );
198 :
199 : /* slots set is deprecated, so we skip it. */
200 0 : CHECK_LEFT( 8U ); ulong slots_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
201 0 : CHECK( slots_len==0U );
202 0 : CHECKED_INC( slots_len*8U ); /* overflowing this currently doesn't matter, but be careful */
203 :
204 : /* TODO: stash vector<EpochIncompleteSlots> is deprecated, but is hard to skip
205 : since EpochIncompleteSlots is a dynamically sized type. So we fail this
206 : parse if there are any entries. Might be worth implementing a skip instead,
207 : TBD after live testing.
208 : Idea: rip out parser from fd_types
209 : https://github.com/anza-xyz/agave/blob/540d5bc56cd44e3cc61b179bd52e9a782a2c99e4/gossip/src/deprecated.rs#L19 */
210 0 : CHECK_LEFT( 8U ); ulong stash_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
211 0 : CHECK( stash_len==0U );
212 :
213 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
214 0 : return BYTES_CONSUMED;
215 0 : }
216 :
217 : static ulong
218 : fd_gossip_msg_crds_account_hashes_parse( fd_gossip_view_crds_value_t * crds_val,
219 : uchar const * payload,
220 : ulong payload_sz,
221 0 : ulong start_offset ) {
222 0 : CHECK_INIT( payload, payload_sz, start_offset );
223 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
224 0 : CHECK_LEFT( 8U ); ulong hashes_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
225 0 : slot_hash_pair_t const * hashes = (slot_hash_pair_t const *)CURSOR;
226 0 : CHECK( hashes_len<(ULONG_MAX-39U)/40U ); /* to prevent overflow in next check */
227 0 : CHECKED_INC( hashes_len*40U );
228 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
229 :
230 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L226-L230 */
231 0 : for( ulong i=0UL; i<hashes_len; i++ ) {
232 0 : CHECK( hashes[i].slot<MAX_SLOT );
233 0 : }
234 0 : return BYTES_CONSUMED;
235 0 : }
236 :
237 : static ulong
238 : fd_gossip_msg_crds_epoch_slots_parse( fd_gossip_view_crds_value_t * crds_val,
239 : uchar const * payload,
240 : ulong payload_sz,
241 0 : ulong start_offset ) {
242 0 : CHECK_INIT( payload, payload_sz, start_offset );
243 0 : CHECK_LEFT( 1U ); crds_val->epoch_slots->index = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
244 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L67-L107 */
245 0 : CHECK( crds_val->epoch_slots->index<FD_GOSSIP_EPOCH_SLOTS_IDX_MAX );
246 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
247 0 : CHECK_LEFT( 8U ); ulong slots_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
248 :
249 0 : for( ulong i=0UL; i<slots_len; i++ ) {
250 0 : CHECK_LEFT( 4U ); uint is_uncompressed = FD_LOAD( uint, CURSOR ) ; INC( 4U );
251 0 : if( is_uncompressed ) {
252 0 : CHECK_LEFT( 8U ); ulong first_slot = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
253 0 : CHECK_LEFT( 8U ); ulong num = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
254 :
255 0 : ulong bits_off, bits_cap, bits_cnt;
256 0 : ulong bytes_consumed = decode_bitvec_u8( payload, payload_sz, CUR_OFFSET, &bits_off, &bits_cap, &bits_cnt );
257 0 : CHECK( !!bytes_consumed );
258 0 : INC( bytes_consumed );
259 :
260 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/epoch_slots.rs#L24-L43 */
261 0 : CHECK( first_slot<MAX_SLOT );
262 0 : CHECK( num<MAX_SLOTS_PER_EPOCH_SLOT );
263 0 : CHECK( bits_cnt%8U==0U ); /* must be multiple of 8 */
264 0 : CHECK( bits_cnt==bits_cap*8U ); /* stricter than check in decode_bitvec_u8 */
265 0 : } else {
266 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/epoch_slots.rs#L79-L86*/
267 0 : CHECK_LEFT( 8U ); ulong first_slot = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
268 0 : CHECK_LEFT( 8U ); ulong num = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
269 0 : CHECK( first_slot<MAX_SLOT );
270 0 : CHECK( num<MAX_SLOTS_PER_EPOCH_SLOT );
271 :
272 0 : CHECK_LEFT( 8U ); ulong compressed_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
273 0 : CHECKED_INC( compressed_len ); /* compressed bitvec */
274 0 : }
275 0 : }
276 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
277 :
278 0 : return BYTES_CONSUMED;
279 0 : }
280 :
281 : static ulong
282 : fd_gossip_msg_crds_legacy_version_parse( fd_gossip_view_crds_value_t * crds_val,
283 : uchar const * payload,
284 : ulong payload_sz,
285 0 : ulong start_offset ) {
286 0 : CHECK_INIT( payload, payload_sz, start_offset );
287 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
288 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
289 :
290 0 : CHECKED_INC( 3*2U ); /* major, minor, patch (all u16s)*/
291 0 : CHECK_LEFT( 1U ); uchar has_commit = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
292 0 : if( has_commit ) {
293 0 : CHECKED_INC( 4U );
294 0 : }
295 0 : return BYTES_CONSUMED;
296 0 : }
297 :
298 : static ulong
299 : fd_gossip_msg_crds_version_parse( fd_gossip_view_crds_value_t * crds_val,
300 : uchar const * payload,
301 : ulong payload_sz,
302 0 : ulong start_offset ) {
303 0 : CHECK_INIT( payload, payload_sz, start_offset );
304 0 : INC( fd_gossip_msg_crds_legacy_version_parse( crds_val, payload, payload_sz, start_offset ) );
305 0 : CHECKED_INC( 4U ); /* feature set */
306 0 : return BYTES_CONSUMED;
307 0 : }
308 :
309 : static ulong
310 : fd_gossip_msg_crds_node_instance_parse( fd_gossip_view_crds_value_t * crds_val,
311 : uchar const * payload,
312 : ulong payload_sz,
313 0 : ulong start_offset ) {
314 0 : CHECK_INIT( payload, payload_sz, start_offset );
315 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
316 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
317 0 : CHECKED_INC( 8U ); /* timestamp (currently unused) */
318 0 : CHECK_LEFT( 8U ); crds_val->node_instance->token = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
319 0 : return BYTES_CONSUMED;
320 0 : }
321 :
322 : static ulong
323 : fd_gossip_msg_crds_duplicate_shred_parse( fd_gossip_view_crds_value_t * crds_val,
324 : uchar const * payload,
325 : ulong payload_sz,
326 0 : ulong start_offset ) {
327 0 : fd_gossip_view_duplicate_shred_t * ds = crds_val->duplicate_shred;
328 :
329 0 : CHECK_INIT( payload, payload_sz, start_offset );
330 :
331 0 : CHECK_LEFT( 2U ); ds->index = FD_LOAD( ushort, CURSOR ) ; INC( 2U );
332 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L67-L107 */
333 0 : CHECK( ds->index<FD_GOSSIP_DUPLICATE_SHRED_IDX_MAX );
334 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
335 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
336 0 : CHECK_LEFT( 8U ); ds->slot = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
337 0 : CHECKED_INC( 4U+1U ); /* (unused) + shred type (unused) */
338 0 : CHECK_LEFT( 1U ); ds->num_chunks = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
339 0 : CHECK_LEFT( 1U ); ds->chunk_index = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
340 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/duplicate_shred.rs#L328-L336 */
341 0 : CHECK( ds->chunk_index<ds->num_chunks );
342 0 : CHECK_LEFT( 8U ); ds->chunk_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
343 0 : CHECK_LEFT( ds->chunk_len ); ds->chunk_off = CUR_OFFSET ; INC( ds->chunk_len );
344 0 : return BYTES_CONSUMED;
345 0 : }
346 :
347 : static ulong
348 : fd_gossip_msg_crds_snapshot_hashes_parse( fd_gossip_view_crds_value_t * crds_val,
349 : uchar const * payload,
350 : ulong payload_sz,
351 0 : ulong start_offset ) {
352 0 : CHECK_INIT( payload, payload_sz, start_offset );
353 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
354 0 : CHECK_LEFT( 40U ); crds_val->snapshot_hashes->full_off = CUR_OFFSET ; INC( 40U );
355 0 : CHECK_LEFT( 8U ); ulong incremental_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
356 0 : CHECK( incremental_len<(ULONG_MAX-39U)/40U ); /* to prevent overflow in next check */
357 0 : CHECK_LEFT( incremental_len*40U ); crds_val->snapshot_hashes->inc_off = CUR_OFFSET ; INC( incremental_len*40U );
358 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
359 0 : crds_val->snapshot_hashes->inc_len = incremental_len;
360 :
361 : /* https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/gossip/src/crds_data.rs#L265-L282 */
362 0 : slot_hash_pair_t * full_pair = (slot_hash_pair_t *)(payload + crds_val->snapshot_hashes->full_off);
363 0 : ulong full_slot = full_pair->slot;
364 0 : CHECK( full_slot<MAX_SLOT );
365 :
366 0 : slot_hash_pair_t * inc_pair = (slot_hash_pair_t *)(payload + crds_val->snapshot_hashes->inc_off);
367 0 : for( ulong i=0UL; i<incremental_len; i++ ) {
368 0 : CHECK( inc_pair[i].slot>full_slot );
369 0 : CHECK( inc_pair[i].slot<MAX_SLOT );
370 0 : }
371 :
372 0 : return BYTES_CONSUMED;
373 0 : }
374 :
375 : static ulong
376 : version_parse( fd_contact_info_t * ci,
377 : uchar const * payload,
378 : ulong payload_sz,
379 0 : ulong start_offset ) {
380 0 : CHECK_INIT( payload, payload_sz, start_offset );
381 0 : ulong decode_sz;
382 0 : READ_CHECKED_COMPACT_U16( decode_sz, ci->version.major, CUR_OFFSET ) ; INC( decode_sz );
383 0 : READ_CHECKED_COMPACT_U16( decode_sz, ci->version.minor, CUR_OFFSET ) ; INC( decode_sz );
384 0 : READ_CHECKED_COMPACT_U16( decode_sz, ci->version.patch, CUR_OFFSET ) ; INC( decode_sz );
385 0 : CHECK_LEFT( 4U ); ci->version.commit = FD_LOAD( uint, CURSOR ) ; INC( 4U );
386 0 : CHECK_LEFT( 4U ); ci->version.feature_set = FD_LOAD( uint, CURSOR ) ; INC( 4U );
387 0 : READ_CHECKED_COMPACT_U16( decode_sz, ci->version.client, CUR_OFFSET ); INC( decode_sz );
388 0 : return BYTES_CONSUMED;
389 0 : }
390 :
391 : /* Contact Infos are checked for the following properties
392 : - All addresses in addrs are unique
393 : - Each socket entry references a unique socket tag
394 : - Socket offsets do not cause an overflow
395 : - All addresses are referenced at least once across all sockets
396 : https://github.com/anza-xyz/agave/blob/540d5bc56cd44e3cc61b179bd52e9a782a2c99e4/gossip/src/contact_info.rs#L599
397 :
398 : We perform additional checks when populating the
399 : contact_info->sockets array:
400 : - Address must be ipv4
401 : - Socket tag must fall within range of tags defined in
402 : fd_gossip_types.c (bounded by FD_CONTACT_INFO_SOCKET_CNT)
403 :
404 : Note that these additional checks are not parser failure conditions.
405 : These sockets are simply skipped when populating
406 : contact_info->sockets (marked as null entries). The CRDS value is
407 : considered valid and is still processed into the CRDS table. */
408 :
409 : #define SET_NAME ip4_seen_set
410 0 : #define SET_MAX (1<<15)
411 : #include "../../util/tmpl/fd_set.c"
412 :
413 : #define SET_NAME ip6_seen_set
414 0 : #define SET_MAX (1<<14)
415 : #include "../../util/tmpl/fd_set.c"
416 :
417 : struct ipv6_addr {
418 : ulong hi;
419 : ulong lo;
420 : };
421 :
422 : typedef struct ipv6_addr ipv6_addr_t;
423 :
424 : static inline ulong
425 0 : ipv6_hash( ipv6_addr_t const * addr ) {
426 0 : return fd_ulong_hash( addr->hi ^ fd_ulong_hash( addr->lo ) );
427 0 : }
428 :
429 : /* Existing sets for socket validation */
430 : #define SET_NAME addr_idx_set
431 : #define SET_MAX FD_GOSSIP_CONTACT_INFO_MAX_ADDRESSES
432 : #include "../../util/tmpl/fd_set.c"
433 :
434 : #define SET_NAME socket_tag_set
435 : #define SET_MAX FD_GOSSIP_CONTACT_INFO_MAX_SOCKETS
436 : #include "../../util/tmpl/fd_set.c"
437 :
438 : static ulong
439 : fd_gossip_msg_crds_contact_info_parse( fd_gossip_view_crds_value_t * crds_val,
440 : uchar const * payload,
441 : ulong payload_sz,
442 0 : ulong start_offset ) {
443 0 : CHECK_INIT( payload, payload_sz, start_offset );
444 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
445 0 : ulong wallclock = 0UL;
446 0 : ulong bytes_consumed = decode_u64_varint( payload, payload_sz, CUR_OFFSET, &wallclock );
447 0 : CHECK( !!bytes_consumed );
448 0 : INC( bytes_consumed );
449 0 : CHECK( wallclock<WALLCLOCK_MAX_MILLIS );
450 0 : crds_val->wallclock_nanos = FD_MILLI_TO_NANOSEC( wallclock );
451 :
452 0 : fd_contact_info_t * ci = crds_val->ci_view->contact_info;
453 0 : fd_memcpy( ci->pubkey.uc, payload + crds_val->pubkey_off, 32UL );
454 0 : ci->wallclock_nanos = crds_val->wallclock_nanos;
455 :
456 0 : CHECK_LEFT( 8U ); ci->instance_creation_wallclock_nanos = FD_MICRO_TO_NANOSEC( FD_LOAD( ulong, CURSOR ) ); INC( 8U );
457 0 : CHECK_LEFT( 2U ); ci->shred_version = FD_LOAD( ushort, CURSOR ) ; INC( 2U );
458 0 : INC( version_parse( ci, payload, payload_sz, CUR_OFFSET ) );
459 :
460 0 : ulong decode_sz, addrs_len;
461 0 : READ_CHECKED_COMPACT_U16( decode_sz, addrs_len, CUR_OFFSET ) ; INC( decode_sz );
462 0 : CHECK( addrs_len<=FD_GOSSIP_CONTACT_INFO_MAX_ADDRESSES );
463 :
464 0 : ip4_seen_set_t ip4_seen[ ip4_seen_set_word_cnt ];
465 0 : ip6_seen_set_t ip6_seen[ ip6_seen_set_word_cnt ];
466 0 : ip4_seen_set_new( ip4_seen );
467 0 : ip6_seen_set_new( ip6_seen );
468 :
469 0 : uint ip4_addrs[ FD_GOSSIP_CONTACT_INFO_MAX_ADDRESSES ];
470 :
471 0 : for( ulong i=0UL; i<addrs_len; i++ ) {
472 0 : CHECK_LEFT( 4U ); uchar is_ip6 = FD_LOAD( uchar, CURSOR ) ; INC( 4U );
473 0 : if( FD_LIKELY( !is_ip6 ) ) {
474 0 : CHECK_LEFT( 4U ); ip4_addrs[ i ] = FD_LOAD( uint, CURSOR ) ; INC( 4U );
475 0 : ulong idx = fd_uint_hash( ip4_addrs[ i ] )&(ip4_seen_set_max( ip4_seen )-1);
476 0 : CHECK( !ip4_seen_set_test( ip4_seen, idx ) ); /* Should not be set initially */
477 0 : ip4_seen_set_insert( ip4_seen, idx );
478 0 : } else {
479 : /* TODO: Support IPv6 ... */
480 0 : CHECK_LEFT( 16U ); ipv6_addr_t * ip6_addr = (ipv6_addr_t *)CURSOR ; INC( 16U );
481 0 : ulong idx = ipv6_hash( ip6_addr )&(ip6_seen_set_max( ip6_seen )-1);
482 0 : CHECK( !ip6_seen_set_test( ip6_seen, idx ) );
483 0 : ip6_seen_set_insert( ip6_seen, idx );
484 0 : ip4_addrs[ i ] = 0U; /* Mark as null entry */
485 0 : }
486 0 : }
487 0 : crds_val->ci_view->ip6_cnt = ip6_seen_set_cnt( ip6_seen );
488 :
489 0 : addr_idx_set_t ip_addr_hits[ addr_idx_set_word_cnt ];
490 0 : socket_tag_set_t socket_tag_hits[ socket_tag_set_word_cnt ];
491 0 : addr_idx_set_new( ip_addr_hits );
492 0 : socket_tag_set_new( socket_tag_hits );
493 :
494 0 : ulong sockets_len;
495 0 : READ_CHECKED_COMPACT_U16( decode_sz, sockets_len, CUR_OFFSET ) ; INC( decode_sz );
496 0 : CHECK( sockets_len<=FD_GOSSIP_CONTACT_INFO_MAX_SOCKETS );
497 :
498 0 : fd_memset( ci->sockets, 0, FD_CONTACT_INFO_SOCKET_CNT*sizeof(fd_ip4_port_t) );
499 0 : crds_val->ci_view->unrecognized_socket_tag_cnt = 0UL;
500 :
501 0 : ushort cur_port = 0U;
502 0 : for( ulong i=0UL; i<sockets_len; i++ ) {
503 0 : uchar tag, addr_idx;
504 0 : CHECK_LEFT( 1U ); tag = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
505 0 : CHECK_LEFT( 1U ); addr_idx = FD_LOAD( uchar, CURSOR ) ; INC( 1U );
506 :
507 0 : ushort offset;
508 0 : READ_CHECKED_COMPACT_U16( decode_sz, offset, CUR_OFFSET ) ; INC( decode_sz );
509 0 : CHECK( ((uint)cur_port + (uint)offset)<=(uint)USHORT_MAX ); /* overflow check */
510 0 : cur_port = (ushort)(cur_port + offset);
511 0 : CHECK( !socket_tag_set_test( socket_tag_hits, tag ) ); socket_tag_set_insert( socket_tag_hits, tag );
512 0 : CHECK( addr_idx<addrs_len );
513 0 : addr_idx_set_insert( ip_addr_hits, addr_idx );
514 :
515 0 : if( FD_LIKELY( tag<FD_CONTACT_INFO_SOCKET_CNT ) ) {
516 0 : if( FD_UNLIKELY( !!ip4_addrs[ addr_idx ] ) ) {
517 0 : ci->sockets[ tag ].addr = ip4_addrs[ addr_idx ];
518 0 : ci->sockets[ tag ].port = fd_ushort_bswap( cur_port ); /* TODO: change this to host order */
519 0 : }
520 0 : } else {
521 0 : crds_val->ci_view->unrecognized_socket_tag_cnt++;
522 0 : }
523 0 : }
524 0 : CHECK( addr_idx_set_cnt( ip_addr_hits )==addrs_len );
525 :
526 : /* extensions are currently unused */
527 0 : READ_CHECKED_COMPACT_U16( decode_sz, crds_val->ci_view->ext_len, CUR_OFFSET ) ; INC( decode_sz );
528 0 : CHECKED_INC( 4*crds_val->ci_view->ext_len );
529 :
530 0 : return BYTES_CONSUMED;
531 0 : }
532 :
533 : static ulong
534 : fd_gossip_msg_crds_last_voted_fork_slots_parse( fd_gossip_view_crds_value_t * crds_val,
535 : uchar const * payload,
536 : ulong payload_sz,
537 0 : ulong start_offset ) {
538 0 : CHECK_INIT( payload, payload_sz, start_offset );
539 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
540 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
541 0 : CHECK_LEFT( 4U ); uint is_rawoffsets = FD_LOAD( uint, CURSOR ) ; INC( 4U );
542 0 : if( !is_rawoffsets ) {
543 0 : CHECK_LEFT( 8U ); ulong slots_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
544 0 : CHECKED_INC( slots_len*4U ); /* RunLengthEncoding */
545 0 : } else {
546 0 : ulong bits_off, bits_cap, bits_cnt;
547 0 : ulong bytes_consumed = decode_bitvec_u8( payload, payload_sz, CUR_OFFSET, &bits_off, &bits_cap, &bits_cnt );
548 0 : CHECK( !!bytes_consumed );
549 0 : INC( bytes_consumed );
550 0 : }
551 0 : CHECKED_INC( 8U+32U+2U ); /* last voted slot + last voted hash + shred version */
552 0 : return BYTES_CONSUMED;
553 0 : }
554 :
555 : static ulong
556 : fd_gossip_msg_crds_restart_heaviest_fork_parse( fd_gossip_view_crds_value_t * crds_val,
557 : uchar const * payload,
558 : ulong payload_sz,
559 0 : ulong start_offset ) {
560 0 : CHECK_INIT( payload, payload_sz, start_offset );
561 0 : CHECK_LEFT( 32U ); crds_val->pubkey_off = CUR_OFFSET ; INC( 32U );
562 0 : CHECKED_WALLCLOCK_LOAD( crds_val->wallclock_nanos );
563 0 : CHECKED_INC( 8U+32U+8U+2U ); /* last slot + last slot hash + observed stake + shred version */
564 0 : return BYTES_CONSUMED;
565 0 : }
566 :
567 :
568 : static ulong
569 : fd_gossip_msg_crds_data_parse( fd_gossip_view_crds_value_t * crds_val,
570 : uchar const * payload,
571 : ulong payload_sz,
572 3 : ulong start_offset ) {
573 3 : switch( crds_val->tag ) {
574 0 : case FD_GOSSIP_VALUE_LEGACY_CONTACT_INFO:
575 0 : return fd_gossip_msg_crds_legacy_contact_info_parse( crds_val, payload, payload_sz, start_offset );
576 3 : case FD_GOSSIP_VALUE_VOTE:
577 3 : return fd_gossip_msg_crds_vote_parse( crds_val, payload, payload_sz, start_offset );
578 0 : case FD_GOSSIP_VALUE_LOWEST_SLOT:
579 0 : return fd_gossip_msg_crds_lowest_slot_parse( crds_val, payload, payload_sz, start_offset );
580 0 : case FD_GOSSIP_VALUE_LEGACY_SNAPSHOT_HASHES:
581 0 : case FD_GOSSIP_VALUE_ACCOUNT_HASHES:
582 0 : return fd_gossip_msg_crds_account_hashes_parse( crds_val, payload, payload_sz, start_offset );
583 0 : case FD_GOSSIP_VALUE_EPOCH_SLOTS:
584 0 : return fd_gossip_msg_crds_epoch_slots_parse( crds_val, payload, payload_sz, start_offset );
585 0 : case FD_GOSSIP_VALUE_LEGACY_VERSION:
586 0 : return fd_gossip_msg_crds_legacy_version_parse( crds_val, payload, payload_sz, start_offset );
587 0 : case FD_GOSSIP_VALUE_VERSION:
588 0 : return fd_gossip_msg_crds_version_parse( crds_val, payload, payload_sz, start_offset );
589 0 : case FD_GOSSIP_VALUE_NODE_INSTANCE:
590 0 : return fd_gossip_msg_crds_node_instance_parse( crds_val, payload, payload_sz, start_offset );
591 0 : case FD_GOSSIP_VALUE_DUPLICATE_SHRED:
592 0 : return fd_gossip_msg_crds_duplicate_shred_parse( crds_val, payload, payload_sz, start_offset );
593 0 : case FD_GOSSIP_VALUE_INC_SNAPSHOT_HASHES:
594 0 : return fd_gossip_msg_crds_snapshot_hashes_parse( crds_val, payload, payload_sz, start_offset );
595 0 : case FD_GOSSIP_VALUE_CONTACT_INFO:
596 0 : return fd_gossip_msg_crds_contact_info_parse( crds_val, payload, payload_sz, start_offset );
597 0 : case FD_GOSSIP_VALUE_RESTART_LAST_VOTED_FORK_SLOTS:
598 0 : return fd_gossip_msg_crds_last_voted_fork_slots_parse( crds_val, payload, payload_sz, start_offset );
599 0 : case FD_GOSSIP_VALUE_RESTART_HEAVIEST_FORK:
600 0 : return fd_gossip_msg_crds_restart_heaviest_fork_parse( crds_val, payload, payload_sz, start_offset );
601 0 : default:
602 0 : return 0;
603 3 : }
604 3 : }
605 :
606 : /* start_offset should point to first byte in first crds value. In
607 : push/pullresponse messages this would be after the crds len */
608 : static ulong
609 : fd_gossip_msg_crds_vals_parse( fd_gossip_view_crds_value_t * crds_values,
610 : ulong crds_values_len,
611 : uchar const * payload,
612 : ulong payload_sz,
613 3 : ulong start_offset ) {
614 3 : CHECK_INIT( payload, payload_sz, start_offset );
615 :
616 6 : for( ulong i=0UL; i<crds_values_len; i++ ) {
617 3 : fd_gossip_view_crds_value_t * crds_view = &crds_values[i];
618 3 : CHECK_LEFT( 64U ); crds_view->signature_off = CUR_OFFSET ; INC( 64U );
619 3 : CHECK_LEFT( 4U ); crds_view->tag = FD_LOAD(uchar, CURSOR ); INC( 4U );
620 3 : ulong crds_data_sz = fd_gossip_msg_crds_data_parse( crds_view, payload, payload_sz, CUR_OFFSET );
621 3 : crds_view->length = (ushort)(crds_data_sz + 64U + 4U); /* signature + tag */
622 3 : INC( crds_data_sz );
623 3 : }
624 3 : return BYTES_CONSUMED;
625 3 : }
626 : static ulong
627 : fd_gossip_msg_ping_pong_parse( fd_gossip_view_t * view,
628 : uchar const * payload,
629 : ulong payload_sz,
630 0 : ulong start_offset ) {
631 0 : CHECK_INIT( payload, payload_sz, start_offset );
632 : /* Ping/Pong share the same memory layout */
633 0 : FD_STATIC_ASSERT( sizeof(fd_gossip_view_ping_t)==sizeof(fd_gossip_view_pong_t), compat );
634 0 : CHECK_LEFT( sizeof(fd_gossip_view_ping_t) );
635 0 : view->ping_pong_off = CUR_OFFSET;
636 0 : INC( sizeof(fd_gossip_view_ping_t) );
637 :
638 0 : return BYTES_CONSUMED;
639 0 : }
640 :
641 : static ulong
642 : fd_gossip_pull_req_parse( fd_gossip_view_t * view,
643 : uchar const * payload,
644 : ulong payload_sz,
645 0 : ulong start_offset ) {
646 0 : CHECK_INIT( payload, payload_sz, start_offset );
647 0 : fd_gossip_view_pull_request_t * pr = view->pull_request;
648 :
649 0 : CHECK_LEFT( 8U ); pr->bloom_keys_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
650 0 : CHECK( pr->bloom_keys_len<=((ULONG_MAX-7U)/8U) );
651 0 : CHECK_LEFT( pr->bloom_keys_len*8U ); pr->bloom_keys_offset = CUR_OFFSET ; INC( pr->bloom_keys_len*8U );
652 :
653 0 : ulong bytes_consumed = decode_bitvec_u64( payload, payload_sz, CUR_OFFSET, &pr->bloom_bits_offset, &pr->bloom_len, &pr->bloom_bits_cnt );
654 0 : CHECK( !!bytes_consumed );
655 0 : INC( bytes_consumed );
656 : /* bloom filter bitvec must have at least one element to avoid
657 : div by zero in fd_bloom
658 : https://github.com/anza-xyz/agave/blob/bff4df9cf6f41520a26c9838ee3d4d8c024a96a1/bloom/src/bloom.rs#L58-L67 */
659 0 : CHECK( pr->bloom_len!=0UL );
660 :
661 0 : CHECK_LEFT( 8U ); pr->bloom_num_bits_set = FD_LOAD( ulong, CURSOR ); INC( 8U );
662 0 : CHECK_LEFT( 8U ); pr->mask = FD_LOAD( ulong, CURSOR ); INC( 8U );
663 0 : CHECK_LEFT( 4U ); pr->mask_bits = FD_LOAD( uint, CURSOR ) ; INC( 4U );
664 :
665 0 : INC( fd_gossip_msg_crds_vals_parse( pr->pr_ci,
666 0 : 1U, /* pull request holds only one contact info */
667 0 : payload,
668 0 : payload_sz,
669 0 : CUR_OFFSET ) );
670 0 : return BYTES_CONSUMED;
671 0 : }
672 :
673 : static ulong
674 : fd_gossip_msg_crds_container_parse( fd_gossip_view_t * view,
675 : uchar const * payload,
676 : ulong payload_sz,
677 3 : ulong start_offset ) {
678 : /* Push and Pull Responses are CRDS composite types, */
679 3 : CHECK_INIT( payload, payload_sz, start_offset );
680 3 : fd_gossip_view_crds_container_t * container = view->tag==FD_GOSSIP_MESSAGE_PUSH ? view->push
681 3 : : view->pull_response;
682 3 : CHECK_LEFT( 32U ); container->from_off = CUR_OFFSET ; INC( 32U );
683 3 : CHECK_LEFT( 8U ); container->crds_values_len = FD_LOAD( ushort, CURSOR ); INC( 8U );
684 3 : CHECK( container->crds_values_len<=FD_GOSSIP_MSG_MAX_CRDS );
685 3 : INC( fd_gossip_msg_crds_vals_parse( container->crds_values,
686 3 : container->crds_values_len,
687 3 : payload,
688 3 : payload_sz,
689 3 : CUR_OFFSET ) );
690 3 : return BYTES_CONSUMED;
691 3 : }
692 :
693 : static ulong
694 : fd_gossip_msg_prune_parse( fd_gossip_view_t * view,
695 : uchar const * payload,
696 : ulong payload_sz,
697 0 : ulong start_offset ) {
698 0 : CHECK_INIT( payload, payload_sz, start_offset );
699 0 : fd_gossip_view_prune_t * prune = view->prune;
700 0 : CHECK_LEFT( 32U ); uchar const * outer = CURSOR ; INC( 32U );
701 0 : CHECK_LEFT( 32U ); prune->pubkey_off = CUR_OFFSET ; INC( 32U );
702 0 : CHECK( memcmp( outer, payload+prune->pubkey_off, 32U )==0 );
703 :
704 0 : CHECK_LEFT( 8U ); prune->origins_len = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
705 0 : CHECK( prune->origins_len<=((ULONG_MAX-31U)/32U) );
706 0 : CHECK_LEFT( prune->origins_len*32U ); prune->origins_off = CUR_OFFSET ; INC( prune->origins_len*32U );
707 0 : CHECK_LEFT( 64U ); prune->signature_off = CUR_OFFSET ; INC( 64U );
708 0 : CHECK_LEFT( 32U ); prune->destination_off = CUR_OFFSET ; INC( 32U );
709 0 : CHECK_LEFT( 8U ); prune->wallclock = FD_LOAD( ulong, CURSOR ) ; INC( 8U );
710 0 : CHECK( prune->wallclock<WALLCLOCK_MAX_MILLIS );
711 :
712 : /* Convert wallclock to nanos */
713 0 : prune->wallclock_nanos = FD_MILLI_TO_NANOSEC( prune->wallclock );
714 :
715 0 : return BYTES_CONSUMED;
716 0 : }
717 :
718 : ulong
719 : fd_gossip_msg_parse( fd_gossip_view_t * view,
720 : uchar const * payload,
721 3 : ulong payload_sz ) {
722 3 : CHECK_INIT( payload, payload_sz, 0U );
723 3 : CHECK( payload_sz<=FD_GOSSIP_MTU );
724 :
725 : /* Extract enum discriminant/tag (4b encoded) */
726 3 : uint tag = 0;
727 3 : CHECK_LEFT( 4U ); tag = FD_LOAD( uchar, CURSOR ); INC( 4U );
728 3 : CHECK( tag<=FD_GOSSIP_MESSAGE_LAST );
729 3 : view->tag = (uchar)tag;
730 :
731 3 : switch( view->tag ){
732 0 : case FD_GOSSIP_MESSAGE_PULL_REQUEST:
733 0 : INC( fd_gossip_pull_req_parse( view, payload, payload_sz, CUR_OFFSET ) );
734 0 : break;
735 0 : case FD_GOSSIP_MESSAGE_PULL_RESPONSE:
736 3 : case FD_GOSSIP_MESSAGE_PUSH:
737 3 : INC( fd_gossip_msg_crds_container_parse( view, payload, payload_sz, CUR_OFFSET ) );
738 3 : break;
739 0 : case FD_GOSSIP_MESSAGE_PRUNE:
740 0 : INC( fd_gossip_msg_prune_parse( view, payload, payload_sz, CUR_OFFSET ) );
741 0 : break;
742 0 : case FD_GOSSIP_MESSAGE_PING:
743 0 : case FD_GOSSIP_MESSAGE_PONG:
744 0 : INC( fd_gossip_msg_ping_pong_parse( view, payload, payload_sz, CUR_OFFSET ) );
745 0 : break;
746 0 : default:
747 0 : return 0;
748 3 : }
749 3 : CHECK( payload_sz==CUR_OFFSET );
750 3 : return BYTES_CONSUMED;
751 3 : }
|