1use super::{
22 error::SbeDecodeError,
23 models::{
24 BinanceAccountInfo, BinanceAccountTrade, BinanceBalance, BinanceCancelOrderResponse,
25 BinanceDepth, BinanceExchangeInfoSbe, BinanceKline, BinanceKlines, BinanceLotSizeFilterSbe,
26 BinanceNewOrderResponse, BinanceOrderFill, BinanceOrderResponse, BinancePriceFilterSbe,
27 BinancePriceLevel, BinanceSymbolFiltersSbe, BinanceSymbolSbe, BinanceTrade, BinanceTrades,
28 },
29};
30use crate::spot::sbe::{
31 cursor::SbeCursor,
32 spot::{
33 SBE_SCHEMA_ID, SBE_SCHEMA_VERSION,
34 account_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TEMPLATE_ID,
35 account_trades_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TRADES_TEMPLATE_ID,
36 account_type::AccountType, bool_enum::BoolEnum,
37 cancel_open_orders_response_codec::SBE_TEMPLATE_ID as CANCEL_OPEN_ORDERS_TEMPLATE_ID,
38 cancel_order_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_TEMPLATE_ID,
39 depth_response_codec::SBE_TEMPLATE_ID as DEPTH_TEMPLATE_ID,
40 exchange_info_response_codec::SBE_TEMPLATE_ID as EXCHANGE_INFO_TEMPLATE_ID,
41 klines_response_codec::SBE_TEMPLATE_ID as KLINES_TEMPLATE_ID,
42 lot_size_filter_codec::SBE_TEMPLATE_ID as LOT_SIZE_FILTER_TEMPLATE_ID,
43 message_header_codec::ENCODED_LENGTH as HEADER_LENGTH,
44 new_order_full_response_codec::SBE_TEMPLATE_ID as NEW_ORDER_FULL_TEMPLATE_ID,
45 order_response_codec::SBE_TEMPLATE_ID as ORDER_TEMPLATE_ID,
46 orders_response_codec::SBE_TEMPLATE_ID as ORDERS_TEMPLATE_ID,
47 ping_response_codec::SBE_TEMPLATE_ID as PING_TEMPLATE_ID,
48 price_filter_codec::SBE_TEMPLATE_ID as PRICE_FILTER_TEMPLATE_ID,
49 server_time_response_codec::SBE_TEMPLATE_ID as SERVER_TIME_TEMPLATE_ID,
50 trades_response_codec::SBE_TEMPLATE_ID as TRADES_TEMPLATE_ID,
51 },
52};
53
54#[derive(Debug, Clone, Copy)]
56struct MessageHeader {
57 block_length: u16,
58 template_id: u16,
59 schema_id: u16,
60 version: u16,
61}
62
63impl MessageHeader {
64 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
66 cursor.require(HEADER_LENGTH)?;
67 Ok(Self {
68 block_length: cursor.read_u16_le()?,
69 template_id: cursor.read_u16_le()?,
70 schema_id: cursor.read_u16_le()?,
71 version: cursor.read_u16_le()?,
72 })
73 }
74
75 fn validate(&self) -> Result<(), SbeDecodeError> {
77 if self.schema_id != SBE_SCHEMA_ID {
78 return Err(SbeDecodeError::SchemaMismatch {
79 expected: SBE_SCHEMA_ID,
80 actual: self.schema_id,
81 });
82 }
83
84 if self.version != SBE_SCHEMA_VERSION {
85 return Err(SbeDecodeError::VersionMismatch {
86 expected: SBE_SCHEMA_VERSION,
87 actual: self.version,
88 });
89 }
90 Ok(())
91 }
92}
93
94pub fn decode_ping(buf: &[u8]) -> Result<(), SbeDecodeError> {
102 let mut cursor = SbeCursor::new(buf);
103 let header = MessageHeader::decode_cursor(&mut cursor)?;
104 header.validate()?;
105
106 if header.template_id != PING_TEMPLATE_ID {
107 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
108 }
109
110 Ok(())
111}
112
113pub fn decode_server_time(buf: &[u8]) -> Result<i64, SbeDecodeError> {
122 let mut cursor = SbeCursor::new(buf);
123 let header = MessageHeader::decode_cursor(&mut cursor)?;
124 header.validate()?;
125
126 if header.template_id != SERVER_TIME_TEMPLATE_ID {
127 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
128 }
129
130 cursor.read_i64_le()
131}
132
133pub fn decode_depth(buf: &[u8]) -> Result<BinanceDepth, SbeDecodeError> {
141 let mut cursor = SbeCursor::new(buf);
142 let header = MessageHeader::decode_cursor(&mut cursor)?;
143 header.validate()?;
144
145 if header.template_id != DEPTH_TEMPLATE_ID {
146 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
147 }
148
149 let last_update_id = cursor.read_i64_le()?;
150 let price_exponent = cursor.read_i8()?;
151 let qty_exponent = cursor.read_i8()?;
152
153 let (block_len, count) = cursor.read_group_header()?;
154 let bids = cursor.read_group(block_len, count, |c| {
155 Ok(BinancePriceLevel {
156 price_mantissa: c.read_i64_le()?,
157 qty_mantissa: c.read_i64_le()?,
158 })
159 })?;
160
161 let (block_len, count) = cursor.read_group_header()?;
162 let asks = cursor.read_group(block_len, count, |c| {
163 Ok(BinancePriceLevel {
164 price_mantissa: c.read_i64_le()?,
165 qty_mantissa: c.read_i64_le()?,
166 })
167 })?;
168
169 Ok(BinanceDepth {
170 last_update_id,
171 price_exponent,
172 qty_exponent,
173 bids,
174 asks,
175 })
176}
177
178pub fn decode_trades(buf: &[u8]) -> Result<BinanceTrades, SbeDecodeError> {
186 let mut cursor = SbeCursor::new(buf);
187 let header = MessageHeader::decode_cursor(&mut cursor)?;
188 header.validate()?;
189
190 if header.template_id != TRADES_TEMPLATE_ID {
191 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
192 }
193
194 let price_exponent = cursor.read_i8()?;
195 let qty_exponent = cursor.read_i8()?;
196
197 let (block_len, count) = cursor.read_group_header()?;
198 let trades = cursor.read_group(block_len, count, |c| {
199 Ok(BinanceTrade {
200 id: c.read_i64_le()?,
201 price_mantissa: c.read_i64_le()?,
202 qty_mantissa: c.read_i64_le()?,
203 quote_qty_mantissa: c.read_i64_le()?,
204 time: c.read_i64_le()?,
205 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
206 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
207 })
208 })?;
209
210 Ok(BinanceTrades {
211 price_exponent,
212 qty_exponent,
213 trades,
214 })
215}
216
217const KLINES_BLOCK_LENGTH: u16 = 120;
219
220pub fn decode_klines(buf: &[u8]) -> Result<BinanceKlines, SbeDecodeError> {
228 let mut cursor = SbeCursor::new(buf);
229 let header = MessageHeader::decode_cursor(&mut cursor)?;
230 header.validate()?;
231
232 if header.template_id != KLINES_TEMPLATE_ID {
233 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
234 }
235
236 let price_exponent = cursor.read_i8()?;
237 let qty_exponent = cursor.read_i8()?;
238
239 let (block_len, count) = cursor.read_group_header()?;
240
241 if block_len != KLINES_BLOCK_LENGTH {
242 return Err(SbeDecodeError::InvalidBlockLength {
243 expected: KLINES_BLOCK_LENGTH,
244 actual: block_len,
245 });
246 }
247
248 let mut klines = Vec::with_capacity(count as usize);
249
250 for _ in 0..count {
251 cursor.require(KLINES_BLOCK_LENGTH as usize)?;
252
253 let open_time = cursor.read_i64_le()?;
254 let open_price = cursor.read_i64_le()?;
255 let high_price = cursor.read_i64_le()?;
256 let low_price = cursor.read_i64_le()?;
257 let close_price = cursor.read_i64_le()?;
258
259 let volume_slice = cursor.read_bytes(16)?;
260 let mut volume = [0u8; 16];
261 volume.copy_from_slice(volume_slice);
262
263 let close_time = cursor.read_i64_le()?;
264
265 let quote_volume_slice = cursor.read_bytes(16)?;
266 let mut quote_volume = [0u8; 16];
267 quote_volume.copy_from_slice(quote_volume_slice);
268
269 let num_trades = cursor.read_i64_le()?;
270
271 let taker_buy_base_volume_slice = cursor.read_bytes(16)?;
272 let mut taker_buy_base_volume = [0u8; 16];
273 taker_buy_base_volume.copy_from_slice(taker_buy_base_volume_slice);
274
275 let taker_buy_quote_volume_slice = cursor.read_bytes(16)?;
276 let mut taker_buy_quote_volume = [0u8; 16];
277 taker_buy_quote_volume.copy_from_slice(taker_buy_quote_volume_slice);
278
279 klines.push(BinanceKline {
280 open_time,
281 open_price,
282 high_price,
283 low_price,
284 close_price,
285 volume,
286 close_time,
287 quote_volume,
288 num_trades,
289 taker_buy_base_volume,
290 taker_buy_quote_volume,
291 });
292 }
293
294 Ok(BinanceKlines {
295 price_exponent,
296 qty_exponent,
297 klines,
298 })
299}
300
301const NEW_ORDER_FULL_FIELDS_END: usize = 135;
304const CANCEL_ORDER_FIELDS_END: usize = 63;
305const ORDER_FIELDS_END: usize = 104;
306
307#[allow(dead_code)]
313pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
314 let mut cursor = SbeCursor::new(buf);
315 let header = MessageHeader::decode_cursor(&mut cursor)?;
316 header.validate()?;
317
318 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
319 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
320 }
321
322 cursor.require(header.block_length as usize)?;
323
324 let price_exponent = cursor.read_i8()?;
325 let qty_exponent = cursor.read_i8()?;
326 let order_id = cursor.read_i64_le()?;
327 let order_list_id = cursor.read_optional_i64_le()?;
328 let transact_time = cursor.read_i64_le()?;
329 let price_mantissa = cursor.read_i64_le()?;
330 let orig_qty_mantissa = cursor.read_i64_le()?;
331 let executed_qty_mantissa = cursor.read_i64_le()?;
332 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
333 let status = cursor.read_u8()?.into();
334 let time_in_force = cursor.read_u8()?.into();
335 let order_type = cursor.read_u8()?.into();
336 let side = cursor.read_u8()?.into();
337 let stop_price_mantissa = cursor.read_optional_i64_le()?;
338
339 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
341
342 cursor.advance(23)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
344
345 cursor.advance(16)?; let _commission_exponent = cursor.read_i8()?;
347
348 cursor.advance(header.block_length as usize - NEW_ORDER_FULL_FIELDS_END)?;
349
350 let fills = decode_fills_cursor(&mut cursor)?;
351
352 let (block_len, count) = cursor.read_group_header()?;
354 cursor.advance(block_len as usize * count as usize)?;
355
356 let symbol = cursor.read_var_string8()?;
357 let client_order_id = cursor.read_var_string8()?;
358
359 Ok(BinanceNewOrderResponse {
360 price_exponent,
361 qty_exponent,
362 order_id,
363 order_list_id,
364 transact_time,
365 price_mantissa,
366 orig_qty_mantissa,
367 executed_qty_mantissa,
368 cummulative_quote_qty_mantissa,
369 status,
370 time_in_force,
371 order_type,
372 side,
373 stop_price_mantissa,
374 working_time,
375 self_trade_prevention_mode,
376 client_order_id,
377 symbol,
378 fills,
379 })
380}
381
382#[allow(dead_code)]
388pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
389 let mut cursor = SbeCursor::new(buf);
390 let header = MessageHeader::decode_cursor(&mut cursor)?;
391 header.validate()?;
392
393 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
394 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
395 }
396
397 cursor.require(header.block_length as usize)?;
398
399 let price_exponent = cursor.read_i8()?;
400 let qty_exponent = cursor.read_i8()?;
401 let order_id = cursor.read_i64_le()?;
402 let order_list_id = cursor.read_optional_i64_le()?;
403 let transact_time = cursor.read_i64_le()?;
404 let price_mantissa = cursor.read_i64_le()?;
405 let orig_qty_mantissa = cursor.read_i64_le()?;
406 let executed_qty_mantissa = cursor.read_i64_le()?;
407 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
408 let status = cursor.read_u8()?.into();
409 let time_in_force = cursor.read_u8()?.into();
410 let order_type = cursor.read_u8()?.into();
411 let side = cursor.read_u8()?.into();
412 let self_trade_prevention_mode = cursor.read_u8()?.into();
413
414 cursor.advance(header.block_length as usize - CANCEL_ORDER_FIELDS_END)?;
415
416 let symbol = cursor.read_var_string8()?;
417 let orig_client_order_id = cursor.read_var_string8()?;
418 let client_order_id = cursor.read_var_string8()?;
419
420 Ok(BinanceCancelOrderResponse {
421 price_exponent,
422 qty_exponent,
423 order_id,
424 order_list_id,
425 transact_time,
426 price_mantissa,
427 orig_qty_mantissa,
428 executed_qty_mantissa,
429 cummulative_quote_qty_mantissa,
430 status,
431 time_in_force,
432 order_type,
433 side,
434 self_trade_prevention_mode,
435 client_order_id,
436 orig_client_order_id,
437 symbol,
438 })
439}
440
441#[allow(dead_code)]
447pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
448 let mut cursor = SbeCursor::new(buf);
449 let header = MessageHeader::decode_cursor(&mut cursor)?;
450 header.validate()?;
451
452 if header.template_id != ORDER_TEMPLATE_ID {
453 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
454 }
455
456 cursor.require(header.block_length as usize)?;
457
458 let price_exponent = cursor.read_i8()?;
459 let qty_exponent = cursor.read_i8()?;
460 let order_id = cursor.read_i64_le()?;
461 let order_list_id = cursor.read_optional_i64_le()?;
462 let price_mantissa = cursor.read_i64_le()?;
463 let orig_qty_mantissa = cursor.read_i64_le()?;
464 let executed_qty_mantissa = cursor.read_i64_le()?;
465 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
466 let status = cursor.read_u8()?.into();
467 let time_in_force = cursor.read_u8()?.into();
468 let order_type = cursor.read_u8()?.into();
469 let side = cursor.read_u8()?.into();
470 let stop_price_mantissa = cursor.read_optional_i64_le()?;
471 let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
472 let time = cursor.read_i64_le()?;
473 let update_time = cursor.read_i64_le()?;
474 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
475 let working_time = cursor.read_optional_i64_le()?;
476 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
477 let self_trade_prevention_mode = cursor.read_u8()?.into();
478
479 cursor.advance(header.block_length as usize - ORDER_FIELDS_END)?;
480
481 let symbol = cursor.read_var_string8()?;
482 let client_order_id = cursor.read_var_string8()?;
483
484 Ok(BinanceOrderResponse {
485 price_exponent,
486 qty_exponent,
487 order_id,
488 order_list_id,
489 price_mantissa,
490 orig_qty_mantissa,
491 executed_qty_mantissa,
492 cummulative_quote_qty_mantissa,
493 status,
494 time_in_force,
495 order_type,
496 side,
497 stop_price_mantissa,
498 iceberg_qty_mantissa,
499 time,
500 update_time,
501 is_working,
502 working_time,
503 orig_quote_order_qty_mantissa,
504 self_trade_prevention_mode,
505 client_order_id,
506 symbol,
507 })
508}
509
510const ORDERS_GROUP_BLOCK_LENGTH: usize = 162;
512
513#[allow(dead_code)]
519pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
520 let mut cursor = SbeCursor::new(buf);
521 let header = MessageHeader::decode_cursor(&mut cursor)?;
522 header.validate()?;
523
524 if header.template_id != ORDERS_TEMPLATE_ID {
525 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
526 }
527
528 let (block_length, count) = cursor.read_group_header()?;
529
530 if count == 0 {
531 return Ok(Vec::new());
532 }
533
534 if block_length as usize != ORDERS_GROUP_BLOCK_LENGTH {
535 return Err(SbeDecodeError::InvalidBlockLength {
536 expected: ORDERS_GROUP_BLOCK_LENGTH as u16,
537 actual: block_length,
538 });
539 }
540
541 let mut orders = Vec::with_capacity(count as usize);
542
543 for _ in 0..count {
544 cursor.require(ORDERS_GROUP_BLOCK_LENGTH)?;
545
546 let price_exponent = cursor.read_i8()?;
547 let qty_exponent = cursor.read_i8()?;
548 let order_id = cursor.read_i64_le()?;
549 let order_list_id = cursor.read_optional_i64_le()?;
550 let price_mantissa = cursor.read_i64_le()?;
551 let orig_qty_mantissa = cursor.read_i64_le()?;
552 let executed_qty_mantissa = cursor.read_i64_le()?;
553 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
554 let status = cursor.read_u8()?.into();
555 let time_in_force = cursor.read_u8()?.into();
556 let order_type = cursor.read_u8()?.into();
557 let side = cursor.read_u8()?.into();
558 let stop_price_mantissa = cursor.read_optional_i64_le()?;
559
560 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
562 let time = cursor.read_i64_le()?;
563 let update_time = cursor.read_i64_le()?;
564 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
565 let working_time = cursor.read_optional_i64_le()?;
566 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
567
568 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
570
571 cursor.advance(28)?; let symbol = cursor.read_var_string8()?;
574 let client_order_id = cursor.read_var_string8()?;
575
576 orders.push(BinanceOrderResponse {
577 price_exponent,
578 qty_exponent,
579 order_id,
580 order_list_id,
581 price_mantissa,
582 orig_qty_mantissa,
583 executed_qty_mantissa,
584 cummulative_quote_qty_mantissa,
585 status,
586 time_in_force,
587 order_type,
588 side,
589 stop_price_mantissa,
590 iceberg_qty_mantissa,
591 time,
592 update_time,
593 is_working,
594 working_time,
595 orig_quote_order_qty_mantissa,
596 self_trade_prevention_mode,
597 client_order_id,
598 symbol,
599 });
600 }
601
602 Ok(orders)
603}
604
605#[allow(dead_code)]
613pub fn decode_cancel_open_orders(
614 buf: &[u8],
615) -> Result<Vec<BinanceCancelOrderResponse>, SbeDecodeError> {
616 let mut cursor = SbeCursor::new(buf);
617 let header = MessageHeader::decode_cursor(&mut cursor)?;
618 header.validate()?;
619
620 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
621 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
622 }
623
624 let (_block_length, count) = cursor.read_group_header()?;
625
626 if count == 0 {
627 return Ok(Vec::new());
628 }
629
630 let mut responses = Vec::with_capacity(count as usize);
631
632 for _ in 0..count {
634 let response_len = cursor.read_u16_le()? as usize;
635 let embedded_bytes = cursor.read_bytes(response_len)?;
636 let cancel_response = decode_cancel_order(embedded_bytes)?;
637 responses.push(cancel_response);
638 }
639
640 Ok(responses)
641}
642
643const ACCOUNT_BLOCK_LENGTH: usize = 64;
645
646const BALANCE_BLOCK_LENGTH: u16 = 17;
648
649#[allow(dead_code)]
655pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
656 let mut cursor = SbeCursor::new(buf);
657 let header = MessageHeader::decode_cursor(&mut cursor)?;
658 header.validate()?;
659
660 if header.template_id != ACCOUNT_TEMPLATE_ID {
661 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
662 }
663
664 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
665
666 let commission_exponent = cursor.read_i8()?;
667 let maker_commission_mantissa = cursor.read_i64_le()?;
668 let taker_commission_mantissa = cursor.read_i64_le()?;
669 let buyer_commission_mantissa = cursor.read_i64_le()?;
670 let seller_commission_mantissa = cursor.read_i64_le()?;
671 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
672 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
673 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
674 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
676 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
677 let update_time = cursor.read_i64_le()?;
678 let account_type_enum = AccountType::from(cursor.read_u8()?);
679 cursor.advance(16)?; let account_type = account_type_enum.to_string();
682
683 let (block_length, balance_count) = cursor.read_group_header()?;
684
685 if block_length != BALANCE_BLOCK_LENGTH {
686 return Err(SbeDecodeError::InvalidBlockLength {
687 expected: BALANCE_BLOCK_LENGTH,
688 actual: block_length,
689 });
690 }
691
692 let mut balances = Vec::with_capacity(balance_count as usize);
693
694 for _ in 0..balance_count {
695 cursor.require(block_length as usize)?;
696
697 let exponent = cursor.read_i8()?;
698 let free_mantissa = cursor.read_i64_le()?;
699 let locked_mantissa = cursor.read_i64_le()?;
700
701 let asset = cursor.read_var_string8()?;
702
703 balances.push(BinanceBalance {
704 asset,
705 free_mantissa,
706 locked_mantissa,
707 exponent,
708 });
709 }
710
711 Ok(BinanceAccountInfo {
712 commission_exponent,
713 maker_commission_mantissa,
714 taker_commission_mantissa,
715 buyer_commission_mantissa,
716 seller_commission_mantissa,
717 can_trade,
718 can_withdraw,
719 can_deposit,
720 require_self_trade_prevention,
721 prevent_sor,
722 update_time,
723 account_type,
724 balances,
725 })
726}
727
728const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
730
731#[allow(dead_code)]
737pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
738 let mut cursor = SbeCursor::new(buf);
739 let header = MessageHeader::decode_cursor(&mut cursor)?;
740 header.validate()?;
741
742 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
743 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
744 }
745
746 let (block_length, trade_count) = cursor.read_group_header()?;
747
748 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
749 return Err(SbeDecodeError::InvalidBlockLength {
750 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
751 actual: block_length,
752 });
753 }
754
755 let mut trades = Vec::with_capacity(trade_count as usize);
756
757 for _ in 0..trade_count {
758 cursor.require(block_length as usize)?;
759
760 let price_exponent = cursor.read_i8()?;
761 let qty_exponent = cursor.read_i8()?;
762 let commission_exponent = cursor.read_i8()?;
763 let id = cursor.read_i64_le()?;
764 let order_id = cursor.read_i64_le()?;
765 let order_list_id = cursor.read_optional_i64_le()?;
766 let price_mantissa = cursor.read_i64_le()?;
767 let qty_mantissa = cursor.read_i64_le()?;
768 let quote_qty_mantissa = cursor.read_i64_le()?;
769 let commission_mantissa = cursor.read_i64_le()?;
770 let time = cursor.read_i64_le()?;
771 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
772 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
773 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
774
775 let symbol = cursor.read_var_string8()?;
776 let commission_asset = cursor.read_var_string8()?;
777
778 trades.push(BinanceAccountTrade {
779 price_exponent,
780 qty_exponent,
781 commission_exponent,
782 id,
783 order_id,
784 order_list_id,
785 price_mantissa,
786 qty_mantissa,
787 quote_qty_mantissa,
788 commission_mantissa,
789 time,
790 is_buyer,
791 is_maker,
792 is_best_match,
793 symbol,
794 commission_asset,
795 });
796 }
797
798 Ok(trades)
799}
800
801const FILLS_BLOCK_LENGTH: u16 = 42;
803
804fn decode_fills_cursor(
806 cursor: &mut SbeCursor<'_>,
807) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
808 let (block_length, count) = cursor.read_group_header()?;
809
810 if block_length != FILLS_BLOCK_LENGTH {
811 return Err(SbeDecodeError::InvalidBlockLength {
812 expected: FILLS_BLOCK_LENGTH,
813 actual: block_length,
814 });
815 }
816
817 let mut fills = Vec::with_capacity(count as usize);
818
819 for _ in 0..count {
820 cursor.require(block_length as usize)?;
821
822 let commission_exponent = cursor.read_i8()?;
823 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
825 let qty_mantissa = cursor.read_i64_le()?;
826 let commission_mantissa = cursor.read_i64_le()?;
827 let trade_id = cursor.read_optional_i64_le()?;
828 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
831
832 fills.push(BinanceOrderFill {
833 price_mantissa,
834 qty_mantissa,
835 commission_mantissa,
836 commission_exponent,
837 commission_asset,
838 trade_id,
839 });
840 }
841
842 Ok(fills)
843}
844
845const SYMBOL_BLOCK_LENGTH: usize = 19;
847
848pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
862 let mut cursor = SbeCursor::new(buf);
863 let header = MessageHeader::decode_cursor(&mut cursor)?;
864 header.validate()?;
865
866 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
867 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
868 }
869
870 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
872 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
873
874 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
876 for _ in 0..exchange_filters_count {
877 cursor.read_var_string8()?;
879 }
880
881 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
883
884 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
885 return Err(SbeDecodeError::InvalidBlockLength {
886 expected: SYMBOL_BLOCK_LENGTH as u16,
887 actual: symbols_block_len,
888 });
889 }
890
891 let mut symbols = Vec::with_capacity(symbols_count as usize);
892
893 for _ in 0..symbols_count {
894 cursor.require(SYMBOL_BLOCK_LENGTH)?;
895
896 let status = cursor.read_u8()?;
898 let base_asset_precision = cursor.read_u8()?;
899 let quote_asset_precision = cursor.read_u8()?;
900 let _base_commission_precision = cursor.read_u8()?;
901 let _quote_commission_precision = cursor.read_u8()?;
902 let order_types = cursor.read_u16_le()?;
903 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
904 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
905 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
906 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
907 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
908 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
909 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
910 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
911 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
912 let _default_self_trade_prevention_mode = cursor.read_u8()?;
913 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
914 let _peg_instructions_allowed = cursor.read_u8()?;
915
916 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
917 let mut filters = BinanceSymbolFiltersSbe::default();
918
919 for _ in 0..filters_count {
920 let filter_bytes = cursor.read_var_bytes8()?;
921
922 let (template_id, offset) = if filter_bytes.len() >= HEADER_LENGTH + 2 {
925 let potential_template = u16::from_le_bytes([filter_bytes[2], filter_bytes[3]]);
926 if potential_template == PRICE_FILTER_TEMPLATE_ID
927 || potential_template == LOT_SIZE_FILTER_TEMPLATE_ID
928 {
929 (potential_template, HEADER_LENGTH)
930 } else {
931 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
932 (raw_template, 2)
933 }
934 } else if filter_bytes.len() >= 2 {
935 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
936 (raw_template, 2)
937 } else {
938 continue;
939 };
940
941 match template_id {
943 PRICE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
944 let price_exp = filter_bytes[offset] as i8;
945 let min_price = i64::from_le_bytes(
946 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
947 );
948 let max_price = i64::from_le_bytes(
949 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
950 );
951 let tick_size = i64::from_le_bytes(
952 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
953 );
954 filters.price_filter = Some(BinancePriceFilterSbe {
955 price_exponent: price_exp,
956 min_price,
957 max_price,
958 tick_size,
959 });
960 }
961 LOT_SIZE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
962 let qty_exp = filter_bytes[offset] as i8;
963 let min_qty = i64::from_le_bytes(
964 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
965 );
966 let max_qty = i64::from_le_bytes(
967 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
968 );
969 let step_size = i64::from_le_bytes(
970 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
971 );
972 filters.lot_size_filter = Some(BinanceLotSizeFilterSbe {
973 qty_exponent: qty_exp,
974 min_qty,
975 max_qty,
976 step_size,
977 });
978 }
979 _ => {}
980 }
981 }
982
983 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
985 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
986 for _ in 0..perm_sets_count {
987 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
989 let mut perm_set = Vec::with_capacity(perms_count as usize);
990 for _ in 0..perms_count {
991 let perm = cursor.read_var_string8()?;
992 perm_set.push(perm);
993 }
994 permissions.push(perm_set);
995 }
996
997 let symbol = cursor.read_var_string8()?;
999 let base_asset = cursor.read_var_string8()?;
1000 let quote_asset = cursor.read_var_string8()?;
1001
1002 symbols.push(BinanceSymbolSbe {
1003 symbol,
1004 base_asset,
1005 quote_asset,
1006 base_asset_precision,
1007 quote_asset_precision,
1008 status,
1009 order_types,
1010 iceberg_allowed,
1011 oco_allowed,
1012 oto_allowed,
1013 quote_order_qty_market_allowed,
1014 allow_trailing_stop,
1015 cancel_replace_allowed,
1016 amend_allowed,
1017 is_spot_trading_allowed,
1018 is_margin_trading_allowed,
1019 filters,
1020 permissions,
1021 });
1022 }
1023
1024 Ok(BinanceExchangeInfoSbe { symbols })
1027}
1028
1029#[cfg(test)]
1030mod tests {
1031 use rstest::rstest;
1032
1033 use super::*;
1034
1035 const NEW_ORDER_FULL_BLOCK_LENGTH: usize = 153;
1037
1038 const CANCEL_ORDER_BLOCK_LENGTH: usize = 137;
1040
1041 const ORDER_BLOCK_LENGTH: usize = 153;
1043
1044 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
1045 let mut buf = [0u8; 8];
1046 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1047 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
1048 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
1049 buf[6..8].copy_from_slice(&version.to_le_bytes());
1050 buf
1051 }
1052
1053 #[rstest]
1054 fn test_decode_ping_valid() {
1055 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1057 assert!(decode_ping(&buf).is_ok());
1058 }
1059
1060 #[rstest]
1061 fn test_decode_ping_buffer_too_short() {
1062 let buf = [0u8; 4];
1063 let err = decode_ping(&buf).unwrap_err();
1064 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1065 }
1066
1067 #[rstest]
1068 fn test_decode_ping_schema_mismatch() {
1069 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
1070 let err = decode_ping(&buf).unwrap_err();
1071 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
1072 }
1073
1074 #[rstest]
1075 fn test_decode_ping_wrong_template() {
1076 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1077 let err = decode_ping(&buf).unwrap_err();
1078 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
1079 }
1080
1081 #[rstest]
1082 fn test_decode_server_time_valid() {
1083 let header = create_header(
1085 8,
1086 SERVER_TIME_TEMPLATE_ID,
1087 SBE_SCHEMA_ID,
1088 SBE_SCHEMA_VERSION,
1089 );
1090 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
1093 buf.extend_from_slice(&header);
1094 buf.extend_from_slice(×tamp.to_le_bytes());
1095
1096 let result = decode_server_time(&buf).unwrap();
1097 assert_eq!(result, timestamp);
1098 }
1099
1100 #[rstest]
1101 fn test_decode_server_time_buffer_too_short() {
1102 let buf = create_header(
1104 8,
1105 SERVER_TIME_TEMPLATE_ID,
1106 SBE_SCHEMA_ID,
1107 SBE_SCHEMA_VERSION,
1108 );
1109 let err = decode_server_time(&buf).unwrap_err();
1110 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1111 }
1112
1113 #[rstest]
1114 fn test_decode_server_time_wrong_template() {
1115 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1116 let mut buf = Vec::with_capacity(16);
1117 buf.extend_from_slice(&header);
1118 buf.extend_from_slice(&0i64.to_le_bytes());
1119
1120 let err = decode_server_time(&buf).unwrap_err();
1121 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1122 }
1123
1124 #[rstest]
1125 fn test_decode_server_time_version_mismatch() {
1126 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, 99);
1127 let mut buf = Vec::with_capacity(16);
1128 buf.extend_from_slice(&header);
1129 buf.extend_from_slice(&0i64.to_le_bytes());
1130
1131 let err = decode_server_time(&buf).unwrap_err();
1132 assert!(matches!(err, SbeDecodeError::VersionMismatch { .. }));
1133 }
1134
1135 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
1136 let mut buf = [0u8; 6];
1137 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1138 buf[2..6].copy_from_slice(&count.to_le_bytes());
1139 buf
1140 }
1141
1142 #[rstest]
1143 fn test_decode_depth_valid() {
1144 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1146
1147 let mut buf = Vec::new();
1148 buf.extend_from_slice(&header);
1149
1150 let last_update_id: i64 = 123456789;
1152 let price_exponent: i8 = -8;
1153 let qty_exponent: i8 = -8;
1154 buf.extend_from_slice(&last_update_id.to_le_bytes());
1155 buf.push(price_exponent as u8);
1156 buf.push(qty_exponent as u8);
1157
1158 buf.extend_from_slice(&create_group_header(16, 2));
1160 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1162 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1163 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1165 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1166
1167 buf.extend_from_slice(&create_group_header(16, 1));
1169 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1171 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1172
1173 let depth = decode_depth(&buf).unwrap();
1174
1175 assert_eq!(depth.last_update_id, 123456789);
1176 assert_eq!(depth.price_exponent, -8);
1177 assert_eq!(depth.qty_exponent, -8);
1178 assert_eq!(depth.bids.len(), 2);
1179 assert_eq!(depth.asks.len(), 1);
1180 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1181 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1182 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1183 }
1184
1185 #[rstest]
1186 fn test_decode_depth_empty_book() {
1187 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1188
1189 let mut buf = Vec::new();
1190 buf.extend_from_slice(&header);
1191 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1197 buf.extend_from_slice(&create_group_header(16, 0));
1199
1200 let depth = decode_depth(&buf).unwrap();
1201
1202 assert!(depth.bids.is_empty());
1203 assert!(depth.asks.is_empty());
1204 }
1205
1206 #[rstest]
1207 fn test_decode_trades_valid() {
1208 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1210
1211 let mut buf = Vec::new();
1212 buf.extend_from_slice(&header);
1213
1214 let price_exponent: i8 = -8;
1216 let qty_exponent: i8 = -8;
1217 buf.push(price_exponent as u8);
1218 buf.push(qty_exponent as u8);
1219
1220 buf.extend_from_slice(&create_group_header(42, 1));
1222
1223 let trade_id: i64 = 999;
1225 let price: i64 = 100_000_000_000;
1226 let qty: i64 = 10_000_000;
1227 let quote_qty: i64 = 1_000_000_000_000;
1228 let time: i64 = 1734300000000;
1229 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1233 buf.extend_from_slice(&price.to_le_bytes());
1234 buf.extend_from_slice(&qty.to_le_bytes());
1235 buf.extend_from_slice("e_qty.to_le_bytes());
1236 buf.extend_from_slice(&time.to_le_bytes());
1237 buf.push(is_buyer_maker);
1238 buf.push(is_best_match);
1239
1240 let trades = decode_trades(&buf).unwrap();
1241
1242 assert_eq!(trades.price_exponent, -8);
1243 assert_eq!(trades.qty_exponent, -8);
1244 assert_eq!(trades.trades.len(), 1);
1245 assert_eq!(trades.trades[0].id, 999);
1246 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1247 assert!(trades.trades[0].is_buyer_maker);
1248 assert!(trades.trades[0].is_best_match);
1249 }
1250
1251 #[rstest]
1252 fn test_decode_trades_empty() {
1253 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1254
1255 let mut buf = Vec::new();
1256 buf.extend_from_slice(&header);
1257 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1262
1263 let trades = decode_trades(&buf).unwrap();
1264
1265 assert!(trades.trades.is_empty());
1266 }
1267
1268 #[rstest]
1269 fn test_decode_depth_wrong_template() {
1270 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1271
1272 let mut buf = Vec::new();
1273 buf.extend_from_slice(&header);
1274 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1277 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1278 }
1279
1280 #[rstest]
1281 fn test_decode_trades_wrong_template() {
1282 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1283
1284 let mut buf = Vec::new();
1285 buf.extend_from_slice(&header);
1286 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1289 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1290 }
1291
1292 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1293 buf.push(s.len() as u8);
1294 buf.extend_from_slice(s.as_bytes());
1295 }
1296
1297 #[rstest]
1298 fn test_decode_order_valid() {
1299 let header = create_header(
1300 ORDER_BLOCK_LENGTH as u16,
1301 ORDER_TEMPLATE_ID,
1302 SBE_SCHEMA_ID,
1303 SBE_SCHEMA_VERSION,
1304 );
1305
1306 let mut buf = Vec::new();
1307 buf.extend_from_slice(&header);
1308
1309 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 {
1333 buf.push(0);
1334 }
1335
1336 write_var_string(&mut buf, "BTCUSDT");
1337 write_var_string(&mut buf, "my-order-123");
1338
1339 let order = decode_order(&buf).unwrap();
1340
1341 assert_eq!(order.order_id, 12345);
1342 assert!(order.order_list_id.is_none());
1343 assert_eq!(order.price_exponent, -8);
1344 assert_eq!(order.price_mantissa, 100_000_000_000);
1345 assert!(order.stop_price_mantissa.is_none());
1346 assert!(order.iceberg_qty_mantissa.is_none());
1347 assert!(order.is_working);
1348 assert_eq!(order.working_time, Some(1734300000500));
1349 assert_eq!(order.symbol, "BTCUSDT");
1350 assert_eq!(order.client_order_id, "my-order-123");
1351 }
1352
1353 #[rstest]
1354 fn test_decode_order_future_block_length() {
1355 const FUTURE_BLOCK_LENGTH: u16 = ORDER_BLOCK_LENGTH as u16 + 4;
1357 let header = create_header(
1358 FUTURE_BLOCK_LENGTH,
1359 ORDER_TEMPLATE_ID,
1360 SBE_SCHEMA_ID,
1361 SBE_SCHEMA_VERSION,
1362 );
1363
1364 let mut buf = Vec::new();
1365 buf.extend_from_slice(&header);
1366
1367 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&5_000_000i64.to_le_bytes()); buf.extend_from_slice(&500_000_000i64.to_le_bytes()); buf.push(1); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&1734300001000i64.to_le_bytes()); buf.push(1); buf.extend_from_slice(&1734300000500i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); while buf.len() < 8 + FUTURE_BLOCK_LENGTH as usize {
1389 buf.push(0); }
1391
1392 write_var_string(&mut buf, "ETHUSDT");
1393 write_var_string(&mut buf, "order-future");
1394
1395 let order = decode_order(&buf).unwrap();
1396
1397 assert_eq!(order.order_id, 12345);
1398 assert_eq!(order.symbol, "ETHUSDT");
1399 assert_eq!(order.client_order_id, "order-future");
1400 }
1401
1402 #[rstest]
1403 fn test_decode_orders_multiple() {
1404 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1406
1407 let mut buf = Vec::new();
1408 buf.extend_from_slice(&header);
1409
1410 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 2));
1412
1413 let order1_start = buf.len();
1415 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 {
1438 buf.push(0);
1439 }
1440 write_var_string(&mut buf, "BTCUSDT");
1441 write_var_string(&mut buf, "order-1");
1442
1443 let order2_start = buf.len();
1445 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 {
1467 buf.push(0);
1468 }
1469 write_var_string(&mut buf, "ETHUSDT");
1470 write_var_string(&mut buf, "order-2");
1471
1472 let orders = decode_orders(&buf).unwrap();
1473
1474 assert_eq!(orders.len(), 2);
1475 assert_eq!(orders[0].order_id, 1001);
1476 assert_eq!(orders[0].symbol, "BTCUSDT");
1477 assert_eq!(orders[0].client_order_id, "order-1");
1478 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
1479
1480 assert_eq!(orders[1].order_id, 2002);
1481 assert_eq!(orders[1].symbol, "ETHUSDT");
1482 assert_eq!(orders[1].client_order_id, "order-2");
1483 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
1484 }
1485
1486 #[rstest]
1487 fn test_decode_orders_empty() {
1488 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1489
1490 let mut buf = Vec::new();
1491 buf.extend_from_slice(&header);
1492 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 0));
1493
1494 let orders = decode_orders(&buf).unwrap();
1495 assert!(orders.is_empty());
1496 }
1497
1498 #[rstest]
1499 fn test_decode_orders_truncated_var_string() {
1500 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1501
1502 let mut buf = Vec::new();
1503 buf.extend_from_slice(&header);
1504 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1505
1506 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1508
1509 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
1514 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1515 }
1516
1517 #[rstest]
1518 fn test_decode_orders_invalid_utf8() {
1519 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1520
1521 let mut buf = Vec::new();
1522 buf.extend_from_slice(&header);
1523 buf.extend_from_slice(&create_group_header(ORDERS_GROUP_BLOCK_LENGTH as u16, 1));
1524
1525 buf.extend_from_slice(&[0u8; ORDERS_GROUP_BLOCK_LENGTH]);
1526
1527 buf.push(4);
1529 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
1530
1531 let err = decode_orders(&buf).unwrap_err();
1532 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
1533 }
1534
1535 #[rstest]
1536 fn test_decode_cancel_order_valid() {
1537 let header = create_header(
1538 CANCEL_ORDER_BLOCK_LENGTH as u16,
1539 CANCEL_ORDER_TEMPLATE_ID,
1540 SBE_SCHEMA_ID,
1541 SBE_SCHEMA_VERSION,
1542 );
1543
1544 let mut buf = Vec::new();
1545 buf.extend_from_slice(&header);
1546
1547 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 {
1564 buf.push(0);
1565 }
1566
1567 write_var_string(&mut buf, "BTCUSDT");
1568 write_var_string(&mut buf, "orig-client-id");
1569 write_var_string(&mut buf, "new-client-id");
1570
1571 let cancel = decode_cancel_order(&buf).unwrap();
1572
1573 assert_eq!(cancel.order_id, 99999);
1574 assert!(cancel.order_list_id.is_none());
1575 assert_eq!(cancel.symbol, "BTCUSDT");
1576 assert_eq!(cancel.orig_client_order_id, "orig-client-id");
1577 assert_eq!(cancel.client_order_id, "new-client-id");
1578 }
1579
1580 #[rstest]
1581 fn test_decode_cancel_order_future_block_length() {
1582 const FUTURE_BLOCK_LENGTH: u16 = CANCEL_ORDER_BLOCK_LENGTH as u16 + 4;
1584 let header = create_header(
1585 FUTURE_BLOCK_LENGTH,
1586 CANCEL_ORDER_TEMPLATE_ID,
1587 SBE_SCHEMA_ID,
1588 SBE_SCHEMA_VERSION,
1589 );
1590
1591 let mut buf = Vec::new();
1592 buf.extend_from_slice(&header);
1593
1594 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&99999i64.to_le_bytes()); buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1734300000000i64.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000i64.to_le_bytes()); buf.push(4); buf.push(1); buf.push(1); buf.push(1); buf.push(0); while buf.len() < 8 + FUTURE_BLOCK_LENGTH as usize {
1610 buf.push(0); }
1612
1613 write_var_string(&mut buf, "BTCUSDT");
1614 write_var_string(&mut buf, "orig-id");
1615 write_var_string(&mut buf, "new-id");
1616
1617 let cancel = decode_cancel_order(&buf).unwrap();
1618
1619 assert_eq!(cancel.order_id, 99999);
1620 assert_eq!(cancel.symbol, "BTCUSDT");
1621 assert_eq!(cancel.orig_client_order_id, "orig-id");
1622 assert_eq!(cancel.client_order_id, "new-id");
1623 }
1624
1625 #[rstest]
1626 fn test_decode_account_with_balances() {
1627 let header = create_header(
1628 ACCOUNT_BLOCK_LENGTH as u16,
1629 ACCOUNT_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.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 {
1654 buf.push(0);
1655 }
1656
1657 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
1659
1660 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");
1665
1666 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");
1671
1672 let account = decode_account(&buf).unwrap();
1673
1674 assert!(account.can_trade);
1675 assert!(account.can_withdraw);
1676 assert!(account.can_deposit);
1677 assert_eq!(account.balances.len(), 2);
1678 assert_eq!(account.balances[0].asset, "BTC");
1679 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
1680 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
1681 assert_eq!(account.balances[1].asset, "USDT");
1682 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
1683 }
1684
1685 #[rstest]
1686 fn test_decode_account_empty_balances() {
1687 let header = create_header(
1688 ACCOUNT_BLOCK_LENGTH as u16,
1689 ACCOUNT_TEMPLATE_ID,
1690 SBE_SCHEMA_ID,
1691 SBE_SCHEMA_VERSION,
1692 );
1693
1694 let mut buf = Vec::new();
1695 buf.extend_from_slice(&header);
1696
1697 buf.push((-8i8) as u8);
1699 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
1703
1704 let account = decode_account(&buf).unwrap();
1705 assert!(account.balances.is_empty());
1706 }
1707
1708 #[rstest]
1709 fn test_decode_account_trades_multiple() {
1710 let header = create_header(
1711 0,
1712 ACCOUNT_TRADES_TEMPLATE_ID,
1713 SBE_SCHEMA_ID,
1714 SBE_SCHEMA_VERSION,
1715 );
1716
1717 let mut buf = Vec::new();
1718 buf.extend_from_slice(&header);
1719
1720 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
1722
1723 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");
1739 write_var_string(&mut buf, "BNB");
1740
1741 buf.push((-8i8) as u8);
1743 buf.push((-8i8) as u8);
1744 buf.push((-8i8) as u8);
1745 buf.extend_from_slice(&1002i64.to_le_bytes());
1746 buf.extend_from_slice(&5002i64.to_le_bytes());
1747 buf.extend_from_slice(&i64::MIN.to_le_bytes());
1748 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
1749 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
1750 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
1751 buf.extend_from_slice(&50_000i64.to_le_bytes());
1752 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
1753 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
1757 write_var_string(&mut buf, "USDT");
1758
1759 let trades = decode_account_trades(&buf).unwrap();
1760
1761 assert_eq!(trades.len(), 2);
1762 assert_eq!(trades[0].id, 1001);
1763 assert_eq!(trades[0].order_id, 5001);
1764 assert!(trades[0].order_list_id.is_none());
1765 assert_eq!(trades[0].symbol, "BTCUSDT");
1766 assert_eq!(trades[0].commission_asset, "BNB");
1767 assert!(trades[0].is_buyer);
1768 assert!(!trades[0].is_maker);
1769
1770 assert_eq!(trades[1].id, 1002);
1771 assert_eq!(trades[1].symbol, "ETHUSDT");
1772 assert_eq!(trades[1].commission_asset, "USDT");
1773 assert!(!trades[1].is_buyer);
1774 assert!(trades[1].is_maker);
1775 }
1776
1777 #[rstest]
1778 fn test_decode_account_trades_empty() {
1779 let header = create_header(
1780 0,
1781 ACCOUNT_TRADES_TEMPLATE_ID,
1782 SBE_SCHEMA_ID,
1783 SBE_SCHEMA_VERSION,
1784 );
1785
1786 let mut buf = Vec::new();
1787 buf.extend_from_slice(&header);
1788 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
1789
1790 let trades = decode_account_trades(&buf).unwrap();
1791 assert!(trades.is_empty());
1792 }
1793
1794 #[rstest]
1795 fn test_decode_exchange_info_single_symbol() {
1796 let header = create_header(
1797 0,
1798 EXCHANGE_INFO_TEMPLATE_ID,
1799 SBE_SCHEMA_ID,
1800 SBE_SCHEMA_VERSION,
1801 );
1802
1803 let mut buf = Vec::new();
1804 buf.extend_from_slice(&header);
1805
1806 buf.extend_from_slice(&create_group_header(11, 0));
1808
1809 buf.extend_from_slice(&create_group_header(0, 0));
1811
1812 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
1814
1815 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));
1837
1838 buf.extend_from_slice(&create_group_header(0, 1));
1840 buf.extend_from_slice(&create_group_header(0, 1));
1841 write_var_string(&mut buf, "SPOT");
1842
1843 write_var_string(&mut buf, "BTCUSDT");
1845 write_var_string(&mut buf, "BTC");
1846 write_var_string(&mut buf, "USDT");
1847
1848 let info = decode_exchange_info(&buf).unwrap();
1849
1850 assert_eq!(info.symbols.len(), 1);
1851 let symbol = &info.symbols[0];
1852 assert_eq!(symbol.symbol, "BTCUSDT");
1853 assert_eq!(symbol.base_asset, "BTC");
1854 assert_eq!(symbol.quote_asset, "USDT");
1855 assert_eq!(symbol.base_asset_precision, 8);
1856 assert_eq!(symbol.quote_asset_precision, 8);
1857 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
1859 assert!(symbol.iceberg_allowed);
1860 assert!(symbol.oco_allowed);
1861 assert!(!symbol.oto_allowed);
1862 assert!(symbol.quote_order_qty_market_allowed);
1863 assert!(symbol.allow_trailing_stop);
1864 assert!(symbol.cancel_replace_allowed);
1865 assert!(!symbol.amend_allowed);
1866 assert!(symbol.is_spot_trading_allowed);
1867 assert!(!symbol.is_margin_trading_allowed);
1868 assert!(symbol.filters.price_filter.is_none()); assert!(symbol.filters.lot_size_filter.is_none());
1870 assert_eq!(symbol.permissions.len(), 1);
1871 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
1872 }
1873
1874 #[rstest]
1875 fn test_decode_exchange_info_empty() {
1876 let header = create_header(
1877 0,
1878 EXCHANGE_INFO_TEMPLATE_ID,
1879 SBE_SCHEMA_ID,
1880 SBE_SCHEMA_VERSION,
1881 );
1882
1883 let mut buf = Vec::new();
1884 buf.extend_from_slice(&header);
1885
1886 buf.extend_from_slice(&create_group_header(11, 0));
1888
1889 buf.extend_from_slice(&create_group_header(0, 0));
1891
1892 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
1894
1895 let info = decode_exchange_info(&buf).unwrap();
1896 assert!(info.symbols.is_empty());
1897 }
1898
1899 #[rstest]
1900 fn test_decode_exchange_info_wrong_template() {
1901 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1902
1903 let mut buf = Vec::new();
1904 buf.extend_from_slice(&header);
1905
1906 let err = decode_exchange_info(&buf).unwrap_err();
1907 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1908 }
1909
1910 #[rstest]
1911 fn test_decode_exchange_info_multiple_symbols() {
1912 let header = create_header(
1913 0,
1914 EXCHANGE_INFO_TEMPLATE_ID,
1915 SBE_SCHEMA_ID,
1916 SBE_SCHEMA_VERSION,
1917 );
1918
1919 let mut buf = Vec::new();
1920 buf.extend_from_slice(&header);
1921
1922 buf.extend_from_slice(&create_group_header(11, 0));
1924
1925 buf.extend_from_slice(&create_group_header(0, 0));
1927
1928 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
1930
1931 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");
1953 write_var_string(&mut buf, "BTC");
1954 write_var_string(&mut buf, "USDT");
1955
1956 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");
1978 write_var_string(&mut buf, "ETH");
1979 write_var_string(&mut buf, "USDT");
1980
1981 let info = decode_exchange_info(&buf).unwrap();
1982
1983 assert_eq!(info.symbols.len(), 2);
1984 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
1985 assert_eq!(info.symbols[0].base_asset, "BTC");
1986 assert!(!info.symbols[0].is_margin_trading_allowed);
1987
1988 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
1989 assert_eq!(info.symbols[1].base_asset, "ETH");
1990 assert!(info.symbols[1].is_margin_trading_allowed);
1991 }
1992
1993 #[rstest]
1994 fn test_decode_klines_valid() {
1995 let header = create_header(2, KLINES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1996
1997 let mut buf = Vec::new();
1998 buf.extend_from_slice(&header);
1999 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&create_group_header(KLINES_BLOCK_LENGTH, 1));
2002 buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&12_500i64.to_le_bytes()); buf.extend_from_slice(&11_900i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&1_234_500i128.to_le_bytes()); buf.extend_from_slice(&1_700_000_059_999_000i64.to_le_bytes()); buf.extend_from_slice(&2_345_600i128.to_le_bytes()); buf.extend_from_slice(&100i64.to_le_bytes()); buf.extend_from_slice(&600_000i128.to_le_bytes()); buf.extend_from_slice(&1_200_000i128.to_le_bytes()); let klines = decode_klines(&buf).unwrap();
2015
2016 assert_eq!(klines.price_exponent, -2);
2017 assert_eq!(klines.qty_exponent, -4);
2018 assert_eq!(klines.klines.len(), 1);
2019 assert_eq!(klines.klines[0].open_time, 1_700_000_000_000_000);
2020 assert_eq!(klines.klines[0].close_price, 12_345);
2021 assert_eq!(i128::from_le_bytes(klines.klines[0].volume), 1_234_500);
2022 assert_eq!(klines.klines[0].num_trades, 100);
2023 }
2024
2025 #[rstest]
2026 fn test_decode_new_order_full_valid() {
2027 let header = create_header(
2028 NEW_ORDER_FULL_BLOCK_LENGTH as u16,
2029 NEW_ORDER_FULL_TEMPLATE_ID,
2030 SBE_SCHEMA_ID,
2031 SBE_SCHEMA_VERSION,
2032 );
2033
2034 let mut buf = Vec::new();
2035 buf.extend_from_slice(&header);
2036
2037 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&99i64.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&25_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&123_450_000i64.to_le_bytes()); buf.push(2); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&1_700_000_000_000_500i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 23]); buf.push(0); buf.extend_from_slice(&[0u8; 16]); buf.push((-8i8) as u8); buf.extend_from_slice(&[0u8; 18]); buf.extend_from_slice(&create_group_header(FILLS_BLOCK_LENGTH, 1));
2060 buf.push((-8i8) as u8); buf.push(0); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&555i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); write_var_string(&mut buf, "USDT");
2068
2069 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2071 write_var_string(&mut buf, "client-123");
2072
2073 let response = decode_new_order_full(&buf).unwrap();
2074
2075 assert_eq!(response.order_id, 12345);
2076 assert_eq!(response.order_list_id, Some(99));
2077 assert_eq!(response.transact_time, 1_700_000_000_000_000);
2078 assert_eq!(response.price_mantissa, 12_345);
2079 assert_eq!(response.orig_qty_mantissa, 25_000);
2080 assert_eq!(response.executed_qty_mantissa, 10_000);
2081 assert_eq!(response.stop_price_mantissa, Some(12_000));
2082 assert_eq!(response.working_time, Some(1_700_000_000_000_500));
2083 assert_eq!(response.symbol, "ETHUSDT");
2084 assert_eq!(response.client_order_id, "client-123");
2085 assert_eq!(response.fills.len(), 1);
2086 assert_eq!(response.fills[0].price_mantissa, 12_345);
2087 assert_eq!(response.fills[0].qty_mantissa, 10_000);
2088 assert_eq!(response.fills[0].trade_id, Some(555));
2089 assert_eq!(response.fills[0].commission_asset, "USDT");
2090 }
2091
2092 #[rstest]
2093 fn test_decode_new_order_full_v3_block_length() {
2094 const V3_BLOCK_LENGTH: u16 = 154;
2097 let header = create_header(
2098 V3_BLOCK_LENGTH,
2099 NEW_ORDER_FULL_TEMPLATE_ID,
2100 SBE_SCHEMA_ID,
2101 SBE_SCHEMA_VERSION,
2102 );
2103
2104 let mut buf = Vec::new();
2105 buf.extend_from_slice(&header);
2106
2107 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&12345i64.to_le_bytes()); buf.extend_from_slice(&99i64.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&25_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&123_450_000i64.to_le_bytes()); buf.push(2); buf.push(1); buf.push(1); buf.push(1); buf.extend_from_slice(&12_000i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 16]); buf.extend_from_slice(&1_700_000_000_000_500i64.to_le_bytes()); buf.extend_from_slice(&[0u8; 23]); buf.push(0); buf.extend_from_slice(&[0u8; 16]); buf.push((-8i8) as u8); buf.extend_from_slice(&[0u8; 18]); buf.push(0xFF); buf.extend_from_slice(&create_group_header(FILLS_BLOCK_LENGTH, 1));
2131 buf.push((-8i8) as u8); buf.push(0); buf.extend_from_slice(&12_345i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&10_000i64.to_le_bytes()); buf.extend_from_slice(&555i64.to_le_bytes()); buf.extend_from_slice(&0i64.to_le_bytes()); write_var_string(&mut buf, "USDT");
2139
2140 buf.extend_from_slice(&create_group_header(0, 0)); write_var_string(&mut buf, "ETHUSDT");
2142 write_var_string(&mut buf, "client-456");
2143
2144 let response = decode_new_order_full(&buf).unwrap();
2145
2146 assert_eq!(response.order_id, 12345);
2147 assert_eq!(response.symbol, "ETHUSDT");
2148 assert_eq!(response.client_order_id, "client-456");
2149 assert_eq!(response.fills.len(), 1);
2150 assert_eq!(response.fills[0].price_mantissa, 12_345);
2151 }
2152
2153 #[rstest]
2154 fn test_decode_cancel_open_orders_valid() {
2155 let header = create_header(
2156 0,
2157 CANCEL_OPEN_ORDERS_TEMPLATE_ID,
2158 SBE_SCHEMA_ID,
2159 SBE_SCHEMA_VERSION,
2160 );
2161 let response_one = create_cancel_order_response_buffer(111, "ETHUSDT", "orig-1", "new-1");
2162 let response_two = create_cancel_order_response_buffer(222, "BTCUSDT", "orig-2", "new-2");
2163
2164 let mut buf = Vec::new();
2165 buf.extend_from_slice(&header);
2166 buf.extend_from_slice(&create_group_header(0, 2));
2167 buf.extend_from_slice(&(response_one.len() as u16).to_le_bytes());
2168 buf.extend_from_slice(&response_one);
2169 buf.extend_from_slice(&(response_two.len() as u16).to_le_bytes());
2170 buf.extend_from_slice(&response_two);
2171
2172 let responses = decode_cancel_open_orders(&buf).unwrap();
2173
2174 assert_eq!(responses.len(), 2);
2175 assert_eq!(responses[0].order_id, 111);
2176 assert_eq!(responses[0].symbol, "ETHUSDT");
2177 assert_eq!(responses[0].orig_client_order_id, "orig-1");
2178 assert_eq!(responses[0].client_order_id, "new-1");
2179 assert_eq!(responses[1].order_id, 222);
2180 assert_eq!(responses[1].symbol, "BTCUSDT");
2181 assert_eq!(responses[1].orig_client_order_id, "orig-2");
2182 assert_eq!(responses[1].client_order_id, "new-2");
2183 }
2184
2185 fn create_cancel_order_response_buffer(
2186 order_id: i64,
2187 symbol: &str,
2188 orig_client_order_id: &str,
2189 client_order_id: &str,
2190 ) -> Vec<u8> {
2191 let header = create_header(
2192 CANCEL_ORDER_BLOCK_LENGTH as u16,
2193 CANCEL_ORDER_TEMPLATE_ID,
2194 SBE_SCHEMA_ID,
2195 SBE_SCHEMA_VERSION,
2196 );
2197
2198 let mut buf = Vec::new();
2199 buf.extend_from_slice(&header);
2200 buf.push((-8i8) as u8); buf.push((-8i8) as u8); buf.extend_from_slice(&order_id.to_le_bytes());
2203 buf.extend_from_slice(&i64::MIN.to_le_bytes()); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&100_000_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&10_000_000i64.to_le_bytes()); buf.extend_from_slice(&1_000_000_000i64.to_le_bytes()); buf.push(4); buf.push(1); buf.push(1); buf.push(1); buf.push(0); while buf.len() < 8 + CANCEL_ORDER_BLOCK_LENGTH {
2216 buf.push(0);
2217 }
2218
2219 write_var_string(&mut buf, symbol);
2220 write_var_string(&mut buf, orig_client_order_id);
2221 write_var_string(&mut buf, client_order_id);
2222
2223 buf
2224 }
2225}