use bytes::BytesMut;
use super::parser::parse;
use super::types::DnpMessage;
use crate::Timestamp;
use crate::session::SessionParser;
pub const PARSER_KIND: &str = "dnp3";
pub const DNP3_PORT: u16 = 20000;
const MAX_FRAME: usize = 292; const MAX_BUFFER: usize = 64 * 1024;
#[derive(Debug, Clone, Default)]
pub struct DnpParser {
init: BytesMut,
resp: BytesMut,
}
impl DnpParser {
pub fn new() -> Self {
Self::default()
}
fn drain(buf: &mut BytesMut, out: &mut Vec<DnpMessage>) {
loop {
let Some(magic_at) = find_magic(buf) else {
if buf.len() > MAX_BUFFER {
buf.clear();
}
return;
};
if magic_at > 0 {
bytes::Buf::advance(buf, magic_at);
}
if buf.len() < 3 {
return;
}
let frame_len = total_frame_len(buf[2]);
if frame_len > MAX_FRAME {
bytes::Buf::advance(buf, 2);
continue;
}
if buf.len() < frame_len {
return;
}
let consumed = match parse(&buf[..frame_len]) {
Ok(msg) => {
out.push(msg);
frame_len
}
Err(_) => 2, };
bytes::Buf::advance(buf, consumed);
}
}
}
fn find_magic(buf: &BytesMut) -> Option<usize> {
buf.windows(2).position(|w| w == [0x05, 0x64])
}
fn total_frame_len(length_byte: u8) -> usize {
let length = length_byte as usize;
if length < 5 {
return 0;
}
let user_data = length - 5;
let mut total = 10;
let full_blocks = user_data / 16;
let remainder = user_data % 16;
total += full_blocks * 18;
if remainder > 0 {
total += remainder + 2;
}
total
}
impl SessionParser for DnpParser {
type Message = DnpMessage;
fn parser_kind(&self) -> crate::ParserKind {
crate::ParserKind::Dnp3
}
fn feed_initiator(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
if self.init.len() + bytes.len() > MAX_BUFFER {
self.init.clear();
}
self.init.extend_from_slice(bytes);
Self::drain(&mut self.init, out);
}
fn feed_responder(&mut self, bytes: &[u8], _ts: Timestamp, out: &mut Vec<Self::Message>) {
if self.resp.len() + bytes.len() > MAX_BUFFER {
self.resp.clear();
}
self.resp.extend_from_slice(bytes);
Self::drain(&mut self.resp, out);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::session::DatagramParser;
fn build_reset_link_frame(dst: u16, src: u16) -> Vec<u8> {
let mut out = vec![0x05, 0x64, 0x05, 0xC0];
out.extend_from_slice(&dst.to_le_bytes());
out.extend_from_slice(&src.to_le_bytes());
out.extend_from_slice(&[0xAA, 0xBB]);
out
}
fn ts() -> Timestamp {
Timestamp::default()
}
#[test]
fn parser_kind_and_port_constants() {
assert_eq!(DnpParser::new().parser_kind().as_str(), "dnp3");
assert_eq!(DNP3_PORT, 20000);
fn assert_session<T: SessionParser>(_: &T) {}
let p = DnpParser::new();
assert_session(&p);
let _: fn(&dyn DatagramParser<Message = ()>) = |_| {};
}
#[test]
fn drains_one_frame() {
let buf = build_reset_link_frame(1, 2);
let mut p = DnpParser::new();
let mut out = Vec::new();
p.feed_initiator(&buf, ts(), &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].dst_addr, 1);
assert_eq!(out[0].src_addr, 2);
}
#[test]
fn drains_back_to_back_frames() {
let mut buf = Vec::new();
for i in 1..=4u16 {
buf.extend_from_slice(&build_reset_link_frame(i, i + 10));
}
let mut p = DnpParser::new();
let mut out = Vec::new();
p.feed_initiator(&buf, ts(), &mut out);
assert_eq!(out.len(), 4);
}
#[test]
fn split_feed_reassembles() {
let buf = build_reset_link_frame(1, 2);
let mut p = DnpParser::new();
let mut out = Vec::new();
p.feed_initiator(&buf[..5], ts(), &mut out);
assert!(out.is_empty());
p.feed_initiator(&buf[5..], ts(), &mut out);
assert_eq!(out.len(), 1);
}
#[test]
fn skips_garbage_then_finds_real_frame() {
let mut buf = vec![0xDE, 0xAD, 0xBE, 0xEF];
buf.extend_from_slice(&build_reset_link_frame(7, 9));
let mut p = DnpParser::new();
let mut out = Vec::new();
p.feed_initiator(&buf, ts(), &mut out);
assert_eq!(out.len(), 1);
assert_eq!(out[0].dst_addr, 7);
}
}