snapshot metadata
diff --git a/src/client.rs b/src/client.rs
index b3d328e..9946054 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -3,9 +3,10 @@
 use chrono::offset::Utc;
 
 use Result;
+use crypto;
 use error::Error;
 use interchange::DataInterchange;
-use metadata::{MetadataVersion, RootMetadata, Role};
+use metadata::{MetadataVersion, RootMetadata, Role, MetadataPath};
 use repository::Repository;
 use tuf::Tuf;
 
@@ -45,8 +46,8 @@
     // TODO this might need to be split into `update_local` and `update_remote` to be useful to
     // implementers.
     pub fn update(&mut self) -> Result<bool> {
-        if self.update_root()? {
-            self.update_timestamp()
+        if self.update_root()? && self.update_timestamp()? {
+            self.update_snapshot()
         } else {
             Ok(false)
         }
@@ -78,6 +79,7 @@
             &Role::Root,
             &MetadataVersion::None,
             max_root_size,
+            None,
         )?;
         let latest_version = D::deserialize::<RootMetadata>(latest_root.unverified_signed())?
             .version();
@@ -100,6 +102,7 @@
                 &Role::Root,
                 &MetadataVersion::Number(i),
                 max_root_size,
+                None,
             )?;
             if !tuf.update_root(signed)? {
                 error!("{}", err_msg);
@@ -120,6 +123,7 @@
             &Role::Timestamp,
             &MetadataVersion::None,
             &self.config.max_timestamp_size,
+            None,
         )?;
         self.tuf.update_timestamp(ts)?;
 
@@ -127,9 +131,49 @@
             &Role::Timestamp,
             &MetadataVersion::None,
             &self.config.max_timestamp_size,
+            None,
         )?;
         self.tuf.update_timestamp(ts)
     }
+
+    /// Returns `true` if an update occurred and `false` otherwise.
+    fn update_snapshot(&mut self) -> Result<bool> {
+        let snapshot_description = match self.tuf.timestamp() {
+            Some(ts) => {
+                match ts.meta().get(&MetadataPath::from_role(&Role::Timestamp)) {
+                    Some(d) => Ok(d),
+                    None => Err(Error::VerificationFailure(
+                        "Timestamp metadata did not contain a description of the \
+                                current snapshot metadata"
+                            .into(),
+                    )),
+                }
+            }
+            None => Err(Error::MissingMetadata(Role::Timestamp)),
+        }?
+            .clone();
+
+        let hashes = match snapshot_description.hashes() {
+            Some(hashes) => Some(crypto::hash_preference(hashes)?),
+            None => None,
+        };
+
+        let snap = self.local.fetch_metadata(
+            &Role::Snapshot,
+            &MetadataVersion::None,
+            &snapshot_description.length(),
+            hashes,
+        )?;
+        self.tuf.update_snapshot(snap)?;
+
+        let snap = self.remote.fetch_metadata(
+            &Role::Snapshot,
+            &MetadataVersion::None,
+            &snapshot_description.length(),
+            hashes,
+        )?;
+        self.tuf.update_snapshot(snap)
+    }
 }
 
 /// Configuration for a TUF `Client`.
diff --git a/src/crypto.rs b/src/crypto.rs
index 64e7de7..0e4b097 100644
--- a/src/crypto.rs
+++ b/src/crypto.rs
@@ -6,6 +6,7 @@
 use ring::signature::{ED25519, RSA_PSS_2048_8192_SHA256, RSA_PSS_2048_8192_SHA512};
 use serde::de::{Deserialize, Deserializer, Error as DeserializeError};
 use serde::ser::{Serialize, Serializer, SerializeTupleStruct, Error as SerializeError};
+use std::collections::HashMap;
 use std::fmt::{self, Debug};
 use std::str::FromStr;
 use untrusted::Input;
@@ -15,6 +16,22 @@
 use rsa;
 use shims;
 
+static HASH_ALG_PREFS: &'static [HashAlgorithm] = &[HashAlgorithm::Sha512, HashAlgorithm::Sha256];
+
+/// Given a map of hash algorithms and their values, get the prefered algorithm and the hash
+/// calculated by it. Returns an `Err` if there is no match.
+pub fn hash_preference<'a>(
+    hashes: &'a HashMap<HashAlgorithm, HashValue>,
+) -> Result<(&'static HashAlgorithm, &'a HashValue)> {
+    for alg in HASH_ALG_PREFS {
+        match hashes.get(alg) {
+            Some(v) => return Ok((alg, v)),
+            None => continue,
+        }
+    }
+    Err(Error::NoSupportedHashAlgorithm)
+}
+
 /// Calculate the given key's ID.
 ///
 /// A `KeyId` is calculated as `sha256(public_key_bytes)`. The TUF spec says that it should be
