Skip to content

Commit 1227836

Browse files
authored
blobby: replace iterators with const fn parsing (#1187)
The new approach parses blb files into statics which allows to use the crate in `no_std` contexts. This PR additionally introduces the `parse_into_structs` macro which defines a struct with byte slice fields and transforms blobby data into list of these structs. For example, it allows us to write: ```rust #[derive(Copy, Clone, Debug)] struct TestVector { key: &'static [u8], nonce: &'static [u8], aad: &'static [u8], plaintext: &'static [u8], ciphertext: &'static [u8] } blobby::parse_into_structs!( include_bytes!("/path/to/file.blb"); static TEST_VECTORS: &[ TestVector { key, nonce, aad, plaintext, ciphertext } ]; ); // Expands into: static TEST_VECTORS: &[TestVector] = { ... }; ``` I also thought about adding support for byte array fields, but with the current Rust capabilities (i.e. without const traits) the resulting code becomes annoyingly complex, so I decided against it in the current version.
1 parent 7b735d7 commit 1227836

File tree

7 files changed

+603
-411
lines changed

7 files changed

+603
-411
lines changed

blobby/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ categories = ["no-std"]
1010
edition = "2024"
1111
rust-version = "1.85"
1212
readme = "README.md"
13+
14+
[features]
15+
alloc = []

blobby/README.md

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,47 @@ Iterators over a simple binary blob storage.
1111

1212
## Examples
1313
```
14-
let buf = b"\x02\x05hello\x06world!\x01\x02 \x00\x03\x06:::\x03\x01\x00";
15-
let mut v = blobby::BlobIterator::new(buf).unwrap();
16-
assert_eq!(v.next(), Some(Ok(&b"hello"[..])));
17-
assert_eq!(v.next(), Some(Ok(&b" "[..])));
18-
assert_eq!(v.next(), Some(Ok(&b""[..])));
19-
assert_eq!(v.next(), Some(Ok(&b"world!"[..])));
20-
assert_eq!(v.next(), Some(Ok(&b":::"[..])));
21-
assert_eq!(v.next(), Some(Ok(&b"world!"[..])));
22-
assert_eq!(v.next(), Some(Ok(&b"hello"[..])));
23-
assert_eq!(v.next(), Some(Ok(&b""[..])));
24-
assert_eq!(v.next(), None);
25-
26-
let mut v = blobby::Blob2Iterator::new(buf).unwrap();
27-
assert_eq!(v.next(), Some(Ok([&b"hello"[..], b" "])));
28-
assert_eq!(v.next(), Some(Ok([&b""[..], b"world!"])));
29-
assert_eq!(v.next(), Some(Ok([&b":::"[..], b"world!"])));
30-
assert_eq!(v.next(), Some(Ok([&b"hello"[..], b""])));
31-
assert_eq!(v.next(), None);
32-
33-
let mut v = blobby::Blob4Iterator::new(buf).unwrap();
34-
assert_eq!(v.next(), Some(Ok([&b"hello"[..], b" ", b"", b"world!"])));
35-
assert_eq!(v.next(), Some(Ok([&b":::"[..], b"world!", b"hello", b""])));
36-
assert_eq!(v.next(), None);
14+
// We recommend to save blobby data into separate files and
15+
// use the `include_bytes!` macro
16+
static BLOBBY_DATA: &[u8] = b"\x02\x05hello\x06world!\x01\x02 \x00\x03\x06:::\x03\x01\x00";
17+
18+
static SLICE: &[&[u8]] = blobby::parse_into_slice!(BLOBBY_DATA);
19+
20+
assert_eq!(SLICE[0], b"hello".as_slice());
21+
assert_eq!(SLICE[1], b" ".as_slice());
22+
assert_eq!(SLICE[2], b"".as_slice());
23+
assert_eq!(SLICE[3], b"world!".as_slice());
24+
assert_eq!(SLICE[4], b":::".as_slice());
25+
assert_eq!(SLICE[5], b"world!".as_slice());
26+
assert_eq!(SLICE[6], b"hello".as_slice());
27+
assert_eq!(SLICE[7], b"".as_slice());
28+
assert_eq!(SLICE.len(), 8);
29+
30+
blobby::parse_into_structs!(
31+
BLOBBY_DATA;
32+
#[define_struct]
33+
static ITEMS: &[Item { a, b, c, d }];
34+
);
35+
36+
assert_eq!(
37+
ITEMS[0],
38+
Item {
39+
a: b"hello",
40+
b: b" ",
41+
c: b"",
42+
d: b"world!",
43+
},
44+
);
45+
assert_eq!(
46+
ITEMS[1],
47+
Item {
48+
a: b":::",
49+
b: b"world!",
50+
c: b"hello",
51+
d: b"",
52+
},
53+
);
54+
assert_eq!(ITEMS.len(), 2);
3755
```
3856

3957
## Encoding and decoding
@@ -76,14 +94,14 @@ with `\n`).
7694

