Skip to content

Commit ff857da

Browse files
committed
Update Tile::from_path
* Support non-data data path elements * Support paths without height element * Update tests
1 parent a9f0d7b commit ff857da

File tree

3 files changed

+159
-68
lines changed

3 files changed

+159
-68
lines changed

crates/tlog_tiles/src/tile.rs

Lines changed: 157 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use crate::tlog::{
1919
node_hash, split_stored_hash_index, stored_hash_index, sub_tree_index, Hash, HashReader,
2020
TlogError, HASH_SIZE,
2121
};
22+
use std::cmp::max;
2223
use std::collections::HashMap;
2324
use std::fmt;
2425
use std::str::FromStr;
@@ -204,11 +205,7 @@ impl Tile {
204205
}
205206

206207
/// Path returns a tile coordinate path describing t.
207-
pub fn path(&self) -> String {
208-
self.path_internal(true)
209-
}
210-
211-
fn path_internal(&self, with_height: bool) -> String {
208+
pub fn path(&self, with_height: bool) -> String {
212209
let mut n = self.n;
213210
let h_str = if with_height {
214211
&format!("/{}", self.h)
@@ -255,8 +252,7 @@ impl Tile {
255252
Some(t)
256253
}
257254

258-
/// Parses a tile coordinate path. This currently supports only paths with
259-
/// height element and with the data path element "data".
255+
/// Parses a tile coordinate path.
260256
///
261257
/// # Errors
262258
///
@@ -265,36 +261,57 @@ impl Tile {
265261
/// # Panics
266262
///
267263
/// Panics if there are internal math errors.
268-
pub fn from_path(path: &str) -> Result<Self, BadPathError> {
269-
// This library bounds N to u64::MAX, which means we can limit the path size here.
270-
if path.len() > 52 {
264+
pub fn from_path(
265+
path: &str,
266+
with_height: bool,
267+
data_path: PathElem,
268+
) -> Result<Self, BadPathError> {
269+
// Calculate based on max supported values.
270+
let max_path_len = "tile/30".len()
271+
+ max(data_path.to_string().len() + 1, "/63".len())
272+
+ "/x018/x446/x744/x073/x709/x551/615.p/1073741823".len();
273+
if path.len() > max_path_len {
271274
return Err(BadPathError(path.into()));
272275
}
273276

277+
let min_path_elems = if with_height { 3 } else { 2 };
278+
274279
let mut components: Vec<&str> = path.split('/').collect();
275280
let len = components.len();
276281

277-
if len < 4 || components[0] != "tile" {
282+
if len < min_path_elems || components[0] != "tile" {
278283
return Err(BadPathError(path.into()));
279284
}
280285

281-
let h = u8::from_str(components[1]).map_err(|_| BadPathError(path.into()))?;
282-
let (l, data_path_opt) = if components[2] == "data" {
283-
(0, Some(PathElem::Data))
286+
let mut next_idx = 1;
287+
let h = if with_height {
288+
next_idx += 1;
289+
u8::from_str(components[1]).map_err(|_| BadPathError(path.into()))?
290+
} else {
291+
TlogTile::HEIGHT
292+
};
293+
294+
let (l, data_path_opt) = if components[next_idx] == data_path.to_string() {
295+
(0, Some(data_path))
284296
} else {
285297
(
286-
u8::from_str(components[2]).map_err(|_| BadPathError(path.into()))?,
298+
u8::from_str(components[next_idx]).map_err(|_| BadPathError(path.into()))?,
287299
None,
288300
)
289301
};
302+
if l > 63 {
303+
return Err(BadPathError(path.into()));
304+
}
305+
306+
next_idx += 1;
290307

291308
if !(1..=30).contains(&h) {
292309
return Err(BadPathError(path.into()));
293310
}
294311

295312
let mut w = 1 << h;
296313
#[allow(clippy::case_sensitive_file_extension_comparisons)]
297-
if len >= 5 && components[len - 2].ends_with(".p") {
314+
if len > min_path_elems && components[len - 2].ends_with(".p") {
298315
let ww = u32::from_str(components[len - 1]).map_err(|_| BadPathError(path.into()))?;
299316
if !(0..w).contains(&ww) {
300317
return Err(BadPathError(path.into()));
@@ -304,7 +321,7 @@ impl Tile {
304321
components.pop();
305322
}
306323

307-
components = components[3..].to_vec();
324+
components = components[next_idx..].to_vec();
308325

309326
let mut n = 0_u64;
310327
for s in components {
@@ -327,7 +344,7 @@ impl Tile {
327344

328345
let tile = Self::new(h, l, n, w, data_path_opt);
329346

330-
if path != tile.path() {
347+
if path != tile.path(with_height) {
331348
return Err(BadPathError(path.into()));
332349
}
333350

@@ -446,7 +463,7 @@ impl TlogTile {
446463
/// parameter should be `"entries"` for tlog-tiles, and `"data"` for
447464
/// static-ct-api.
448465
pub fn path(&self) -> String {
449-
self.0.path_internal(false)
466+
self.0.path(false)
450467
}
451468

452469
/// Returns the tile's `k`'th tile parent in the tiles for a tree of size `n`. If there is no such
@@ -675,17 +692,126 @@ mod tests {
675692
}
676693

677694
#[test]
678-
fn test_tile_from_path() -> Result<(), BadPathError> {
679-
// Valid tile path with all parameters at the maximum supported by this library.
680-
let _ = Tile::from_path("tile/30/data/x018/x446/x744/x073/x709/x551/615.p/255")?;
681-
// Valid tile path.
682-
let _ = Tile::from_path("tile/3/5/x13/x4/5/x3/6/07/5");
683-
// Same as above, but tile path is not in canonical form.
684-
let _ = Tile::from_path("tile/3/5/x013/x004/x005/x003/x006/x007/005")?;
685-
// Triggers integer overflow.
686-
let _ = Tile::from_path("tile/30/data/x018/x446/x744/x073/x709/x551/616.p/255");
687-
// Path too long.
688-
let _ = Tile::from_path("tile/30/data/x018/x446/x744/x073/x709/x551/616.p/2555");
689-
Ok(())
695+
fn test_tile_from_path() {
696+
let tests = [
697+
// Valid: minimum params support by library, without height
698+
("tile/0/000", false, PathElem::Custom(""), true),
699+
// Invalid: N not in canonical form
700+
("tile/0/00", false, PathElem::Custom(""), false),
701+
// Invalid: H = 0
702+
("tile/0/0/000", true, PathElem::Custom(""), false),
703+
// Valid: minimum params support by library, with height
704+
("tile/1/0/000", true, PathElem::Custom(""), true),
705+
// Valid: all parameters at max supported by library
706+
(
707+
"tile/30/63/x018/x446/x744/x073/x709/x551/615.p/1073741823",
708+
true,
709+
PathElem::Data,
710+
true,
711+
),
712+
// Valid: same as above, with data path
713+
(
714+
"tile/30/data/x018/x446/x744/x073/x709/x551/615.p/1073741823",
715+
true,
716+
PathElem::Data,
717+
true,
718+
),
719+
// Invalid: total path too long (also data path element mismatch)
720+
(
721+
"tile/too_long/63/x018/x446/x744/x073/x709/x551/615.p/1073741823",
722+
true,
723+
PathElem::Data,
724+
false,
725+
),
726+
// Invalid: non-canonical trailing zero path elements
727+
("tile/30/63/x000/615.p/1", true, PathElem::Data, false),
728+
// Invalid: N > u64::MAX
729+
(
730+
"tile/30/data/x018/x446/x744/x073/x709/x551/616.p/1073741823",
731+
true,
732+
PathElem::Data,
733+
false,
734+
),
735+
// Invalid: W > 2**H
736+
(
737+
"tile/30/data/x018/x446/x744/x073/x709/x551/615.p/1073741824",
738+
true,
739+
PathElem::Data,
740+
false,
741+
),
742+
// Invalid: H > 30
743+
(
744+
"tile/31/data/x018/x446/x744/x073/x709/x551/615.p/1073741824",
745+
true,
746+
PathElem::Data,
747+
false,
748+
),
749+
// Invalid: L > 63
750+
(
751+
"tile/30/64/x018/x446/x744/x073/x709/x551/615.p/1073741824",
752+
true,
753+
PathElem::Data,
754+
false,
755+
),
756+
// Valid
757+
(
758+
"tile/3/5/x013/x004/x005/x003/x006/x007/005",
759+
true,
760+
PathElem::Data,
761+
true,
762+
),
763+
// Invalid: same as above, but not in canonical form
764+
("tile/3/5/x13/x4/5/x3/6/07/5", true, PathElem::Data, false),
765+
// Invalid: mismatched data path element
766+
(
767+
"tile/3/data/x013/x004/x005/x003/x006/x007/005",
768+
true,
769+
PathElem::Entries,
770+
false,
771+
),
772+
];
773+
774+
for (path, with_height, path_elem, valid) in tests {
775+
let result = Tile::from_path(path, with_height, path_elem);
776+
if valid {
777+
result.unwrap();
778+
} else {
779+
result.unwrap_err();
780+
}
781+
}
782+
}
783+
784+
#[test]
785+
fn test_tile_path() {
786+
let tile_paths = vec![
787+
("tile/4/0/001", Some(Tile::new(4, 0, 1, 16, None))),
788+
("tile/4/0/001.p/5", Some(Tile::new(4, 0, 1, 5, None))),
789+
(
790+
"tile/3/5/x123/x456/078",
791+
Some(Tile::new(3, 5, 123_456_078, 8, None)),
792+
),
793+
(
794+
"tile/3/5/x123/x456/078.p/2",
795+
Some(Tile::new(3, 5, 123_456_078, 2, None)),
796+
),
797+
(
798+
"tile/1/0/x003/x057/500",
799+
Some(Tile::new(1, 0, 3_057_500, 2, None)),
800+
),
801+
("tile/3/5/123/456/078", None),
802+
("tile/3/-1/123/456/078", None),
803+
(
804+
"tile/1/data/x003/x057/500",
805+
Some(Tile::new(1, 0, 3_057_500, 2, Some(PathElem::Data))),
806+
),
807+
];
808+
809+
for (path, want) in tile_paths {
810+
let got = Tile::from_path(path, true, PathElem::Data).ok();
811+
assert_eq!(want, got);
812+
if let Some(t) = want {
813+
assert_eq!(t.path(true), path);
814+
}
815+
}
690816
}
691817
}

crates/tlog_tiles/src/tlog.rs

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,6 @@ fn run_tree_proof(
703703
mod tests {
704704
use super::*;
705705
use crate::tile::{Tile, TileHashReader, TileReader};
706-
use crate::PathElem;
707706
use std::cell::Cell;
708707
use std::collections::HashMap;
709708

@@ -903,40 +902,6 @@ mod tests {
903902
}
904903
}
905904

906-
#[test]
907-
fn test_tile_path() {
908-
let tile_paths = vec![
909-
("tile/4/0/001", Some(Tile::new(4, 0, 1, 16, None))),
910-
("tile/4/0/001.p/5", Some(Tile::new(4, 0, 1, 5, None))),
911-
(
912-
"tile/3/5/x123/x456/078",
913-
Some(Tile::new(3, 5, 123_456_078, 8, None)),
914-
),
915-
(
916-
"tile/3/5/x123/x456/078.p/2",
917-
Some(Tile::new(3, 5, 123_456_078, 2, None)),
918-
),
919-
(
920-
"tile/1/0/x003/x057/500",
921-
Some(Tile::new(1, 0, 3_057_500, 2, None)),
922-
),
923-
("tile/3/5/123/456/078", None),
924-
("tile/3/-1/123/456/078", None),
925-
(
926-
"tile/1/data/x003/x057/500",
927-
Some(Tile::new(1, 0, 3_057_500, 2, Some(PathElem::Data))),
928-
),
929-
];
930-
931-
for (path, want) in tile_paths {
932-
let got = Tile::from_path(path).ok();
933-
assert_eq!(want, got);
934-
if let Some(t) = want {
935-
assert_eq!(t.path(), path);
936-
}
937-
}
938-
}
939-
940905
#[test]
941906
fn test_empty_tree() {
942907
let h = tree_hash(0, &TestHashStorage::new()).unwrap();

fuzz/fuzz_targets/fuzz_parse_tile_path.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
#![no_main]
1616

1717
use libfuzzer_sys::fuzz_target;
18-
use tlog_tiles::tile::Tile;
18+
use tlog_tiles::{tile::Tile, PathElem};
1919

2020
fuzz_target!(|data: &str| {
21-
let _ = Tile::from_path(data);
21+
let _ = Tile::from_path(data, true, PathElem::Data);
2222
});

0 commit comments

Comments
 (0)