marketdata-shootout/src/parsers.rs

114 lines
3.2 KiB
Rust

use nom::{
branch::alt, bytes::complete::tag, bytes::complete::take, number::complete::*, sequence::tuple,
IResult,
};
pub enum Block<'a> {
SectionHeader(SectionHeaderBlock),
InterfaceDescription(InterfaceDescriptionBlock),
EnhancedPacket(EnhancedPacketBlock<'a>),
}
pub fn read_block(input: &[u8]) -> IResult<&[u8], Block> {
// TODO: Curious if this is faster than `match`
// Theoretically it should be because we're almost always using
// enhanced packet blocks, but don't know if the branch predictor
// would catch on as well.
alt((
enhanced_packet_block,
section_header_block,
interface_description_block,
))(input)
}
#[derive(Debug)]
pub struct SectionHeaderBlock {
block_len: u32,
}
const SECTION_HEADER: [u8; 4] = [0x0a, 0x0d, 0x0d, 0x0a];
pub fn section_header_block(input: &[u8]) -> IResult<&[u8], Block> {
let header_len = 12;
let (rem, (_, block_len, _)) =
tuple((tag(SECTION_HEADER), le_u32, tag([0x4d, 0x3c, 0x2b, 0x1a])))(input)?;
take(block_len - header_len)(rem)
.map(|i| (i.0, Block::SectionHeader(SectionHeaderBlock { block_len })))
}
#[derive(Debug)]
pub struct InterfaceDescriptionBlock {
block_len: u32,
}
const INTERFACE_DESCRIPTION: [u8; 4] = [0x01, 0x00, 0x00, 0x00];
pub fn interface_description_block(input: &[u8]) -> IResult<&[u8], Block> {
let header_len = 8;
let (rem, (_, block_len)) = tuple((tag(INTERFACE_DESCRIPTION), le_u32))(input)?;
take(block_len - header_len)(rem).map(|i| {
(
i.0,
Block::InterfaceDescription(InterfaceDescriptionBlock { block_len }),
)
})
}
pub struct EnhancedPacketBlock<'a> {
pub block_len: u32,
pub packet_data: &'a [u8],
}
const ENHANCED_PACKET: [u8; 4] = [0x06, 0x00, 0x00, 0x00];
pub fn enhanced_packet_block(input: &[u8]) -> IResult<&[u8], Block> {
let header_len = 28;
let (rem, (_, block_len, _, _, _, captured_len, _)) = tuple((
tag(ENHANCED_PACKET),
le_u32,
le_u32,
le_u32,
le_u32,
le_u32,
le_u32,
))(input)?;
let (rem, packet_data) = take(captured_len)(rem)?;
// Packets are supposed to be padded to 32 bits, but IEX DEEP doesn't
// seem to respect this
//let packet_total_len = (captured_len + 3) / 4 * 4;
take(block_len - header_len - captured_len)(rem).map(|i| {
(
i.0,
Block::EnhancedPacket(EnhancedPacketBlock {
block_len,
packet_data,
}),
)
})
}
pub fn extract_iex_data(input: &[u8]) -> IResult<&[u8], &[u8]> {
// Get the IEX payload out of each packet
// First step is the ethernet frame
let (rem, (_, _, _)) = tuple((take(6usize), take(6usize), tag([0x08, 0x00])))(input)?;
// Then the IP header
let header_words = rem[0] & 0x0F;
let header_len = header_words as u32 * 4;
let (udp_data, _header_data) = take(header_len)(rem)?;
// And finally, extract out of UDP
let (rem, (_, _, udp_len, _)) = tuple((be_u16, be_u16, be_u16, be_u16))(udp_data)?;
let udp_header_len = 8;
let (rem, iex_data) = take(udp_len - udp_header_len)(rem)?;
debug_assert!(rem.len() == 0);
Ok((rem, iex_data))
}