1use rust_decimal::Decimal;
22
23use super::{
24 error::SbeDecodeError,
25 models::{
26 BinanceAccountInfo, BinanceAccountTrade, BinanceAggTrade, BinanceAggTrades, BinanceBalance,
27 BinanceCancelOpenOrdersResponse, BinanceCancelOrderListOrder,
28 BinanceCancelOrderListResponse, BinanceCancelOrderResponse, BinanceDepth,
29 BinanceExchangeInfoSbe, BinanceKline, BinanceKlines, BinanceLotSizeFilterSbe,
30 BinanceNewOrderResponse, BinanceNotionalFilter, BinanceOrderFill, BinanceOrderResponse,
31 BinancePriceFilterSbe, BinancePriceLevel, BinanceSymbolFiltersSbe, BinanceSymbolSbe,
32 BinanceTrade, BinanceTrades,
33 },
34};
35use crate::spot::sbe::{
36 cursor::SbeCursor,
37 spot::{
38 SBE_SCHEMA_ID, account_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TEMPLATE_ID,
39 account_trades_response_codec::SBE_TEMPLATE_ID as ACCOUNT_TRADES_TEMPLATE_ID,
40 account_type::AccountType,
41 agg_trades_response_codec::SBE_TEMPLATE_ID as AGG_TRADES_TEMPLATE_ID, bool_enum::BoolEnum,
42 cancel_open_orders_response_codec::SBE_TEMPLATE_ID as CANCEL_OPEN_ORDERS_TEMPLATE_ID,
43 cancel_order_list_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_LIST_TEMPLATE_ID,
44 cancel_order_response_codec::SBE_TEMPLATE_ID as CANCEL_ORDER_TEMPLATE_ID,
45 cancel_replace_order_response_codec::SBE_TEMPLATE_ID as CANCEL_REPLACE_TEMPLATE_ID,
46 cancel_replace_status::CancelReplaceStatus,
47 depth_response_codec::SBE_TEMPLATE_ID as DEPTH_TEMPLATE_ID,
48 exchange_info_response_codec::SBE_TEMPLATE_ID as EXCHANGE_INFO_TEMPLATE_ID,
49 klines_response_codec::SBE_TEMPLATE_ID as KLINES_TEMPLATE_ID,
50 lot_size_filter_codec::SBE_TEMPLATE_ID as LOT_SIZE_FILTER_TEMPLATE_ID,
51 message_header_codec::ENCODED_LENGTH as HEADER_LENGTH,
52 min_notional_filter_codec::SBE_TEMPLATE_ID as MIN_NOTIONAL_FILTER_TEMPLATE_ID,
53 new_order_full_response_codec::SBE_TEMPLATE_ID as NEW_ORDER_FULL_TEMPLATE_ID,
54 notional_filter_codec::SBE_TEMPLATE_ID as NOTIONAL_FILTER_TEMPLATE_ID,
55 order_response_codec::SBE_TEMPLATE_ID as ORDER_TEMPLATE_ID,
56 orders_response_codec::SBE_TEMPLATE_ID as ORDERS_TEMPLATE_ID,
57 ping_response_codec::SBE_TEMPLATE_ID as PING_TEMPLATE_ID,
58 price_filter_codec::SBE_TEMPLATE_ID as PRICE_FILTER_TEMPLATE_ID,
59 server_time_response_codec::SBE_TEMPLATE_ID as SERVER_TIME_TEMPLATE_ID,
60 trades_response_codec::SBE_TEMPLATE_ID as TRADES_TEMPLATE_ID,
61 },
62};
63
64#[derive(Debug, Clone, Copy)]
66struct MessageHeader {
67 block_length: u16,
68 template_id: u16,
69 schema_id: u16,
70 version: u16,
71}
72
73impl MessageHeader {
74 fn decode_cursor(cursor: &mut SbeCursor<'_>) -> Result<Self, SbeDecodeError> {
76 cursor.require(HEADER_LENGTH)?;
77 let block_length = cursor.read_u16_le()?;
78 let template_id = cursor.read_u16_le()?;
79 let schema_id = cursor.read_u16_le()?;
80 let version = cursor.read_u16_le()?;
81 Ok(Self {
82 block_length,
83 template_id,
84 schema_id,
85 version,
86 })
87 }
88
89 fn validate(&self) -> Result<(), SbeDecodeError> {
106 if self.schema_id != SBE_SCHEMA_ID {
107 return Err(SbeDecodeError::SchemaMismatch {
108 expected: SBE_SCHEMA_ID,
109 actual: self.schema_id,
110 });
111 }
112 Ok(())
113 }
114}
115
116pub fn decode_ping(buf: &[u8]) -> Result<(), SbeDecodeError> {
124 let mut cursor = SbeCursor::new(buf);
125 let header = MessageHeader::decode_cursor(&mut cursor)?;
126 header.validate()?;
127
128 if header.template_id != PING_TEMPLATE_ID {
129 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
130 }
131
132 Ok(())
133}
134
135pub fn decode_server_time(buf: &[u8]) -> Result<i64, SbeDecodeError> {
144 let mut cursor = SbeCursor::new(buf);
145 let header = MessageHeader::decode_cursor(&mut cursor)?;
146 header.validate()?;
147
148 if header.template_id != SERVER_TIME_TEMPLATE_ID {
149 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
150 }
151
152 cursor.read_i64_le()
153}
154
155const DEPTH_LEVEL_BLOCK_LENGTH: u16 = 16;
156const TRADE_BLOCK_LENGTH: u16 = 42;
157const AGG_TRADE_BLOCK_LENGTH: u16 = 50;
158
159pub fn decode_depth(buf: &[u8]) -> Result<BinanceDepth, SbeDecodeError> {
171 let mut cursor = SbeCursor::new(buf);
172 let header = MessageHeader::decode_cursor(&mut cursor)?;
173 header.validate()?;
174
175 if header.template_id != DEPTH_TEMPLATE_ID {
176 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
177 }
178
179 let last_update_id = cursor.read_i64_le()?;
180 let price_exponent = cursor.read_i8()?;
181 let qty_exponent = cursor.read_i8()?;
182
183 let (block_len, count) = read_group_header(&mut cursor, DEPTH_LEVEL_BLOCK_LENGTH)?;
184
185 let bids = cursor.read_group(block_len, count, |c| {
186 let mut c = SbeCursor::new(c.read_bytes(usize::from(DEPTH_LEVEL_BLOCK_LENGTH))?);
187 Ok(BinancePriceLevel {
188 price_mantissa: c.read_i64_le()?,
189 qty_mantissa: c.read_i64_le()?,
190 })
191 })?;
192
193 let (block_len, count) = read_group_header(&mut cursor, DEPTH_LEVEL_BLOCK_LENGTH)?;
194
195 let asks = cursor.read_group(block_len, count, |c| {
196 let mut c = SbeCursor::new(c.read_bytes(usize::from(DEPTH_LEVEL_BLOCK_LENGTH))?);
197 Ok(BinancePriceLevel {
198 price_mantissa: c.read_i64_le()?,
199 qty_mantissa: c.read_i64_le()?,
200 })
201 })?;
202
203 Ok(BinanceDepth {
204 last_update_id,
205 price_exponent,
206 qty_exponent,
207 bids,
208 asks,
209 })
210}
211
212pub fn decode_trades(buf: &[u8]) -> Result<BinanceTrades, SbeDecodeError> {
224 let mut cursor = SbeCursor::new(buf);
225 let header = MessageHeader::decode_cursor(&mut cursor)?;
226 header.validate()?;
227
228 if header.template_id != TRADES_TEMPLATE_ID {
229 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
230 }
231
232 let price_exponent = cursor.read_i8()?;
233 let qty_exponent = cursor.read_i8()?;
234
235 let (block_len, count) = read_group_header(&mut cursor, TRADE_BLOCK_LENGTH)?;
236
237 let trades = cursor.read_group(block_len, count, |c| {
238 let mut c = SbeCursor::new(c.read_bytes(usize::from(TRADE_BLOCK_LENGTH))?);
239 Ok(BinanceTrade {
240 id: c.read_i64_le()?,
241 price_mantissa: c.read_i64_le()?,
242 qty_mantissa: c.read_i64_le()?,
243 quote_qty_mantissa: c.read_i64_le()?,
244 time: c.read_i64_le()?,
245 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
246 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
247 })
248 })?;
249
250 Ok(BinanceTrades {
251 price_exponent,
252 qty_exponent,
253 trades,
254 })
255}
256
257pub fn decode_agg_trades(buf: &[u8]) -> Result<BinanceAggTrades, SbeDecodeError> {
267 let mut cursor = SbeCursor::new(buf);
268 let header = MessageHeader::decode_cursor(&mut cursor)?;
269 header.validate()?;
270
271 if header.template_id != AGG_TRADES_TEMPLATE_ID {
272 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
273 }
274
275 let price_exponent = cursor.read_i8()?;
276 let qty_exponent = cursor.read_i8()?;
277 let (block_len, count) = read_group_header(&mut cursor, AGG_TRADE_BLOCK_LENGTH)?;
278
279 let trades = cursor.read_group(block_len, count, |c| {
280 let mut c = SbeCursor::new(c.read_bytes(usize::from(AGG_TRADE_BLOCK_LENGTH))?);
281 Ok(BinanceAggTrade {
282 id: c.read_i64_le()?,
283 price_mantissa: c.read_i64_le()?,
284 qty_mantissa: c.read_i64_le()?,
285 first_trade_id: c.read_i64_le()?,
286 last_trade_id: c.read_i64_le()?,
287 time: c.read_i64_le()?,
288 is_buyer_maker: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
289 is_best_match: BoolEnum::from(c.read_u8()?) == BoolEnum::True,
290 })
291 })?;
292
293 Ok(BinanceAggTrades {
294 price_exponent,
295 qty_exponent,
296 trades,
297 })
298}
299
300fn read_group_header(
301 cursor: &mut SbeCursor<'_>,
302 minimum: u16,
303) -> Result<(u16, u32), SbeDecodeError> {
304 let (block_length, count) = cursor.read_group_header()?;
305 if block_length < minimum {
306 return Err(SbeDecodeError::InvalidBlockLength {
307 expected: minimum,
308 actual: block_length,
309 });
310 }
311
312 Ok((block_length, count))
313}
314
315const KLINES_BLOCK_LENGTH: u16 = 120;
317
318pub fn decode_klines(buf: &[u8]) -> Result<BinanceKlines, SbeDecodeError> {
326 let mut cursor = SbeCursor::new(buf);
327 let header = MessageHeader::decode_cursor(&mut cursor)?;
328 header.validate()?;
329
330 if header.template_id != KLINES_TEMPLATE_ID {
331 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
332 }
333
334 let price_exponent = cursor.read_i8()?;
335 let qty_exponent = cursor.read_i8()?;
336
337 let (block_len, count) = cursor.read_group_header()?;
338
339 if block_len != KLINES_BLOCK_LENGTH {
340 return Err(SbeDecodeError::InvalidBlockLength {
341 expected: KLINES_BLOCK_LENGTH,
342 actual: block_len,
343 });
344 }
345
346 let mut klines = Vec::with_capacity(count as usize);
347
348 for _ in 0..count {
349 cursor.require(KLINES_BLOCK_LENGTH as usize)?;
350
351 let open_time = cursor.read_i64_le()?;
352 let open_price = cursor.read_i64_le()?;
353 let high_price = cursor.read_i64_le()?;
354 let low_price = cursor.read_i64_le()?;
355 let close_price = cursor.read_i64_le()?;
356
357 let volume_slice = cursor.read_bytes(16)?;
358 let mut volume = [0u8; 16];
359 volume.copy_from_slice(volume_slice);
360
361 let close_time = cursor.read_i64_le()?;
362
363 let quote_volume_slice = cursor.read_bytes(16)?;
364 let mut quote_volume = [0u8; 16];
365 quote_volume.copy_from_slice(quote_volume_slice);
366
367 let num_trades = cursor.read_i64_le()?;
368
369 let taker_buy_base_volume_slice = cursor.read_bytes(16)?;
370 let mut taker_buy_base_volume = [0u8; 16];
371 taker_buy_base_volume.copy_from_slice(taker_buy_base_volume_slice);
372
373 let taker_buy_quote_volume_slice = cursor.read_bytes(16)?;
374 let mut taker_buy_quote_volume = [0u8; 16];
375 taker_buy_quote_volume.copy_from_slice(taker_buy_quote_volume_slice);
376
377 klines.push(BinanceKline {
378 open_time,
379 open_price,
380 high_price,
381 low_price,
382 close_price,
383 volume,
384 close_time,
385 quote_volume,
386 num_trades,
387 taker_buy_base_volume,
388 taker_buy_quote_volume,
389 });
390 }
391
392 Ok(BinanceKlines {
393 price_exponent,
394 qty_exponent,
395 klines,
396 })
397}
398
399const NEW_ORDER_FULL_FIELDS_END: usize = 117;
402const CANCEL_ORDER_FIELDS_END: usize = 109;
403const CANCEL_ORDER_LIST_FIELDS_END: usize = 21;
404const CANCEL_ORDER_LIST_ORDER_FIELDS_END: usize = 8;
405const CANCEL_ORDER_LIST_REPORT_FIELDS_END: usize = 107;
406const CANCEL_ORDER_LIST_REPORT_V0_BLOCK_LENGTH: u16 = 124;
407const CANCEL_ORDER_LIST_REPORT_V1_BLOCK_LENGTH: u16 = 135;
408const ORDER_FIELDS_END: usize = 134;
409
410const ORDER_MIN_BLOCK_LENGTH: u16 = 162;
413
414const EXPIRY_REASON_NULL: u8 = 0xff;
416
417const NEW_ORDER_FULL_EXPIRY_REASON_OFFSET: usize = 153;
421const ORDER_EXPIRY_REASON_OFFSET: usize = 162;
422
423fn read_trailing_expiry_reason(
433 cursor: &mut SbeCursor<'_>,
434 block_length: usize,
435 fields_end: usize,
436 expiry_reason_offset: usize,
437) -> Result<Option<u8>, SbeDecodeError> {
438 debug_assert!(fields_end <= expiry_reason_offset);
439 if block_length < fields_end {
440 return Ok(None);
441 }
442 let trailer = block_length - fields_end;
443 if trailer == 0 {
444 return Ok(None);
445 }
446 if block_length <= expiry_reason_offset {
448 cursor.advance(trailer)?;
449 return Ok(None);
450 }
451 let pre = expiry_reason_offset - fields_end;
452 cursor.advance(pre)?;
453 let byte = cursor.read_u8()?;
454 let post = trailer - pre - 1;
455 if post > 0 {
456 cursor.advance(post)?;
457 }
458 Ok((byte != EXPIRY_REASON_NULL).then_some(byte))
459}
460
461#[allow(dead_code)]
467pub fn decode_new_order_full(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
468 let mut cursor = SbeCursor::new(buf);
469 let header = MessageHeader::decode_cursor(&mut cursor)?;
470 header.validate()?;
471
472 if header.template_id != NEW_ORDER_FULL_TEMPLATE_ID {
473 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
474 }
475
476 if usize::from(header.block_length) < NEW_ORDER_FULL_FIELDS_END {
477 return Err(SbeDecodeError::InvalidBlockLength {
478 expected: NEW_ORDER_FULL_FIELDS_END as u16,
479 actual: header.block_length,
480 });
481 }
482
483 cursor.require(header.block_length as usize)?;
484
485 let price_exponent = cursor.read_i8()?;
486 let qty_exponent = cursor.read_i8()?;
487 let order_id = cursor.read_i64_le()?;
488 let order_list_id = cursor.read_optional_i64_le()?;
489 let transact_time = cursor.read_i64_le()?;
490 let price_mantissa = cursor.read_i64_le()?;
491 let orig_qty_mantissa = cursor.read_i64_le()?;
492 let executed_qty_mantissa = cursor.read_i64_le()?;
493 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
494 let status = cursor.read_u8()?.into();
495 let time_in_force = cursor.read_u8()?.into();
496 let order_type = cursor.read_u8()?.into();
497 let side = cursor.read_u8()?.into();
498 let stop_price_mantissa = cursor.read_optional_i64_le()?;
499
500 cursor.advance(16)?; let working_time = cursor.read_optional_i64_le()?;
502
503 cursor.advance(22)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
505
506 let expiry_reason = read_trailing_expiry_reason(
507 &mut cursor,
508 header.block_length as usize,
509 NEW_ORDER_FULL_FIELDS_END,
510 NEW_ORDER_FULL_EXPIRY_REASON_OFFSET,
511 )?;
512
513 let fills = decode_fills_cursor(&mut cursor)?;
514
515 let (block_len, count) = cursor.read_group_header()?;
517 cursor.advance(block_len as usize * count as usize)?;
518
519 let symbol = cursor.read_var_string8()?;
520 let client_order_id = cursor.read_var_string8()?;
521
522 Ok(BinanceNewOrderResponse {
523 price_exponent,
524 qty_exponent,
525 order_id,
526 order_list_id,
527 transact_time,
528 price_mantissa,
529 orig_qty_mantissa,
530 executed_qty_mantissa,
531 cummulative_quote_qty_mantissa,
532 status,
533 time_in_force,
534 order_type,
535 side,
536 stop_price_mantissa,
537 working_time,
538 self_trade_prevention_mode,
539 client_order_id,
540 symbol,
541 fills,
542 expiry_reason,
543 })
544}
545
546pub fn decode_cancel_replace(buf: &[u8]) -> Result<BinanceNewOrderResponse, SbeDecodeError> {
552 let (_, replacement) = decode_cancel_replace_payloads(buf)?;
553 decode_new_order_full(replacement)
554}
555
556pub(crate) fn decode_cancel_replace_orders(
557 buf: &[u8],
558) -> Result<(BinanceCancelOrderResponse, BinanceNewOrderResponse), SbeDecodeError> {
559 let (cancellation, replacement) = decode_cancel_replace_payloads(buf)?;
560 Ok((
561 decode_cancel_order(cancellation)?,
562 decode_new_order_full(replacement)?,
563 ))
564}
565
566fn decode_cancel_replace_payloads(buf: &[u8]) -> Result<(&[u8], &[u8]), SbeDecodeError> {
567 let mut cursor = SbeCursor::new(buf);
568 let header = MessageHeader::decode_cursor(&mut cursor)?;
569 header.validate()?;
570 if header.template_id != CANCEL_REPLACE_TEMPLATE_ID {
571 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
572 }
573
574 if header.block_length < 2 {
575 return Err(SbeDecodeError::InvalidBlockLength {
576 expected: 2,
577 actual: header.block_length,
578 });
579 }
580 let cancel_result = cursor.read_u8()?;
581 let new_order_result = cursor.read_u8()?;
582 cursor.advance(usize::from(header.block_length) - 2)?;
583
584 if cancel_result != CancelReplaceStatus::Success as u8
585 || new_order_result != CancelReplaceStatus::Success as u8
586 {
587 return Err(SbeDecodeError::InvalidValue {
588 field: "cancel-replace result",
589 });
590 }
591 let cancel_len = usize::from(cursor.read_u16_le()?);
592 let cancellation = cursor.read_bytes(cancel_len)?;
593 let new_order_len = cursor.read_u32_le()? as usize;
594 let replacement = cursor.read_bytes(new_order_len)?;
595 Ok((cancellation, replacement))
596}
597
598#[allow(dead_code)]
604pub fn decode_cancel_order(buf: &[u8]) -> Result<BinanceCancelOrderResponse, SbeDecodeError> {
605 let mut cursor = SbeCursor::new(buf);
606 let header = MessageHeader::decode_cursor(&mut cursor)?;
607 header.validate()?;
608
609 if header.template_id != CANCEL_ORDER_TEMPLATE_ID {
610 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
611 }
612
613 if usize::from(header.block_length) < CANCEL_ORDER_FIELDS_END {
614 return Err(SbeDecodeError::InvalidBlockLength {
615 expected: CANCEL_ORDER_FIELDS_END as u16,
616 actual: header.block_length,
617 });
618 }
619
620 cursor.require(header.block_length as usize)?;
621
622 let price_exponent = cursor.read_i8()?;
623 let qty_exponent = cursor.read_i8()?;
624 let order_id = cursor.read_i64_le()?;
625 let order_list_id = cursor.read_optional_i64_le()?;
626 let transact_time = cursor.read_i64_le()?;
627 let price_mantissa = cursor.read_i64_le()?;
628 let orig_qty_mantissa = cursor.read_i64_le()?;
629 let executed_qty_mantissa = cursor.read_i64_le()?;
630 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
631 let status = cursor.read_u8()?.into();
632 let time_in_force = cursor.read_u8()?.into();
633 let order_type = cursor.read_u8()?.into();
634 let side = cursor.read_u8()?.into();
635
636 cursor.advance(46)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
638
639 cursor.advance(header.block_length as usize - CANCEL_ORDER_FIELDS_END)?;
640
641 let symbol = cursor.read_var_string8()?;
642 let orig_client_order_id = cursor.read_var_string8()?;
643 let client_order_id = cursor.read_var_string8()?;
644
645 Ok(BinanceCancelOrderResponse {
646 price_exponent,
647 qty_exponent,
648 order_id,
649 order_list_id,
650 transact_time,
651 price_mantissa,
652 orig_qty_mantissa,
653 executed_qty_mantissa,
654 cummulative_quote_qty_mantissa,
655 status,
656 time_in_force,
657 order_type,
658 side,
659 self_trade_prevention_mode,
660 client_order_id,
661 orig_client_order_id,
662 symbol,
663 })
664}
665
666fn decode_cancel_order_list(buf: &[u8]) -> Result<BinanceCancelOrderListResponse, SbeDecodeError> {
667 let mut cursor = SbeCursor::new(buf);
668 let header = MessageHeader::decode_cursor(&mut cursor)?;
669 header.validate()?;
670
671 if header.template_id != CANCEL_ORDER_LIST_TEMPLATE_ID {
672 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
673 }
674
675 if usize::from(header.block_length) < CANCEL_ORDER_LIST_FIELDS_END {
676 return Err(SbeDecodeError::InvalidBlockLength {
677 expected: CANCEL_ORDER_LIST_FIELDS_END as u16,
678 actual: header.block_length,
679 });
680 }
681
682 cursor.require(header.block_length as usize)?;
683 let order_list_id = cursor.read_i64_le()?;
684 let contingency_type = cursor.read_u8()?.into();
685 let list_status_type = cursor.read_u8()?.into();
686 let list_order_status = cursor.read_u8()?.into();
687 let transaction_time = cursor.read_i64_le()?;
688 let price_exponent = cursor.read_i8()?;
689 let qty_exponent = cursor.read_i8()?;
690 cursor.advance(usize::from(header.block_length) - CANCEL_ORDER_LIST_FIELDS_END)?;
691
692 let (order_block_length, order_count) = cursor.read_group_header_16()?;
693 if usize::from(order_block_length) < CANCEL_ORDER_LIST_ORDER_FIELDS_END {
694 return Err(SbeDecodeError::InvalidBlockLength {
695 expected: CANCEL_ORDER_LIST_ORDER_FIELDS_END as u16,
696 actual: order_block_length,
697 });
698 }
699
700 let mut orders = Vec::with_capacity(order_count as usize);
701 for _ in 0..order_count {
702 cursor.require(order_block_length as usize)?;
703 let order_id = cursor.read_i64_le()?;
704 cursor.advance(usize::from(order_block_length) - CANCEL_ORDER_LIST_ORDER_FIELDS_END)?;
705 let symbol = cursor.read_var_string8()?;
706 let client_order_id = cursor.read_var_string8()?;
707 orders.push(BinanceCancelOrderListOrder {
708 symbol,
709 order_id,
710 client_order_id,
711 });
712 }
713
714 let (report_block_length, report_count) = cursor.read_group_header_16()?;
715 let report_min_block_length = if header.version == 0 {
716 CANCEL_ORDER_LIST_REPORT_V0_BLOCK_LENGTH
717 } else {
718 CANCEL_ORDER_LIST_REPORT_V1_BLOCK_LENGTH
719 };
720
721 if report_block_length < report_min_block_length {
722 return Err(SbeDecodeError::InvalidBlockLength {
723 expected: report_min_block_length,
724 actual: report_block_length,
725 });
726 }
727
728 let mut order_reports = Vec::with_capacity(report_count as usize);
729
730 for _ in 0..report_count {
731 cursor.require(report_block_length as usize)?;
732 let order_id = cursor.read_i64_le()?;
733 let order_list_id = cursor.read_optional_i64_le()?;
734 let transact_time = cursor.read_i64_le()?;
735 let price_mantissa = cursor.read_i64_le()?;
736 let orig_qty_mantissa = cursor.read_i64_le()?;
737 let executed_qty_mantissa = cursor.read_i64_le()?;
738 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
739 let status = cursor.read_u8()?.into();
740 let time_in_force = cursor.read_u8()?.into();
741 let order_type = cursor.read_u8()?.into();
742 let side = cursor.read_u8()?.into();
743 cursor.advance(46)?;
744 let self_trade_prevention_mode = cursor.read_u8()?.into();
745 cursor.advance(usize::from(report_block_length) - CANCEL_ORDER_LIST_REPORT_FIELDS_END)?;
746 let symbol = cursor.read_var_string8()?;
747 let orig_client_order_id = cursor.read_var_string8()?;
748 let client_order_id = cursor.read_var_string8()?;
749 order_reports.push(BinanceCancelOrderResponse {
750 price_exponent,
751 qty_exponent,
752 order_id,
753 order_list_id,
754 transact_time,
755 price_mantissa,
756 orig_qty_mantissa,
757 executed_qty_mantissa,
758 cummulative_quote_qty_mantissa,
759 status,
760 time_in_force,
761 order_type,
762 side,
763 self_trade_prevention_mode,
764 client_order_id,
765 orig_client_order_id,
766 symbol,
767 });
768 }
769
770 let list_client_order_id = cursor.read_var_string8()?;
771 let symbol = cursor.read_var_string8()?;
772
773 Ok(BinanceCancelOrderListResponse {
774 order_list_id,
775 contingency_type,
776 list_status_type,
777 list_order_status,
778 transaction_time,
779 list_client_order_id,
780 symbol,
781 orders,
782 order_reports,
783 })
784}
785
786#[allow(dead_code)]
792pub fn decode_order(buf: &[u8]) -> Result<BinanceOrderResponse, SbeDecodeError> {
793 let mut cursor = SbeCursor::new(buf);
794 let header = MessageHeader::decode_cursor(&mut cursor)?;
795 header.validate()?;
796
797 if header.template_id != ORDER_TEMPLATE_ID {
798 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
799 }
800
801 decode_order_block(&mut cursor, header.block_length)
802}
803
804#[allow(dead_code)]
810pub fn decode_orders(buf: &[u8]) -> Result<Vec<BinanceOrderResponse>, SbeDecodeError> {
811 let mut cursor = SbeCursor::new(buf);
812 let header = MessageHeader::decode_cursor(&mut cursor)?;
813 header.validate()?;
814
815 if header.template_id != ORDERS_TEMPLATE_ID {
816 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
817 }
818
819 let (block_length, count) = cursor.read_group_header()?;
820 let mut orders = Vec::with_capacity(count as usize);
821
822 for _ in 0..count {
823 orders.push(decode_order_block(&mut cursor, block_length)?);
824 }
825
826 Ok(orders)
827}
828
829fn decode_order_block(
832 cursor: &mut SbeCursor<'_>,
833 block_length: u16,
834) -> Result<BinanceOrderResponse, SbeDecodeError> {
835 if block_length < ORDER_MIN_BLOCK_LENGTH {
836 return Err(SbeDecodeError::InvalidBlockLength {
837 expected: ORDER_MIN_BLOCK_LENGTH,
838 actual: block_length,
839 });
840 }
841
842 cursor.require(block_length as usize)?;
843
844 let price_exponent = cursor.read_i8()?;
845 let qty_exponent = cursor.read_i8()?;
846 let order_id = cursor.read_i64_le()?;
847 let order_list_id = cursor.read_optional_i64_le()?;
848 let price_mantissa = cursor.read_i64_le()?;
849 let orig_qty_mantissa = cursor.read_i64_le()?;
850 let executed_qty_mantissa = cursor.read_i64_le()?;
851 let cummulative_quote_qty_mantissa = cursor.read_i64_le()?;
852 let status = cursor.read_u8()?.into();
853 let time_in_force = cursor.read_u8()?.into();
854 let order_type = cursor.read_u8()?.into();
855 let side = cursor.read_u8()?.into();
856 let stop_price_mantissa = cursor.read_optional_i64_le()?;
857
858 cursor.advance(16)?; let iceberg_qty_mantissa = cursor.read_optional_i64_le()?;
860 let time = cursor.read_i64_le()?;
861 let update_time = cursor.read_i64_le()?;
862 let is_working = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
863 let working_time = cursor.read_optional_i64_le()?;
864 let orig_quote_order_qty_mantissa = cursor.read_i64_le()?;
865
866 cursor.advance(14)?; let self_trade_prevention_mode = cursor.read_u8()?.into();
868
869 let expiry_reason = read_trailing_expiry_reason(
870 cursor,
871 block_length as usize,
872 ORDER_FIELDS_END,
873 ORDER_EXPIRY_REASON_OFFSET,
874 )?;
875
876 let symbol = cursor.read_var_string8()?;
877 let client_order_id = cursor.read_var_string8()?;
878
879 Ok(BinanceOrderResponse {
880 price_exponent,
881 qty_exponent,
882 order_id,
883 order_list_id,
884 price_mantissa,
885 orig_qty_mantissa,
886 executed_qty_mantissa,
887 cummulative_quote_qty_mantissa,
888 status,
889 time_in_force,
890 order_type,
891 side,
892 stop_price_mantissa,
893 iceberg_qty_mantissa,
894 time,
895 update_time,
896 is_working,
897 working_time,
898 orig_quote_order_qty_mantissa,
899 self_trade_prevention_mode,
900 client_order_id,
901 symbol,
902 expiry_reason,
903 })
904}
905
906#[allow(dead_code)]
914pub fn decode_cancel_open_orders(
915 buf: &[u8],
916) -> Result<Vec<BinanceCancelOpenOrdersResponse>, SbeDecodeError> {
917 let mut cursor = SbeCursor::new(buf);
918 let header = MessageHeader::decode_cursor(&mut cursor)?;
919 header.validate()?;
920
921 if header.template_id != CANCEL_OPEN_ORDERS_TEMPLATE_ID {
922 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
923 }
924
925 if header.block_length != 0 {
926 return Err(SbeDecodeError::InvalidBlockLength {
927 expected: 0,
928 actual: header.block_length,
929 });
930 }
931
932 let (block_length, count) = cursor.read_group_header()?;
933 if block_length != 0 {
934 return Err(SbeDecodeError::InvalidBlockLength {
935 expected: 0,
936 actual: block_length,
937 });
938 }
939
940 if count == 0 {
941 return Ok(Vec::new());
942 }
943
944 let mut responses = Vec::with_capacity(count as usize);
945
946 for _ in 0..count {
948 let response_len = cursor.read_u16_le()? as usize;
949 let embedded_bytes = cursor.read_bytes(response_len)?;
950 let mut embedded_cursor = SbeCursor::new(embedded_bytes);
951 let embedded_header = MessageHeader::decode_cursor(&mut embedded_cursor)?;
952 embedded_header.validate()?;
953 let response = match embedded_header.template_id {
954 CANCEL_ORDER_TEMPLATE_ID => {
955 BinanceCancelOpenOrdersResponse::Order(decode_cancel_order(embedded_bytes)?)
956 }
957 CANCEL_ORDER_LIST_TEMPLATE_ID => BinanceCancelOpenOrdersResponse::OrderList(
958 decode_cancel_order_list(embedded_bytes)?,
959 ),
960 template_id => return Err(SbeDecodeError::UnknownTemplateId(template_id)),
961 };
962 responses.push(response);
963 }
964
965 Ok(responses)
966}
967
968const ACCOUNT_BLOCK_LENGTH: usize = 64;
970
971const BALANCE_BLOCK_LENGTH: u16 = 17;
973
974#[allow(dead_code)]
980pub fn decode_account(buf: &[u8]) -> Result<BinanceAccountInfo, SbeDecodeError> {
981 let mut cursor = SbeCursor::new(buf);
982 let header = MessageHeader::decode_cursor(&mut cursor)?;
983 header.validate()?;
984
985 if header.template_id != ACCOUNT_TEMPLATE_ID {
986 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
987 }
988
989 cursor.require(ACCOUNT_BLOCK_LENGTH)?;
990
991 let commission_exponent = cursor.read_i8()?;
992 let maker_commission_mantissa = cursor.read_i64_le()?;
993 let taker_commission_mantissa = cursor.read_i64_le()?;
994 let buyer_commission_mantissa = cursor.read_i64_le()?;
995 let seller_commission_mantissa = cursor.read_i64_le()?;
996 let can_trade = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
997 let can_withdraw = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
998 let can_deposit = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
999 cursor.advance(1)?; let require_self_trade_prevention = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
1001 let prevent_sor = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
1002 let update_time = cursor.read_i64_le()?;
1003 let account_type_enum = AccountType::from(cursor.read_u8()?);
1004 cursor.advance(16)?; let account_type = account_type_enum.to_string();
1007
1008 let (block_length, balance_count) = cursor.read_group_header()?;
1009
1010 if block_length != BALANCE_BLOCK_LENGTH {
1011 return Err(SbeDecodeError::InvalidBlockLength {
1012 expected: BALANCE_BLOCK_LENGTH,
1013 actual: block_length,
1014 });
1015 }
1016
1017 let mut balances = Vec::with_capacity(balance_count as usize);
1018
1019 for _ in 0..balance_count {
1020 cursor.require(block_length as usize)?;
1021
1022 let exponent = cursor.read_i8()?;
1023 let free_mantissa = cursor.read_i64_le()?;
1024 let locked_mantissa = cursor.read_i64_le()?;
1025
1026 let asset = cursor.read_var_string8()?;
1027
1028 balances.push(BinanceBalance {
1029 asset,
1030 free_mantissa,
1031 locked_mantissa,
1032 exponent,
1033 });
1034 }
1035
1036 Ok(BinanceAccountInfo {
1037 commission_exponent,
1038 maker_commission_mantissa,
1039 taker_commission_mantissa,
1040 buyer_commission_mantissa,
1041 seller_commission_mantissa,
1042 can_trade,
1043 can_withdraw,
1044 can_deposit,
1045 require_self_trade_prevention,
1046 prevent_sor,
1047 update_time,
1048 account_type,
1049 balances,
1050 })
1051}
1052
1053const ACCOUNT_TRADE_BLOCK_LENGTH: u16 = 70;
1055
1056#[allow(dead_code)]
1062pub fn decode_account_trades(buf: &[u8]) -> Result<Vec<BinanceAccountTrade>, SbeDecodeError> {
1063 let mut cursor = SbeCursor::new(buf);
1064 let header = MessageHeader::decode_cursor(&mut cursor)?;
1065 header.validate()?;
1066
1067 if header.template_id != ACCOUNT_TRADES_TEMPLATE_ID {
1068 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
1069 }
1070
1071 let (block_length, trade_count) = cursor.read_group_header()?;
1072
1073 if block_length != ACCOUNT_TRADE_BLOCK_LENGTH {
1074 return Err(SbeDecodeError::InvalidBlockLength {
1075 expected: ACCOUNT_TRADE_BLOCK_LENGTH,
1076 actual: block_length,
1077 });
1078 }
1079
1080 let mut trades = Vec::with_capacity(trade_count as usize);
1081
1082 for _ in 0..trade_count {
1083 cursor.require(block_length as usize)?;
1084
1085 let price_exponent = cursor.read_i8()?;
1086 let qty_exponent = cursor.read_i8()?;
1087 let commission_exponent = cursor.read_i8()?;
1088 let id = cursor.read_i64_le()?;
1089 let order_id = cursor.read_i64_le()?;
1090 let order_list_id = cursor.read_optional_i64_le()?;
1091 let price_mantissa = cursor.read_i64_le()?;
1092 let qty_mantissa = cursor.read_i64_le()?;
1093 let quote_qty_mantissa = cursor.read_i64_le()?;
1094 let commission_mantissa = cursor.read_i64_le()?;
1095 let time = cursor.read_i64_le()?;
1096 let is_buyer = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
1097 let is_maker = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
1098 let is_best_match = BoolEnum::from(cursor.read_u8()?) == BoolEnum::True;
1099
1100 let symbol = cursor.read_var_string8()?;
1101 let commission_asset = cursor.read_var_string8()?;
1102
1103 trades.push(BinanceAccountTrade {
1104 price_exponent,
1105 qty_exponent,
1106 commission_exponent,
1107 id,
1108 order_id,
1109 order_list_id,
1110 price_mantissa,
1111 qty_mantissa,
1112 quote_qty_mantissa,
1113 commission_mantissa,
1114 time,
1115 is_buyer,
1116 is_maker,
1117 is_best_match,
1118 symbol,
1119 commission_asset,
1120 });
1121 }
1122
1123 Ok(trades)
1124}
1125
1126const FILLS_BLOCK_LENGTH: u16 = 42;
1128
1129fn decode_fills_cursor(
1131 cursor: &mut SbeCursor<'_>,
1132) -> Result<Vec<BinanceOrderFill>, SbeDecodeError> {
1133 let (block_length, count) = cursor.read_group_header()?;
1134
1135 if block_length != FILLS_BLOCK_LENGTH {
1136 return Err(SbeDecodeError::InvalidBlockLength {
1137 expected: FILLS_BLOCK_LENGTH,
1138 actual: block_length,
1139 });
1140 }
1141
1142 let mut fills = Vec::with_capacity(count as usize);
1143
1144 for _ in 0..count {
1145 cursor.require(block_length as usize)?;
1146
1147 let commission_exponent = cursor.read_i8()?;
1148 cursor.advance(1)?; let price_mantissa = cursor.read_i64_le()?;
1150 let qty_mantissa = cursor.read_i64_le()?;
1151 let commission_mantissa = cursor.read_i64_le()?;
1152 let trade_id = cursor.read_optional_i64_le()?;
1153 cursor.advance(8)?; let commission_asset = cursor.read_var_string8()?;
1156
1157 fills.push(BinanceOrderFill {
1158 price_mantissa,
1159 qty_mantissa,
1160 commission_mantissa,
1161 commission_exponent,
1162 commission_asset,
1163 trade_id,
1164 });
1165 }
1166
1167 Ok(fills)
1168}
1169
1170const SYMBOL_BLOCK_LENGTH: usize = 19;
1172
1173pub fn decode_exchange_info(buf: &[u8]) -> Result<BinanceExchangeInfoSbe, SbeDecodeError> {
1187 let mut cursor = SbeCursor::new(buf);
1188 let header = MessageHeader::decode_cursor(&mut cursor)?;
1189 header.validate()?;
1190
1191 if header.template_id != EXCHANGE_INFO_TEMPLATE_ID {
1192 return Err(SbeDecodeError::UnknownTemplateId(header.template_id));
1193 }
1194
1195 let (rate_limits_block_len, rate_limits_count) = cursor.read_group_header()?;
1197 cursor.advance(rate_limits_block_len as usize * rate_limits_count as usize)?;
1198
1199 let (_exchange_filters_block_len, exchange_filters_count) = cursor.read_group_header()?;
1201 for _ in 0..exchange_filters_count {
1202 cursor.read_var_string8()?;
1204 }
1205
1206 let (symbols_block_len, symbols_count) = cursor.read_group_header()?;
1208
1209 if symbols_block_len != SYMBOL_BLOCK_LENGTH as u16 {
1210 return Err(SbeDecodeError::InvalidBlockLength {
1211 expected: SYMBOL_BLOCK_LENGTH as u16,
1212 actual: symbols_block_len,
1213 });
1214 }
1215
1216 let mut symbols = Vec::with_capacity(symbols_count as usize);
1217
1218 for _ in 0..symbols_count {
1219 cursor.require(SYMBOL_BLOCK_LENGTH)?;
1220
1221 let status = cursor.read_u8()?;
1223 let base_asset_precision = cursor.read_u8()?;
1224 let quote_asset_precision = cursor.read_u8()?;
1225 let _base_commission_precision = cursor.read_u8()?;
1226 let _quote_commission_precision = cursor.read_u8()?;
1227 let order_types = cursor.read_u16_le()?;
1228 let iceberg_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1229 let oco_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1230 let oto_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1231 let quote_order_qty_market_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1232 let allow_trailing_stop = cursor.read_u8()? == BoolEnum::True as u8;
1233 let cancel_replace_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1234 let amend_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1235 let is_spot_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1236 let is_margin_trading_allowed = cursor.read_u8()? == BoolEnum::True as u8;
1237 let _default_self_trade_prevention_mode = cursor.read_u8()?;
1238 let _allowed_self_trade_prevention_modes = cursor.read_u8()?;
1239 let _peg_instructions_allowed = cursor.read_u8()?;
1240
1241 let (_filters_block_len, filters_count) = cursor.read_group_header()?;
1242 let mut filters = BinanceSymbolFiltersSbe::default();
1243
1244 for _ in 0..filters_count {
1245 let filter_bytes = cursor.read_var_bytes8()?;
1246
1247 let (template_id, offset) = if filter_bytes.len() >= HEADER_LENGTH + 2 {
1250 let potential_template = u16::from_le_bytes([filter_bytes[2], filter_bytes[3]]);
1251 if potential_template == PRICE_FILTER_TEMPLATE_ID
1252 || potential_template == LOT_SIZE_FILTER_TEMPLATE_ID
1253 || potential_template == MIN_NOTIONAL_FILTER_TEMPLATE_ID
1254 || potential_template == NOTIONAL_FILTER_TEMPLATE_ID
1255 {
1256 (potential_template, HEADER_LENGTH)
1257 } else {
1258 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1259 (raw_template, 2)
1260 }
1261 } else if filter_bytes.len() >= 2 {
1262 let raw_template = u16::from_le_bytes([filter_bytes[0], filter_bytes[1]]);
1263 (raw_template, 2)
1264 } else {
1265 continue;
1266 };
1267
1268 match template_id {
1270 MIN_NOTIONAL_FILTER_TEMPLATE_ID | NOTIONAL_FILTER_TEMPLATE_ID => {
1271 let mut filter = SbeCursor::new(&filter_bytes[offset..]);
1272 let exponent = filter.read_i8()?;
1273 let min = decode_notional_amount(&mut filter, exponent)?;
1274 let apply_min_to_market = decode_notional_market_flag(&mut filter)?;
1275 let (max, apply_max_to_market) = if template_id == NOTIONAL_FILTER_TEMPLATE_ID {
1276 (
1277 Some(decode_notional_amount(&mut filter, exponent)?),
1278 decode_notional_market_flag(&mut filter)?,
1279 )
1280 } else {
1281 (None, false)
1282 };
1283
1284 let avg_price_mins = u32::try_from(filter.read_i32_le()?).map_err(|_| {
1285 SbeDecodeError::InvalidValue {
1286 field: "avgPriceMins",
1287 }
1288 })?;
1289
1290 filters.notional_filters.push(BinanceNotionalFilter {
1291 min,
1292 max,
1293 apply_min_to_market,
1294 apply_max_to_market,
1295 avg_price_mins,
1296 });
1297 }
1298 PRICE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1299 let price_exp = filter_bytes[offset] as i8;
1300 let min_price = i64::from_le_bytes(
1301 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1302 );
1303 let max_price = i64::from_le_bytes(
1304 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1305 );
1306 let tick_size = i64::from_le_bytes(
1307 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1308 );
1309 filters.price_filter = Some(BinancePriceFilterSbe {
1310 price_exponent: price_exp,
1311 min_price,
1312 max_price,
1313 tick_size,
1314 });
1315 }
1316 LOT_SIZE_FILTER_TEMPLATE_ID if filter_bytes.len() >= offset + 25 => {
1317 let qty_exp = filter_bytes[offset] as i8;
1318 let min_qty = i64::from_le_bytes(
1319 filter_bytes[offset + 1..offset + 9].try_into().unwrap(),
1320 );
1321 let max_qty = i64::from_le_bytes(
1322 filter_bytes[offset + 9..offset + 17].try_into().unwrap(),
1323 );
1324 let step_size = i64::from_le_bytes(
1325 filter_bytes[offset + 17..offset + 25].try_into().unwrap(),
1326 );
1327 filters.lot_size_filter = Some(BinanceLotSizeFilterSbe {
1328 qty_exponent: qty_exp,
1329 min_qty,
1330 max_qty,
1331 step_size,
1332 });
1333 }
1334 _ => {}
1335 }
1336 }
1337
1338 let (_perm_sets_block_len, perm_sets_count) = cursor.read_group_header()?;
1340 let mut permissions = Vec::with_capacity(perm_sets_count as usize);
1341 for _ in 0..perm_sets_count {
1342 let (_perms_block_len, perms_count) = cursor.read_group_header()?;
1344 let mut perm_set = Vec::with_capacity(perms_count as usize);
1345 for _ in 0..perms_count {
1346 let perm = cursor.read_var_string8()?;
1347 perm_set.push(perm);
1348 }
1349 permissions.push(perm_set);
1350 }
1351
1352 let symbol = cursor.read_var_string8()?;
1354 let base_asset = cursor.read_var_string8()?;
1355 let quote_asset = cursor.read_var_string8()?;
1356
1357 symbols.push(BinanceSymbolSbe {
1358 symbol,
1359 base_asset,
1360 quote_asset,
1361 base_asset_precision,
1362 quote_asset_precision,
1363 status,
1364 order_types,
1365 iceberg_allowed,
1366 oco_allowed,
1367 oto_allowed,
1368 quote_order_qty_market_allowed,
1369 allow_trailing_stop,
1370 cancel_replace_allowed,
1371 amend_allowed,
1372 is_spot_trading_allowed,
1373 is_margin_trading_allowed,
1374 filters,
1375 permissions,
1376 });
1377 }
1378
1379 Ok(BinanceExchangeInfoSbe { symbols })
1382}
1383
1384fn decode_notional_amount(
1385 cursor: &mut SbeCursor<'_>,
1386 exponent: i8,
1387) -> Result<Decimal, SbeDecodeError> {
1388 let mantissa = cursor.read_i64_le()?;
1389 if exponent == i8::MIN || mantissa < 0 {
1390 return Err(SbeDecodeError::InvalidValue { field: "notional" });
1391 }
1392 Decimal::from_scientific(&format!("{mantissa}e{exponent}"))
1393 .map_err(|_| SbeDecodeError::InvalidValue { field: "notional" })
1394}
1395
1396fn decode_notional_market_flag(cursor: &mut SbeCursor<'_>) -> Result<bool, SbeDecodeError> {
1397 match cursor.read_u8()? {
1398 value if value == BoolEnum::False as u8 => Ok(false),
1399 value if value == BoolEnum::True as u8 => Ok(true),
1400 _ => Err(SbeDecodeError::InvalidValue {
1401 field: "notional market flag",
1402 }),
1403 }
1404}
1405
1406#[cfg(test)]
1407mod tests {
1408 use rstest::rstest;
1409
1410 use super::*;
1411 use crate::spot::sbe::spot::{
1412 SBE_SCHEMA_VERSION, WriteBuf,
1413 cancel_order_response_codec::CancelOrderResponseEncoder,
1414 expiry_reason::ExpiryReason,
1415 floor::Floor,
1416 match_type::MatchType,
1417 new_order_full_response_codec::{
1418 NewOrderFullResponseEncoder,
1419 encoder::{FillsEncoder, PreventedMatchesEncoder},
1420 },
1421 order_capacity::OrderCapacity,
1422 order_response_codec::OrderResponseEncoder,
1423 order_side::OrderSide,
1424 order_status::OrderStatus,
1425 order_type::OrderType,
1426 orders_response_codec::{OrdersResponseEncoder, encoder::OrdersEncoder},
1427 self_trade_prevention_mode::SelfTradePreventionMode,
1428 time_in_force::TimeInForce,
1429 };
1430
1431 fn create_header(block_length: u16, template_id: u16, schema_id: u16, version: u16) -> [u8; 8] {
1432 let mut buf = [0u8; 8];
1433 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1434 buf[2..4].copy_from_slice(&template_id.to_le_bytes());
1435 buf[4..6].copy_from_slice(&schema_id.to_le_bytes());
1436 buf[6..8].copy_from_slice(&version.to_le_bytes());
1437 buf
1438 }
1439
1440 #[rstest]
1441 fn test_decode_ping_valid() {
1442 let buf = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1444 decode_ping(&buf).unwrap();
1445 }
1446
1447 #[rstest]
1448 fn test_decode_ping_buffer_too_short() {
1449 let buf = [0u8; 4];
1450 let err = decode_ping(&buf).unwrap_err();
1451 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1452 }
1453
1454 #[rstest]
1455 fn test_decode_ping_schema_mismatch() {
1456 let buf = create_header(0, PING_TEMPLATE_ID, 99, SBE_SCHEMA_VERSION);
1457 let err = decode_ping(&buf).unwrap_err();
1458 assert!(matches!(err, SbeDecodeError::SchemaMismatch { .. }));
1459 }
1460
1461 #[rstest]
1462 fn test_decode_ping_wrong_template() {
1463 let buf = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1464 let err = decode_ping(&buf).unwrap_err();
1465 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(999)));
1466 }
1467
1468 #[rstest]
1469 fn test_decode_server_time_valid() {
1470 let header = create_header(
1472 8,
1473 SERVER_TIME_TEMPLATE_ID,
1474 SBE_SCHEMA_ID,
1475 SBE_SCHEMA_VERSION,
1476 );
1477 let timestamp: i64 = 1734300000000; let mut buf = Vec::with_capacity(16);
1480 buf.extend_from_slice(&header);
1481 buf.extend_from_slice(×tamp.to_le_bytes());
1482
1483 let result = decode_server_time(&buf).unwrap();
1484 assert_eq!(result, timestamp);
1485 }
1486
1487 #[rstest]
1488 fn test_decode_server_time_buffer_too_short() {
1489 let buf = create_header(
1491 8,
1492 SERVER_TIME_TEMPLATE_ID,
1493 SBE_SCHEMA_ID,
1494 SBE_SCHEMA_VERSION,
1495 );
1496 let err = decode_server_time(&buf).unwrap_err();
1497 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
1498 }
1499
1500 #[rstest]
1501 fn test_decode_server_time_wrong_template() {
1502 let header = create_header(8, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1503 let mut buf = Vec::with_capacity(16);
1504 buf.extend_from_slice(&header);
1505 buf.extend_from_slice(&0i64.to_le_bytes());
1506
1507 let err = decode_server_time(&buf).unwrap_err();
1508 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1509 }
1510
1511 #[rstest]
1515 #[case(SBE_SCHEMA_VERSION - 1)]
1516 #[case(SBE_SCHEMA_VERSION)]
1517 #[case(SBE_SCHEMA_VERSION + 1)]
1518 #[case(99)]
1519 fn test_decode_server_time_accepts_any_version(#[case] version: u16) {
1520 let header = create_header(8, SERVER_TIME_TEMPLATE_ID, SBE_SCHEMA_ID, version);
1521 let mut buf = Vec::with_capacity(16);
1522 buf.extend_from_slice(&header);
1523 buf.extend_from_slice(&1_700_000_000_000i64.to_le_bytes());
1524
1525 let result = decode_server_time(&buf).unwrap();
1526 assert_eq!(result, 1_700_000_000_000);
1527 }
1528
1529 fn create_group_header(block_length: u16, count: u32) -> [u8; 6] {
1530 let mut buf = [0u8; 6];
1531 buf[0..2].copy_from_slice(&block_length.to_le_bytes());
1532 buf[2..6].copy_from_slice(&count.to_le_bytes());
1533 buf
1534 }
1535
1536 #[rstest]
1537 #[case::bids(DEPTH_TEMPLATE_ID, 16, false)]
1538 #[case::asks(DEPTH_TEMPLATE_ID, 16, true)]
1539 #[case::trades(TRADES_TEMPLATE_ID, 42, false)]
1540 #[case::agg_trades(AGG_TRADES_TEMPLATE_ID, 50, false)]
1541 fn test_market_data_rejects_undersized_groups(
1542 #[case] template: u16,
1543 #[case] minimum: u16,
1544 #[case] asks: bool,
1545 #[values(0, 1, 10_000)] count: u32,
1546 ) {
1547 let fixed_width = if template == DEPTH_TEMPLATE_ID { 10 } else { 2 };
1548
1549 for width in 0..minimum {
1550 let mut buf =
1551 create_header(fixed_width, template, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION).to_vec();
1552 buf.resize(HEADER_LENGTH + usize::from(fixed_width), 0);
1553 if asks {
1554 buf.extend_from_slice(&create_group_header(minimum, 1));
1555 buf.extend_from_slice(&[0; 16]);
1556 }
1557
1558 buf.extend_from_slice(&create_group_header(width, count));
1559 buf.extend_from_slice(&[0; 200]);
1560 let result = match template {
1561 DEPTH_TEMPLATE_ID => decode_depth(&buf).map(|_| ()),
1562 TRADES_TEMPLATE_ID => decode_trades(&buf).map(|_| ()),
1563 AGG_TRADES_TEMPLATE_ID => decode_agg_trades(&buf).map(|_| ()),
1564 _ => unreachable!(),
1565 };
1566
1567 assert_eq!(
1568 result,
1569 Err(SbeDecodeError::InvalidBlockLength {
1570 expected: minimum,
1571 actual: width,
1572 })
1573 );
1574 }
1575 }
1576
1577 #[rstest]
1578 #[case::depth(DEPTH_TEMPLATE_ID, 16)]
1579 #[case::trades(TRADES_TEMPLATE_ID, 42)]
1580 #[case::agg_trades(AGG_TRADES_TEMPLATE_ID, 50)]
1581 fn test_market_data_group_extensions(#[case] template: u16, #[case] minimum: u16) {
1582 let fixed_width = if template == DEPTH_TEMPLATE_ID { 10 } else { 2 };
1583 let mut buf =
1584 create_header(fixed_width, template, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION).to_vec();
1585 if template == DEPTH_TEMPLATE_ID {
1586 buf.extend_from_slice(&999i64.to_le_bytes());
1587 }
1588
1589 buf.extend_from_slice(&[(-2i8) as u8, (-8i8) as u8]);
1590
1591 let group_count = if template == DEPTH_TEMPLATE_ID { 2 } else { 1 };
1592 for group in 0..group_count {
1593 buf.extend_from_slice(&create_group_header(minimum + 3, 2));
1594 for entry in 0..2 {
1595 for field in 1..=minimum / 8 {
1596 let value = i64::from(group * 200 + entry * 100 + field + 100);
1597 buf.extend_from_slice(&value.to_le_bytes());
1598 }
1599
1600 if template != DEPTH_TEMPLATE_ID {
1601 buf.extend_from_slice(&[1, 0]);
1602 }
1603
1604 buf.extend_from_slice(&[0xFF; 3]);
1605 }
1606 }
1607
1608 match template {
1609 DEPTH_TEMPLATE_ID => assert_eq!(
1610 decode_depth(&buf).unwrap(),
1611 BinanceDepth {
1612 last_update_id: 999,
1613 price_exponent: -2,
1614 qty_exponent: -8,
1615 bids: vec![
1616 BinancePriceLevel {
1617 price_mantissa: 101,
1618 qty_mantissa: 102
1619 },
1620 BinancePriceLevel {
1621 price_mantissa: 201,
1622 qty_mantissa: 202
1623 }
1624 ],
1625 asks: vec![
1626 BinancePriceLevel {
1627 price_mantissa: 301,
1628 qty_mantissa: 302
1629 },
1630 BinancePriceLevel {
1631 price_mantissa: 401,
1632 qty_mantissa: 402
1633 }
1634 ],
1635 }
1636 ),
1637 TRADES_TEMPLATE_ID => assert_eq!(
1638 decode_trades(&buf).unwrap(),
1639 BinanceTrades {
1640 price_exponent: -2,
1641 qty_exponent: -8,
1642 trades: vec![
1643 BinanceTrade {
1644 id: 101,
1645 price_mantissa: 102,
1646 qty_mantissa: 103,
1647 quote_qty_mantissa: 104,
1648 time: 105,
1649 is_buyer_maker: true,
1650 is_best_match: false
1651 },
1652 BinanceTrade {
1653 id: 201,
1654 price_mantissa: 202,
1655 qty_mantissa: 203,
1656 quote_qty_mantissa: 204,
1657 time: 205,
1658 is_buyer_maker: true,
1659 is_best_match: false
1660 }
1661 ],
1662 }
1663 ),
1664 AGG_TRADES_TEMPLATE_ID => assert_eq!(
1665 decode_agg_trades(&buf).unwrap(),
1666 BinanceAggTrades {
1667 price_exponent: -2,
1668 qty_exponent: -8,
1669 trades: vec![
1670 BinanceAggTrade {
1671 id: 101,
1672 price_mantissa: 102,
1673 qty_mantissa: 103,
1674 first_trade_id: 104,
1675 last_trade_id: 105,
1676 time: 106,
1677 is_buyer_maker: true,
1678 is_best_match: false
1679 },
1680 BinanceAggTrade {
1681 id: 201,
1682 price_mantissa: 202,
1683 qty_mantissa: 203,
1684 first_trade_id: 204,
1685 last_trade_id: 205,
1686 time: 206,
1687 is_buyer_maker: true,
1688 is_best_match: false
1689 }
1690 ],
1691 }
1692 ),
1693 _ => unreachable!(),
1694 }
1695 }
1696
1697 #[rstest]
1698 fn test_decode_depth_valid() {
1699 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1701
1702 let mut buf = Vec::new();
1703 buf.extend_from_slice(&header);
1704
1705 let last_update_id: i64 = 123456789;
1707 let price_exponent: i8 = -8;
1708 let qty_exponent: i8 = -8;
1709 buf.extend_from_slice(&last_update_id.to_le_bytes());
1710 buf.push(price_exponent as u8);
1711 buf.push(qty_exponent as u8);
1712
1713 buf.extend_from_slice(&create_group_header(16, 2));
1715 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
1717 buf.extend_from_slice(&50_000_000i64.to_le_bytes());
1718 buf.extend_from_slice(&99_900_000_000i64.to_le_bytes());
1720 buf.extend_from_slice(&30_000_000i64.to_le_bytes());
1721
1722 buf.extend_from_slice(&create_group_header(16, 1));
1724 buf.extend_from_slice(&100_100_000_000i64.to_le_bytes());
1726 buf.extend_from_slice(&25_000_000i64.to_le_bytes());
1727
1728 let depth = decode_depth(&buf).unwrap();
1729
1730 assert_eq!(depth.last_update_id, 123456789);
1731 assert_eq!(depth.price_exponent, -8);
1732 assert_eq!(depth.qty_exponent, -8);
1733 assert_eq!(depth.bids.len(), 2);
1734 assert_eq!(depth.asks.len(), 1);
1735 assert_eq!(depth.bids[0].price_mantissa, 100_000_000_000);
1736 assert_eq!(depth.bids[0].qty_mantissa, 50_000_000);
1737 assert_eq!(depth.asks[0].price_mantissa, 100_100_000_000);
1738 }
1739
1740 #[rstest]
1741 fn test_decode_depth_empty_book() {
1742 let header = create_header(10, DEPTH_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1743
1744 let mut buf = Vec::new();
1745 buf.extend_from_slice(&header);
1746 buf.extend_from_slice(&0i64.to_le_bytes()); buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(16, 0));
1752 buf.extend_from_slice(&create_group_header(16, 0));
1754
1755 let depth = decode_depth(&buf).unwrap();
1756
1757 assert!(depth.bids.is_empty());
1758 assert!(depth.asks.is_empty());
1759 }
1760
1761 #[rstest]
1762 fn test_decode_trades_valid() {
1763 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1765
1766 let mut buf = Vec::new();
1767 buf.extend_from_slice(&header);
1768
1769 let price_exponent: i8 = -8;
1771 let qty_exponent: i8 = -8;
1772 buf.push(price_exponent as u8);
1773 buf.push(qty_exponent as u8);
1774
1775 buf.extend_from_slice(&create_group_header(42, 1));
1777
1778 let trade_id: i64 = 999;
1780 let price: i64 = 100_000_000_000;
1781 let qty: i64 = 10_000_000;
1782 let quote_qty: i64 = 1_000_000_000_000;
1783 let time: i64 = 1734300000000;
1784 let is_buyer_maker: u8 = 1; let is_best_match: u8 = 1; buf.extend_from_slice(&trade_id.to_le_bytes());
1788 buf.extend_from_slice(&price.to_le_bytes());
1789 buf.extend_from_slice(&qty.to_le_bytes());
1790 buf.extend_from_slice("e_qty.to_le_bytes());
1791 buf.extend_from_slice(&time.to_le_bytes());
1792 buf.push(is_buyer_maker);
1793 buf.push(is_best_match);
1794
1795 let trades = decode_trades(&buf).unwrap();
1796
1797 assert_eq!(trades.price_exponent, -8);
1798 assert_eq!(trades.qty_exponent, -8);
1799 assert_eq!(trades.trades.len(), 1);
1800 assert_eq!(trades.trades[0].id, 999);
1801 assert_eq!(trades.trades[0].price_mantissa, 100_000_000_000);
1802 assert!(trades.trades[0].is_buyer_maker);
1803 assert!(trades.trades[0].is_best_match);
1804 }
1805
1806 #[rstest]
1807 fn test_decode_trades_empty() {
1808 let header = create_header(2, TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1809
1810 let mut buf = Vec::new();
1811 buf.extend_from_slice(&header);
1812 buf.push(0); buf.push(0); buf.extend_from_slice(&create_group_header(42, 0));
1817
1818 let trades = decode_trades(&buf).unwrap();
1819
1820 assert!(trades.trades.is_empty());
1821 }
1822
1823 #[rstest]
1824 fn test_decode_agg_trades_preserves_all_fields() {
1825 let header = create_header(2, AGG_TRADES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1826 let mut buf = Vec::new();
1827 buf.extend_from_slice(&header);
1828 buf.push((-7_i8) as u8);
1829 buf.push((-5_i8) as u8);
1830 buf.extend_from_slice(&create_group_header(50, 1));
1831 buf.extend_from_slice(&101_i64.to_le_bytes());
1832 buf.extend_from_slice(&123_456_789_i64.to_le_bytes());
1833 buf.extend_from_slice(&765_432_i64.to_le_bytes());
1834 buf.extend_from_slice(&201_i64.to_le_bytes());
1835 buf.extend_from_slice(&207_i64.to_le_bytes());
1836 buf.extend_from_slice(&1_700_000_000_123_i64.to_le_bytes());
1837 buf.push(1);
1838 buf.push(0);
1839
1840 let trades = decode_agg_trades(&buf).unwrap();
1841
1842 assert_eq!(trades.price_exponent, -7);
1843 assert_eq!(trades.qty_exponent, -5);
1844 assert_eq!(trades.trades.len(), 1);
1845 assert_eq!(trades.trades[0].id, 101);
1846 assert_eq!(trades.trades[0].price_mantissa, 123_456_789);
1847 assert_eq!(trades.trades[0].qty_mantissa, 765_432);
1848 assert_eq!(trades.trades[0].first_trade_id, 201);
1849 assert_eq!(trades.trades[0].last_trade_id, 207);
1850 assert_eq!(trades.trades[0].time, 1_700_000_000_123);
1851 assert!(trades.trades[0].is_buyer_maker);
1852 assert!(!trades.trades[0].is_best_match);
1853 }
1854
1855 #[rstest]
1856 fn test_decode_agg_trades_rejects_wrong_template() {
1857 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1858 let mut buf = Vec::new();
1859 buf.extend_from_slice(&header);
1860 buf.extend_from_slice(&[0_u8; 2]);
1861
1862 let error = decode_agg_trades(&buf).unwrap_err();
1863
1864 assert!(matches!(error, SbeDecodeError::UnknownTemplateId(101)));
1865 }
1866
1867 #[rstest]
1868 fn test_decode_depth_wrong_template() {
1869 let header = create_header(10, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1870
1871 let mut buf = Vec::new();
1872 buf.extend_from_slice(&header);
1873 buf.extend_from_slice(&[0u8; 10]); let err = decode_depth(&buf).unwrap_err();
1876 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1877 }
1878
1879 #[rstest]
1880 fn test_decode_trades_wrong_template() {
1881 let header = create_header(2, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
1882
1883 let mut buf = Vec::new();
1884 buf.extend_from_slice(&header);
1885 buf.extend_from_slice(&[0u8; 2]); let err = decode_trades(&buf).unwrap_err();
1888 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
1889 }
1890
1891 fn write_var_string(buf: &mut Vec<u8>, s: &str) {
1892 buf.push(s.len() as u8);
1893 buf.extend_from_slice(s.as_bytes());
1894 }
1895
1896 fn resize_fixed_block(buf: &mut Vec<u8>, block_length: usize) {
1897 let current = usize::from(u16::from_le_bytes([buf[0], buf[1]]));
1898 let block_end = HEADER_LENGTH + current;
1899 let new_end = HEADER_LENGTH + block_length;
1900
1901 if new_end < block_end {
1902 buf.drain(new_end..block_end);
1903 } else {
1904 buf.splice(block_end..block_end, vec![0u8; new_end - block_end]);
1905 }
1906
1907 buf[0..2].copy_from_slice(&(block_length as u16).to_le_bytes());
1908 }
1909
1910 fn build_order_response_buffer(expiry_reason: ExpiryReason) -> Vec<u8> {
1911 let mut bytes = vec![0u8; 256];
1912 let enc = OrderResponseEncoder::default().wrap(WriteBuf::new(&mut bytes), HEADER_LENGTH);
1913 let mut header = enc.header(0);
1914 let mut enc = header.parent().unwrap();
1915 enc.price_exponent(-2);
1916 enc.qty_exponent(-4);
1917 enc.order_id(12345);
1918 enc.order_list_id(99);
1919 enc.price(12_345);
1920 enc.orig_qty(25_000);
1921 enc.executed_qty(10_000);
1922 enc.cummulative_quote_qty(123_450_000);
1923 enc.status(OrderStatus::PartiallyFilled);
1924 enc.time_in_force(TimeInForce::Gtc);
1925 enc.order_type(OrderType::StopLossLimit);
1926 enc.side(OrderSide::Sell);
1927 enc.stop_price(12_000);
1928 enc.trailing_delta(100);
1929 enc.trailing_time(1_700_000_000_000_100);
1930 enc.iceberg_qty(5_000);
1931 enc.time(1_700_000_000_000_000);
1932 enc.update_time(1_700_000_000_000_200);
1933 enc.is_working(BoolEnum::True);
1934 enc.working_time(1_700_000_000_000_300);
1935 enc.orig_quote_order_qty(7_000);
1936 enc.strategy_id(42);
1937 enc.strategy_type(7);
1938 enc.order_capacity(OrderCapacity::Principal);
1939 enc.working_floor(Floor::Sor);
1940 enc.self_trade_prevention_mode(SelfTradePreventionMode::ExpireMaker);
1941 enc.prevented_match_id(77);
1942 enc.prevented_quantity(3);
1943 enc.used_sor(BoolEnum::True);
1944 enc.expiry_reason(expiry_reason);
1945 enc.symbol("BTCUSDT");
1946 enc.client_order_id("my-order-123");
1947 let len = HEADER_LENGTH + enc.encoded_length();
1948 bytes.truncate(len);
1949 bytes
1950 }
1951
1952 fn expected_order_response(expiry_reason: Option<u8>) -> BinanceOrderResponse {
1953 BinanceOrderResponse {
1954 price_exponent: -2,
1955 qty_exponent: -4,
1956 order_id: 12345,
1957 order_list_id: Some(99),
1958 price_mantissa: 12_345,
1959 orig_qty_mantissa: 25_000,
1960 executed_qty_mantissa: 10_000,
1961 cummulative_quote_qty_mantissa: 123_450_000,
1962 status: OrderStatus::PartiallyFilled,
1963 time_in_force: TimeInForce::Gtc,
1964 order_type: OrderType::StopLossLimit,
1965 side: OrderSide::Sell,
1966 stop_price_mantissa: Some(12_000),
1967 iceberg_qty_mantissa: Some(5_000),
1968 time: 1_700_000_000_000_000,
1969 update_time: 1_700_000_000_000_200,
1970 is_working: true,
1971 working_time: Some(1_700_000_000_000_300),
1972 orig_quote_order_qty_mantissa: 7_000,
1973 self_trade_prevention_mode: SelfTradePreventionMode::ExpireMaker,
1974 client_order_id: "my-order-123".to_string(),
1975 symbol: "BTCUSDT".to_string(),
1976 expiry_reason,
1977 }
1978 }
1979
1980 #[rstest]
1981 #[case::pre_v4_no_expiry_reason(162, ExpiryReason::NullVal, None)]
1982 #[case::v4_null_sentinel(163, ExpiryReason::NullVal, None)]
1983 #[case::v4_captures_value(163, ExpiryReason::UnfilledIocQuantityExpired, Some(0x05))]
1984 #[case::future_block_length(167, ExpiryReason::UnfilledIocQuantityExpired, Some(0x05))]
1985 fn test_decode_order_matches_codec(
1986 #[case] block_length: usize,
1987 #[case] expiry_reason: ExpiryReason,
1988 #[case] expected: Option<u8>,
1989 ) {
1990 let mut buf = build_order_response_buffer(expiry_reason);
1991 resize_fixed_block(&mut buf, block_length);
1992
1993 let order = decode_order(&buf).unwrap();
1994
1995 assert_eq!(order, expected_order_response(expected));
1996 }
1997
1998 #[rstest]
1999 fn test_decode_order_rejects_short_block() {
2000 let mut buf = build_order_response_buffer(ExpiryReason::NullVal);
2001 resize_fixed_block(&mut buf, 161);
2002
2003 let error = decode_order(&buf).unwrap_err();
2004
2005 assert_eq!(
2006 error,
2007 SbeDecodeError::InvalidBlockLength {
2008 expected: 162,
2009 actual: 161,
2010 }
2011 );
2012 }
2013
2014 fn build_orders_response_buffer(orders: &[(i64, &str)]) -> Vec<u8> {
2015 let mut bytes = vec![0u8; 512];
2016 let enc = OrdersResponseEncoder::default().wrap(WriteBuf::new(&mut bytes), HEADER_LENGTH);
2017 let mut header = enc.header(0);
2018 let enc = header.parent().unwrap();
2019 let mut group = enc.orders_encoder(orders.len() as u32, OrdersEncoder::default());
2020
2021 for (order_id, client_order_id) in orders {
2022 group.advance().unwrap();
2023 group.order_id(*order_id);
2024 group.iceberg_qty(5_000);
2025 group.time(order_id * 10);
2026 group.update_time(order_id * 10 + 1);
2027 group.is_working(BoolEnum::True);
2028 group.working_time(order_id * 10 + 2);
2029 group.self_trade_prevention_mode(SelfTradePreventionMode::ExpireTaker);
2030 group.expiry_reason(ExpiryReason::NullVal);
2031 group.symbol("BTCUSDT");
2032 group.client_order_id(client_order_id);
2033 }
2034
2035 let enc = group.parent().unwrap();
2036 let len = HEADER_LENGTH + enc.encoded_length();
2037 bytes.truncate(len);
2038 bytes
2039 }
2040
2041 #[rstest]
2042 fn test_decode_orders_matches_codec() {
2043 let expected = [(111, "order-1"), (222, "order-2")];
2044 let buf = build_orders_response_buffer(&expected);
2045
2046 let orders = decode_orders(&buf).unwrap();
2047
2048 assert_eq!(orders.len(), 2);
2049
2050 for (order, (order_id, client_order_id)) in orders.iter().zip(expected) {
2051 assert_eq!(order.order_id, order_id);
2052 assert_eq!(order.iceberg_qty_mantissa, Some(5_000));
2053 assert_eq!(order.time, order_id * 10);
2054 assert_eq!(order.update_time, order_id * 10 + 1);
2055 assert!(order.is_working);
2056 assert_eq!(order.working_time, Some(order_id * 10 + 2));
2057 assert_eq!(
2058 order.self_trade_prevention_mode,
2059 SelfTradePreventionMode::ExpireTaker
2060 );
2061 assert_eq!(order.expiry_reason, None);
2062 assert_eq!(order.symbol, "BTCUSDT");
2063 assert_eq!(order.client_order_id, client_order_id);
2064 }
2065 }
2066
2067 #[rstest]
2068 fn test_decode_orders_multiple() {
2069 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2071
2072 let mut buf = Vec::new();
2073 buf.extend_from_slice(&header);
2074
2075 buf.extend_from_slice(&create_group_header(ORDER_MIN_BLOCK_LENGTH, 2));
2077
2078 let order1_start = buf.len();
2080 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 < ORDER_MIN_BLOCK_LENGTH as usize {
2103 buf.push(0);
2104 }
2105 write_var_string(&mut buf, "BTCUSDT");
2106 write_var_string(&mut buf, "order-1");
2107
2108 let order2_start = buf.len();
2110 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 < ORDER_MIN_BLOCK_LENGTH as usize {
2132 buf.push(0);
2133 }
2134 write_var_string(&mut buf, "ETHUSDT");
2135 write_var_string(&mut buf, "order-2");
2136
2137 let orders = decode_orders(&buf).unwrap();
2138
2139 assert_eq!(orders.len(), 2);
2140 assert_eq!(orders[0].order_id, 1001);
2141 assert_eq!(orders[0].symbol, "BTCUSDT");
2142 assert_eq!(orders[0].client_order_id, "order-1");
2143 assert_eq!(orders[0].price_mantissa, 100_000_000_000);
2144
2145 assert_eq!(orders[1].order_id, 2002);
2146 assert_eq!(orders[1].symbol, "ETHUSDT");
2147 assert_eq!(orders[1].client_order_id, "order-2");
2148 assert_eq!(orders[1].price_mantissa, 200_000_000_000);
2149 }
2150
2151 #[rstest]
2152 fn test_decode_orders_v4_trailing_expiry_reason() {
2153 const V4_BLOCK_LENGTH: u16 = 163;
2157 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2158
2159 let mut buf = Vec::new();
2160 buf.extend_from_slice(&header);
2161 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
2162
2163 let order_start = buf.len();
2164 buf.extend_from_slice(&[0u8; ORDER_MIN_BLOCK_LENGTH as usize]);
2165 buf.push(0xFF); assert_eq!(buf.len() - order_start, V4_BLOCK_LENGTH as usize);
2167
2168 write_var_string(&mut buf, "BTCUSDT");
2169 write_var_string(&mut buf, "v4-order");
2170
2171 let orders = decode_orders(&buf).unwrap();
2172
2173 assert_eq!(orders.len(), 1);
2174 assert_eq!(orders[0].symbol, "BTCUSDT");
2175 assert_eq!(orders[0].client_order_id, "v4-order");
2176 assert!(orders[0].expiry_reason.is_none());
2177 }
2178
2179 #[rstest]
2180 fn test_decode_orders_pre_v4_block_returns_no_expiry_reason() {
2181 const PRE_V4_BLOCK_LENGTH: u16 = ORDER_MIN_BLOCK_LENGTH;
2185 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2186
2187 let mut buf = Vec::new();
2188 buf.extend_from_slice(&header);
2189 buf.extend_from_slice(&create_group_header(PRE_V4_BLOCK_LENGTH, 1));
2190
2191 buf.extend_from_slice(&[0xAAu8; PRE_V4_BLOCK_LENGTH as usize]);
2194
2195 write_var_string(&mut buf, "BTCUSDT");
2196 write_var_string(&mut buf, "pre-v4-order");
2197
2198 let orders = decode_orders(&buf).unwrap();
2199 assert!(orders[0].expiry_reason.is_none());
2200 }
2201
2202 #[rstest]
2203 fn test_decode_orders_v4_captures_expiry_reason_value() {
2204 const V4_BLOCK_LENGTH: u16 = 163;
2208 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2209
2210 let mut buf = Vec::new();
2211 buf.extend_from_slice(&header);
2212 buf.extend_from_slice(&create_group_header(V4_BLOCK_LENGTH, 1));
2213
2214 buf.extend_from_slice(&[0u8; ORDER_MIN_BLOCK_LENGTH as usize]);
2215 buf.push(0x05);
2216
2217 write_var_string(&mut buf, "BTCUSDT");
2218 write_var_string(&mut buf, "v4-expired");
2219
2220 let orders = decode_orders(&buf).unwrap();
2221 assert_eq!(orders[0].expiry_reason, Some(0x05));
2222 }
2223
2224 #[rstest]
2225 fn test_decode_orders_empty() {
2226 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2227
2228 let mut buf = Vec::new();
2229 buf.extend_from_slice(&header);
2230 buf.extend_from_slice(&create_group_header(ORDER_MIN_BLOCK_LENGTH, 0));
2231
2232 let orders = decode_orders(&buf).unwrap();
2233 assert!(orders.is_empty());
2234 }
2235
2236 #[rstest]
2237 fn test_decode_orders_truncated_var_string() {
2238 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2239
2240 let mut buf = Vec::new();
2241 buf.extend_from_slice(&header);
2242 buf.extend_from_slice(&create_group_header(ORDER_MIN_BLOCK_LENGTH, 1));
2243
2244 buf.extend_from_slice(&[0u8; ORDER_MIN_BLOCK_LENGTH as usize]);
2246
2247 buf.push(7); buf.extend_from_slice(b"BTC"); let err = decode_orders(&buf).unwrap_err();
2252 assert!(matches!(err, SbeDecodeError::BufferTooShort { .. }));
2253 }
2254
2255 #[rstest]
2256 fn test_decode_orders_invalid_utf8() {
2257 let header = create_header(0, ORDERS_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2258
2259 let mut buf = Vec::new();
2260 buf.extend_from_slice(&header);
2261 buf.extend_from_slice(&create_group_header(ORDER_MIN_BLOCK_LENGTH, 1));
2262
2263 buf.extend_from_slice(&[0u8; ORDER_MIN_BLOCK_LENGTH as usize]);
2264
2265 buf.push(4);
2267 buf.extend_from_slice(&[0xFF, 0xFE, 0x00, 0x01]);
2268
2269 let err = decode_orders(&buf).unwrap_err();
2270 assert!(matches!(err, SbeDecodeError::InvalidUtf8));
2271 }
2272
2273 #[rstest]
2274 #[case::current(137)]
2275 #[case::future_block_length(141)]
2276 fn test_decode_cancel_order_matches_codec(#[case] block_length: usize) {
2277 let mut buf = create_cancel_order_response_buffer(99999, "BTCUSDT", "orig-id", "new-id");
2278 resize_fixed_block(&mut buf, block_length);
2279
2280 let cancel = decode_cancel_order(&buf).unwrap();
2281
2282 assert_eq!(cancel.order_id, 99999);
2283 assert!(cancel.order_list_id.is_none());
2284 assert_eq!(cancel.transact_time, 1_700_000_000_000_000);
2285 assert_eq!(cancel.status, OrderStatus::Canceled);
2286 assert_eq!(
2287 cancel.self_trade_prevention_mode,
2288 SelfTradePreventionMode::ExpireTaker
2289 );
2290 assert_eq!(cancel.symbol, "BTCUSDT");
2291 assert_eq!(cancel.orig_client_order_id, "orig-id");
2292 assert_eq!(cancel.client_order_id, "new-id");
2293 }
2294
2295 #[rstest]
2296 fn test_decode_account_with_balances() {
2297 let header = create_header(
2298 ACCOUNT_BLOCK_LENGTH as u16,
2299 ACCOUNT_TEMPLATE_ID,
2300 SBE_SCHEMA_ID,
2301 SBE_SCHEMA_VERSION,
2302 );
2303
2304 let mut buf = Vec::new();
2305 buf.extend_from_slice(&header);
2306
2307 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 {
2324 buf.push(0);
2325 }
2326
2327 buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 2));
2329
2330 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");
2335
2336 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");
2341
2342 let account = decode_account(&buf).unwrap();
2343
2344 assert!(account.can_trade);
2345 assert!(account.can_withdraw);
2346 assert!(account.can_deposit);
2347 assert_eq!(account.balances.len(), 2);
2348 assert_eq!(account.balances[0].asset, "BTC");
2349 assert_eq!(account.balances[0].free_mantissa, 100_000_000);
2350 assert_eq!(account.balances[0].locked_mantissa, 50_000_000);
2351 assert_eq!(account.balances[1].asset, "USDT");
2352 assert_eq!(account.balances[1].free_mantissa, 1_000_000_000_000);
2353 }
2354
2355 #[rstest]
2356 fn test_decode_account_empty_balances() {
2357 let header = create_header(
2358 ACCOUNT_BLOCK_LENGTH as u16,
2359 ACCOUNT_TEMPLATE_ID,
2360 SBE_SCHEMA_ID,
2361 SBE_SCHEMA_VERSION,
2362 );
2363
2364 let mut buf = Vec::new();
2365 buf.extend_from_slice(&header);
2366
2367 buf.push((-8i8) as u8);
2369 buf.extend_from_slice(&[0u8; 63]); buf.extend_from_slice(&create_group_header(BALANCE_BLOCK_LENGTH, 0));
2373
2374 let account = decode_account(&buf).unwrap();
2375 assert!(account.balances.is_empty());
2376 }
2377
2378 #[rstest]
2379 fn test_decode_account_trades_multiple() {
2380 let header = create_header(
2381 0,
2382 ACCOUNT_TRADES_TEMPLATE_ID,
2383 SBE_SCHEMA_ID,
2384 SBE_SCHEMA_VERSION,
2385 );
2386
2387 let mut buf = Vec::new();
2388 buf.extend_from_slice(&header);
2389
2390 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 2));
2392
2393 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");
2409 write_var_string(&mut buf, "BNB");
2410
2411 buf.push((-8i8) as u8);
2413 buf.push((-8i8) as u8);
2414 buf.push((-8i8) as u8);
2415 buf.extend_from_slice(&1002i64.to_le_bytes());
2416 buf.extend_from_slice(&5002i64.to_le_bytes());
2417 buf.extend_from_slice(&i64::MIN.to_le_bytes());
2418 buf.extend_from_slice(&200_000_000_000i64.to_le_bytes());
2419 buf.extend_from_slice(&5_000_000i64.to_le_bytes());
2420 buf.extend_from_slice(&1_000_000_000_000i64.to_le_bytes());
2421 buf.extend_from_slice(&50_000i64.to_le_bytes());
2422 buf.extend_from_slice(&1734300001000i64.to_le_bytes());
2423 buf.push(0); buf.push(1); buf.push(1); write_var_string(&mut buf, "ETHUSDT");
2427 write_var_string(&mut buf, "USDT");
2428
2429 let trades = decode_account_trades(&buf).unwrap();
2430
2431 assert_eq!(trades.len(), 2);
2432 assert_eq!(trades[0].id, 1001);
2433 assert_eq!(trades[0].order_id, 5001);
2434 assert!(trades[0].order_list_id.is_none());
2435 assert_eq!(trades[0].symbol, "BTCUSDT");
2436 assert_eq!(trades[0].commission_asset, "BNB");
2437 assert!(trades[0].is_buyer);
2438 assert!(!trades[0].is_maker);
2439
2440 assert_eq!(trades[1].id, 1002);
2441 assert_eq!(trades[1].symbol, "ETHUSDT");
2442 assert_eq!(trades[1].commission_asset, "USDT");
2443 assert!(!trades[1].is_buyer);
2444 assert!(trades[1].is_maker);
2445 }
2446
2447 #[rstest]
2448 fn test_decode_account_trades_empty() {
2449 let header = create_header(
2450 0,
2451 ACCOUNT_TRADES_TEMPLATE_ID,
2452 SBE_SCHEMA_ID,
2453 SBE_SCHEMA_VERSION,
2454 );
2455
2456 let mut buf = Vec::new();
2457 buf.extend_from_slice(&header);
2458 buf.extend_from_slice(&create_group_header(ACCOUNT_TRADE_BLOCK_LENGTH, 0));
2459
2460 let trades = decode_account_trades(&buf).unwrap();
2461 assert!(trades.is_empty());
2462 }
2463
2464 #[rstest]
2465 fn test_decode_exchange_info_single_symbol() {
2466 let header = create_header(
2467 0,
2468 EXCHANGE_INFO_TEMPLATE_ID,
2469 SBE_SCHEMA_ID,
2470 SBE_SCHEMA_VERSION,
2471 );
2472
2473 let mut buf = Vec::new();
2474 buf.extend_from_slice(&header);
2475
2476 buf.extend_from_slice(&create_group_header(11, 0));
2478
2479 buf.extend_from_slice(&create_group_header(0, 0));
2481
2482 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 1));
2484
2485 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));
2507
2508 buf.extend_from_slice(&create_group_header(0, 1));
2510 buf.extend_from_slice(&create_group_header(0, 1));
2511 write_var_string(&mut buf, "SPOT");
2512
2513 write_var_string(&mut buf, "BTCUSDT");
2515 write_var_string(&mut buf, "BTC");
2516 write_var_string(&mut buf, "USDT");
2517
2518 let info = decode_exchange_info(&buf).unwrap();
2519
2520 assert_eq!(info.symbols.len(), 1);
2521 let symbol = &info.symbols[0];
2522 assert_eq!(symbol.symbol, "BTCUSDT");
2523 assert_eq!(symbol.base_asset, "BTC");
2524 assert_eq!(symbol.quote_asset, "USDT");
2525 assert_eq!(symbol.base_asset_precision, 8);
2526 assert_eq!(symbol.quote_asset_precision, 8);
2527 assert_eq!(symbol.status, 0); assert_eq!(symbol.order_types, 0b0000_0111);
2529 assert!(symbol.iceberg_allowed);
2530 assert!(symbol.oco_allowed);
2531 assert!(!symbol.oto_allowed);
2532 assert!(symbol.quote_order_qty_market_allowed);
2533 assert!(symbol.allow_trailing_stop);
2534 assert!(symbol.cancel_replace_allowed);
2535 assert!(!symbol.amend_allowed);
2536 assert!(symbol.is_spot_trading_allowed);
2537 assert!(!symbol.is_margin_trading_allowed);
2538 assert!(symbol.filters.price_filter.is_none()); assert!(symbol.filters.lot_size_filter.is_none());
2540 assert_eq!(symbol.permissions.len(), 1);
2541 assert_eq!(symbol.permissions[0], vec!["SPOT"]);
2542 }
2543
2544 #[rstest]
2545 #[case::exponent(0, 128, "notional")]
2546 #[case::minimum(8, 255, "notional")]
2547 #[case::min_flag(9, 255, "notional market flag")]
2548 #[case::maximum(17, 255, "notional")]
2549 #[case::max_flag(18, 255, "notional market flag")]
2550 #[case::average(22, 255, "avgPriceMins")]
2551 fn test_spot_notional_sbe_invalid_fields(
2552 #[case] offset: usize,
2553 #[case] value: u8,
2554 #[case] field: &'static str,
2555 ) {
2556 let mut wire =
2557 include_bytes!("../../../test_data/spot/http_sbe/notional_range.sbe").to_vec();
2558 let header = [23, 0, 6, 0, 3, 0, 5, 0];
2559 let start = wire
2560 .windows(header.len())
2561 .position(|bytes| bytes == header)
2562 .unwrap();
2563 wire[start + header.len() + offset] = value;
2564 assert!(
2565 matches!(decode_exchange_info(&wire), Err(SbeDecodeError::InvalidValue { field: actual }) if actual == field)
2566 );
2567 }
2568
2569 #[rstest]
2570 fn test_spot_notional_sbe_truncated_filter() {
2571 let mut wire =
2572 include_bytes!("../../../test_data/spot/http_sbe/notional_range.sbe").to_vec();
2573 let header = [23, 0, 6, 0, 3, 0, 5, 0];
2574 let start = wire
2575 .windows(header.len())
2576 .position(|bytes| bytes == header)
2577 .unwrap();
2578 wire[start - 1] -= 1;
2579 assert!(matches!(
2580 decode_exchange_info(&wire),
2581 Err(SbeDecodeError::BufferTooShort {
2582 expected: 23,
2583 actual: 22
2584 })
2585 ));
2586 }
2587
2588 #[rstest]
2589 fn test_decode_exchange_info_empty() {
2590 let header = create_header(
2591 0,
2592 EXCHANGE_INFO_TEMPLATE_ID,
2593 SBE_SCHEMA_ID,
2594 SBE_SCHEMA_VERSION,
2595 );
2596
2597 let mut buf = Vec::new();
2598 buf.extend_from_slice(&header);
2599
2600 buf.extend_from_slice(&create_group_header(11, 0));
2602
2603 buf.extend_from_slice(&create_group_header(0, 0));
2605
2606 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 0));
2608
2609 let info = decode_exchange_info(&buf).unwrap();
2610 assert!(info.symbols.is_empty());
2611 }
2612
2613 #[rstest]
2614 fn test_decode_exchange_info_wrong_template() {
2615 let header = create_header(0, PING_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2616
2617 let mut buf = Vec::new();
2618 buf.extend_from_slice(&header);
2619
2620 let err = decode_exchange_info(&buf).unwrap_err();
2621 assert!(matches!(err, SbeDecodeError::UnknownTemplateId(101)));
2622 }
2623
2624 #[rstest]
2625 fn test_decode_exchange_info_multiple_symbols() {
2626 let header = create_header(
2627 0,
2628 EXCHANGE_INFO_TEMPLATE_ID,
2629 SBE_SCHEMA_ID,
2630 SBE_SCHEMA_VERSION,
2631 );
2632
2633 let mut buf = Vec::new();
2634 buf.extend_from_slice(&header);
2635
2636 buf.extend_from_slice(&create_group_header(11, 0));
2638
2639 buf.extend_from_slice(&create_group_header(0, 0));
2641
2642 buf.extend_from_slice(&create_group_header(SYMBOL_BLOCK_LENGTH as u16, 2));
2644
2645 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");
2667 write_var_string(&mut buf, "BTC");
2668 write_var_string(&mut buf, "USDT");
2669
2670 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");
2692 write_var_string(&mut buf, "ETH");
2693 write_var_string(&mut buf, "USDT");
2694
2695 let info = decode_exchange_info(&buf).unwrap();
2696
2697 assert_eq!(info.symbols.len(), 2);
2698 assert_eq!(info.symbols[0].symbol, "BTCUSDT");
2699 assert_eq!(info.symbols[0].base_asset, "BTC");
2700 assert!(!info.symbols[0].is_margin_trading_allowed);
2701
2702 assert_eq!(info.symbols[1].symbol, "ETHUSDT");
2703 assert_eq!(info.symbols[1].base_asset, "ETH");
2704 assert!(info.symbols[1].is_margin_trading_allowed);
2705 }
2706
2707 #[rstest]
2708 fn test_decode_klines_valid() {
2709 let header = create_header(2, KLINES_TEMPLATE_ID, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION);
2710
2711 let mut buf = Vec::new();
2712 buf.extend_from_slice(&header);
2713 buf.push((-2i8) as u8); buf.push((-4i8) as u8); buf.extend_from_slice(&create_group_header(KLINES_BLOCK_LENGTH, 1));
2716 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();
2729
2730 assert_eq!(klines.price_exponent, -2);
2731 assert_eq!(klines.qty_exponent, -4);
2732 assert_eq!(klines.klines.len(), 1);
2733 assert_eq!(klines.klines[0].open_time, 1_700_000_000_000_000);
2734 assert_eq!(klines.klines[0].close_price, 12_345);
2735 assert_eq!(i128::from_le_bytes(klines.klines[0].volume), 1_234_500);
2736 assert_eq!(klines.klines[0].num_trades, 100);
2737 }
2738
2739 fn build_new_order_full_buffer(expiry_reason: ExpiryReason) -> Vec<u8> {
2740 let mut bytes = vec![0u8; 512];
2741 let enc =
2742 NewOrderFullResponseEncoder::default().wrap(WriteBuf::new(&mut bytes), HEADER_LENGTH);
2743 let mut header = enc.header(0);
2744 let mut enc = header.parent().unwrap();
2745 enc.price_exponent(-2);
2746 enc.qty_exponent(-4);
2747 enc.order_id(12345);
2748 enc.order_list_id(99);
2749 enc.transact_time(1_700_000_000_000_000);
2750 enc.price(12_345);
2751 enc.orig_qty(25_000);
2752 enc.executed_qty(10_000);
2753 enc.cummulative_quote_qty(123_450_000);
2754 enc.status(OrderStatus::PartiallyFilled);
2755 enc.time_in_force(TimeInForce::Gtc);
2756 enc.order_type(OrderType::Limit);
2757 enc.side(OrderSide::Buy);
2758 enc.stop_price(12_000);
2759 enc.working_time(1_700_000_000_000_500);
2760 enc.iceberg_qty(5_000);
2761 enc.order_capacity(OrderCapacity::Principal);
2762 enc.working_floor(Floor::Sor);
2763 enc.self_trade_prevention_mode(SelfTradePreventionMode::ExpireMaker);
2764 enc.trade_group_id(1);
2765 enc.used_sor(BoolEnum::True);
2766 enc.expiry_reason(expiry_reason);
2767 let mut fills = enc.fills_encoder(1, FillsEncoder::default());
2768 fills.advance().unwrap();
2769 fills.commission_exponent(-8);
2770 fills.match_type(MatchType::AutoMatch);
2771 fills.price(12_345);
2772 fills.qty(10_000);
2773 fills.commission(10_000);
2774 fills.trade_id(555);
2775 fills.alloc_id(i64::MIN);
2776 fills.commission_asset("USDT");
2777 let enc = fills.parent().unwrap();
2778 let mut prevented = enc.prevented_matches_encoder(0, PreventedMatchesEncoder::default());
2779 let mut enc = prevented.parent().unwrap();
2780 enc.symbol("ETHUSDT");
2781 enc.client_order_id("client-456");
2782 let len = HEADER_LENGTH + enc.encoded_length();
2783 bytes.truncate(len);
2784 bytes
2785 }
2786
2787 #[rstest]
2788 #[case::pre_v4_no_expiry_reason(153, ExpiryReason::NullVal, None)]
2789 #[case::v4_null_sentinel(154, ExpiryReason::NullVal, None)]
2790 #[case::v4_captures_value(154, ExpiryReason::UnfilledIocQuantityExpired, Some(0x05))]
2791 #[case::future_block_length(158, ExpiryReason::UnfilledIocQuantityExpired, Some(0x05))]
2792 fn test_decode_new_order_full_matches_codec(
2793 #[case] block_length: usize,
2794 #[case] expiry_reason: ExpiryReason,
2795 #[case] expected: Option<u8>,
2796 ) {
2797 let mut buf = build_new_order_full_buffer(expiry_reason);
2798 resize_fixed_block(&mut buf, block_length);
2799
2800 let response = decode_new_order_full(&buf).unwrap();
2801
2802 assert_eq!(response.order_id, 12345);
2803 assert_eq!(response.order_list_id, Some(99));
2804 assert_eq!(response.transact_time, 1_700_000_000_000_000);
2805 assert_eq!(response.price_mantissa, 12_345);
2806 assert_eq!(response.orig_qty_mantissa, 25_000);
2807 assert_eq!(response.executed_qty_mantissa, 10_000);
2808 assert_eq!(response.status, OrderStatus::PartiallyFilled);
2809 assert_eq!(response.stop_price_mantissa, Some(12_000));
2810 assert_eq!(response.working_time, Some(1_700_000_000_000_500));
2811 assert_eq!(
2812 response.self_trade_prevention_mode,
2813 SelfTradePreventionMode::ExpireMaker
2814 );
2815 assert_eq!(response.expiry_reason, expected);
2816 assert_eq!(response.symbol, "ETHUSDT");
2817 assert_eq!(response.client_order_id, "client-456");
2818 assert_eq!(response.fills.len(), 1);
2819 assert_eq!(response.fills[0].price_mantissa, 12_345);
2820 assert_eq!(response.fills[0].qty_mantissa, 10_000);
2821 assert_eq!(response.fills[0].trade_id, Some(555));
2822 assert_eq!(response.fills[0].commission_asset, "USDT");
2823 }
2824
2825 #[rstest]
2826 fn test_decode_new_order_full_rejects_short_block() {
2827 let mut buf = build_new_order_full_buffer(ExpiryReason::NullVal);
2828 resize_fixed_block(&mut buf, 116);
2829
2830 let error = decode_new_order_full(&buf).unwrap_err();
2831
2832 assert_eq!(
2833 error,
2834 SbeDecodeError::InvalidBlockLength {
2835 expected: 117,
2836 actual: 116,
2837 }
2838 );
2839 }
2840
2841 #[rstest]
2842 #[case::current(2)]
2843 #[case::extended(5)]
2844 fn test_decode_cancel_replace_nested_order(#[case] block_length: u16) {
2845 let nested = build_new_order_full_buffer(ExpiryReason::NullVal);
2846 let mut buf = create_header(
2847 block_length,
2848 CANCEL_REPLACE_TEMPLATE_ID,
2849 SBE_SCHEMA_ID,
2850 SBE_SCHEMA_VERSION,
2851 )
2852 .to_vec();
2853 buf.resize(HEADER_LENGTH + usize::from(block_length), 0);
2854 buf.extend_from_slice(&3_u16.to_le_bytes());
2855 buf.extend_from_slice(&[11, 22, 33]);
2856 buf.extend_from_slice(&(nested.len() as u32).to_le_bytes());
2857 buf.extend_from_slice(&nested);
2858
2859 let response = decode_cancel_replace(&buf).unwrap();
2860 let expected = decode_new_order_full(&nested).unwrap();
2861 assert_eq!(response, expected);
2862
2863 for end in 0..buf.len() {
2864 assert!(
2865 decode_cancel_replace(&buf[..end]).is_err(),
2866 "Accepted truncated response at {end}"
2867 );
2868 }
2869 buf[HEADER_LENGTH + 1] = CancelReplaceStatus::Failure as u8;
2870 assert!(matches!(
2871 decode_cancel_replace(&buf),
2872 Err(SbeDecodeError::InvalidValue {
2873 field: "cancel-replace result"
2874 })
2875 ));
2876 }
2877
2878 #[rstest]
2879 fn test_decode_cancel_open_orders_valid() {
2880 let response_one = create_cancel_order_response_buffer(111, "ETHUSDT", "orig-1", "new-1");
2881 let response_two = create_cancel_order_response_buffer(222, "BTCUSDT", "orig-2", "new-2");
2882 let buf = create_cancel_open_orders_buffer(&[response_one, response_two]);
2883 let responses = decode_cancel_open_orders(&buf).unwrap();
2884
2885 assert_eq!(responses.len(), 2);
2886 let BinanceCancelOpenOrdersResponse::Order(first) = &responses[0] else {
2887 panic!("Expected ordinary cancel response");
2888 };
2889 assert_eq!(first.order_id, 111);
2890 assert_eq!(first.symbol, "ETHUSDT");
2891 assert_eq!(first.orig_client_order_id, "orig-1");
2892 assert_eq!(first.client_order_id, "new-1");
2893 let BinanceCancelOpenOrdersResponse::Order(second) = &responses[1] else {
2894 panic!("Expected ordinary cancel response");
2895 };
2896 assert_eq!(second.order_id, 222);
2897 assert_eq!(second.symbol, "BTCUSDT");
2898 assert_eq!(second.orig_client_order_id, "orig-2");
2899 assert_eq!(second.client_order_id, "new-2");
2900 }
2901
2902 #[rstest]
2903 fn test_decode_cancel_open_orders_empty() {
2904 let buf = create_cancel_open_orders_buffer(&[]);
2905
2906 let responses = decode_cancel_open_orders(&buf).unwrap();
2907
2908 assert!(responses.is_empty());
2909 }
2910
2911 #[rstest]
2912 fn test_decode_cancel_open_orders_order_list() {
2913 let order_list = create_cancel_order_list_response_buffer();
2914 let buf = create_cancel_open_orders_buffer(&[order_list]);
2915
2916 let responses = decode_cancel_open_orders(&buf).unwrap();
2917
2918 let BinanceCancelOpenOrdersResponse::OrderList(response) = &responses[0] else {
2919 panic!("Expected order-list cancel response");
2920 };
2921 assert_eq!(response.order_list_id, 44);
2922 assert_eq!(response.symbol, "BTCUSDT");
2923 assert_eq!(response.list_client_order_id, "list-44");
2924 assert_eq!(response.orders.len(), 2);
2925 assert_eq!(response.orders[0].order_id, 111);
2926 assert_eq!(response.orders[0].client_order_id, "orig-1");
2927 assert_eq!(response.orders[1].order_id, 222);
2928 assert_eq!(response.orders[1].client_order_id, "orig-2");
2929 assert_eq!(response.order_reports.len(), 2);
2930 assert_eq!(response.order_reports[0].order_id, 111);
2931 assert_eq!(response.order_reports[0].order_list_id, Some(44));
2932 assert_eq!(response.order_reports[0].orig_client_order_id, "orig-1");
2933 assert_eq!(response.order_reports[1].order_id, 222);
2934 assert_eq!(response.order_reports[1].orig_client_order_id, "orig-2");
2935 }
2936
2937 #[rstest]
2938 fn test_decode_cancel_open_orders_mixed() {
2939 let order = create_cancel_order_response_buffer(333, "BTCUSDT", "orig-3", "new-3");
2940 let order_list = create_cancel_order_list_response_buffer();
2941 let buf = create_cancel_open_orders_buffer(&[order, order_list]);
2942
2943 let responses = decode_cancel_open_orders(&buf).unwrap();
2944
2945 assert!(matches!(
2946 responses[0],
2947 BinanceCancelOpenOrdersResponse::Order(_)
2948 ));
2949 assert!(matches!(
2950 responses[1],
2951 BinanceCancelOpenOrdersResponse::OrderList(_)
2952 ));
2953 }
2954
2955 #[rstest]
2956 fn test_decode_cancel_open_orders_unknown_embedded_template() {
2957 let unknown = create_header(0, 999, SBE_SCHEMA_ID, SBE_SCHEMA_VERSION).to_vec();
2958 let buf = create_cancel_open_orders_buffer(&[unknown]);
2959
2960 let error = decode_cancel_open_orders(&buf).unwrap_err();
2961
2962 assert_eq!(error, SbeDecodeError::UnknownTemplateId(999));
2963 }
2964
2965 #[rstest]
2966 fn test_decode_cancel_open_orders_truncated_item() {
2967 let mut order_list = create_cancel_order_list_response_buffer();
2968 order_list.pop();
2969 let buf = create_cancel_open_orders_buffer(&[order_list]);
2970
2971 let error = decode_cancel_open_orders(&buf).unwrap_err();
2972
2973 assert!(matches!(error, SbeDecodeError::BufferTooShort { .. }));
2974 }
2975
2976 #[rstest]
2977 fn test_decode_cancel_open_orders_rejects_group_item_block() {
2978 let mut buf = create_cancel_open_orders_buffer(&[]);
2979 buf[8..10].copy_from_slice(&1u16.to_le_bytes());
2980
2981 let error = decode_cancel_open_orders(&buf).unwrap_err();
2982
2983 assert_eq!(
2984 error,
2985 SbeDecodeError::InvalidBlockLength {
2986 expected: 0,
2987 actual: 1,
2988 }
2989 );
2990 }
2991
2992 #[rstest]
2993 fn test_decode_cancel_open_orders_rejects_short_order_list_block() {
2994 let mut order_list = create_cancel_order_list_response_buffer();
2995 order_list[0..2].copy_from_slice(&20u16.to_le_bytes());
2996 let buf = create_cancel_open_orders_buffer(&[order_list]);
2997
2998 let error = decode_cancel_open_orders(&buf).unwrap_err();
2999
3000 assert_eq!(
3001 error,
3002 SbeDecodeError::InvalidBlockLength {
3003 expected: 21,
3004 actual: 20,
3005 }
3006 );
3007 }
3008
3009 #[rstest]
3010 fn test_decode_cancel_open_orders_rejects_short_order_block() {
3011 let mut order = create_cancel_order_response_buffer(111, "BTCUSDT", "orig-1", "new-1");
3012 order[0..2].copy_from_slice(&108u16.to_le_bytes());
3013 let buf = create_cancel_open_orders_buffer(&[order]);
3014
3015 let error = decode_cancel_open_orders(&buf).unwrap_err();
3016
3017 assert_eq!(
3018 error,
3019 SbeDecodeError::InvalidBlockLength {
3020 expected: 109,
3021 actual: 108,
3022 }
3023 );
3024 }
3025
3026 #[rstest]
3027 fn test_decode_cancel_open_orders_rejects_short_order_report_block() {
3028 let mut order_list = create_cancel_order_list_response_buffer();
3029 let report_header = order_list
3030 .windows(4)
3031 .position(|window| window == [135, 0, 2, 0])
3032 .unwrap();
3033 order_list[report_header..report_header + 2].copy_from_slice(&134u16.to_le_bytes());
3034 let buf = create_cancel_open_orders_buffer(&[order_list]);
3035
3036 let error = decode_cancel_open_orders(&buf).unwrap_err();
3037
3038 assert_eq!(
3039 error,
3040 SbeDecodeError::InvalidBlockLength {
3041 expected: 135,
3042 actual: 134,
3043 }
3044 );
3045 }
3046
3047 #[rstest]
3048 fn test_decode_cancel_open_orders_rejects_invalid_utf8() {
3049 let mut order_list = create_cancel_order_list_response_buffer();
3050 let symbol_start = order_list
3051 .windows(b"BTCUSDT".len())
3052 .position(|window| window == b"BTCUSDT")
3053 .unwrap();
3054 order_list[symbol_start] = 0xff;
3055 let buf = create_cancel_open_orders_buffer(&[order_list]);
3056
3057 let error = decode_cancel_open_orders(&buf).unwrap_err();
3058
3059 assert_eq!(error, SbeDecodeError::InvalidUtf8);
3060 }
3061
3062 fn create_cancel_order_response_buffer(
3063 order_id: i64,
3064 symbol: &str,
3065 orig_client_order_id: &str,
3066 client_order_id: &str,
3067 ) -> Vec<u8> {
3068 let mut bytes = vec![0u8; 256];
3069 let enc =
3070 CancelOrderResponseEncoder::default().wrap(WriteBuf::new(&mut bytes), HEADER_LENGTH);
3071 let mut header = enc.header(0);
3072 let mut enc = header.parent().unwrap();
3073 enc.price_exponent(-8);
3074 enc.qty_exponent(-8);
3075 enc.order_id(order_id);
3076 enc.order_list_id(i64::MIN);
3077 enc.transact_time(1_700_000_000_000_000);
3078 enc.price(100_000_000_000);
3079 enc.orig_qty(10_000_000);
3080 enc.executed_qty(10_000_000);
3081 enc.cummulative_quote_qty(1_000_000_000);
3082 enc.status(OrderStatus::Canceled);
3083 enc.time_in_force(TimeInForce::Gtc);
3084 enc.order_type(OrderType::Limit);
3085 enc.side(OrderSide::Buy);
3086 enc.stop_price(3);
3087 enc.self_trade_prevention_mode(SelfTradePreventionMode::ExpireTaker);
3088 enc.symbol(symbol);
3089 enc.orig_client_order_id(orig_client_order_id);
3090 enc.client_order_id(client_order_id);
3091 let len = HEADER_LENGTH + enc.encoded_length();
3092 bytes.truncate(len);
3093 bytes
3094 }
3095
3096 fn create_cancel_order_list_response_buffer() -> Vec<u8> {
3097 const REPORT_BLOCK_LENGTH: usize = 135;
3098
3099 let mut buf = Vec::new();
3100 buf.extend_from_slice(&create_header(
3101 CANCEL_ORDER_LIST_FIELDS_END as u16,
3102 CANCEL_ORDER_LIST_TEMPLATE_ID,
3103 SBE_SCHEMA_ID,
3104 SBE_SCHEMA_VERSION,
3105 ));
3106 buf.extend_from_slice(&44i64.to_le_bytes());
3107 buf.push(1); buf.push(2); buf.push(2); buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes());
3111 buf.push((-8i8) as u8);
3112 buf.push((-8i8) as u8);
3113
3114 buf.extend_from_slice(&8u16.to_le_bytes());
3115 buf.extend_from_slice(&2u16.to_le_bytes());
3116 for (order_id, client_order_id) in [(111i64, "orig-1"), (222, "orig-2")] {
3117 buf.extend_from_slice(&order_id.to_le_bytes());
3118 write_var_string(&mut buf, "BTCUSDT");
3119 write_var_string(&mut buf, client_order_id);
3120 }
3121
3122 buf.extend_from_slice(&(REPORT_BLOCK_LENGTH as u16).to_le_bytes());
3123 buf.extend_from_slice(&2u16.to_le_bytes());
3124 for (order_id, orig_client_order_id) in [(111i64, "orig-1"), (222, "orig-2")] {
3125 let report_start = buf.len();
3126 buf.extend_from_slice(&order_id.to_le_bytes());
3127 buf.extend_from_slice(&44i64.to_le_bytes());
3128 buf.extend_from_slice(&1_700_000_000_000_000i64.to_le_bytes());
3129 buf.extend_from_slice(&100_000_000_000i64.to_le_bytes());
3130 buf.extend_from_slice(&10_000_000i64.to_le_bytes());
3131 buf.extend_from_slice(&0i64.to_le_bytes());
3132 buf.extend_from_slice(&0i64.to_le_bytes());
3133 buf.push(3); buf.push(0); buf.push(1); buf.push(1); while buf.len() - report_start < REPORT_BLOCK_LENGTH {
3138 buf.push(0);
3139 }
3140 write_var_string(&mut buf, "BTCUSDT");
3141 write_var_string(&mut buf, orig_client_order_id);
3142 write_var_string(&mut buf, "cancel-44");
3143 }
3144
3145 write_var_string(&mut buf, "list-44");
3146 write_var_string(&mut buf, "BTCUSDT");
3147 buf
3148 }
3149
3150 fn create_cancel_open_orders_buffer(responses: &[Vec<u8>]) -> Vec<u8> {
3151 let mut buf = Vec::new();
3152 buf.extend_from_slice(&create_header(
3153 0,
3154 CANCEL_OPEN_ORDERS_TEMPLATE_ID,
3155 SBE_SCHEMA_ID,
3156 SBE_SCHEMA_VERSION,
3157 ));
3158 buf.extend_from_slice(&create_group_header(0, responses.len() as u32));
3159 for response in responses {
3160 buf.extend_from_slice(&(response.len() as u16).to_le_bytes());
3161 buf.extend_from_slice(response);
3162 }
3163 buf
3164 }
3165}