Created
November 6, 2023 07:42
-
-
Save micolous/cca4e46fb7e4c877e495320c70d25bc5 to your computer and use it in GitHub Desktop.
binrw seeking issues
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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