2019-08-24 22:49:07 -04:00
|
|
|
use std::convert::TryInto;
|
|
|
|
|
|
|
|
use nom::{bytes::complete::take, IResult, number::complete::*, sequence::tuple};
|
|
|
|
|
2019-08-24 23:58:24 -04:00
|
|
|
use crate::parsers::{Block, extract_iex_data, read_block};
|
|
|
|
|
|
|
|
pub struct IexParser<'a> {
|
2019-08-25 21:21:10 -04:00
|
|
|
pcap_buffer: &'a [u8],
|
2019-08-24 23:58:24 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> IexParser<'a> {
|
|
|
|
pub fn new(pcap_buffer: &[u8]) -> IexParser {
|
|
|
|
IexParser { pcap_buffer }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Iterator for IexParser<'a> {
|
|
|
|
type Item = IexPayload;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
let mut buffer = self.pcap_buffer;
|
|
|
|
while let Ok((rem, block)) = read_block(buffer) {
|
|
|
|
self.pcap_buffer = rem;
|
|
|
|
buffer = rem;
|
|
|
|
match block {
|
|
|
|
Block::EnhancedPacket(e) => {
|
|
|
|
let (_, iex_data) = extract_iex_data(e.packet_data).unwrap();
|
|
|
|
let (_, payload) = IexPayload::parse(iex_data).unwrap();
|
|
|
|
return Some(payload);
|
|
|
|
}
|
2019-08-25 21:21:10 -04:00
|
|
|
_ => (),
|
2019-08-24 23:58:24 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 22:49:07 -04:00
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct IexPayload {
|
2019-08-24 22:49:07 -04:00
|
|
|
version: u8,
|
|
|
|
_reserved: u8,
|
|
|
|
proto_id: u16,
|
|
|
|
channel_id: u32,
|
|
|
|
session_id: u32,
|
|
|
|
payload_len: u16,
|
2019-08-25 21:21:10 -04:00
|
|
|
pub msg_count: u16,
|
2019-08-24 22:49:07 -04:00
|
|
|
stream_offset: u64,
|
2019-08-25 21:21:10 -04:00
|
|
|
pub first_seq_no: u64,
|
2019-08-24 22:49:07 -04:00
|
|
|
send_time: i64,
|
2019-08-25 21:21:10 -04:00
|
|
|
pub messages: smallvec::SmallVec<[IexMessage; 256]>,
|
2019-08-24 22:49:07 -04:00
|
|
|
}
|
|
|
|
|
2019-08-24 23:58:24 -04:00
|
|
|
impl IexPayload {
|
|
|
|
pub fn parse(payload: &[u8]) -> IResult<&[u8], IexPayload> {
|
2019-08-25 21:21:10 -04:00
|
|
|
let (
|
|
|
|
mut rem,
|
|
|
|
(
|
|
|
|
version,
|
|
|
|
_reserved,
|
|
|
|
proto_id,
|
|
|
|
channel_id,
|
|
|
|
session_id,
|
|
|
|
payload_len,
|
|
|
|
msg_count,
|
|
|
|
stream_offset,
|
|
|
|
first_seq_no,
|
|
|
|
send_time,
|
|
|
|
),
|
|
|
|
) = tuple((
|
|
|
|
le_u8, le_u8, le_u16, le_u32, le_u32, le_u16, le_u16, le_u64, le_u64, le_i64,
|
|
|
|
))(payload)?;
|
2019-08-24 23:58:24 -04:00
|
|
|
|
|
|
|
let mut messages = smallvec::SmallVec::new();
|
|
|
|
for _i in 0..msg_count {
|
|
|
|
let (_rem, msg) = IexMessage::parse(rem)?;
|
|
|
|
rem = _rem;
|
|
|
|
messages.push(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
debug_assert!(rem.len() == 0);
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
IexPayload {
|
|
|
|
version,
|
|
|
|
_reserved,
|
|
|
|
proto_id,
|
|
|
|
channel_id,
|
|
|
|
session_id,
|
|
|
|
payload_len,
|
|
|
|
msg_count,
|
|
|
|
stream_offset,
|
|
|
|
first_seq_no,
|
|
|
|
send_time,
|
|
|
|
messages,
|
2019-08-25 21:21:10 -04:00
|
|
|
},
|
2019-08-24 23:58:24 -04:00
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-24 22:49:07 -04:00
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub enum IexMessage {
|
2019-08-24 22:49:07 -04:00
|
|
|
SystemEvent(SystemEvent),
|
|
|
|
SecurityDirectory(SecurityDirectory),
|
|
|
|
TradingStatus(TradingStatus),
|
|
|
|
OperationalHaltStatus(OperationalHaltStatus),
|
|
|
|
ShortSalePriceTest(ShortSalePriceTest),
|
|
|
|
SecurityEvent(SecurityEvent),
|
|
|
|
PriceLevelUpdate(PriceLevelUpdate),
|
|
|
|
TradeReport(TradeReport),
|
|
|
|
OfficialPrice(OfficialPrice),
|
|
|
|
TradeBreak(TradeBreak),
|
|
|
|
AuctionInformation(AuctionInformation),
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! parse_msg {
|
2019-08-24 23:58:24 -04:00
|
|
|
($input:ident, $len:ident, $msg_type:ident) => {{
|
|
|
|
let (_, msg) = $msg_type::parse($input)?;
|
|
|
|
IexMessage::$msg_type(msg)
|
2019-08-24 22:49:07 -04:00
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IexMessage {
|
|
|
|
// TODO: Benchmark a version where we cast a packed struct instead of parsing
|
2019-08-24 23:58:24 -04:00
|
|
|
pub fn parse(input: &[u8]) -> IResult<&[u8], IexMessage> {
|
|
|
|
let (payload, msg_len) = le_u16(input)?;
|
|
|
|
|
|
|
|
// Minor technical note: IEX's docs state that the message format
|
|
|
|
// can grow at any time, and we should always trust the msg_len field,
|
|
|
|
// so we first slice off the total msg_len for future compatibility
|
|
|
|
let (rem, payload) = take(msg_len)(payload)?;
|
|
|
|
let msg = match payload[0] {
|
|
|
|
0x53 => parse_msg!(payload, msg_len, SystemEvent),
|
|
|
|
0x44 => parse_msg!(payload, msg_len, SecurityDirectory),
|
|
|
|
0x48 => parse_msg!(payload, msg_len, TradingStatus),
|
|
|
|
0x4f => parse_msg!(payload, msg_len, OperationalHaltStatus),
|
|
|
|
0x50 => parse_msg!(payload, msg_len, ShortSalePriceTest),
|
|
|
|
0x45 => parse_msg!(payload, msg_len, SecurityEvent),
|
2019-08-24 22:49:07 -04:00
|
|
|
// Why the "match multiple" looks like bitwise-OR is beyond me.
|
2019-08-24 23:58:24 -04:00
|
|
|
0x38 | 0x35 => parse_msg!(payload, msg_len, PriceLevelUpdate),
|
|
|
|
0x54 => parse_msg!(payload, msg_len, TradeReport),
|
|
|
|
0x58 => parse_msg!(payload, msg_len, OfficialPrice),
|
|
|
|
0x42 => parse_msg!(payload, msg_len, TradeBreak),
|
|
|
|
0x41 => parse_msg!(payload, msg_len, AuctionInformation),
|
|
|
|
t => panic!("Unrecognized message type={}, payload={:?}", t, payload),
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok((rem, msg))
|
2019-08-24 22:49:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct SystemEvent {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
system_event: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SystemEvent {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], SystemEvent> {
|
|
|
|
let (rem, (msg_type, system_event, timestamp)) = tuple((le_u8, le_u8, le_i64))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
SystemEvent {
|
|
|
|
msg_type,
|
|
|
|
system_event,
|
|
|
|
timestamp,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct SecurityDirectory {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
flags: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
lot_size: u32,
|
|
|
|
previous_closing: u64,
|
|
|
|
luld_tier: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SecurityDirectory {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], SecurityDirectory> {
|
|
|
|
let (rem, (msg_type, flags, timestamp, symbol, lot_size, previous_closing, luld_tier)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u32, le_u64, le_u8))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
SecurityDirectory {
|
|
|
|
msg_type,
|
|
|
|
flags,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
lot_size,
|
|
|
|
previous_closing,
|
|
|
|
luld_tier,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct TradingStatus {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
trading_status: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
reason: [u8; 4],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TradingStatus {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], TradingStatus> {
|
|
|
|
let (rem, (msg_type, trading_status, timestamp, symbol, reason)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), take(4usize)))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
TradingStatus {
|
|
|
|
msg_type,
|
|
|
|
trading_status,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
reason: reason.try_into().unwrap(),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct OperationalHaltStatus {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
halt_status: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OperationalHaltStatus {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], OperationalHaltStatus> {
|
|
|
|
let (rem, (msg_type, halt_status, timestamp, symbol)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize)))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
OperationalHaltStatus {
|
|
|
|
msg_type,
|
|
|
|
halt_status,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct ShortSalePriceTest {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
sspt_status: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
detail: u8,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ShortSalePriceTest {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], ShortSalePriceTest> {
|
|
|
|
let (rem, (msg_type, sspt_status, timestamp, symbol, detail)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u8))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
ShortSalePriceTest {
|
|
|
|
msg_type,
|
|
|
|
sspt_status,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
detail,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct SecurityEvent {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
security_event: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SecurityEvent {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], SecurityEvent> {
|
|
|
|
let (rem, (msg_type, security_event, timestamp, symbol)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize)))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
SecurityEvent {
|
|
|
|
msg_type,
|
|
|
|
security_event,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct PriceLevelUpdate {
|
2019-08-25 21:21:10 -04:00
|
|
|
pub msg_type: u8,
|
|
|
|
pub event_flags: u8,
|
|
|
|
pub timestamp: i64,
|
|
|
|
pub symbol: [u8; 8],
|
|
|
|
pub size: u32,
|
|
|
|
pub price: u64,
|
2019-08-24 22:49:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PriceLevelUpdate {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], PriceLevelUpdate> {
|
|
|
|
let (rem, (msg_type, event_flags, timestamp, symbol, size, price)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u32, le_u64))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
PriceLevelUpdate {
|
|
|
|
msg_type,
|
|
|
|
event_flags,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
size,
|
|
|
|
price,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct TradeReport {
|
2019-08-25 21:21:10 -04:00
|
|
|
pub msg_type: u8,
|
|
|
|
pub sale_condition: u8,
|
|
|
|
pub timestamp: i64,
|
|
|
|
pub symbol: [u8; 8],
|
|
|
|
pub size: u32,
|
|
|
|
pub price: u64,
|
|
|
|
pub trade_id: u64,
|
2019-08-24 22:49:07 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TradeReport {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], TradeReport> {
|
|
|
|
let (rem, (msg_type, sale_condition, timestamp, symbol, size, price, trade_id)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u32, le_u64, le_u64))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
TradeReport {
|
|
|
|
msg_type,
|
|
|
|
sale_condition,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
size,
|
|
|
|
price,
|
|
|
|
trade_id,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct OfficialPrice {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
price_type: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
official_price: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl OfficialPrice {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], OfficialPrice> {
|
|
|
|
let (rem, (msg_type, price_type, timestamp, symbol, official_price)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u64))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
OfficialPrice {
|
|
|
|
msg_type,
|
|
|
|
price_type,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
official_price,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct TradeBreak {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
sale_condition: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
size: u32,
|
|
|
|
price: u64,
|
|
|
|
trade_id: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TradeBreak {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], TradeBreak> {
|
|
|
|
let (rem, (msg_type, sale_condition, timestamp, symbol, size, price, trade_id)) =
|
|
|
|
tuple((le_u8, le_u8, le_i64, take(8usize), le_u32, le_u64, le_u64))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
TradeBreak {
|
|
|
|
msg_type,
|
|
|
|
sale_condition,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
size,
|
|
|
|
price,
|
|
|
|
trade_id,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2019-08-24 23:58:24 -04:00
|
|
|
pub struct AuctionInformation {
|
2019-08-24 22:49:07 -04:00
|
|
|
msg_type: u8,
|
|
|
|
auction_type: u8,
|
|
|
|
timestamp: i64,
|
|
|
|
symbol: [u8; 8],
|
|
|
|
paired_shares: u32,
|
|
|
|
reference_price: u64,
|
|
|
|
indicative_clearing_price: u64,
|
|
|
|
imbalance_shares: u32,
|
|
|
|
imbalance_side: u8,
|
|
|
|
extension_number: u8,
|
|
|
|
scheduled_auction: u32,
|
|
|
|
auction_book_clearing_price: u64,
|
|
|
|
collar_reference_price: u64,
|
|
|
|
lower_auction_collar: u64,
|
|
|
|
upper_auction_collar: u64,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AuctionInformation {
|
|
|
|
fn parse(input: &[u8]) -> IResult<&[u8], AuctionInformation> {
|
|
|
|
// Dear Lord, why?
|
|
|
|
let (
|
|
|
|
rem,
|
|
|
|
(
|
|
|
|
msg_type,
|
|
|
|
auction_type,
|
|
|
|
timestamp,
|
|
|
|
symbol,
|
|
|
|
paired_shares,
|
|
|
|
reference_price,
|
|
|
|
indicative_clearing_price,
|
|
|
|
imbalance_shares,
|
|
|
|
imbalance_side,
|
|
|
|
extension_number,
|
|
|
|
scheduled_auction,
|
|
|
|
auction_book_clearing_price,
|
|
|
|
collar_reference_price,
|
|
|
|
lower_auction_collar,
|
|
|
|
upper_auction_collar,
|
|
|
|
),
|
|
|
|
) = tuple((
|
|
|
|
le_u8,
|
|
|
|
le_u8,
|
|
|
|
le_i64,
|
|
|
|
take(8usize),
|
|
|
|
le_u32,
|
|
|
|
le_u64,
|
|
|
|
le_u64,
|
|
|
|
le_u32,
|
|
|
|
le_u8,
|
|
|
|
le_u8,
|
|
|
|
le_u32,
|
|
|
|
le_u64,
|
|
|
|
le_u64,
|
|
|
|
le_u64,
|
|
|
|
le_u64,
|
|
|
|
))(input)?;
|
|
|
|
|
|
|
|
Ok((
|
|
|
|
rem,
|
|
|
|
AuctionInformation {
|
|
|
|
msg_type,
|
|
|
|
auction_type,
|
|
|
|
timestamp,
|
|
|
|
symbol: symbol.try_into().unwrap(),
|
|
|
|
paired_shares,
|
|
|
|
reference_price,
|
|
|
|
indicative_clearing_price,
|
|
|
|
imbalance_shares,
|
|
|
|
imbalance_side,
|
|
|
|
extension_number,
|
|
|
|
scheduled_auction,
|
|
|
|
auction_book_clearing_price,
|
|
|
|
collar_reference_price,
|
|
|
|
lower_auction_collar,
|
|
|
|
upper_auction_collar,
|
|
|
|
},
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|