Moving krill_commons into workspace.

This commit is contained in:
Tim Bruijnzeels
2019-04-11 17:40:25 +02:00
parent 37fb82c1a2
commit fc7cbbaffc
23 changed files with 4565 additions and 8 deletions
+227
View File
@@ -0,0 +1,227 @@
//! Support for admin tasks, such as managing publishers and RFC8181 clients
use rpki::uri;
use crate::api::Link;
use crate::eventsourcing::AggregateId;
use crate::util::ext_serde;
use std::fmt;
use std::fmt::Display;
//------------ PublisherHandle -----------------------------------------------
/// A type for referring to publishers, both in the api as well as to the
/// aggregates.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct PublisherHandle(AggregateId);
impl PublisherHandle {
pub fn name(&self) -> &str {
self.0.as_str()
}
}
impl From<&str> for PublisherHandle {
fn from(s: &str) -> Self {
PublisherHandle::from(AggregateId::from(s))
}
}
impl From<String> for PublisherHandle {
fn from(s: String) -> Self { PublisherHandle::from(AggregateId::from(s))}
}
impl From<AggregateId> for PublisherHandle {
fn from(id: AggregateId) -> Self {
PublisherHandle(id)
}
}
impl From<&AggregateId> for PublisherHandle {
fn from(id: &AggregateId) -> Self {
PublisherHandle(id.clone())
}
}
impl AsRef<str> for PublisherHandle {
fn as_ref(&self) -> &str {
self.name()
}
}
impl AsRef<AggregateId> for PublisherHandle {
fn as_ref(&self) -> &AggregateId {
&self.0
}
}
impl Display for PublisherHandle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
//------------ PublisherRequest ----------------------------------------------
/// This type defines request for a new Publisher (CA that is allowed to
/// publish).
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PublisherRequest {
handle: String,
/// The token used by the API
token: String,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
base_uri: uri::Rsync,
}
impl PublisherRequest {
pub fn new(
handle: String,
token: String,
base_uri: uri::Rsync,
) -> Self {
PublisherRequest {
handle,
token,
base_uri,
}
}
}
impl PublisherRequest {
pub fn handle(&self) -> &String {
&self.handle
}
pub fn token(&self) -> &String {
&self.token
}
pub fn base_uri(&self) -> &uri::Rsync {
&self.base_uri
}
/// Return all the values (handle, token, base_uri).
pub fn unwrap(self) -> (String, String, uri::Rsync) {
(self.handle, self.token, self.base_uri)
}
}
impl PartialEq for PublisherRequest {
fn eq(&self, other: &PublisherRequest) -> bool {
self.handle == other.handle &&
self.base_uri == other.base_uri
}
}
impl Eq for PublisherRequest {}
//------------ PublisherSummaryInfo ------------------------------------------
/// Defines a summary of publisher information to be used in the publisher
/// list.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublisherSummary {
id: String,
links: Vec<Link>
}
impl PublisherSummary {
pub fn from(
handle: &PublisherHandle,
path_publishers: &str
) -> PublisherSummary {
let mut links = Vec::new();
let self_link = Link {
rel: "self".to_string(),
link: format!("{}/{}", path_publishers, handle)
};
links.push(self_link);
PublisherSummary {
id: handle.to_string(),
links
}
}
pub fn id(&self) -> &str { &self.id }
}
//------------ PublisherList -------------------------------------------------
/// This type represents a list of (all) current publishers to show in the API
#[derive(Clone, Eq, Debug, Deserialize, PartialEq, Serialize)]
pub struct PublisherList {
publishers: Vec<PublisherSummary>
}
impl PublisherList {
pub fn build(
publishers: &[PublisherHandle],
path_publishers: &str
) -> PublisherList {
let publishers: Vec<PublisherSummary> = publishers.iter().map(|p|
PublisherSummary::from(&p, path_publishers)
).collect();
PublisherList {
publishers
}
}
pub fn publishers(&self) -> &Vec<PublisherSummary> {
&self.publishers
}
}
//------------ PublisherDetails ----------------------------------------------
/// This type defines the publisher details for:
/// /api/v1/publishers/{handle}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PublisherDetails {
handle: String,
deactivated: bool,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri"
)]
base_uri: uri::Rsync,
}
impl PublisherDetails {
pub fn new(handle: &str, deactivated: bool, base_uri: &uri::Rsync) -> Self {
PublisherDetails {
handle: handle.to_string(),
deactivated,
base_uri: base_uri.clone()
}
}
pub fn handle(&self) -> &str { &self.handle }
pub fn deactivated(&self) -> bool { self.deactivated }
pub fn base_uri(&self) -> &uri::Rsync { &self.base_uri }
}
impl PartialEq for PublisherDetails {
fn eq(&self, other: &PublisherDetails) -> bool {
match (serde_json::to_string(self), serde_json::to_string(other)) {
(Ok(ser_self), Ok(ser_other)) => ser_self == ser_other,
_ => false
}
}
}
impl Eq for PublisherDetails {}
+318
View File
@@ -0,0 +1,318 @@
//! Data structures for the API, shared between client and server.
pub mod admin;
pub mod publication;
pub mod rrdp;
use bytes::Bytes;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::util::sha256;
//------------ Base64 --------------------------------------------------------
/// This type contains a base64 encoded structure. The publication protocol
/// deals with objects in their base64 encoded form.
///
/// Note that we store this in a Bytes to make it cheap to clone this.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Base64(Bytes);
impl Base64 {
pub fn from_content(content: &[u8]) -> Self {
Base64::from(base64::encode(content))
}
/// Decodes into bytes (e.g. for saving to disk for rcync)
pub fn to_bytes(&self) -> Bytes {
Bytes::from(base64::decode(&self.0).unwrap())
}
pub fn to_hex_hash(&self) -> String {
hex::encode(sha256(&self.to_bytes()))
}
pub fn to_encoded_hash(&self) -> EncodedHash {
EncodedHash::from(self.to_hex_hash())
}
}
impl AsRef<str> for Base64 {
fn as_ref(&self) -> &str {
use std::str;
str::from_utf8(&self.0).unwrap()
}
}
impl From<String> for Base64 {
fn from(s: String) -> Self {
Base64(Bytes::from(s))
}
}
impl ToString for Base64 {
fn to_string(&self) -> String {
unsafe {
String::from_utf8_unchecked(self.0.to_vec())
}
}
}
impl Serialize for Base64 {
fn serialize<S>(
&self, serializer: S
) -> Result<S::Ok, S::Error> where S: Serializer {
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Base64 {
fn deserialize<D>(
deserializer: D
) -> Result<Base64, D::Error> where D: Deserializer<'de> {
let string = String::deserialize(deserializer)?;
Ok(Base64::from(string))
}
}
//------------ EncodedHash ---------------------------------------------------
/// This type contains a hex encoded sha256 hash.
///
/// Note that we store this in a Bytes for cheap cloning.
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct EncodedHash(Bytes);
impl EncodedHash {
pub fn from_content(content: &[u8]) -> Self {
let sha256 = sha256(content);
let hex = hex::encode(sha256);
EncodedHash::from(hex)
}
}
impl AsRef<str> for EncodedHash {
fn as_ref(&self) -> &str {
use std::str;
str::from_utf8(&self.0).unwrap()
}
}
impl From<String> for EncodedHash {
fn from(s: String) -> Self {
EncodedHash(Bytes::from(s.to_lowercase()))
}
}
impl ToString for EncodedHash {
fn to_string(&self) -> String {
unsafe {
String::from_utf8_unchecked(self.0.to_vec())
}
}
}
impl Serialize for EncodedHash {
fn serialize<S>(
&self, serializer: S
) -> Result<S::Ok, S::Error> where S: Serializer {
self.to_string().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for EncodedHash {
fn deserialize<D>(
deserializer: D
) -> Result<EncodedHash, D::Error> where D: Deserializer<'de> {
let string = String::deserialize(deserializer)?;
Ok(EncodedHash::from(string))
}
}
//------------ Link ----------------------------------------------------------
/// Defines a link element to include as part of a links array in a Json
/// response.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Link {
rel: String,
link: String
}
//------------ ErrorResponse --------------------------------------------------
/// Defines an error response. Codes are unique and documented here:
/// https://rpki.readthedocs.io/en/latest/krill/pub/api.html#error-responses
#[derive(Debug, Deserialize, Serialize)]
pub struct ErrorResponse {
code: usize,
msg: String
}
impl ErrorResponse {
pub fn new(code: usize, msg: String) -> Self { ErrorResponse { code, msg }}
pub fn code(&self) -> usize { self.code }
pub fn msg(&self) -> &str { &self.msg }
}
impl Into<ErrorCode> for ErrorResponse {
fn into(self) -> ErrorCode {
ErrorCode::from(self.code)
}
}
/// This type defines externally visible errors that the API may return.
#[derive(Clone, Debug, Display, Eq, PartialEq)]
pub enum ErrorCode {
// 1000s (User Input Errors)
#[display(fmt="Submitted Json cannot be parsed")]
InvalidJson,
#[display(fmt="Invalid RFC8183 Publisher Request")]
InvalidPublisherRequest,
#[display(fmt="Issue with submitted publication XML")]
InvalidPublicationXml,
#[display(fmt="Invalid handle name")]
InvalidHandle,
#[display(fmt="Handle already in use")]
DuplicateHandle,
// 2000s (Authorisation and Consistency issues)
#[display(fmt="Unknown publisher")]
UnknownPublisher,
#[display(fmt="Submitted protocol CMS does not validate")]
CmsValidation,
#[display(fmt="Base URI for publisher is outside of publisher base URI")]
InvalidBaseUri,
#[display(fmt="Out of sync with server, please send requests for instances sequentially")]
ConcurrentModification,
#[display(fmt="Publisher has been deactivated")]
PublisherDeactivated,
#[display(fmt="Not allowed to publish outside of publisher jail")]
UriOutsideJail,
#[display(fmt="File already exists for uri (use update!)")]
ObjectAlreadyPresent,
#[display(fmt="No file found for hash at uri")]
NoObjectForHashAndOrUri,
// 3000s (Server Errors)
#[display(fmt="Cannot update internal state, issue with work_dir?")]
Persistence,
#[display(fmt="Cannot update repository, issue with repo_dir?")]
RepositoryUpdate,
#[display(fmt="Signing error, issue with openssl version or work_dir?")]
SigningError,
#[display(fmt="Proxy server error.")]
ProxyError,
#[display(fmt="Unrecognised error (this is a bug)")]
Unknown
}
impl From<usize> for ErrorCode {
fn from(n: usize) -> Self {
match n {
1001 => ErrorCode::InvalidJson,
1002 => ErrorCode::InvalidPublisherRequest,
1003 => ErrorCode::InvalidPublicationXml,
1004 => ErrorCode::InvalidHandle,
2001 => ErrorCode::UnknownPublisher,
2002 => ErrorCode::CmsValidation,
2003 => ErrorCode::InvalidBaseUri,
2004 => ErrorCode::ConcurrentModification,
2005 => ErrorCode::PublisherDeactivated,
2006 => ErrorCode::UriOutsideJail,
2007 => ErrorCode::ObjectAlreadyPresent,
2008 => ErrorCode::NoObjectForHashAndOrUri,
2009 => ErrorCode::DuplicateHandle,
3001 => ErrorCode::Persistence,
3002 => ErrorCode::RepositoryUpdate,
3003 => ErrorCode::SigningError,
3004 => ErrorCode::ProxyError,
_ => ErrorCode::Unknown
}
}
}
impl Into<ErrorResponse> for ErrorCode {
fn into(self) -> ErrorResponse {
let code = match self {
ErrorCode::InvalidJson => 1001,
ErrorCode::InvalidPublisherRequest => 1002,
ErrorCode::InvalidPublicationXml => 1003,
ErrorCode::InvalidHandle => 1004,
ErrorCode::UnknownPublisher => 2001,
ErrorCode::CmsValidation => 2002,
ErrorCode::InvalidBaseUri => 2003,
ErrorCode::ConcurrentModification => 2004,
ErrorCode::PublisherDeactivated => 2005,
ErrorCode::UriOutsideJail => 2006,
ErrorCode::ObjectAlreadyPresent => 2007,
ErrorCode::NoObjectForHashAndOrUri => 2008,
ErrorCode::DuplicateHandle => 2009,
ErrorCode::Persistence => 3001,
ErrorCode::RepositoryUpdate => 3002,
ErrorCode::SigningError => 3003,
ErrorCode::ProxyError => 3004,
ErrorCode::Unknown => 65535
};
let msg = format!("{}", self);
ErrorResponse { code, msg }
}
}
//------------ Tests ---------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_convert_code_to_number_and_back() {
fn test_code(number_to_test: usize) {
let code = ErrorCode::from(number_to_test);
let response: ErrorResponse = code.into();
assert_eq!(number_to_test, response.code());
}
for n in 1001..1005 {
test_code(n)
}
for n in 2001..2010 {
test_code(n)
}
for n in 3001..3005 {
test_code(n)
}
}
}
+298
View File
@@ -0,0 +1,298 @@
//! Support for requests sent to the Json API
use rpki::uri;
use crate::api::{ Base64, EncodedHash };
use crate::util::ext_serde;
use crate::util::file::CurrentFile;
//------------ PublishRequest ------------------------------------------------
/// This type provides a convenience wrapper to contain the request found
/// inside of a validated RFC8181 request.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PublishRequest {
List, // See https://tools.ietf.org/html/rfc8181#section-2.3
Delta(PublishDelta)
}
//------------ PublishDelta ------------------------------------------------
/// This type represents a multi element query as described in
/// https://tools.ietf.org/html/rfc8181#section-3.7
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct PublishDelta {
publishes: Vec<Publish>,
updates: Vec<Update>,
withdraws: Vec<Withdraw>
}
impl PublishDelta {
pub fn new(
publishes: Vec<Publish>,
updates: Vec<Update>,
withdraws: Vec<Withdraw>
) -> Self {
PublishDelta { publishes, updates, withdraws }
}
pub fn publishes(&self) -> &Vec<Publish> {
&self.publishes
}
pub fn updates(&self) -> &Vec<Update> {
&self.updates
}
pub fn withdraws(&self) -> &Vec<Withdraw> {
&self.withdraws
}
pub fn len(&self) -> usize {
self.publishes.len() + self.updates.len() + self.withdraws.len()
}
pub fn is_empty(&self) -> bool { self.len() == 0 }
pub fn unwrap(self) -> (Vec<Publish>, Vec<Update>, Vec<Withdraw>) {
(self.publishes, self.updates, self.withdraws)
}
}
//------------ PublishDeltaBuilder -------------------------------------------
#[derive(Default)]
pub struct PublishDeltaBuilder {
publishes: Vec<Publish>,
updates: Vec<Update>,
withdraws: Vec<Withdraw>
}
impl PublishDeltaBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn add_publish(&mut self, publish: Publish) {
self.publishes.push(publish);
}
pub fn add_update(&mut self, update: Update) {
self.updates.push(update);
}
pub fn add_withdraw(&mut self, withdraw: Withdraw) {
self.withdraws.push(withdraw);
}
pub fn finish(self) -> PublishDelta {
PublishDelta {
publishes: self.publishes,
updates: self.updates,
withdraws: self.withdraws
}
}
}
//------------ Publish ------------------------------------------------------
/// Type representing a json equivalent to the publish element, that does not
/// update any existing object, defined in:
/// https://tools.ietf.org/html/rfc8181#section-3.1
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Publish {
tag: Option<String>,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
content: Base64
}
impl Publish {
pub fn new(tag: Option<String>, uri: uri::Rsync, content: Base64) -> Self {
Publish { tag, uri, content }
}
pub fn with_hash_tag(uri: uri::Rsync, content: Base64) -> Self {
let tag = Some(content.to_hex_hash());
Publish { tag, uri, content }
}
pub fn tag(&self) -> &Option<String> { &self.tag }
pub fn tag_for_xml(&self) -> String {
match &self.tag {
None => "".to_string(),
Some(t) => t.clone()
}
}
pub fn uri(&self) -> &uri::Rsync{ &self.uri}
pub fn content(&self) -> &Base64{ &self.content }
pub fn unwrap(self) -> (Option<String>, uri::Rsync, Base64) {
(self.tag, self.uri, self.content)
}
}
//------------ Update --------------------------------------------------------
/// Type representing a json equivalent to the publish element, that updates
/// an existing object:
/// https://tools.ietf.org/html/rfc8181#section-3.2
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Update {
tag: Option<String>,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
content: Base64,
hash: EncodedHash,
}
impl Update {
pub fn new(
tag: Option<String>,
uri: uri::Rsync,
content: Base64,
old_hash: EncodedHash
) -> Self {
Update { tag, uri, content, hash: old_hash }
}
pub fn with_hash_tag(
uri: uri::Rsync,
content: Base64,
old_hash: EncodedHash
) -> Self {
let tag = Some(content.to_hex_hash());
Update { tag, uri, content, hash: old_hash }
}
pub fn tag(&self) -> &Option<String> { &self.tag }
pub fn tag_for_xml(&self) -> String {
match &self.tag {
Some(t) => t.clone(),
None => "".to_string()
}
}
pub fn uri(&self) -> &uri::Rsync { &self.uri}
pub fn content(&self) -> &Base64 { &self.content }
pub fn hash(&self) -> &EncodedHash { &self.hash }
pub fn unwrap(self) -> (Option<String>, uri::Rsync, Base64, EncodedHash) {
(self.tag, self.uri, self.content, self.hash)
}
}
//------------ Withdraw ------------------------------------------------------
/// Type representing a json equivalent to a withdraw element that removes an
/// object from the repository:
/// https://tools.ietf.org/html/rfc8181#section-3.3
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Withdraw {
tag: Option<String>,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
hash: EncodedHash,
}
impl Withdraw {
pub fn new(tag: Option<String>, uri: uri::Rsync, hash: EncodedHash) -> Self {
Withdraw { tag, uri, hash }
}
pub fn with_hash_tag(uri: uri::Rsync, hash: EncodedHash) -> Self {
let tag = Some(hash.to_string());
Withdraw { tag, uri, hash }
}
pub fn from_list_element(el: &ListElement) -> Self {
Withdraw {
tag: None,
uri: el.uri().clone(),
hash: el.hash().clone()
}
}
pub fn tag(&self) -> &Option<String> { &self.tag }
pub fn tag_for_xml(&self) -> String {
match &self.tag {
Some(t) => t.clone(),
None => "".to_string()
}
}
pub fn uri(&self) -> &uri::Rsync { &self.uri}
pub fn hash(&self) -> &EncodedHash { &self.hash }
pub fn unwrap(self) -> (Option<String>, uri::Rsync, EncodedHash) {
(self.tag, self.uri, self.hash)
}
}
//------------ PublishReply --------------------------------------------------
/// This type is used to wrap API responses for publication requests.
pub enum PublishReply {
Success, // See https://tools.ietf.org/html/rfc8181#section-3.4
List(ListReply)
}
//------------ ListReply -----------------------------------------------------
/// This type represents the list reply as described in
/// https://tools.ietf.org/html/rfc8181#section-2.3
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ListReply {
elements: Vec<ListElement>
}
impl ListReply {
pub fn new(elements: Vec<ListElement>) -> Self {
ListReply { elements }
}
pub fn from_files(files: Vec<CurrentFile>) -> Self {
let elements = files.into_iter().map(|f| f.into_list_element()).collect();
ListReply { elements }
}
pub fn elements(&self) -> &Vec<ListElement> {
&self.elements
}
}
//------------ ListElement ---------------------------------------------------
/// This type represents a single object that is published at a publication
/// server.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ListElement {
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
hash: EncodedHash
}
impl ListElement {
pub fn new(uri: uri::Rsync, hash: EncodedHash) -> Self {
ListElement { uri, hash }
}
pub fn uri(&self) -> &uri::Rsync { &self.uri }
pub fn hash(&self) -> &EncodedHash { &self.hash }
}
+671
View File
@@ -0,0 +1,671 @@
//! Data objects used in the (RRDP) repository. I.e. the publish, update, and
//! withdraw elements, as well as the notification, snapshot and delta file
//! definitions.
use std::collections::HashMap;
use std::io;
use std::path::PathBuf;
use bytes::Bytes;
use rpki::uri;
use crate::api::publication;
use crate::api::Base64;
use crate::api::EncodedHash;
use crate::util::ext_serde;
use crate::util::file;
use crate::util::Time;
use crate::util::xml::XmlWriter;
const VERSION: &str = "1";
const NS: &str = "http://www.ripe.net/rpki/rrdp";
//------------ PublishElement ------------------------------------------------
/// The publishes as used in the RRDP protocol.
///
/// Note that the difference with the publication protocol is the absence of
/// the tag.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PublishElement {
base64: Base64,
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync
}
impl PublishElement {
pub fn new(base64: Base64, uri: uri::Rsync) -> Self {
PublishElement { base64, uri }
}
pub fn base64(&self) -> &Base64 { &self.base64 }
pub fn uri(&self) -> &uri::Rsync { &self.uri }
}
impl From<publication::Publish> for PublishElement {
fn from(p: publication::Publish) -> Self {
let (_tag, uri, base64) = p.unwrap();
PublishElement { uri, base64 }
}
}
//------------ UpdateElement -------------------------------------------------
/// The updates as used in the RRDP protocol.
///
/// Note that the difference with the publication protocol is the absence of
/// the tag.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UpdateElement {
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
hash: EncodedHash,
base64: Base64
}
impl UpdateElement {
pub fn uri(&self) -> &uri::Rsync { &self.uri }
pub fn hash(&self) -> &EncodedHash { &self.hash }
pub fn base64(&self) -> &Base64 { &self.base64 }
}
impl From<publication::Update> for UpdateElement {
fn from(u: publication::Update) -> Self {
let (_tag, uri, base64, hash) = u.unwrap();
UpdateElement { uri, base64, hash }
}
}
impl Into<PublishElement> for UpdateElement {
fn into(self) -> PublishElement {
PublishElement { uri: self.uri, base64: self.base64 }
}
}
//------------ WithdrawElement -----------------------------------------------
/// The withdraws as used in the RRDP protocol.
///
/// Note that the difference with the publication protocol is the absence of
/// the tag.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WithdrawElement {
#[serde(
deserialize_with = "ext_serde::de_rsync_uri",
serialize_with = "ext_serde::ser_rsync_uri")]
uri: uri::Rsync,
hash: EncodedHash
}
impl WithdrawElement {
pub fn uri(&self) -> &uri::Rsync { &self.uri }
pub fn hash(&self) -> &EncodedHash { &self.hash }
}
impl From<publication::Withdraw> for WithdrawElement {
fn from(w: publication::Withdraw) -> Self {
let (_tag, uri, hash) = w.unwrap();
WithdrawElement { uri, hash }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Notification {
session: String,
serial: u64,
time: Time,
snapshot: SnapshotRef,
deltas: Vec<DeltaRef>,
old_refs: Vec<(Time, FileRef)>
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NotificationUpdate {
time: Time,
session: Option<String>,
snapshot: SnapshotRef,
delta: DeltaRef,
last_delta: u64
}
impl NotificationUpdate {
pub fn new(
time: Time,
session: Option<String>,
snapshot: SnapshotRef,
delta: DeltaRef,
last_delta: u64
) -> Self {
NotificationUpdate { time, session, snapshot, delta, last_delta }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NotificationCreate {
session: String,
snapshot: SnapshotRef
}
impl NotificationUpdate {
pub fn unwrap(self) -> (Time, Option<String>, SnapshotRef, DeltaRef, u64) {
(self.time, self.session, self.snapshot, self.delta, self.last_delta)
}
}
impl Notification {
pub fn old_refs(&self) -> &Vec<(Time, FileRef)> {
&self.old_refs
}
pub fn update(&mut self, update: NotificationUpdate) {
let (time, session_opt, snapshot, delta, last_delta) = update.unwrap();
if let Some(session) = session_opt {
self.session = session;
}
self.serial += 1;
self.time = time;
let mut refs_to_retire = vec![];
refs_to_retire.push((Time::now(), self.snapshot.clone()));
self.snapshot = snapshot;
for d in &self.deltas {
if d.serial < last_delta {
refs_to_retire.push((Time::now(), d.file_ref.clone()));
}
}
self.deltas.insert(0, delta);
self.deltas.retain(|delta| delta.serial >= last_delta);
self.old_refs.append(&mut refs_to_retire);
}
/// Cleans up all old references from before the given time.
pub fn clean_up(&mut self, t: Time) {
self.old_refs.retain(|old_ref| {! old_ref.0.on_or_before(&t)})
}
pub fn create(session: String, snapshot: SnapshotRef) -> Self {
Notification {
session,
serial: 0,
time: Time::now(),
snapshot,
deltas: vec![],
old_refs: vec![]
}
}
pub fn write_xml(&self, path: &PathBuf) -> Result<(), io::Error> {
debug!("Writing notification file: {}", path.to_string_lossy());
let mut file = file::create_file_with_path(&path)?;
XmlWriter::encode_to_file(& mut file, |w| {
let a = [
("xmlns", NS),
("version", VERSION),
("session_id", self.session.as_ref()),
("serial", &format!("{}", self.serial)),
];
w.put_element(
"notification",
Some(&a),
|w| {
{
// snapshot ref
let uri = self.snapshot.uri.to_string();
let a = [
("uri", uri.as_str()),
("hash", self.snapshot.hash.as_ref())
];
w.put_element(
"snapshot",
Some(&a),
|w| { w.empty() }
)?;
}
{
// delta refs
for delta in &self.deltas {
let serial = format!("{}", delta.serial);
let uri = delta.file_ref.uri.to_string();
let a = [
("serial", serial.as_ref()),
("uri", uri.as_str()),
("hash", delta.file_ref.hash.as_ref())
];
w.put_element(
"delta",
Some(&a),
|w| { w.empty() }
)?;
}
}
Ok(())
}
)
})?;
Ok(())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FileRef {
#[serde(
deserialize_with = "ext_serde::de_http_uri",
serialize_with = "ext_serde::ser_http_uri")]
uri: uri::Http,
path: PathBuf,
hash: EncodedHash,
}
impl FileRef {
pub fn new(uri: uri::Http, path: PathBuf, hash: EncodedHash) -> Self {
FileRef { uri, path, hash }
}
pub fn uri(&self) -> &uri::Http { &self.uri }
pub fn path(&self) -> &PathBuf { &self.path }
pub fn hash(&self) -> &EncodedHash { &self.hash }
}
pub type SnapshotRef = FileRef;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeltaRef {
serial: u64,
file_ref: FileRef
}
impl DeltaRef {
pub fn new(serial: u64, file_ref: FileRef) -> Self {
DeltaRef { serial, file_ref }
}
pub fn serial(&self) -> u64 { self.serial }
}
impl AsRef<FileRef> for DeltaRef {
fn as_ref(&self) -> &FileRef {
&self.file_ref
}
}
//------------ CurrentObjects ------------------------------------------------
/// Defines a current set of published elements.
///
// Note this is mapped internally for speedy access, by hash, rather than uri
// for two reasons:
// a) URIs in RPKI may change in future
// b) The publish element as it appears in an RFC8182 snapshot.xml includes
// the uri and the base64, but not the hash. So keeping the actual elements
// around means we can be more efficient in producing that output.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CurrentObjects(HashMap<EncodedHash, PublishElement>);
impl Default for CurrentObjects {
fn default() -> Self {
CurrentObjects(HashMap::new())
}
}
impl CurrentObjects {
fn elements(&self) -> Vec<&PublishElement> {
let mut res = vec![];
for el in self.0.values() {
res.push(el)
}
res
}
}
//------------ VerificationError ---------------------------------------------
/// Issues with relation to verifying deltas.
#[derive(Clone, Debug, Display)]
pub enum VerificationError {
#[display(fmt="Publishing ({}) outside of jail URI ({}) is not allowed.", _0, _1)]
UriOutsideJail(uri::Rsync, uri::Rsync),
#[display(fmt="File already exists for uri (use update!): {}", _0)]
ObjectAlreadyPresent(uri::Rsync),
#[display(fmt="File does not match hash at uri: {}", _0)]
NoObjectForHashAndOrUri(uri::Rsync),
}
impl VerificationError {
fn outside(jail: &uri::Rsync, uri: &uri::Rsync) -> Self {
VerificationError::UriOutsideJail(uri.clone(), jail.clone())
}
fn present(uri: &uri::Rsync) -> Self {
VerificationError::ObjectAlreadyPresent(uri.clone())
}
fn no_match(uri: &uri::Rsync) -> Self {
VerificationError::NoObjectForHashAndOrUri(uri.clone())
}
}
impl CurrentObjects {
fn has_match(
&self,
hash: &EncodedHash,
uri: &uri::Rsync
) -> bool {
match self.0.get(hash) {
Some(el) => el.uri() == uri,
None => false
}
}
pub fn verify_delta(
&self,
delta: &DeltaElements,
jail: &uri::Rsync
) -> Result<(), VerificationError> {
for p in delta.publishes() {
if ! jail.is_parent_of(p.uri()) {
return Err(VerificationError::outside(jail, p.uri()))
}
let hash = p.base64().to_encoded_hash();
if self.0.contains_key(&hash) {
return Err(VerificationError::present(p.uri()))
}
}
for u in delta.updates() {
if ! self.has_match(u.hash(), u.uri()) {
return Err(VerificationError::no_match(u.uri()));
}
}
for w in delta.withdraws() {
if ! self.has_match(w.hash(), w.uri()) {
return Err(VerificationError::no_match(w.uri()));
}
}
Ok(())
}
/// Applies a delta to CurrentObjects. This will asume that the delta
/// contains only valid updates for this delta.
pub fn apply_delta(&mut self, delta: DeltaElements) {
let (publishes, updates, withdraws) = delta.unwrap();
for p in publishes {
let hash = p.base64().to_encoded_hash();
self.0.insert(hash, p);
}
for u in updates {
self.0.remove(u.hash());
let p: PublishElement = u.into();
let hash = p.base64().to_encoded_hash();
self.0.insert(hash, p);
}
for w in withdraws {
self.0.remove(w.hash());
}
}
pub fn len(&self) -> usize { self.0.len() }
pub fn is_empty(&self) -> bool { self.0.is_empty() }
pub fn to_list_reply(&self) -> publication::ListReply {
let elements = self.0.iter().map(|el| {
let hash = el.0.clone();
let uri = el.1.uri().clone();
publication::ListElement::new(uri, hash)
}).collect();
publication::ListReply::new(elements)
}
}
//------------ Snapshot ------------------------------------------------------
/// A structure to contain the RRDP snapshot data.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Snapshot {
session: String,
serial: u64,
current_objects: CurrentObjects
}
impl Snapshot {
pub fn new(session: String) -> Self {
let current_objects = CurrentObjects::default();
Snapshot { session, serial: 0, current_objects }
}
pub fn apply_delta(&mut self, delta: Delta) {
let (session, serial, elements) = delta.unwrap();
self.session = session;
self.serial = serial;
self.current_objects.apply_delta(elements)
}
pub fn len(&self) -> usize { self.current_objects.len() }
pub fn is_empty(&self) -> bool { self.current_objects.is_empty() }
pub fn write_xml(&self, path: &PathBuf) -> Result<EncodedHash, io::Error> {
let vec = XmlWriter::encode_vec(|w| {
let a = [
("xmlns", NS),
("version", VERSION),
("session_id", self.session.as_ref()),
("serial", &format!("{}", self.serial)),
];
w.put_element(
"snapshot",
Some(&a),
|w| {
for el in self.current_objects.elements() {
let uri = el.uri.to_string();
let atr = [ ("uri", uri.as_ref())];
w.put_element(
"publish",
Some(&atr),
|w| {
w.put_text(el.base64.as_ref())
}
)?;
}
Ok(())
}
)
});
let bytes = Bytes::from(vec);
file::save(&bytes, path)?;
let hash = EncodedHash::from_content(&bytes);
Ok(hash)
}
}
//------------ DeltaElements -------------------------------------------------
/// Defines the elements for an RRDP delta.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DeltaElements {
publishes: Vec<PublishElement>,
updates: Vec<UpdateElement>,
withdraws: Vec<WithdrawElement>
}
impl From<publication::PublishDelta> for DeltaElements {
fn from(d: publication::PublishDelta) -> Self {
let (pbls, upds, wdrs) = d.unwrap();
let publishes = pbls.into_iter().map(PublishElement::from).collect();
let updates = upds.into_iter().map(UpdateElement::from).collect();
let withdraws = wdrs.into_iter().map(WithdrawElement::from).collect();
DeltaElements { publishes, updates, withdraws }
}
}
impl DeltaElements {
pub fn unwrap(
self
) -> (Vec<PublishElement>, Vec<UpdateElement>, Vec<WithdrawElement>) {
(self.publishes, self.updates, self.withdraws)
}
pub fn len(&self) -> usize {
self.publishes.len() + self.updates.len() + self.withdraws.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn publishes(&self) -> &Vec<PublishElement> {
&self.publishes
}
pub fn updates(&self) -> &Vec<UpdateElement> {
&self.updates
}
pub fn withdraws(&self) -> &Vec<WithdrawElement> {
&self.withdraws
}
}
//------------ Delta ---------------------------------------------------------
/// Defines an RRDP delta.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Delta {
session: String,
serial: u64,
time: Time,
elements: DeltaElements
}
impl Delta {
pub fn new(
session: String,
serial: u64,
elements: DeltaElements
) -> Self {
Delta { session, time: Time::now(), serial, elements }
}
pub fn session(&self) -> &str { &self.session }
pub fn serial(&self) -> u64 { self.serial }
pub fn time(&self) -> &Time { &self.time }
pub fn elements(&self) -> &DeltaElements { &self.elements }
/// Total number of elements
///
/// This is a cheap approximation of the size of the delta that can help
/// in determining the choice of how many deltas to include in a
/// notification file.
pub fn len(&self) -> usize { self.elements.len() }
pub fn is_empty(&self) -> bool { self.elements.is_empty() }
pub fn unwrap(self) -> (String, u64, DeltaElements) {
(self.session, self.serial, self.elements)
}
pub fn write_xml(&self, path: &PathBuf) -> Result<EncodedHash, io::Error> {
let vec = XmlWriter::encode_vec(|w| {
let a = [
("xmlns", NS),
("version", VERSION),
("session_id", self.session.as_ref()),
("serial", &format!("{}", self.serial)),
];
w.put_element(
"delta",
Some(&a),
|w| {
for el in &self.elements.publishes {
let uri = el.uri.to_string();
let atr = [ ("uri", uri.as_ref())];
w.put_element(
"publish",
Some(&atr),
|w| {
w.put_text(el.base64.as_ref())
}
)?;
}
for el in &self.elements.updates {
let uri = el.uri.to_string();
let atr = [
("uri", uri.as_ref()),
("hash", el.hash.as_ref())
];
w.put_element(
"publish",
Some(&atr),
|w| {
w.put_text(el.base64.as_ref())
}
)?;
}
for el in &self.elements.withdraws {
let uri = el.uri.to_string();
let atr = [
("uri", uri.as_ref()),
("hash", el.hash.as_ref())
];
w.put_element(
"withdraw",
Some(&atr),
|w| { w.empty() }
)?;
}
Ok(())
}
)
});
let bytes = Bytes::from(vec);
file::save(&bytes, &path)?;
let hash = EncodedHash::from_content(&bytes);
Ok(hash)
}
}