|
@@ -1,6 +1,8 @@
|
|
|
use std::{
|
|
|
collections::HashMap,
|
|
|
+ convert::TryFrom,
|
|
|
hash::Hash as Hashable,
|
|
|
+ fmt::{self, Display, Formatter},
|
|
|
};
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
use serde_big_array::BigArray;
|
|
@@ -96,6 +98,18 @@ struct FragmentRecord {
|
|
|
stored_by: Principal,
|
|
|
}
|
|
|
|
|
|
+impl FragmentRecord {
|
|
|
+ /// Creates a new `FragmentRecord` whose `serial` and `stored_by` fields are set to
|
|
|
+ /// the given values.
|
|
|
+ #[allow(dead_code)]
|
|
|
+ fn new(serial: u32, stored_by: Hash) -> FragmentRecord {
|
|
|
+ FragmentRecord {
|
|
|
+ serial: FragmentSerial(serial),
|
|
|
+ stored_by: Principal(stored_by),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// An identifier for a security principal, which is any entity that can be authenticated.
|
|
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Hashable)]
|
|
|
struct Principal(Hash);
|
|
@@ -108,6 +122,109 @@ struct Ciphertext<T>(T);
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
struct Path(Vec<String>);
|
|
|
|
|
|
+impl Path {
|
|
|
+ /// The character that is used to separate path components.
|
|
|
+ const SEP: char = '/';
|
|
|
+ /// The limit, in bytes, of a `Path`'s length.
|
|
|
+ const BYTE_LIMIT: usize = 4096;
|
|
|
+}
|
|
|
+
|
|
|
+/// Errors which can occur when converting a string to a `Path`.
|
|
|
+#[derive(Debug, PartialEq)]
|
|
|
+enum PathError {
|
|
|
+ /// Occurs when the number of bytes in a string is greater than `Path::BYTE_LIMIT`.
|
|
|
+ PathTooLong(usize),
|
|
|
+ /// Indicates that a path string was empty.
|
|
|
+ Empty,
|
|
|
+ /// Occurs when a component in a path string was empty.
|
|
|
+ EmptyComponent,
|
|
|
+}
|
|
|
+
|
|
|
+impl Display for PathError {
|
|
|
+ fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
|
|
+ match self {
|
|
|
+ PathError::PathTooLong(length) => formatter.write_fmt(format_args!(
|
|
|
+ "path contained {} bytes, which is over the {} byte limit",
|
|
|
+ length,
|
|
|
+ Path::BYTE_LIMIT)
|
|
|
+ ),
|
|
|
+ PathError::Empty => formatter.write_str("path was empty"),
|
|
|
+ PathError::EmptyComponent => formatter.write_str("component of path was empty"),
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl Path {
|
|
|
+ fn consume_component<I: Iterator<Item = (usize, char)>>(
|
|
|
+ start: usize, first: char, pairs: &mut I
|
|
|
+ ) -> Result<usize, PathError> {
|
|
|
+ if first == Path::SEP {
|
|
|
+ return Err(PathError::EmptyComponent);
|
|
|
+ }
|
|
|
+ let end;
|
|
|
+ let mut last = start;
|
|
|
+ loop {
|
|
|
+ match pairs.next() {
|
|
|
+ Some((index, Path::SEP)) => {
|
|
|
+ end = index;
|
|
|
+ break;
|
|
|
+ },
|
|
|
+ Some((index, _)) => last = index,
|
|
|
+ None => {
|
|
|
+ end = last + 1;
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if end == start {
|
|
|
+ Err(PathError::EmptyComponent)
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ Ok(end)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn trim_leading_sep(mut string: &str) -> Result<&str, PathError> {
|
|
|
+ let mut pairs = string.char_indices();
|
|
|
+ string = match pairs.next() {
|
|
|
+ Some((_, Path::SEP)) => {
|
|
|
+ match pairs.next() {
|
|
|
+ Some((index, _)) => &string[index..],
|
|
|
+ None => return Err(PathError::Empty)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Some((_, _)) => string,
|
|
|
+ None => return Err(PathError::Empty)
|
|
|
+ };
|
|
|
+ Ok(string)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'s> TryFrom<&'s str> for Path {
|
|
|
+ type Error = PathError;
|
|
|
+
|
|
|
+ fn try_from(mut string: &'s str) -> Result<Path, PathError> {
|
|
|
+ {
|
|
|
+ let len = string.len();
|
|
|
+ if len >= Path::BYTE_LIMIT {
|
|
|
+ return Err(PathError::PathTooLong(len))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ string = Path::trim_leading_sep(string)?;
|
|
|
+ let mut pairs = string.char_indices();
|
|
|
+ let mut components = Vec::new();
|
|
|
+ loop {
|
|
|
+ let (start, end) = match pairs.next() {
|
|
|
+ Some((start, c)) => (start, Path::consume_component(start, c, &mut pairs)),
|
|
|
+ None => break
|
|
|
+ };
|
|
|
+ let slice = &string[start..end?];
|
|
|
+ components.push(slice.to_string());
|
|
|
+ }
|
|
|
+ Ok(Path(components))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// An instant in time represented by the number of seconds since January 1st 1970, 00:00:00 UTC.
|
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
|
struct Epoch(u64);
|
|
@@ -143,3 +260,54 @@ enum Key {
|
|
|
fn main() {
|
|
|
println!("Hello, world!");
|
|
|
}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ fn path_from_str_test_case(
|
|
|
+ expected: Result<Path, PathError>, input: &str
|
|
|
+ ) -> Result<(), PathError> {
|
|
|
+ let result = Path::try_from(input);
|
|
|
+ assert_eq!(expected, result);
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_multiple_components_ok() -> Result<(), PathError> {
|
|
|
+ let expected = Path(vec!["red".to_string(), "green".to_string(), "blue".to_string()]);
|
|
|
+ path_from_str_test_case(Ok(expected), "red/green/blue")?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_one_component_ok() -> Result<(), PathError> {
|
|
|
+ let expected = Path(vec!["red".to_string()]);
|
|
|
+ path_from_str_test_case(Ok(expected), "red")?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_leading_slash_ok() -> Result<(), PathError> {
|
|
|
+ let expected = Path(vec![
|
|
|
+ "orange".to_string(), "banana".to_string(), "shotgun".to_string()
|
|
|
+ ]);
|
|
|
+ path_from_str_test_case(Ok(expected), "/orange/banana/shotgun")?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_path_too_long_fail() -> Result<(), PathError> {
|
|
|
+ let expected = Err(PathError::PathTooLong(4097));
|
|
|
+ let input = "*".repeat(4097);
|
|
|
+ path_from_str_test_case(expected, input.as_str())?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn path_from_str_multiple_slashes_fail() -> Result<(), PathError> {
|
|
|
+ let expected = Err(PathError::EmptyComponent);
|
|
|
+ path_from_str_test_case(expected, "//orange")?;
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|