use super::{AppID, Mailbox, Mood, MySide, Nameplate, Phase, TheirSide};
use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Nameplate_ {
pub id: String,
}
impl Nameplate_ {
fn deserialize<'de, D>(de: D) -> Result<Vec<Nameplate>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: Vec<Nameplate_> = serde::Deserialize::deserialize(de)?;
Ok(value.into_iter().map(|value| Nameplate(value.id)).collect())
}
#[allow(clippy::all, dead_code)]
fn serialize<S>(value: &Vec<Nameplate>, ser: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
ser.collect_seq(value.iter().map(|value| Self {
id: value.to_string(),
}))
}
}
#[derive(Serialize, Debug, PartialEq, Eq, derive_more::Display)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "method")]
pub enum SubmitPermission {
#[display(fmt = "Hashcash {{ stamp: '{}' }}", stamp)]
Hashcash { stamp: String },
}
#[derive(Deserialize, Debug, PartialEq, Eq, Default)]
pub struct WelcomeMessage {
#[deprecated(note = "This is for the Python client")]
pub current_cli_version: Option<String>,
pub motd: Option<String>,
#[deprecated(note = "Servers should send a proper error message instead")]
pub error: Option<String>,
#[serde(rename = "permission-required")]
pub permission_required: Option<PermissionRequired>,
}
impl std::fmt::Display for WelcomeMessage {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "WelcomeMessage {{ ")?;
if let Some(motd) = &self.motd {
write!(f, "motd: '{}', ", motd)?;
}
if let Some(permission_required) = &self.permission_required {
write!(f, "permission_required: '{}', ", permission_required)?;
}
write!(f, ".. }}")?;
Ok(())
}
}
#[derive(Deserialize, Debug, PartialEq, Eq)]
pub struct PermissionRequired {
#[serde(deserialize_with = "PermissionRequired::deserialize_none")]
pub none: bool,
pub hashcash: Option<HashcashPermission>,
#[serde(flatten)]
pub other: HashMap<String, serde_json::Value>,
}
impl PermissionRequired {
fn deserialize_none<'de, D>(de: D) -> Result<bool, D::Error>
where
D: serde::Deserializer<'de>,
{
let value: Option<serde_json::Map<String, serde_json::Value>> =
serde::Deserialize::deserialize(de)?;
Ok(value.is_some())
}
}
impl std::fmt::Display for PermissionRequired {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let none_iter = std::iter::once("none").filter(|_| self.none);
let hashcash_iter = std::iter::once("hashcash").filter(|_| self.hashcash.is_some());
let other_iter = self.other.keys().map(String::as_str);
write!(
f,
"PermissionRequired {{ one of: {:?}}}",
none_iter.chain(hashcash_iter).chain(other_iter)
)
}
}
#[derive(Deserialize, Debug, PartialEq, Eq, derive_more::Display)]
#[display(
fmt = "HashcashPermission {{ bits: {}, resource: '{}' }}",
bits,
resource
)]
#[serde(deny_unknown_fields)]
pub struct HashcashPermission {
pub bits: u32,
pub resource: String,
}
#[derive(Debug, PartialEq, Clone, Deserialize, derive_more::Display)]
#[display(
fmt = "EncryptedMessage {{ side: {}, phase: {}, body: {}",
side,
phase,
"crate::util::DisplayBytes(body)"
)]
pub struct EncryptedMessage {
pub side: TheirSide,
pub phase: Phase,
#[serde(deserialize_with = "hex::serde::deserialize")]
pub body: Vec<u8>,
}
impl EncryptedMessage {
pub fn decrypt(&self, key: &xsalsa20poly1305::Key) -> Option<Vec<u8>> {
use super::key;
let data_key = key::derive_phase_key(&self.side, key, &self.phase);
key::decrypt_data(&data_key, &self.body)
}
}
#[derive(Serialize, Debug, PartialEq, derive_more::Display)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "type")]
pub enum OutboundMessage {
#[display(fmt = "SubmitPermission({})", _0)]
SubmitPermission(SubmitPermission),
#[display(fmt = "Bind {{ appid: {}, side: {} }}", appid, side)]
Bind {
appid: AppID,
side: MySide,
},
List,
Allocate,
#[display(fmt = "Claim({})", nameplate)]
Claim {
nameplate: String,
},
#[display(fmt = "Release({})", nameplate)]
Release {
nameplate: String,
}, #[display(fmt = "Open({})", mailbox)]
Open {
mailbox: Mailbox,
},
#[display(
fmt = "Add {{ phase: {}, body: {} }}",
phase,
"crate::util::DisplayBytes(body)"
)]
Add {
phase: Phase,
#[serde(serialize_with = "hex::serde::serialize")]
body: Vec<u8>,
},
#[display(fmt = "Close {{ mailbox: {}, mood: {} }}", mailbox, mood)]
Close {
mailbox: Mailbox,
mood: Mood,
},
#[display(fmt = "Ping({})", ping)]
Ping {
ping: u64,
},
}
impl OutboundMessage {
pub fn bind(appid: AppID, side: MySide) -> Self {
OutboundMessage::Bind { appid, side }
}
pub fn claim(nameplate: impl Into<String>) -> Self {
OutboundMessage::Claim {
nameplate: nameplate.into(),
}
}
pub fn release(nameplate: impl Into<String>) -> Self {
OutboundMessage::Release {
nameplate: nameplate.into(),
}
}
pub fn open(mailbox: Mailbox) -> Self {
OutboundMessage::Open { mailbox }
}
pub fn add(phase: Phase, body: Vec<u8>) -> Self {
OutboundMessage::Add { body, phase }
}
pub fn close(mailbox: Mailbox, mood: Mood) -> Self {
OutboundMessage::Close { mailbox, mood }
}
}
#[derive(Deserialize, Debug, PartialEq, derive_more::Display)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "type")]
pub enum InboundMessage {
#[display(fmt = "Welcome({})", welcome)]
Welcome {
welcome: WelcomeMessage,
},
#[display(fmt = "Nameplates({:?})", nameplates)]
Nameplates {
#[serde(with = "Nameplate_")]
nameplates: Vec<Nameplate>,
},
#[display(fmt = "Allocated({})", nameplate)]
Allocated {
nameplate: Nameplate,
},
#[display(fmt = "Claimed({})", mailbox)]
Claimed {
mailbox: Mailbox,
},
Released,
#[display(
fmt = "Message {{ side: {}, phase: {:?}, body: {} }}",
_0.side,
_0.phase,
"crate::util::DisplayBytes(_0.body.as_bytes())"
)]
Message(EncryptedMessage),
Closed,
Ack,
#[display(fmt = "Pong({})", pong)]
Pong {
pong: u64,
},
#[display(fmt = "Error {{ error: {:?}, .. }}", error)]
Error {
error: String,
orig: Box<InboundMessage>,
},
#[serde(other)]
Unknown,
}
#[cfg(test)]
mod test {
use super::*;
use serde_json::{from_str, json, Value};
#[test]
fn test_bind() {
let m1 = OutboundMessage::bind(
AppID::new("appid"),
MySide::unchecked_from_string(String::from("side1")),
);
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(
m2,
json!({"type": "bind", "appid": "appid",
"side": "side1"})
);
}
#[test]
fn test_list() {
let m1 = OutboundMessage::List;
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(m2, json!({"type": "list"}));
}
#[test]
fn test_allocate() {
let m1 = OutboundMessage::Allocate;
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(m2, json!({"type": "allocate"}));
}
#[test]
fn test_claim() {
let m1 = OutboundMessage::claim("nameplate1");
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(m2, json!({"type": "claim", "nameplate": "nameplate1"}));
}
#[test]
fn test_release() {
let m1 = OutboundMessage::release("nameplate1");
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(m2, json!({"type": "release", "nameplate": "nameplate1"}));
}
#[test]
fn test_open() {
let m1 = OutboundMessage::open(Mailbox(String::from("mailbox1")));
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(m2, json!({"type": "open", "mailbox": "mailbox1"}));
}
#[test]
fn test_add() {
let m1 = OutboundMessage::add(Phase("phase1".into()), b"body".to_vec());
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(
m2,
json!({"type": "add", "phase": "phase1",
"body": "626f6479"})
); }
#[test]
fn test_close() {
let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Happy);
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(
m2,
json!({"type": "close", "mailbox": "mailbox1",
"mood": "happy"})
);
}
#[test]
fn test_close_errory() {
let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Errory);
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(
m2,
json!({"type": "close", "mailbox": "mailbox1",
"mood": "errory"})
);
}
#[test]
fn test_close_scared() {
let m1 = OutboundMessage::close(Mailbox(String::from("mailbox1")), Mood::Scared);
let s = serde_json::to_string(&m1).unwrap();
let m2: Value = from_str(&s).unwrap();
assert_eq!(
m2,
json!({"type": "close", "mailbox": "mailbox1",
"mood": "scary"})
);
}
#[test]
#[allow(deprecated)]
fn test_welcome3() {
let s = r#"{"type": "welcome", "welcome": {}, "server_tx": 1234.56}"#;
let m = serde_json::from_str(s).unwrap();
assert!(matches!(
m,
InboundMessage::Welcome {
welcome: WelcomeMessage {
current_cli_version: None,
motd: None,
error: None,
permission_required: None
}
}
));
}
#[test]
#[allow(deprecated)]
fn test_welcome4() {
let s = r#"{"type": "welcome", "welcome": {} }"#;
let m = serde_json::from_str(s).unwrap();
assert!(matches!(
m,
InboundMessage::Welcome {
welcome: WelcomeMessage {
current_cli_version: None,
motd: None,
error: None,
permission_required: None
}
}
));
}
#[test]
#[rustfmt::skip]
#[allow(deprecated)]
fn test_welcome5() {
let s = r#"{"type": "welcome", "welcome": { "motd": "hello world" }, "server_tx": 1234.56 }"#;
let m = serde_json::from_str(s).unwrap();
assert!(matches!(m, InboundMessage::Welcome { welcome: WelcomeMessage { current_cli_version: None, motd: Some(_), error: None, permission_required: None } }));
}
#[test]
#[allow(deprecated)]
fn test_welcome6() {
let s = r#"{"type": "welcome", "welcome": { "motd": "hello world", "permission-required": { "none": {}, "hashcash": { "bits": 6, "resource": "resource-string" }, "dark-ritual": { "hocrux": true } } } }"#;
let m: InboundMessage = serde_json::from_str(s).unwrap();
assert_eq!(
m,
InboundMessage::Welcome {
welcome: WelcomeMessage {
motd: Some("hello world".into()),
permission_required: Some(PermissionRequired {
none: true,
hashcash: Some(HashcashPermission {
bits: 6,
resource: "resource-string".into(),
}),
other: [("dark-ritual".to_string(), json!({ "hocrux": true }))]
.into_iter()
.collect()
}),
current_cli_version: None,
error: None,
}
}
)
}
#[test]
fn test_submit_permissions() {
let m = OutboundMessage::SubmitPermission(SubmitPermission::Hashcash {
stamp: "stamp".into(),
});
let s = serde_json::to_string(&m).unwrap();
assert_eq!(
s,
r#"{"type":"submit-permission","method":"hashcash","stamp":"stamp"}"#
);
}
#[test]
fn test_ack() {
let s = r#"{"type": "ack", "id": null, "server_tx": 1234.56}"#;
let m = serde_json::from_str(s).unwrap();
match m {
InboundMessage::Ack {} => (),
_ => panic!(),
}
}
#[test]
fn test_message() {
let s = r#"{"body": "7b2270616b655f7631223a22353361346566366234363434303364376534633439343832663964373236646538396462366631336632613832313537613335646562393562366237633536353533227d", "server_rx": 1523468188.293486, "id": null, "phase": "pake", "server_tx": 1523498654.753594, "type": "message", "side": "side1"}"#;
let m = serde_json::from_str(s).unwrap();
match m {
InboundMessage::Message(EncryptedMessage {
side: _s,
phase: _p,
body: _b,
}) => (),
_ => panic!(),
}
}
}