Skip to content

A simple archiving format, designed for storing assets in compact and secure containers

License

Notifications You must be signed in to change notification settings

zeskeertwee/vach

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

.vach logo

vach

A simple archiving format, designed for storing assets in compact secure containers

Crate Version on Crates.io docs.rs
GitHub GitHub Build and Test actions GitHub issues

Docs | Repo

vach, pronounced like "puck" but with a "v", is an archiving and resource transmission format. It was built to be secure, contained and protected. It was, in fact, designed by the SCP to keep your anomalous assets compact and secure during transmission. vach also has in-built support for multiple compression schemes (LZ4, Snappy and Brolti), data signing, leaf bitflags, encryption and some degree of archive customization. Check out the vach spec at spec.txt. Any and all help will be much appreciated, especially proof reading the docs and code review.


⛏ Who is this for?

  • You just released some software and don't want your assets pirated or easily read.
  • You want a simple convinient way to manage, decompress, decrypt and authenticate assets in distribution.
  • You want a pure Rustℒ️ archive format with no C bindings underneath (bindings for C may become available in the future).
  • You want your product to be neat, and all your assets to be in one neat secure container.

🀷 Who is what, when where?

  • vach: An archiving format, like tar, zip and rar. Also the base crate for handling .vach files in your application.
  • vach-cli: Crate Version on Crates.io A CLI tool for dealing with .vach files.

πŸ‘„ Terminologies

  • Archive Source: Any source of data. That implements io::Seek and io::Read, for example a file (fs::File) or in memory buffer (io::Cursor<Vec<u8>>).
  • Leaf: Any actual data endpoint within an archive, for example footstep1.wav in sounds.vach.
  • Entry: Some data in the registry section of a vach source on an corresponding leaf. For example, { id: footstep.wav, location: 45, offset: 2345, flags: 0b0000_0000_0000_0000u16 }.

πŸ€„ Show me some code dang it!

> Building a basic unsigned .vach file
use std::{io::Cursor, fs::File};
use vach::prelude::{Builder, BuilderConfig};

let config = BuilderConfig::default();
let mut builder = Builder::default();

// Use `Builder::add( reader, ID )` to add data to the write queue
builder.add(File::open("test_data/background.wav")?, "ambient").unwrap();
builder.add(vec![12, 23, 34, 45, 56, 67, 78, 89, 10], "ftstep").unwrap();
builder.add(b"Fast-Acting Long-Lasting, *Bathroom Reader*" as &[u8], "hello").unwrap();

// let mut target = File::create("sounds.vach")?;
let mut target = Cursor::new(Vec::new());

// The number of bytes written to the file
let size = builder.dump(&mut target, &config).unwrap();
> Loading resources from an unsigned .vach file
use std::fs::File;
use vach::prelude::{Archive, Resource, Flags};

let target = File::open("sounds.vach")?;
let mut archive = Archive::new(target)?;
let resource: Resource = archive.fetch_mut("ambient")?;

// By default all resources are flagged as NOT authenticated
println!("{}", Sound::new(&resource.data)?);
assert!(!resource.authenticated);
> Build a signed .vach file
use std::{io::Cursor, fs::File};
use vach::prelude::{Builder, BuilderConfig, Keypair};
use vach::crypto_utils::gen_keypair;

let keypair:      Keypair = gen_keypair();
let config:       BuilderConfig = BuilderConfig::default().keypair(keypair);
let mut builder:  Builder = Builder::default();

// Use different data types under the same builder umbrella, uses dynamic dispatch
let data_1 = vec![12, 23, 45, 56, 67 ,78, 89, 69];
let data_2 = File::open("test_data/footstep.wav").unwrap();
let data_3 = b"Hello, Cassandra!" as &[u8];

// Use `Builder::add( reader, ID )` to add data to the write queue
builder.add(data_3, "ambient").unwrap();
builder.add(data_2, "ftstep").unwrap();
builder.add(data_1.as_slice(), "hello").unwrap();

let mut target = File::create("sounds.vach")?;
builder.dump(&mut target, &config).unwrap();
> Serialize and de-serialize a Keypair, SecretKey and PublicKey

As Keypair, SecretKey and PublicKey are reflected from ed25519_dalek, you could refer to their docs to read further about them.

use vach::prelude::{Keypair, SecretKey, PublicKey};
use vach::crypto_utils::gen_keypair;

// Generate keys
let keypair :   Keypair  = gen_keypair();
let secret :    SecretKey = keypair.secret;
let public :    PublicKey = keypair.public;

// Serialize
let public_key_bytes : [u8; vach::PUBLIC_KEY_LENGTH] = public.to_bytes();
let secret_key_bytes : [u8; vach::SECRET_KEY_LENGTH] = secret.to_bytes();
let keypair_bytes : [u8; vach::KEYPAIR_LENGTH]    = keypair.to_bytes();

// Deserialize
let public_key :  PublicKey = PublicKey::from_bytes(&public_key_bytes).unwrap();
let secret_key :  SecretKey = SecretKey::from_bytes(&secret_key_bytes).unwrap();
let keypair :     Keypair   = Keypair::from_bytes(&keypair_bytes).unwrap();
> Load resources from a signed .vach source
// Load public_key
let mut public_key_bytes: [u8; crate::PUBLIC_KEY_LENGTH] = include_bytes!(PUBLIC_KEY);

// Build the Loader config
let mut config = ArchiveConfig::default().key(PublicKey::from_bytes(&public_key_bytes)?);

let target = File::open("sounds.vach")?;
let mut archive = Archive::with_config(target, &config)?;

// Resources are marked as secure (=true) if the signatures match the data
let resource = archive.fetch_mut("ambient")?;
println!("{}", Sound::new(&resource.data)?);
assert!(resource.authenticated);
> A quick consolidated example
const MAGIC: &[u8; 5] = b"CSDTD";
let mut target: Cursor<Vec<u8>> = Cursor::new(Vec::new());

// Data to be written
let data_1 = b"Around The World, Fatter better stronker" as &[u8];
let data_2 = b"Imagine if this made sense" as &[u8];
let data_3 = b"Fast-Acting Long-Lasting, *Bathroom Reader*" as &[u8];

// Builder definition
let mut builder = Builder::new();
let config = BuilderConfig::default().magic(*MAGIC);

// Add data
builder.add_leaf(Leaf::new(data_1).id("d1").compress(CompressMode::Always))?;
builder.add_leaf(Leaf::new(data_2).id("d2").compress(CompressMode::Never))?;
builder.add_leaf(Leaf::new(data_3).id("d3").compress(CompressMode::Detect))?;

// Dump data
builder.dump(&mut target, &config)?;

// Load data
let config = ArchiveConfig::default().magic(*MAGIC);
let mut archive = Archive::with_config(target, &config)?;

// Quick assertions
assert_eq!(archive.fetch_mut("d1")?.data.as_slice(), data_1);
assert_eq!(archive.fetch_mut("d2")?.data.as_slice(), data_2);
assert_eq!(archive.fetch_mut("d3")?.data.as_slice(), data_3);

For more information on how to use the library, read the documentation. Always read the documentation! or read the tests, they offer great insight into how the crate works.


πŸ›  Yet to be implemented

  • An official CLI, check it out.
  • Data encryption.
  • Benchmarks.
  • Features to turn off (or to turn on) either the Builder or the Loader modules.
  • Some(examples) instead of None
  • Skynet, (coming very soon).
  • Some proper benchmarking code. (Call for participation)

If you appreciate the works of this repo, consider dropping a star. It will be much appreciated; 🌟