Skip to content

Commit

Permalink
feat: deeper in the attribute encoding and decoding on a filesystem, …
Browse files Browse the repository at this point in the history
…pretty close to that being complete
  • Loading branch information
sstelfox committed Feb 11, 2024
1 parent 3762a03 commit 56d9499
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 9 deletions.
27 changes: 27 additions & 0 deletions src/codec/actor_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use async_trait::async_trait;
use futures::AsyncWrite;

use crate::codec::crypto::Fingerprint;
use crate::codec::AsyncEncodable;

// todo(sstelfox) likely need a vector clock here...
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ActorId(Fingerprint);

impl ActorId {
pub fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
let (remaining, fingerprint) = Fingerprint::parse(input)?;
Ok((remaining, ActorId(fingerprint)))
}
}

#[async_trait]
impl AsyncEncodable for ActorId {
async fn encode<W: AsyncWrite + Unpin + Send>(
&self,
writer: &mut W,
pos: usize,
) -> std::io::Result<usize> {
self.0.encode(writer, pos).await
}
}
6 changes: 6 additions & 0 deletions src/codec/cid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ impl Cid {
}
}

impl From<[u8; CID_LENGTH]> for Cid {
fn from(bytes: [u8; CID_LENGTH]) -> Self {
Self(bytes)
}
}

#[async_trait]
impl AsyncEncodable for Cid {
async fn encode<W: AsyncWrite + Unpin + Send>(
Expand Down
29 changes: 28 additions & 1 deletion src/codec/crypto/fingerprint.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
use async_trait::async_trait;
use futures::{AsyncWrite, AsyncWriteExt};
use nom::bytes::streaming::take;

use crate::codec::crypto::{KeyId, VerifyingKey};
use crate::codec::AsyncEncodable;

const FINGERPRINT_SIZE: usize = 32;

#[derive(Clone, Copy, PartialEq)]
pub struct Fingerprint([u8; FINGERPRINT_SIZE]);

impl Fingerprint {
pub(crate) fn key_id(&self) -> KeyId {
pub fn key_id(&self) -> KeyId {
let mut key_id = [0u8; 2];
key_id.copy_from_slice(&self.0[..2]);
KeyId::from(u16::from_le_bytes(key_id))
}

pub fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
let (remaining, id_bytes) = take(FINGERPRINT_SIZE)(input)?;

let mut bytes = [0u8; FINGERPRINT_SIZE];
bytes.copy_from_slice(id_bytes);

Ok((remaining, Self(bytes)))
}
}

#[async_trait]
impl AsyncEncodable for Fingerprint {
async fn encode<W: AsyncWrite + Unpin + Send>(
&self,
writer: &mut W,
pos: usize,
) -> std::io::Result<usize> {
writer.write_all(&self.0).await?;
Ok(pos + self.0.len())
}
}

impl std::fmt::Debug for Fingerprint {
Expand Down
172 changes: 172 additions & 0 deletions src/codec/filesystem/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use async_trait::async_trait;
use futures::{AsyncWrite, AsyncWriteExt};
use nom::bytes::streaming::take;
use nom::error::{Error as NomError, ErrorKind};
use nom::number::streaming::{le_u64, le_u8};
use nom::IResult;
use time::OffsetDateTime;

use crate::codec::filesystem::Permissions;
use crate::codec::ActorId;
use crate::codec::AsyncEncodable;

const ATTRIBUTE_CUSTOM_TYPE_ID: u8 = 0x00;

const ATTRIBUTE_OWNER_TYPE_ID: u8 = 0x01;

const ATTRIBUTE_PERMISSIONS_TYPE_ID: u8 = 0x02;

const ATTRIBUTE_CREATED_AT_TYPE_ID: u8 = 0x03;

const ATTRIBUTE_MODIFIED_AT_TYPE_ID: u8 = 0x04;

const ATTRIBUTE_MIME_TYPE_TYPE_ID: u8 = 0x05;

pub enum Attribute {
// Note: key and value both must encode to fewer than 255 bytes each
Custom { key: String, value: String },

Owner(ActorId),
Permissions(Permissions),

CreatedAt(OffsetDateTime),
ModifiedAt(OffsetDateTime),

MimeType(String),
}

impl Attribute {
pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
let (remaining, type_byte) = le_u8(input)?;

let parsed = match type_byte {
ATTRIBUTE_CUSTOM_TYPE_ID => {
let (remaining, (key_len, value_len)) =
nom::sequence::pair(le_u8, le_u8)(remaining)?;

let (remaining, key_bytes) = take(key_len)(remaining)?;
let key = String::from_utf8(key_bytes.to_vec())
.map_err(|_| nom::Err::Failure(NomError::new(input, ErrorKind::Verify)))?;

let (remaining, value_bytes) = take(value_len)(remaining)?;
let value = String::from_utf8(value_bytes.to_vec())
.map_err(|_| nom::Err::Failure(NomError::new(input, ErrorKind::Verify)))?;

(remaining, Self::Custom { key, value })
}
ATTRIBUTE_OWNER_TYPE_ID => {
let (remaining, actor_id) = ActorId::parse(remaining)?;
(remaining, Self::Owner(actor_id))
}
ATTRIBUTE_PERMISSIONS_TYPE_ID => {
let (remaining, permissions) = Permissions::parse(remaining)?;
(remaining, Self::Permissions(permissions))
}
ATTRIBUTE_CREATED_AT_TYPE_ID => {
let (remaining, unix_milliseconds) = le_u64(remaining)?;

let unix_nanos = unix_milliseconds as i128 * 1_000_000;
let time = OffsetDateTime::from_unix_timestamp_nanos(unix_nanos)
.map_err(|_| nom::Err::Failure(NomError::new(input, ErrorKind::Verify)))?;

(remaining, Self::CreatedAt(time))
}
ATTRIBUTE_MODIFIED_AT_TYPE_ID => {
let (remaining, unix_milliseconds) = le_u64(remaining)?;

let unix_nanos = unix_milliseconds as i128 * 1_000_000;
let time = OffsetDateTime::from_unix_timestamp_nanos(unix_nanos)
.map_err(|_| nom::Err::Failure(NomError::new(input, ErrorKind::Verify)))?;

(remaining, Self::ModifiedAt(time))
}
ATTRIBUTE_MIME_TYPE_TYPE_ID => {
let (remaining, mime_len) = le_u8(remaining)?;

let (remaining, mime_bytes) = take(mime_len)(remaining)?;
let mime = String::from_utf8(mime_bytes.to_vec())
.map_err(|_| nom::Err::Failure(NomError::new(input, ErrorKind::Verify)))?;

(remaining, Self::MimeType(mime))
}
_ => return Err(nom::Err::Failure(NomError::new(input, ErrorKind::Tag))),
};

Ok(parsed)
}
}

#[async_trait]
impl AsyncEncodable for Attribute {
async fn encode<W: AsyncWrite + Unpin + Send>(
&self,
writer: &mut W,
pos: usize,
) -> std::io::Result<usize> {
match self {
Self::Custom { key, value } => {
let key_bytes = key.as_bytes();
let key_len = key_bytes.len();

let value_bytes = value.as_bytes();
let value_len = value_bytes.len();

if key_len > 255 || value_len > 255 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"attribute key or value longer than 255 bytes when encoded",
));
}

writer.write_all(&[ATTRIBUTE_CUSTOM_TYPE_ID]).await?;
writer.write_all(&[key_len as u8, value_len as u8]).await?;
writer.write_all(key_bytes).await?;
writer.write_all(value_bytes).await?;

Ok(pos + 1 + 2 + key_len + value_len)
}
Self::Owner(actor_id) => {
writer.write_all(&[ATTRIBUTE_OWNER_TYPE_ID]).await?;
actor_id.encode(writer, pos + 1).await
}
Self::Permissions(permissions) => {
writer.write_all(&[ATTRIBUTE_PERMISSIONS_TYPE_ID]).await?;
permissions.encode(writer, pos + 1).await
}
Self::CreatedAt(time) => {
writer.write_all(&[ATTRIBUTE_CREATED_AT_TYPE_ID]).await?;

let unix_milliseconds: u64 = (time.unix_timestamp_nanos() / 1_000_000) as u64;
let ts_bytes = unix_milliseconds.to_le_bytes();
writer.write_all(&ts_bytes).await?;

Ok(pos + 1 + 8)
}
Self::ModifiedAt(time) => {
writer.write_all(&[ATTRIBUTE_MODIFIED_AT_TYPE_ID]).await?;

let unix_milliseconds: u64 = (time.unix_timestamp_nanos() / 1_000_000) as u64;
let ts_bytes = unix_milliseconds.to_le_bytes();
writer.write_all(&ts_bytes).await?;

Ok(pos + 1 + 8)
}
Self::MimeType(mime) => {
let mime_bytes = mime.as_bytes();
let mime_len = mime_bytes.len();

if mime_len > 255 {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"mime type longer than 255 bytes when encoded",
));
}

writer.write_all(&[ATTRIBUTE_MIME_TYPE_TYPE_ID]).await?;
writer.write_all(mime_bytes).await?;

Ok(pos + 1 + mime_len)
}
}
}
}
2 changes: 2 additions & 0 deletions src/codec/filesystem/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
mod attribute;
mod node_type;
mod permissions;

