use std::io::{self, Read, Write};
#[inline]
pub fn pad4(n: usize) -> usize {
n.div_ceil(4) * 4
}
#[inline]
pub fn pad_len(n: usize) -> usize {
pad4(n) - n
}
#[inline]
pub fn read_i32<R: Read>(r: &mut R) -> io::Result<i32> {
let mut b = [0u8; 4];
r.read_exact(&mut b)?;
Ok(i32::from_be_bytes(b))
}
#[inline]
pub fn read_u32<R: Read>(r: &mut R) -> io::Result<u32> {
let mut b = [0u8; 4];
r.read_exact(&mut b)?;
Ok(u32::from_be_bytes(b))
}
#[inline]
pub fn read_i64<R: Read>(r: &mut R) -> io::Result<i64> {
let mut b = [0u8; 8];
r.read_exact(&mut b)?;
Ok(i64::from_be_bytes(b))
}
#[inline]
pub fn read_f32<R: Read>(r: &mut R) -> io::Result<f32> {
let mut b = [0u8; 4];
r.read_exact(&mut b)?;
Ok(f32::from_be_bytes(b))
}
#[inline]
pub fn read_f64<R: Read>(r: &mut R) -> io::Result<f64> {
let mut b = [0u8; 8];
r.read_exact(&mut b)?;
Ok(f64::from_be_bytes(b))
}
#[inline]
pub fn read_real<R: Read>(r: &mut R, is_double: bool) -> io::Result<f64> {
if is_double {
read_f64(r)
} else {
Ok(read_f32(r)? as f64)
}
}
pub fn read_opaque<R: Read>(r: &mut R, n: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0u8; n];
r.read_exact(&mut buf)?;
let pad = pad_len(n);
if pad > 0 {
let mut skip = [0u8; 4];
r.read_exact(&mut skip[..pad])?;
}
Ok(buf)
}
pub fn read_string<R: Read>(r: &mut R) -> io::Result<String> {
let len = read_u32(r)? as usize;
let bytes = read_opaque(r, len)?;
Ok(String::from_utf8_lossy(&bytes)
.trim_end_matches('\0')
.to_string())
}
#[inline]
pub fn write_i32<W: Write>(w: &mut W, v: i32) -> io::Result<()> {
w.write_all(&v.to_be_bytes())
}
#[inline]
pub fn write_u32<W: Write>(w: &mut W, v: u32) -> io::Result<()> {
w.write_all(&v.to_be_bytes())
}
#[inline]
pub fn write_f32<W: Write>(w: &mut W, v: f32) -> io::Result<()> {
w.write_all(&v.to_be_bytes())
}
#[inline]
pub fn write_f64<W: Write>(w: &mut W, v: f64) -> io::Result<()> {
w.write_all(&v.to_be_bytes())
}
pub fn write_opaque<W: Write>(w: &mut W, bytes: &[u8]) -> io::Result<()> {
w.write_all(bytes)?;
let pad = pad_len(bytes.len());
if pad > 0 {
w.write_all(&[0u8; 4][..pad])?;
}
Ok(())
}
pub fn write_string<W: Write>(w: &mut W, s: &str) -> io::Result<()> {
let bytes = s.as_bytes();
write_u32(w, bytes.len() as u32)?;
write_opaque(w, bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn pad4_rounds_up() {
assert_eq!(pad4(0), 0);
assert_eq!(pad4(1), 4);
assert_eq!(pad4(4), 4);
assert_eq!(pad4(5), 8);
assert_eq!(pad_len(13), 3);
assert_eq!(pad_len(12), 0);
}
#[test]
fn scalar_round_trip_big_endian() {
let mut buf = Vec::new();
write_i32(&mut buf, -42).unwrap();
write_u32(&mut buf, 1995).unwrap();
write_f32(&mut buf, 1.5).unwrap();
write_f64(&mut buf, -2.25).unwrap();
assert_eq!(&buf[4..8], &[0x00, 0x00, 0x07, 0xCB]);
let mut c = Cursor::new(buf);
assert_eq!(read_i32(&mut c).unwrap(), -42);
assert_eq!(read_u32(&mut c).unwrap(), 1995);
assert_eq!(read_f32(&mut c).unwrap(), 1.5);
assert_eq!(read_f64(&mut c).unwrap(), -2.25);
}
#[test]
fn opaque_round_trip_with_padding() {
let mut buf = Vec::new();
write_opaque(&mut buf, &[1, 2, 3, 4, 5]).unwrap();
assert_eq!(buf.len(), 8, "5 bytes padded to 8");
assert_eq!(&buf[5..], &[0, 0, 0]);
let mut c = Cursor::new(buf);
assert_eq!(read_opaque(&mut c, 5).unwrap(), vec![1, 2, 3, 4, 5]);
assert_eq!(c.position(), 8, "padding consumed");
}
#[test]
fn string_round_trip_standard_xdr() {
let mut buf = Vec::new();
write_string(&mut buf, "GMX_trn_file").unwrap();
assert_eq!(u32::from_be_bytes(buf[0..4].try_into().unwrap()), 12);
assert_eq!(buf.len(), 16);
let mut c = Cursor::new(buf);
assert_eq!(read_string(&mut c).unwrap(), "GMX_trn_file");
assert_eq!(c.position(), 16, "length + padded payload consumed");
}
#[test]
fn read_real_selects_width() {
let mut buf = Vec::new();
write_f32(&mut buf, 3.0).unwrap();
write_f64(&mut buf, 3.0).unwrap();
let mut c = Cursor::new(buf);
assert_eq!(read_real(&mut c, false).unwrap(), 3.0);
assert_eq!(read_real(&mut c, true).unwrap(), 3.0);
assert_eq!(c.position(), 12, "f32 then f64 consumed");
}
}