@@ -232,19 +249,17 @@
 
         let pkcs1_value = match format {
             KeyFormat::Pkcs1 => {
-                let bytes = rsa::from_pkcs1(value.value()).ok_or(
+                let bytes = rsa::from_pkcs1(value.value()).ok_or_else(|| {
                     Error::IllegalArgument(
-                        "Key claimed to be PKCS1 but could not be parsed."
-                            .into(),
-                    ),
-                )?;
+                        "Key claimed to be PKCS1 but could not be parsed.".into(),
+                    )
+                })?;
                 PublicKeyValue(bytes)
             }
             KeyFormat::Spki => {
-                let bytes = rsa::from_spki(value.value()).ok_or(Error::IllegalArgument(
-                    "Key claimed to be SPKI but could not be parsed."
-                        .into(),
-                ))?;
+                let bytes = rsa::from_spki(value.value()).ok_or_else(|| {
+                    Error::IllegalArgument("Key claimed to be SPKI but could not be parsed.".into())
+                })?;
                 PublicKeyValue(bytes)
             }
             x => {
@@ -385,3 +400,10 @@
 /// Wrapper for the value of a hash digest.
 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
 pub struct HashValue(Vec<u8>);
+
+impl HashValue {
+    /// An immutable reference to the bytes of the hash value.
+    pub fn value(&self) -> &[u8] {
+        &self.0
+    }
+}
diff --git a/src/error.rs b/src/error.rs
index 33c862e..4d4840b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -29,6 +29,8 @@
     Io(String),
     /// The metadata was missing, so an operation could not be completed.
     MissingMetadata(Role),
+    /// There were no available hash algorithms.
+    NoSupportedHashAlgorithm,
     /// The metadata or target was not found.
     NotFound,
     /// There was an internal `serde` error.
diff --git a/src/metadata.rs b/src/metadata.rs
index 7479a65..04848aa 100644
--- a/src/metadata.rs
+++ b/src/metadata.rs
@@ -376,6 +376,11 @@
 pub struct MetadataPath(String);
 
 impl MetadataPath {
+    /// Create a metadata path from the given role.
+    pub fn from_role(role: &Role) -> Self {
+        MetadataPath(role.to_string())
+    }
+
     // TODO convert to/from paths/urls/etc
 }
 
@@ -495,6 +500,77 @@
     }
 }
 
+/// Metdata for the snapshot role.
+#[derive(Debug, PartialEq)]
+pub struct SnapshotMetadata {
+    version: u32,
+    expires: DateTime<Utc>,
+    meta: HashMap<MetadataPath, MetadataDescription>,
+}
+
+impl SnapshotMetadata {
+    /// Create new `SnapshotMetadata`.
+    pub fn new(
+        version: u32,
+        expires: DateTime<Utc>,
+        meta: HashMap<MetadataPath, MetadataDescription>,
+    ) -> Result<Self> {
+        if version < 1 {
+            return Err(Error::IllegalArgument(format!(
+                "Metadata version must be greater than zero. Found: {}",
+                version
+            )));
+        }
+
+        Ok(SnapshotMetadata {
+            version: version,
+            expires: expires,
+            meta: meta,
+        })
+    }
+
+    /// The version number.
+    pub fn version(&self) -> u32 {
+        self.version
+    }
+
+    /// An immutable reference to the metadata's expiration `DateTime`.
+    pub fn expires(&self) -> &DateTime<Utc> {
+        &self.expires
+    }
+
+    /// An immutable reference to the metadata paths and descriptions.
+    pub fn meta(&self) -> &HashMap<MetadataPath, MetadataDescription> {
+        &self.meta
+    }
+}
+
+impl Metadata for SnapshotMetadata {
+    fn role() -> Role {
+        Role::Snapshot
+    }
+}
+
+impl Serialize for SnapshotMetadata {
+    fn serialize<S>(&self, ser: S) -> ::std::result::Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        shims::SnapshotMetadata::from(self)
+            .map_err(|e| SerializeError::custom(format!("{:?}", e)))?
+            .serialize(ser)
+    }
+}
+
+impl<'de> Deserialize<'de> for SnapshotMetadata {
+    fn deserialize<D: Deserializer<'de>>(de: D) -> ::std::result::Result<Self, D::Error> {
+        let intermediate: shims::SnapshotMetadata = Deserialize::deserialize(de)?;
+        intermediate.try_into().map_err(|e| {
+            DeserializeError::custom(format!("{:?}", e))
+        })
+    }
+}
+
 #[cfg(test)]
 mod test {
     use json;
diff --git a/src/repository.rs b/src/repository.rs
index a9e13ed..7b8a6a1 100644
--- a/src/repository.rs
+++ b/src/repository.rs
@@ -3,13 +3,15 @@
 use hyper::{Url, Client};
 use hyper::client::response::Response;
 use hyper::header::{Headers, UserAgent};
+use ring::digest::{self, SHA256, SHA512};
 use std::collections::HashMap;
 use std::fs::{self, File, DirBuilder};
-use std::io::Read;
+use std::io::{Read, Write};
 use std::marker::PhantomData;
 use std::path::PathBuf;
 
 use Result;
+use crypto::{HashAlgorithm, HashValue};
 use error::Error;
 use metadata::{SignedMetadata, MetadataVersion, Unverified, Verified, Role, Metadata};
 use interchange::DataInterchange;
@@ -39,6 +41,7 @@
         role: &Role,
         version: &MetadataVersion,
         max_size: &Option<usize>,
+        hash_data: Option<(&HashAlgorithm, &HashValue)>,
     ) -> Result<SignedMetadata<D, M, Unverified>>
     where
         M: Metadata;