pub use attribute::Attribute;
pub use node_type::NodeType;
pub use permissions::Permissions;
2 changes: 2 additions & 0 deletions src/codec/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod actor_id;
mod cid;
pub mod content_payload;
pub mod crypto;
Expand All @@ -8,6 +9,7 @@ pub mod header;
use async_trait::async_trait;
use futures::AsyncWrite;

pub use actor_id::ActorId;
pub use cid::Cid;
pub use filesystem_id::FilesystemId;

Expand Down
41 changes: 40 additions & 1 deletion src/filesystem/content_reference.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
use crate::codec::Cid;
use async_trait::async_trait;
use futures::{AsyncWrite, AsyncWriteExt};
use nom::number::streaming::le_u32;

use crate::codec::{AsyncEncodable, Cid};

pub struct ContentReference {
data_block_cid: Cid,
offset: u32,
length: u32,
}

impl ContentReference {
pub fn parse(input: &[u8]) -> nom::IResult<&[u8], Self> {
let (remaining, data_block_cid) = Cid::parse(input)?;

let (remaining, offset) = le_u32(remaining)?;
let (remaining, length) = le_u32(remaining)?;

let content_reference = Self {
data_block_cid,
offset,
length,
};

Ok((remaining, content_reference))
}
}

#[async_trait]
impl AsyncEncodable for ContentReference {
async fn encode<W: AsyncWrite + Unpin + Send>(
&self,
writer: &mut W,
pos: usize,
) -> std::io::Result<usize> {
let pos = self.data_block_cid.encode(writer, pos).await?;

writer.write_all(&self.offset.to_le_bytes()).await?;
writer.write_all(&self.length.to_le_bytes()).await?;

Ok(pos + 8)
}
}
2 changes: 0 additions & 2 deletions src/filesystem/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ pub use nodes::*;
use crate::codec::crypto::SigningKey;
use crate::codec::FilesystemId;

pub type ActorId = u16;

pub struct Drive {
_filesystem_id: FilesystemId,
_root: DriveDirectory,
Expand Down
Loading

0 comments on commit 56d9499

Please sign in to comment.