-
Notifications
You must be signed in to change notification settings - Fork 2
Initial Commit of Alvarium Annotator traits package #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "alvarium-annotator" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
serde = { version = "1.0.143", features = ["derive"]} | ||
serde_json = "1.0.96" | ||
thiserror = "1.0.40" | ||
ulid = "1.0.0" | ||
chrono = "0.4.22" | ||
async-trait = "0.1.68" | ||
lazy_static = "1.4.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,11 @@ | ||
# alvarium_annotator | ||
# Alvarium Annotator Core | ||
|
||
This crate contains traits, types and constants for use in the Alvarium SDK. | ||
This includes interface definitions for [Annotators](src/annotator.rs) and | ||
[Annotations](src/annotations.rs), as well as Hash, Signature, and Stream | ||
[Providers](src/providers.rs). | ||
|
||
The type definitions such as HashType and KeyAlgorithm provided flexible | ||
wrappers that can be expanded beyond the preset constants. This allows for | ||
custom annotators to be developed that still conforms to the interface | ||
patterns required for use within the SDK. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
use crate::constants::{self, AnnotationType, HashType}; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] | ||
pub struct Annotation { | ||
pub id: String, | ||
pub key: String, | ||
pub hash: HashType, | ||
pub host: String, | ||
pub kind: AnnotationType, | ||
pub signature: String, | ||
#[serde(rename = "isSatisfied")] | ||
pub is_satisfied: bool, | ||
pub timestamp: String, | ||
} | ||
|
||
#[derive(Clone, PartialEq, Default, Serialize, Deserialize)] | ||
pub struct AnnotationList { | ||
pub items: Vec<Annotation>, | ||
} | ||
|
||
impl Annotation { | ||
pub fn new( | ||
key: &str, | ||
hash: HashType, | ||
host: &str, | ||
kind: AnnotationType, | ||
is_satisfied: bool, | ||
) -> Self { | ||
let timestamp = chrono::Local::now().to_rfc3339(); | ||
Annotation { | ||
id: ulid::Ulid::new().to_string(), | ||
key: key.to_string(), | ||
hash, | ||
host: host.to_string(), | ||
kind, | ||
signature: String::new(), | ||
is_satisfied, | ||
timestamp, | ||
} | ||
} | ||
|
||
pub fn with_signature(&mut self, signature: &str) { | ||
self.signature = signature.to_string() | ||
} | ||
|
||
pub fn validate_base(&self) -> bool { | ||
self.hash.is_base_hash_type() && self.kind.is_base_annotation_type() | ||
} | ||
} | ||
|
||
pub fn mock_annotation() -> Annotation { | ||
let key = "The hash of the contents"; | ||
let hash = constants::SHA256_HASH.clone(); | ||
let host = "Host Device"; | ||
let kind = constants::ANNOTATION_SOURCE.clone(); | ||
let satisfied = true; | ||
|
||
Annotation::new(key, hash, host, kind, satisfied) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
use crate::Annotation; | ||
|
||
pub trait Annotator { | ||
type Error: std::error::Error; | ||
fn annotate(&mut self, data: &[u8]) -> Result<Annotation, Self::Error>; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
use crate::errors::{Error, Result}; | ||
use lazy_static::lazy_static; | ||
use serde::{Deserialize, Serialize}; | ||
use std::ops::Deref; | ||
|
||
pub trait Validate { | ||
fn validate(&self) -> bool; | ||
} | ||
|
||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] | ||
pub struct HashType(pub String); | ||
|
||
lazy_static! { | ||
pub static ref MD5_HASH: HashType = HashType(String::from("md5")); | ||
pub static ref SHA256_HASH: HashType = HashType(String::from("sha256")); | ||
pub static ref NO_HASH: HashType = HashType(String::from("none")); | ||
} | ||
|
||
impl HashType { | ||
pub fn is_base_hash_type(&self) -> bool { | ||
self == MD5_HASH.deref() || self == SHA256_HASH.deref() || self == NO_HASH.deref() | ||
} | ||
} | ||
|
||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] | ||
pub struct KeyAlgorithm(pub String); | ||
|
||
lazy_static! { | ||
pub static ref ED25519_KEY: KeyAlgorithm = KeyAlgorithm(String::from("ed25519")); | ||
} | ||
|
||
impl KeyAlgorithm { | ||
pub fn is_base_key_algorithm(&self) -> bool { | ||
self == ED25519_KEY.deref() | ||
} | ||
} | ||
|
||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] | ||
pub struct StreamType(pub String); | ||
|
||
lazy_static! { | ||
pub static ref DEMIA_STREAM: StreamType = StreamType(String::from("demia")); | ||
pub static ref MOCK_STREAM: StreamType = StreamType(String::from("mock")); | ||
pub static ref MQTT_STREAM: StreamType = StreamType(String::from("mqtt")); | ||
} | ||
|
||
impl StreamType { | ||
pub fn is_base_stream_type(&self) -> bool { | ||
self == DEMIA_STREAM.deref() || self == MOCK_STREAM.deref() || self == MQTT_STREAM.deref() | ||
} | ||
} | ||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] | ||
pub struct AnnotationType(pub String); | ||
|
||
lazy_static! { | ||
pub static ref ANNOTATION_PKI: AnnotationType = AnnotationType(String::from("pki")); | ||
pub static ref ANNOTATION_SOURCE: AnnotationType = AnnotationType(String::from("source")); | ||
pub static ref ANNOTATION_TLS: AnnotationType = AnnotationType(String::from("tls")); | ||
pub static ref ANNOTATION_TPM: AnnotationType = AnnotationType(String::from("tpm")); | ||
} | ||
|
||
impl AnnotationType { | ||
pub fn kind(&self) -> &str { | ||
&self.0 | ||
} | ||
pub fn is_base_annotation_type(&self) -> bool { | ||
self == ANNOTATION_PKI.deref() | ||
|| self == ANNOTATION_SOURCE.deref() | ||
|| self == ANNOTATION_TLS.deref() | ||
|| self == ANNOTATION_TPM.deref() | ||
} | ||
} | ||
|
||
impl TryFrom<&str> for AnnotationType { | ||
type Error = Error; | ||
fn try_from(kind: &str) -> Result<Self> { | ||
match kind { | ||
"source" => Ok(ANNOTATION_SOURCE.clone()), | ||
"pki" => Ok(ANNOTATION_PKI.clone()), | ||
"tls" => Ok(ANNOTATION_TLS.clone()), | ||
"tpm" => Ok(ANNOTATION_TPM.clone()), | ||
_ => Err(Error::UnknownAnnotation), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | ||
pub struct SdkAction(pub String); | ||
|
||
lazy_static! { | ||
pub static ref ACTION_CREATE: SdkAction = SdkAction(String::from("create")); | ||
pub static ref ACTION_MUTATE: SdkAction = SdkAction(String::from("mutate")); | ||
pub static ref ACTION_TRANSIT: SdkAction = SdkAction(String::from("transit")); | ||
pub static ref ACTION_PUBLISH: SdkAction = SdkAction(String::from("publish")); | ||
} | ||
|
||
impl SdkAction { | ||
pub fn is_base_action(&self) -> bool { | ||
self == ACTION_CREATE.deref() | ||
|| self == ACTION_MUTATE.deref() | ||
|| self == ACTION_TRANSIT.deref() | ||
|| self == ACTION_PUBLISH.deref() | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
use thiserror::Error; | ||
|
||
pub type Result<T> = core::result::Result<T, Error>; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum Error { | ||
#[error("unknown annotation type")] | ||
UnknownAnnotation, | ||
#[error("annotation failed to serialize: {0}")] | ||
AnnotationSerialize(serde_json::Error), | ||
|
||
#[cfg(test)] | ||
#[error("provider failed to sign")] | ||
SignatureError, | ||
#[cfg(test)] | ||
#[error("provider failed to verify")] | ||
VerificationError, | ||
} | ||
|
||
impl From<serde_json::Error> for Error { | ||
fn from(err: serde_json::Error) -> Self { | ||
Error::AnnotationSerialize(err) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
mod annotations; | ||
mod annotator; | ||
mod errors; | ||
mod providers; | ||
mod config { | ||
pub trait StreamConfigWrapper { | ||
fn stream_type(&self) -> &crate::constants::StreamType; | ||
} | ||
} | ||
|
||
pub use annotations::*; | ||
pub use annotator::Annotator; | ||
pub use config::StreamConfigWrapper; | ||
pub use errors::Error; | ||
pub use providers::*; | ||
|
||
pub mod constants; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
mod hash_providers { | ||
|
||
pub trait HashProvider { | ||
fn derive(&self, data: &[u8]) -> String; | ||
} | ||
|
||
pub fn derive_hash<H: HashProvider>(hash_type: H, data: &[u8]) -> String { | ||
hash_type.derive(data) | ||
} | ||
|
||
#[cfg(test)] | ||
mod hash_provider_tests { | ||
use crate::HashProvider; | ||
|
||
struct MockHashProvider {} | ||
|
||
impl HashProvider for MockHashProvider { | ||
fn derive(&self, _data: &[u8]) -> String { | ||
"Derived".to_string() | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_mock_derive() { | ||
let hash_provider = MockHashProvider {}; | ||
let derived = hash_provider.derive("data".as_bytes()); | ||
assert_eq!("Derived", derived) | ||
} | ||
} | ||
} | ||
|
||
mod signature_provider { | ||
use crate::Annotation; | ||
|
||
pub trait SignProvider { | ||
type Error: std::error::Error; | ||
fn sign(&self, content: &[u8]) -> Result<String, Self::Error>; | ||
fn verify(&self, content: &[u8], signed: &[u8]) -> Result<bool, Self::Error>; | ||
} | ||
|
||
pub fn serialise_and_sign<P>(provider: &P, annotation: &Annotation) -> Result<String, P::Error> | ||
where | ||
P: SignProvider, | ||
<P as SignProvider>::Error: From<serde_json::Error> | ||
{ | ||
let serialised = serde_json::to_vec(annotation)?; | ||
provider.sign(&serialised) | ||
} | ||
|
||
#[cfg(test)] | ||
mod annotation_utility_tests { | ||
use super::serialise_and_sign; | ||
use crate::annotations::mock_annotation; | ||
use crate::errors::{Error, Result}; | ||
use crate::SignProvider; | ||
|
||
struct MockSignProvider { | ||
pub public: String, | ||
pub private: String, | ||
} | ||
|
||
impl SignProvider for MockSignProvider { | ||
type Error = crate::errors::Error; | ||
fn sign(&self, _content: &[u8]) -> Result<String> { | ||
match self.private.as_str().eq("A known and correct key") { | ||
true => Ok("Signed".to_string()), | ||
false => Err(Error::SignatureError), | ||
} | ||
} | ||
|
||
fn verify(&self, _content: &[u8], _signed: &[u8]) -> Result<bool> { | ||
match self.public.as_str().eq("A known and correct key") { | ||
true => Ok(true), | ||
false => Err(Error::VerificationError), | ||
} | ||
} | ||
} | ||
|
||
#[test] | ||
fn mock_sign_provider() { | ||
let correct_key = "A known and correct key".to_string(); | ||
let unknown_key = "An unknown key".to_string(); | ||
|
||
let mock_provider = MockSignProvider { | ||
private: correct_key.clone(), | ||
public: correct_key.clone(), | ||
}; | ||
|
||
let bad_mock_provider = MockSignProvider { | ||
private: unknown_key.clone(), | ||
public: unknown_key.clone(), | ||
}; | ||
|
||
let annotation = mock_annotation(); | ||
|
||
let failed_signature = serialise_and_sign(&bad_mock_provider, &annotation); | ||
assert!(failed_signature.is_err()); | ||
let signature = serialise_and_sign(&mock_provider, &annotation); | ||
assert!(signature.is_ok()); | ||
|
||
let ann_bytes = serde_json::to_vec(&annotation).unwrap(); | ||
let failed_verify = bad_mock_provider.verify("Content".as_bytes(), &ann_bytes); | ||
assert!(failed_verify.is_err()); | ||
let verified = mock_provider.verify("Content".as_bytes(), &ann_bytes); | ||
assert!(verified.is_ok()) | ||
} | ||
} | ||
} | ||
|
||
mod stream_provider { | ||
use crate::constants::SdkAction; | ||
use crate::StreamConfigWrapper; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
pub struct MessageWrapper<'a> { | ||
pub action: SdkAction, | ||
#[serde(rename = "messageType")] | ||
pub message_type: &'a str, | ||
pub content: &'a str, | ||
} | ||
|
||
#[async_trait::async_trait] | ||
pub trait Publisher: Sized { | ||
type StreamConfig: StreamConfigWrapper; | ||
type Error: std::error::Error; | ||
async fn new(cfg: &Self::StreamConfig) -> Result<Self, Self::Error>; | ||
async fn close(&mut self) -> Result<(), Self::Error>; | ||
async fn connect(&mut self) -> Result<(), Self::Error>; | ||
async fn reconnect(&mut self) -> Result<(), Self::Error>; | ||
async fn publish(&mut self, msg: MessageWrapper<'_>) -> Result<(), Self::Error>; | ||
} | ||
} | ||
|
||
pub use hash_providers::{derive_hash, HashProvider}; | ||
pub use signature_provider::{serialise_and_sign, SignProvider}; | ||
pub use stream_provider::{MessageWrapper, Publisher}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.