| // 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))) |
| } |
| } |