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