mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-21 17:07:44 +02:00
Moving krill_commons into workspace.
This commit is contained in:
@@ -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 {}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user