Merge pull request #130 from heartsucker/virtual-fs
split path into TargetPath and VirtualTargetPath
diff --git a/src/bin/tuf.rs b/src/bin/tuf.rs
index a12e569..f5a267d 100644
--- a/src/bin/tuf.rs
+++ b/src/bin/tuf.rs
@@ -1,6 +1,5 @@
extern crate clap;
extern crate ring;
-extern crate tuf;
use clap::{App, AppSettings, SubCommand, Arg, ArgMatches};
use ring::rand::SystemRandom;
diff --git a/src/client.rs b/src/client.rs
index d7243b3..615dc88 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -53,37 +53,84 @@
use crypto::{self, KeyId};
use error::Error;
use interchange::DataInterchange;
-use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath, TargetPath, TargetDescription,
- TargetsMetadata, SnapshotMetadata};
+use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath, VirtualTargetPath,
+ TargetDescription, TargetsMetadata, SnapshotMetadata, TargetPath};
use repository::Repository;
use tuf::Tuf;
use util::SafeReader;
+
+/// Translates real paths (where a file is stored) into virtual paths (how it is addressed in TUF)
+/// and back.
+///
+/// Implementations must obey the following identities for all possible inputs.
+///
+/// ```
+/// # use tuf::client::{PathTranslator, DefaultTranslator};
+/// # use tuf::metadata::{VirtualTargetPath, TargetPath};
+/// # let path = TargetPath::new("foo".into()).unwrap();
+/// # let virt = VirtualTargetPath::new("foo".into()).unwrap();
+/// # let translator = DefaultTranslator::new();
+/// assert_eq!(path,
+/// translator.virtual_to_real(&translator.real_to_virtual(&path).unwrap()).unwrap());
+/// assert_eq!(virt,
+/// translator.real_to_virtual(&translator.virtual_to_real(&virt).unwrap()).unwrap());
+/// ```
+pub trait PathTranslator {
+ /// Convert a real path into a virtual path.
+ fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath>;
+
+ /// Convert a virtual path into a real path.
+ fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath>;
+}
+
+/// A `PathTranslator` that does nothing.
+pub struct DefaultTranslator {}
+
+impl DefaultTranslator {
+ /// Create a new `DefaultTranslator`.
+ pub fn new() -> Self {
+ DefaultTranslator {}
+ }
+}
+
+impl PathTranslator for DefaultTranslator {
+ fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath> {
+ VirtualTargetPath::new(path.value().into())
+ }
+
+ fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath> {
+ TargetPath::new(path.value().into())
+ }
+}
+
/// A client that interacts with TUF repositories.
-pub struct Client<D, L, R>
+pub struct Client<D, L, R, T>
where
D: DataInterchange,
L: Repository<D>,
R: Repository<D>,
+ T: PathTranslator,
{
tuf: Tuf<D>,
- config: Config,
+ config: Config<T>,
local: L,
remote: R,
}
-impl<D, L, R> Client<D, L, R>
+impl<D, L, R, T> Client<D, L, R, T>
where
D: DataInterchange,
L: Repository<D>,
R: Repository<D>,
+ T: PathTranslator,
{
/// Create a new TUF client. It will attempt to load initial root metadata from the local repo
/// and return an error if it cannot do so.
///
/// **WARNING**: This method offers weaker security guarantees than the related method
/// `with_root_pinned`.
- pub fn new(config: Config, mut local: L, mut remote: R) -> Result<Self> {
+ pub fn new(config: Config<T>, mut local: L, mut remote: R) -> Result<Self> {
local.initialize()?;
remote.initialize()?;
@@ -123,12 +170,13 @@
/// This is the preferred method of creating a client.
pub fn with_root_pinned<'a, I>(
trusted_root_keys: I,
- config: Config,
+ config: Config<T>,
mut local: L,
mut remote: R,
) -> Result<Self>
where
I: IntoIterator<Item = &'a KeyId>,
+ T: PathTranslator,
{
local.initialize()?;
remote.initialize()?;
@@ -215,9 +263,10 @@
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_root<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+ fn update_root<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
where
- T: Repository<D>,
+ V: Repository<D>,
+ U: PathTranslator,
{
let latest_root = repo.fetch_metadata(
&Role::Root,
@@ -266,9 +315,10 @@
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_timestamp<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+ fn update_timestamp<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
where
- T: Repository<D>,
+ V: Repository<D>,
+ U: PathTranslator,
{
let ts = repo.fetch_metadata(
&Role::Timestamp,
@@ -282,9 +332,10 @@
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_snapshot<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+ fn update_snapshot<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
where
- T: Repository<D>,
+ V: Repository<D>,
+ U: PathTranslator,
{
let snapshot_description = match tuf.timestamp() {
Some(ts) => Ok(ts.snapshot()),
@@ -316,9 +367,10 @@
}
/// Returns `true` if an update occurred and `false` otherwise.
- fn update_targets<T>(tuf: &mut Tuf<D>, repo: &mut T, config: &Config) -> Result<bool>
+ fn update_targets<V, U>(tuf: &mut Tuf<D>, repo: &mut V, config: &Config<U>) -> Result<bool>
where
- T: Repository<D>,
+ V: Repository<D>,
+ U: PathTranslator,
{
let targets_description = match tuf.snapshot() {
Some(sn) => {
@@ -360,7 +412,8 @@
/// Fetch a target from the remote repo and write it to the local repo.
pub fn fetch_target(&mut self, target: &TargetPath) -> Result<()> {
- let read = self._fetch_target(target)?;
+ let virt = self.config.path_translator.real_to_virtual(target)?;
+ let read = self._fetch_target(&virt)?;
self.local.store_target(read, target)
}
@@ -370,7 +423,8 @@
target: &TargetPath,
mut write: W,
) -> Result<()> {
- let mut read = self._fetch_target(target)?;
+ let virt = self.config.path_translator.real_to_virtual(target)?;
+ let mut read = self._fetch_target(&virt)?;
let mut buf = [0; 1024];
loop {
let bytes_read = read.read(&mut buf)?;
@@ -383,22 +437,23 @@
}
// TODO this should check the local repo first
- fn _fetch_target(&mut self, target: &TargetPath) -> Result<SafeReader<R::TargetRead>> {
- fn lookup<_D, _L, _R>(
- tuf: &mut Tuf<_D>,
- config: &Config,
+ fn _fetch_target(&mut self, target: &VirtualTargetPath) -> Result<SafeReader<R::TargetRead>> {
+ fn lookup<D_, L_, R_, T_>(
+ tuf: &mut Tuf<D_>,
+ config: &Config<T_>,
default_terminate: bool,
current_depth: u32,
- target: &TargetPath,
+ target: &VirtualTargetPath,
snapshot: &SnapshotMetadata,
targets: Option<&TargetsMetadata>,
- local: &mut _L,
- remote: &mut _R,
+ local: &mut L_,
+ remote: &mut R_,
) -> (bool, Result<TargetDescription>)
where
- _D: DataInterchange,
- _L: Repository<_D>,
- _R: Repository<_D>,
+ D_: DataInterchange,
+ L_: Repository<D_>,
+ R_: Repository<D_>,
+ T_: PathTranslator,
{
if current_depth > config.max_delegation_depth {
warn!(
@@ -555,7 +610,7 @@
let target_description = target_description?;
self.remote.fetch_target(
- target,
+ &self.config.path_translator.virtual_to_real(&target)?,
&target_description,
self.config.min_bytes_per_second,
)
@@ -571,27 +626,37 @@
/// `ConfigBuilder` and set your own values.
///
/// ```
-/// # use tuf::client::Config;
+/// # use tuf::client::{Config, DefaultTranslator};
/// let config = Config::default();
/// assert_eq!(config.max_root_size(), &Some(1024 * 1024));
/// assert_eq!(config.max_timestamp_size(), &Some(32 * 1024));
/// assert_eq!(config.min_bytes_per_second(), 4096);
/// assert_eq!(config.max_delegation_depth(), 8);
+/// let _: &DefaultTranslator = config.path_translator();
/// ```
#[derive(Debug)]
-pub struct Config {
+pub struct Config<T>
+where
+ T: PathTranslator,
+{
max_root_size: Option<usize>,
max_timestamp_size: Option<usize>,
min_bytes_per_second: u32,
max_delegation_depth: u32,
+ path_translator: T,
}
-impl Config {
+impl Config<DefaultTranslator> {
/// Initialize a `ConfigBuilder` with the default values.
- pub fn build() -> ConfigBuilder {
+ pub fn build() -> ConfigBuilder<DefaultTranslator> {
ConfigBuilder::default()
}
+}
+impl<T> Config<T>
+where
+ T: PathTranslator,
+{
/// Return the optional maximum root metadata size.
pub fn max_root_size(&self) -> &Option<usize> {
&self.max_root_size
@@ -611,36 +676,50 @@
pub fn max_delegation_depth(&self) -> u32 {
self.max_delegation_depth
}
+
+ /// The `PathTranslator`.
+ pub fn path_translator(&self) -> &T {
+ &self.path_translator
+ }
}
-impl Default for Config {
+impl Default for Config<DefaultTranslator> {
fn default() -> Self {
Config {
max_root_size: Some(1024 * 1024),
max_timestamp_size: Some(32 * 1024),
min_bytes_per_second: 4096,
max_delegation_depth: 8,
+ path_translator: DefaultTranslator::new(),
}
}
}
/// Helper for building and validating a TUF client `Config`.
#[derive(Debug, PartialEq)]
-pub struct ConfigBuilder {
+pub struct ConfigBuilder<T>
+where
+ T: PathTranslator,
+{
max_root_size: Option<usize>,
max_timestamp_size: Option<usize>,
min_bytes_per_second: u32,
max_delegation_depth: u32,
+ path_translator: T,
}
-impl ConfigBuilder {
+impl<T> ConfigBuilder<T>
+where
+ T: PathTranslator,
+{
/// Validate this builder return a `Config` if validation succeeds.
- pub fn finish(self) -> Result<Config> {
+ pub fn finish(self) -> Result<Config<T>> {
Ok(Config {
max_root_size: self.max_root_size,
max_timestamp_size: self.max_timestamp_size,
min_bytes_per_second: self.min_bytes_per_second,
max_delegation_depth: self.max_delegation_depth,
+ path_translator: self.path_translator,
})
}
@@ -667,16 +746,31 @@
self.max_delegation_depth = max;
self
}
+
+ /// Set the `PathTranslator`.
+ pub fn path_translator<TT>(self, path_translator: TT) -> ConfigBuilder<TT>
+ where
+ TT: PathTranslator,
+ {
+ ConfigBuilder {
+ max_root_size: self.max_root_size,
+ max_timestamp_size: self.max_timestamp_size,
+ min_bytes_per_second: self.min_bytes_per_second,
+ max_delegation_depth: self.max_delegation_depth,
+ path_translator: path_translator,
+ }
+ }
}
-impl Default for ConfigBuilder {
- fn default() -> Self {
+impl Default for ConfigBuilder<DefaultTranslator> {
+ fn default() -> ConfigBuilder<DefaultTranslator> {
let cfg = Config::default();
ConfigBuilder {
max_root_size: cfg.max_root_size,
max_timestamp_size: cfg.max_timestamp_size,
min_bytes_per_second: cfg.min_bytes_per_second,
max_delegation_depth: cfg.max_delegation_depth,
+ path_translator: cfg.path_translator,
}
}
}
diff --git a/src/metadata.rs b/src/metadata.rs
index 22eae38..7c349d0 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -230,7 +230,7 @@
#[serde(skip_serializing, skip_deserializing)]
_interchage: PhantomData<D>,
#[serde(skip_serializing, skip_deserializing)]
- _metadata: PhantomData<M>,
+ metadata: PhantomData<M>,
}
impl<D, M> SignedMetadata<D, M>
@@ -272,7 +272,7 @@
signatures: vec![sig],
signed: raw,
_interchage: PhantomData,
- _metadata: PhantomData,
+ metadata: PhantomData,
})
}
@@ -993,35 +993,35 @@
}
-/// Wrapper for a path to a target.
+/// Wrapper for the virtual path to a target.
#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
-pub struct TargetPath(String);
+pub struct VirtualTargetPath(String);
-impl TargetPath {
- /// Create a new `TargetPath` from a `String`.
+impl VirtualTargetPath {
+ /// Create a new `VirtualTargetPath` from a `String`.
///
/// ```
- /// # use tuf::metadata::TargetPath;
- /// assert!(TargetPath::new("foo".into()).is_ok());
- /// assert!(TargetPath::new("/foo".into()).is_err());
- /// assert!(TargetPath::new("../foo".into()).is_err());
- /// assert!(TargetPath::new("foo/..".into()).is_err());
- /// assert!(TargetPath::new("foo/../bar".into()).is_err());
- /// assert!(TargetPath::new("..foo".into()).is_ok());
- /// assert!(TargetPath::new("foo/..bar".into()).is_ok());
- /// assert!(TargetPath::new("foo/bar..".into()).is_ok());
+ /// # use tuf::metadata::VirtualTargetPath;
+ /// assert!(VirtualTargetPath::new("foo".into()).is_ok());
+ /// assert!(VirtualTargetPath::new("/foo".into()).is_err());
+ /// assert!(VirtualTargetPath::new("../foo".into()).is_err());
+ /// assert!(VirtualTargetPath::new("foo/..".into()).is_err());
+ /// assert!(VirtualTargetPath::new("foo/../bar".into()).is_err());
+ /// assert!(VirtualTargetPath::new("..foo".into()).is_ok());
+ /// assert!(VirtualTargetPath::new("foo/..bar".into()).is_ok());
+ /// assert!(VirtualTargetPath::new("foo/bar..".into()).is_ok());
/// ```
pub fn new(path: String) -> Result<Self> {
safe_path(&path)?;
- Ok(TargetPath(path))
+ Ok(VirtualTargetPath(path))
}
- /// Split `TargetPath` into components that can be joined to create URL paths, Unix paths, or
- /// Windows paths.
+ /// Split `VirtualTargetPath` into components that can be joined to create URL paths, Unix
+ /// paths, or Windows paths.
///
/// ```
- /// # use tuf::metadata::TargetPath;
- /// let path = TargetPath::new("foo/bar".into()).unwrap();
+ /// # use tuf::metadata::VirtualTargetPath;
+ /// let path = VirtualTargetPath::new("foo/bar".into()).unwrap();
/// assert_eq!(path.components(), ["foo".to_string(), "bar".to_string()]);
/// ```
pub fn components(&self) -> Vec<String> {
@@ -1031,19 +1031,19 @@
/// Return whether this path is the child of another path.
///
/// ```
- /// # use tuf::metadata::TargetPath;
- /// let path1 = TargetPath::new("foo".into()).unwrap();
- /// let path2 = TargetPath::new("foo/bar".into()).unwrap();
+ /// # use tuf::metadata::VirtualTargetPath;
+ /// let path1 = VirtualTargetPath::new("foo".into()).unwrap();
+ /// let path2 = VirtualTargetPath::new("foo/bar".into()).unwrap();
/// assert!(!path2.is_child(&path1));
///
- /// let path1 = TargetPath::new("foo/".into()).unwrap();
- /// let path2 = TargetPath::new("foo/bar".into()).unwrap();
+ /// let path1 = VirtualTargetPath::new("foo/".into()).unwrap();
+ /// let path2 = VirtualTargetPath::new("foo/bar".into()).unwrap();
/// assert!(path2.is_child(&path1));
///
- /// let path2 = TargetPath::new("foo/bar/baz".into()).unwrap();
+ /// let path2 = VirtualTargetPath::new("foo/bar/baz".into()).unwrap();
/// assert!(path2.is_child(&path1));
///
- /// let path2 = TargetPath::new("wat".into()).unwrap();
+ /// let path2 = VirtualTargetPath::new("wat".into()).unwrap();
/// assert!(!path2.is_child(&path1))
/// ```
pub fn is_child(&self, parent: &Self) -> bool {
@@ -1059,7 +1059,7 @@
/// previous groups.
// TODO this is hideous and uses way too much clone/heap but I think recursively,
// so here we are
- pub fn matches_chain(&self, parents: &[HashSet<TargetPath>]) -> bool {
+ pub fn matches_chain(&self, parents: &[HashSet<VirtualTargetPath>]) -> bool {
if parents.is_empty() {
return false;
}
@@ -1083,18 +1083,52 @@
.collect::<Vec<_>>();
self.matches_chain(&*new)
}
+
+ /// The string value of the path.
+ pub fn value(&self) -> &str {
+ &self.0
+ }
}
-impl ToString for TargetPath {
+impl ToString for VirtualTargetPath {
fn to_string(&self) -> String {
self.0.clone()
}
}
-impl<'de> Deserialize<'de> for TargetPath {
+impl<'de> Deserialize<'de> for VirtualTargetPath {
fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
let s: String = Deserialize::deserialize(de)?;
- TargetPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
+ VirtualTargetPath::new(s).map_err(|e| DeserializeError::custom(format!("{:?}", e)))
+ }
+}
+
+/// Wrapper for the real path to a target.
+#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord, Serialize)]
+pub struct TargetPath(String);
+
+impl TargetPath {
+ /// Create a new `TargetPath`.
+ pub fn new(path: String) -> Result<Self> {
+ safe_path(&path)?;
+ Ok(TargetPath(path))
+ }
+
+ /// Split `TargetPath` into components that can be joined to create URL paths, Unix paths, or
+ /// Windows paths.
+ ///
+ /// ```
+ /// # use tuf::metadata::TargetPath;
+ /// let path = TargetPath::new("foo/bar".into()).unwrap();
+ /// assert_eq!(path.components(), ["foo".to_string(), "bar".to_string()]);
+ /// ```
+ pub fn components(&self) -> Vec<String> {
+ self.0.split('/').map(|s| s.to_string()).collect()
+ }
+
+ /// The string value of the path.
+ pub fn value(&self) -> &str {
+ &self.0
}
}
@@ -1189,7 +1223,7 @@
pub struct TargetsMetadata {
version: u32,
expires: DateTime<Utc>,
- targets: HashMap<TargetPath, TargetDescription>,
+ targets: HashMap<VirtualTargetPath, TargetDescription>,
delegations: Option<Delegations>,
}
@@ -1198,7 +1232,7 @@
pub fn new(
version: u32,
expires: DateTime<Utc>,
- targets: HashMap<TargetPath, TargetDescription>,
+ targets: HashMap<VirtualTargetPath, TargetDescription>,
delegations: Option<Delegations>,
) -> Result<Self> {
if version < 1 {
@@ -1227,7 +1261,7 @@
}
/// An immutable reference to the descriptions of targets.
- pub fn targets(&self) -> &HashMap<TargetPath, TargetDescription> {
+ pub fn targets(&self) -> &HashMap<VirtualTargetPath, TargetDescription> {
&self.targets
}
@@ -1341,7 +1375,7 @@
terminating: bool,
threshold: u32,
key_ids: HashSet<KeyId>,
- paths: HashSet<TargetPath>,
+ paths: HashSet<VirtualTargetPath>,
}
impl Delegation {
@@ -1351,7 +1385,7 @@
terminating: bool,
threshold: u32,
key_ids: HashSet<KeyId>,
- paths: HashSet<TargetPath>,
+ paths: HashSet<VirtualTargetPath>,
) -> Result<Self> {
if key_ids.is_empty() {
return Err(Error::IllegalArgument("Cannot have empty key IDs".into()));
@@ -1401,7 +1435,7 @@
}
/// An immutable reference to the delegation's authorized paths.
- pub fn paths(&self) -> &HashSet<TargetPath> {
+ pub fn paths(&self) -> &HashSet<VirtualTargetPath> {
&self.paths
}
}
@@ -1469,13 +1503,13 @@
for case in test_cases {
let expected = case.0;
- let target = TargetPath::new(case.1.into()).unwrap();
+ let target = VirtualTargetPath::new(case.1.into()).unwrap();
let parents = case.2
.iter()
.map(|group| {
group
.iter()
- .map(|p| TargetPath::new(p.to_string()).unwrap())
+ .map(|p| VirtualTargetPath::new(p.to_string()).unwrap())
.collect::<HashSet<_>>()
})
.collect::<Vec<_>>();
@@ -1492,7 +1526,7 @@
#[test]
fn serde_target_path() {
let s = "foo/bar";
- let t = json::from_str::<TargetPath>(&format!("\"{}\"", s)).unwrap();
+ let t = json::from_str::<VirtualTargetPath>(&format!("\"{}\"", s)).unwrap();
assert_eq!(t.to_string().as_str(), s);
assert_eq!(json::to_value(t).unwrap(), json!("foo/bar"));
}
@@ -1720,7 +1754,7 @@
1,
Utc.ymd(2017, 1, 1).and_hms(0, 0, 0),
hashmap! {
- TargetPath::new("foo".into()).unwrap() =>
+ VirtualTargetPath::new("foo".into()).unwrap() =>
TargetDescription::from_reader(
b"foo" as &[u8],
&[HashAlgorithm::Sha256],
@@ -1759,7 +1793,7 @@
false,
1,
hashset!(key.key_id().clone()),
- hashset!(TargetPath::new("baz/quux".into()).unwrap()),
+ hashset!(VirtualTargetPath::new("baz/quux".into()).unwrap()),
).unwrap()],
).unwrap();
@@ -1960,7 +1994,7 @@
false,
1,
hashset!(key.key_id().clone()),
- hashset!(TargetPath::new("bar".into()).unwrap()),
+ hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
).unwrap()],
).unwrap();
@@ -1977,7 +2011,7 @@
false,
1,
hashset!(key.key_id().clone()),
- hashset!(TargetPath::new("bar".into()).unwrap()),
+ hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
).unwrap();
json::to_value(&delegation).unwrap()
@@ -2300,7 +2334,7 @@
false,
1,
hashset!(key.key_id().clone()),
- hashset!(TargetPath::new("bar".into()).unwrap()),
+ hashset!(VirtualTargetPath::new("bar".into()).unwrap()),
).unwrap()],
).unwrap();
let mut delegations = json::to_value(delegations).unwrap();
diff --git a/src/repository.rs b/src/repository.rs
index 607d66d..1615bb2 100644
--- a/src/repository.rs
+++ b/src/repository.rs
@@ -100,7 +100,7 @@
D: DataInterchange,
{
local_path: PathBuf,
- _interchange: PhantomData<D>,
+ interchange: PhantomData<D>,
}
impl<D> FileSystemRepository<D>
@@ -111,7 +111,7 @@
pub fn new(local_path: PathBuf) -> Self {
FileSystemRepository {
local_path: local_path,
- _interchange: PhantomData,
+ interchange: PhantomData,
}
}
}
@@ -241,7 +241,7 @@
client: Client,
user_agent: String,
metadata_prefix: Option<Vec<String>>,
- _interchange: PhantomData<D>,
+ interchange: PhantomData<D>,
}
impl<D> HttpRepository<D>
@@ -274,7 +274,7 @@
client: client,
user_agent: user_agent,
metadata_prefix: metadata_prefix,
- _interchange: PhantomData,
+ interchange: PhantomData,
}
}
@@ -399,7 +399,7 @@
{
metadata: HashMap<(MetadataPath, MetadataVersion), Vec<u8>>,
targets: HashMap<TargetPath, Vec<u8>>,
- _interchange: PhantomData<D>,
+ interchange: PhantomData<D>,
}
impl<D> EphemeralRepository<D>
@@ -411,7 +411,7 @@
EphemeralRepository {
metadata: HashMap::new(),
targets: HashMap::new(),
- _interchange: PhantomData,
+ interchange: PhantomData,
}
}
}
diff --git a/src/shims.rs b/src/shims.rs
index 286af1d..e252aec 100644
--- a/src/shims.rs
+++ b/src/shims.rs
@@ -196,7 +196,7 @@
typ: metadata::Role,
version: u32,
expires: String,
- targets: HashMap<metadata::TargetPath, metadata::TargetDescription>,
+ targets: HashMap<metadata::VirtualTargetPath, metadata::TargetDescription>,
#[serde(skip_serializing_if = "Option::is_none")]
delegations: Option<metadata::Delegations>,
}
@@ -269,7 +269,7 @@
terminating: bool,
threshold: u32,
key_ids: Vec<crypto::KeyId>,
- paths: Vec<metadata::TargetPath>,
+ paths: Vec<metadata::VirtualTargetPath>,
}
impl Delegation {
@@ -277,7 +277,7 @@
let mut paths = meta.paths()
.iter()
.cloned()
- .collect::<Vec<metadata::TargetPath>>();
+ .collect::<Vec<metadata::VirtualTargetPath>>();
paths.sort();
let mut key_ids = meta.key_ids()
.iter()
@@ -298,7 +298,7 @@
let paths = self.paths
.iter()
.cloned()
- .collect::<HashSet<metadata::TargetPath>>();
+ .collect::<HashSet<metadata::VirtualTargetPath>>();
if paths.len() != self.paths.len() {
return Err(Error::Encoding("Non-unique delegation paths.".into()));
}
diff --git a/src/tuf.rs b/src/tuf.rs
index 9b99384..eea3eec 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -9,7 +9,7 @@
use error::Error;
use interchange::DataInterchange;
use metadata::{SignedMetadata, RootMetadata, TimestampMetadata, Role, SnapshotMetadata,
- MetadataPath, TargetsMetadata, TargetPath, TargetDescription, Delegations};
+ MetadataPath, TargetsMetadata, VirtualTargetPath, TargetDescription, Delegations};
/// Contains trusted TUF metadata and can be used to verify other metadata and targets.
#[derive(Debug)]
@@ -19,7 +19,7 @@
targets: Option<TargetsMetadata>,
timestamp: Option<TimestampMetadata>,
delegations: HashMap<MetadataPath, TargetsMetadata>,
- _interchange: PhantomData<D>,
+ interchange: PhantomData<D>,
}
impl<D: DataInterchange> Tuf<D> {
@@ -65,7 +65,7 @@
targets: None,
timestamp: None,
delegations: HashMap::new(),
- _interchange: PhantomData,
+ interchange: PhantomData,
})
}
@@ -439,10 +439,10 @@
}
/// Get a reference to the description needed to verify the target defined by the given
- /// `TargetPath`. Returns an `Error` if the target is not defined in the trusted metadata. This
- /// may mean the target exists somewhere in the metadata, but the chain of trust to that target
- /// may be invalid or incomplete.
- pub fn target_description(&self, target_path: &TargetPath) -> Result<TargetDescription> {
+ /// `VirtualTargetPath`. Returns an `Error` if the target is not defined in the trusted
+ /// metadata. This may mean the target exists somewhere in the metadata, but the chain of trust
+ /// to that target may be invalid or incomplete.
+ pub fn target_description(&self, target_path: &VirtualTargetPath) -> Result<TargetDescription> {
let _ = self.safe_root_ref()?;
let _ = self.safe_snapshot_ref()?;
let targets = self.safe_targets_ref()?;
@@ -456,9 +456,9 @@
tuf: &Tuf<D>,
default_terminate: bool,
current_depth: u32,
- target_path: &TargetPath,
+ target_path: &VirtualTargetPath,
delegations: &Delegations,
- parents: Vec<HashSet<TargetPath>>,
+ parents: Vec<HashSet<VirtualTargetPath>>,
visited: &mut HashSet<MetadataPath>,
) -> (bool, Option<TargetDescription>) {
for delegation in delegations.roles() {
diff --git a/tests/integration.rs b/tests/integration.rs
index ba8095c..4f46a83 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -10,7 +10,7 @@
use tuf::crypto::{PrivateKey, SignatureScheme, HashAlgorithm};
use tuf::interchange::Json;
use tuf::metadata::{RoleDefinition, RootMetadata, MetadataPath, SignedMetadata, TargetDescription,
- TargetPath, TargetsMetadata, MetadataDescription, SnapshotMetadata,
+ VirtualTargetPath, TargetsMetadata, MetadataDescription, SnapshotMetadata,
TimestampMetadata, Delegation, Delegations};
const ED25519_1_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-1.pk8.der");
@@ -92,7 +92,7 @@
.iter()
.cloned()
.collect(),
- vec![TargetPath::new("foo".into()).unwrap()]
+ vec![VirtualTargetPath::new("foo".into()).unwrap()]
.iter()
.cloned()
.collect()
@@ -114,7 +114,7 @@
let target_file: &[u8] = b"bar";
let target_map =
hashmap! {
- TargetPath::new("foo".into()).unwrap() =>
+ VirtualTargetPath::new("foo".into()).unwrap() =>
TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
};
let delegation =
@@ -127,7 +127,7 @@
.unwrap();
assert!(
- tuf.target_description(&TargetPath::new("foo".into()).unwrap())
+ tuf.target_description(&VirtualTargetPath::new("foo".into()).unwrap())
.is_ok()
);
}
@@ -207,7 +207,7 @@
.iter()
.cloned()
.collect(),
- vec![TargetPath::new("foo".into()).unwrap()]
+ vec![VirtualTargetPath::new("foo".into()).unwrap()]
.iter()
.cloned()
.collect()
@@ -237,7 +237,7 @@
.iter()
.cloned()
.collect(),
- vec![TargetPath::new("foo".into()).unwrap()]
+ vec![VirtualTargetPath::new("foo".into()).unwrap()]
.iter()
.cloned()
.collect()
@@ -261,7 +261,7 @@
let target_file: &[u8] = b"bar";
let target_map =
hashmap! {
- TargetPath::new("foo".into()).unwrap() =>
+ VirtualTargetPath::new("foo".into()).unwrap() =>
TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256]).unwrap(),
};
@@ -275,7 +275,7 @@
.unwrap();
assert!(
- tuf.target_description(&TargetPath::new("foo".into()).unwrap())
+ tuf.target_description(&VirtualTargetPath::new("foo".into()).unwrap())
.is_ok()
);
}
diff --git a/tests/simple_example.rs b/tests/simple_example.rs
index a4c451a..fe7a89c 100644
--- a/tests/simple_example.rs
+++ b/tests/simple_example.rs
@@ -5,13 +5,13 @@
use chrono::prelude::*;
use chrono::offset::Utc;
-use tuf::Error;
-use tuf::client::{Client, Config};
+use tuf::Result;
+use tuf::client::{Client, Config, PathTranslator};
use tuf::crypto::{PrivateKey, SignatureScheme, KeyId, HashAlgorithm};
use tuf::interchange::{DataInterchange, Json};
use tuf::metadata::{RoleDefinition, RootMetadata, Role, MetadataVersion, MetadataPath,
- SignedMetadata, TargetDescription, TargetPath, TargetsMetadata,
- MetadataDescription, SnapshotMetadata, TimestampMetadata};
+ SignedMetadata, TargetDescription, VirtualTargetPath, TargetsMetadata,
+ MetadataDescription, SnapshotMetadata, TimestampMetadata, TargetPath};
use tuf::repository::{EphemeralRepository, Repository};
// Ironically, this is far from simple, but it's as simple as it can be made.
@@ -21,26 +21,59 @@
const ED25519_3_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-3.pk8.der");
const ED25519_4_PK8: &'static [u8] = include_bytes!("./ed25519/ed25519-4.pk8.der");
-#[test]
-fn main() {
- let mut remote = EphemeralRepository::<Json>::new();
- let root_key_ids = init_server(&mut remote).unwrap();
- init_client(&root_key_ids, remote).unwrap();
+struct MyPathTranslator {}
+
+impl PathTranslator for MyPathTranslator {
+ fn real_to_virtual(&self, path: &TargetPath) -> Result<VirtualTargetPath> {
+ VirtualTargetPath::new(path.value().to_owned().replace("-", "/"))
+ }
+
+ fn virtual_to_real(&self, path: &VirtualTargetPath) -> Result<TargetPath> {
+ TargetPath::new(path.value().to_owned().replace("/", "-"))
+ }
}
-fn init_client(root_key_ids: &[KeyId], remote: EphemeralRepository<Json>) -> Result<(), Error> {
- let local = EphemeralRepository::<Json>::new();
+#[test]
+fn with_translator() {
+ let mut remote = EphemeralRepository::<Json>::new();
let config = Config::default();
+ let root_key_ids = init_server(&mut remote, &config).unwrap();
+ init_client(&root_key_ids, remote, config).unwrap();
+}
+
+#[test]
+fn without_translator() {
+ let mut remote = EphemeralRepository::<Json>::new();
+ let config = Config::build()
+ .path_translator(MyPathTranslator {})
+ .finish()
+ .unwrap();
+ let root_key_ids = init_server(&mut remote, &config).unwrap();
+ init_client(&root_key_ids, remote, config).unwrap();
+}
+
+fn init_client<T>(
+ root_key_ids: &[KeyId],
+ remote: EphemeralRepository<Json>,
+ config: Config<T>,
+) -> Result<()>
+where
+ T: PathTranslator,
+{
+ let local = EphemeralRepository::<Json>::new();
let mut client = Client::with_root_pinned(root_key_ids, config, local, remote)?;
match client.update_local() {
Ok(_) => (),
Err(e) => println!("{:?}", e),
}
let _ = client.update_remote()?;
- client.fetch_target(&TargetPath::new("grendel".into())?)
+ client.fetch_target(&TargetPath::new("foo-bar".into())?)
}
-fn init_server(remote: &mut EphemeralRepository<Json>) -> Result<Vec<KeyId>, Error> {
+fn init_server<T>(remote: &mut EphemeralRepository<Json>, config: &Config<T>) -> Result<Vec<KeyId>>
+where
+ T: PathTranslator,
+{
// in real life, you wouldn't want these keys on the same machine ever
let root_key = PrivateKey::from_pkcs8(ED25519_1_PK8, SignatureScheme::Ed25519)?;
let snapshot_key = PrivateKey::from_pkcs8(ED25519_2_PK8, SignatureScheme::Ed25519)?;
@@ -90,11 +123,12 @@
//// build the targets ////
let target_file: &[u8] = b"things fade, alternatives exclude";
- let target_path = TargetPath::new("grendel".into())?;
+ let target_path = TargetPath::new("foo-bar".into())?;
let target_description = TargetDescription::from_reader(target_file, &[HashAlgorithm::Sha256])?;
let _ = remote.store_target(target_file, &target_path);
- let target_map = hashmap!(target_path => target_description);
+ let target_map =
+ hashmap!(config.path_translator().real_to_virtual(&target_path)? => target_description);
let targets = TargetsMetadata::new(1, Utc.ymd(2038, 1, 1).and_hms(0, 0, 0), target_map, None)?;
let signed = SignedMetadata::<Json, TargetsMetadata>::new(&targets, &targets_key)?;