use alloc::{
format,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use miden_assembly_syntax::{
Library,
ast::{AttributeSet, PathBuf},
};
use miden_core::{
Word,
serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
};
use super::{ConstantExport, PackageId, ProcedureExport, TargetType, TypeExport};
use crate::{Dependency, Package, PackageExport, PackageManifest, Section};
const MAGIC_PACKAGE: &[u8; 5] = b"MASP\0";
const VERSION: [u8; 3] = [4, 0, 0];
impl Serializable for Package {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_bytes(MAGIC_PACKAGE);
target.write_bytes(&VERSION);
self.name.write_into(target);
self.version.to_string().write_into(target);
self.description.write_into(target);
target.write_u8(self.kind.into());
self.mast.write_into(target);
self.manifest.write_into(target);
target.write_usize(self.sections.len());
for section in self.sections.iter() {
section.write_into(target);
}
}
}
impl Deserializable for Package {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let magic: [u8; 5] = source.read_array()?;
if magic != *MAGIC_PACKAGE {
return Err(DeserializationError::InvalidValue(format!(
"invalid magic bytes. Expected '{MAGIC_PACKAGE:?}', got '{magic:?}'"
)));
}
let version: [u8; 3] = source.read_array()?;
if version != VERSION {
return Err(DeserializationError::InvalidValue(format!(
"unsupported version. Got '{version:?}', but only '{VERSION:?}' is supported"
)));
}
let name = PackageId::read_from(source)?;
let version = String::read_from(source)?
.parse::<crate::Version>()
.map_err(|err| DeserializationError::InvalidValue(err.to_string()))?;
let description = Option::<String>::read_from(source)?;
let kind_tag = source.read_u8()?;
let kind = TargetType::try_from(kind_tag)
.map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
let mast = Arc::new(Library::read_from(source)?);
let manifest = PackageManifest::read_from(source)?;
let sections = Vec::<Section>::read_from(source)?;
Ok(Self {
name,
version,
description,
kind,
mast,
manifest,
sections,
})
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for PackageManifest {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use alloc::collections::BTreeMap;
use miden_assembly_syntax::Path;
use serde::ser::SerializeStruct;
struct PackageExports<'a>(&'a BTreeMap<Arc<Path>, PackageExport>);
impl serde::Serialize for PackageExports<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
let mut serializer = serializer.serialize_seq(Some(self.0.len()))?;
for value in self.0.values() {
serializer.serialize_element(value)?;
}
serializer.end()
}
}
let mut serializer = serializer.serialize_struct("PackageManifest", 2)?;
serializer.serialize_field("exports", &PackageExports(&self.exports))?;
serializer.serialize_field("dependencies", &self.dependencies)?;
serializer.end()
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PackageManifest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
#[serde(field_identifier, rename_all = "lowercase")]
enum Field {
Exports,
Dependencies,
}
struct PackageManifestVisitor;
impl<'de> serde::de::Visitor<'de> for PackageManifestVisitor {
type Value = PackageManifest;
fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
formatter.write_str("struct PackageManifest")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let exports = seq
.next_element::<Vec<PackageExport>>()?
.ok_or_else(|| serde::de::Error::invalid_length(0, &self))?;
let dependencies = seq
.next_element::<Vec<Dependency>>()?
.ok_or_else(|| serde::de::Error::invalid_length(1, &self))?;
PackageManifest::new(exports)
.and_then(|manifest| manifest.with_dependencies(dependencies))
.map_err(serde::de::Error::custom)
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut exports = None;
let mut dependencies = None;
while let Some(key) = map.next_key()? {
match key {
Field::Exports => {
if exports.is_some() {
return Err(serde::de::Error::duplicate_field("exports"));
}
exports = Some(map.next_value::<Vec<PackageExport>>()?);
},
Field::Dependencies => {
if dependencies.is_some() {
return Err(serde::de::Error::duplicate_field("dependencies"));
}
dependencies = Some(map.next_value::<Vec<Dependency>>()?);
},
}
}
let exports = exports.ok_or_else(|| serde::de::Error::missing_field("exports"))?;
let dependencies =
dependencies.ok_or_else(|| serde::de::Error::missing_field("dependencies"))?;
PackageManifest::new(exports)
.and_then(|manifest| manifest.with_dependencies(dependencies))
.map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_struct(
"PackageManifest",
&["exports", "dependencies"],
PackageManifestVisitor,
)
}
}
impl Serializable for PackageManifest {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_usize(self.num_exports());
for export in self.exports() {
export.write_into(target);
}
target.write_usize(self.num_dependencies());
for dep in self.dependencies() {
dep.write_into(target);
}
}
}
impl Deserializable for PackageManifest {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let exports_len = source.read_usize()?;
let mut exports = Vec::with_capacity(exports_len);
for _ in 0..exports_len {
exports.push(PackageExport::read_from(source)?);
}
let dependencies = Vec::<Dependency>::read_from(source)?;
PackageManifest::new(exports)
.and_then(|manifest| manifest.with_dependencies(dependencies))
.map_err(|error| DeserializationError::InvalidValue(error.to_string()))
}
}
impl Serializable for PackageExport {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(self.tag());
match self {
Self::Procedure(export) => export.write_into(target),
Self::Constant(export) => export.write_into(target),
Self::Type(export) => export.write_into(target),
}
}
}
impl Deserializable for PackageExport {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
1 => ProcedureExport::read_from(source).map(Self::Procedure),
2 => ConstantExport::read_from(source).map(Self::Constant),
3 => TypeExport::read_from(source).map(Self::Type),
invalid => Err(DeserializationError::InvalidValue(format!(
"unexpected PackageExport tag: '{invalid}'"
))),
}
}
}
impl Serializable for ProcedureExport {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.path.write_into(target);
self.digest.write_into(target);
match self.signature.as_ref() {
Some(sig) => {
target.write_bool(true);
sig.write_into(target);
},
None => {
target.write_bool(false);
},
}
self.attributes.write_into(target);
}
}
impl Deserializable for ProcedureExport {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
use miden_assembly_syntax::ast::types::FunctionType;
let path = PathBuf::read_from(source)?.into_boxed_path().into();
let digest = Word::read_from(source)?;
let signature = if source.read_bool()? {
Some(FunctionType::read_from(source)?)
} else {
None
};
let attributes = AttributeSet::read_from(source)?;
Ok(Self { path, digest, signature, attributes })
}
}
impl Serializable for ConstantExport {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.path.write_into(target);
self.value.write_into(target);
}
}
impl Deserializable for ConstantExport {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let path = PathBuf::read_from(source)?.into_boxed_path().into();
let value = miden_assembly_syntax::ast::ConstantValue::read_from(source)?;
Ok(Self { path, value })
}
}
impl Serializable for TypeExport {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.path.write_into(target);
self.ty.write_into(target);
}
}
impl Deserializable for TypeExport {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
use miden_assembly_syntax::ast::types::Type;
let path = PathBuf::read_from(source)?.into_boxed_path().into();
let ty = Type::read_from(source)?;
Ok(Self { path, ty })
}
}
#[cfg(test)]
mod tests {
use alloc::{collections::BTreeMap, string::ToString, sync::Arc, vec, vec::Vec};
use miden_assembly_syntax::{
Library,
ast::{AttributeSet, Path as AstPath, PathBuf},
library::{LibraryExport, ProcedureExport as LibraryProcedureExport},
};
use miden_core::{
mast::{BasicBlockNodeBuilder, MastForest, MastForestContributor, MastNodeExt, MastNodeId},
operations::Operation,
serde::{
BudgetedReader, ByteWriter, Deserializable, DeserializationError, Serializable,
SliceReader,
},
};
#[cfg(feature = "serde")]
use serde_json::{json, to_value};
use super::{MAGIC_PACKAGE, Package, PackageExport, PackageManifest, VERSION};
use crate::{
Dependency, ManifestValidationError, PackageId, TargetType,
package::manifest::ProcedureExport as PackageProcedureExport,
};
fn build_forest() -> (MastForest, MastNodeId) {
let mut forest = MastForest::new();
let node_id = BasicBlockNodeBuilder::new(vec![Operation::Add], Vec::new())
.add_to_forest(&mut forest)
.expect("failed to build basic block");
forest.make_root(node_id);
(forest, node_id)
}
fn absolute_path(name: &str) -> Arc<AstPath> {
let path = PathBuf::new(name).expect("invalid path");
let path = path.as_path().to_absolute().into_owned();
Arc::from(path.into_boxed_path())
}
fn build_library() -> Arc<Library> {
let (forest, node_id) = build_forest();
let path = absolute_path("test::proc");
let export = LibraryProcedureExport::new(node_id, Arc::clone(&path));
let mut exports = BTreeMap::new();
exports.insert(path, LibraryExport::Procedure(export));
Arc::new(Library::new(Arc::new(forest), exports).expect("failed to build library"))
}
fn build_package() -> Package {
let library = build_library();
let path = absolute_path("test::proc");
let node_id = library.get_export_node_id(path.as_ref());
let digest = library.mast_forest()[node_id].digest();
let export = PackageExport::Procedure(PackageProcedureExport {
path: Arc::clone(&path),
digest,
signature: None,
attributes: AttributeSet::default(),
});
let manifest =
PackageManifest::new([export]).expect("test package manifest should be valid");
Package {
name: PackageId::from("test_pkg"),
version: crate::Version::new(0, 0, 0),
description: None,
kind: TargetType::Library,
mast: library,
manifest,
sections: Vec::new(),
}
}
fn build_dependency() -> Dependency {
Dependency {
name: PackageId::from("dep"),
kind: TargetType::Library,
version: crate::Version::new(1, 0, 0),
digest: Default::default(),
}
}
fn package_bytes_with_sections_count(count: usize) -> Vec<u8> {
let package = build_package();
let mut bytes = Vec::new();
bytes.write_bytes(MAGIC_PACKAGE);
bytes.write_bytes(&VERSION);
package.name.write_into(&mut bytes);
package.version.to_string().write_into(&mut bytes);
package.description.write_into(&mut bytes);
bytes.write_u8(package.kind.into());
package.mast.write_into(&mut bytes);
package.manifest.write_into(&mut bytes);
bytes.write_usize(count);
bytes
}
#[test]
fn package_manifest_rejects_over_budget_dependencies() {
let mut bytes = Vec::new();
bytes.write_usize(0);
bytes.write_usize(2);
let mut reader = BudgetedReader::new(SliceReader::new(&bytes), 2);
let err = PackageManifest::read_from(&mut reader).unwrap_err();
assert!(matches!(err, DeserializationError::InvalidValue(_)));
}
#[test]
fn package_rejects_over_budget_sections() {
let bytes = package_bytes_with_sections_count(2);
let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
let err = Package::read_from(&mut reader).unwrap_err();
assert!(matches!(err, DeserializationError::InvalidValue(_)));
}
#[test]
fn package_manifest_new_rejects_duplicate_export_paths() {
let library = build_library();
let path = absolute_path("test::proc");
let node_id = library.get_export_node_id(path.as_ref());
let digest = library.mast_forest()[node_id].digest();
let export = PackageExport::Procedure(PackageProcedureExport {
path: path.clone(),
digest,
signature: None,
attributes: AttributeSet::default(),
});
let err = PackageManifest::new([export.clone(), export])
.expect_err("duplicate export paths should be rejected by constructors");
assert_eq!(err, ManifestValidationError::DuplicateExport(path));
}
#[test]
fn package_manifest_add_dependency_rejects_duplicate_dependencies() {
let mut manifest =
PackageManifest::new([]).expect("empty package manifest should be valid");
let dependency = build_dependency();
manifest
.add_dependency(dependency.clone())
.expect("first dependency should be accepted");
let err = manifest
.add_dependency(dependency)
.expect_err("duplicate dependencies should be rejected by helpers");
assert_eq!(err, ManifestValidationError::DuplicateDependency(PackageId::from("dep")));
}
#[test]
fn package_manifest_rejects_duplicate_export_paths() {
let library = build_library();
let path = absolute_path("test::proc");
let node_id = library.get_export_node_id(path.as_ref());
let digest = library.mast_forest()[node_id].digest();
let export = PackageExport::Procedure(PackageProcedureExport {
path,
digest,
signature: None,
attributes: AttributeSet::default(),
});
let mut bytes = Vec::new();
bytes.write_usize(2);
export.write_into(&mut bytes);
export.write_into(&mut bytes);
bytes.write_usize(0);
let mut reader = SliceReader::new(&bytes);
let err = PackageManifest::read_from(&mut reader)
.expect_err("duplicate export paths should be rejected during deserialization");
assert!(matches!(err, DeserializationError::InvalidValue(_)));
}
#[test]
fn package_manifest_rejects_duplicate_dependencies() {
let dependency = build_dependency();
let mut bytes = Vec::new();
bytes.write_usize(0);
bytes.write_usize(2);
dependency.write_into(&mut bytes);
dependency.write_into(&mut bytes);
let mut reader = SliceReader::new(&bytes);
let err = PackageManifest::read_from(&mut reader)
.expect_err("duplicate dependencies should be rejected during deserialization");
assert!(matches!(err, DeserializationError::InvalidValue(_)));
}
#[cfg(feature = "serde")]
#[test]
fn serde_package_manifest_rejects_duplicate_export_paths() {
let library = build_library();
let path = absolute_path("test::proc");
let node_id = library.get_export_node_id(path.as_ref());
let digest = library.mast_forest()[node_id].digest();
let export = PackageExport::Procedure(PackageProcedureExport {
path,
digest,
signature: None,
attributes: AttributeSet::default(),
});
let export = to_value(&export).expect("export should serialize");
let manifest = serde_json::to_string(&json!({
"exports": [export.clone(), export],
"dependencies": [],
}))
.expect("manifest should serialize to JSON");
let err = serde_json::from_str::<PackageManifest>(&manifest)
.expect_err("serde deserialization should reject duplicate export paths");
let message = err.to_string();
assert!(message.contains("duplicate export path"));
}
#[cfg(feature = "serde")]
#[test]
fn serde_package_manifest_rejects_duplicate_dependencies() {
let dependency = to_value(build_dependency()).expect("dependency should serialize");
let manifest = serde_json::to_string(&json!({
"exports": [],
"dependencies": [dependency.clone(), dependency],
}))
.expect("manifest should serialize to JSON");
let err = serde_json::from_str::<PackageManifest>(&manifest)
.expect_err("serde deserialization should reject duplicate dependencies");
let message = err.to_string();
assert!(message.contains("duplicate dependency"));
}
}