@@ -49,19 +52,75 @@
         format!("{}{}{}", version.prefix(), role, D::extension())
     }
 
-    /// Read the from given reader, optionally capped at `max_size` bytes.
-    fn safe_read<R: Read>(read: &mut R, max_size: &Option<usize>) -> Result<Vec<u8>> {
-        match max_size {
-            &Some(max_size) => {
-                let mut buf = vec![0; max_size];
-                read.read_exact(&mut buf)?;
-                Ok(buf)
+    /// Read the from given reader, optionally capped at `max_size` bytes, optionally requiring
+    /// hashes to match.
+    fn safe_read<R, W>(
+        read: &mut R,
+        write: &mut W,
+        max_size: Option<i64>,
+        hash_data: Option<(&HashAlgorithm, &HashValue)>,
+    ) -> Result<()>
+    where
+        R: Read,
+        W: Write,
+    {
+        let mut context = match hash_data {
+            Some((&HashAlgorithm::Sha256, _)) => Some(digest::Context::new(&SHA256)),
+            Some((&HashAlgorithm::Sha512, _)) => Some(digest::Context::new(&SHA512)),
+            None => None,
+        };
+
+        let mut buf = [0; 1024];
+        let mut bytes_left = max_size.unwrap_or(::std::i64::MAX);
+
+        loop {
+            match read.read(&mut buf) {
+                Ok(read_bytes) => {
+                    if read_bytes == 0 {
+                        break;
+                    }
+
+                    bytes_left -= read_bytes as i64;
+                    if bytes_left < 0 {
+                        return Err(Error::VerificationFailure(
+                            "Read exceeded the maximum allowed bytes.".into(),
+                        ));
+                    }
+
+                    write.write_all(&buf[0..read_bytes])?;
+
+                    match context {
+                        Some(ref mut c) => c.update(&buf[0..read_bytes]),
+                        None => (),
+                    };
+                }
+                e @ Err(_) => e.map(|_| ())?,
             }
-            &None => {
-                let mut buf = Vec::new();
-                let _ = read.read_to_end(&mut buf)?;
-                Ok(buf)
+        }
+
+        let generated_hash = context.map(|c| c.finish());
+
+        match (generated_hash, hash_data) {
+            (Some(generated_hash), Some((_, expected_hash)))
+                if generated_hash.as_ref() != expected_hash.value() => {
+                Err(Error::VerificationFailure(
+                    "Generated hash did not match expected hash.".into(),
+                ))
             }
+            (Some(_), None) => {
+                let msg = "Hash calculated when no expected hash supplied. \
+                           This is a programming error. Please report this as a bug.";
+                error!("{}", msg);
+                Err(Error::Generic(msg.into()))
+            }
+            (None, Some(_)) => {
+                let msg = "No hash calculated when expected hash supplied. \
+                           This is a programming error. Please report this as a bug.";
+                error!("{}", msg);
+                Err(Error::Generic(msg.into()))
+            }
+            (Some(_), Some(_)) |
+            (None, None) => Ok(()),
         }
     }
 }
@@ -138,6 +197,7 @@
         role: &Role,
         version: &MetadataVersion,
         max_size: &Option<usize>,
+        hash_data: Option<(&HashAlgorithm, &HashValue)>,
     ) -> Result<SignedMetadata<D, M, Unverified>>
     where
         M: Metadata,
@@ -145,8 +205,9 @@
         let version_str = Self::version_string(role, version);
         let path = self.local_path.join("metadata").join(&version_str);
         let mut file = File::open(&path)?;
-        let buf = Self::safe_read(&mut file, max_size)?;
-        Ok(D::from_reader(&*buf)?)
+        let mut out = Vec::new();
+        Self::safe_read(&mut file, &mut out, max_size.map(|x| x as i64), hash_data)?;
+        Ok(D::from_reader(&*out)?)
     }
 }
 