7795
This file can be converted to the Blobby format by running the following command:
7896
```sh
79-
cargo run --releae --bin encode -- /path/to/input.txt /path/to/output.blb
97+
cargo run --release --features alloc --bin encode -- /path/to/input.txt /path/to/output.blb
8098
```
8199

82100
This will create a file which can be read using `blobby::Blob2Iterator`.
83101

84102
To see contents of an existing Blobby file you can use the following command:
85103
```sh
86-
cargo run --releae --bin decode -- /path/to/input.blb /path/to/output.txt
104+
cargo run --release --features alloc --bin decode -- /path/to/input.blb /path/to/output.txt
87105
```
88106
The output file will contain a sequence of hex-encoded byte strings stored
89107
in the input file.

blobby/src/bin/decode.rs

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,41 @@
11
//! Encoding utility
2-
use blobby::BlobIterator;
3-
use std::io::{self, BufRead, BufReader, BufWriter, Write};
4-
use std::{env, error::Error, fs::File};
5-
6-
fn encode_hex(data: &[u8]) -> String {
7-
let mut res = String::with_capacity(2 * data.len());
8-
for &byte in data {
9-
res.push_str(&format!("{byte:02X}"));
10-
}
11-
res
2+
use std::error::Error;
3+
4+
#[cfg(not(feature = "alloc"))]
5+
fn main() -> Result<(), Box<dyn Error>> {
6+
Err("The decode binary should be compiled with enabled `alloc` feature!".into())
127
}
138

14-
fn decode<R: BufRead, W: Write>(mut reader: R, mut writer: W) -> io::Result<usize> {
15-
let mut data = Vec::new();
16-
reader.read_to_end(&mut data)?;
17-
let res = BlobIterator::new(&data)
18-
.map_err(|e| {
19-
io::Error::new(
20-
io::ErrorKind::InvalidData,
21-
format!("invalid blobby data: {e:?}"),
22-
)
23-
})?
24-
.collect::<Vec<_>>();
25-
for blob in res.iter() {
26-
let blob = blob.map_err(|e| {
9+
#[cfg(feature = "alloc")]
10+
fn main() -> Result<(), Box<dyn Error>> {
11+
use std::io::{self, BufRead, BufReader, BufWriter, Write};
12+
use std::{env, fs::File};
13+
14+
fn encode_hex(data: &[u8]) -> String {
15+
let mut res = String::with_capacity(2 * data.len());
16+
for &byte in data {
17+
res.push_str(&format!("{byte:02X}"));
18+
}
19+
res
20+
}
21+
22+
fn decode<R: BufRead, W: Write>(mut reader: R, mut writer: W) -> io::Result<usize> {
23+
let mut data = Vec::new();
24+
reader.read_to_end(&mut data)?;
25+
let res = blobby::parse_into_vec(&data).map_err(|e| {
2726
io::Error::new(
2827
io::ErrorKind::InvalidData,
2928
format!("invalid blobby data: {e:?}"),
3029
)
3130
})?;
32-
writer.write_all(encode_hex(blob).as_bytes())?;
33-
writer.write_all(b"\n")?;
31+
let len = res.len();
32+
for blob in res {
33+
writer.write_all(encode_hex(blob).as_bytes())?;
34+
writer.write_all(b"\n")?;
35+
}
36+
Ok(len)
3437
}
35-
Ok(res.len())
36-
}
3738

38-
fn main() -> Result<(), Box<dyn Error>> {
3939
let args: Vec<String> = env::args().skip(1).collect();
4040

4141
if args.is_empty() {

blobby/src/bin/encode.rs

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,57 @@
11
//! Encoding utility
2-
use blobby::encode_blobs;
3-
use std::io::{self, BufRead, BufReader, BufWriter, Write};
4-
use std::{env, error::Error, fs::File};
2+
use std::error::Error;
53

6-
fn decode_hex_char(b: u8) -> io::Result<u8> {
7-
let res = match b {
8-
b'0'..=b'9' => b - b'0',
9-
b'a'..=b'f' => b - b'a' + 10,
10-
b'A'..=b'F' => b - b'A' + 10,
11-
_ => {
12-
let msg = "Invalid hex string: invalid byte {b}";
13-
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
14-
}
15-
};
16-
Ok(res)
4+
#[cfg(not(feature = "alloc"))]
5+
fn main() -> Result<(), Box<dyn Error>> {
6+
Err("The encode binary should be compiled with enabled `alloc` feature!".into())
177
}
188

19-
fn decode_hex(data: &str) -> io::Result<Vec<u8>> {
20-
if data.len() % 2 != 0 {
21-
let msg = "Invalid hex string: length is not even";
22-
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
9+
#[cfg(feature = "alloc")]
10+
fn main() -> Result<(), Box<dyn Error>> {
11+
use blobby::encode_blobs;
12+
use std::io::{self, BufRead, BufReader, BufWriter, Write};
13+
use std::{env, fs::File};
14+
15+
fn decode_hex_char(b: u8) -> io::Result<u8> {
16+
let res = match b {
17+
b'0'..=b'9' => b - b'0',
18+
b'a'..=b'f' => b - b'a' + 10,
19+
b'A'..=b'F' => b - b'A' + 10,
20+
_ => {
21+
let msg = "Invalid hex string: invalid byte {b}";
22+
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
23+
}
24+
};
25+
Ok(res)
2326
}
24-
data.as_bytes()
25-
.chunks_exact(2)
26-
.map(|chunk| {
27-
let a = decode_hex_char(chunk[0])?;
28-
let b = decode_hex_char(chunk[1])?;
29-
Ok((a << 4) | b)
30-
})
31-
.collect()
32-
}
3327

34-
fn encode(reader: impl BufRead, mut writer: impl Write) -> io::Result<usize> {
35-
let mut blobs = Vec::new();
36-
for line in reader.lines() {
37-
let blob = decode_hex(&line?)?;
38-
blobs.push(blob);
28+
fn decode_hex(data: &str) -> io::Result<Vec<u8>> {
29+
if data.len() % 2 != 0 {
30+
let msg = "Invalid hex string: length is not even";
31+
return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
32+
}
33+
data.as_bytes()
34+
.chunks_exact(2)
35+
.map(|chunk| {
36+
let a = decode_hex_char(chunk[0])?;
37+
let b = decode_hex_char(chunk[1])?;
38+
Ok((a << 4) | b)
39+
})
40+
.collect()
41+
}
42+
43+
fn encode(reader: impl BufRead, mut writer: impl Write) -> io::Result<usize> {
44+
let mut blobs = Vec::new();
45+
for line in reader.lines() {
46+
let blob = decode_hex(&line?)?;
47+
blobs.push(blob);
48+
}
49+
let (data, idx_len) = encode_blobs(&blobs);
50+
println!("Index len: {idx_len:?}");
51+
writer.write_all(&data)?;
52+
Ok(blobs.len())
3953
}
40-
let (data, idx_len) = encode_blobs(&blobs);
41-
println!("Index len: {idx_len:?}");
42-
writer.write_all(&data)?;
43-
Ok(blobs.len())
44-
}
4554

46-
fn main() -> Result<(), Box<dyn Error>> {
4755
let args: Vec<String> = env::args().skip(1).collect();
4856

4957
if args.is_empty() {

0 commit comments

Comments
 (0)