Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: handle nonce as incoming string/arraybuf #611

Merged
merged 3 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ private
.DS_Store
homestar-functions/**/out/*.png
homestar-wasm/out
homestar-invocation/test_*
homestar-runtime/fixtures/test_*
homestar-invocation/test*
homestar-runtime/fixtures/test*
homestar-runtime/tests/fixtures/*.toml
homestar-workflow/fixtures/test_*
homestar-workflow/fixtures/test*
.zed
result-alejandra
report.json
Expand Down
17 changes: 4 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions homestar-invocation/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub enum Error<T> {
/// `Display` methods through to an underlying error.
#[error("cannot convert from Ipld structure: {0}")]
FromIpld(#[from] libipld::error::SerdeError),
/// Error with a [libipld::multibase] encoding/decoding.
#[error("failed to decode/encode structure: {0}")]
FromMultibase(#[from] libipld::multibase::Error),
/// Invalid match discriminant or enumeration.
#[error("invalid discriminant {0:#?}")]
InvalidDiscriminant(T),
Expand Down
46 changes: 45 additions & 1 deletion homestar-invocation/src/ipld/dag_cbor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
use crate::{consts::DAG_CBOR, Error, Unit};
use libipld::{
cbor::DagCborCodec,
json::DagJsonCodec,
multihash::{Code, MultihashDigest},
prelude::Codec,
prelude::{Codec, Decode},
Cid, Ipld,
};
use std::{
fs,
io::{Cursor, Write},
};

/// Trait for DagCbor-related encode/decode.
pub trait DagCbor
Expand All @@ -21,6 +26,45 @@ where
let hash = Code::Sha3_256.digest(&bytes);
Ok(Cid::new_v1(DAG_CBOR, hash))
}

/// Serialize `Self` to JSON bytes.
fn to_dag_json(self) -> Result<Vec<u8>, Error<Unit>> {
let ipld: Ipld = self.into();
Ok(DagJsonCodec.encode(&ipld)?)
}

/// Serialize `Self` to JSON [String].
fn to_dagjson_string(self) -> Result<String, Error<Unit>> {
let encoded = self.to_dag_json()?;
// JSON spec requires UTF-8 support
let s = std::str::from_utf8(&encoded)?;
Ok(s.to_string())
}

/// Serialize `Self` to CBOR bytes.
fn to_cbor(self) -> Result<Vec<u8>, Error<Unit>> {
let ipld: Ipld = self.into();
Ok(DagCborCodec.encode(&ipld)?)
}

/// Deserialize `Self` from CBOR bytes.
fn from_cbor(data: &[u8]) -> Result<Self, Error<Unit>>
where
Self: TryFrom<Ipld>,
{
let ipld = Ipld::decode(DagCborCodec, &mut Cursor::new(data))?;
let from_ipld = Self::try_from(ipld).map_err(|_err| {
Error::<Unit>::UnexpectedIpldType(Ipld::String(
"Failed to convert Ipld to expected type".to_string(),
))
})?;
Ok(from_ipld)
}

/// Serialize `Self` to a CBOR file.
fn to_cbor_file(self, filename: String) -> Result<(), Error<Unit>> {
Ok(fs::File::create(filename)?.write_all(&self.to_cbor()?)?)
}
}

/// Trait for DagCbor-related encode/decode for references.
Expand Down
145 changes: 126 additions & 19 deletions homestar-invocation/src/task/instruction/nonce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use generic_array::{
use libipld::{multibase::Base::Base32HexLower, Ipld};
use schemars::{
gen::SchemaGenerator,
schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec},
schema::{InstanceType, Metadata, Schema, SchemaObject, SingleOrVec, StringValidation},
JsonSchema,
};
use serde::{Deserialize, Serialize};
Expand All @@ -23,13 +23,22 @@ use uuid::Uuid;
type Nonce96 = GenericArray<u8, U12>;
type Nonce128 = GenericArray<u8, U16>;

/// Incoming type for nonce conversion.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum IncomingTyp {
/// Nonce incoming as a string.
String,
/// Nonce incoming as bytes.
Bytes,
}

/// Enumeration over allowed `nonce` types.
#[derive(Clone, Debug, PartialEq, EnumAsInner, Serialize, Deserialize)]
pub enum Nonce {
/// 96-bit, 12-byte nonce, e.g. [xid].
Nonce96(Nonce96),
Nonce96(Nonce96, IncomingTyp),
/// 128-bit, 16-byte nonce.
Nonce128(Nonce128),
Nonce128(Nonce128, IncomingTyp),
/// No Nonce attributed.
Empty,
}
Expand All @@ -38,22 +47,37 @@ impl Nonce {
/// Default generator, outputting a [xid] nonce, which is a 96-bit, 12-byte
/// nonce.
pub fn generate() -> Self {
Nonce::Nonce96(*GenericArray::from_slice(xid::new().as_bytes()))
Nonce::Nonce96(
*GenericArray::from_slice(xid::new().as_bytes()),
IncomingTyp::Bytes,
)
}

/// Generate a default, 128-bit, 16-byte nonce via [Uuid::new_v4()].
pub fn generate_128() -> Self {
Nonce::Nonce128(*GenericArray::from_slice(Uuid::new_v4().as_bytes()))
Nonce::Nonce128(
*GenericArray::from_slice(Uuid::new_v4().as_bytes()),
IncomingTyp::Bytes,
)
}

/// Convert the nonce to a byte vector.
pub fn to_vec(&self) -> Vec<u8> {
match self {
Nonce::Nonce96(nonce, _) => nonce.to_vec(),
Nonce::Nonce128(nonce, _) => nonce.to_vec(),
Nonce::Empty => vec![],
}
}
}

impl fmt::Display for Nonce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Nonce::Nonce96(nonce) => {
Nonce::Nonce96(nonce, _) => {
write!(f, "{}", Base32HexLower.encode(nonce.as_slice()))
}
Nonce::Nonce128(nonce) => {
Nonce::Nonce128(nonce, _) => {
write!(f, "{}", Base32HexLower.encode(nonce.as_slice()))
}
Nonce::Empty => write!(f, ""),
Expand All @@ -64,8 +88,20 @@ impl fmt::Display for Nonce {
impl From<Nonce> for Ipld {
fn from(nonce: Nonce) -> Self {
match nonce {
Nonce::Nonce96(nonce) => Ipld::Bytes(nonce.to_vec()),
Nonce::Nonce128(nonce) => Ipld::Bytes(nonce.to_vec()),
Nonce::Nonce96(nonce, typ) => {
if let IncomingTyp::Bytes = typ {
Ipld::Bytes(nonce.to_vec())
} else {
Ipld::String(Base32HexLower.encode(nonce.as_slice()))
}
}
Nonce::Nonce128(nonce, typ) => {
if let IncomingTyp::Bytes = typ {
Ipld::Bytes(nonce.to_vec())
} else {
Ipld::String(Base32HexLower.encode(nonce.as_slice()))
}
}
Nonce::Empty => Ipld::String("".to_string()),
}
}
Expand All @@ -75,14 +111,37 @@ impl TryFrom<Ipld> for Nonce {
type Error = Error<Unit>;

fn try_from(ipld: Ipld) -> Result<Self, Self::Error> {
if let Ipld::Bytes(v) = ipld {
match v.len() {
12 => Ok(Nonce::Nonce96(*GenericArray::from_slice(&v))),
16 => Ok(Nonce::Nonce128(*GenericArray::from_slice(&v))),
other_ipld => Err(Error::unexpected_ipld(other_ipld.to_owned().into())),
match ipld {
Ipld::String(s) if s.is_empty() => Ok(Nonce::Empty),
Ipld::String(s) => {
let bytes = Base32HexLower.decode(s)?;
match bytes.len() {
12 => Ok(Nonce::Nonce96(
*GenericArray::from_slice(&bytes),
IncomingTyp::String,
)),
16 => Ok(Nonce::Nonce128(
*GenericArray::from_slice(&bytes),
IncomingTyp::String,
)),
other => Err(Error::unexpected_ipld(other.to_owned().into())),
}
}
} else {
Ok(Nonce::Empty)
Ipld::Bytes(v) => match v.len() {
12 => Ok(Nonce::Nonce96(
*GenericArray::from_slice(&v),
IncomingTyp::Bytes,
)),
16 => Ok(Nonce::Nonce128(
*GenericArray::from_slice(&v),
IncomingTyp::Bytes,
)),
other_ipld => {
println!("other_ipld: {:?}", v.len());
Err(Error::unexpected_ipld(other_ipld.to_owned().into()))
}
},
_ => Ok(Nonce::Empty),
}
}
}
Expand Down Expand Up @@ -122,9 +181,23 @@ impl JsonSchema for Nonce {
..Default::default()
};

let non_empty_string = SchemaObject {
instance_type: Some(SingleOrVec::Single(InstanceType::String.into())),
metadata: Some(Box::new(Metadata {
description: Some("A 12-byte or 16-byte nonce encoded as a string, which expects to be decoded with Base32hex lower".to_string()),
..Default::default()
})),
string: Some(Box::new(StringValidation {
min_length: Some(1),
..Default::default()
})),
..Default::default()
};

schema.subschemas().one_of = Some(vec![
gen.subschema_for::<schema::IpldBytesStub>(),
Schema::Object(empty_string),
Schema::Object(non_empty_string),
]);

schema.into()
Expand All @@ -141,7 +214,7 @@ mod test {
let gen = Nonce::generate();
let ipld = Ipld::from(gen.clone());

let inner = if let Nonce::Nonce96(nonce) = gen {
let inner = if let Nonce::Nonce96(nonce, _) = gen {
Ipld::Bytes(nonce.to_vec())
} else {
panic!("No conversion!")
Expand All @@ -156,7 +229,7 @@ mod test {
let gen = Nonce::generate_128();
let ipld = Ipld::from(gen.clone());

let inner = if let Nonce::Nonce128(nonce) = gen {
let inner = if let Nonce::Nonce128(nonce, _) = gen {
Ipld::Bytes(nonce.to_vec())
} else {
panic!("No conversion!")
Expand Down Expand Up @@ -192,11 +265,45 @@ mod test {

assert_eq!(bytes, b);
assert_eq!(ipld, Ipld::Bytes(b.to_vec()));
assert_eq!(nonce, Nonce::Nonce128(*GenericArray::from_slice(b)));
assert_eq!(
nonce,
Nonce::Nonce128(*GenericArray::from_slice(b), IncomingTyp::Bytes)
);
assert_eq!(nonce, Nonce::try_from(ipld.clone()).unwrap());

let nonce: Nonce = ipld.clone().try_into().unwrap();
let ipld = Ipld::from(nonce.clone());
assert_eq!(ipld, Ipld::Bytes(b.to_vec()));
}

#[test]
fn nonce_as_string_roundtrip() {
let nonce = Nonce::generate();
let string = nonce.to_string();
let from_string = Nonce::try_from(Ipld::String(string.clone())).unwrap();

assert_eq!(nonce.to_vec(), from_string.to_vec());
assert_eq!(string, nonce.to_string());
}

#[test]
fn json_nonce_string_roundtrip() {
let in_nnc = "1sod60ml6g26mfhsrsa0";
let json = json!({
"nnc": in_nnc
});

let ipld: Ipld = DagJsonCodec.decode(json.to_string().as_bytes()).unwrap();
let Ipld::Map(map) = ipld else {
panic!("IPLD is not a map");
};
let nnc = map.get("nnc").unwrap();
let nnc: Nonce = Nonce::try_from(nnc.clone()).unwrap();
assert_eq!(nnc.to_string(), in_nnc);
let nonce = Nonce::Nonce96(
*GenericArray::from_slice(Base32HexLower.decode(in_nnc).unwrap().as_slice()),
IncomingTyp::String,
);
assert_eq!(nnc, nonce);
}
}
4 changes: 2 additions & 2 deletions homestar-invocation/src/test_utils/invocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn wasm_instruction_with_nonce<'a, T>() -> (Instruction<'a, T>, NonceBytes)
]))),
nonce.clone(),
),
nonce.as_nonce96().unwrap().to_vec(),
nonce.to_vec(),
)
}

Expand All @@ -139,7 +139,7 @@ pub fn instruction_with_nonce<'a, T>() -> (Instruction<'a, T>, NonceBytes) {
Input::Ipld(Ipld::List(vec![Ipld::Bool(true)])),
nonce.clone(),
),
nonce.as_nonce96().unwrap().to_vec(),
nonce.to_vec(),
)
}

Expand Down
Loading
Loading