1use super::{
22 error::SbeDecodeError,
23 models::{
24 BinanceAccountInfo, BinanceAccountTrade, BinanceBalance, BinanceCancelOrderResponse,
25 BinanceDepth, BinanceExchangeInfoSbe, BinanceNewOrderResponse, BinanceOrderFill,
26 BinanceOrderResponse, BinancePriceLevel, BinanceSymbolSbe, BinanceTrade, BinanceTrades,
27 },
28};
29use crate::common::sbe::{
30 cursor::SbeCursor,
31 spot::{
32 SBE_SCHEMA_ID, SBE_SCHEMA_VERSION,
33 account_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TEMPLATE_ID,
34 account_trades_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TRADES_TEMPLATE_ID,
35 account_type::AccountType, bool_enum::BoolEnum,
36 cancel_open_orders_response_codec::SBE_TEMPLATE_ID as CANCEL_OPEN_ORDERS_TEMPLATE_ID,
37 cancel_order_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_TEMPLATE_ID,
38 depth_response_codec::SBE_TEMPLATE_ID as DEPTH_TEMPLATE_ID,
39 exchange_info_response_codec::SBE_TEMPLATE_ID as EXCHANGE_INFO_TEMPLATE_ID,
40 message_header_codec::ENCODED_LENGTH as HEADER_LENGTH,
41 new_order_full_response_codec::SBE_TEMPLATE_ID as NEW_ORDER_FULL_TEMPLATE_ID,
42 order_response_codec::SBE_TEMPLATE_ID as ORDER_TEMPLATE_ID,
43 orders_response_codec::SBE_TEMPLATE_ID as ORDERS_TEMPLATE_ID,
44 ping_response_codec::SBE_TEMPLATE_ID as PING_TEMPLATE_ID,
45 server_time_response_codec::SBE_TEMPLATE_ID as SERVER_TIME_TEMPLATE_ID,
46 trades_response_codec::SBE_TEMPLATE_ID as TRADES_TEMPLATE_ID,
47 },
48};
49
50#[derive(Debug, Clone, Copy)]
52struct MessageHeader {
53 #[allow(dead_code)]
54 block_length: u16,
55 template_id: u16,
56 schema_id: u16,
57 version: u16,
58}
59
60impl MessageHeader {
61 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
63 cursor.require(HEADER_LENGTH)?;
64 Ok(Self {
65 block_length: cursor.read_u16_le()?,
66 template_id: cursor.read_u16_le()?,
67 schema_id: cursor.read_u16_le()?,
68 version: cursor.read_u16_le()?,
69 })
70 }
71
72 fn validate(&self) -> Result<(), SbeDecodeError> {
74 if self.schema_id != SBE_SCHEMA_ID {
75 return Err(SbeDecodeError::SchemaMismatch {
76 expected: SBE_SCHEMA_ID,
77 actual: self.schema_id,
78 });
79 }
80 if self.version != SBE_SCHEMA_VERSION {
81 return Err(SbeDecodeError::VersionMismatch {
82 expected: SBE_SCHEMA_VERSION,
83 actual: self.version,
84 });
85 }
86 Ok(())
87 }
88}
89
90pub fn decode_ping(buf: &[u8]) -> Result<(), SbeDecodeError> {
98 let mut cursor = SbeCursor::new(buf);
99 let header = MessageHeader::decode_cursor(&mut cursor)?;
100 header.validate()?;
101
102 if header.template_id != PING_TEMPLATE_ID {
103 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
104 }
105
106 Ok(())
107}
108
109pub fn decode_server_time(buf: &[u8]) -> Result<i64, SbeDecodeError> {
118 let mut cursor = SbeCursor::new(buf);
119 let header = MessageHeader::decode_cursor(&mut cursor)?;
120 header.validate()?;
121
122 if header.template_id != SERVER_TIME_TEMPLATE_ID {
123 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
124 }
125
126 cursor.read_i64_le()
127}
128
129pub fn decode_depth(buf: &[u8]) -> Result<BinanceDepth, SbeDecodeError> {
137 let mut cursor = SbeCursor::new(buf);
138 let header = MessageHeader::decode_cursor(&mut cursor)?;
139 header.validate()?;
140
141 if header.template_id != DEPTH_TEMPLATE_ID {
142 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
143 }
144
145 let last_update_id = cursor.read_i64_le()?;
146 let price_exponent = cursor.read_i8()?;
147 let qty_exponent = cursor.read_i8()?;
148
149 let (block_len, count) = cursor.read_group_header()?;
150 let bids = cursor.read_group(block_len, count, |c| {
151 Ok(BinancePriceLevel {
152 price_mantissa: c.read_i64_le()?,
153 qty_mantissa: c.read_i64_le()?,
154 })
155 })?;
156
157 let (block_len, count) = cursor.read_group_header()?;
158 let asks = cursor.read_group(block_len, count, |c| {
159 Ok(BinancePriceLevel {
160 price_mantissa: c.read_i64_le()?,
161 qty_mantissa: c.read_i64_le()?,
162 })
163 })?;
164
165 Ok(BinanceDepth {
166 last_update_id,
167 price_exponent,
168 qty_exponent,
169 bids,
170 asks,
171 })
172}
173
174pub fn decode_trades(buf: &[u8]) -> Result<BinanceTrades, SbeDecodeError> {
182 let mut cursor = SbeCursor::new(buf);
183 let header = MessageHeader::decode_cursor(&mut cursor)?;
184 header.validate()?;
185
186 if header.template_id != TRADES_TEMPLATE_ID {
187 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
188 }
189
190 let price_exponent = cursor.read_i8()?;
191 let qty_exponent = cursor.read_i8()?;
192
193 let (block_len, count) = cursor.read_group_header()?;
194 let trades = cursor.read_group(block_len, count, |c| {
195 Ok(BinanceTrade {
196 id: c.read_i64_le()?,
197 price_mantissa: c.read_i64_le()?,
198 qty_mantissa: c.read_i64_le()?,
199 quote_qty_mantissa: c.read_i64_le()?,
200 time: c.read_i64_le()?,
201 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
202 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
203 })
204 })?;
205
206 Ok(BinanceTrades {
207 price_exponent,
208 qty_exponent,
209 trades,
210 })
211}
212
213const NEW_ORDER_FULL_BLOCK_LENGTH: usize = 153;
215
216const CANCEL_ORDER_BLOCK_LENGTH: usize = 137;
218
219const ORDER_BLOCK_LENGTH: usize = 153;
221
222#[allow(dead_code)]
228pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
229 let mut cursor = SbeCursor::new(buf);
230 let header = MessageHeader::decode_cursor(&mut cursor)?;
231 header.validate()?;
232
233 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
234 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
235 }
236
237 cursor.require(NEW_ORDER_FULL_BLOCK_LENGTH)?;
238
239 let price_exponent = cursor.read_i8()?;
240 let qty_exponent = cursor.read_i8()?;
241 let order_id = cursor.read_i64_le()?;
242 let order_list_id = cursor.read_optional_i64_le()?;
243 let transact_time = cursor.read_i64_le()?;
244 let price_mantissa = cursor.read_i64_le()?;
245 let orig_qty_mantissa = cursor.read_i64_le()?;
246 let executed_qty_mantissa = cursor.read_i64_le()?;
247 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
248 let status = cursor.read_u8()?.into();
249 let time_in_force = cursor.read_u8()?.into();
250 let order_type = cursor.read_u8()?.into();
251 let side = cursor.read_u8()?.into();
252 let stop_price_mantissa = cursor.read_optional_i64_le()?;
253
254 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
256
257 cursor.advance(23)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
259
260 cursor.advance(16)?; let _commission_exponent = cursor.read_i8()?;
262
263 cursor.advance(18)?; let fills = decode_fills_cursor(&mut cursor)?;
266
267 let (block_len, count) = cursor.read_group_header()?;
269 cursor.advance(block_len as usize * count as usize)?;
270
271 let symbol = cursor.read_var_string8()?;
272 let client_order_id = cursor.read_var_string8()?;
273
274 Ok(BinanceNewOrderResponse {
275 price_exponent,
276 qty_exponent,
277 order_id,
278 order_list_id,
279 transact_time,
280 price_mantissa,
281 orig_qty_mantissa,
282 executed_qty_mantissa,
283 cummulative_quote_qty_mantissa,
284 status,
285 time_in_force,
286 order_type,
287 side,
288 stop_price_mantissa,
289 working_time,
290 self_trade_prevention_mode,
291 client_order_id,
292 symbol,
293 fills,
294 })
295}
296
297#[allow(dead_code)]
303pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
304 let mut cursor = SbeCursor::new(buf);
305 let header = MessageHeader::decode_cursor(&mut cursor)?;
306 header.validate()?;
307
308 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
309 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
310 }
311
312 cursor.require(CANCEL_ORDER_BLOCK_LENGTH)?;
313
314 let price_exponent = cursor.read_i8()?;
315 let qty_exponent = cursor.read_i8()?;
316 let order_id = cursor.read_i64_le()?;
317 let order_list_id = cursor.read_optional_i64_le()?;
318 let transact_time = cursor.read_i64_le()?;
319 let price_mantissa = cursor.read_i64_le()?;
320 let orig_qty_mantissa = cursor.read_i64_le()?;
321 let executed_qty_mantissa = cursor.read_i64_le()?;
322 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
323 let status = cursor.read_u8()?.into();
324 let time_in_force = cursor.read_u8()?.into();
325 let order_type = cursor.read_u8()?.into();
326 let side = cursor.read_u8()?.into();
327 let self_trade_prevention_mode = cursor.read_u8()?.into();
328
329 cursor.advance(CANCEL_ORDER_BLOCK_LENGTH - 63)?; let symbol = cursor.read_var_string8()?;
332 let orig_client_order_id = cursor.read_var_string8()?;
333 let client_order_id = cursor.read_var_string8()?;
334
335 Ok(BinanceCancelOrderResponse {
336 price_exponent,
337 qty_exponent,
338 order_id,
339 order_list_id,
340 transact_time,
341 price_mantissa,
342 orig_qty_mantissa,
343 executed_qty_mantissa,
344 cummulative_quote_qty_mantissa,
345 status,
346 time_in_force,
347 order_type,
348 side,
349 self_trade_prevention_mode,
350 client_order_id,
351 orig_client_order_id,
352 symbol,
353 })
354}
355
356#[allow(dead_code)]
362pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
363 let mut cursor = SbeCursor::new(buf);
364 let header = MessageHeader::decode_cursor(&mut cursor)?;
365 header.validate()?;
366
367 if header.template_id != ORDER_TEMPLATE_ID {
368 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
369 }
370
371 cursor.require(ORDER_BLOCK_LENGTH)?;
372
373 let price_exponent = cursor.read_i8()?;
374 let qty_exponent = cursor.read_i8()?;
375 let order_id = cursor.read_i64_le()?;
376 let order_list_id = cursor.read_optional_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 let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
387 let time = cursor.read_i64_le()?;
388 let update_time = cursor.read_i64_le()?;
389 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
390 let working_time = cursor.read_optional_i64_le()?;
391 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
392 let self_trade_prevention_mode = cursor.read_u8()?.into();
393
394 cursor.advance(ORDER_BLOCK_LENGTH - 104)?; let symbol = cursor.read_var_string8()?;
397 let client_order_id = cursor.read_var_string8()?;
398
399 Ok(BinanceOrderResponse {
400 price_exponent,
401 qty_exponent,
402 order_id,
403 order_list_id,
404 price_mantissa,
405 orig_qty_mantissa,
406 executed_qty_mantissa,
407 cummulative_quote_qty_mantissa,
408 status,
409 time_in_force,
410 order_type,
411 side,
412 stop_price_mantissa,
413 iceberg_qty_mantissa,
414 time,
415 update_time,
416 is_working,
417 working_time,
418 orig_quote_order_qty_mantissa,
419 self_trade_prevention_mode,
420 client_order_id,
421 symbol,
422 })
423}
424
425const ORDERS_GROUP_BLOCK_LENGTH: usize = 162;
427
428#[allow(dead_code)]
434pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
435 let mut cursor = SbeCursor::new(buf);
436 let header = MessageHeader::decode_cursor(&mut cursor)?;
437 header.validate()?;
438
439 if header.template_id != ORDERS_TEMPLATE_ID {
440 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
441 }
442
443 let (block_length, count) = cursor.read_group_header()?;
444
445 if count == 0 {
446 return Ok(Vec::new());
447 }
448
449 if block_length as usize != ORDERS_GROUP_BLOCK_LENGTH {
450 return Err(SbeDecodeError::InvalidBlockLength {
451 expected: ORDERS_GROUP_BLOCK_LENGTH as u16,
452 actual: block_length,
453 });
454 }
455
456 let mut orders = Vec::with_capacity(count as usize);
457
458 for _ in 0..count {
459 cursor.require(ORDERS_GROUP_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
475 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
477 let time = cursor.read_i64_le()?;
478 let update_time = cursor.read_i64_le()?;
479 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
480 let working_time = cursor.read_optional_i64_le()?;
481 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
482
483 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
485
486 cursor.advance(28)?; let symbol = cursor.read_var_string8()?;
489 let client_order_id = cursor.read_var_string8()?;
490
491 orders.push(BinanceOrderResponse {
492 price_exponent,
493 qty_exponent,
494 order_id,
495 order_list_id,
496 price_mantissa,
497 orig_qty_mantissa,
498 executed_qty_mantissa,
499 cummulative_quote_qty_mantissa,
500 status,
501 time_in_force,
502 order_type,
503 side,
504 stop_price_mantissa,
505 iceberg_qty_mantissa,
506 time,
507 update_time,
508 is_working,
509 working_time,
510 orig_quote_order_qty_mantissa,
511 self_trade_prevention_mode,
512 client_order_id,
513 symbol,
514 });
515 }
516
517 Ok(orders)
518}
519
520#[allow(dead_code)]
528pub fn decode_cancel_open_orders(
529 buf: &[u8],
530) -> Result<Vec<BinanceCancelOrderResponse>, SbeDecodeError> {
531 let mut cursor = SbeCursor::new(buf);
532 let header = MessageHeader::decode_cursor(&mut cursor)?;
533 header.validate()?;
534
535 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
536 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
537 }
538
539 let (_block_length, count) = cursor.read_group_header()?;
540
541 if count == 0 {
542 return Ok(Vec::new());
543 }
544
545 let mut responses = Vec::with_capacity(count as usize);
546
547 for _ in 0..count {
549 let response_len = cursor.read_u16_le()? as usize;
550 let embedded_bytes = cursor.read_bytes(response_len)?;
551 let cancel_response = decode_cancel_order(embedded_bytes)?;
552 responses.push(cancel_response);
553 }
554
555 Ok(responses)
556}
557
558const ACCOUNT_BLOCK_LENGTH: usize = 64;
560
561const BALANCE_BLOCK_LENGTH: u16 = 17;
563
564#[allow(dead_code)]
570pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
571 let mut cursor = SbeCursor::new(buf);
572 let header = MessageHeader::decode_cursor(&mut cursor)?;
573 header.validate()?;
574
575 if header.template_id != ACCOUNT_TEMPLATE_ID {
576 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
577 }
578
579 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
580
581 let commission_exponent = cursor.read_i8()?;
582 let maker_commission_mantissa = cursor.read_i64_le()?;
583 let taker_commission_mantissa = cursor.read_i64_le()?;
584 let buyer_commission_mantissa = cursor.read_i64_le()?;
585 let seller_commission_mantissa = cursor.read_i64_le()?;
586 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
587 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
588 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
589 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
591 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
592 let update_time = cursor.read_i64_le()?;
593 let account_type_enum = AccountType::from(cursor.read_u8()?);
594 cursor.advance(16)?; let account_type = account_type_enum.to_string();
597
598 let (block_length, balance_count) = cursor.read_group_header()?;
599
600 if block_length != BALANCE_BLOCK_LENGTH {
601 return Err(SbeDecodeError::InvalidBlockLength {
602 expected: BALANCE_BLOCK_LENGTH,
603 actual: block_length,
604 });
605 }
606
607 let mut balances = Vec::with_capacity(balance_count as usize);
608
609 for _ in 0..balance_count {
610 cursor.require(block_length as usize)?;
611
612 let exponent = cursor.read_i8()?;
613 let free_mantissa = cursor.read_i64_le()?;
614 let locked_mantissa = cursor.read_i64_le()?;
615
616 let asset = cursor.read_var_string8()?;
617
618 balances.push(BinanceBalance {
619 asset,
620 free_mantissa,
621 locked_mantissa,
622 exponent,
623 });
624 }
625
626 Ok(BinanceAccountInfo {
627 commission_exponent,
628 maker_commission_mantissa,
629 taker_commission_mantissa,
630 buyer_commission_mantissa,
631 seller_commission_mantissa,
632 can_trade,
633 can_withdraw,
634 can_deposit,
635 require_self_trade_prevention,
636 prevent_sor,
637 update_time,
638 account_type,
639 balances,
640 })
641}
642
643const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
645
646#[allow(dead_code)]
652pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
653 let mut cursor = SbeCursor::new(buf);
654 let header = MessageHeader::decode_cursor(&mut cursor)?;
655 header.validate()?;
656
657 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
658 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
659 }
660
661 let (block_length, trade_count) = cursor.read_group_header()?;
662
663 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
664 return Err(SbeDecodeError::InvalidBlockLength {
665 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
666 actual: block_length,
667 });
668 }
669
670 let mut trades = Vec::with_capacity(trade_count as usize);
671
672 for _ in 0..trade_count {
673 cursor.require(block_length as usize)?;
674
675 let price_exponent = cursor.read_i8()?;
676 let qty_exponent = cursor.read_i8()?;
677 let commission_exponent = cursor.read_i8()?;
678 let id = cursor.read_i64_le()?;
679 let order_id = cursor.read_i64_le()?;
680 let order_list_id = cursor.read_optional_i64_le()?;
681 let price_mantissa = cursor.read_i64_le()?;
682 let qty_mantissa = cursor.read_i64_le()?;
683 let quote_qty_mantissa = cursor.read_i64_le()?;
684 let commission_mantissa = cursor.read_i64_le()?;
685 let time = cursor.read_i64_le()?;
686 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
687 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
688 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
689
690 let symbol = cursor.read_var_string8()?;
691 let commission_asset = cursor.read_var_string8()?;
692
693 trades.push(BinanceAccountTrade {
694 price_exponent,
695 qty_exponent,
696 commission_exponent,
697 id,
698 order_id,
699 order_list_id,
700 price_mantissa,
701 qty_mantissa,
702 quote_qty_mantissa,
703 commission_mantissa,
704 time,
705 is_buyer,
706 is_maker,
707 is_best_match,
708 symbol,
709 commission_asset,
710 });
711 }
712
713 Ok(trades)
714}
715
716const FILLS_BLOCK_LENGTH: u16 = 42;
718
719fn decode_fills_cursor(
721 cursor: &mut SbeCursor<'_>,
722) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
723 let (block_length, count) = cursor.read_group_header()?;
724
725 if block_length != FILLS_BLOCK_LENGTH {
726 return Err(SbeDecodeError::InvalidBlockLength {
727 expected: FILLS_BLOCK_LENGTH,
728 actual: block_length,
729 });
730 }
731
732 let mut fills = Vec::with_capacity(count as usize);
733
734 for _ in 0..count {
735 cursor.require(block_length as usize)?;
736
737 let commission_exponent = cursor.read_i8()?;
738 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
740 let qty_mantissa = cursor.read_i64_le()?;
741 let commission_mantissa = cursor.read_i64_le()?;
742 let trade_id = cursor.read_optional_i64_le()?;
743 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
746
747 fills.push(BinanceOrderFill {
748 price_mantissa,
749 qty_mantissa,
750 commission_mantissa,
751 commission_exponent,
752 commission_asset,
753 trade_id,
754 });
755 }
756
757 Ok(fills)
758}
759
760const SYMBOL_BLOCK_LENGTH: usize = 19;
762
763pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
772 let mut cursor = SbeCursor::new(buf);
773 let header = MessageHeader::decode_cursor(&mut cursor)?;
774 header.validate()?;
775
776 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
777 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
778 }
779
780 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
782 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
783
784 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
786 for _ in 0..exchange_filters_count {
787 cursor.read_var_string8()?;
789 }
790
791 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
793
794 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
795 return Err(SbeDecodeError::InvalidBlockLength {
796 expected: SYMBOL_BLOCK_LENGTH as u16,
797 actual: symbols_block_len,
798 });
799 }
800
801 let mut symbols = Vec::with_capacity(symbols_count as usize);
802
803 for _ in 0..symbols_count {
804 cursor.require(SYMBOL_BLOCK_LENGTH)?;
805
806 let status = cursor.read_u8()?;
808 let base_asset_precision = cursor.read_u8()?;
809 let quote_asset_precision = cursor.read_u8()?;
810 let _base_commission_precision = cursor.read_u8()?;
811 let _quote_commission_precision = cursor.read_u8()?;
812 let order_types = cursor.read_u16_le()?;
813 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
814 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
815 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
816 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
817 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
818 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
819 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
820 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
821 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
822 let _default_self_trade_prevention_mode = cursor.read_u8()?;
823 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
824 let _peg_instructions_allowed = cursor.read_u8()?;
825
826 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
828 let mut filters = Vec::with_capacity(filters_count as usize);
829 for _ in 0..filters_count {
830 let filter_json = cursor.read_var_string8()?;
831 if let Ok(value) = serde_json::from_str(&filter_json) {
832 filters.push(value);
833 }
834 }
835
836 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
838 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
839 for _ in 0..perm_sets_count {
840 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
842 let mut perm_set = Vec::with_capacity(perms_count as usize);
843 for _ in 0..perms_count {
844 let perm = cursor.read_var_string8()?;
845 perm_set.push(perm);
846 }
847 permissions.push(perm_set);
848 }
849
850 let symbol = cursor.read_var_string8()?;
852 let base_asset = cursor.read_var_string8()?;
853 let quote_asset = cursor.read_var_string8()?;
854
855 symbols.push(BinanceSymbolSbe {
856 symbol,
857 base_asset,
858 quote_asset,
859 base_asset_precision,
860 quote_asset_precision,
861 status,
862 order_types,
863 iceberg_allowed,
864 oco_allowed,
865 oto_allowed,
866 quote_order_qty_market_allowed,
867 allow_trailing_stop,
868 cancel_replace_allowed,
869 amend_allowed,
870 is_spot_trading_allowed,
871 is_margin_trading_allowed,
872 filters,
873 permissions,
874 });
875 }
876
877 Ok(BinanceExchangeInfoSbe { symbols })
880}
881
882#[cfg(test)]
883mod tests {
884 use rstest::rstest;
885
886 use super::*;
887
888 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
889 let mut buf = [0u8; 8];
890 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
891 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
892 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
893 buf[6..8].copy_from_slice(&version.to_le_bytes());
894 buf
895 }
896
897 #[rstest]
898 fn test_decode_ping_valid() {
899 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
901 assert!(decode_ping(&buf).is_ok());
902 }
903
904 #[rstest]
905 fn test_decode_ping_buffer_too_short() {
906 let buf = [0u8; 4];
907 let err = decode_ping(&buf).unwrap_err();
908 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
909 }
910
911 #[rstest]
912 fn test_decode_ping_schema_mismatch() {
913 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
914 let err = decode_ping(&buf).unwrap_err();
915 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
916 }
917
918 #[rstest]
919 fn test_decode_ping_wrong_template() {
920 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
921 let err = decode_ping(&buf).unwrap_err();
922 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
923 }
924
925 #[rstest]
926 fn test_decode_server_time_valid() {
927 let header = create_header(
929 8,
930 SERVER_TIME_TEMPLATE_ID,
931 SBE_SCHEMA_ID,
932 SBE_SCHEMA_VERSION,
933 );
934 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
937 buf.extend_from_slice(&header);
938 buf.extend_from_slice(×tamp.to_le_bytes());
939
940 let result = decode_server_time(&buf).unwrap();
941 assert_eq!(result, timestamp);
942 }
943
944 #[rstest]
945 fn test_decode_server_time_buffer_too_short() {
946 let buf = create_header(
948 8,
949 SERVER_TIME_TEMPLATE_ID,
950 SBE_SCHEMA_ID,
951 SBE_SCHEMA_VERSION,
952 );
953 let err = decode_server_time(&buf).unwrap_err();
954 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
955 }
956
957 #[rstest]
958 fn test_decode_server_time_wrong_template() {
959 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
960 let mut buf = Vec::with_capacity(16);
961 buf.extend_from_slice(&header);
962 buf.extend_from_slice(&0i64.to_le_bytes());
963
964 let err = decode_server_time(&buf).unwrap_err();
965 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
966 }
967
968 #[rstest]
969 fn test_decode_server_time_version_mismatch() {
970 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, 99);
971 let mut buf = Vec::with_capacity(16);
972 buf.extend_from_slice(&header);
973 buf.extend_from_slice(&0i64.to_le_bytes());
974
975 let err = decode_server_time(&buf).unwrap_err();
976 assert!(matches!(err, SbeDecodeError::VersionMismatch { .. }));
977 }
978
979 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
980 let mut buf = [0u8; 6];
981 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
982 buf[2..6].copy_from_slice(&count.to_le_bytes());
983 buf
984 }
985
986 #[rstest]
987 fn test_decode_depth_valid() {
988 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
990
991 let mut buf = Vec::new();
992 buf.extend_from_slice(&header);
993
994 let last_update_id: i64 = 123456789;
996 let price_exponent: i8 = -8;
997 let qty_exponent: i8 = -8;
998 buf.extend_from_slice(&last_update_id.to_le_bytes());
999 buf.push(price_exponent as u8);
1000 buf.push(qty_exponent as u8);
1001
1002 buf.extend_from_slice(&create_group_header(16, 2));
1004 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1006 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1007 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1009 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1010
1011 buf.extend_from_slice(&create_group_header(16, 1));
1013 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1015 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1016
1017 let depth = decode_depth(&buf).unwrap();
1018
1019 assert_eq!(depth.last_update_id, 123456789);
1020 assert_eq!(depth.price_exponent, -8);
1021 assert_eq!(depth.qty_exponent, -8);
1022 assert_eq!(depth.bids.len(), 2);
1023 assert_eq!(depth.asks.len(), 1);
1024 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1025 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1026 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1027 }
1028
1029 #[rstest]
1030 fn test_decode_depth_empty_book() {
1031 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1032
1033 let mut buf = Vec::new();
1034 buf.extend_from_slice(&header);
1035 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1041 buf.extend_from_slice(&create_group_header(16, 0));
1043
1044 let depth = decode_depth(&buf).unwrap();
1045
1046 assert!(depth.bids.is_empty());
1047 assert!(depth.asks.is_empty());
1048 }
1049
1050 #[rstest]
1051 fn test_decode_trades_valid() {
1052 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1054
1055 let mut buf = Vec::new();
1056 buf.extend_from_slice(&header);
1057
1058 let price_exponent: i8 = -8;
1060 let qty_exponent: i8 = -8;
1061 buf.push(price_exponent as u8);
1062 buf.push(qty_exponent as u8);
1063
1064 buf.extend_from_slice(&create_group_header(42, 1));
1066
1067 let trade_id: i64 = 999;
1069 let price: i64 = 100_000_000_000;
1070 let qty: i64 = 10_000_000;
1071 let quote_qty: i64 = 1_000_000_000_000;
1072 let time: i64 = 1734300000000;
1073 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1077 buf.extend_from_slice(&price.to_le_bytes());
1078 buf.extend_from_slice(&qty.to_le_bytes());
1079 buf.extend_from_slice("e_qty.to_le_bytes());
1080 buf.extend_from_slice(&time.to_le_bytes());
1081 buf.push(is_buyer_maker);
1082 buf.push(is_best_match);
1083
1084 let trades = decode_trades(&buf).unwrap();
1085
1086 assert_eq!(trades.price_exponent, -8);
1087 assert_eq!(trades.qty_exponent, -8);
1088 assert_eq!(trades.trades.len(), 1);
1089 assert_eq!(trades.trades[0].id, 999);
1090 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1091 assert!(trades.trades[0].is_buyer_maker);
1092 assert!(trades.trades[0].is_best_match);
1093 }
1094
1095 #[rstest]
1096 fn test_decode_trades_empty() {
1097 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1098
1099 let mut buf = Vec::new();
1100 buf.extend_from_slice(&header);
1101 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1106
1107 let trades = decode_trades(&buf).unwrap();
1108
1109 assert!(trades.trades.is_empty());
1110 }
1111
1112 #[rstest]
1113 fn test_decode_depth_wrong_template() {
1114 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1115
1116 let mut buf = Vec::new();
1117 buf.extend_from_slice(&header);
1118 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1121 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1122 }
1123
1124 #[rstest]
1125 fn test_decode_trades_wrong_template() {
1126 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1127
1128 let mut buf = Vec::new();
1129 buf.extend_from_slice(&header);
1130 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1133 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1134 }
1135
1136 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1137 buf.push(s.len() as u8);
1138 buf.extend_from_slice(s.as_bytes());
1139 }
1140
1141 #[rstest]
1142 fn test_decode_order_valid() {
1143 let header = create_header(
1144 ORDER_BLOCK_LENGTH as u16,
1145 ORDER_TEMPLATE_ID,
1146 SBE_SCHEMA_ID,
1147 SBE_SCHEMA_VERSION,
1148 );
1149
1150 let mut buf = Vec::new();
1151 buf.extend_from_slice(&header);
1152
1153 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 {
1177 buf.push(0);
1178 }
1179
1180 write_var_string(&mut buf, "BTCUSDT");
1181 write_var_string(&mut buf, "my-order-123");
1182
1183 let order = decode_order(&buf).unwrap();
1184
1185 assert_eq!(order.order_id, 12345);
1186 assert!(order.order_list_id.is_none());
1187 assert_eq!(order.price_exponent, -8);
1188 assert_eq!(order.price_mantissa, 100_000_000_000);
1189 assert!(order.stop_price_mantissa.is_none());
1190 assert!(order.iceberg_qty_mantissa.is_none());
1191 assert!(order.is_working);
1192 assert_eq!(order.working_time, Some(1734300000500));
1193 assert_eq!(order.symbol, "BTCUSDT");
1194 assert_eq!(order.client_order_id, "my-order-123");
1195 }
1196
1197 #[rstest]
1198 fn test_decode_orders_multiple() {
1199 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1201
1202 let mut buf = Vec::new();
1203 buf.extend_from_slice(&header);
1204
1205 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 2));
1207
1208 let order1_start = buf.len();
1210 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 {
1233 buf.push(0);
1234 }
1235 write_var_string(&mut buf, "BTCUSDT");
1236 write_var_string(&mut buf, "order-1");
1237
1238 let order2_start = buf.len();
1240 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 {
1262 buf.push(0);
1263 }
1264 write_var_string(&mut buf, "ETHUSDT");
1265 write_var_string(&mut buf, "order-2");
1266
1267 let orders = decode_orders(&buf).unwrap();
1268
1269 assert_eq!(orders.len(), 2);
1270 assert_eq!(orders[0].order_id, 1001);
1271 assert_eq!(orders[0].symbol, "BTCUSDT");
1272 assert_eq!(orders[0].client_order_id, "order-1");
1273 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
1274
1275 assert_eq!(orders[1].order_id, 2002);
1276 assert_eq!(orders[1].symbol, "ETHUSDT");
1277 assert_eq!(orders[1].client_order_id, "order-2");
1278 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
1279 }
1280
1281 #[rstest]
1282 fn test_decode_orders_empty() {
1283 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1284
1285 let mut buf = Vec::new();
1286 buf.extend_from_slice(&header);
1287 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 0));
1288
1289 let orders = decode_orders(&buf).unwrap();
1290 assert!(orders.is_empty());
1291 }
1292
1293 #[rstest]
1294 fn test_decode_orders_truncated_var_string() {
1295 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1296
1297 let mut buf = Vec::new();
1298 buf.extend_from_slice(&header);
1299 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1300
1301 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1303
1304 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
1309 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1310 }
1311
1312 #[rstest]
1313 fn test_decode_orders_invalid_utf8() {
1314 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1315
1316 let mut buf = Vec::new();
1317 buf.extend_from_slice(&header);
1318 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1319
1320 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1321
1322 buf.push(4);
1324 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
1325
1326 let err = decode_orders(&buf).unwrap_err();
1327 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
1328 }
1329
1330 #[rstest]
1331 fn test_decode_cancel_order_valid() {
1332 let header = create_header(
1333 CANCEL_ORDER_BLOCK_LENGTH as u16,
1334 CANCEL_ORDER_TEMPLATE_ID,
1335 SBE_SCHEMA_ID,
1336 SBE_SCHEMA_VERSION,
1337 );
1338
1339 let mut buf = Vec::new();
1340 buf.extend_from_slice(&header);
1341
1342 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 {
1359 buf.push(0);
1360 }
1361
1362 write_var_string(&mut buf, "BTCUSDT");
1363 write_var_string(&mut buf, "orig-client-id");
1364 write_var_string(&mut buf, "new-client-id");
1365
1366 let cancel = decode_cancel_order(&buf).unwrap();
1367
1368 assert_eq!(cancel.order_id, 99999);
1369 assert!(cancel.order_list_id.is_none());
1370 assert_eq!(cancel.symbol, "BTCUSDT");
1371 assert_eq!(cancel.orig_client_order_id, "orig-client-id");
1372 assert_eq!(cancel.client_order_id, "new-client-id");
1373 }
1374
1375 #[rstest]
1376 fn test_decode_account_with_balances() {
1377 let header = create_header(
1378 ACCOUNT_BLOCK_LENGTH as u16,
1379 ACCOUNT_TEMPLATE_ID,
1380 SBE_SCHEMA_ID,
1381 SBE_SCHEMA_VERSION,
1382 );
1383
1384 let mut buf = Vec::new();
1385 buf.extend_from_slice(&header);
1386
1387 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 {
1404 buf.push(0);
1405 }
1406
1407 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
1409
1410 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");
1415
1416 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");
1421
1422 let account = decode_account(&buf).unwrap();
1423
1424 assert!(account.can_trade);
1425 assert!(account.can_withdraw);
1426 assert!(account.can_deposit);
1427 assert_eq!(account.balances.len(), 2);
1428 assert_eq!(account.balances[0].asset, "BTC");
1429 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
1430 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
1431 assert_eq!(account.balances[1].asset, "USDT");
1432 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
1433 }
1434
1435 #[rstest]
1436 fn test_decode_account_empty_balances() {
1437 let header = create_header(
1438 ACCOUNT_BLOCK_LENGTH as u16,
1439 ACCOUNT_TEMPLATE_ID,
1440 SBE_SCHEMA_ID,
1441 SBE_SCHEMA_VERSION,
1442 );
1443
1444 let mut buf = Vec::new();
1445 buf.extend_from_slice(&header);
1446
1447 buf.push((-8i8) as u8);
1449 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
1453
1454 let account = decode_account(&buf).unwrap();
1455 assert!(account.balances.is_empty());
1456 }
1457
1458 #[rstest]
1459 fn test_decode_account_trades_multiple() {
1460 let header = create_header(
1461 0,
1462 ACCOUNT_TRADES_TEMPLATE_ID,
1463 SBE_SCHEMA_ID,
1464 SBE_SCHEMA_VERSION,
1465 );
1466
1467 let mut buf = Vec::new();
1468 buf.extend_from_slice(&header);
1469
1470 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
1472
1473 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");
1489 write_var_string(&mut buf, "BNB");
1490
1491 buf.push((-8i8) as u8);
1493 buf.push((-8i8) as u8);
1494 buf.push((-8i8) as u8);
1495 buf.extend_from_slice(&1002i64.to_le_bytes());
1496 buf.extend_from_slice(&5002i64.to_le_bytes());
1497 buf.extend_from_slice(&i64::MIN.to_le_bytes());
1498 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
1499 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
1500 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
1501 buf.extend_from_slice(&50_000i64.to_le_bytes());
1502 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
1503 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
1507 write_var_string(&mut buf, "USDT");
1508
1509 let trades = decode_account_trades(&buf).unwrap();
1510
1511 assert_eq!(trades.len(), 2);
1512 assert_eq!(trades[0].id, 1001);
1513 assert_eq!(trades[0].order_id, 5001);
1514 assert!(trades[0].order_list_id.is_none());
1515 assert_eq!(trades[0].symbol, "BTCUSDT");
1516 assert_eq!(trades[0].commission_asset, "BNB");
1517 assert!(trades[0].is_buyer);
1518 assert!(!trades[0].is_maker);
1519
1520 assert_eq!(trades[1].id, 1002);
1521 assert_eq!(trades[1].symbol, "ETHUSDT");
1522 assert_eq!(trades[1].commission_asset, "USDT");
1523 assert!(!trades[1].is_buyer);
1524 assert!(trades[1].is_maker);
1525 }
1526
1527 #[rstest]
1528 fn test_decode_account_trades_empty() {
1529 let header = create_header(
1530 0,
1531 ACCOUNT_TRADES_TEMPLATE_ID,
1532 SBE_SCHEMA_ID,
1533 SBE_SCHEMA_VERSION,
1534 );
1535
1536 let mut buf = Vec::new();
1537 buf.extend_from_slice(&header);
1538 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
1539
1540 let trades = decode_account_trades(&buf).unwrap();
1541 assert!(trades.is_empty());
1542 }
1543
1544 #[rstest]
1545 fn test_decode_exchange_info_single_symbol() {
1546 let header = create_header(
1547 0,
1548 EXCHANGE_INFO_TEMPLATE_ID,
1549 SBE_SCHEMA_ID,
1550 SBE_SCHEMA_VERSION,
1551 );
1552
1553 let mut buf = Vec::new();
1554 buf.extend_from_slice(&header);
1555
1556 buf.extend_from_slice(&create_group_header(11, 0));
1558
1559 buf.extend_from_slice(&create_group_header(0, 0));
1561
1562 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
1564
1565 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, 1));
1587 let filter_json = r#"{"filterType":"PRICE_FILTER","minPrice":"0.01","maxPrice":"100000","tickSize":"0.01"}"#;
1588 write_var_string(&mut buf, filter_json);
1589
1590 buf.extend_from_slice(&create_group_header(0, 1));
1592 buf.extend_from_slice(&create_group_header(0, 1));
1593 write_var_string(&mut buf, "SPOT");
1594
1595 write_var_string(&mut buf, "BTCUSDT");
1597 write_var_string(&mut buf, "BTC");
1598 write_var_string(&mut buf, "USDT");
1599
1600 let info = decode_exchange_info(&buf).unwrap();
1601
1602 assert_eq!(info.symbols.len(), 1);
1603 let symbol = &info.symbols[0];
1604 assert_eq!(symbol.symbol, "BTCUSDT");
1605 assert_eq!(symbol.base_asset, "BTC");
1606 assert_eq!(symbol.quote_asset, "USDT");
1607 assert_eq!(symbol.base_asset_precision, 8);
1608 assert_eq!(symbol.quote_asset_precision, 8);
1609 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
1611 assert!(symbol.iceberg_allowed);
1612 assert!(symbol.oco_allowed);
1613 assert!(!symbol.oto_allowed);
1614 assert!(symbol.quote_order_qty_market_allowed);
1615 assert!(symbol.allow_trailing_stop);
1616 assert!(symbol.cancel_replace_allowed);
1617 assert!(!symbol.amend_allowed);
1618 assert!(symbol.is_spot_trading_allowed);
1619 assert!(!symbol.is_margin_trading_allowed);
1620 assert_eq!(symbol.filters.len(), 1);
1621 assert_eq!(symbol.permissions.len(), 1);
1622 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
1623 }
1624
1625 #[rstest]
1626 fn test_decode_exchange_info_empty() {
1627 let header = create_header(
1628 0,
1629 EXCHANGE_INFO_TEMPLATE_ID,
1630 SBE_SCHEMA_ID,
1631 SBE_SCHEMA_VERSION,
1632 );
1633
1634 let mut buf = Vec::new();
1635 buf.extend_from_slice(&header);
1636
1637 buf.extend_from_slice(&create_group_header(11, 0));
1639
1640 buf.extend_from_slice(&create_group_header(0, 0));
1642
1643 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
1645
1646 let info = decode_exchange_info(&buf).unwrap();
1647 assert!(info.symbols.is_empty());
1648 }
1649
1650 #[rstest]
1651 fn test_decode_exchange_info_wrong_template() {
1652 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1653
1654 let mut buf = Vec::new();
1655 buf.extend_from_slice(&header);
1656
1657 let err = decode_exchange_info(&buf).unwrap_err();
1658 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1659 }
1660
1661 #[rstest]
1662 fn test_decode_exchange_info_multiple_symbols() {
1663 let header = create_header(
1664 0,
1665 EXCHANGE_INFO_TEMPLATE_ID,
1666 SBE_SCHEMA_ID,
1667 SBE_SCHEMA_VERSION,
1668 );
1669
1670 let mut buf = Vec::new();
1671 buf.extend_from_slice(&header);
1672
1673 buf.extend_from_slice(&create_group_header(11, 0));
1675
1676 buf.extend_from_slice(&create_group_header(0, 0));
1678
1679 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
1681
1682 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");
1704 write_var_string(&mut buf, "BTC");
1705 write_var_string(&mut buf, "USDT");
1706
1707 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");
1729 write_var_string(&mut buf, "ETH");
1730 write_var_string(&mut buf, "USDT");
1731
1732 let info = decode_exchange_info(&buf).unwrap();
1733
1734 assert_eq!(info.symbols.len(), 2);
1735 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
1736 assert_eq!(info.symbols[0].base_asset, "BTC");
1737 assert!(!info.symbols[0].is_margin_trading_allowed);
1738
1739 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
1740 assert_eq!(info.symbols[1].base_asset, "ETH");
1741 assert!(info.symbols[1].is_margin_trading_allowed);
1742 }
1743}