Skip to content

Commit

Permalink
chore: begin work on artifact format (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
koehlma committed Aug 30, 2024
1 parent b877231 commit 40260b0
Show file tree
Hide file tree
Showing 11 changed files with 1,694 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://github.com/silitics/rugpi/"
homepage = "https://oss.silitics.com/rugpi/"

[workspace.dependencies]
bytes = "1.7.1"
anyhow = "1.0.71"
clap = { version = "4.3", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
Expand Down
22 changes: 22 additions & 0 deletions crates/rugpi-artifact/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "rugpi-artifact"
edition = "2021"
authors.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true

[dependencies]
bytes.workspace = true
console = "0.15.8"
serde.workspace = true
sha2 = "0.10.8"
thiserror = "1.0.63"

[dev-dependencies]
clap = { version = "4.5.16", features = ["derive"] }
serde_json = "1.0.127"

[lints]
workspace = true
22 changes: 22 additions & 0 deletions crates/rugpi-artifact/examples/pretty-print-stlv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::{error::Error, fs, io::BufReader, path::PathBuf};

use clap::Parser;
use rugpi_artifact::format::{
stlv::{self, SkipSeek},
tags::TagNameResolver,
};

/// Pretty print an STLV stream.
#[derive(Debug, Clone, Parser)]
pub struct Args {
/// File with the STLV stream to pretty print.
file: PathBuf,
}

pub fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
let file = fs::File::open(&args.file)?;
let mut reader = BufReader::new(file);
stlv::pretty_print::<_, SkipSeek>(&mut reader, Some(&TagNameResolver))?;
Ok(())
}
119 changes: 119 additions & 0 deletions crates/rugpi-artifact/examples/rugpi-artifact.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::{
error::Error,
fs::File,
io::{self, BufRead, BufReader, BufWriter, Write},
path::PathBuf,
};

use clap::Parser;
use rugpi_artifact::format::{
encode::{self, Encode},
stlv::{write_atom_head, write_close_segment, write_open_segment, AtomHead},
tags, ArtifactHeader, FragmentHeader, FragmentInfo, Hash, Metadata,
};
use sha2::Digest;

#[derive(Debug, Clone, Parser)]
pub struct Args {
#[clap(subcommand)]
cmd: Cmd,
}

#[derive(Debug, Clone, Parser)]
pub enum Cmd {
/// Create an artifact.
Create(CreateCmd),
}

#[derive(Debug, Clone, Parser)]
pub struct CreateCmd {
/// File containing artifact metadata.
#[clap(long)]
metadata: Option<PathBuf>,
/// Path to the artifact.
artifact: PathBuf,
/// Paths to the fragments of the artifact.
fragments: Vec<PathBuf>,
}

pub fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
match args.cmd {
Cmd::Create(cmd) => {
let metadata = cmd
.metadata
.map(|path| {
Ok::<_, Box<dyn Error>>(Metadata {
map: serde_json::from_str(&std::fs::read_to_string(path)?)?,
})
})
.transpose()?
.unwrap_or_default();
let mut fragment_infos = Vec::new();
let mut fragment_headers = Vec::new();
let mut offset = 0;
for fragment in &cmd.fragments {
let size = fragment.metadata()?.len();
let header = FragmentHeader {
compression: None,
encoded_index: None,
decoded_index: None,
};
let mut hasher = sha2::Sha512_256::new();
let mut reader = BufReader::new(File::open(fragment)?);
loop {
let buffer = reader.fill_buf()?;
if buffer.is_empty() {
break;
}
hasher.update(buffer);
let consumed = buffer.len();
reader.consume(consumed);
}
let payload_digest = hasher.finalize();
let payload_hash = Hash {
algorithm: "sha512_256".to_owned(),
digest: payload_digest.as_slice().to_vec().into(),
};
let encoded_header = encode::to_vec(header, tags::FRAGMENT_HEADER);
let header_digest = sha2::Sha512_256::digest(&encoded_header);
let header_hash = Hash {
algorithm: "sha512_256".to_owned(),
digest: header_digest.as_slice().to_vec().into(),
};
fragment_infos.push(FragmentInfo {
metadata: Metadata::default(),
filename: fragment.to_str().unwrap().to_owned(),
offset: Some(offset),
slot: None,
header_hash,
payload_hash,
});
offset += AtomHead::open(tags::FRAGMENT).atom_size();
offset += encoded_header.len() as u64;
offset += AtomHead::value(tags::FRAGMENT_PAYLOAD, size).atom_size();
offset += AtomHead::close(tags::FRAGMENT).atom_size();
fragment_headers.push(encoded_header);
}
let header = ArtifactHeader {
metadata,
fragments: fragment_infos,
};
let mut writer = BufWriter::new(File::create(&cmd.artifact)?);
write_open_segment(&mut writer, tags::ARTIFACT)?;
header.encode(&mut writer, tags::ARTIFACT_HEADER)?;
write_open_segment(&mut writer, tags::FRAGMENTS)?;
for (idx, fragment) in cmd.fragments.iter().enumerate() {
write_open_segment(&mut writer, tags::FRAGMENT)?;
writer.write_all(&fragment_headers[idx])?;
let size = fragment.metadata()?.len();
write_atom_head(&mut writer, AtomHead::value(tags::FRAGMENT_PAYLOAD, size))?;
io::copy(&mut File::open(fragment)?, &mut writer)?;
write_close_segment(&mut writer, tags::FRAGMENT)?;
}
write_close_segment(&mut writer, tags::FRAGMENTS)?;
write_close_segment(&mut writer, tags::ARTIFACT)?;
}
}
Ok(())
}
Loading

0 comments on commit 40260b0

Please sign in to comment.