initial version
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
8
Cargo.toml
Normal file
8
Cargo.toml
Normal 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
181
src/main.rs
Normal 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user