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