@@ -226,14 +287,16 @@
         role: &Role,
         version: &MetadataVersion,
         max_size: &Option<usize>,
+        hash_data: Option<(&HashAlgorithm, &HashValue)>,
     ) -> Result<SignedMetadata<D, M, Unverified>>
     where
         M: Metadata,
     {
         let version_str = Self::version_string(role, version);
         let mut resp = self.get(&version_str)?;
-        let buf = Self::safe_read(&mut resp, max_size)?;
-        Ok(D::from_reader(&*buf)?)
+        let mut out = Vec::new();
+        Self::safe_read(&mut resp, &mut out, max_size.map(|x| x as i64), hash_data)?;
+        Ok(D::from_reader(&*out)?)
     }
 }
 
@@ -297,6 +360,7 @@
         role: &Role,
         version: &MetadataVersion,
         _: &Option<usize>,
+        _: Option<(&HashAlgorithm, &HashValue)>,
     ) -> Result<SignedMetadata<D, M, Unverified>>
     where
         M: Metadata,
diff --git a/src/shims.rs b/src/shims.rs
index db461fc..c9b4734 100644
--- a/src/shims.rs
+++ b/src/shims.rs
@@ -66,29 +66,21 @@
             }
         }
 
-        let root = self.roles.remove(&metadata::Role::Root).ok_or(
-            Error::Decode(
-                "Missing root role definition"
-                    .into(),
-            ),
+        let root = self.roles.remove(&metadata::Role::Root).ok_or_else(|| {
+            Error::Decode("Missing root role definition".into())
+        })?;
+        let snapshot = self.roles.remove(&metadata::Role::Snapshot).ok_or_else(
+            || {
+                Error::Decode("Missing snapshot role definition".into())
+            },
         )?;
-        let snapshot = self.roles.remove(&metadata::Role::Snapshot).ok_or(
-            Error::Decode(
-                "Missing snapshot role definition"
-                    .into(),
-            ),
-        )?;
-        let targets = self.roles.remove(&metadata::Role::Targets).ok_or(
-            Error::Decode(
-                "Missing targets role definition"
-                    .into(),
-            ),
-        )?;
-        let timestamp = self.roles.remove(&metadata::Role::Timestamp).ok_or(
-            Error::Decode(
-                "Missing timestamp role definition"
-                    .into(),
-            ),
+        let targets = self.roles.remove(&metadata::Role::Targets).ok_or_else(|| {
+            Error::Decode("Missing targets role definition".into())
+        })?;
+        let timestamp = self.roles.remove(&metadata::Role::Timestamp).ok_or_else(
+            || {
+                Error::Decode("Missing timestamp role definition".into())
+            },
         )?;
 
         metadata::RootMetadata::new(
@@ -245,3 +237,34 @@
         metadata::TimestampMetadata::new(self.version, self.expires, self.meta)
     }
 }
+
+#[derive(Serialize, Deserialize)]
+pub struct SnapshotMetadata {
+    #[serde(rename = "type")]
+    typ: metadata::Role,
+    version: u32,
+    expires: DateTime<Utc>,
+    meta: HashMap<metadata::MetadataPath, metadata::MetadataDescription>,
+}
+
+impl SnapshotMetadata {
+    pub fn from(metadata: &metadata::SnapshotMetadata) -> Result<Self> {
+        Ok(SnapshotMetadata {
+            typ: metadata::Role::Snapshot,
+            version: metadata.version(),
+            expires: metadata.expires().clone(),
+            meta: metadata.meta().clone(),
+        })
+    }
+
+    pub fn try_into(self) -> Result<metadata::SnapshotMetadata> {
+        if self.typ != metadata::Role::Snapshot {
+            return Err(Error::Decode(format!(
+                "Attempted to decode snapshot metdata labeled as {:?}",
+                self.typ
+            )));
+        }
+
+        metadata::SnapshotMetadata::new(self.version, self.expires, self.meta)
+    }
+}
diff --git a/src/tuf.rs b/src/tuf.rs
index 678fbb9..62a66fe 100644
--- a/src/tuf.rs
+++ b/src/tuf.rs
@@ -7,12 +7,14 @@
 use crypto::KeyId;
 use error::Error;
 use interchange::DataInterchange;
-use metadata::{SignedMetadata, RootMetadata, VerificationStatus, TimestampMetadata, Role};
+use metadata::{SignedMetadata, RootMetadata, VerificationStatus, TimestampMetadata, Role,
+               SnapshotMetadata, MetadataPath};
 
 /// Contains trusted TUF metadata and can be used to verify other metadata and targets.
 #[derive(Debug)]
 pub struct Tuf<D: DataInterchange> {
     root: RootMetadata,
+    snapshot: Option<SnapshotMetadata>,
     timestamp: Option<TimestampMetadata>,
     _interchange: PhantomData<D>,
 }
@@ -49,6 +51,7 @@
         )?;
         Ok(Tuf {
             root: root,
+            snapshot: None,
             timestamp: None,
             _interchange: PhantomData,
         })
