Skip to content

Instantly share code, notes, and snippets.

@micolous
Created November 6, 2023 07:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save micolous/cca4e46fb7e4c877e495320c70d25bc5 to your computer and use it in GitHub Desktop.
Save micolous/cca4e46fb7e4c877e495320c70d25bc5 to your computer and use it in GitHub Desktop.
binrw seeking issues
/*
https://github.com/jam1garner/binrw/issues/235
[dependencies]
binrw = "0.13.1"
hex = "0.4.3"
sha2 = "0.10.8"
## OUTPUT
Payload: Payload { a: 12648430, b: 4660, c: 244837814094590 }
Payload as bytes: 1234000000c0ffee0000deadbeefcafe
Payload read back: Payload { a: 12648430, b: 4660, c: 244837814094590 }
SHA256 of payload: f0d3f5a9214fc316f601ddba30ad226f65d7b06378db8a7435db28a3621a7383
Data: Data { payload: Payload { a: 12648430, b: 4660, c: 244837814094590 } }
seek: Current(0)
seek: Current(0)
write: hashing: 00000000
write: hashing: 00c0ffee
seek: Start(32)
write: hashing: 1234
seek: Current(6)
write: hashing: 0000deadbeefcafe
seek: Start(0)
check: 7a5e2388003259b86b183add4bfbdcd5fdf58286c4b3101282898ac97dabb067
Data as bytes: 7a5e2388003259b86b183add4bfbdcd5fdf58286c4b3101282898ac97dabb0671234000000c0ffee0000deadbeefcafe
First 32 bytes should match SHA256 (but doesn't), next 16 bytes should match Payload bytes (and does)
Trying to read data back (but this gives fails with another checksum)
seek: Current(32)
seek: Current(0)
seek: Current(0)
seek: Current(4)
seek: Current(0)
read: hashing: 00c0ffee
seek: Start(32)
seek: Current(0)
read: hashing: 1234
seek: Current(6)
seek: Current(0)
read: hashing: 0000deadbeefcafe
seek: Start(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
seek: Current(0)
check: 0a48af60ef0c2bc5b042cb77c4e035f261b9c1681f58b02beed5b6fb62acf398
check: 0a48af60ef0c2bc5b042cb77c4e035f261b9c1681f58b02beed5b6fb62acf398
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: bad checksum: 7a5e2388003259b86b183add4bfbdcd5fdf58286c4b3101282898ac97dabb067 != 0a48af60ef0c2bc5b042cb77c4e035f261b9c1681f58b02beed5b6fb62acf398 at 0x0', src/main.rs:235:54
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
*/
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use binrw::{binrw, BinWrite, BinRead};
use sha2::{Digest, Sha256};
/// Data structure
///
/// First 32 bytes are SHA256 checksum, followed by [Payload].
#[binrw]
#[derive(Debug, Default, Clone)]
#[brw(big, stream = r, map_stream = Checksum::new)]
pub struct Data {
#[brw(pad_before = 32)]
pub payload: Payload,
#[brw(seek_before = SeekFrom::Start(0))]
#[br(temp, assert(checksum == r.check(), "bad checksum: {} != {}", hex::encode(checksum), hex::encode(r.check())))]
#[bw(calc(r.check()))]
pub checksum: [u8; 32],
}
/// Payload data.
///
/// This is a contrived example with a bunch of seeks to demonstrate what a more
/// complicated structure (like something with FilePtr or that depended on
/// `calc()`) would do.
#[binrw]
#[derive(Debug, Default, Clone)]
#[brw(big)]
pub struct Payload {
#[brw(pad_before = 4, restore_position)]
pub a: u32,
pub b: u16,
#[brw(seek_before = SeekFrom::Current(6))]
pub c: u64,
}
struct Checksum<T> {
inner: T,
sha256: Sha256,
p: u64,
}
impl<T> Checksum<T> {
fn new(inner: T) -> Self {
Self {
inner,
sha256: Sha256::new(),
p: 0,
}
}
fn check(&self) -> [u8; 32] {
let x = self.sha256.clone().finalize().into();
println!("check: {}", hex::encode(&x));
x
}
}
impl<T: Read> Read for Checksum<T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let size = self.inner.read(buf)?;
if self.p < 32 {
// Don't hash the first 32 bytes of the file.
// Position of byte 32 in this buffer
let x = (32 - self.p) as usize;
if x < size {
// Byte 32 is in this buffer
println!("read: hashing: {}", hex::encode(&buf[x..size]));
self.sha256.update(&buf[x..size]);
}
} else {
// We're past the first 32 bytes of the file.
println!("read: hashing: {}", hex::encode(&buf[0..size]));
self.sha256.update(&buf[0..size]);
}
self.p += size as u64;
Ok(size)
}
}
impl<T: Seek> Seek for Checksum<T> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
println!("seek: {pos:?}");
self.p = self.inner.seek(pos)?;
Ok(self.p)
}
}
impl<T: Write> Write for Checksum<T> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let size = self.inner.write(buf)?;
if self.p < 32 {
// Don't hash the first 32 bytes of the file
// Position of byte 32 in this buffer
let x = (32 - self.p) as usize;
if x < size {
// Byte 32 is in this buffer
println!("write: hashing: {}", hex::encode(&buf[x..size]));
self.sha256.update(&buf[x..size]);
}
} else {
// We're past the first 32 bytes of the file.
println!("write: hashing: {}", hex::encode(&buf[..size]));
self.sha256.update(&buf[..size]);
}
self.p += size as u64;
Ok(size)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
fn main() {
let p = Payload {
a: 0xc0ffee,
b: 0x1234,
c: 0xdeadbeefcafe,
};
println!("Payload: {p:?}");
let mut c = Cursor::new(Vec::new());
p.write(&mut c).unwrap();
let c = c.into_inner();
println!("Payload as bytes: {}", hex::encode(&c));
let p2 = Payload::read(&mut Cursor::new(c.clone())).unwrap();
println!("Payload read back: {p2:?}");
println!();
let mut s = Sha256::new();
s.update(&c);
println!("SHA256 of payload: {}", hex::encode(s.finalize()));
println!();
// Wrap it in a Data to get our checksum
let d = Data { payload: p };
println!("Data: {d:?}");
let mut c = Cursor::new(Vec::new());
d.write(&mut c).unwrap();
let c = c.into_inner();
println!("Data as bytes: {}", hex::encode(&c));
println!("First 32 bytes should match SHA256 (but doesn't), next 16 bytes should match Payload bytes (and does)");
// read it back
println!("Trying to read data back (but this gives fails with another checksum)");
let d2 = Data::read(&mut Cursor::new(c.clone())).unwrap();
println!("Data read back: {d2:?}");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment