use crate::error::DecodeError;
use crate::tags::SOH;
pub const MAX_BODY_LEN: usize = 16 * 1024 * 1024;
pub fn frame_length(buf: &[u8]) -> Result<Option<usize>, DecodeError> {
let Some(soh1) = find(buf, SOH) else {
return Ok(None);
};
if buf.get(0..2) != Some(b"8=") {
return Err(DecodeError::MissingBeginString);
}
let begin_string_value = buf.get(2..soh1).unwrap_or(&[]);
if !looks_like_fix_begin_string(begin_string_value) {
return Err(DecodeError::MissingBeginString);
}
let after8 = buf.get(soh1 + 1..).unwrap_or(&[]);
let Some(soh2_rel) = find(after8, SOH) else {
return Ok(None);
};
let bl_field = after8.get(..soh2_rel).unwrap_or(&[]);
if bl_field.get(0..2) != Some(b"9=") {
return Err(DecodeError::InvalidBodyLength);
}
let bl_val = bl_field.get(2..).unwrap_or(&[]);
let body_len: usize = core::str::from_utf8(bl_val)
.ok()
.and_then(|s| s.parse().ok())
.ok_or(DecodeError::InvalidBodyLength)?;
if body_len == 0 {
return Err(DecodeError::ZeroBodyLength);
}
if body_len > MAX_BODY_LEN {
return Err(DecodeError::BodyLengthTooLarge {
declared: body_len,
max: MAX_BODY_LEN,
});
}
let overflow = || DecodeError::BodyLengthTooLarge {
declared: body_len,
max: MAX_BODY_LEN,
};
let body_start = soh1
.checked_add(1)
.and_then(|v| v.checked_add(soh2_rel))
.and_then(|v| v.checked_add(1))
.ok_or_else(overflow)?;
let total = body_start
.checked_add(body_len)
.and_then(|v| v.checked_add(7))
.ok_or_else(overflow)?;
if buf.len() >= total {
if buf.get(total - 7..total - 4) != Some(b"10=") {
return Err(DecodeError::InvalidBodyLength);
}
if buf.get(total - 1) != Some(&SOH) {
return Err(DecodeError::InvalidBodyLength);
}
Ok(Some(total))
} else {
Ok(None)
}
}
fn find(haystack: &[u8], needle: u8) -> Option<usize> {
memchr::memchr(needle, haystack)
}
fn looks_like_fix_begin_string(value: &[u8]) -> bool {
let Ok(s) = core::str::from_utf8(value) else {
return false;
};
let Some(rest) = s.strip_prefix("FIXT.").or_else(|| s.strip_prefix("FIX.")) else {
return false;
};
let bytes = rest.as_bytes();
matches!(bytes.get(0..3), Some([d1, b'.', d2]) if d1.is_ascii_digit() && d2.is_ascii_digit())
}