pub const PATH_SEPARATOR: u8 = b'/';
pub const PATH_MAX_SIZE: usize = 250 - DURABLE_STORAGE_PREFIX_INNER.len();
const DURABLE_STORAGE_PREFIX_INNER: &[u8] = b"/durable";
pub const PATH_KERNEL_BOOT: RefPath = RefPath::assert_from(b"/kernel/boot.wasm");
pub unsafe trait Path: core::fmt::Debug + core::fmt::Display {
fn as_bytes(&self) -> &[u8];
fn as_ptr(&self) -> *const u8 {
self.as_bytes().as_ptr()
}
fn size(&self) -> usize {
let size = self.as_bytes().len();
debug_assert!(size <= PATH_MAX_SIZE);
size
}
fn len_steps(&self) -> usize {
self.as_bytes()
.iter()
.filter(|b| b == &&PATH_SEPARATOR)
.count()
}
}
#[derive(Copy, Eq, PartialEq, Clone, Debug)]
pub enum PathError {
PathEmpty,
PathTooLong,
InvalidStart,
InvalidEmptyStep,
InvalidByteInStep,
ReadOnly,
}
#[cfg(feature = "std")]
impl std::error::Error for PathError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
impl core::fmt::Display for PathError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::PathEmpty => write!(f, "PathError::PathEmpty"),
Self::PathTooLong => write!(f, "PathError::PathTooLong"),
Self::InvalidStart => write!(f, "PathError::InvalidStart"),
Self::InvalidEmptyStep => write!(f, "PathError::InvalidEmptyStep"),
Self::InvalidByteInStep => write!(f, "PathError::InvalidByteInStep"),
Self::ReadOnly => write!(f, "PathError:ReadOnly"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RefPath<'a> {
inner: &'a str,
}
impl<'a> RefPath<'a> {
pub const fn assert_from(path: &[u8]) -> RefPath {
assert_ok(validate_path(path));
RefPath {
inner: unsafe { core::str::from_utf8_unchecked(path) },
}
}
pub(crate) const fn assert_from_readonly(path: &[u8]) -> RefPath {
assert_ok(validate_path_internal(path));
RefPath {
inner: unsafe { core::str::from_utf8_unchecked(path) },
}
}
}
impl core::fmt::Display for RefPath<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.inner.fmt(f)
}
}
unsafe impl Path for RefPath<'_> {
fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
}
const fn is_allowed_step_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || byte == b'.' || byte == b'_' || byte == b'-'
}
const fn assert_ok(res: Result<(), PathError>) {
match res {
Err(PathError::PathEmpty) => panic!("Path must contain at least one empty step"),
Err(PathError::PathTooLong) => panic!("Path contained too many bytes"),
Err(PathError::InvalidStart) => panic!("Path must begin with a path separator"),
Err(PathError::InvalidEmptyStep) => panic!("Path steps must be non empty"),
Err(PathError::InvalidByteInStep) => {
panic!(
"Path step bytes must be ascii_alphanumeric or
one of the following symbols \"b'.'\" , \"b'_'\" , \"b'-'\""
)
}
Err(PathError::ReadOnly) => {
panic!(
"Path must not start by /readonly, this is a reserved
part of the storage. Uses the appropriate function to
look values in this storage."
)
}
Ok(()) => (),
}
}
const fn validate_path_internal(path: &[u8]) -> Result<(), PathError> {
match path {
[] => Err(PathError::PathEmpty),
[PATH_SEPARATOR] | [.., PATH_SEPARATOR] => Err(PathError::InvalidEmptyStep),
_ if path.len() > PATH_MAX_SIZE => Err(PathError::PathTooLong),
[PATH_SEPARATOR, ..] => {
let mut i = 1;
let size = path.len();
while i < size {
match (path[i - 1], path[i]) {
(PATH_SEPARATOR, PATH_SEPARATOR) => {
return Err(PathError::InvalidEmptyStep)
}
(_, PATH_SEPARATOR) => (),
(_, c) if !is_allowed_step_byte(c) => {
return Err(PathError::InvalidByteInStep)
}
_ => (),
}
i += 1;
}
Ok(())
}
_ => Err(PathError::InvalidStart),
}
}
const fn validate_path(path: &[u8]) -> Result<(), PathError> {
match validate_path_internal(path) {
Ok(()) => match path {
[PATH_SEPARATOR, b'r', b'e', b'a', b'd', b'o', b'n', b'l', b'y']
| [PATH_SEPARATOR, b'r', b'e', b'a', b'd', b'o', b'n', b'l', b'y', PATH_SEPARATOR, ..] => {
Err(PathError::ReadOnly)
}
_ => Ok(()),
},
Err(e) => Err(e),
}
}
impl<'a> TryFrom<&'a str> for RefPath<'a> {
type Error = PathError;
fn try_from(slice: &'a str) -> Result<RefPath, Self::Error> {
Self::try_from(slice.as_bytes())
}
}
impl<'a> TryFrom<&'a [u8]> for RefPath<'a> {
type Error = PathError;
fn try_from(slice: &'a [u8]) -> Result<RefPath, PathError> {
validate_path(slice)?;
let inner = unsafe { core::str::from_utf8_unchecked(slice) };
Ok(RefPath { inner })
}
}
#[cfg(feature = "alloc")]
pub use owned::*;
#[cfg(feature = "alloc")]
mod owned {
use super::{validate_path, Path, PathError, RefPath};
use crate::path::PATH_MAX_SIZE;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use tezos_data_encoding::enc::{put_bytes, BinResult, BinWriter};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OwnedPath {
inner: String,
}
impl OwnedPath {
pub unsafe fn from_bytes_unchecked(bytes: Vec<u8>) -> Self {
Self {
inner: String::from_utf8_unchecked(bytes),
}
}
}
unsafe impl Path for OwnedPath {
fn as_bytes(&self) -> &[u8] {
self.inner.as_bytes()
}
}
impl<'a> From<RefPath<'a>> for OwnedPath {
fn from(path: RefPath<'a>) -> Self {
Self {
inner: path.inner.to_string(),
}
}
}
impl<P: Path> From<&P> for OwnedPath {
fn from(path: &P) -> Self {
let path_bytes = path.as_bytes().to_vec();
let inner = unsafe { String::from_utf8_unchecked(path_bytes) };
Self { inner }
}
}
impl<'a> From<&'a OwnedPath> for RefPath<'a> {
fn from(path: &'a OwnedPath) -> Self {
Self { inner: &path.inner }
}
}
impl TryFrom<String> for OwnedPath {
type Error = PathError;
fn try_from(inner: String) -> Result<Self, Self::Error> {
validate_path(inner.as_bytes())?;
Ok(OwnedPath { inner })
}
}
impl TryFrom<Vec<u8>> for OwnedPath {
type Error = PathError;
fn try_from(bytes: Vec<u8>) -> Result<OwnedPath, PathError> {
validate_path(&bytes)?;
let inner = unsafe { String::from_utf8_unchecked(bytes) };
Ok(OwnedPath { inner })
}
}
impl core::fmt::Display for OwnedPath {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
self.inner.fmt(f)
}
}
pub fn concat(
prefix: &impl Path,
suffix: &impl Path,
) -> Result<OwnedPath, PathError> {
let mut bytes = Vec::with_capacity(prefix.size() + suffix.size());
bytes.extend_from_slice(prefix.as_bytes());
bytes.extend_from_slice(suffix.as_bytes());
if bytes.len() <= PATH_MAX_SIZE {
Ok(unsafe { OwnedPath::from_bytes_unchecked(bytes) })
} else {
Err(PathError::PathTooLong)
}
}
impl<'a> BinWriter for RefPath<'a> {
fn bin_write(&self, output: &mut Vec<u8>) -> BinResult {
let data = self.inner;
put_bytes(data.as_bytes(), output);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::{vec, vec::Vec};
#[test]
fn from_slice_path_cannot_be_empty() {
let empty: Vec<u8> = vec![];
let result = OwnedPath::try_from(empty);
assert_eq!(Err(PathError::PathEmpty), result);
}
#[test]
fn from_slice_path_err_on_sep_not_first_byte() {
for v in u8::MIN..=u8::MAX {
match v {
PATH_SEPARATOR => continue,
v => {
let path = vec![v, b'r', b'e', b's', b't'];
let result = OwnedPath::try_from(path);
assert_eq!(Err(PathError::InvalidStart), result);
}
}
}
}
#[test]
fn from_slice_path_err_on_sep_last_byte() {
let path: Vec<u8> = vec![PATH_SEPARATOR, b'p', b'a', b't', b'h', PATH_SEPARATOR];
let result = OwnedPath::try_from(path);
assert_eq!(Err(PathError::InvalidEmptyStep), result);
}
#[test]
fn from_slice_err_on_duplicate_separator() {
let path = "/this/path/is/completely/fine/except/for/the//in/the/middle/of/it";
let result: Result<RefPath, PathError> = path.as_bytes().try_into();
assert_eq!(Err(PathError::InvalidEmptyStep), result);
}
#[test]
fn from_slice_too_long() {
let bytes = [b'i'; PATH_MAX_SIZE - 1];
let mut path = vec![PATH_SEPARATOR];
path.extend_from_slice(&bytes);
let result: Result<RefPath, _> = path.as_slice().try_into();
assert!(result.is_ok());
path.extend_from_slice(&bytes[0..1]);
let result: Result<RefPath, _> = path.as_slice().try_into();
assert_eq!(Err(PathError::PathTooLong), result);
}
#[test]
fn store_path_readonly() {
let path = "/readonly/this/path/is/read/only";
let result: Result<RefPath, PathError> = path.as_bytes().try_into();
assert_eq!(Err(PathError::ReadOnly), result);
let path = "/readonly";
let result: Result<RefPath, PathError> = path.as_bytes().try_into();
assert_eq!(Err(PathError::ReadOnly), result);
let path = "/readonly.is/a/correct_path";
let result: Result<RefPath, PathError> = path.as_bytes().try_into();
assert!(result.is_ok());
}
#[test]
fn from_slice_ok() {
let path = "/Th1s/PATH/1s/absolut3Ly/f1ne/and/sh0u.ld/.../work";
let result = RefPath::try_from(path.as_bytes());
let expected = RefPath { inner: path };
assert_eq!(Ok(expected), result);
}
#[test]
fn store_path_get_len_steps() {
let path = "/this/path/is/absolutely/fine/and/should.must/work";
let result = RefPath::try_from(path.as_bytes()).unwrap();
assert_eq!(8, result.len_steps());
}
#[test]
fn test_concat() {
let p1 = RefPath::assert_from(b"/a/b");
let p2 = RefPath::assert_from(b"/c/d");
let p3 = concat(&p1, &p2).unwrap();
assert_eq!(b"/a/b/c/d", p3.as_bytes());
}
#[test]
fn test_ownedpath_display_roundtrip() {
let p1 = OwnedPath::try_from("/hello".to_string()).unwrap();
assert_eq!(p1.to_string(), "/hello");
}
#[test]
fn test_refpath_display_roundtrip() {
let p1 = RefPath::assert_from(b"/test/path");
assert_eq!(p1.to_string(), "/test/path");
}
}