initial commit
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "rply-codec"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rmp = "0.8.14"
|
||||||
|
thiserror = "2.0.17"
|
||||||
Binary file not shown.
+26
@@ -0,0 +1,26 @@
|
|||||||
|
mod rply;
|
||||||
|
|
||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v2_header() {
|
||||||
|
let mut file = std::fs::File::open("examples/bobl.replay").unwrap();
|
||||||
|
let header = match rply::read_header(&mut file).unwrap() {
|
||||||
|
rply::Header::V0V1(_) => panic!("Version too low"),
|
||||||
|
rply::Header::V2(h) => h,
|
||||||
|
};
|
||||||
|
// content_crc: 2199475946,
|
||||||
|
// initial_state_size: 2531,
|
||||||
|
// identifier: 1761326589,
|
||||||
|
assert_eq!(header.base.version, 2);
|
||||||
|
assert_eq!(header.base.content_crc, 2199475946);
|
||||||
|
assert_eq!(header.base.initial_state_size, 2531);
|
||||||
|
assert_eq!(header.base.identifier, 1761326589);
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
#[repr(usize)]
|
||||||
|
pub enum HeaderV0V1Part {
|
||||||
|
Magic = 0,
|
||||||
|
Version = 4,
|
||||||
|
CRC = 8,
|
||||||
|
StateSize = 12,
|
||||||
|
Identifier = 16,
|
||||||
|
HeaderLen = 24,
|
||||||
|
}
|
||||||
|
#[repr(usize)]
|
||||||
|
enum HeaderV2Part {
|
||||||
|
Magic = 0,
|
||||||
|
Version = 4,
|
||||||
|
CRC = 8,
|
||||||
|
StateSize = 12,
|
||||||
|
Identifier = 16,
|
||||||
|
FrameCount = 24,
|
||||||
|
BlockSize = 28,
|
||||||
|
SuperblockSize = 32,
|
||||||
|
CheckpointConfig = 36,
|
||||||
|
HeaderLen = 40,
|
||||||
|
}
|
||||||
|
const HEADER_V0V1_LEN_BYTES: usize = HeaderV0V1Part::HeaderLen as usize;
|
||||||
|
const HEADER_LEN_BYTES: usize = HeaderV2Part::HeaderLen as usize;
|
||||||
|
|
||||||
|
const VERSION: u32 = 2;
|
||||||
|
const MAGIC: u32 = 0x42535632;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum FrameToken {
|
||||||
|
Invalid = 0,
|
||||||
|
Regular = b'f',
|
||||||
|
Checkpoint = b'c',
|
||||||
|
Checkpoint2 = b'C',
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum SSToken {
|
||||||
|
Start = 0,
|
||||||
|
NewBlock = 1,
|
||||||
|
NewSuperblock = 2,
|
||||||
|
SuperblockSeq = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Compression {
|
||||||
|
None = 0,
|
||||||
|
Zlib = 1,
|
||||||
|
Zstd = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Compression {}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Encoding {
|
||||||
|
Raw = 0,
|
||||||
|
Statestream = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for Encoding {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HeaderBase {
|
||||||
|
pub version: u32,
|
||||||
|
pub content_crc: u32,
|
||||||
|
pub initial_state_size: u32,
|
||||||
|
pub identifier: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HeaderV2 {
|
||||||
|
pub base: HeaderBase,
|
||||||
|
pub frame_count: u32,
|
||||||
|
pub block_size: u32,
|
||||||
|
pub superblock_size: u32,
|
||||||
|
pub checkpoint_commit_interval: u8,
|
||||||
|
pub checkpoint_commit_threshold: u8,
|
||||||
|
pub checkpoint_compression: Compression,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Header {
|
||||||
|
V0V1(HeaderBase),
|
||||||
|
V2(HeaderV2),
|
||||||
|
}
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ReplayError {
|
||||||
|
#[error("Invalid replay magic {0}")]
|
||||||
|
Magic(u32),
|
||||||
|
#[error("Unsupported version {0}")]
|
||||||
|
Version(u32),
|
||||||
|
#[error("Unsupported compression scheme {0}")]
|
||||||
|
Compression(u8),
|
||||||
|
#[error("I/O Error")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = std::result::Result<T, ReplayError>;
|
||||||
|
|
||||||
|
pub fn read_header(rply: &mut impl std::io::Read) -> Result<Header> {
|
||||||
|
let mut bytes = vec![0; HEADER_LEN_BYTES];
|
||||||
|
rply.read_exact(&mut bytes[0..HEADER_V0V1_LEN_BYTES])?;
|
||||||
|
// These unwraps are safe because if I can take e.g. a slice of length 4, I already have a 4-byte value.
|
||||||
|
// And I know I can take those slices because read_exact read exactly 24 bytes.
|
||||||
|
let magic = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV0V1Part::Magic as usize)..(HeaderV0V1Part::Magic as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
if magic != MAGIC {
|
||||||
|
return Err(ReplayError::Magic(magic));
|
||||||
|
}
|
||||||
|
let version = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV0V1Part::Version as usize)..(HeaderV0V1Part::Version as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
if version > 2 {
|
||||||
|
return Err(ReplayError::Version(version));
|
||||||
|
}
|
||||||
|
let content_crc = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV0V1Part::CRC as usize)..(HeaderV0V1Part::CRC as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let initial_state_size = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV0V1Part::StateSize as usize)..(HeaderV0V1Part::StateSize as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let identifier = u64::from_le_bytes(
|
||||||
|
<[u8; 8]>::try_from(
|
||||||
|
&bytes
|
||||||
|
[(HeaderV0V1Part::Identifier as usize)..(HeaderV0V1Part::Identifier as usize + 8)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let base = HeaderBase {
|
||||||
|
version,
|
||||||
|
content_crc,
|
||||||
|
initial_state_size,
|
||||||
|
identifier,
|
||||||
|
};
|
||||||
|
if version < 2 {
|
||||||
|
return Ok(Header::V0V1(base));
|
||||||
|
}
|
||||||
|
rply.read_exact(&mut bytes[HEADER_V0V1_LEN_BYTES..HEADER_LEN_BYTES]);
|
||||||
|
let frame_count = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV2Part::FrameCount as usize)..(HeaderV2Part::FrameCount as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let block_size = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV2Part::BlockSize as usize)..(HeaderV2Part::BlockSize as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let superblock_size = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV2Part::SuperblockSize as usize)
|
||||||
|
..(HeaderV2Part::SuperblockSize as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let cp_config = u32::from_le_bytes(
|
||||||
|
<[u8; 4]>::try_from(
|
||||||
|
&bytes[(HeaderV2Part::CheckpointConfig as usize)
|
||||||
|
..(HeaderV2Part::CheckpointConfig as usize + 4)],
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let checkpoint_commit_interval = (cp_config >> 24) as u8;
|
||||||
|
let checkpoint_commit_threshold = ((cp_config >> 16) & 0xFF) as u8;
|
||||||
|
let checkpoint_compression = Compression::try_from(((cp_config >> 8) & 0xFF) as u8)?;
|
||||||
|
Ok(Header::V2(HeaderV2 {
|
||||||
|
base,
|
||||||
|
frame_count,
|
||||||
|
block_size,
|
||||||
|
superblock_size,
|
||||||
|
checkpoint_commit_interval,
|
||||||
|
checkpoint_commit_threshold,
|
||||||
|
checkpoint_compression,
|
||||||
|
}))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user