1use super::{
22 error::SbeDecodeError,
23 models::{
24 BinanceAccountInfo, BinanceAccountTrade, BinanceAggTrade, BinanceAggTrades, BinanceBalance,
25 BinanceCancelOrderResponse, BinanceDepth, BinanceExchangeInfoSbe, BinanceKline,
26 BinanceKlines, BinanceLotSizeFilterSbe, BinanceNewOrderResponse, BinanceOrderFill,
27 BinanceOrderResponse, BinancePriceFilterSbe, BinancePriceLevel, BinanceSymbolFiltersSbe,
28 BinanceSymbolSbe, BinanceTrade, BinanceTrades,
29 },
30};
31use crate::spot::sbe::{
32 cursor::SbeCursor,
33 spot::{
34 SBE_SCHEMA_ID, account_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TEMPLATE_ID,
35 account_trades_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TRADES_TEMPLATE_ID,
36 account_type::AccountType,
37 agg_trades_response_codec::SBE_TEMPLATE_ID as AGG_TRADES_TEMPLATE_ID, bool_enum::BoolEnum,
38 cancel_open_orders_response_codec::SBE_TEMPLATE_ID as CANCEL_OPEN_ORDERS_TEMPLATE_ID,
39 cancel_order_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_TEMPLATE_ID,
40 depth_response_codec::SBE_TEMPLATE_ID as DEPTH_TEMPLATE_ID,
41 exchange_info_response_codec::SBE_TEMPLATE_ID as EXCHANGE_INFO_TEMPLATE_ID,
42 klines_response_codec::SBE_TEMPLATE_ID as KLINES_TEMPLATE_ID,
43 lot_size_filter_codec::SBE_TEMPLATE_ID as LOT_SIZE_FILTER_TEMPLATE_ID,
44 message_header_codec::ENCODED_LENGTH as HEADER_LENGTH,
45 new_order_full_response_codec::SBE_TEMPLATE_ID as NEW_ORDER_FULL_TEMPLATE_ID,
46 order_response_codec::SBE_TEMPLATE_ID as ORDER_TEMPLATE_ID,
47 orders_response_codec::SBE_TEMPLATE_ID as ORDERS_TEMPLATE_ID,
48 ping_response_codec::SBE_TEMPLATE_ID as PING_TEMPLATE_ID,
49 price_filter_codec::SBE_TEMPLATE_ID as PRICE_FILTER_TEMPLATE_ID,
50 server_time_response_codec::SBE_TEMPLATE_ID as SERVER_TIME_TEMPLATE_ID,
51 trades_response_codec::SBE_TEMPLATE_ID as TRADES_TEMPLATE_ID,
52 },
53};
54
55#[derive(Debug, Clone, Copy)]
57struct MessageHeader {
58 block_length: u16,
59 template_id: u16,
60 schema_id: u16,
61}
62
63impl MessageHeader {
64 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
66 cursor.require(HEADER_LENGTH)?;
67 let block_length = cursor.read_u16_le()?;
68 let template_id = cursor.read_u16_le()?;
69 let schema_id = cursor.read_u16_le()?;
70 let _version = cursor.read_u16_le()?; Ok(Self {
72 block_length,
73 template_id,
74 schema_id,
75 })
76 }
77
78 fn validate(&self) -> Result<(), SbeDecodeError> {
95 if self.schema_id != SBE_SCHEMA_ID {
96 return Err(SbeDecodeError::SchemaMismatch {
97 expected: SBE_SCHEMA_ID,
98 actual: self.schema_id,
99 });
100 }
101 Ok(())
102 }
103}
104
105pub fn decode_ping(buf: &[u8]) -> Result<(), SbeDecodeError> {
113 let mut cursor = SbeCursor::new(buf);
114 let header = MessageHeader::decode_cursor(&mut cursor)?;
115 header.validate()?;
116
117 if header.template_id != PING_TEMPLATE_ID {
118 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
119 }
120
121 Ok(())
122}
123
124pub fn decode_server_time(buf: &[u8]) -> Result<i64, SbeDecodeError> {
133 let mut cursor = SbeCursor::new(buf);
134 let header = MessageHeader::decode_cursor(&mut cursor)?;
135 header.validate()?;
136
137 if header.template_id != SERVER_TIME_TEMPLATE_ID {
138 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
139 }
140
141 cursor.read_i64_le()
142}
143
144pub fn decode_depth(buf: &[u8]) -> Result<BinanceDepth, SbeDecodeError> {
152 let mut cursor = SbeCursor::new(buf);
153 let header = MessageHeader::decode_cursor(&mut cursor)?;
154 header.validate()?;
155
156 if header.template_id != DEPTH_TEMPLATE_ID {
157 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
158 }
159
160 let last_update_id = cursor.read_i64_le()?;
161 let price_exponent = cursor.read_i8()?;
162 let qty_exponent = cursor.read_i8()?;
163
164 let (block_len, count) = cursor.read_group_header()?;
165 let bids = cursor.read_group(block_len, count, |c| {
166 Ok(BinancePriceLevel {
167 price_mantissa: c.read_i64_le()?,
168 qty_mantissa: c.read_i64_le()?,
169 })
170 })?;
171
172 let (block_len, count) = cursor.read_group_header()?;
173 let asks = cursor.read_group(block_len, count, |c| {
174 Ok(BinancePriceLevel {
175 price_mantissa: c.read_i64_le()?,
176 qty_mantissa: c.read_i64_le()?,
177 })
178 })?;
179
180 Ok(BinanceDepth {
181 last_update_id,
182 price_exponent,
183 qty_exponent,
184 bids,
185 asks,
186 })
187}
188
189pub fn decode_trades(buf: &[u8]) -> Result<BinanceTrades, SbeDecodeError> {
197 let mut cursor = SbeCursor::new(buf);
198 let header = MessageHeader::decode_cursor(&mut cursor)?;
199 header.validate()?;
200
201 if header.template_id != TRADES_TEMPLATE_ID {
202 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
203 }
204
205 let price_exponent = cursor.read_i8()?;
206 let qty_exponent = cursor.read_i8()?;
207
208 let (block_len, count) = cursor.read_group_header()?;
209 let trades = cursor.read_group(block_len, count, |c| {
210 Ok(BinanceTrade {
211 id: c.read_i64_le()?,
212 price_mantissa: c.read_i64_le()?,
213 qty_mantissa: c.read_i64_le()?,
214 quote_qty_mantissa: c.read_i64_le()?,
215 time: c.read_i64_le()?,
216 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
217 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
218 })
219 })?;
220
221 Ok(BinanceTrades {
222 price_exponent,
223 qty_exponent,
224 trades,
225 })
226}
227
228pub fn decode_agg_trades(buf: &[u8]) -> Result<BinanceAggTrades, SbeDecodeError> {
234 let mut cursor = SbeCursor::new(buf);
235 let header = MessageHeader::decode_cursor(&mut cursor)?;
236 header.validate()?;
237
238 if header.template_id != AGG_TRADES_TEMPLATE_ID {
239 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
240 }
241
242 let price_exponent = cursor.read_i8()?;
243 let qty_exponent = cursor.read_i8()?;
244 let (block_len, count) = cursor.read_group_header()?;
245 let trades = cursor.read_group(block_len, count, |c| {
246 Ok(BinanceAggTrade {
247 id: c.read_i64_le()?,
248 price_mantissa: c.read_i64_le()?,
249 qty_mantissa: c.read_i64_le()?,
250 first_trade_id: c.read_i64_le()?,
251 last_trade_id: c.read_i64_le()?,
252 time: c.read_i64_le()?,
253 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
254 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
255 })
256 })?;
257
258 Ok(BinanceAggTrades {
259 price_exponent,
260 qty_exponent,
261 trades,
262 })
263}
264
265const KLINES_BLOCK_LENGTH: u16 = 120;
267
268pub fn decode_klines(buf: &[u8]) -> Result<BinanceKlines, SbeDecodeError> {
276 let mut cursor = SbeCursor::new(buf);
277 let header = MessageHeader::decode_cursor(&mut cursor)?;
278 header.validate()?;
279
280 if header.template_id != KLINES_TEMPLATE_ID {
281 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
282 }
283
284 let price_exponent = cursor.read_i8()?;
285 let qty_exponent = cursor.read_i8()?;
286
287 let (block_len, count) = cursor.read_group_header()?;
288
289 if block_len != KLINES_BLOCK_LENGTH {
290 return Err(SbeDecodeError::InvalidBlockLength {
291 expected: KLINES_BLOCK_LENGTH,
292 actual: block_len,
293 });
294 }
295
296 let mut klines = Vec::with_capacity(count as usize);
297
298 for _ in 0..count {
299 cursor.require(KLINES_BLOCK_LENGTH as usize)?;
300
301 let open_time = cursor.read_i64_le()?;
302 let open_price = cursor.read_i64_le()?;
303 let high_price = cursor.read_i64_le()?;
304 let low_price = cursor.read_i64_le()?;
305 let close_price = cursor.read_i64_le()?;
306
307 let volume_slice = cursor.read_bytes(16)?;
308 let mut volume = [0u8; 16];
309 volume.copy_from_slice(volume_slice);
310
311 let close_time = cursor.read_i64_le()?;
312
313 let quote_volume_slice = cursor.read_bytes(16)?;
314 let mut quote_volume = [0u8; 16];
315 quote_volume.copy_from_slice(quote_volume_slice);
316
317 let num_trades = cursor.read_i64_le()?;
318
319 let taker_buy_base_volume_slice = cursor.read_bytes(16)?;
320 let mut taker_buy_base_volume = [0u8; 16];
321 taker_buy_base_volume.copy_from_slice(taker_buy_base_volume_slice);
322
323 let taker_buy_quote_volume_slice = cursor.read_bytes(16)?;
324 let mut taker_buy_quote_volume = [0u8; 16];
325 taker_buy_quote_volume.copy_from_slice(taker_buy_quote_volume_slice);
326
327 klines.push(BinanceKline {
328 open_time,
329 open_price,
330 high_price,
331 low_price,
332 close_price,
333 volume,
334 close_time,
335 quote_volume,
336 num_trades,
337 taker_buy_base_volume,
338 taker_buy_quote_volume,
339 });
340 }
341
342 Ok(BinanceKlines {
343 price_exponent,
344 qty_exponent,
345 klines,
346 })
347}
348
349const NEW_ORDER_FULL_FIELDS_END: usize = 135;
352const CANCEL_ORDER_FIELDS_END: usize = 63;
353const ORDER_FIELDS_END: usize = 104;
354
355const EXPIRY_REASON_NULL: u8 = 0xff;
357
358const NEW_ORDER_FULL_EXPIRY_REASON_OFFSET: usize = 153;
362const ORDER_EXPIRY_REASON_OFFSET: usize = 162;
363const ORDERS_GROUP_EXPIRY_REASON_OFFSET: usize = 162;
364
365fn read_trailing_expiry_reason(
375 cursor: &mut SbeCursor<'_>,
376 block_length: usize,
377 fields_end: usize,
378 expiry_reason_offset: usize,
379) -> Result<Option<u8>, SbeDecodeError> {
380 debug_assert!(fields_end <= expiry_reason_offset);
381 if block_length < fields_end {
382 return Ok(None);
383 }
384 let trailer = block_length - fields_end;
385 if trailer == 0 {
386 return Ok(None);
387 }
388 if block_length <= expiry_reason_offset {
390 cursor.advance(trailer)?;
391 return Ok(None);
392 }
393 let pre = expiry_reason_offset - fields_end;
394 cursor.advance(pre)?;
395 let byte = cursor.read_u8()?;
396 let post = trailer - pre - 1;
397 if post > 0 {
398 cursor.advance(post)?;
399 }
400 Ok((byte != EXPIRY_REASON_NULL).then_some(byte))
401}
402
403#[allow(dead_code)]
409pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
410 let mut cursor = SbeCursor::new(buf);
411 let header = MessageHeader::decode_cursor(&mut cursor)?;
412 header.validate()?;
413
414 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
415 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
416 }
417
418 cursor.require(header.block_length as usize)?;
419
420 let price_exponent = cursor.read_i8()?;
421 let qty_exponent = cursor.read_i8()?;
422 let order_id = cursor.read_i64_le()?;
423 let order_list_id = cursor.read_optional_i64_le()?;
424 let transact_time = cursor.read_i64_le()?;
425 let price_mantissa = cursor.read_i64_le()?;
426 let orig_qty_mantissa = cursor.read_i64_le()?;
427 let executed_qty_mantissa = cursor.read_i64_le()?;
428 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
429 let status = cursor.read_u8()?.into();
430 let time_in_force = cursor.read_u8()?.into();
431 let order_type = cursor.read_u8()?.into();
432 let side = cursor.read_u8()?.into();
433 let stop_price_mantissa = cursor.read_optional_i64_le()?;
434
435 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
437
438 cursor.advance(23)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
440
441 cursor.advance(16)?; let _commission_exponent = cursor.read_i8()?;
443
444 let expiry_reason = read_trailing_expiry_reason(
445 &mut cursor,
446 header.block_length as usize,
447 NEW_ORDER_FULL_FIELDS_END,
448 NEW_ORDER_FULL_EXPIRY_REASON_OFFSET,
449 )?;
450
451 let fills = decode_fills_cursor(&mut cursor)?;
452
453 let (block_len, count) = cursor.read_group_header()?;
455 cursor.advance(block_len as usize * count as usize)?;
456
457 let symbol = cursor.read_var_string8()?;
458 let client_order_id = cursor.read_var_string8()?;
459
460 Ok(BinanceNewOrderResponse {
461 price_exponent,
462 qty_exponent,
463 order_id,
464 order_list_id,
465 transact_time,
466 price_mantissa,
467 orig_qty_mantissa,
468 executed_qty_mantissa,
469 cummulative_quote_qty_mantissa,
470 status,
471 time_in_force,
472 order_type,
473 side,
474 stop_price_mantissa,
475 working_time,
476 self_trade_prevention_mode,
477 client_order_id,
478 symbol,
479 fills,
480 expiry_reason,
481 })
482}
483
484#[allow(dead_code)]
490pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
491 let mut cursor = SbeCursor::new(buf);
492 let header = MessageHeader::decode_cursor(&mut cursor)?;
493 header.validate()?;
494
495 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
496 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
497 }
498
499 cursor.require(header.block_length as usize)?;
500
501 let price_exponent = cursor.read_i8()?;
502 let qty_exponent = cursor.read_i8()?;
503 let order_id = cursor.read_i64_le()?;
504 let order_list_id = cursor.read_optional_i64_le()?;
505 let transact_time = cursor.read_i64_le()?;
506 let price_mantissa = cursor.read_i64_le()?;
507 let orig_qty_mantissa = cursor.read_i64_le()?;
508 let executed_qty_mantissa = cursor.read_i64_le()?;
509 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
510 let status = cursor.read_u8()?.into();
511 let time_in_force = cursor.read_u8()?.into();
512 let order_type = cursor.read_u8()?.into();
513 let side = cursor.read_u8()?.into();
514 let self_trade_prevention_mode = cursor.read_u8()?.into();
515
516 cursor.advance(header.block_length as usize - CANCEL_ORDER_FIELDS_END)?;
517
518 let symbol = cursor.read_var_string8()?;
519 let orig_client_order_id = cursor.read_var_string8()?;
520 let client_order_id = cursor.read_var_string8()?;
521
522 Ok(BinanceCancelOrderResponse {
523 price_exponent,
524 qty_exponent,
525 order_id,
526 order_list_id,
527 transact_time,
528 price_mantissa,
529 orig_qty_mantissa,
530 executed_qty_mantissa,
531 cummulative_quote_qty_mantissa,
532 status,
533 time_in_force,
534 order_type,
535 side,
536 self_trade_prevention_mode,
537 client_order_id,
538 orig_client_order_id,
539 symbol,
540 })
541}
542
543#[allow(dead_code)]
549pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
550 let mut cursor = SbeCursor::new(buf);
551 let header = MessageHeader::decode_cursor(&mut cursor)?;
552 header.validate()?;
553
554 if header.template_id != ORDER_TEMPLATE_ID {
555 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
556 }
557
558 cursor.require(header.block_length as usize)?;
559
560 let price_exponent = cursor.read_i8()?;
561 let qty_exponent = cursor.read_i8()?;
562 let order_id = cursor.read_i64_le()?;
563 let order_list_id = cursor.read_optional_i64_le()?;
564 let price_mantissa = cursor.read_i64_le()?;
565 let orig_qty_mantissa = cursor.read_i64_le()?;
566 let executed_qty_mantissa = cursor.read_i64_le()?;
567 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
568 let status = cursor.read_u8()?.into();
569 let time_in_force = cursor.read_u8()?.into();
570 let order_type = cursor.read_u8()?.into();
571 let side = cursor.read_u8()?.into();
572 let stop_price_mantissa = cursor.read_optional_i64_le()?;
573 let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
574 let time = cursor.read_i64_le()?;
575 let update_time = cursor.read_i64_le()?;
576 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
577 let working_time = cursor.read_optional_i64_le()?;
578 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
579 let self_trade_prevention_mode = cursor.read_u8()?.into();
580
581 let expiry_reason = read_trailing_expiry_reason(
582 &mut cursor,
583 header.block_length as usize,
584 ORDER_FIELDS_END,
585 ORDER_EXPIRY_REASON_OFFSET,
586 )?;
587
588 let symbol = cursor.read_var_string8()?;
589 let client_order_id = cursor.read_var_string8()?;
590
591 Ok(BinanceOrderResponse {
592 price_exponent,
593 qty_exponent,
594 order_id,
595 order_list_id,
596 price_mantissa,
597 orig_qty_mantissa,
598 executed_qty_mantissa,
599 cummulative_quote_qty_mantissa,
600 status,
601 time_in_force,
602 order_type,
603 side,
604 stop_price_mantissa,
605 iceberg_qty_mantissa,
606 time,
607 update_time,
608 is_working,
609 working_time,
610 orig_quote_order_qty_mantissa,
611 self_trade_prevention_mode,
612 client_order_id,
613 symbol,
614 expiry_reason,
615 })
616}
617
618const ORDERS_GROUP_MIN_BLOCK_LENGTH: u16 = 162;
620
621const ORDERS_GROUP_FIELDS_END: usize = 134;
625
626#[allow(dead_code)]
632pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
633 let mut cursor = SbeCursor::new(buf);
634 let header = MessageHeader::decode_cursor(&mut cursor)?;
635 header.validate()?;
636
637 if header.template_id != ORDERS_TEMPLATE_ID {
638 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
639 }
640
641 let (block_length, count) = cursor.read_group_header()?;
642
643 if count == 0 {
644 return Ok(Vec::new());
645 }
646
647 if block_length < ORDERS_GROUP_MIN_BLOCK_LENGTH {
648 return Err(SbeDecodeError::InvalidBlockLength {
649 expected: ORDERS_GROUP_MIN_BLOCK_LENGTH,
650 actual: block_length,
651 });
652 }
653
654 let mut orders = Vec::with_capacity(count as usize);
655
656 for _ in 0..count {
657 cursor.require(block_length as usize)?;
658
659 let price_exponent = cursor.read_i8()?;
660 let qty_exponent = cursor.read_i8()?;
661 let order_id = cursor.read_i64_le()?;
662 let order_list_id = cursor.read_optional_i64_le()?;
663 let price_mantissa = cursor.read_i64_le()?;
664 let orig_qty_mantissa = cursor.read_i64_le()?;
665 let executed_qty_mantissa = cursor.read_i64_le()?;
666 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
667 let status = cursor.read_u8()?.into();
668 let time_in_force = cursor.read_u8()?.into();
669 let order_type = cursor.read_u8()?.into();
670 let side = cursor.read_u8()?.into();
671 let stop_price_mantissa = cursor.read_optional_i64_le()?;
672
673 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
675 let time = cursor.read_i64_le()?;
676 let update_time = cursor.read_i64_le()?;
677 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
678 let working_time = cursor.read_optional_i64_le()?;
679 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
680
681 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
683
684 let expiry_reason = read_trailing_expiry_reason(
685 &mut cursor,
686 block_length as usize,
687 ORDERS_GROUP_FIELDS_END,
688 ORDERS_GROUP_EXPIRY_REASON_OFFSET,
689 )?;
690
691 let symbol = cursor.read_var_string8()?;
692 let client_order_id = cursor.read_var_string8()?;
693
694 orders.push(BinanceOrderResponse {
695 price_exponent,
696 qty_exponent,
697 order_id,
698 order_list_id,
699 price_mantissa,
700 orig_qty_mantissa,
701 executed_qty_mantissa,
702 cummulative_quote_qty_mantissa,
703 status,
704 time_in_force,
705 order_type,
706 side,
707 stop_price_mantissa,
708 iceberg_qty_mantissa,
709 time,
710 update_time,
711 is_working,
712 working_time,
713 orig_quote_order_qty_mantissa,
714 self_trade_prevention_mode,
715 client_order_id,
716 symbol,
717 expiry_reason,
718 });
719 }
720
721 Ok(orders)
722}
723
724#[allow(dead_code)]
732pub fn decode_cancel_open_orders(
733 buf: &[u8],
734) -> Result<Vec<BinanceCancelOrderResponse>, SbeDecodeError> {
735 let mut cursor = SbeCursor::new(buf);
736 let header = MessageHeader::decode_cursor(&mut cursor)?;
737 header.validate()?;
738
739 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
740 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
741 }
742
743 let (_block_length, count) = cursor.read_group_header()?;
744
745 if count == 0 {
746 return Ok(Vec::new());
747 }
748
749 let mut responses = Vec::with_capacity(count as usize);
750
751 for _ in 0..count {
753 let response_len = cursor.read_u16_le()? as usize;
754 let embedded_bytes = cursor.read_bytes(response_len)?;
755 let cancel_response = decode_cancel_order(embedded_bytes)?;
756 responses.push(cancel_response);
757 }
758
759 Ok(responses)
760}
761
762const ACCOUNT_BLOCK_LENGTH: usize = 64;
764
765const BALANCE_BLOCK_LENGTH: u16 = 17;
767
768#[allow(dead_code)]
774pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
775 let mut cursor = SbeCursor::new(buf);
776 let header = MessageHeader::decode_cursor(&mut cursor)?;
777 header.validate()?;
778
779 if header.template_id != ACCOUNT_TEMPLATE_ID {
780 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
781 }
782
783 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
784
785 let commission_exponent = cursor.read_i8()?;
786 let maker_commission_mantissa = cursor.read_i64_le()?;
787 let taker_commission_mantissa = cursor.read_i64_le()?;
788 let buyer_commission_mantissa = cursor.read_i64_le()?;
789 let seller_commission_mantissa = cursor.read_i64_le()?;
790 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
791 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
792 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
793 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
795 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
796 let update_time = cursor.read_i64_le()?;
797 let account_type_enum = AccountType::from(cursor.read_u8()?);
798 cursor.advance(16)?; let account_type = account_type_enum.to_string();
801
802 let (block_length, balance_count) = cursor.read_group_header()?;
803
804 if block_length != BALANCE_BLOCK_LENGTH {
805 return Err(SbeDecodeError::InvalidBlockLength {
806 expected: BALANCE_BLOCK_LENGTH,
807 actual: block_length,
808 });
809 }
810
811 let mut balances = Vec::with_capacity(balance_count as usize);
812
813 for _ in 0..balance_count {
814 cursor.require(block_length as usize)?;
815
816 let exponent = cursor.read_i8()?;
817 let free_mantissa = cursor.read_i64_le()?;
818 let locked_mantissa = cursor.read_i64_le()?;
819
820 let asset = cursor.read_var_string8()?;
821
822 balances.push(BinanceBalance {
823 asset,
824 free_mantissa,
825 locked_mantissa,
826 exponent,
827 });
828 }
829
830 Ok(BinanceAccountInfo {
831 commission_exponent,
832 maker_commission_mantissa,
833 taker_commission_mantissa,
834 buyer_commission_mantissa,
835 seller_commission_mantissa,
836 can_trade,
837 can_withdraw,
838 can_deposit,
839 require_self_trade_prevention,
840 prevent_sor,
841 update_time,
842 account_type,
843 balances,
844 })
845}
846
847const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
849
850#[allow(dead_code)]
856pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
857 let mut cursor = SbeCursor::new(buf);
858 let header = MessageHeader::decode_cursor(&mut cursor)?;
859 header.validate()?;
860
861 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
862 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
863 }
864
865 let (block_length, trade_count) = cursor.read_group_header()?;
866
867 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
868 return Err(SbeDecodeError::InvalidBlockLength {
869 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
870 actual: block_length,
871 });
872 }
873
874 let mut trades = Vec::with_capacity(trade_count as usize);
875
876 for _ in 0..trade_count {
877 cursor.require(block_length as usize)?;
878
879 let price_exponent = cursor.read_i8()?;
880 let qty_exponent = cursor.read_i8()?;
881 let commission_exponent = cursor.read_i8()?;
882 let id = cursor.read_i64_le()?;
883 let order_id = cursor.read_i64_le()?;
884 let order_list_id = cursor.read_optional_i64_le()?;
885 let price_mantissa = cursor.read_i64_le()?;
886 let qty_mantissa = cursor.read_i64_le()?;
887 let quote_qty_mantissa = cursor.read_i64_le()?;
888 let commission_mantissa = cursor.read_i64_le()?;
889 let time = cursor.read_i64_le()?;
890 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
891 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
892 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
893
894 let symbol = cursor.read_var_string8()?;
895 let commission_asset = cursor.read_var_string8()?;
896
897 trades.push(BinanceAccountTrade {
898 price_exponent,
899 qty_exponent,
900 commission_exponent,
901 id,
902 order_id,
903 order_list_id,
904 price_mantissa,
905 qty_mantissa,
906 quote_qty_mantissa,
907 commission_mantissa,
908 time,
909 is_buyer,
910 is_maker,
911 is_best_match,
912 symbol,
913 commission_asset,
914 });
915 }
916
917 Ok(trades)
918}
919
920const FILLS_BLOCK_LENGTH: u16 = 42;
922
923fn decode_fills_cursor(
925 cursor: &mut SbeCursor<'_>,
926) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
927 let (block_length, count) = cursor.read_group_header()?;
928
929 if block_length != FILLS_BLOCK_LENGTH {
930 return Err(SbeDecodeError::InvalidBlockLength {
931 expected: FILLS_BLOCK_LENGTH,
932 actual: block_length,
933 });
934 }
935
936 let mut fills = Vec::with_capacity(count as usize);
937
938 for _ in 0..count {
939 cursor.require(block_length as usize)?;
940
941 let commission_exponent = cursor.read_i8()?;
942 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
944 let qty_mantissa = cursor.read_i64_le()?;
945 let commission_mantissa = cursor.read_i64_le()?;
946 let trade_id = cursor.read_optional_i64_le()?;
947 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
950
951 fills.push(BinanceOrderFill {
952 price_mantissa,
953 qty_mantissa,
954 commission_mantissa,
955 commission_exponent,
956 commission_asset,
957 trade_id,
958 });
959 }
960
961 Ok(fills)
962}
963
964const SYMBOL_BLOCK_LENGTH: usize = 19;
966
967pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
981 let mut cursor = SbeCursor::new(buf);
982 let header = MessageHeader::decode_cursor(&mut cursor)?;
983 header.validate()?;
984
985 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
986 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
987 }
988
989 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
991 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
992
993 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
995 for _ in 0..exchange_filters_count {
996 cursor.read_var_string8()?;
998 }
999
1000 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
1002
1003 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
1004 return Err(SbeDecodeError::InvalidBlockLength {
1005 expected: SYMBOL_BLOCK_LENGTH as u16,
1006 actual: symbols_block_len,
1007 });
1008 }
1009
1010 let mut symbols = Vec::with_capacity(symbols_count as usize);
1011
1012 for _ in 0..symbols_count {
1013 cursor.require(SYMBOL_BLOCK_LENGTH)?;
1014
1015 let status = cursor.read_u8()?;
1017 let base_asset_precision = cursor.read_u8()?;
1018 let quote_asset_precision = cursor.read_u8()?;
1019 let _base_commission_precision = cursor.read_u8()?;
1020 let _quote_commission_precision = cursor.read_u8()?;
1021 let order_types = cursor.read_u16_le()?;
1022 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1023 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1024 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1025 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1026 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
1027 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1028 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1029 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1030 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1031 let _default_self_trade_prevention_mode = cursor.read_u8()?;
1032 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
1033 let _peg_instructions_allowed = cursor.read_u8()?;
1034
1035 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
1036 let mut filters = BinanceSymbolFiltersSbe::default();
1037
1038 for _ in 0..filters_count {
1039 let filter_bytes = cursor.read_var_bytes8()?;
1040
1041 let (template_id, offset) = if filter_bytes.len() >= HEADER_LENGTH + 2 {
1044 let potential_template = u16::from_le_bytes([filter_bytes[2], filter_bytes[3]]);
1045 if potential_template == PRICE_FILTER_TEMPLATE_ID
1046 || potential_template == LOT_SIZE_FILTER_TEMPLATE_ID
1047 {
1048 (potential_template, HEADER_LENGTH)
1049 } else {
1050 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1051 (raw_template, 2)
1052 }
1053 } else if filter_bytes.len() >= 2 {
1054 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1055 (raw_template, 2)
1056 } else {
1057 continue;
1058 };
1059
1060 match template_id {
1062 PRICE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1063 let price_exp = filter_bytes[offset] as i8;
1064 let min_price = i64::from_le_bytes(
1065 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1066 );
1067 let max_price = i64::from_le_bytes(
1068 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1069 );
1070 let tick_size = i64::from_le_bytes(
1071 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1072 );
1073 filters.price_filter = Some(BinancePriceFilterSbe {
1074 price_exponent: price_exp,
1075 min_price,
1076 max_price,
1077 tick_size,
1078 });
1079 }
1080 LOT_SIZE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1081 let qty_exp = filter_bytes[offset] as i8;
1082 let min_qty = i64::from_le_bytes(
1083 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1084 );
1085 let max_qty = i64::from_le_bytes(
1086 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1087 );
1088 let step_size = i64::from_le_bytes(
1089 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1090 );
1091 filters.lot_size_filter = Some(BinanceLotSizeFilterSbe {
1092 qty_exponent: qty_exp,
1093 min_qty,
1094 max_qty,
1095 step_size,
1096 });
1097 }
1098 _ => {}
1099 }
1100 }
1101
1102 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
1104 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
1105 for _ in 0..perm_sets_count {
1106 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
1108 let mut perm_set = Vec::with_capacity(perms_count as usize);
1109 for _ in 0..perms_count {
1110 let perm = cursor.read_var_string8()?;
1111 perm_set.push(perm);
1112 }
1113 permissions.push(perm_set);
1114 }
1115
1116 let symbol = cursor.read_var_string8()?;
1118 let base_asset = cursor.read_var_string8()?;
1119 let quote_asset = cursor.read_var_string8()?;
1120
1121 symbols.push(BinanceSymbolSbe {
1122 symbol,
1123 base_asset,
1124 quote_asset,
1125 base_asset_precision,
1126 quote_asset_precision,
1127 status,
1128 order_types,
1129 iceberg_allowed,
1130 oco_allowed,
1131 oto_allowed,
1132 quote_order_qty_market_allowed,
1133 allow_trailing_stop,
1134 cancel_replace_allowed,
1135 amend_allowed,
1136 is_spot_trading_allowed,
1137 is_margin_trading_allowed,
1138 filters,
1139 permissions,
1140 });
1141 }
1142
1143 Ok(BinanceExchangeInfoSbe { symbols })
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150 use rstest::rstest;
1151
1152 use super::*;
1153 use crate::spot::sbe::spot::SBE_SCHEMA_VERSION;
1154
1155 const NEW_ORDER_FULL_BLOCK_LENGTH: usize = 153;
1157
1158 const CANCEL_ORDER_BLOCK_LENGTH: usize = 137;
1160
1161 const ORDER_BLOCK_LENGTH: usize = 153;
1163
1164 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
1165 let mut buf = [0u8; 8];
1166 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1167 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
1168 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
1169 buf[6..8].copy_from_slice(&version.to_le_bytes());
1170 buf
1171 }
1172
1173 #[rstest]
1174 fn test_decode_ping_valid() {
1175 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1177 decode_ping(&buf).unwrap();
1178 }
1179
1180 #[rstest]
1181 fn test_decode_ping_buffer_too_short() {
1182 let buf = [0u8; 4];
1183 let err = decode_ping(&buf).unwrap_err();
1184 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1185 }
1186
1187 #[rstest]
1188 fn test_decode_ping_schema_mismatch() {
1189 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
1190 let err = decode_ping(&buf).unwrap_err();
1191 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
1192 }
1193
1194 #[rstest]
1195 fn test_decode_ping_wrong_template() {
1196 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1197 let err = decode_ping(&buf).unwrap_err();
1198 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
1199 }
1200
1201 #[rstest]
1202 fn test_decode_server_time_valid() {
1203 let header = create_header(
1205 8,
1206 SERVER_TIME_TEMPLATE_ID,
1207 SBE_SCHEMA_ID,
1208 SBE_SCHEMA_VERSION,
1209 );
1210 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
1213 buf.extend_from_slice(&header);
1214 buf.extend_from_slice(×tamp.to_le_bytes());
1215
1216 let result = decode_server_time(&buf).unwrap();
1217 assert_eq!(result, timestamp);
1218 }
1219
1220 #[rstest]
1221 fn test_decode_server_time_buffer_too_short() {
1222 let buf = create_header(
1224 8,
1225 SERVER_TIME_TEMPLATE_ID,
1226 SBE_SCHEMA_ID,
1227 SBE_SCHEMA_VERSION,
1228 );
1229 let err = decode_server_time(&buf).unwrap_err();
1230 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1231 }
1232
1233 #[rstest]
1234 fn test_decode_server_time_wrong_template() {
1235 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1236 let mut buf = Vec::with_capacity(16);
1237 buf.extend_from_slice(&header);
1238 buf.extend_from_slice(&0i64.to_le_bytes());
1239
1240 let err = decode_server_time(&buf).unwrap_err();
1241 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1242 }
1243
1244 #[rstest]
1248 #[case(SBE_SCHEMA_VERSION - 1)]
1249 #[case(SBE_SCHEMA_VERSION)]
1250 #[case(SBE_SCHEMA_VERSION + 1)]
1251 #[case(99)]
1252 fn test_decode_server_time_accepts_any_version(#[case] version: u16) {
1253 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, version);
1254 let mut buf = Vec::with_capacity(16);
1255 buf.extend_from_slice(&header);
1256 buf.extend_from_slice(&1_700_000_000_000i64.to_le_bytes());
1257
1258 let result = decode_server_time(&buf).unwrap();
1259 assert_eq!(result, 1_700_000_000_000);
1260 }
1261
1262 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
1263 let mut buf = [0u8; 6];
1264 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1265 buf[2..6].copy_from_slice(&count.to_le_bytes());
1266 buf
1267 }
1268
1269 #[rstest]
1270 fn test_decode_depth_valid() {
1271 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1273
1274 let mut buf = Vec::new();
1275 buf.extend_from_slice(&header);
1276
1277 let last_update_id: i64 = 123456789;
1279 let price_exponent: i8 = -8;
1280 let qty_exponent: i8 = -8;
1281 buf.extend_from_slice(&last_update_id.to_le_bytes());
1282 buf.push(price_exponent as u8);
1283 buf.push(qty_exponent as u8);
1284
1285 buf.extend_from_slice(&create_group_header(16, 2));
1287 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1289 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1290 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1292 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1293
1294 buf.extend_from_slice(&create_group_header(16, 1));
1296 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1298 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1299
1300 let depth = decode_depth(&buf).unwrap();
1301
1302 assert_eq!(depth.last_update_id, 123456789);
1303 assert_eq!(depth.price_exponent, -8);
1304 assert_eq!(depth.qty_exponent, -8);
1305 assert_eq!(depth.bids.len(), 2);
1306 assert_eq!(depth.asks.len(), 1);
1307 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1308 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1309 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1310 }
1311
1312 #[rstest]
1313 fn test_decode_depth_empty_book() {
1314 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1315
1316 let mut buf = Vec::new();
1317 buf.extend_from_slice(&header);
1318 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1324 buf.extend_from_slice(&create_group_header(16, 0));
1326
1327 let depth = decode_depth(&buf).unwrap();
1328
1329 assert!(depth.bids.is_empty());
1330 assert!(depth.asks.is_empty());
1331 }
1332
1333 #[rstest]
1334 fn test_decode_trades_valid() {
1335 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1337
1338 let mut buf = Vec::new();
1339 buf.extend_from_slice(&header);
1340
1341 let price_exponent: i8 = -8;
1343 let qty_exponent: i8 = -8;
1344 buf.push(price_exponent as u8);
1345 buf.push(qty_exponent as u8);
1346
1347 buf.extend_from_slice(&create_group_header(42, 1));
1349
1350 let trade_id: i64 = 999;
1352 let price: i64 = 100_000_000_000;
1353 let qty: i64 = 10_000_000;
1354 let quote_qty: i64 = 1_000_000_000_000;
1355 let time: i64 = 1734300000000;
1356 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1360 buf.extend_from_slice(&price.to_le_bytes());
1361 buf.extend_from_slice(&qty.to_le_bytes());
1362 buf.extend_from_slice("e_qty.to_le_bytes());
1363 buf.extend_from_slice(&time.to_le_bytes());
1364 buf.push(is_buyer_maker);
1365 buf.push(is_best_match);
1366
1367 let trades = decode_trades(&buf).unwrap();
1368
1369 assert_eq!(trades.price_exponent, -8);
1370 assert_eq!(trades.qty_exponent, -8);
1371 assert_eq!(trades.trades.len(), 1);
1372 assert_eq!(trades.trades[0].id, 999);
1373 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1374 assert!(trades.trades[0].is_buyer_maker);
1375 assert!(trades.trades[0].is_best_match);
1376 }
1377
1378 #[rstest]
1379 fn test_decode_trades_empty() {
1380 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1381
1382 let mut buf = Vec::new();
1383 buf.extend_from_slice(&header);
1384 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1389
1390 let trades = decode_trades(&buf).unwrap();
1391
1392 assert!(trades.trades.is_empty());
1393 }
1394
1395 #[rstest]
1396 fn test_decode_agg_trades_preserves_all_fields() {
1397 let header = create_header(2, AGG_TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1398 let mut buf = Vec::new();
1399 buf.extend_from_slice(&header);
1400 buf.push((-7_i8) as u8);
1401 buf.push((-5_i8) as u8);
1402 buf.extend_from_slice(&create_group_header(50, 1));
1403 buf.extend_from_slice(&101_i64.to_le_bytes());
1404 buf.extend_from_slice(&123_456_789_i64.to_le_bytes());
1405 buf.extend_from_slice(&765_432_i64.to_le_bytes());
1406 buf.extend_from_slice(&201_i64.to_le_bytes());
1407 buf.extend_from_slice(&207_i64.to_le_bytes());
1408 buf.extend_from_slice(&1_700_000_000_123_i64.to_le_bytes());
1409 buf.push(1);
1410 buf.push(0);
1411
1412 let trades = decode_agg_trades(&buf).unwrap();
1413
1414 assert_eq!(trades.price_exponent, -7);
1415 assert_eq!(trades.qty_exponent, -5);
1416 assert_eq!(trades.trades.len(), 1);
1417 assert_eq!(trades.trades[0].id, 101);
1418 assert_eq!(trades.trades[0].price_mantissa, 123_456_789);
1419 assert_eq!(trades.trades[0].qty_mantissa, 765_432);
1420 assert_eq!(trades.trades[0].first_trade_id, 201);
1421 assert_eq!(trades.trades[0].last_trade_id, 207);
1422 assert_eq!(trades.trades[0].time, 1_700_000_000_123);
1423 assert!(trades.trades[0].is_buyer_maker);
1424 assert!(!trades.trades[0].is_best_match);
1425 }
1426
1427 #[rstest]
1428 fn test_decode_agg_trades_rejects_wrong_template() {
1429 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1430 let mut buf = Vec::new();
1431 buf.extend_from_slice(&header);
1432 buf.extend_from_slice(&[0_u8; 2]);
1433
1434 let error = decode_agg_trades(&buf).unwrap_err();
1435
1436 assert!(matches!(error, SbeDecodeError::UnknownTemplateId(101)));
1437 }
1438
1439 #[rstest]
1440 fn test_decode_depth_wrong_template() {
1441 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1442
1443 let mut buf = Vec::new();
1444 buf.extend_from_slice(&header);
1445 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1448 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1449 }
1450
1451 #[rstest]
1452 fn test_decode_trades_wrong_template() {
1453 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1454
1455 let mut buf = Vec::new();
1456 buf.extend_from_slice(&header);
1457 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1460 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1461 }
1462
1463 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1464 buf.push(s.len() as u8);
1465 buf.extend_from_slice(s.as_bytes());
1466 }
1467
1468 fn build_order_response_buffer(block_length: u16, trailing_byte: u8) -> Vec<u8> {
1474 let header = create_header(
1475 block_length,
1476 ORDER_TEMPLATE_ID,
1477 SBE_SCHEMA_ID,
1478 SBE_SCHEMA_VERSION,
1479 );
1480
1481 let mut buf = Vec::new();
1482 buf.extend_from_slice(&header);
1483
1484 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&5_000_000i64.to_le_bytes()); buf.extend_from_slice(&500_000_000i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300000500i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); let block_end = HEADER_LENGTH + block_length as usize;
1506 while buf.len() < block_end.saturating_sub(1) {
1508 buf.push(0);
1509 }
1510
1511 if buf.len() < block_end {
1512 buf.push(trailing_byte);
1513 }
1514
1515 write_var_string(&mut buf, "BTCUSDT");
1516 write_var_string(&mut buf, "my-order-123");
1517 buf
1518 }
1519
1520 #[rstest]
1521 #[case::pre_v4_no_expiry_reason(153, 0x00, None)]
1522 #[case::v4_null_sentinel(163, 0xFF, None)]
1523 #[case::v4_captures_value(163, 0x05, Some(0x05))]
1524 fn test_decode_order_expiry_reason(
1525 #[case] block_length: u16,
1526 #[case] trailing_byte: u8,
1527 #[case] expected: Option<u8>,
1528 ) {
1529 let buf = build_order_response_buffer(block_length, trailing_byte);
1530 let order = decode_order(&buf).unwrap();
1531 assert_eq!(order.expiry_reason, expected);
1532 assert_eq!(order.symbol, "BTCUSDT");
1535 assert_eq!(order.client_order_id, "my-order-123");
1536 }
1537
1538 #[rstest]
1539 fn test_decode_order_valid() {
1540 let header = create_header(
1541 ORDER_BLOCK_LENGTH as u16,
1542 ORDER_TEMPLATE_ID,
1543 SBE_SCHEMA_ID,
1544 SBE_SCHEMA_VERSION,
1545 );
1546
1547 let mut buf = Vec::new();
1548 buf.extend_from_slice(&header);
1549
1550 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&5_000_000i64.to_le_bytes()); buf.extend_from_slice(&500_000_000i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300000500i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); while buf.len() < 8 + ORDER_BLOCK_LENGTH {
1574 buf.push(0);
1575 }
1576
1577 write_var_string(&mut buf, "BTCUSDT");
1578 write_var_string(&mut buf, "my-order-123");
1579
1580 let order = decode_order(&buf).unwrap();
1581
1582 assert_eq!(order.order_id, 12345);
1583 assert!(order.order_list_id.is_none());
1584 assert_eq!(order.price_exponent, -8);
1585 assert_eq!(order.price_mantissa, 100_000_000_000);
1586 assert!(order.stop_price_mantissa.is_none());
1587 assert!(order.iceberg_qty_mantissa.is_none());
1588 assert!(order.is_working);
1589 assert_eq!(order.working_time, Some(1734300000500));
1590 assert_eq!(order.symbol, "BTCUSDT");
1591 assert_eq!(order.client_order_id, "my-order-123");
1592 }
1593
1594 #[rstest]
1595 fn test_decode_order_future_block_length() {
1596 const FUTURE_BLOCK_LENGTH: u16 = ORDER_BLOCK_LENGTH as u16 + 4;
1598 let header = create_header(
1599 FUTURE_BLOCK_LENGTH,
1600 ORDER_TEMPLATE_ID,
1601 SBE_SCHEMA_ID,
1602 SBE_SCHEMA_VERSION,
1603 );
1604
1605 let mut buf = Vec::new();
1606 buf.extend_from_slice(&header);
1607
1608 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&5_000_000i64.to_le_bytes()); buf.extend_from_slice(&500_000_000i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300000500i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); while buf.len() < 8 + FUTURE_BLOCK_LENGTH as usize {
1630 buf.push(0); }
1632
1633 write_var_string(&mut buf, "ETHUSDT");
1634 write_var_string(&mut buf, "order-future");
1635
1636 let order = decode_order(&buf).unwrap();
1637
1638 assert_eq!(order.order_id, 12345);
1639 assert_eq!(order.symbol, "ETHUSDT");
1640 assert_eq!(order.client_order_id, "order-future");
1641 }
1642
1643 #[rstest]
1644 fn test_decode_orders_multiple() {
1645 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1647
1648 let mut buf = Vec::new();
1649 buf.extend_from_slice(&header);
1650
1651 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 2));
1653
1654 let order1_start = buf.len();
1656 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&1001i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); while buf.len() - order1_start < ORDERS_GROUP_MIN_BLOCK_LENGTH as usize {
1679 buf.push(0);
1680 }
1681 write_var_string(&mut buf, "BTCUSDT");
1682 write_var_string(&mut buf, "order-1");
1683
1684 let order2_start = buf.len();
1686 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&2002i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&200_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&20_000_000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(2); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); while buf.len() - order2_start < ORDERS_GROUP_MIN_BLOCK_LENGTH as usize {
1708 buf.push(0);
1709 }
1710 write_var_string(&mut buf, "ETHUSDT");
1711 write_var_string(&mut buf, "order-2");
1712
1713 let orders = decode_orders(&buf).unwrap();
1714
1715 assert_eq!(orders.len(), 2);
1716 assert_eq!(orders[0].order_id, 1001);
1717 assert_eq!(orders[0].symbol, "BTCUSDT");
1718 assert_eq!(orders[0].client_order_id, "order-1");
1719 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
1720
1721 assert_eq!(orders[1].order_id, 2002);
1722 assert_eq!(orders[1].symbol, "ETHUSDT");
1723 assert_eq!(orders[1].client_order_id, "order-2");
1724 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
1725 }
1726
1727 #[rstest]
1728 fn test_decode_orders_v4_trailing_expiry_reason() {
1729 const V4_BLOCK_LENGTH: u16 = 163;
1733 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1734
1735 let mut buf = Vec::new();
1736 buf.extend_from_slice(&header);
1737 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
1738
1739 let order_start = buf.len();
1740 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1741 buf.push(0xFF); assert_eq!(buf.len() - order_start, V4_BLOCK_LENGTH as usize);
1743
1744 write_var_string(&mut buf, "BTCUSDT");
1745 write_var_string(&mut buf, "v4-order");
1746
1747 let orders = decode_orders(&buf).unwrap();
1748
1749 assert_eq!(orders.len(), 1);
1750 assert_eq!(orders[0].symbol, "BTCUSDT");
1751 assert_eq!(orders[0].client_order_id, "v4-order");
1752 assert!(orders[0].expiry_reason.is_none());
1753 }
1754
1755 #[rstest]
1756 fn test_decode_orders_pre_v4_block_returns_no_expiry_reason() {
1757 const PRE_V4_BLOCK_LENGTH: u16 = ORDERS_GROUP_MIN_BLOCK_LENGTH;
1761 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1762
1763 let mut buf = Vec::new();
1764 buf.extend_from_slice(&header);
1765 buf.extend_from_slice(&create_group_header(PRE_V4_BLOCK_LENGTH, 1));
1766
1767 buf.extend_from_slice(&[0xAAu8; PRE_V4_BLOCK_LENGTH as usize]);
1770
1771 write_var_string(&mut buf, "BTCUSDT");
1772 write_var_string(&mut buf, "pre-v4-order");
1773
1774 let orders = decode_orders(&buf).unwrap();
1775 assert!(orders[0].expiry_reason.is_none());
1776 }
1777
1778 #[rstest]
1779 fn test_decode_orders_v4_captures_expiry_reason_value() {
1780 const V4_BLOCK_LENGTH: u16 = 163;
1784 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1785
1786 let mut buf = Vec::new();
1787 buf.extend_from_slice(&header);
1788 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
1789
1790 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1791 buf.push(0x05);
1792
1793 write_var_string(&mut buf, "BTCUSDT");
1794 write_var_string(&mut buf, "v4-expired");
1795
1796 let orders = decode_orders(&buf).unwrap();
1797 assert_eq!(orders[0].expiry_reason, Some(0x05));
1798 }
1799
1800 #[rstest]
1801 fn test_decode_orders_empty() {
1802 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1803
1804 let mut buf = Vec::new();
1805 buf.extend_from_slice(&header);
1806 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 0));
1807
1808 let orders = decode_orders(&buf).unwrap();
1809 assert!(orders.is_empty());
1810 }
1811
1812 #[rstest]
1813 fn test_decode_orders_truncated_var_string() {
1814 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1815
1816 let mut buf = Vec::new();
1817 buf.extend_from_slice(&header);
1818 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 1));
1819
1820 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1822
1823 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
1828 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1829 }
1830
1831 #[rstest]
1832 fn test_decode_orders_invalid_utf8() {
1833 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1834
1835 let mut buf = Vec::new();
1836 buf.extend_from_slice(&header);
1837 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 1));
1838
1839 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1840
1841 buf.push(4);
1843 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
1844
1845 let err = decode_orders(&buf).unwrap_err();
1846 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
1847 }
1848
1849 #[rstest]
1850 fn test_decode_cancel_order_valid() {
1851 let header = create_header(
1852 CANCEL_ORDER_BLOCK_LENGTH as u16,
1853 CANCEL_ORDER_TEMPLATE_ID,
1854 SBE_SCHEMA_ID,
1855 SBE_SCHEMA_VERSION,
1856 );
1857
1858 let mut buf = Vec::new();
1859 buf.extend_from_slice(&header);
1860
1861 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&99999i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000i64.to_le_bytes()); buf.push(4); buf.push(1); buf.push(1); buf.push(1); buf.push(0); while buf.len() < 8 + CANCEL_ORDER_BLOCK_LENGTH {
1878 buf.push(0);
1879 }
1880
1881 write_var_string(&mut buf, "BTCUSDT");
1882 write_var_string(&mut buf, "orig-client-id");
1883 write_var_string(&mut buf, "new-client-id");
1884
1885 let cancel = decode_cancel_order(&buf).unwrap();
1886
1887 assert_eq!(cancel.order_id, 99999);
1888 assert!(cancel.order_list_id.is_none());
1889 assert_eq!(cancel.symbol, "BTCUSDT");
1890 assert_eq!(cancel.orig_client_order_id, "orig-client-id");
1891 assert_eq!(cancel.client_order_id, "new-client-id");
1892 }
1893
1894 #[rstest]
1895 fn test_decode_cancel_order_future_block_length() {
1896 const FUTURE_BLOCK_LENGTH: u16 = CANCEL_ORDER_BLOCK_LENGTH as u16 + 4;
1898 let header = create_header(
1899 FUTURE_BLOCK_LENGTH,
1900 CANCEL_ORDER_TEMPLATE_ID,
1901 SBE_SCHEMA_ID,
1902 SBE_SCHEMA_VERSION,
1903 );
1904
1905 let mut buf = Vec::new();
1906 buf.extend_from_slice(&header);
1907
1908 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&99999i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000i64.to_le_bytes()); buf.push(4); buf.push(1); buf.push(1); buf.push(1); buf.push(0); while buf.len() < 8 + FUTURE_BLOCK_LENGTH as usize {
1924 buf.push(0); }
1926
1927 write_var_string(&mut buf, "BTCUSDT");
1928 write_var_string(&mut buf, "orig-id");
1929 write_var_string(&mut buf, "new-id");
1930
1931 let cancel = decode_cancel_order(&buf).unwrap();
1932
1933 assert_eq!(cancel.order_id, 99999);
1934 assert_eq!(cancel.symbol, "BTCUSDT");
1935 assert_eq!(cancel.orig_client_order_id, "orig-id");
1936 assert_eq!(cancel.client_order_id, "new-id");
1937 }
1938
1939 #[rstest]
1940 fn test_decode_account_with_balances() {
1941 let header = create_header(
1942 ACCOUNT_BLOCK_LENGTH as u16,
1943 ACCOUNT_TEMPLATE_ID,
1944 SBE_SCHEMA_ID,
1945 SBE_SCHEMA_VERSION,
1946 );
1947
1948 let mut buf = Vec::new();
1949 buf.extend_from_slice(&header);
1950
1951 buf.push((-8i8) as u8); buf.extend_from_slice(&100_000i64.to_le_bytes()); buf.extend_from_slice(&100_000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(0); buf.push(0); buf.push(0); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.push(1); while buf.len() < 8 + ACCOUNT_BLOCK_LENGTH {
1968 buf.push(0);
1969 }
1970
1971 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
1973
1974 buf.push((-8i8) as u8); buf.extend_from_slice(&100_000_000i64.to_le_bytes()); buf.extend_from_slice(&50_000_000i64.to_le_bytes()); write_var_string(&mut buf, "BTC");
1979
1980 buf.push((-8i8) as u8); buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); write_var_string(&mut buf, "USDT");
1985
1986 let account = decode_account(&buf).unwrap();
1987
1988 assert!(account.can_trade);
1989 assert!(account.can_withdraw);
1990 assert!(account.can_deposit);
1991 assert_eq!(account.balances.len(), 2);
1992 assert_eq!(account.balances[0].asset, "BTC");
1993 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
1994 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
1995 assert_eq!(account.balances[1].asset, "USDT");
1996 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
1997 }
1998
1999 #[rstest]
2000 fn test_decode_account_empty_balances() {
2001 let header = create_header(
2002 ACCOUNT_BLOCK_LENGTH as u16,
2003 ACCOUNT_TEMPLATE_ID,
2004 SBE_SCHEMA_ID,
2005 SBE_SCHEMA_VERSION,
2006 );
2007
2008 let mut buf = Vec::new();
2009 buf.extend_from_slice(&header);
2010
2011 buf.push((-8i8) as u8);
2013 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
2017
2018 let account = decode_account(&buf).unwrap();
2019 assert!(account.balances.is_empty());
2020 }
2021
2022 #[rstest]
2023 fn test_decode_account_trades_multiple() {
2024 let header = create_header(
2025 0,
2026 ACCOUNT_TRADES_TEMPLATE_ID,
2027 SBE_SCHEMA_ID,
2028 SBE_SCHEMA_VERSION,
2029 );
2030
2031 let mut buf = Vec::new();
2032 buf.extend_from_slice(&header);
2033
2034 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
2036
2037 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&1001i64.to_le_bytes()); buf.extend_from_slice(&5001i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&100_000i64.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.push(1); buf.push(0); buf.push(1); write_var_string(&mut buf, "BTCUSDT");
2053 write_var_string(&mut buf, "BNB");
2054
2055 buf.push((-8i8) as u8);
2057 buf.push((-8i8) as u8);
2058 buf.push((-8i8) as u8);
2059 buf.extend_from_slice(&1002i64.to_le_bytes());
2060 buf.extend_from_slice(&5002i64.to_le_bytes());
2061 buf.extend_from_slice(&i64::MIN.to_le_bytes());
2062 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
2063 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
2064 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
2065 buf.extend_from_slice(&50_000i64.to_le_bytes());
2066 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
2067 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
2071 write_var_string(&mut buf, "USDT");
2072
2073 let trades = decode_account_trades(&buf).unwrap();
2074
2075 assert_eq!(trades.len(), 2);
2076 assert_eq!(trades[0].id, 1001);
2077 assert_eq!(trades[0].order_id, 5001);
2078 assert!(trades[0].order_list_id.is_none());
2079 assert_eq!(trades[0].symbol, "BTCUSDT");
2080 assert_eq!(trades[0].commission_asset, "BNB");
2081 assert!(trades[0].is_buyer);
2082 assert!(!trades[0].is_maker);
2083
2084 assert_eq!(trades[1].id, 1002);
2085 assert_eq!(trades[1].symbol, "ETHUSDT");
2086 assert_eq!(trades[1].commission_asset, "USDT");
2087 assert!(!trades[1].is_buyer);
2088 assert!(trades[1].is_maker);
2089 }
2090
2091 #[rstest]
2092 fn test_decode_account_trades_empty() {
2093 let header = create_header(
2094 0,
2095 ACCOUNT_TRADES_TEMPLATE_ID,
2096 SBE_SCHEMA_ID,
2097 SBE_SCHEMA_VERSION,
2098 );
2099
2100 let mut buf = Vec::new();
2101 buf.extend_from_slice(&header);
2102 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
2103
2104 let trades = decode_account_trades(&buf).unwrap();
2105 assert!(trades.is_empty());
2106 }
2107
2108 #[rstest]
2109 fn test_decode_exchange_info_single_symbol() {
2110 let header = create_header(
2111 0,
2112 EXCHANGE_INFO_TEMPLATE_ID,
2113 SBE_SCHEMA_ID,
2114 SBE_SCHEMA_VERSION,
2115 );
2116
2117 let mut buf = Vec::new();
2118 buf.extend_from_slice(&header);
2119
2120 buf.extend_from_slice(&create_group_header(11, 0));
2122
2123 buf.extend_from_slice(&create_group_header(0, 0));
2125
2126 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
2128
2129 buf.push(0); buf.push(8); buf.push(8); buf.push(8); buf.push(8); buf.extend_from_slice(&0b0000_0111u16.to_le_bytes()); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(0); buf.push(0); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(0, 0));
2151
2152 buf.extend_from_slice(&create_group_header(0, 1));
2154 buf.extend_from_slice(&create_group_header(0, 1));
2155 write_var_string(&mut buf, "SPOT");
2156
2157 write_var_string(&mut buf, "BTCUSDT");
2159 write_var_string(&mut buf, "BTC");
2160 write_var_string(&mut buf, "USDT");
2161
2162 let info = decode_exchange_info(&buf).unwrap();
2163
2164 assert_eq!(info.symbols.len(), 1);
2165 let symbol = &info.symbols[0];
2166 assert_eq!(symbol.symbol, "BTCUSDT");
2167 assert_eq!(symbol.base_asset, "BTC");
2168 assert_eq!(symbol.quote_asset, "USDT");
2169 assert_eq!(symbol.base_asset_precision, 8);
2170 assert_eq!(symbol.quote_asset_precision, 8);
2171 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
2173 assert!(symbol.iceberg_allowed);
2174 assert!(symbol.oco_allowed);
2175 assert!(!symbol.oto_allowed);
2176 assert!(symbol.quote_order_qty_market_allowed);
2177 assert!(symbol.allow_trailing_stop);
2178 assert!(symbol.cancel_replace_allowed);
2179 assert!(!symbol.amend_allowed);
2180 assert!(symbol.is_spot_trading_allowed);
2181 assert!(!symbol.is_margin_trading_allowed);
2182 assert!(symbol.filters.price_filter.is_none()); assert!(symbol.filters.lot_size_filter.is_none());
2184 assert_eq!(symbol.permissions.len(), 1);
2185 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
2186 }
2187
2188 #[rstest]
2189 fn test_decode_exchange_info_empty() {
2190 let header = create_header(
2191 0,
2192 EXCHANGE_INFO_TEMPLATE_ID,
2193 SBE_SCHEMA_ID,
2194 SBE_SCHEMA_VERSION,
2195 );
2196
2197 let mut buf = Vec::new();
2198 buf.extend_from_slice(&header);
2199
2200 buf.extend_from_slice(&create_group_header(11, 0));
2202
2203 buf.extend_from_slice(&create_group_header(0, 0));
2205
2206 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
2208
2209 let info = decode_exchange_info(&buf).unwrap();
2210 assert!(info.symbols.is_empty());
2211 }
2212
2213 #[rstest]
2214 fn test_decode_exchange_info_wrong_template() {
2215 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2216
2217 let mut buf = Vec::new();
2218 buf.extend_from_slice(&header);
2219
2220 let err = decode_exchange_info(&buf).unwrap_err();
2221 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
2222 }
2223
2224 #[rstest]
2225 fn test_decode_exchange_info_multiple_symbols() {
2226 let header = create_header(
2227 0,
2228 EXCHANGE_INFO_TEMPLATE_ID,
2229 SBE_SCHEMA_ID,
2230 SBE_SCHEMA_VERSION,
2231 );
2232
2233 let mut buf = Vec::new();
2234 buf.extend_from_slice(&header);
2235
2236 buf.extend_from_slice(&create_group_header(11, 0));
2238
2239 buf.extend_from_slice(&create_group_header(0, 0));
2241
2242 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
2244
2245 buf.push(0); buf.push(8); buf.push(8); buf.push(8); buf.push(8); buf.extend_from_slice(&0b0000_0011u16.to_le_bytes()); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(0); buf.push(0); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(0, 0)); buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "BTCUSDT");
2267 write_var_string(&mut buf, "BTC");
2268 write_var_string(&mut buf, "USDT");
2269
2270 buf.push(0); buf.push(8); buf.push(8); buf.push(8); buf.push(8); buf.extend_from_slice(&0b0000_0011u16.to_le_bytes()); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(1); buf.push(0); buf.push(1); buf.push(1); buf.push(0); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(0, 0)); buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2292 write_var_string(&mut buf, "ETH");
2293 write_var_string(&mut buf, "USDT");
2294
2295 let info = decode_exchange_info(&buf).unwrap();
2296
2297 assert_eq!(info.symbols.len(), 2);
2298 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
2299 assert_eq!(info.symbols[0].base_asset, "BTC");
2300 assert!(!info.symbols[0].is_margin_trading_allowed);
2301
2302 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
2303 assert_eq!(info.symbols[1].base_asset, "ETH");
2304 assert!(info.symbols[1].is_margin_trading_allowed);
2305 }
2306
2307 #[rstest]
2308 fn test_decode_klines_valid() {
2309 let header = create_header(2, KLINES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2310
2311 let mut buf = Vec::new();
2312 buf.extend_from_slice(&header);
2313 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&create_group_header(KLINES_BLOCK_LENGTH, 1));
2316 buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&12_500i64.to_le_bytes()); buf.extend_from_slice(&11_900i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&1_234_500i128.to_le_bytes()); buf.extend_from_slice(&1_700_000_059_999_000i64.to_le_bytes()); buf.extend_from_slice(&2_345_600i128.to_le_bytes()); buf.extend_from_slice(&100i64.to_le_bytes()); buf.extend_from_slice(&600_000i128.to_le_bytes()); buf.extend_from_slice(&1_200_000i128.to_le_bytes()); let klines = decode_klines(&buf).unwrap();
2329
2330 assert_eq!(klines.price_exponent, -2);
2331 assert_eq!(klines.qty_exponent, -4);
2332 assert_eq!(klines.klines.len(), 1);
2333 assert_eq!(klines.klines[0].open_time, 1_700_000_000_000_000);
2334 assert_eq!(klines.klines[0].close_price, 12_345);
2335 assert_eq!(i128::from_le_bytes(klines.klines[0].volume), 1_234_500);
2336 assert_eq!(klines.klines[0].num_trades, 100);
2337 }
2338
2339 #[rstest]
2340 fn test_decode_new_order_full_valid() {
2341 let header = create_header(
2342 NEW_ORDER_FULL_BLOCK_LENGTH as u16,
2343 NEW_ORDER_FULL_TEMPLATE_ID,
2344 SBE_SCHEMA_ID,
2345 SBE_SCHEMA_VERSION,
2346 );
2347
2348 let mut buf = Vec::new();
2349 buf.extend_from_slice(&header);
2350
2351 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&99i64.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&25_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&123_450_000i64.to_le_bytes()); buf.push(2); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&1_700_000_000_000_500i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 23]); buf.push(0); buf.extend_from_slice(&[0u8; 16]); buf.push((-8i8) as u8); buf.extend_from_slice(&[0u8; 18]); buf.extend_from_slice(&create_group_header(FILLS_BLOCK_LENGTH, 1));
2374 buf.push((-8i8) as u8); buf.push(0); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&555i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); write_var_string(&mut buf, "USDT");
2382
2383 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2385 write_var_string(&mut buf, "client-123");
2386
2387 let response = decode_new_order_full(&buf).unwrap();
2388
2389 assert_eq!(response.order_id, 12345);
2390 assert_eq!(response.order_list_id, Some(99));
2391 assert_eq!(response.transact_time, 1_700_000_000_000_000);
2392 assert_eq!(response.price_mantissa, 12_345);
2393 assert_eq!(response.orig_qty_mantissa, 25_000);
2394 assert_eq!(response.executed_qty_mantissa, 10_000);
2395 assert_eq!(response.stop_price_mantissa, Some(12_000));
2396 assert_eq!(response.working_time, Some(1_700_000_000_000_500));
2397 assert_eq!(response.symbol, "ETHUSDT");
2398 assert_eq!(response.client_order_id, "client-123");
2399 assert_eq!(response.fills.len(), 1);
2400 assert_eq!(response.fills[0].price_mantissa, 12_345);
2401 assert_eq!(response.fills[0].qty_mantissa, 10_000);
2402 assert_eq!(response.fills[0].trade_id, Some(555));
2403 assert_eq!(response.fills[0].commission_asset, "USDT");
2404 }
2405
2406 fn build_new_order_full_v3_buffer(expiry_reason_byte: u8) -> Vec<u8> {
2409 const V3_BLOCK_LENGTH: u16 = 154;
2410 let header = create_header(
2411 V3_BLOCK_LENGTH,
2412 NEW_ORDER_FULL_TEMPLATE_ID,
2413 SBE_SCHEMA_ID,
2414 SBE_SCHEMA_VERSION,
2415 );
2416
2417 let mut buf = Vec::new();
2418 buf.extend_from_slice(&header);
2419
2420 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&99i64.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&25_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&123_450_000i64.to_le_bytes()); buf.push(2); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&1_700_000_000_000_500i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 23]); buf.push(0); buf.extend_from_slice(&[0u8; 16]); buf.push((-8i8) as u8); buf.extend_from_slice(&[0u8; 18]); buf.push(expiry_reason_byte); buf.extend_from_slice(&create_group_header(FILLS_BLOCK_LENGTH, 1));
2444 buf.push((-8i8) as u8); buf.push(0); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&555i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); write_var_string(&mut buf, "USDT");
2452
2453 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2455 write_var_string(&mut buf, "client-456");
2456 buf
2457 }
2458
2459 #[rstest]
2460 fn test_decode_new_order_full_v3_block_length() {
2461 let buf = build_new_order_full_v3_buffer(0xFF);
2464
2465 let response = decode_new_order_full(&buf).unwrap();
2466
2467 assert_eq!(response.order_id, 12345);
2468 assert_eq!(response.symbol, "ETHUSDT");
2469 assert_eq!(response.client_order_id, "client-456");
2470 assert_eq!(response.fills.len(), 1);
2471 assert_eq!(response.fills[0].price_mantissa, 12_345);
2472 assert!(response.expiry_reason.is_none());
2473 }
2474
2475 #[rstest]
2476 fn test_decode_new_order_full_v3_captures_expiry_reason_value() {
2477 let buf = build_new_order_full_v3_buffer(0x05);
2480
2481 let response = decode_new_order_full(&buf).unwrap();
2482
2483 assert_eq!(response.expiry_reason, Some(0x05));
2484 assert_eq!(response.symbol, "ETHUSDT");
2487 assert_eq!(response.client_order_id, "client-456");
2488 }
2489
2490 #[rstest]
2491 fn test_decode_cancel_open_orders_valid() {
2492 let header = create_header(
2493 0,
2494 CANCEL_OPEN_ORDERS_TEMPLATE_ID,
2495 SBE_SCHEMA_ID,
2496 SBE_SCHEMA_VERSION,
2497 );
2498 let response_one = create_cancel_order_response_buffer(111, "ETHUSDT", "orig-1", "new-1");
2499 let response_two = create_cancel_order_response_buffer(222, "BTCUSDT", "orig-2", "new-2");
2500
2501 let mut buf = Vec::new();
2502 buf.extend_from_slice(&header);
2503 buf.extend_from_slice(&create_group_header(0, 2));
2504 buf.extend_from_slice(&(response_one.len() as u16).to_le_bytes());
2505 buf.extend_from_slice(&response_one);
2506 buf.extend_from_slice(&(response_two.len() as u16).to_le_bytes());
2507 buf.extend_from_slice(&response_two);
2508
2509 let responses = decode_cancel_open_orders(&buf).unwrap();
2510
2511 assert_eq!(responses.len(), 2);
2512 assert_eq!(responses[0].order_id, 111);
2513 assert_eq!(responses[0].symbol, "ETHUSDT");
2514 assert_eq!(responses[0].orig_client_order_id, "orig-1");
2515 assert_eq!(responses[0].client_order_id, "new-1");
2516 assert_eq!(responses[1].order_id, 222);
2517 assert_eq!(responses[1].symbol, "BTCUSDT");
2518 assert_eq!(responses[1].orig_client_order_id, "orig-2");
2519 assert_eq!(responses[1].client_order_id, "new-2");
2520 }
2521
2522 fn create_cancel_order_response_buffer(
2523 order_id: i64,
2524 symbol: &str,
2525 orig_client_order_id: &str,
2526 client_order_id: &str,
2527 ) -> Vec<u8> {
2528 let header = create_header(
2529 CANCEL_ORDER_BLOCK_LENGTH as u16,
2530 CANCEL_ORDER_TEMPLATE_ID,
2531 SBE_SCHEMA_ID,
2532 SBE_SCHEMA_VERSION,
2533 );
2534
2535 let mut buf = Vec::new();
2536 buf.extend_from_slice(&header);
2537 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&order_id.to_le_bytes());
2540 buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000i64.to_le_bytes()); buf.push(4); buf.push(1); buf.push(1); buf.push(1); buf.push(0); while buf.len() < 8 + CANCEL_ORDER_BLOCK_LENGTH {
2553 buf.push(0);
2554 }
2555
2556 write_var_string(&mut buf, symbol);
2557 write_var_string(&mut buf, orig_client_order_id);
2558 write_var_string(&mut buf, client_order_id);
2559
2560 buf
2561 }
2562}