commit 737f43de067fc446e723f9b1f4e42b9f74db19b3 Author: Florian Stecker Date: Sun May 11 23:54:41 2025 -0400 initial version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e200bd3 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "unitree" +version = "0.1.0" +edition = "2024" + +[dependencies] +chrono = "0.4.40" +num_enum = "0.7.3" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f1051d6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,181 @@ +use std::fs::File; +use std::error::Error; +use std::io::{Read, Write, ErrorKind}; +use num_enum::TryFromPrimitive; +use std::fmt::Display; +use chrono::DateTime; + +#[derive(TryFromPrimitive,Debug)] +#[repr(u16)] +enum CommandType { + Unspec = 0, + Subvol = 1, + Snapshot = 2, + MkFile = 3, + MkDir = 4, + MkNod = 5, + MkFifo = 6, + MkSock = 7, + Symlink = 8, + Rename = 9, + Link = 10, + Unlink = 11, + RmDir = 12, + SetXAttr = 13, + RemoveXAttr = 14, + Write = 15, + Clone = 16, + Truncate = 17, + Chmod = 18, + Chown = 19, + UTimes = 20, + End = 21, + UpdateExtent = 22, + FAllocate = 23, + FileAttr = 24, + EncodedWrite = 25, +} + +#[derive(TryFromPrimitive,Debug,Clone,Copy)] +#[repr(u16)] +enum TLVType { + Unspec = 0, + UUID = 1, + CTransID = 2, + Ino = 3, + Size = 4, + Mode = 5, + UID = 6, + GID = 7, + RDev = 8, + CTime = 9, + MTime = 10, + ATime = 11, + OTime = 12, + XAttrName = 13, + XAttrData = 14, + Path = 15, + PathTo = 16, + PathLink = 17, + FileOffset = 18, + Data = 19, + CloneUUID = 20, + CloneCTransID = 21, + ClonePath = 22, + CloneOffset = 23, + CloneLen = 24, + FAllocateMode = 25, + FileAttr = 26, + UnencodedFileLen = 27, + UnencodedLen = 28, + UnencodedOffset = 29, + Compression = 30, + Encryption = 31, +} + +struct UUID(u128); + +impl Display for UUID { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let x = self.0.to_le_bytes(); + write!(f, + "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[11], x[12], x[13], x[14], x[15], + ) + } +} + +struct Time { + secs: i64, + nanos: u32, +} + +impl Display for Time { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", DateTime::from_timestamp(self.secs, self.nanos).ok_or(std::fmt::Error)?) + +// write!(f, "{}.{:09}", self.secs, self.nanos) + } +} + +fn parse_tlv(ty: TLVType, data: &[u8]) -> Option> { + match ty { + TLVType::CTransID | TLVType::Ino | TLVType::Size | TLVType::Mode | TLVType::UID | + TLVType::GID | TLVType::RDev | TLVType::FileOffset | TLVType::CloneCTransID | + TLVType::CloneOffset | TLVType::CloneLen | TLVType::FileAttr | TLVType::UnencodedFileLen | + TLVType::UnencodedLen | TLVType::UnencodedOffset => + Some(Box::new(u64::from_le_bytes(data.try_into().ok()?))), + TLVType::FAllocateMode | TLVType::Compression | TLVType::Encryption => + Some(Box::new(u32::from_le_bytes(data.try_into().ok()?))), + TLVType::XAttrName | TLVType::Path | TLVType::PathTo | TLVType::PathLink | TLVType::ClonePath => + Some(Box::new(String::from_utf8(data.to_vec()).ok()?)), + TLVType::UUID | TLVType::CloneUUID => + Some(Box::new(UUID(u128::from_le_bytes(data.try_into().ok()?)))), + TLVType::CTime | TLVType::MTime | TLVType::ATime | TLVType::OTime => { + let secs = i64::from_le_bytes(data[0..8].try_into().ok()?); + let nanos = u32::from_le_bytes(data[8..12].try_into().ok()?); + Some(Box::new(Time {secs, nanos})) + }, + _ => None + } +} + +macro_rules! out { + ($($arg:tt)*) => { + let result = writeln!(std::io::stdout(), $($arg)*); + if let Some(e) = result.err().filter(|e| e.kind() != ErrorKind::BrokenPipe) { + panic!("I/O error: {}", &e); + } + }; +} + +fn main() -> Result<(), Box> { + let mut contents: Vec = Vec::new(); + + File::open("../btrfs_send_test")?.read_to_end(&mut contents)?; + + out!("Magic: {}", &String::from_utf8(contents[..12].to_vec())?); + out!("Version: {}", u32::from_le_bytes(contents[13..17].try_into()?)); + out!(""); + + let mut offset: usize = 17; + + while offset < contents.len() { + let len = u32::from_le_bytes(contents[offset .. offset + 4].try_into()?); + let cmd = CommandType::try_from_primitive( + u16::from_le_bytes( + contents[offset + 4 .. offset + 6].try_into()? + ) + )?; + let _checksum = u32::from_le_bytes(contents[offset + 6.. offset + 10].try_into()?); + + out!("{cmd:?}"); + + let mut inner_offset: usize = 0; + while inner_offset < len as usize { + let tlvtype = TLVType::try_from_primitive( + u16::from_le_bytes( + contents[offset + inner_offset + 10 .. offset + inner_offset + 12].try_into()? + ) + )?; + let tlvlen = u16::from_le_bytes(contents[offset + inner_offset + 12 .. offset + inner_offset + 14].try_into()?); + + let tlvvalue = parse_tlv( + tlvtype, + &contents[offset + inner_offset + 14 .. offset + inner_offset + 14 + tlvlen as usize] + ); + + if let Some(tlv) = tlvvalue { + out!(" {tlvtype:?}: {tlv}"); + } else { + out!(" {tlvtype:?}: ({tlvlen} bytes)"); + } + + inner_offset += tlvlen as usize + 4; + } + + offset += len as usize + 10; // 10 byte header + } + + Ok(()) +}