blob: 59ce8884268776969173f4379ee39eb73b023694 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::errors::RepositoryParseError,
fidl_fuchsia_pkg as fidl,
fuchsia_uri::pkg_uri::{PkgUri, RepoUri},
serde_derive::{Deserialize, Serialize},
std::convert::TryFrom,
std::{fmt, mem},
};
/// Convenience wrapper for the FIDL RepositoryKeyConfig type
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", tag = "type", content = "value", deny_unknown_fields)]
pub enum RepositoryKey {
Ed25519(#[serde(with = "hex_serde")] Vec<u8>),
}
/// Convenience wrapper for the FIDL RepositoryBlobConfig type
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase", tag = "type", content = "value", deny_unknown_fields)]
pub enum RepositoryBlobKey {
Aes(#[serde(with = "hex_serde")] Vec<u8>),
}
/// Convenience wrapper for the FIDL MirrorConfig type
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct MirrorConfig {
mirror_url: String,
subscribe: bool,
blob_key: Option<RepositoryBlobKey>,
}
impl MirrorConfig {
pub fn mirror_url(&self) -> &str {
&self.mirror_url
}
}
/// Convenience wrapper for generating [MirrorConfig] values.
#[derive(Clone, Debug)]
pub struct MirrorConfigBuilder {
config: MirrorConfig,
}
impl MirrorConfigBuilder {
pub fn new(mirror_url: impl Into<String>) -> Self {
MirrorConfigBuilder {
config: MirrorConfig {
mirror_url: mirror_url.into(),
subscribe: false,
blob_key: None,
},
}
}
pub fn mirror_url(mut self, mirror_url: impl Into<String>) -> Self {
self.config.mirror_url = mirror_url.into();
self
}
pub fn subscribe(mut self, subscribe: bool) -> Self {
self.config.subscribe = subscribe;
self
}
pub fn blob_key(mut self, blob_key: RepositoryBlobKey) -> Self {
self.config.blob_key = Some(blob_key);
self
}
pub fn build(self) -> MirrorConfig {
self.config
}
}
impl Into<MirrorConfig> for MirrorConfigBuilder {
fn into(self) -> MirrorConfig {
self.build()
}
}
impl TryFrom<fidl::MirrorConfig> for MirrorConfig {
type Error = RepositoryParseError;
fn try_from(other: fidl::MirrorConfig) -> Result<Self, RepositoryParseError> {
Ok(Self {
mirror_url: other.mirror_url.ok_or(RepositoryParseError::MirrorUrlMissing)?,
subscribe: other.subscribe.ok_or(RepositoryParseError::SubscribeMissing)?,
blob_key: other.blob_key.map(RepositoryBlobKey::try_from).transpose()?,
})
}
}
impl Into<fidl::MirrorConfig> for MirrorConfig {
fn into(self: Self) -> fidl::MirrorConfig {
fidl::MirrorConfig {
mirror_url: Some(self.mirror_url),
subscribe: Some(self.subscribe),
blob_key: self.blob_key.map(|k| k.into()),
}
}
}
/// Convenience wrapper type for the autogenerated FIDL `RepositoryConfig`.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RepositoryConfig {
repo_url: RepoUri,
root_keys: Vec<RepositoryKey>,
mirrors: Vec<MirrorConfig>,
update_package_uri: Option<PkgUri>,
}
impl RepositoryConfig {
pub fn repo_url(&self) -> &RepoUri {
&self.repo_url
}
pub fn insert_mirror(&mut self, mut mirror: MirrorConfig) -> Option<MirrorConfig> {
if let Some(m) = self.mirrors.iter_mut().find(|m| m.mirror_url == mirror.mirror_url) {
mem::swap(m, &mut mirror);
Some(mirror)
} else {
self.mirrors.push(mirror);
None
}
}
pub fn remove_mirror(&mut self, mirror_url: &str) -> Option<MirrorConfig> {
if let Some(pos) = self.mirrors.iter().position(|m| m.mirror_url == mirror_url) {
Some(self.mirrors.remove(pos))
} else {
None
}
}
}
impl TryFrom<fidl::RepositoryConfig> for RepositoryConfig {
type Error = RepositoryParseError;
fn try_from(other: fidl::RepositoryConfig) -> Result<Self, RepositoryParseError> {
let repo_url: RepoUri = other
.repo_url
.ok_or(RepositoryParseError::RepoUrlMissing)?
.parse()
.map_err(|err| RepositoryParseError::InvalidRepoUrl(err))?;
let update_package_uri = if let Some(uri) = other.update_package_uri {
let uri =
uri.parse().map_err(|err| RepositoryParseError::InvalidUpdatePackageUri(err))?;
Some(uri)
} else {
None
};
Ok(Self {
repo_url: repo_url,
root_keys: other
.root_keys
.unwrap_or(vec![])
.into_iter()
.map(RepositoryKey::try_from)
.collect::<Result<_, _>>()?,
mirrors: other
.mirrors
.unwrap_or(vec![])
.into_iter()
.map(MirrorConfig::try_from)
.collect::<Result<_, _>>()?,
update_package_uri: update_package_uri,
})
}
}
impl Into<fidl::RepositoryConfig> for RepositoryConfig {
fn into(self: Self) -> fidl::RepositoryConfig {
fidl::RepositoryConfig {
repo_url: Some(self.repo_url.to_string()),
root_keys: Some(self.root_keys.into_iter().map(RepositoryKey::into).collect()),
mirrors: Some(self.mirrors.into_iter().map(MirrorConfig::into).collect()),
update_package_uri: self.update_package_uri.map(|uri| uri.to_string()),
}
}
}
/// Convenience wrapper for generating [RepositoryConfig] values.
#[derive(Clone, Debug)]
pub struct RepositoryConfigBuilder {
config: RepositoryConfig,
}
impl RepositoryConfigBuilder {
pub fn new(repo_url: RepoUri) -> Self {
RepositoryConfigBuilder {
config: RepositoryConfig {
repo_url,
root_keys: vec![],
mirrors: vec![],
update_package_uri: None,
},
}
}
pub fn repo_url(mut self, repo_url: RepoUri) -> Self {
self.config.repo_url = repo_url;
self
}
pub fn add_root_key(mut self, key: RepositoryKey) -> Self {
self.config.root_keys.push(key);
self
}
pub fn add_mirror(mut self, mirror: impl Into<MirrorConfig>) -> Self {
self.config.mirrors.push(mirror.into());
self
}
pub fn update_package_uri(mut self, uri: PkgUri) -> Self {
self.config.update_package_uri = Some(uri);
self
}
pub fn build(self) -> RepositoryConfig {
self.config
}
}
impl Into<RepositoryConfig> for RepositoryConfigBuilder {
fn into(self) -> RepositoryConfig {
self.build()
}
}
/// Wraper for serializing repository configs to the on-disk JSON format.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(tag = "version", content = "content", deny_unknown_fields)]
pub enum RepositoryConfigs {
#[serde(rename = "1")]
Version1(Vec<RepositoryConfig>),
}
impl TryFrom<fidl::RepositoryKeyConfig> for RepositoryKey {
type Error = RepositoryParseError;
fn try_from(id: fidl::RepositoryKeyConfig) -> Result<Self, RepositoryParseError> {
match id {
fidl::RepositoryKeyConfig::Ed25519Key(key) => Ok(RepositoryKey::Ed25519(key)),
_ => Err(RepositoryParseError::UnsupportedKeyType),
}
}
}
impl Into<fidl::RepositoryKeyConfig> for RepositoryKey {
fn into(self: Self) -> fidl::RepositoryKeyConfig {
match self {
RepositoryKey::Ed25519(key) => fidl::RepositoryKeyConfig::Ed25519Key(key),
}
}
}
impl fmt::Debug for RepositoryKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let RepositoryKey::Ed25519(ref value) = self;
f.debug_tuple("Ed25519").field(&hex::encode(value)).finish()
}
}
impl TryFrom<fidl::RepositoryBlobKey> for RepositoryBlobKey {
type Error = RepositoryParseError;
fn try_from(id: fidl::RepositoryBlobKey) -> Result<Self, RepositoryParseError> {
match id {
fidl::RepositoryBlobKey::AesKey(key) => Ok(RepositoryBlobKey::Aes(key)),
_ => Err(RepositoryParseError::UnsupportedKeyType),
}
}
}
impl Into<fidl::RepositoryBlobKey> for RepositoryBlobKey {
fn into(self: Self) -> fidl::RepositoryBlobKey {
match self {
RepositoryBlobKey::Aes(key) => fidl::RepositoryBlobKey::AesKey(key),
}
}
}
impl fmt::Debug for RepositoryBlobKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let RepositoryBlobKey::Aes(ref value) = self;
f.debug_tuple("Aes").field(&hex::encode(value)).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn test_repository_key_serialize() {
let key = RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]);
assert_eq!(
serde_json::to_string(&key).unwrap(),
r#"{"type":"ed25519","value":"f10f1003"}"#
);
}
#[test]
fn test_repository_key_deserialize() {
let json = r#"{"type":"ed25519","value":"00010203"}"#;
assert_eq!(
serde_json::from_str::<RepositoryKey>(json).unwrap(),
RepositoryKey::Ed25519(vec![0, 1, 2, 3])
);
}
#[test]
fn test_repository_key_deserialize_with_bad_type() {
let json = r#"{"type":"bogus","value":"00010203"}"#;
let result = serde_json::from_str::<RepositoryKey>(json);
let error_message = result.unwrap_err().to_string();
assert!(
error_message.contains("unknown variant `bogus`"),
r#"Error message did not contain "unknown variant `bogus`", was "{}""#,
error_message
);
}
#[test]
fn test_repository_key_deserialize_serialize_roundtrip() {
let json = r#"{"type":"ed25519","value":"00010203"}"#;
let key: RepositoryKey = serde_json::from_str(json).unwrap();
assert_eq!(serde_json::to_string(&key).unwrap(), json);
}
#[test]
fn test_repository_key_into_fidl() {
let key = RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]);
let as_fidl: fidl::RepositoryKeyConfig = key.into();
assert_eq!(as_fidl, fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3]))
}
#[test]
fn test_repository_key_from_fidl() {
let as_fidl = fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3]);
assert_eq!(
RepositoryKey::try_from(as_fidl),
Ok(RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]))
);
}
#[test]
fn test_repository_key_from_fidl_with_bad_type() {
let as_fidl = fidl::RepositoryKeyConfig::__UnknownVariant {
ordinal: 999,
bytes: vec![],
handles: vec![],
};
assert_eq!(RepositoryKey::try_from(as_fidl), Err(RepositoryParseError::UnsupportedKeyType));
}
#[test]
fn test_repository_key_into_from_fidl_roundtrip() {
let key = RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]);
let as_fidl: fidl::RepositoryKeyConfig = key.clone().into();
assert_eq!(RepositoryKey::try_from(as_fidl).unwrap(), key,)
}
#[test]
fn test_blob_key_into_fidl() {
let key = RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3]);
let as_fidl: fidl::RepositoryBlobKey = key.into();
assert_eq!(as_fidl, fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3]))
}
#[test]
fn test_blob_key_from_fidl() {
let as_fidl = fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3]);
assert_eq!(
RepositoryBlobKey::try_from(as_fidl),
Ok(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3]))
);
}
#[test]
fn test_blob_key_from_fidl_with_bad_type() {
let as_fidl = fidl::RepositoryBlobKey::__UnknownVariant {
ordinal: 999,
bytes: vec![],
handles: vec![],
};
assert_eq!(
RepositoryBlobKey::try_from(as_fidl),
Err(RepositoryParseError::UnsupportedKeyType)
);
}
#[test]
fn test_blob_key_into_from_fidl_roundtrip() {
let key = RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3]);
let as_fidl: fidl::RepositoryBlobKey = key.clone().into();
assert_eq!(RepositoryBlobKey::try_from(as_fidl).unwrap(), key);
}
#[test]
fn test_mirror_config_into_fidl() {
let config = MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
};
let as_fidl: fidl::MirrorConfig = config.into();
assert_eq!(
as_fidl,
fidl::MirrorConfig {
mirror_url: Some("http://example.com/tuf/repo".into()),
subscribe: Some(true),
blob_key: Some(fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3])),
}
);
}
#[test]
fn test_mirror_config_from_fidl() {
let as_fidl = fidl::MirrorConfig {
mirror_url: Some("http://example.com/tuf/repo".into()),
subscribe: Some(true),
blob_key: Some(fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3])),
};
assert_eq!(
MirrorConfig::try_from(as_fidl),
Ok(MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
})
);
}
#[test]
fn test_mirror_config_into_from_fidl_roundtrip() {
let config = MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
};
let as_fidl: fidl::MirrorConfig = config.clone().into();
assert_eq!(MirrorConfig::try_from(as_fidl).unwrap(), config);
}
#[test]
fn test_repository_config_into_fidl() {
let config = RepositoryConfig {
repo_url: "fuchsia-pkg://fuchsia.com".try_into().unwrap(),
root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3])],
mirrors: vec![MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
}],
update_package_uri: Some("fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()),
};
let as_fidl: fidl::RepositoryConfig = config.into();
assert_eq!(
as_fidl,
fidl::RepositoryConfig {
repo_url: Some("fuchsia-pkg://fuchsia.com".try_into().unwrap()),
root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
mirrors: Some(vec![fidl::MirrorConfig {
mirror_url: Some("http://example.com/tuf/repo".into()),
subscribe: Some(true),
blob_key: Some(fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3])),
}]),
update_package_uri: Some(
"fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()
),
}
);
}
#[test]
fn test_repository_config_from_fidl() {
let as_fidl = fidl::RepositoryConfig {
repo_url: Some("fuchsia-pkg://fuchsia.com".try_into().unwrap()),
root_keys: Some(vec![fidl::RepositoryKeyConfig::Ed25519Key(vec![0xf1, 15, 16, 3])]),
mirrors: Some(vec![fidl::MirrorConfig {
mirror_url: Some("http://example.com/tuf/repo".into()),
subscribe: Some(true),
blob_key: Some(fidl::RepositoryBlobKey::AesKey(vec![0xf1, 15, 16, 3])),
}]),
update_package_uri: Some("fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()),
};
assert_eq!(
RepositoryConfig::try_from(as_fidl),
Ok(RepositoryConfig {
repo_url: "fuchsia-pkg://fuchsia.com".try_into().unwrap(),
root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3]),],
mirrors: vec![MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
},],
update_package_uri: Some(
"fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()
),
})
);
}
#[test]
fn test_repository_config_from_fidl_repo_url_missing() {
let as_fidl = fidl::RepositoryConfig {
repo_url: None,
root_keys: Some(vec![]),
mirrors: Some(vec![]),
update_package_uri: Some("fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()),
};
assert_eq!(RepositoryConfig::try_from(as_fidl), Err(RepositoryParseError::RepoUrlMissing));
}
#[test]
fn test_repository_config_into_from_fidl_roundtrip() {
let config = RepositoryConfig {
repo_url: "fuchsia-pkg://fuchsia.com".try_into().unwrap(),
root_keys: vec![RepositoryKey::Ed25519(vec![0xf1, 15, 16, 3])],
mirrors: vec![MirrorConfig {
mirror_url: "http://example.com/tuf/repo".into(),
subscribe: true,
blob_key: Some(RepositoryBlobKey::Aes(vec![0xf1, 15, 16, 3])),
}],
update_package_uri: Some("fuchsia-pkg://fuchsia.com/systemupdate".try_into().unwrap()),
};
let as_fidl: fidl::RepositoryConfig = config.clone().into();
assert_eq!(RepositoryConfig::try_from(as_fidl).unwrap(), config);
}
#[test]
fn test_repository_configs_serialize() {
let configs = RepositoryConfigs::Version1(vec![RepositoryConfig {
repo_url: "fuchsia-pkg://fuchsia.com".try_into().unwrap(),
root_keys: vec![],
mirrors: vec![],
update_package_uri: None,
}]);
assert_eq!(
serde_json::to_string(&configs).unwrap(),
r#"{"version":"1","content":[{"repo_url":"fuchsia-pkg://fuchsia.com","root_keys":[],"mirrors":[],"update_package_uri":null}]}"#
)
}
}
mod hex_serde {
use {hex, serde::Deserialize};
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = hex::encode(bytes);
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
hex::decode(value.as_bytes())
.map_err(|e| serde::de::Error::custom(format!("bad hex value: {:?}: {}", value, e)))
}
}