1use super::{
22 error::SbeDecodeError,
23 models::{
24 BinanceAccountInfo, BinanceAccountTrade, BinanceBalance, BinanceCancelOrderResponse,
25 BinanceDepth, BinanceExchangeInfoSbe, BinanceKline, BinanceKlines, BinanceLotSizeFilterSbe,
26 BinanceNewOrderResponse, BinanceOrderFill, BinanceOrderResponse, BinancePriceFilterSbe,
27 BinancePriceLevel, BinanceSymbolFiltersSbe, BinanceSymbolSbe, BinanceTrade, BinanceTrades,
28 },
29};
30use crate::spot::sbe::{
31 cursor::SbeCursor,
32 spot::{
33 SBE_SCHEMA_ID, SBE_SCHEMA_VERSION,
34 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, bool_enum::BoolEnum,
37 cancel_open_orders_response_codec::SBE_TEMPLATE_ID as CANCEL_OPEN_ORDERS_TEMPLATE_ID,
38 cancel_order_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_TEMPLATE_ID,
39 depth_response_codec::SBE_TEMPLATE_ID as DEPTH_TEMPLATE_ID,
40 exchange_info_response_codec::SBE_TEMPLATE_ID as EXCHANGE_INFO_TEMPLATE_ID,
41 klines_response_codec::SBE_TEMPLATE_ID as KLINES_TEMPLATE_ID,
42 lot_size_filter_codec::SBE_TEMPLATE_ID as LOT_SIZE_FILTER_TEMPLATE_ID,
43 message_header_codec::ENCODED_LENGTH as HEADER_LENGTH,
44 new_order_full_response_codec::SBE_TEMPLATE_ID as NEW_ORDER_FULL_TEMPLATE_ID,
45 order_response_codec::SBE_TEMPLATE_ID as ORDER_TEMPLATE_ID,
46 orders_response_codec::SBE_TEMPLATE_ID as ORDERS_TEMPLATE_ID,
47 ping_response_codec::SBE_TEMPLATE_ID as PING_TEMPLATE_ID,
48 price_filter_codec::SBE_TEMPLATE_ID as PRICE_FILTER_TEMPLATE_ID,
49 server_time_response_codec::SBE_TEMPLATE_ID as SERVER_TIME_TEMPLATE_ID,
50 trades_response_codec::SBE_TEMPLATE_ID as TRADES_TEMPLATE_ID,
51 },
52};
53
54#[derive(Debug, Clone, Copy)]
56struct MessageHeader {
57 block_length: u16,
58 template_id: u16,
59 schema_id: u16,
60 version: u16,
61}
62
63impl MessageHeader {
64 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
66 cursor.require(HEADER_LENGTH)?;
67 Ok(Self {
68 block_length: cursor.read_u16_le()?,
69 template_id: cursor.read_u16_le()?,
70 schema_id: cursor.read_u16_le()?,
71 version: cursor.read_u16_le()?,
72 })
73 }
74
75 fn validate(&self) -> Result<(), SbeDecodeError> {
77 if self.schema_id != SBE_SCHEMA_ID {
78 return Err(SbeDecodeError::SchemaMismatch {
79 expected: SBE_SCHEMA_ID,
80 actual: self.schema_id,
81 });
82 }
83
84 if self.version != SBE_SCHEMA_VERSION {
85 return Err(SbeDecodeError::VersionMismatch {
86 expected: SBE_SCHEMA_VERSION,
87 actual: self.version,
88 });
89 }
90 Ok(())
91 }
92}
93
94pub fn decode_ping(buf: &[u8]) -> Result<(), SbeDecodeError> {
102 let mut cursor = SbeCursor::new(buf);
103 let header = MessageHeader::decode_cursor(&mut cursor)?;
104 header.validate()?;
105
106 if header.template_id != PING_TEMPLATE_ID {
107 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
108 }
109
110 Ok(())
111}
112
113pub fn decode_server_time(buf: &[u8]) -> Result<i64, SbeDecodeError> {
122 let mut cursor = SbeCursor::new(buf);
123 let header = MessageHeader::decode_cursor(&mut cursor)?;
124 header.validate()?;
125
126 if header.template_id != SERVER_TIME_TEMPLATE_ID {
127 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
128 }
129
130 cursor.read_i64_le()
131}
132
133pub fn decode_depth(buf: &[u8]) -> Result<BinanceDepth, SbeDecodeError> {
141 let mut cursor = SbeCursor::new(buf);
142 let header = MessageHeader::decode_cursor(&mut cursor)?;
143 header.validate()?;
144
145 if header.template_id != DEPTH_TEMPLATE_ID {
146 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
147 }
148
149 let last_update_id = cursor.read_i64_le()?;
150 let price_exponent = cursor.read_i8()?;
151 let qty_exponent = cursor.read_i8()?;
152
153 let (block_len, count) = cursor.read_group_header()?;
154 let bids = cursor.read_group(block_len, count, |c| {
155 Ok(BinancePriceLevel {
156 price_mantissa: c.read_i64_le()?,
157 qty_mantissa: c.read_i64_le()?,
158 })
159 })?;
160
161 let (block_len, count) = cursor.read_group_header()?;
162 let asks = cursor.read_group(block_len, count, |c| {
163 Ok(BinancePriceLevel {
164 price_mantissa: c.read_i64_le()?,
165 qty_mantissa: c.read_i64_le()?,
166 })
167 })?;
168
169 Ok(BinanceDepth {
170 last_update_id,
171 price_exponent,
172 qty_exponent,
173 bids,
174 asks,
175 })
176}
177
178pub fn decode_trades(buf: &[u8]) -> Result<BinanceTrades, SbeDecodeError> {
186 let mut cursor = SbeCursor::new(buf);
187 let header = MessageHeader::decode_cursor(&mut cursor)?;
188 header.validate()?;
189
190 if header.template_id != TRADES_TEMPLATE_ID {
191 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
192 }
193
194 let price_exponent = cursor.read_i8()?;
195 let qty_exponent = cursor.read_i8()?;
196
197 let (block_len, count) = cursor.read_group_header()?;
198 let trades = cursor.read_group(block_len, count, |c| {
199 Ok(BinanceTrade {
200 id: c.read_i64_le()?,
201 price_mantissa: c.read_i64_le()?,
202 qty_mantissa: c.read_i64_le()?,
203 quote_qty_mantissa: c.read_i64_le()?,
204 time: c.read_i64_le()?,
205 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
206 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
207 })
208 })?;
209
210 Ok(BinanceTrades {
211 price_exponent,
212 qty_exponent,
213 trades,
214 })
215}
216
217const KLINES_BLOCK_LENGTH: u16 = 120;
219
220pub fn decode_klines(buf: &[u8]) -> Result<BinanceKlines, SbeDecodeError> {
228 let mut cursor = SbeCursor::new(buf);
229 let header = MessageHeader::decode_cursor(&mut cursor)?;
230 header.validate()?;
231
232 if header.template_id != KLINES_TEMPLATE_ID {
233 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
234 }
235
236 let price_exponent = cursor.read_i8()?;
237 let qty_exponent = cursor.read_i8()?;
238
239 let (block_len, count) = cursor.read_group_header()?;
240
241 if block_len != KLINES_BLOCK_LENGTH {
242 return Err(SbeDecodeError::InvalidBlockLength {
243 expected: KLINES_BLOCK_LENGTH,
244 actual: block_len,
245 });
246 }
247
248 let mut klines = Vec::with_capacity(count as usize);
249
250 for _ in 0..count {
251 cursor.require(KLINES_BLOCK_LENGTH as usize)?;
252
253 let open_time = cursor.read_i64_le()?;
254 let open_price = cursor.read_i64_le()?;
255 let high_price = cursor.read_i64_le()?;
256 let low_price = cursor.read_i64_le()?;
257 let close_price = cursor.read_i64_le()?;
258
259 let volume_slice = cursor.read_bytes(16)?;
260 let mut volume = [0u8; 16];
261 volume.copy_from_slice(volume_slice);
262
263 let close_time = cursor.read_i64_le()?;
264
265 let quote_volume_slice = cursor.read_bytes(16)?;
266 let mut quote_volume = [0u8; 16];
267 quote_volume.copy_from_slice(quote_volume_slice);
268
269 let num_trades = cursor.read_i64_le()?;
270
271 let taker_buy_base_volume_slice = cursor.read_bytes(16)?;
272 let mut taker_buy_base_volume = [0u8; 16];
273 taker_buy_base_volume.copy_from_slice(taker_buy_base_volume_slice);
274
275 let taker_buy_quote_volume_slice = cursor.read_bytes(16)?;
276 let mut taker_buy_quote_volume = [0u8; 16];
277 taker_buy_quote_volume.copy_from_slice(taker_buy_quote_volume_slice);
278
279 klines.push(BinanceKline {
280 open_time,
281 open_price,
282 high_price,
283 low_price,
284 close_price,
285 volume,
286 close_time,
287 quote_volume,
288 num_trades,
289 taker_buy_base_volume,
290 taker_buy_quote_volume,
291 });
292 }
293
294 Ok(BinanceKlines {
295 price_exponent,
296 qty_exponent,
297 klines,
298 })
299}
300
301const NEW_ORDER_FULL_FIELDS_END: usize = 135;
304const CANCEL_ORDER_FIELDS_END: usize = 63;
305const ORDER_FIELDS_END: usize = 104;
306
307const EXPIRY_REASON_NULL: u8 = 0xff;
309
310const NEW_ORDER_FULL_EXPIRY_REASON_OFFSET: usize = 153;
314const ORDER_EXPIRY_REASON_OFFSET: usize = 162;
315const ORDERS_GROUP_EXPIRY_REASON_OFFSET: usize = 162;
316
317fn read_trailing_expiry_reason(
327 cursor: &mut SbeCursor<'_>,
328 block_length: usize,
329 fields_end: usize,
330 expiry_reason_offset: usize,
331) -> Result<Option<u8>, SbeDecodeError> {
332 debug_assert!(fields_end <= expiry_reason_offset);
333 if block_length < fields_end {
334 return Ok(None);
335 }
336 let trailer = block_length - fields_end;
337 if trailer == 0 {
338 return Ok(None);
339 }
340 if block_length <= expiry_reason_offset {
342 cursor.advance(trailer)?;
343 return Ok(None);
344 }
345 let pre = expiry_reason_offset - fields_end;
346 cursor.advance(pre)?;
347 let byte = cursor.read_u8()?;
348 let post = trailer - pre - 1;
349 if post > 0 {
350 cursor.advance(post)?;
351 }
352 Ok((byte != EXPIRY_REASON_NULL).then_some(byte))
353}
354
355#[allow(dead_code)]
361pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
362 let mut cursor = SbeCursor::new(buf);
363 let header = MessageHeader::decode_cursor(&mut cursor)?;
364 header.validate()?;
365
366 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
367 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
368 }
369
370 cursor.require(header.block_length as usize)?;
371
372 let price_exponent = cursor.read_i8()?;
373 let qty_exponent = cursor.read_i8()?;
374 let order_id = cursor.read_i64_le()?;
375 let order_list_id = cursor.read_optional_i64_le()?;
376 let transact_time = cursor.read_i64_le()?;
377 let price_mantissa = cursor.read_i64_le()?;
378 let orig_qty_mantissa = cursor.read_i64_le()?;
379 let executed_qty_mantissa = cursor.read_i64_le()?;
380 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
381 let status = cursor.read_u8()?.into();
382 let time_in_force = cursor.read_u8()?.into();
383 let order_type = cursor.read_u8()?.into();
384 let side = cursor.read_u8()?.into();
385 let stop_price_mantissa = cursor.read_optional_i64_le()?;
386
387 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
389
390 cursor.advance(23)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
392
393 cursor.advance(16)?; let _commission_exponent = cursor.read_i8()?;
395
396 let expiry_reason = read_trailing_expiry_reason(
397 &mut cursor,
398 header.block_length as usize,
399 NEW_ORDER_FULL_FIELDS_END,
400 NEW_ORDER_FULL_EXPIRY_REASON_OFFSET,
401 )?;
402
403 let fills = decode_fills_cursor(&mut cursor)?;
404
405 let (block_len, count) = cursor.read_group_header()?;
407 cursor.advance(block_len as usize * count as usize)?;
408
409 let symbol = cursor.read_var_string8()?;
410 let client_order_id = cursor.read_var_string8()?;
411
412 Ok(BinanceNewOrderResponse {
413 price_exponent,
414 qty_exponent,
415 order_id,
416 order_list_id,
417 transact_time,
418 price_mantissa,
419 orig_qty_mantissa,
420 executed_qty_mantissa,
421 cummulative_quote_qty_mantissa,
422 status,
423 time_in_force,
424 order_type,
425 side,
426 stop_price_mantissa,
427 working_time,
428 self_trade_prevention_mode,
429 client_order_id,
430 symbol,
431 fills,
432 expiry_reason,
433 })
434}
435
436#[allow(dead_code)]
442pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
443 let mut cursor = SbeCursor::new(buf);
444 let header = MessageHeader::decode_cursor(&mut cursor)?;
445 header.validate()?;
446
447 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
448 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
449 }
450
451 cursor.require(header.block_length as usize)?;
452
453 let price_exponent = cursor.read_i8()?;
454 let qty_exponent = cursor.read_i8()?;
455 let order_id = cursor.read_i64_le()?;
456 let order_list_id = cursor.read_optional_i64_le()?;
457 let transact_time = cursor.read_i64_le()?;
458 let price_mantissa = cursor.read_i64_le()?;
459 let orig_qty_mantissa = cursor.read_i64_le()?;
460 let executed_qty_mantissa = cursor.read_i64_le()?;
461 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
462 let status = cursor.read_u8()?.into();
463 let time_in_force = cursor.read_u8()?.into();
464 let order_type = cursor.read_u8()?.into();
465 let side = cursor.read_u8()?.into();
466 let self_trade_prevention_mode = cursor.read_u8()?.into();
467
468 cursor.advance(header.block_length as usize - CANCEL_ORDER_FIELDS_END)?;
469
470 let symbol = cursor.read_var_string8()?;
471 let orig_client_order_id = cursor.read_var_string8()?;
472 let client_order_id = cursor.read_var_string8()?;
473
474 Ok(BinanceCancelOrderResponse {
475 price_exponent,
476 qty_exponent,
477 order_id,
478 order_list_id,
479 transact_time,
480 price_mantissa,
481 orig_qty_mantissa,
482 executed_qty_mantissa,
483 cummulative_quote_qty_mantissa,
484 status,
485 time_in_force,
486 order_type,
487 side,
488 self_trade_prevention_mode,
489 client_order_id,
490 orig_client_order_id,
491 symbol,
492 })
493}
494
495#[allow(dead_code)]
501pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
502 let mut cursor = SbeCursor::new(buf);
503 let header = MessageHeader::decode_cursor(&mut cursor)?;
504 header.validate()?;
505
506 if header.template_id != ORDER_TEMPLATE_ID {
507 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
508 }
509
510 cursor.require(header.block_length as usize)?;
511
512 let price_exponent = cursor.read_i8()?;
513 let qty_exponent = cursor.read_i8()?;
514 let order_id = cursor.read_i64_le()?;
515 let order_list_id = cursor.read_optional_i64_le()?;
516 let price_mantissa = cursor.read_i64_le()?;
517 let orig_qty_mantissa = cursor.read_i64_le()?;
518 let executed_qty_mantissa = cursor.read_i64_le()?;
519 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
520 let status = cursor.read_u8()?.into();
521 let time_in_force = cursor.read_u8()?.into();
522 let order_type = cursor.read_u8()?.into();
523 let side = cursor.read_u8()?.into();
524 let stop_price_mantissa = cursor.read_optional_i64_le()?;
525 let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
526 let time = cursor.read_i64_le()?;
527 let update_time = cursor.read_i64_le()?;
528 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
529 let working_time = cursor.read_optional_i64_le()?;
530 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
531 let self_trade_prevention_mode = cursor.read_u8()?.into();
532
533 let expiry_reason = read_trailing_expiry_reason(
534 &mut cursor,
535 header.block_length as usize,
536 ORDER_FIELDS_END,
537 ORDER_EXPIRY_REASON_OFFSET,
538 )?;
539
540 let symbol = cursor.read_var_string8()?;
541 let client_order_id = cursor.read_var_string8()?;
542
543 Ok(BinanceOrderResponse {
544 price_exponent,
545 qty_exponent,
546 order_id,
547 order_list_id,
548 price_mantissa,
549 orig_qty_mantissa,
550 executed_qty_mantissa,
551 cummulative_quote_qty_mantissa,
552 status,
553 time_in_force,
554 order_type,
555 side,
556 stop_price_mantissa,
557 iceberg_qty_mantissa,
558 time,
559 update_time,
560 is_working,
561 working_time,
562 orig_quote_order_qty_mantissa,
563 self_trade_prevention_mode,
564 client_order_id,
565 symbol,
566 expiry_reason,
567 })
568}
569
570const ORDERS_GROUP_MIN_BLOCK_LENGTH: u16 = 162;
572
573const ORDERS_GROUP_FIELDS_END: usize = 134;
577
578#[allow(dead_code)]
584pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
585 let mut cursor = SbeCursor::new(buf);
586 let header = MessageHeader::decode_cursor(&mut cursor)?;
587 header.validate()?;
588
589 if header.template_id != ORDERS_TEMPLATE_ID {
590 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
591 }
592
593 let (block_length, count) = cursor.read_group_header()?;
594
595 if count == 0 {
596 return Ok(Vec::new());
597 }
598
599 if block_length < ORDERS_GROUP_MIN_BLOCK_LENGTH {
600 return Err(SbeDecodeError::InvalidBlockLength {
601 expected: ORDERS_GROUP_MIN_BLOCK_LENGTH,
602 actual: block_length,
603 });
604 }
605
606 let mut orders = Vec::with_capacity(count as usize);
607
608 for _ in 0..count {
609 cursor.require(block_length as usize)?;
610
611 let price_exponent = cursor.read_i8()?;
612 let qty_exponent = cursor.read_i8()?;
613 let order_id = cursor.read_i64_le()?;
614 let order_list_id = cursor.read_optional_i64_le()?;
615 let price_mantissa = cursor.read_i64_le()?;
616 let orig_qty_mantissa = cursor.read_i64_le()?;
617 let executed_qty_mantissa = cursor.read_i64_le()?;
618 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
619 let status = cursor.read_u8()?.into();
620 let time_in_force = cursor.read_u8()?.into();
621 let order_type = cursor.read_u8()?.into();
622 let side = cursor.read_u8()?.into();
623 let stop_price_mantissa = cursor.read_optional_i64_le()?;
624
625 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
627 let time = cursor.read_i64_le()?;
628 let update_time = cursor.read_i64_le()?;
629 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
630 let working_time = cursor.read_optional_i64_le()?;
631 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
632
633 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
635
636 let expiry_reason = read_trailing_expiry_reason(
637 &mut cursor,
638 block_length as usize,
639 ORDERS_GROUP_FIELDS_END,
640 ORDERS_GROUP_EXPIRY_REASON_OFFSET,
641 )?;
642
643 let symbol = cursor.read_var_string8()?;
644 let client_order_id = cursor.read_var_string8()?;
645
646 orders.push(BinanceOrderResponse {
647 price_exponent,
648 qty_exponent,
649 order_id,
650 order_list_id,
651 price_mantissa,
652 orig_qty_mantissa,
653 executed_qty_mantissa,
654 cummulative_quote_qty_mantissa,
655 status,
656 time_in_force,
657 order_type,
658 side,
659 stop_price_mantissa,
660 iceberg_qty_mantissa,
661 time,
662 update_time,
663 is_working,
664 working_time,
665 orig_quote_order_qty_mantissa,
666 self_trade_prevention_mode,
667 client_order_id,
668 symbol,
669 expiry_reason,
670 });
671 }
672
673 Ok(orders)
674}
675
676#[allow(dead_code)]
684pub fn decode_cancel_open_orders(
685 buf: &[u8],
686) -> Result<Vec<BinanceCancelOrderResponse>, SbeDecodeError> {
687 let mut cursor = SbeCursor::new(buf);
688 let header = MessageHeader::decode_cursor(&mut cursor)?;
689 header.validate()?;
690
691 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
692 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
693 }
694
695 let (_block_length, count) = cursor.read_group_header()?;
696
697 if count == 0 {
698 return Ok(Vec::new());
699 }
700
701 let mut responses = Vec::with_capacity(count as usize);
702
703 for _ in 0..count {
705 let response_len = cursor.read_u16_le()? as usize;
706 let embedded_bytes = cursor.read_bytes(response_len)?;
707 let cancel_response = decode_cancel_order(embedded_bytes)?;
708 responses.push(cancel_response);
709 }
710
711 Ok(responses)
712}
713
714const ACCOUNT_BLOCK_LENGTH: usize = 64;
716
717const BALANCE_BLOCK_LENGTH: u16 = 17;
719
720#[allow(dead_code)]
726pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
727 let mut cursor = SbeCursor::new(buf);
728 let header = MessageHeader::decode_cursor(&mut cursor)?;
729 header.validate()?;
730
731 if header.template_id != ACCOUNT_TEMPLATE_ID {
732 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
733 }
734
735 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
736
737 let commission_exponent = cursor.read_i8()?;
738 let maker_commission_mantissa = cursor.read_i64_le()?;
739 let taker_commission_mantissa = cursor.read_i64_le()?;
740 let buyer_commission_mantissa = cursor.read_i64_le()?;
741 let seller_commission_mantissa = cursor.read_i64_le()?;
742 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
743 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
744 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
745 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
747 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
748 let update_time = cursor.read_i64_le()?;
749 let account_type_enum = AccountType::from(cursor.read_u8()?);
750 cursor.advance(16)?; let account_type = account_type_enum.to_string();
753
754 let (block_length, balance_count) = cursor.read_group_header()?;
755
756 if block_length != BALANCE_BLOCK_LENGTH {
757 return Err(SbeDecodeError::InvalidBlockLength {
758 expected: BALANCE_BLOCK_LENGTH,
759 actual: block_length,
760 });
761 }
762
763 let mut balances = Vec::with_capacity(balance_count as usize);
764
765 for _ in 0..balance_count {
766 cursor.require(block_length as usize)?;
767
768 let exponent = cursor.read_i8()?;
769 let free_mantissa = cursor.read_i64_le()?;
770 let locked_mantissa = cursor.read_i64_le()?;
771
772 let asset = cursor.read_var_string8()?;
773
774 balances.push(BinanceBalance {
775 asset,
776 free_mantissa,
777 locked_mantissa,
778 exponent,
779 });
780 }
781
782 Ok(BinanceAccountInfo {
783 commission_exponent,
784 maker_commission_mantissa,
785 taker_commission_mantissa,
786 buyer_commission_mantissa,
787 seller_commission_mantissa,
788 can_trade,
789 can_withdraw,
790 can_deposit,
791 require_self_trade_prevention,
792 prevent_sor,
793 update_time,
794 account_type,
795 balances,
796 })
797}
798
799const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
801
802#[allow(dead_code)]
808pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
809 let mut cursor = SbeCursor::new(buf);
810 let header = MessageHeader::decode_cursor(&mut cursor)?;
811 header.validate()?;
812
813 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
814 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
815 }
816
817 let (block_length, trade_count) = cursor.read_group_header()?;
818
819 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
820 return Err(SbeDecodeError::InvalidBlockLength {
821 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
822 actual: block_length,
823 });
824 }
825
826 let mut trades = Vec::with_capacity(trade_count as usize);
827
828 for _ in 0..trade_count {
829 cursor.require(block_length as usize)?;
830
831 let price_exponent = cursor.read_i8()?;
832 let qty_exponent = cursor.read_i8()?;
833 let commission_exponent = cursor.read_i8()?;
834 let id = cursor.read_i64_le()?;
835 let order_id = cursor.read_i64_le()?;
836 let order_list_id = cursor.read_optional_i64_le()?;
837 let price_mantissa = cursor.read_i64_le()?;
838 let qty_mantissa = cursor.read_i64_le()?;
839 let quote_qty_mantissa = cursor.read_i64_le()?;
840 let commission_mantissa = cursor.read_i64_le()?;
841 let time = cursor.read_i64_le()?;
842 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
843 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
844 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
845
846 let symbol = cursor.read_var_string8()?;
847 let commission_asset = cursor.read_var_string8()?;
848
849 trades.push(BinanceAccountTrade {
850 price_exponent,
851 qty_exponent,
852 commission_exponent,
853 id,
854 order_id,
855 order_list_id,
856 price_mantissa,
857 qty_mantissa,
858 quote_qty_mantissa,
859 commission_mantissa,
860 time,
861 is_buyer,
862 is_maker,
863 is_best_match,
864 symbol,
865 commission_asset,
866 });
867 }
868
869 Ok(trades)
870}
871
872const FILLS_BLOCK_LENGTH: u16 = 42;
874
875fn decode_fills_cursor(
877 cursor: &mut SbeCursor<'_>,
878) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
879 let (block_length, count) = cursor.read_group_header()?;
880
881 if block_length != FILLS_BLOCK_LENGTH {
882 return Err(SbeDecodeError::InvalidBlockLength {
883 expected: FILLS_BLOCK_LENGTH,
884 actual: block_length,
885 });
886 }
887
888 let mut fills = Vec::with_capacity(count as usize);
889
890 for _ in 0..count {
891 cursor.require(block_length as usize)?;
892
893 let commission_exponent = cursor.read_i8()?;
894 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
896 let qty_mantissa = cursor.read_i64_le()?;
897 let commission_mantissa = cursor.read_i64_le()?;
898 let trade_id = cursor.read_optional_i64_le()?;
899 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
902
903 fills.push(BinanceOrderFill {
904 price_mantissa,
905 qty_mantissa,
906 commission_mantissa,
907 commission_exponent,
908 commission_asset,
909 trade_id,
910 });
911 }
912
913 Ok(fills)
914}
915
916const SYMBOL_BLOCK_LENGTH: usize = 19;
918
919pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
933 let mut cursor = SbeCursor::new(buf);
934 let header = MessageHeader::decode_cursor(&mut cursor)?;
935 header.validate()?;
936
937 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
938 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
939 }
940
941 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
943 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
944
945 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
947 for _ in 0..exchange_filters_count {
948 cursor.read_var_string8()?;
950 }
951
952 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
954
955 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
956 return Err(SbeDecodeError::InvalidBlockLength {
957 expected: SYMBOL_BLOCK_LENGTH as u16,
958 actual: symbols_block_len,
959 });
960 }
961
962 let mut symbols = Vec::with_capacity(symbols_count as usize);
963
964 for _ in 0..symbols_count {
965 cursor.require(SYMBOL_BLOCK_LENGTH)?;
966
967 let status = cursor.read_u8()?;
969 let base_asset_precision = cursor.read_u8()?;
970 let quote_asset_precision = cursor.read_u8()?;
971 let _base_commission_precision = cursor.read_u8()?;
972 let _quote_commission_precision = cursor.read_u8()?;
973 let order_types = cursor.read_u16_le()?;
974 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
975 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
976 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
977 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
978 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
979 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
980 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
981 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
982 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
983 let _default_self_trade_prevention_mode = cursor.read_u8()?;
984 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
985 let _peg_instructions_allowed = cursor.read_u8()?;
986
987 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
988 let mut filters = BinanceSymbolFiltersSbe::default();
989
990 for _ in 0..filters_count {
991 let filter_bytes = cursor.read_var_bytes8()?;
992
993 let (template_id, offset) = if filter_bytes.len() >= HEADER_LENGTH + 2 {
996 let potential_template = u16::from_le_bytes([filter_bytes[2], filter_bytes[3]]);
997 if potential_template == PRICE_FILTER_TEMPLATE_ID
998 || potential_template == LOT_SIZE_FILTER_TEMPLATE_ID
999 {
1000 (potential_template, HEADER_LENGTH)
1001 } else {
1002 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1003 (raw_template, 2)
1004 }
1005 } else if filter_bytes.len() >= 2 {
1006 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1007 (raw_template, 2)
1008 } else {
1009 continue;
1010 };
1011
1012 match template_id {
1014 PRICE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1015 let price_exp = filter_bytes[offset] as i8;
1016 let min_price = i64::from_le_bytes(
1017 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1018 );
1019 let max_price = i64::from_le_bytes(
1020 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1021 );
1022 let tick_size = i64::from_le_bytes(
1023 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1024 );
1025 filters.price_filter = Some(BinancePriceFilterSbe {
1026 price_exponent: price_exp,
1027 min_price,
1028 max_price,
1029 tick_size,
1030 });
1031 }
1032 LOT_SIZE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1033 let qty_exp = filter_bytes[offset] as i8;
1034 let min_qty = i64::from_le_bytes(
1035 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1036 );
1037 let max_qty = i64::from_le_bytes(
1038 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1039 );
1040 let step_size = i64::from_le_bytes(
1041 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1042 );
1043 filters.lot_size_filter = Some(BinanceLotSizeFilterSbe {
1044 qty_exponent: qty_exp,
1045 min_qty,
1046 max_qty,
1047 step_size,
1048 });
1049 }
1050 _ => {}
1051 }
1052 }
1053
1054 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
1056 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
1057 for _ in 0..perm_sets_count {
1058 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
1060 let mut perm_set = Vec::with_capacity(perms_count as usize);
1061 for _ in 0..perms_count {
1062 let perm = cursor.read_var_string8()?;
1063 perm_set.push(perm);
1064 }
1065 permissions.push(perm_set);
1066 }
1067
1068 let symbol = cursor.read_var_string8()?;
1070 let base_asset = cursor.read_var_string8()?;
1071 let quote_asset = cursor.read_var_string8()?;
1072
1073 symbols.push(BinanceSymbolSbe {
1074 symbol,
1075 base_asset,
1076 quote_asset,
1077 base_asset_precision,
1078 quote_asset_precision,
1079 status,
1080 order_types,
1081 iceberg_allowed,
1082 oco_allowed,
1083 oto_allowed,
1084 quote_order_qty_market_allowed,
1085 allow_trailing_stop,
1086 cancel_replace_allowed,
1087 amend_allowed,
1088 is_spot_trading_allowed,
1089 is_margin_trading_allowed,
1090 filters,
1091 permissions,
1092 });
1093 }
1094
1095 Ok(BinanceExchangeInfoSbe { symbols })
1098}
1099
1100#[cfg(test)]
1101mod tests {
1102 use rstest::rstest;
1103
1104 use super::*;
1105
1106 const NEW_ORDER_FULL_BLOCK_LENGTH: usize = 153;
1108
1109 const CANCEL_ORDER_BLOCK_LENGTH: usize = 137;
1111
1112 const ORDER_BLOCK_LENGTH: usize = 153;
1114
1115 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
1116 let mut buf = [0u8; 8];
1117 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1118 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
1119 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
1120 buf[6..8].copy_from_slice(&version.to_le_bytes());
1121 buf
1122 }
1123
1124 #[rstest]
1125 fn test_decode_ping_valid() {
1126 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1128 decode_ping(&buf).unwrap();
1129 }
1130
1131 #[rstest]
1132 fn test_decode_ping_buffer_too_short() {
1133 let buf = [0u8; 4];
1134 let err = decode_ping(&buf).unwrap_err();
1135 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1136 }
1137
1138 #[rstest]
1139 fn test_decode_ping_schema_mismatch() {
1140 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
1141 let err = decode_ping(&buf).unwrap_err();
1142 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
1143 }
1144
1145 #[rstest]
1146 fn test_decode_ping_wrong_template() {
1147 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1148 let err = decode_ping(&buf).unwrap_err();
1149 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
1150 }
1151
1152 #[rstest]
1153 fn test_decode_server_time_valid() {
1154 let header = create_header(
1156 8,
1157 SERVER_TIME_TEMPLATE_ID,
1158 SBE_SCHEMA_ID,
1159 SBE_SCHEMA_VERSION,
1160 );
1161 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
1164 buf.extend_from_slice(&header);
1165 buf.extend_from_slice(×tamp.to_le_bytes());
1166
1167 let result = decode_server_time(&buf).unwrap();
1168 assert_eq!(result, timestamp);
1169 }
1170
1171 #[rstest]
1172 fn test_decode_server_time_buffer_too_short() {
1173 let buf = create_header(
1175 8,
1176 SERVER_TIME_TEMPLATE_ID,
1177 SBE_SCHEMA_ID,
1178 SBE_SCHEMA_VERSION,
1179 );
1180 let err = decode_server_time(&buf).unwrap_err();
1181 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1182 }
1183
1184 #[rstest]
1185 fn test_decode_server_time_wrong_template() {
1186 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1187 let mut buf = Vec::with_capacity(16);
1188 buf.extend_from_slice(&header);
1189 buf.extend_from_slice(&0i64.to_le_bytes());
1190
1191 let err = decode_server_time(&buf).unwrap_err();
1192 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1193 }
1194
1195 #[rstest]
1196 fn test_decode_server_time_version_mismatch() {
1197 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, 99);
1198 let mut buf = Vec::with_capacity(16);
1199 buf.extend_from_slice(&header);
1200 buf.extend_from_slice(&0i64.to_le_bytes());
1201
1202 let err = decode_server_time(&buf).unwrap_err();
1203 assert!(matches!(err, SbeDecodeError::VersionMismatch { .. }));
1204 }
1205
1206 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
1207 let mut buf = [0u8; 6];
1208 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1209 buf[2..6].copy_from_slice(&count.to_le_bytes());
1210 buf
1211 }
1212
1213 #[rstest]
1214 fn test_decode_depth_valid() {
1215 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1217
1218 let mut buf = Vec::new();
1219 buf.extend_from_slice(&header);
1220
1221 let last_update_id: i64 = 123456789;
1223 let price_exponent: i8 = -8;
1224 let qty_exponent: i8 = -8;
1225 buf.extend_from_slice(&last_update_id.to_le_bytes());
1226 buf.push(price_exponent as u8);
1227 buf.push(qty_exponent as u8);
1228
1229 buf.extend_from_slice(&create_group_header(16, 2));
1231 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1233 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1234 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1236 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1237
1238 buf.extend_from_slice(&create_group_header(16, 1));
1240 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1242 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1243
1244 let depth = decode_depth(&buf).unwrap();
1245
1246 assert_eq!(depth.last_update_id, 123456789);
1247 assert_eq!(depth.price_exponent, -8);
1248 assert_eq!(depth.qty_exponent, -8);
1249 assert_eq!(depth.bids.len(), 2);
1250 assert_eq!(depth.asks.len(), 1);
1251 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1252 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1253 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1254 }
1255
1256 #[rstest]
1257 fn test_decode_depth_empty_book() {
1258 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1259
1260 let mut buf = Vec::new();
1261 buf.extend_from_slice(&header);
1262 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1268 buf.extend_from_slice(&create_group_header(16, 0));
1270
1271 let depth = decode_depth(&buf).unwrap();
1272
1273 assert!(depth.bids.is_empty());
1274 assert!(depth.asks.is_empty());
1275 }
1276
1277 #[rstest]
1278 fn test_decode_trades_valid() {
1279 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1281
1282 let mut buf = Vec::new();
1283 buf.extend_from_slice(&header);
1284
1285 let price_exponent: i8 = -8;
1287 let qty_exponent: i8 = -8;
1288 buf.push(price_exponent as u8);
1289 buf.push(qty_exponent as u8);
1290
1291 buf.extend_from_slice(&create_group_header(42, 1));
1293
1294 let trade_id: i64 = 999;
1296 let price: i64 = 100_000_000_000;
1297 let qty: i64 = 10_000_000;
1298 let quote_qty: i64 = 1_000_000_000_000;
1299 let time: i64 = 1734300000000;
1300 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1304 buf.extend_from_slice(&price.to_le_bytes());
1305 buf.extend_from_slice(&qty.to_le_bytes());
1306 buf.extend_from_slice("e_qty.to_le_bytes());
1307 buf.extend_from_slice(&time.to_le_bytes());
1308 buf.push(is_buyer_maker);
1309 buf.push(is_best_match);
1310
1311 let trades = decode_trades(&buf).unwrap();
1312
1313 assert_eq!(trades.price_exponent, -8);
1314 assert_eq!(trades.qty_exponent, -8);
1315 assert_eq!(trades.trades.len(), 1);
1316 assert_eq!(trades.trades[0].id, 999);
1317 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1318 assert!(trades.trades[0].is_buyer_maker);
1319 assert!(trades.trades[0].is_best_match);
1320 }
1321
1322 #[rstest]
1323 fn test_decode_trades_empty() {
1324 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1325
1326 let mut buf = Vec::new();
1327 buf.extend_from_slice(&header);
1328 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1333
1334 let trades = decode_trades(&buf).unwrap();
1335
1336 assert!(trades.trades.is_empty());
1337 }
1338
1339 #[rstest]
1340 fn test_decode_depth_wrong_template() {
1341 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1342
1343 let mut buf = Vec::new();
1344 buf.extend_from_slice(&header);
1345 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1348 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1349 }
1350
1351 #[rstest]
1352 fn test_decode_trades_wrong_template() {
1353 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1354
1355 let mut buf = Vec::new();
1356 buf.extend_from_slice(&header);
1357 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1360 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1361 }
1362
1363 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1364 buf.push(s.len() as u8);
1365 buf.extend_from_slice(s.as_bytes());
1366 }
1367
1368 fn build_order_response_buffer(block_length: u16, trailing_byte: u8) -> Vec<u8> {
1374 let header = create_header(
1375 block_length,
1376 ORDER_TEMPLATE_ID,
1377 SBE_SCHEMA_ID,
1378 SBE_SCHEMA_VERSION,
1379 );
1380
1381 let mut buf = Vec::new();
1382 buf.extend_from_slice(&header);
1383
1384 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;
1406 while buf.len() < block_end.saturating_sub(1) {
1408 buf.push(0);
1409 }
1410
1411 if buf.len() < block_end {
1412 buf.push(trailing_byte);
1413 }
1414
1415 write_var_string(&mut buf, "BTCUSDT");
1416 write_var_string(&mut buf, "my-order-123");
1417 buf
1418 }
1419
1420 #[rstest]
1421 #[case::pre_v4_no_expiry_reason(153, 0x00, None)]
1422 #[case::v4_null_sentinel(163, 0xFF, None)]
1423 #[case::v4_captures_value(163, 0x05, Some(0x05))]
1424 fn test_decode_order_expiry_reason(
1425 #[case] block_length: u16,
1426 #[case] trailing_byte: u8,
1427 #[case] expected: Option<u8>,
1428 ) {
1429 let buf = build_order_response_buffer(block_length, trailing_byte);
1430 let order = decode_order(&buf).unwrap();
1431 assert_eq!(order.expiry_reason, expected);
1432 assert_eq!(order.symbol, "BTCUSDT");
1435 assert_eq!(order.client_order_id, "my-order-123");
1436 }
1437
1438 #[rstest]
1439 fn test_decode_order_valid() {
1440 let header = create_header(
1441 ORDER_BLOCK_LENGTH as u16,
1442 ORDER_TEMPLATE_ID,
1443 SBE_SCHEMA_ID,
1444 SBE_SCHEMA_VERSION,
1445 );
1446
1447 let mut buf = Vec::new();
1448 buf.extend_from_slice(&header);
1449
1450 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 {
1474 buf.push(0);
1475 }
1476
1477 write_var_string(&mut buf, "BTCUSDT");
1478 write_var_string(&mut buf, "my-order-123");
1479
1480 let order = decode_order(&buf).unwrap();
1481
1482 assert_eq!(order.order_id, 12345);
1483 assert!(order.order_list_id.is_none());
1484 assert_eq!(order.price_exponent, -8);
1485 assert_eq!(order.price_mantissa, 100_000_000_000);
1486 assert!(order.stop_price_mantissa.is_none());
1487 assert!(order.iceberg_qty_mantissa.is_none());
1488 assert!(order.is_working);
1489 assert_eq!(order.working_time, Some(1734300000500));
1490 assert_eq!(order.symbol, "BTCUSDT");
1491 assert_eq!(order.client_order_id, "my-order-123");
1492 }
1493
1494 #[rstest]
1495 fn test_decode_order_future_block_length() {
1496 const FUTURE_BLOCK_LENGTH: u16 = ORDER_BLOCK_LENGTH as u16 + 4;
1498 let header = create_header(
1499 FUTURE_BLOCK_LENGTH,
1500 ORDER_TEMPLATE_ID,
1501 SBE_SCHEMA_ID,
1502 SBE_SCHEMA_VERSION,
1503 );
1504
1505 let mut buf = Vec::new();
1506 buf.extend_from_slice(&header);
1507
1508 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 {
1530 buf.push(0); }
1532
1533 write_var_string(&mut buf, "ETHUSDT");
1534 write_var_string(&mut buf, "order-future");
1535
1536 let order = decode_order(&buf).unwrap();
1537
1538 assert_eq!(order.order_id, 12345);
1539 assert_eq!(order.symbol, "ETHUSDT");
1540 assert_eq!(order.client_order_id, "order-future");
1541 }
1542
1543 #[rstest]
1544 fn test_decode_orders_multiple() {
1545 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1547
1548 let mut buf = Vec::new();
1549 buf.extend_from_slice(&header);
1550
1551 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 2));
1553
1554 let order1_start = buf.len();
1556 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 {
1579 buf.push(0);
1580 }
1581 write_var_string(&mut buf, "BTCUSDT");
1582 write_var_string(&mut buf, "order-1");
1583
1584 let order2_start = buf.len();
1586 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 {
1608 buf.push(0);
1609 }
1610 write_var_string(&mut buf, "ETHUSDT");
1611 write_var_string(&mut buf, "order-2");
1612
1613 let orders = decode_orders(&buf).unwrap();
1614
1615 assert_eq!(orders.len(), 2);
1616 assert_eq!(orders[0].order_id, 1001);
1617 assert_eq!(orders[0].symbol, "BTCUSDT");
1618 assert_eq!(orders[0].client_order_id, "order-1");
1619 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
1620
1621 assert_eq!(orders[1].order_id, 2002);
1622 assert_eq!(orders[1].symbol, "ETHUSDT");
1623 assert_eq!(orders[1].client_order_id, "order-2");
1624 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
1625 }
1626
1627 #[rstest]
1628 fn test_decode_orders_v4_trailing_expiry_reason() {
1629 const V4_BLOCK_LENGTH: u16 = 163;
1633 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1634
1635 let mut buf = Vec::new();
1636 buf.extend_from_slice(&header);
1637 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
1638
1639 let order_start = buf.len();
1640 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1641 buf.push(0xFF); assert_eq!(buf.len() - order_start, V4_BLOCK_LENGTH as usize);
1643
1644 write_var_string(&mut buf, "BTCUSDT");
1645 write_var_string(&mut buf, "v4-order");
1646
1647 let orders = decode_orders(&buf).unwrap();
1648
1649 assert_eq!(orders.len(), 1);
1650 assert_eq!(orders[0].symbol, "BTCUSDT");
1651 assert_eq!(orders[0].client_order_id, "v4-order");
1652 assert!(orders[0].expiry_reason.is_none());
1653 }
1654
1655 #[rstest]
1656 fn test_decode_orders_pre_v4_block_returns_no_expiry_reason() {
1657 const PRE_V4_BLOCK_LENGTH: u16 = ORDERS_GROUP_MIN_BLOCK_LENGTH;
1661 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1662
1663 let mut buf = Vec::new();
1664 buf.extend_from_slice(&header);
1665 buf.extend_from_slice(&create_group_header(PRE_V4_BLOCK_LENGTH, 1));
1666
1667 buf.extend_from_slice(&[0xAAu8; PRE_V4_BLOCK_LENGTH as usize]);
1670
1671 write_var_string(&mut buf, "BTCUSDT");
1672 write_var_string(&mut buf, "pre-v4-order");
1673
1674 let orders = decode_orders(&buf).unwrap();
1675 assert!(orders[0].expiry_reason.is_none());
1676 }
1677
1678 #[rstest]
1679 fn test_decode_orders_v4_captures_expiry_reason_value() {
1680 const V4_BLOCK_LENGTH: u16 = 163;
1684 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1685
1686 let mut buf = Vec::new();
1687 buf.extend_from_slice(&header);
1688 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
1689
1690 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1691 buf.push(0x05);
1692
1693 write_var_string(&mut buf, "BTCUSDT");
1694 write_var_string(&mut buf, "v4-expired");
1695
1696 let orders = decode_orders(&buf).unwrap();
1697 assert_eq!(orders[0].expiry_reason, Some(0x05));
1698 }
1699
1700 #[rstest]
1701 fn test_decode_orders_empty() {
1702 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1703
1704 let mut buf = Vec::new();
1705 buf.extend_from_slice(&header);
1706 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 0));
1707
1708 let orders = decode_orders(&buf).unwrap();
1709 assert!(orders.is_empty());
1710 }
1711
1712 #[rstest]
1713 fn test_decode_orders_truncated_var_string() {
1714 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1715
1716 let mut buf = Vec::new();
1717 buf.extend_from_slice(&header);
1718 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_MIN_BLOCK_LENGTH, 1));
1719
1720 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1722
1723 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
1728 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1729 }
1730
1731 #[rstest]
1732 fn test_decode_orders_invalid_utf8() {
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(ORDERS_GROUP_MIN_BLOCK_LENGTH, 1));
1738
1739 buf.extend_from_slice(&[0u8; ORDERS_GROUP_MIN_BLOCK_LENGTH as usize]);
1740
1741 buf.push(4);
1743 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
1744
1745 let err = decode_orders(&buf).unwrap_err();
1746 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
1747 }
1748
1749 #[rstest]
1750 fn test_decode_cancel_order_valid() {
1751 let header = create_header(
1752 CANCEL_ORDER_BLOCK_LENGTH as u16,
1753 CANCEL_ORDER_TEMPLATE_ID,
1754 SBE_SCHEMA_ID,
1755 SBE_SCHEMA_VERSION,
1756 );
1757
1758 let mut buf = Vec::new();
1759 buf.extend_from_slice(&header);
1760
1761 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 {
1778 buf.push(0);
1779 }
1780
1781 write_var_string(&mut buf, "BTCUSDT");
1782 write_var_string(&mut buf, "orig-client-id");
1783 write_var_string(&mut buf, "new-client-id");
1784
1785 let cancel = decode_cancel_order(&buf).unwrap();
1786
1787 assert_eq!(cancel.order_id, 99999);
1788 assert!(cancel.order_list_id.is_none());
1789 assert_eq!(cancel.symbol, "BTCUSDT");
1790 assert_eq!(cancel.orig_client_order_id, "orig-client-id");
1791 assert_eq!(cancel.client_order_id, "new-client-id");
1792 }
1793
1794 #[rstest]
1795 fn test_decode_cancel_order_future_block_length() {
1796 const FUTURE_BLOCK_LENGTH: u16 = CANCEL_ORDER_BLOCK_LENGTH as u16 + 4;
1798 let header = create_header(
1799 FUTURE_BLOCK_LENGTH,
1800 CANCEL_ORDER_TEMPLATE_ID,
1801 SBE_SCHEMA_ID,
1802 SBE_SCHEMA_VERSION,
1803 );
1804
1805 let mut buf = Vec::new();
1806 buf.extend_from_slice(&header);
1807
1808 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 {
1824 buf.push(0); }
1826
1827 write_var_string(&mut buf, "BTCUSDT");
1828 write_var_string(&mut buf, "orig-id");
1829 write_var_string(&mut buf, "new-id");
1830
1831 let cancel = decode_cancel_order(&buf).unwrap();
1832
1833 assert_eq!(cancel.order_id, 99999);
1834 assert_eq!(cancel.symbol, "BTCUSDT");
1835 assert_eq!(cancel.orig_client_order_id, "orig-id");
1836 assert_eq!(cancel.client_order_id, "new-id");
1837 }
1838
1839 #[rstest]
1840 fn test_decode_account_with_balances() {
1841 let header = create_header(
1842 ACCOUNT_BLOCK_LENGTH as u16,
1843 ACCOUNT_TEMPLATE_ID,
1844 SBE_SCHEMA_ID,
1845 SBE_SCHEMA_VERSION,
1846 );
1847
1848 let mut buf = Vec::new();
1849 buf.extend_from_slice(&header);
1850
1851 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 {
1868 buf.push(0);
1869 }
1870
1871 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
1873
1874 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");
1879
1880 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");
1885
1886 let account = decode_account(&buf).unwrap();
1887
1888 assert!(account.can_trade);
1889 assert!(account.can_withdraw);
1890 assert!(account.can_deposit);
1891 assert_eq!(account.balances.len(), 2);
1892 assert_eq!(account.balances[0].asset, "BTC");
1893 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
1894 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
1895 assert_eq!(account.balances[1].asset, "USDT");
1896 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
1897 }
1898
1899 #[rstest]
1900 fn test_decode_account_empty_balances() {
1901 let header = create_header(
1902 ACCOUNT_BLOCK_LENGTH as u16,
1903 ACCOUNT_TEMPLATE_ID,
1904 SBE_SCHEMA_ID,
1905 SBE_SCHEMA_VERSION,
1906 );
1907
1908 let mut buf = Vec::new();
1909 buf.extend_from_slice(&header);
1910
1911 buf.push((-8i8) as u8);
1913 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
1917
1918 let account = decode_account(&buf).unwrap();
1919 assert!(account.balances.is_empty());
1920 }
1921
1922 #[rstest]
1923 fn test_decode_account_trades_multiple() {
1924 let header = create_header(
1925 0,
1926 ACCOUNT_TRADES_TEMPLATE_ID,
1927 SBE_SCHEMA_ID,
1928 SBE_SCHEMA_VERSION,
1929 );
1930
1931 let mut buf = Vec::new();
1932 buf.extend_from_slice(&header);
1933
1934 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
1936
1937 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");
1953 write_var_string(&mut buf, "BNB");
1954
1955 buf.push((-8i8) as u8);
1957 buf.push((-8i8) as u8);
1958 buf.push((-8i8) as u8);
1959 buf.extend_from_slice(&1002i64.to_le_bytes());
1960 buf.extend_from_slice(&5002i64.to_le_bytes());
1961 buf.extend_from_slice(&i64::MIN.to_le_bytes());
1962 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
1963 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
1964 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
1965 buf.extend_from_slice(&50_000i64.to_le_bytes());
1966 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
1967 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
1971 write_var_string(&mut buf, "USDT");
1972
1973 let trades = decode_account_trades(&buf).unwrap();
1974
1975 assert_eq!(trades.len(), 2);
1976 assert_eq!(trades[0].id, 1001);
1977 assert_eq!(trades[0].order_id, 5001);
1978 assert!(trades[0].order_list_id.is_none());
1979 assert_eq!(trades[0].symbol, "BTCUSDT");
1980 assert_eq!(trades[0].commission_asset, "BNB");
1981 assert!(trades[0].is_buyer);
1982 assert!(!trades[0].is_maker);
1983
1984 assert_eq!(trades[1].id, 1002);
1985 assert_eq!(trades[1].symbol, "ETHUSDT");
1986 assert_eq!(trades[1].commission_asset, "USDT");
1987 assert!(!trades[1].is_buyer);
1988 assert!(trades[1].is_maker);
1989 }
1990
1991 #[rstest]
1992 fn test_decode_account_trades_empty() {
1993 let header = create_header(
1994 0,
1995 ACCOUNT_TRADES_TEMPLATE_ID,
1996 SBE_SCHEMA_ID,
1997 SBE_SCHEMA_VERSION,
1998 );
1999
2000 let mut buf = Vec::new();
2001 buf.extend_from_slice(&header);
2002 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
2003
2004 let trades = decode_account_trades(&buf).unwrap();
2005 assert!(trades.is_empty());
2006 }
2007
2008 #[rstest]
2009 fn test_decode_exchange_info_single_symbol() {
2010 let header = create_header(
2011 0,
2012 EXCHANGE_INFO_TEMPLATE_ID,
2013 SBE_SCHEMA_ID,
2014 SBE_SCHEMA_VERSION,
2015 );
2016
2017 let mut buf = Vec::new();
2018 buf.extend_from_slice(&header);
2019
2020 buf.extend_from_slice(&create_group_header(11, 0));
2022
2023 buf.extend_from_slice(&create_group_header(0, 0));
2025
2026 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
2028
2029 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));
2051
2052 buf.extend_from_slice(&create_group_header(0, 1));
2054 buf.extend_from_slice(&create_group_header(0, 1));
2055 write_var_string(&mut buf, "SPOT");
2056
2057 write_var_string(&mut buf, "BTCUSDT");
2059 write_var_string(&mut buf, "BTC");
2060 write_var_string(&mut buf, "USDT");
2061
2062 let info = decode_exchange_info(&buf).unwrap();
2063
2064 assert_eq!(info.symbols.len(), 1);
2065 let symbol = &info.symbols[0];
2066 assert_eq!(symbol.symbol, "BTCUSDT");
2067 assert_eq!(symbol.base_asset, "BTC");
2068 assert_eq!(symbol.quote_asset, "USDT");
2069 assert_eq!(symbol.base_asset_precision, 8);
2070 assert_eq!(symbol.quote_asset_precision, 8);
2071 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
2073 assert!(symbol.iceberg_allowed);
2074 assert!(symbol.oco_allowed);
2075 assert!(!symbol.oto_allowed);
2076 assert!(symbol.quote_order_qty_market_allowed);
2077 assert!(symbol.allow_trailing_stop);
2078 assert!(symbol.cancel_replace_allowed);
2079 assert!(!symbol.amend_allowed);
2080 assert!(symbol.is_spot_trading_allowed);
2081 assert!(!symbol.is_margin_trading_allowed);
2082 assert!(symbol.filters.price_filter.is_none()); assert!(symbol.filters.lot_size_filter.is_none());
2084 assert_eq!(symbol.permissions.len(), 1);
2085 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
2086 }
2087
2088 #[rstest]
2089 fn test_decode_exchange_info_empty() {
2090 let header = create_header(
2091 0,
2092 EXCHANGE_INFO_TEMPLATE_ID,
2093 SBE_SCHEMA_ID,
2094 SBE_SCHEMA_VERSION,
2095 );
2096
2097 let mut buf = Vec::new();
2098 buf.extend_from_slice(&header);
2099
2100 buf.extend_from_slice(&create_group_header(11, 0));
2102
2103 buf.extend_from_slice(&create_group_header(0, 0));
2105
2106 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
2108
2109 let info = decode_exchange_info(&buf).unwrap();
2110 assert!(info.symbols.is_empty());
2111 }
2112
2113 #[rstest]
2114 fn test_decode_exchange_info_wrong_template() {
2115 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2116
2117 let mut buf = Vec::new();
2118 buf.extend_from_slice(&header);
2119
2120 let err = decode_exchange_info(&buf).unwrap_err();
2121 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
2122 }
2123
2124 #[rstest]
2125 fn test_decode_exchange_info_multiple_symbols() {
2126 let header = create_header(
2127 0,
2128 EXCHANGE_INFO_TEMPLATE_ID,
2129 SBE_SCHEMA_ID,
2130 SBE_SCHEMA_VERSION,
2131 );
2132
2133 let mut buf = Vec::new();
2134 buf.extend_from_slice(&header);
2135
2136 buf.extend_from_slice(&create_group_header(11, 0));
2138
2139 buf.extend_from_slice(&create_group_header(0, 0));
2141
2142 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
2144
2145 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");
2167 write_var_string(&mut buf, "BTC");
2168 write_var_string(&mut buf, "USDT");
2169
2170 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");
2192 write_var_string(&mut buf, "ETH");
2193 write_var_string(&mut buf, "USDT");
2194
2195 let info = decode_exchange_info(&buf).unwrap();
2196
2197 assert_eq!(info.symbols.len(), 2);
2198 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
2199 assert_eq!(info.symbols[0].base_asset, "BTC");
2200 assert!(!info.symbols[0].is_margin_trading_allowed);
2201
2202 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
2203 assert_eq!(info.symbols[1].base_asset, "ETH");
2204 assert!(info.symbols[1].is_margin_trading_allowed);
2205 }
2206
2207 #[rstest]
2208 fn test_decode_klines_valid() {
2209 let header = create_header(2, KLINES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2210
2211 let mut buf = Vec::new();
2212 buf.extend_from_slice(&header);
2213 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&create_group_header(KLINES_BLOCK_LENGTH, 1));
2216 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();
2229
2230 assert_eq!(klines.price_exponent, -2);
2231 assert_eq!(klines.qty_exponent, -4);
2232 assert_eq!(klines.klines.len(), 1);
2233 assert_eq!(klines.klines[0].open_time, 1_700_000_000_000_000);
2234 assert_eq!(klines.klines[0].close_price, 12_345);
2235 assert_eq!(i128::from_le_bytes(klines.klines[0].volume), 1_234_500);
2236 assert_eq!(klines.klines[0].num_trades, 100);
2237 }
2238
2239 #[rstest]
2240 fn test_decode_new_order_full_valid() {
2241 let header = create_header(
2242 NEW_ORDER_FULL_BLOCK_LENGTH as u16,
2243 NEW_ORDER_FULL_TEMPLATE_ID,
2244 SBE_SCHEMA_ID,
2245 SBE_SCHEMA_VERSION,
2246 );
2247
2248 let mut buf = Vec::new();
2249 buf.extend_from_slice(&header);
2250
2251 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));
2274 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");
2282
2283 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2285 write_var_string(&mut buf, "client-123");
2286
2287 let response = decode_new_order_full(&buf).unwrap();
2288
2289 assert_eq!(response.order_id, 12345);
2290 assert_eq!(response.order_list_id, Some(99));
2291 assert_eq!(response.transact_time, 1_700_000_000_000_000);
2292 assert_eq!(response.price_mantissa, 12_345);
2293 assert_eq!(response.orig_qty_mantissa, 25_000);
2294 assert_eq!(response.executed_qty_mantissa, 10_000);
2295 assert_eq!(response.stop_price_mantissa, Some(12_000));
2296 assert_eq!(response.working_time, Some(1_700_000_000_000_500));
2297 assert_eq!(response.symbol, "ETHUSDT");
2298 assert_eq!(response.client_order_id, "client-123");
2299 assert_eq!(response.fills.len(), 1);
2300 assert_eq!(response.fills[0].price_mantissa, 12_345);
2301 assert_eq!(response.fills[0].qty_mantissa, 10_000);
2302 assert_eq!(response.fills[0].trade_id, Some(555));
2303 assert_eq!(response.fills[0].commission_asset, "USDT");
2304 }
2305
2306 fn build_new_order_full_v3_buffer(expiry_reason_byte: u8) -> Vec<u8> {
2309 const V3_BLOCK_LENGTH: u16 = 154;
2310 let header = create_header(
2311 V3_BLOCK_LENGTH,
2312 NEW_ORDER_FULL_TEMPLATE_ID,
2313 SBE_SCHEMA_ID,
2314 SBE_SCHEMA_VERSION,
2315 );
2316
2317 let mut buf = Vec::new();
2318 buf.extend_from_slice(&header);
2319
2320 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));
2344 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");
2352
2353 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2355 write_var_string(&mut buf, "client-456");
2356 buf
2357 }
2358
2359 #[rstest]
2360 fn test_decode_new_order_full_v3_block_length() {
2361 let buf = build_new_order_full_v3_buffer(0xFF);
2364
2365 let response = decode_new_order_full(&buf).unwrap();
2366
2367 assert_eq!(response.order_id, 12345);
2368 assert_eq!(response.symbol, "ETHUSDT");
2369 assert_eq!(response.client_order_id, "client-456");
2370 assert_eq!(response.fills.len(), 1);
2371 assert_eq!(response.fills[0].price_mantissa, 12_345);
2372 assert!(response.expiry_reason.is_none());
2373 }
2374
2375 #[rstest]
2376 fn test_decode_new_order_full_v3_captures_expiry_reason_value() {
2377 let buf = build_new_order_full_v3_buffer(0x05);
2380
2381 let response = decode_new_order_full(&buf).unwrap();
2382
2383 assert_eq!(response.expiry_reason, Some(0x05));
2384 assert_eq!(response.symbol, "ETHUSDT");
2387 assert_eq!(response.client_order_id, "client-456");
2388 }
2389
2390 #[rstest]
2391 fn test_decode_cancel_open_orders_valid() {
2392 let header = create_header(
2393 0,
2394 CANCEL_OPEN_ORDERS_TEMPLATE_ID,
2395 SBE_SCHEMA_ID,
2396 SBE_SCHEMA_VERSION,
2397 );
2398 let response_one = create_cancel_order_response_buffer(111, "ETHUSDT", "orig-1", "new-1");
2399 let response_two = create_cancel_order_response_buffer(222, "BTCUSDT", "orig-2", "new-2");
2400
2401 let mut buf = Vec::new();
2402 buf.extend_from_slice(&header);
2403 buf.extend_from_slice(&create_group_header(0, 2));
2404 buf.extend_from_slice(&(response_one.len() as u16).to_le_bytes());
2405 buf.extend_from_slice(&response_one);
2406 buf.extend_from_slice(&(response_two.len() as u16).to_le_bytes());
2407 buf.extend_from_slice(&response_two);
2408
2409 let responses = decode_cancel_open_orders(&buf).unwrap();
2410
2411 assert_eq!(responses.len(), 2);
2412 assert_eq!(responses[0].order_id, 111);
2413 assert_eq!(responses[0].symbol, "ETHUSDT");
2414 assert_eq!(responses[0].orig_client_order_id, "orig-1");
2415 assert_eq!(responses[0].client_order_id, "new-1");
2416 assert_eq!(responses[1].order_id, 222);
2417 assert_eq!(responses[1].symbol, "BTCUSDT");
2418 assert_eq!(responses[1].orig_client_order_id, "orig-2");
2419 assert_eq!(responses[1].client_order_id, "new-2");
2420 }
2421
2422 fn create_cancel_order_response_buffer(
2423 order_id: i64,
2424 symbol: &str,
2425 orig_client_order_id: &str,
2426 client_order_id: &str,
2427 ) -> Vec<u8> {
2428 let header = create_header(
2429 CANCEL_ORDER_BLOCK_LENGTH as u16,
2430 CANCEL_ORDER_TEMPLATE_ID,
2431 SBE_SCHEMA_ID,
2432 SBE_SCHEMA_VERSION,
2433 );
2434
2435 let mut buf = Vec::new();
2436 buf.extend_from_slice(&header);
2437 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&order_id.to_le_bytes());
2440 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 {
2453 buf.push(0);
2454 }
2455
2456 write_var_string(&mut buf, symbol);
2457 write_var_string(&mut buf, orig_client_order_id);
2458 write_var_string(&mut buf, client_order_id);
2459
2460 buf
2461 }
2462}