initial version

This commit is contained in:
Florian Stecker
2025-05-11 23:54:41 -04:00
commit 737f43de06
3 changed files with 191 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
Cargo.lock

8
Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "unitree"
version = "0.1.0"
edition = "2024"
[dependencies]
chrono = "0.4.40"
num_enum = "0.7.3"

181
src/main.rs Normal file
View File

@@ -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<Box<dyn Display>> {
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<dyn Error>> {
let mut contents: Vec<u8> = 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(())
}