use std::io;
use bytes::{Bytes, BytesMut};
use memchr::memmem;
use smol_str::SmolStr;
use tokio_util::codec::{Decoder, Encoder};
const JPEG_START_MARKER: [u8; 4] = [0xff, 0xd8, 0xff, 0xe0];
const JPEG_END_MARKER: [u8; 2] = [0xff, 0xd9];
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CameraPacket {
Auth {
username: SmolStr,
access_code: SmolStr,
},
Jpeg(Bytes),
}
#[derive(Default)]
pub struct JpegCodec(());
fn decode_jpeg_packet(src: &mut BytesMut) -> io::Result<Option<Bytes>> {
let start_idx = match find_subsequence(src, &JPEG_START_MARKER) {
Some(idx) => idx,
None => return Ok(None), };
let search_start = start_idx + JPEG_START_MARKER.len();
let end_rel_idx = match find_subsequence(&src[search_start..], &JPEG_END_MARKER) {
Some(idx) => idx,
None => return Ok(None), };
let end_idx = search_start + end_rel_idx + JPEG_END_MARKER.len();
let mut head = src.split_to(end_idx);
let frame = head.split_off(start_idx);
Ok(Some(frame.freeze()))
}
fn decode_auth_packet(src: &mut BytesMut) -> io::Result<Option<(SmolStr, SmolStr)>> {
if src.len() >= 80 {
let magic_1 = u32::from_le_bytes(src[..4].try_into().unwrap());
let magic_2 = u32::from_le_bytes(src[4..8].try_into().unwrap());
let zero_1 = u32::from_le_bytes(src[8..12].try_into().unwrap());
let zero_2 = u32::from_le_bytes(src[12..16].try_into().unwrap());
if magic_1 == 64 && magic_2 == 12288 && zero_1 == 0 && zero_2 == 0 {
let username_part = &src[16..48];
let access_part = &src[48..80];
let username =
SmolStr::new(String::from_utf8_lossy(username_part).trim_end_matches('\0'));
let access_code =
SmolStr::new(String::from_utf8_lossy(access_part).trim_end_matches('\0'));
let _raw = src.split_to(80);
return Ok(Some((username, access_code)));
}
}
Ok(None)
}
#[inline]
fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
memmem::find(haystack, needle)
}
impl Decoder for JpegCodec {
type Item = CameraPacket;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if let Some((username, access_code)) = decode_auth_packet(src)? {
Ok(Some(CameraPacket::Auth {
username,
access_code,
}))
} else if let Some(jpeg) = decode_jpeg_packet(src)? {
Ok(Some(CameraPacket::Jpeg(jpeg)))
} else {
Ok(None)
}
}
}
impl Encoder<CameraPacket> for JpegCodec {
type Error = io::Error;
fn encode(&mut self, item: CameraPacket, dst: &mut BytesMut) -> Result<(), Self::Error> {
match item {
CameraPacket::Auth {
username,
access_code,
} => {
dst.extend_from_slice(&0x40u32.to_le_bytes()); dst.extend_from_slice(&0x3000u32.to_le_bytes()); dst.extend_from_slice(&0u32.to_le_bytes());
dst.extend_from_slice(&0u32.to_le_bytes());
let mut username_bytes = [0u8; 32];
let username_utf8 = username.as_bytes();
let len = username_utf8.len().min(32);
username_bytes[..len].copy_from_slice(&username_utf8[..len]);
dst.extend_from_slice(&username_bytes);
let mut access_bytes = [0u8; 32];
let code_utf8 = access_code.as_bytes();
let len = code_utf8.len().min(32);
access_bytes[..len].copy_from_slice(&code_utf8[..len]);
dst.extend_from_slice(&access_bytes);
}
CameraPacket::Jpeg(bytes) => dst.extend_from_slice(&bytes),
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_subsequence() {
let haystack = b"hello world";
let needle = b"world";
assert_eq!(find_subsequence(haystack, needle), Some(6));
let needle = b"foo";
assert_eq!(find_subsequence(haystack, needle), None);
}
#[test]
fn test_decode_complete_frame() {
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(&b"\xff\xd8\xff\xe0hello world\xff\xd9"[..]);
let expected = src.clone().freeze();
let frame = codec.decode(&mut src).unwrap().unwrap();
assert_eq!(frame, CameraPacket::Jpeg(expected));
assert!(src.is_empty());
}
#[test]
fn test_decode_partial_frame() {
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(&b"\xff\xd8\xff\xe0hello"[..]);
let frame = codec.decode(&mut src).unwrap();
assert!(frame.is_none());
assert_eq!(src, &b"\xff\xd8\xff\xe0hello"[..]);
}
#[test]
fn test_decode_multiple_frames() {
let mut codec = JpegCodec::default();
let mut src =
BytesMut::from(&b"\xff\xd8\xff\xe0frame1\xff\xd9\xff\xd8\xff\xe0frame2\xff\xd9"[..]);
let frame1 = codec.decode(&mut src).unwrap().unwrap();
assert_eq!(
frame1,
CameraPacket::Jpeg(Bytes::from_static(b"\xff\xd8\xff\xe0frame1\xff\xd9"))
);
let frame2 = codec.decode(&mut src).unwrap().unwrap();
assert_eq!(
frame2,
CameraPacket::Jpeg(Bytes::from_static(b"\xff\xd8\xff\xe0frame2\xff\xd9"))
);
assert!(src.is_empty());
}
#[test]
fn test_decode_no_start_marker() {
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(&b"hello world\xff\xd9"[..]);
let frame = codec.decode(&mut src).unwrap();
assert!(frame.is_none());
assert_eq!(src, &b"hello world\xff\xd9"[..]);
}
#[test]
fn test_decode_no_end_marker() {
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(&b"\xff\xd8\xff\xe0hello world"[..]);
let frame = codec.decode(&mut src).unwrap();
assert!(frame.is_none());
assert_eq!(src, &b"\xff\xd8\xff\xe0hello world"[..]);
}
#[test]
fn test_decode_invalid_marker_regression() {
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(&b"\xff\xd8\xff\xe0\0!AVI1\0\x01\x01\x01\0x\0x\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xdb"[..]);
let frame = codec.decode(&mut src).unwrap();
assert!(frame.is_none());
assert_eq!(src, &b"\xff\xd8\xff\xe0\0!AVI1\0\x01\x01\x01\0x\0x\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\xff\xdb"[..]);
}
fn create_auth_packet(username: &str, access_code: &str) -> Vec<u8> {
let mut auth_data = Vec::new();
auth_data.extend_from_slice(&0x40u32.to_le_bytes()); auth_data.extend_from_slice(&0x3000u32.to_le_bytes()); auth_data.extend_from_slice(&0u32.to_le_bytes());
auth_data.extend_from_slice(&0u32.to_le_bytes());
let mut username_bytes = [0u8; 32];
let username_utf8 = username.as_bytes();
let len = username_utf8.len().min(32);
username_bytes[..len].copy_from_slice(&username_utf8[..len]);
auth_data.extend_from_slice(&username_bytes);
let mut access_bytes = [0u8; 32];
let code_utf8 = access_code.as_bytes();
let len = code_utf8.len().min(32);
access_bytes[..len].copy_from_slice(&code_utf8[..len]);
auth_data.extend_from_slice(&access_bytes);
auth_data
}
#[test]
fn foo() {
let mut stream = create_auth_packet("bblp", "1234");
let image1 = {
let mut image = Vec::new();
image.extend_from_slice(&JPEG_START_MARKER);
image.extend_from_slice(b"foobar");
image.extend_from_slice(&JPEG_END_MARKER);
image
};
stream.extend_from_slice(&image1);
let mut codec = JpegCodec::default();
let mut src = BytesMut::from(dbg!(Bytes::from(stream)));
assert_eq!(
codec.decode(&mut src).unwrap(),
Some(CameraPacket::Auth {
username: "bblp".into(),
access_code: "1234".into(),
})
);
assert_eq!(
codec.decode(&mut src).unwrap(),
Some(CameraPacket::Jpeg(Bytes::from(image1)))
);
}
}