@@ -104,6 +107,8 @@
             root.keys(),
         )?;
 
+        self.purge_metadata();
+
         self.root = root;
         Ok(true)
     }
@@ -141,4 +146,96 @@
             Ok(true)
         }
     }
+
+    /// Verify and update the snapshot metadata.
+    pub fn update_snapshot<V>(
+        &mut self,
+        signed_snapshot: SignedMetadata<D, SnapshotMetadata, V>,
+    ) -> Result<bool>
+    where
+        V: VerificationStatus,
+    {
+        let root = self.safe_root_ref()?;
+        let timestamp = self.safe_timestamp_ref()?;
+        let snapshot_description = timestamp
+            .meta()
+            .get(&MetadataPath::from_role(&Role::Snapshot))
+            .ok_or_else(|| {
+                Error::VerificationFailure(
+                    "Timestamp metadata had no description of the snapshot metadata".into(),
+                )
+            })?;
+
+        let signed_snapshot = signed_snapshot.verify(
+            root.snapshot().threshold(),
+            root.snapshot().key_ids(),
+            root.keys(),
+        )?;
+
+        let current_version = self.snapshot.as_ref().map(|t| t.version()).unwrap_or(0);
+        let snapshot: SnapshotMetadata = D::deserialize(&signed_snapshot.signed())?;
+
+        if snapshot.version() != snapshot_description.version() {
+            return Err(Error::VerificationFailure(format!(
+                "The timestamp metadata reported that the snapshot metadata should be at \
+                version {} but version {} was found instead.",
+                snapshot_description.version(),
+                snapshot.version()
+            )));
+        }
+
+        if snapshot.expires() <= &Utc::now() {
+            return Err(Error::ExpiredMetadata(Role::Snapshot));
+        }
+
+        if snapshot.version() < current_version {
+            Err(Error::VerificationFailure(format!(
+                "Attempted to roll back snapshot metdata at version {} to {}.",
+                current_version,
+                snapshot.version()
+            )))
+        } else if snapshot.version() == current_version {
+            Ok(false)
+        } else {
+            Ok(true)
+        }
+    }
+
+    fn purge_metadata(&mut self) {
+        self.snapshot = None;
+        self.timestamp = None;
+        // TODO include targets
+        // TODO include delegations
+    }
+
+    fn safe_root_ref(&self) -> Result<&RootMetadata> {
+        if self.root.expires() <= &Utc::now() {
+            return Err(Error::ExpiredMetadata(Role::Root));
+        }
+        Ok(&self.root)
+    }
+
+    fn safe_timestamp_ref(&self) -> Result<&TimestampMetadata> {
+        match &self.timestamp {
+            &Some(ref ts) => {
+                if self.root.expires() <= &Utc::now() {
+                    return Err(Error::ExpiredMetadata(Role::Root));
+                }
+                Ok(ts)
+            }
+            &None => Err(Error::MissingMetadata(Role::Timestamp)),
+        }
+    }
+
+    fn safe_snapshot_ref(&self) -> Result<&SnapshotMetadata> {
+        match &self.snapshot {
+            &Some(ref ts) => {
+                if self.root.expires() <= &Utc::now() {
+                    return Err(Error::ExpiredMetadata(Role::Root));
+                }
+                Ok(ts)
+            }
+            &None => Err(Error::MissingMetadata(Role::Snapshot)),
+        }
+    }
 }