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::common::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 #[allow(dead_code)]
58 block_length: u16,
59 template_id: u16,
60 schema_id: u16,
61 version: u16,
62}
63
64impl MessageHeader {
65 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
67 cursor.require(HEADER_LENGTH)?;
68 Ok(Self {
69 block_length: cursor.read_u16_le()?,
70 template_id: cursor.read_u16_le()?,
71 schema_id: cursor.read_u16_le()?,
72 version: cursor.read_u16_le()?,
73 })
74 }
75
76 fn validate(&self) -> Result<(), SbeDecodeError> {
78 if self.schema_id != SBE_SCHEMA_ID {
79 return Err(SbeDecodeError::SchemaMismatch {
80 expected: SBE_SCHEMA_ID,
81 actual: self.schema_id,
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_BLOCK_LENGTH: usize = 153;
303
304const CANCEL_ORDER_BLOCK_LENGTH: usize = 137;
306
307const ORDER_BLOCK_LENGTH: usize = 153;
309
310#[allow(dead_code)]
316pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
317 let mut cursor = SbeCursor::new(buf);
318 let header = MessageHeader::decode_cursor(&mut cursor)?;
319 header.validate()?;
320
321 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
322 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
323 }
324
325 cursor.require(NEW_ORDER_FULL_BLOCK_LENGTH)?;
326
327 let price_exponent = cursor.read_i8()?;
328 let qty_exponent = cursor.read_i8()?;
329 let order_id = cursor.read_i64_le()?;
330 let order_list_id = cursor.read_optional_i64_le()?;
331 let transact_time = cursor.read_i64_le()?;
332 let price_mantissa = cursor.read_i64_le()?;
333 let orig_qty_mantissa = cursor.read_i64_le()?;
334 let executed_qty_mantissa = cursor.read_i64_le()?;
335 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
336 let status = cursor.read_u8()?.into();
337 let time_in_force = cursor.read_u8()?.into();
338 let order_type = cursor.read_u8()?.into();
339 let side = cursor.read_u8()?.into();
340 let stop_price_mantissa = cursor.read_optional_i64_le()?;
341
342 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
344
345 cursor.advance(23)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
347
348 cursor.advance(16)?; let _commission_exponent = cursor.read_i8()?;
350
351 cursor.advance(18)?; let fills = decode_fills_cursor(&mut cursor)?;
354
355 let (block_len, count) = cursor.read_group_header()?;
357 cursor.advance(block_len as usize * count as usize)?;
358
359 let symbol = cursor.read_var_string8()?;
360 let client_order_id = cursor.read_var_string8()?;
361
362 Ok(BinanceNewOrderResponse {
363 price_exponent,
364 qty_exponent,
365 order_id,
366 order_list_id,
367 transact_time,
368 price_mantissa,
369 orig_qty_mantissa,
370 executed_qty_mantissa,
371 cummulative_quote_qty_mantissa,
372 status,
373 time_in_force,
374 order_type,
375 side,
376 stop_price_mantissa,
377 working_time,
378 self_trade_prevention_mode,
379 client_order_id,
380 symbol,
381 fills,
382 })
383}
384
385#[allow(dead_code)]
391pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
392 let mut cursor = SbeCursor::new(buf);
393 let header = MessageHeader::decode_cursor(&mut cursor)?;
394 header.validate()?;
395
396 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
397 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
398 }
399
400 cursor.require(CANCEL_ORDER_BLOCK_LENGTH)?;
401
402 let price_exponent = cursor.read_i8()?;
403 let qty_exponent = cursor.read_i8()?;
404 let order_id = cursor.read_i64_le()?;
405 let order_list_id = cursor.read_optional_i64_le()?;
406 let transact_time = cursor.read_i64_le()?;
407 let price_mantissa = cursor.read_i64_le()?;
408 let orig_qty_mantissa = cursor.read_i64_le()?;
409 let executed_qty_mantissa = cursor.read_i64_le()?;
410 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
411 let status = cursor.read_u8()?.into();
412 let time_in_force = cursor.read_u8()?.into();
413 let order_type = cursor.read_u8()?.into();
414 let side = cursor.read_u8()?.into();
415 let self_trade_prevention_mode = cursor.read_u8()?.into();
416
417 cursor.advance(CANCEL_ORDER_BLOCK_LENGTH - 63)?; let symbol = cursor.read_var_string8()?;
420 let orig_client_order_id = cursor.read_var_string8()?;
421 let client_order_id = cursor.read_var_string8()?;
422
423 Ok(BinanceCancelOrderResponse {
424 price_exponent,
425 qty_exponent,
426 order_id,
427 order_list_id,
428 transact_time,
429 price_mantissa,
430 orig_qty_mantissa,
431 executed_qty_mantissa,
432 cummulative_quote_qty_mantissa,
433 status,
434 time_in_force,
435 order_type,
436 side,
437 self_trade_prevention_mode,
438 client_order_id,
439 orig_client_order_id,
440 symbol,
441 })
442}
443
444#[allow(dead_code)]
450pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
451 let mut cursor = SbeCursor::new(buf);
452 let header = MessageHeader::decode_cursor(&mut cursor)?;
453 header.validate()?;
454
455 if header.template_id != ORDER_TEMPLATE_ID {
456 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
457 }
458
459 cursor.require(ORDER_BLOCK_LENGTH)?;
460
461 let price_exponent = cursor.read_i8()?;
462 let qty_exponent = cursor.read_i8()?;
463 let order_id = cursor.read_i64_le()?;
464 let order_list_id = cursor.read_optional_i64_le()?;
465 let price_mantissa = cursor.read_i64_le()?;
466 let orig_qty_mantissa = cursor.read_i64_le()?;
467 let executed_qty_mantissa = cursor.read_i64_le()?;
468 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
469 let status = cursor.read_u8()?.into();
470 let time_in_force = cursor.read_u8()?.into();
471 let order_type = cursor.read_u8()?.into();
472 let side = cursor.read_u8()?.into();
473 let stop_price_mantissa = cursor.read_optional_i64_le()?;
474 let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
475 let time = cursor.read_i64_le()?;
476 let update_time = cursor.read_i64_le()?;
477 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
478 let working_time = cursor.read_optional_i64_le()?;
479 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
480 let self_trade_prevention_mode = cursor.read_u8()?.into();
481
482 cursor.advance(ORDER_BLOCK_LENGTH - 104)?; let symbol = cursor.read_var_string8()?;
485 let client_order_id = cursor.read_var_string8()?;
486
487 Ok(BinanceOrderResponse {
488 price_exponent,
489 qty_exponent,
490 order_id,
491 order_list_id,
492 price_mantissa,
493 orig_qty_mantissa,
494 executed_qty_mantissa,
495 cummulative_quote_qty_mantissa,
496 status,
497 time_in_force,
498 order_type,
499 side,
500 stop_price_mantissa,
501 iceberg_qty_mantissa,
502 time,
503 update_time,
504 is_working,
505 working_time,
506 orig_quote_order_qty_mantissa,
507 self_trade_prevention_mode,
508 client_order_id,
509 symbol,
510 })
511}
512
513const ORDERS_GROUP_BLOCK_LENGTH: usize = 162;
515
516#[allow(dead_code)]
522pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
523 let mut cursor = SbeCursor::new(buf);
524 let header = MessageHeader::decode_cursor(&mut cursor)?;
525 header.validate()?;
526
527 if header.template_id != ORDERS_TEMPLATE_ID {
528 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
529 }
530
531 let (block_length, count) = cursor.read_group_header()?;
532
533 if count == 0 {
534 return Ok(Vec::new());
535 }
536
537 if block_length as usize != ORDERS_GROUP_BLOCK_LENGTH {
538 return Err(SbeDecodeError::InvalidBlockLength {
539 expected: ORDERS_GROUP_BLOCK_LENGTH as u16,
540 actual: block_length,
541 });
542 }
543
544 let mut orders = Vec::with_capacity(count as usize);
545
546 for _ in 0..count {
547 cursor.require(ORDERS_GROUP_BLOCK_LENGTH)?;
548
549 let price_exponent = cursor.read_i8()?;
550 let qty_exponent = cursor.read_i8()?;
551 let order_id = cursor.read_i64_le()?;
552 let order_list_id = cursor.read_optional_i64_le()?;
553 let price_mantissa = cursor.read_i64_le()?;
554 let orig_qty_mantissa = cursor.read_i64_le()?;
555 let executed_qty_mantissa = cursor.read_i64_le()?;
556 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
557 let status = cursor.read_u8()?.into();
558 let time_in_force = cursor.read_u8()?.into();
559 let order_type = cursor.read_u8()?.into();
560 let side = cursor.read_u8()?.into();
561 let stop_price_mantissa = cursor.read_optional_i64_le()?;
562
563 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
565 let time = cursor.read_i64_le()?;
566 let update_time = cursor.read_i64_le()?;
567 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
568 let working_time = cursor.read_optional_i64_le()?;
569 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
570
571 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
573
574 cursor.advance(28)?; let symbol = cursor.read_var_string8()?;
577 let client_order_id = cursor.read_var_string8()?;
578
579 orders.push(BinanceOrderResponse {
580 price_exponent,
581 qty_exponent,
582 order_id,
583 order_list_id,
584 price_mantissa,
585 orig_qty_mantissa,
586 executed_qty_mantissa,
587 cummulative_quote_qty_mantissa,
588 status,
589 time_in_force,
590 order_type,
591 side,
592 stop_price_mantissa,
593 iceberg_qty_mantissa,
594 time,
595 update_time,
596 is_working,
597 working_time,
598 orig_quote_order_qty_mantissa,
599 self_trade_prevention_mode,
600 client_order_id,
601 symbol,
602 });
603 }
604
605 Ok(orders)
606}
607
608#[allow(dead_code)]
616pub fn decode_cancel_open_orders(
617 buf: &[u8],
618) -> Result<Vec<BinanceCancelOrderResponse>, SbeDecodeError> {
619 let mut cursor = SbeCursor::new(buf);
620 let header = MessageHeader::decode_cursor(&mut cursor)?;
621 header.validate()?;
622
623 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
624 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
625 }
626
627 let (_block_length, count) = cursor.read_group_header()?;
628
629 if count == 0 {
630 return Ok(Vec::new());
631 }
632
633 let mut responses = Vec::with_capacity(count as usize);
634
635 for _ in 0..count {
637 let response_len = cursor.read_u16_le()? as usize;
638 let embedded_bytes = cursor.read_bytes(response_len)?;
639 let cancel_response = decode_cancel_order(embedded_bytes)?;
640 responses.push(cancel_response);
641 }
642
643 Ok(responses)
644}
645
646const ACCOUNT_BLOCK_LENGTH: usize = 64;
648
649const BALANCE_BLOCK_LENGTH: u16 = 17;
651
652#[allow(dead_code)]
658pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
659 let mut cursor = SbeCursor::new(buf);
660 let header = MessageHeader::decode_cursor(&mut cursor)?;
661 header.validate()?;
662
663 if header.template_id != ACCOUNT_TEMPLATE_ID {
664 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
665 }
666
667 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
668
669 let commission_exponent = cursor.read_i8()?;
670 let maker_commission_mantissa = cursor.read_i64_le()?;
671 let taker_commission_mantissa = cursor.read_i64_le()?;
672 let buyer_commission_mantissa = cursor.read_i64_le()?;
673 let seller_commission_mantissa = cursor.read_i64_le()?;
674 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
675 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
676 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
677 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
679 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
680 let update_time = cursor.read_i64_le()?;
681 let account_type_enum = AccountType::from(cursor.read_u8()?);
682 cursor.advance(16)?; let account_type = account_type_enum.to_string();
685
686 let (block_length, balance_count) = cursor.read_group_header()?;
687
688 if block_length != BALANCE_BLOCK_LENGTH {
689 return Err(SbeDecodeError::InvalidBlockLength {
690 expected: BALANCE_BLOCK_LENGTH,
691 actual: block_length,
692 });
693 }
694
695 let mut balances = Vec::with_capacity(balance_count as usize);
696
697 for _ in 0..balance_count {
698 cursor.require(block_length as usize)?;
699
700 let exponent = cursor.read_i8()?;
701 let free_mantissa = cursor.read_i64_le()?;
702 let locked_mantissa = cursor.read_i64_le()?;
703
704 let asset = cursor.read_var_string8()?;
705
706 balances.push(BinanceBalance {
707 asset,
708 free_mantissa,
709 locked_mantissa,
710 exponent,
711 });
712 }
713
714 Ok(BinanceAccountInfo {
715 commission_exponent,
716 maker_commission_mantissa,
717 taker_commission_mantissa,
718 buyer_commission_mantissa,
719 seller_commission_mantissa,
720 can_trade,
721 can_withdraw,
722 can_deposit,
723 require_self_trade_prevention,
724 prevent_sor,
725 update_time,
726 account_type,
727 balances,
728 })
729}
730
731const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
733
734#[allow(dead_code)]
740pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
741 let mut cursor = SbeCursor::new(buf);
742 let header = MessageHeader::decode_cursor(&mut cursor)?;
743 header.validate()?;
744
745 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
746 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
747 }
748
749 let (block_length, trade_count) = cursor.read_group_header()?;
750
751 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
752 return Err(SbeDecodeError::InvalidBlockLength {
753 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
754 actual: block_length,
755 });
756 }
757
758 let mut trades = Vec::with_capacity(trade_count as usize);
759
760 for _ in 0..trade_count {
761 cursor.require(block_length as usize)?;
762
763 let price_exponent = cursor.read_i8()?;
764 let qty_exponent = cursor.read_i8()?;
765 let commission_exponent = cursor.read_i8()?;
766 let id = cursor.read_i64_le()?;
767 let order_id = cursor.read_i64_le()?;
768 let order_list_id = cursor.read_optional_i64_le()?;
769 let price_mantissa = cursor.read_i64_le()?;
770 let qty_mantissa = cursor.read_i64_le()?;
771 let quote_qty_mantissa = cursor.read_i64_le()?;
772 let commission_mantissa = cursor.read_i64_le()?;
773 let time = cursor.read_i64_le()?;
774 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
775 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
776 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
777
778 let symbol = cursor.read_var_string8()?;
779 let commission_asset = cursor.read_var_string8()?;
780
781 trades.push(BinanceAccountTrade {
782 price_exponent,
783 qty_exponent,
784 commission_exponent,
785 id,
786 order_id,
787 order_list_id,
788 price_mantissa,
789 qty_mantissa,
790 quote_qty_mantissa,
791 commission_mantissa,
792 time,
793 is_buyer,
794 is_maker,
795 is_best_match,
796 symbol,
797 commission_asset,
798 });
799 }
800
801 Ok(trades)
802}
803
804const FILLS_BLOCK_LENGTH: u16 = 42;
806
807fn decode_fills_cursor(
809 cursor: &mut SbeCursor<'_>,
810) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
811 let (block_length, count) = cursor.read_group_header()?;
812
813 if block_length != FILLS_BLOCK_LENGTH {
814 return Err(SbeDecodeError::InvalidBlockLength {
815 expected: FILLS_BLOCK_LENGTH,
816 actual: block_length,
817 });
818 }
819
820 let mut fills = Vec::with_capacity(count as usize);
821
822 for _ in 0..count {
823 cursor.require(block_length as usize)?;
824
825 let commission_exponent = cursor.read_i8()?;
826 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
828 let qty_mantissa = cursor.read_i64_le()?;
829 let commission_mantissa = cursor.read_i64_le()?;
830 let trade_id = cursor.read_optional_i64_le()?;
831 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
834
835 fills.push(BinanceOrderFill {
836 price_mantissa,
837 qty_mantissa,
838 commission_mantissa,
839 commission_exponent,
840 commission_asset,
841 trade_id,
842 });
843 }
844
845 Ok(fills)
846}
847
848const SYMBOL_BLOCK_LENGTH: usize = 19;
850
851pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
865 let mut cursor = SbeCursor::new(buf);
866 let header = MessageHeader::decode_cursor(&mut cursor)?;
867 header.validate()?;
868
869 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
870 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
871 }
872
873 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
875 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
876
877 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
879 for _ in 0..exchange_filters_count {
880 cursor.read_var_string8()?;
882 }
883
884 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
886
887 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
888 return Err(SbeDecodeError::InvalidBlockLength {
889 expected: SYMBOL_BLOCK_LENGTH as u16,
890 actual: symbols_block_len,
891 });
892 }
893
894 let mut symbols = Vec::with_capacity(symbols_count as usize);
895
896 for _ in 0..symbols_count {
897 cursor.require(SYMBOL_BLOCK_LENGTH)?;
898
899 let status = cursor.read_u8()?;
901 let base_asset_precision = cursor.read_u8()?;
902 let quote_asset_precision = cursor.read_u8()?;
903 let _base_commission_precision = cursor.read_u8()?;
904 let _quote_commission_precision = cursor.read_u8()?;
905 let order_types = cursor.read_u16_le()?;
906 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
907 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
908 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
909 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
910 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
911 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
912 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
913 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
914 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
915 let _default_self_trade_prevention_mode = cursor.read_u8()?;
916 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
917 let _peg_instructions_allowed = cursor.read_u8()?;
918
919 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
920 let mut filters = BinanceSymbolFiltersSbe::default();
921
922 for _ in 0..filters_count {
923 let filter_bytes = cursor.read_var_bytes8()?;
924
925 let (template_id, offset) = if filter_bytes.len() >= HEADER_LENGTH + 2 {
928 let potential_template = u16::from_le_bytes([filter_bytes[2], filter_bytes[3]]);
929 if potential_template == PRICE_FILTER_TEMPLATE_ID
930 || potential_template == LOT_SIZE_FILTER_TEMPLATE_ID
931 {
932 (potential_template, HEADER_LENGTH)
933 } else {
934 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
935 (raw_template, 2)
936 }
937 } else if filter_bytes.len() >= 2 {
938 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
939 (raw_template, 2)
940 } else {
941 continue;
942 };
943
944 match template_id {
946 PRICE_FILTER_TEMPLATE_ID => {
947 if filter_bytes.len() >= offset + 25 {
948 let price_exp = filter_bytes[offset] as i8;
949 let min_price = i64::from_le_bytes(
950 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
951 );
952 let max_price = i64::from_le_bytes(
953 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
954 );
955 let tick_size = i64::from_le_bytes(
956 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
957 );
958 filters.price_filter = Some(BinancePriceFilterSbe {
959 price_exponent: price_exp,
960 min_price,
961 max_price,
962 tick_size,
963 });
964 }
965 }
966 LOT_SIZE_FILTER_TEMPLATE_ID => {
967 if filter_bytes.len() >= offset + 25 {
968 let qty_exp = filter_bytes[offset] as i8;
969 let min_qty = i64::from_le_bytes(
970 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
971 );
972 let max_qty = i64::from_le_bytes(
973 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
974 );
975 let step_size = i64::from_le_bytes(
976 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
977 );
978 filters.lot_size_filter = Some(BinanceLotSizeFilterSbe {
979 qty_exponent: qty_exp,
980 min_qty,
981 max_qty,
982 step_size,
983 });
984 }
985 }
986 _ => {}
987 }
988 }
989
990 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
992 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
993 for _ in 0..perm_sets_count {
994 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
996 let mut perm_set = Vec::with_capacity(perms_count as usize);
997 for _ in 0..perms_count {
998 let perm = cursor.read_var_string8()?;
999 perm_set.push(perm);
1000 }
1001 permissions.push(perm_set);
1002 }
1003
1004 let symbol = cursor.read_var_string8()?;
1006 let base_asset = cursor.read_var_string8()?;
1007 let quote_asset = cursor.read_var_string8()?;
1008
1009 symbols.push(BinanceSymbolSbe {
1010 symbol,
1011 base_asset,
1012 quote_asset,
1013 base_asset_precision,
1014 quote_asset_precision,
1015 status,
1016 order_types,
1017 iceberg_allowed,
1018 oco_allowed,
1019 oto_allowed,
1020 quote_order_qty_market_allowed,
1021 allow_trailing_stop,
1022 cancel_replace_allowed,
1023 amend_allowed,
1024 is_spot_trading_allowed,
1025 is_margin_trading_allowed,
1026 filters,
1027 permissions,
1028 });
1029 }
1030
1031 Ok(BinanceExchangeInfoSbe { symbols })
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038 use rstest::rstest;
1039
1040 use super::*;
1041
1042 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
1043 let mut buf = [0u8; 8];
1044 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1045 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
1046 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
1047 buf[6..8].copy_from_slice(&version.to_le_bytes());
1048 buf
1049 }
1050
1051 #[rstest]
1052 fn test_decode_ping_valid() {
1053 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1055 assert!(decode_ping(&buf).is_ok());
1056 }
1057
1058 #[rstest]
1059 fn test_decode_ping_buffer_too_short() {
1060 let buf = [0u8; 4];
1061 let err = decode_ping(&buf).unwrap_err();
1062 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1063 }
1064
1065 #[rstest]
1066 fn test_decode_ping_schema_mismatch() {
1067 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
1068 let err = decode_ping(&buf).unwrap_err();
1069 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
1070 }
1071
1072 #[rstest]
1073 fn test_decode_ping_wrong_template() {
1074 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1075 let err = decode_ping(&buf).unwrap_err();
1076 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
1077 }
1078
1079 #[rstest]
1080 fn test_decode_server_time_valid() {
1081 let header = create_header(
1083 8,
1084 SERVER_TIME_TEMPLATE_ID,
1085 SBE_SCHEMA_ID,
1086 SBE_SCHEMA_VERSION,
1087 );
1088 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
1091 buf.extend_from_slice(&header);
1092 buf.extend_from_slice(×tamp.to_le_bytes());
1093
1094 let result = decode_server_time(&buf).unwrap();
1095 assert_eq!(result, timestamp);
1096 }
1097
1098 #[rstest]
1099 fn test_decode_server_time_buffer_too_short() {
1100 let buf = create_header(
1102 8,
1103 SERVER_TIME_TEMPLATE_ID,
1104 SBE_SCHEMA_ID,
1105 SBE_SCHEMA_VERSION,
1106 );
1107 let err = decode_server_time(&buf).unwrap_err();
1108 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1109 }
1110
1111 #[rstest]
1112 fn test_decode_server_time_wrong_template() {
1113 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1114 let mut buf = Vec::with_capacity(16);
1115 buf.extend_from_slice(&header);
1116 buf.extend_from_slice(&0i64.to_le_bytes());
1117
1118 let err = decode_server_time(&buf).unwrap_err();
1119 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1120 }
1121
1122 #[rstest]
1123 fn test_decode_server_time_version_mismatch() {
1124 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, 99);
1125 let mut buf = Vec::with_capacity(16);
1126 buf.extend_from_slice(&header);
1127 buf.extend_from_slice(&0i64.to_le_bytes());
1128
1129 let err = decode_server_time(&buf).unwrap_err();
1130 assert!(matches!(err, SbeDecodeError::VersionMismatch { .. }));
1131 }
1132
1133 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
1134 let mut buf = [0u8; 6];
1135 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1136 buf[2..6].copy_from_slice(&count.to_le_bytes());
1137 buf
1138 }
1139
1140 #[rstest]
1141 fn test_decode_depth_valid() {
1142 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1144
1145 let mut buf = Vec::new();
1146 buf.extend_from_slice(&header);
1147
1148 let last_update_id: i64 = 123456789;
1150 let price_exponent: i8 = -8;
1151 let qty_exponent: i8 = -8;
1152 buf.extend_from_slice(&last_update_id.to_le_bytes());
1153 buf.push(price_exponent as u8);
1154 buf.push(qty_exponent as u8);
1155
1156 buf.extend_from_slice(&create_group_header(16, 2));
1158 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1160 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1161 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1163 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1164
1165 buf.extend_from_slice(&create_group_header(16, 1));
1167 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1169 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1170
1171 let depth = decode_depth(&buf).unwrap();
1172
1173 assert_eq!(depth.last_update_id, 123456789);
1174 assert_eq!(depth.price_exponent, -8);
1175 assert_eq!(depth.qty_exponent, -8);
1176 assert_eq!(depth.bids.len(), 2);
1177 assert_eq!(depth.asks.len(), 1);
1178 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1179 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1180 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1181 }
1182
1183 #[rstest]
1184 fn test_decode_depth_empty_book() {
1185 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1186
1187 let mut buf = Vec::new();
1188 buf.extend_from_slice(&header);
1189 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1195 buf.extend_from_slice(&create_group_header(16, 0));
1197
1198 let depth = decode_depth(&buf).unwrap();
1199
1200 assert!(depth.bids.is_empty());
1201 assert!(depth.asks.is_empty());
1202 }
1203
1204 #[rstest]
1205 fn test_decode_trades_valid() {
1206 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1208
1209 let mut buf = Vec::new();
1210 buf.extend_from_slice(&header);
1211
1212 let price_exponent: i8 = -8;
1214 let qty_exponent: i8 = -8;
1215 buf.push(price_exponent as u8);
1216 buf.push(qty_exponent as u8);
1217
1218 buf.extend_from_slice(&create_group_header(42, 1));
1220
1221 let trade_id: i64 = 999;
1223 let price: i64 = 100_000_000_000;
1224 let qty: i64 = 10_000_000;
1225 let quote_qty: i64 = 1_000_000_000_000;
1226 let time: i64 = 1734300000000;
1227 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1231 buf.extend_from_slice(&price.to_le_bytes());
1232 buf.extend_from_slice(&qty.to_le_bytes());
1233 buf.extend_from_slice("e_qty.to_le_bytes());
1234 buf.extend_from_slice(&time.to_le_bytes());
1235 buf.push(is_buyer_maker);
1236 buf.push(is_best_match);
1237
1238 let trades = decode_trades(&buf).unwrap();
1239
1240 assert_eq!(trades.price_exponent, -8);
1241 assert_eq!(trades.qty_exponent, -8);
1242 assert_eq!(trades.trades.len(), 1);
1243 assert_eq!(trades.trades[0].id, 999);
1244 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1245 assert!(trades.trades[0].is_buyer_maker);
1246 assert!(trades.trades[0].is_best_match);
1247 }
1248
1249 #[rstest]
1250 fn test_decode_trades_empty() {
1251 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1252
1253 let mut buf = Vec::new();
1254 buf.extend_from_slice(&header);
1255 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1260
1261 let trades = decode_trades(&buf).unwrap();
1262
1263 assert!(trades.trades.is_empty());
1264 }
1265
1266 #[rstest]
1267 fn test_decode_depth_wrong_template() {
1268 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1269
1270 let mut buf = Vec::new();
1271 buf.extend_from_slice(&header);
1272 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1275 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1276 }
1277
1278 #[rstest]
1279 fn test_decode_trades_wrong_template() {
1280 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1281
1282 let mut buf = Vec::new();
1283 buf.extend_from_slice(&header);
1284 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1287 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1288 }
1289
1290 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1291 buf.push(s.len() as u8);
1292 buf.extend_from_slice(s.as_bytes());
1293 }
1294
1295 #[rstest]
1296 fn test_decode_order_valid() {
1297 let header = create_header(
1298 ORDER_BLOCK_LENGTH as u16,
1299 ORDER_TEMPLATE_ID,
1300 SBE_SCHEMA_ID,
1301 SBE_SCHEMA_VERSION,
1302 );
1303
1304 let mut buf = Vec::new();
1305 buf.extend_from_slice(&header);
1306
1307 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 {
1331 buf.push(0);
1332 }
1333
1334 write_var_string(&mut buf, "BTCUSDT");
1335 write_var_string(&mut buf, "my-order-123");
1336
1337 let order = decode_order(&buf).unwrap();
1338
1339 assert_eq!(order.order_id, 12345);
1340 assert!(order.order_list_id.is_none());
1341 assert_eq!(order.price_exponent, -8);
1342 assert_eq!(order.price_mantissa, 100_000_000_000);
1343 assert!(order.stop_price_mantissa.is_none());
1344 assert!(order.iceberg_qty_mantissa.is_none());
1345 assert!(order.is_working);
1346 assert_eq!(order.working_time, Some(1734300000500));
1347 assert_eq!(order.symbol, "BTCUSDT");
1348 assert_eq!(order.client_order_id, "my-order-123");
1349 }
1350
1351 #[rstest]
1352 fn test_decode_orders_multiple() {
1353 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1355
1356 let mut buf = Vec::new();
1357 buf.extend_from_slice(&header);
1358
1359 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 2));
1361
1362 let order1_start = buf.len();
1364 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_BLOCK_LENGTH {
1387 buf.push(0);
1388 }
1389 write_var_string(&mut buf, "BTCUSDT");
1390 write_var_string(&mut buf, "order-1");
1391
1392 let order2_start = buf.len();
1394 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_BLOCK_LENGTH {
1416 buf.push(0);
1417 }
1418 write_var_string(&mut buf, "ETHUSDT");
1419 write_var_string(&mut buf, "order-2");
1420
1421 let orders = decode_orders(&buf).unwrap();
1422
1423 assert_eq!(orders.len(), 2);
1424 assert_eq!(orders[0].order_id, 1001);
1425 assert_eq!(orders[0].symbol, "BTCUSDT");
1426 assert_eq!(orders[0].client_order_id, "order-1");
1427 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
1428
1429 assert_eq!(orders[1].order_id, 2002);
1430 assert_eq!(orders[1].symbol, "ETHUSDT");
1431 assert_eq!(orders[1].client_order_id, "order-2");
1432 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
1433 }
1434
1435 #[rstest]
1436 fn test_decode_orders_empty() {
1437 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1438
1439 let mut buf = Vec::new();
1440 buf.extend_from_slice(&header);
1441 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 0));
1442
1443 let orders = decode_orders(&buf).unwrap();
1444 assert!(orders.is_empty());
1445 }
1446
1447 #[rstest]
1448 fn test_decode_orders_truncated_var_string() {
1449 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1450
1451 let mut buf = Vec::new();
1452 buf.extend_from_slice(&header);
1453 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1454
1455 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1457
1458 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
1463 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1464 }
1465
1466 #[rstest]
1467 fn test_decode_orders_invalid_utf8() {
1468 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1469
1470 let mut buf = Vec::new();
1471 buf.extend_from_slice(&header);
1472 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1473
1474 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1475
1476 buf.push(4);
1478 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
1479
1480 let err = decode_orders(&buf).unwrap_err();
1481 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
1482 }
1483
1484 #[rstest]
1485 fn test_decode_cancel_order_valid() {
1486 let header = create_header(
1487 CANCEL_ORDER_BLOCK_LENGTH as u16,
1488 CANCEL_ORDER_TEMPLATE_ID,
1489 SBE_SCHEMA_ID,
1490 SBE_SCHEMA_VERSION,
1491 );
1492
1493 let mut buf = Vec::new();
1494 buf.extend_from_slice(&header);
1495
1496 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 {
1513 buf.push(0);
1514 }
1515
1516 write_var_string(&mut buf, "BTCUSDT");
1517 write_var_string(&mut buf, "orig-client-id");
1518 write_var_string(&mut buf, "new-client-id");
1519
1520 let cancel = decode_cancel_order(&buf).unwrap();
1521
1522 assert_eq!(cancel.order_id, 99999);
1523 assert!(cancel.order_list_id.is_none());
1524 assert_eq!(cancel.symbol, "BTCUSDT");
1525 assert_eq!(cancel.orig_client_order_id, "orig-client-id");
1526 assert_eq!(cancel.client_order_id, "new-client-id");
1527 }
1528
1529 #[rstest]
1530 fn test_decode_account_with_balances() {
1531 let header = create_header(
1532 ACCOUNT_BLOCK_LENGTH as u16,
1533 ACCOUNT_TEMPLATE_ID,
1534 SBE_SCHEMA_ID,
1535 SBE_SCHEMA_VERSION,
1536 );
1537
1538 let mut buf = Vec::new();
1539 buf.extend_from_slice(&header);
1540
1541 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 {
1558 buf.push(0);
1559 }
1560
1561 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
1563
1564 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");
1569
1570 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");
1575
1576 let account = decode_account(&buf).unwrap();
1577
1578 assert!(account.can_trade);
1579 assert!(account.can_withdraw);
1580 assert!(account.can_deposit);
1581 assert_eq!(account.balances.len(), 2);
1582 assert_eq!(account.balances[0].asset, "BTC");
1583 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
1584 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
1585 assert_eq!(account.balances[1].asset, "USDT");
1586 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
1587 }
1588
1589 #[rstest]
1590 fn test_decode_account_empty_balances() {
1591 let header = create_header(
1592 ACCOUNT_BLOCK_LENGTH as u16,
1593 ACCOUNT_TEMPLATE_ID,
1594 SBE_SCHEMA_ID,
1595 SBE_SCHEMA_VERSION,
1596 );
1597
1598 let mut buf = Vec::new();
1599 buf.extend_from_slice(&header);
1600
1601 buf.push((-8i8) as u8);
1603 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
1607
1608 let account = decode_account(&buf).unwrap();
1609 assert!(account.balances.is_empty());
1610 }
1611
1612 #[rstest]
1613 fn test_decode_account_trades_multiple() {
1614 let header = create_header(
1615 0,
1616 ACCOUNT_TRADES_TEMPLATE_ID,
1617 SBE_SCHEMA_ID,
1618 SBE_SCHEMA_VERSION,
1619 );
1620
1621 let mut buf = Vec::new();
1622 buf.extend_from_slice(&header);
1623
1624 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
1626
1627 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");
1643 write_var_string(&mut buf, "BNB");
1644
1645 buf.push((-8i8) as u8);
1647 buf.push((-8i8) as u8);
1648 buf.push((-8i8) as u8);
1649 buf.extend_from_slice(&1002i64.to_le_bytes());
1650 buf.extend_from_slice(&5002i64.to_le_bytes());
1651 buf.extend_from_slice(&i64::MIN.to_le_bytes());
1652 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
1653 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
1654 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
1655 buf.extend_from_slice(&50_000i64.to_le_bytes());
1656 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
1657 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
1661 write_var_string(&mut buf, "USDT");
1662
1663 let trades = decode_account_trades(&buf).unwrap();
1664
1665 assert_eq!(trades.len(), 2);
1666 assert_eq!(trades[0].id, 1001);
1667 assert_eq!(trades[0].order_id, 5001);
1668 assert!(trades[0].order_list_id.is_none());
1669 assert_eq!(trades[0].symbol, "BTCUSDT");
1670 assert_eq!(trades[0].commission_asset, "BNB");
1671 assert!(trades[0].is_buyer);
1672 assert!(!trades[0].is_maker);
1673
1674 assert_eq!(trades[1].id, 1002);
1675 assert_eq!(trades[1].symbol, "ETHUSDT");
1676 assert_eq!(trades[1].commission_asset, "USDT");
1677 assert!(!trades[1].is_buyer);
1678 assert!(trades[1].is_maker);
1679 }
1680
1681 #[rstest]
1682 fn test_decode_account_trades_empty() {
1683 let header = create_header(
1684 0,
1685 ACCOUNT_TRADES_TEMPLATE_ID,
1686 SBE_SCHEMA_ID,
1687 SBE_SCHEMA_VERSION,
1688 );
1689
1690 let mut buf = Vec::new();
1691 buf.extend_from_slice(&header);
1692 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
1693
1694 let trades = decode_account_trades(&buf).unwrap();
1695 assert!(trades.is_empty());
1696 }
1697
1698 #[rstest]
1699 fn test_decode_exchange_info_single_symbol() {
1700 let header = create_header(
1701 0,
1702 EXCHANGE_INFO_TEMPLATE_ID,
1703 SBE_SCHEMA_ID,
1704 SBE_SCHEMA_VERSION,
1705 );
1706
1707 let mut buf = Vec::new();
1708 buf.extend_from_slice(&header);
1709
1710 buf.extend_from_slice(&create_group_header(11, 0));
1712
1713 buf.extend_from_slice(&create_group_header(0, 0));
1715
1716 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
1718
1719 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));
1741
1742 buf.extend_from_slice(&create_group_header(0, 1));
1744 buf.extend_from_slice(&create_group_header(0, 1));
1745 write_var_string(&mut buf, "SPOT");
1746
1747 write_var_string(&mut buf, "BTCUSDT");
1749 write_var_string(&mut buf, "BTC");
1750 write_var_string(&mut buf, "USDT");
1751
1752 let info = decode_exchange_info(&buf).unwrap();
1753
1754 assert_eq!(info.symbols.len(), 1);
1755 let symbol = &info.symbols[0];
1756 assert_eq!(symbol.symbol, "BTCUSDT");
1757 assert_eq!(symbol.base_asset, "BTC");
1758 assert_eq!(symbol.quote_asset, "USDT");
1759 assert_eq!(symbol.base_asset_precision, 8);
1760 assert_eq!(symbol.quote_asset_precision, 8);
1761 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
1763 assert!(symbol.iceberg_allowed);
1764 assert!(symbol.oco_allowed);
1765 assert!(!symbol.oto_allowed);
1766 assert!(symbol.quote_order_qty_market_allowed);
1767 assert!(symbol.allow_trailing_stop);
1768 assert!(symbol.cancel_replace_allowed);
1769 assert!(!symbol.amend_allowed);
1770 assert!(symbol.is_spot_trading_allowed);
1771 assert!(!symbol.is_margin_trading_allowed);
1772 assert!(symbol.filters.price_filter.is_none()); assert!(symbol.filters.lot_size_filter.is_none());
1774 assert_eq!(symbol.permissions.len(), 1);
1775 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
1776 }
1777
1778 #[rstest]
1779 fn test_decode_exchange_info_empty() {
1780 let header = create_header(
1781 0,
1782 EXCHANGE_INFO_TEMPLATE_ID,
1783 SBE_SCHEMA_ID,
1784 SBE_SCHEMA_VERSION,
1785 );
1786
1787 let mut buf = Vec::new();
1788 buf.extend_from_slice(&header);
1789
1790 buf.extend_from_slice(&create_group_header(11, 0));
1792
1793 buf.extend_from_slice(&create_group_header(0, 0));
1795
1796 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
1798
1799 let info = decode_exchange_info(&buf).unwrap();
1800 assert!(info.symbols.is_empty());
1801 }
1802
1803 #[rstest]
1804 fn test_decode_exchange_info_wrong_template() {
1805 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1806
1807 let mut buf = Vec::new();
1808 buf.extend_from_slice(&header);
1809
1810 let err = decode_exchange_info(&buf).unwrap_err();
1811 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1812 }
1813
1814 #[rstest]
1815 fn test_decode_exchange_info_multiple_symbols() {
1816 let header = create_header(
1817 0,
1818 EXCHANGE_INFO_TEMPLATE_ID,
1819 SBE_SCHEMA_ID,
1820 SBE_SCHEMA_VERSION,
1821 );
1822
1823 let mut buf = Vec::new();
1824 buf.extend_from_slice(&header);
1825
1826 buf.extend_from_slice(&create_group_header(11, 0));
1828
1829 buf.extend_from_slice(&create_group_header(0, 0));
1831
1832 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
1834
1835 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");
1857 write_var_string(&mut buf, "BTC");
1858 write_var_string(&mut buf, "USDT");
1859
1860 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");
1882 write_var_string(&mut buf, "ETH");
1883 write_var_string(&mut buf, "USDT");
1884
1885 let info = decode_exchange_info(&buf).unwrap();
1886
1887 assert_eq!(info.symbols.len(), 2);
1888 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
1889 assert_eq!(info.symbols[0].base_asset, "BTC");
1890 assert!(!info.symbols[0].is_margin_trading_allowed);
1891
1892 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
1893 assert_eq!(info.symbols[1].base_asset, "ETH");
1894 assert!(info.symbols[1].is_margin_trading_allowed);
1895 }
1896}