rust-teos/watchtower-plugin/src/convert.rs
2022-07-06 17:39:47 +02:00

435 lines
15 KiB
Rust

use std::fmt;
use std::{convert::TryFrom, str::FromStr};
use hex::FromHex;
use serde::{Deserialize, Serialize};
use bitcoin::{Transaction, Txid};
use teos_common::appointment::Locator;
use teos_common::TowerId;
/// Errors related to the `registertower` command.
#[derive(Debug)]
pub enum RegisterError {
InvalidId(String),
InvalidHost(String),
InvalidPort(String),
InvalidFormat(String),
}
impl std::fmt::Display for RegisterError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RegisterError::InvalidId(x) => write!(f, "{}", x),
RegisterError::InvalidHost(x) => write!(f, "{}", x),
RegisterError::InvalidPort(x) => write!(f, "{}", x),
RegisterError::InvalidFormat(x) => write!(f, "{}", x),
}
}
}
/// Parameters related to the `registertower` command.
#[derive(Debug, Serialize)]
pub struct RegisterParams {
pub tower_id: TowerId,
pub host: Option<String>,
pub port: Option<u16>,
}
impl RegisterParams {
fn new(tower_id: &str, host: Option<&str>, port: Option<u64>) -> Result<Self, RegisterError> {
let mut params = RegisterParams::from_id(tower_id)?;
if host.is_some() {
params = params.with_host(host.unwrap())?
}
if port.is_some() {
params = params.with_port(port.unwrap())?
}
Ok(params)
}
fn from_id(tower_id: &str) -> Result<Self, RegisterError> {
Ok(Self {
tower_id: TowerId::from_str(tower_id)
.map_err(|_| RegisterError::InvalidId("Invalid tower id".into()))?,
host: None,
port: None,
})
}
fn with_host(self, host: &str) -> Result<Self, RegisterError> {
if host.is_empty() {
Err(RegisterError::InvalidHost("hostname is empty".into()))
} else if host.contains(' ') {
Err(RegisterError::InvalidHost(
"hostname contains white spaces".into(),
))
} else {
Ok(Self {
host: Some(String::from(host)),
..self
})
}
}
fn with_port(self, port: u64) -> Result<Self, RegisterError> {
if port > u16::MAX as u64 {
Err(RegisterError::InvalidPort(format!(
"port must be a 16-byte integer. Received: {}",
port
)))
} else {
Ok(Self {
port: Some(port as u16),
..self
})
}
}
}
impl TryFrom<serde_json::Value> for RegisterParams {
type Error = RegisterError;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
match value {
serde_json::Value::String(s) => {
let s = s.trim();
let mut v = s.split('@');
let tower_id = v.next().unwrap();
match v.next() {
Some(x) => {
let mut v = x.split(':');
let host = v.next();
let port = if let Some(p) = v.next() {
p.parse()
.map(Some)
.map_err(|_| RegisterError::InvalidPort(format!("Port is not a number: {}", p)))?
} else {
None
};
RegisterParams::new(tower_id, host, port)
}
None => RegisterParams::from_id(tower_id),
}
},
serde_json::Value::Array(mut a) => {
let param_count = a.len();
match param_count {
1 => RegisterParams::try_from(a.pop().unwrap()),
2 | 3 => {
let tower_id = a.get(0).unwrap();
let host = a.get(1).unwrap();
if !tower_id.is_string() {
return Err(RegisterError::InvalidId(format!("tower_id must be a string. Received: {}", tower_id)));
}
if !host.is_string() {
return Err(RegisterError::InvalidHost(format!("host must be a string. Received: {}", host)));
}
let port = if param_count == 3 {
let p = a.get(2).unwrap();
if !p.is_u64() {
return Err(RegisterError::InvalidPort(format!("port must be a number. Received: {}", p)));
}
p.as_u64()
} else{
None
};
RegisterParams::new(tower_id.as_str().unwrap(), host.as_str(), port)
}
_ => Err(RegisterError::InvalidFormat(format!("Unexpected request format. The request needs 1-3 parameters. Received: {}", param_count))),
}
},
_ => Err(RegisterError::InvalidFormat(
format!("Unexpected request format. Expected: 'tower_id[@host][:port]' or 'tower_id [host] [port]'. Received: '{}'", value),
)),
}
}
}
/// Errors related to the `getappointment` command.
#[derive(Debug)]
pub enum GetAppointmentError {
InvalidId(String),
InvalidLocator(String),
InvalidFormat(String),
}
impl std::fmt::Display for GetAppointmentError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
GetAppointmentError::InvalidId(x) => write!(f, "{}", x),
GetAppointmentError::InvalidLocator(x) => write!(f, "{}", x),
GetAppointmentError::InvalidFormat(x) => write!(f, "{}", x),
}
}
}
/// Parameters related to the `getappointment` command.
#[derive(Debug)]
pub struct GetAppointmentParams {
pub tower_id: TowerId,
pub locator: Locator,
}
impl TryFrom<serde_json::Value> for GetAppointmentParams {
type Error = GetAppointmentError;
fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
match value {
serde_json::Value::Array(a) => {
let param_count = a.len();
if param_count != 2 {
Err(GetAppointmentError::InvalidFormat(format!(
"Unexpected request format. The request needs 2 parameter. Received: {}",
param_count
)))
} else {
let tower_id = if let Some(s) = a.get(0).unwrap().as_str() {
TowerId::from_str(s)
.map_err(|_| GetAppointmentError::InvalidId("Invalid tower id".into()))
} else {
Err(GetAppointmentError::InvalidId(
"tower_id must be a hex encoded string".into(),
))
}?;
let locator = if let Some(s) = a.get(1).unwrap().as_str() {
Locator::from_hex(s).map_err(|_| {
GetAppointmentError::InvalidLocator("Invalid locator".into())
})
} else {
Err(GetAppointmentError::InvalidLocator(
"locator must be a hex encoded string".into(),
))
}?;
Ok(Self { tower_id, locator })
}
}
_ => Err(GetAppointmentError::InvalidFormat(format!(
"Unexpected request format. Expected: tower_id locator. Received: '{}'",
value
))),
}
}
}
/// Data associated with a commitment revocation. Represents the data sent by CoreLN through the `commitment_revocation` hook.
#[derive(Debug, Serialize, Deserialize)]
pub struct CommitmentRevocation {
pub channel_id: String,
#[serde(rename(deserialize = "commitnum"))]
pub commit_num: u32,
pub commitment_txid: Txid,
#[serde(deserialize_with = "crate::ser::deserialize_tx")]
pub penalty_tx: Transaction,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
const VALID_ID: &str = "020dea894c967319407265764aba31bdef75d463f96800f34dd6df61380d82dfc0";
mod register_command {
use super::*;
#[test]
fn test_from_id() {
// The tower id should be a valid id, otherwise the params construction will fail
let params = RegisterParams::from_id(VALID_ID).unwrap();
assert!(params.host.is_none());
assert!(params.port.is_none());
// Any incorrectly formatted id will make it fail
assert!(matches!(
RegisterParams::from_id(""),
Err(RegisterError::InvalidId(..))
));
}
#[test]
fn test_with_host() {
// Any properly formatted host should work
let params = RegisterParams::from_id(VALID_ID).unwrap();
let host = "myhost";
assert_eq!(params.with_host(host).unwrap().host, Some(host.into()));
// Host must not be empty not have spaces
assert!(matches!(
RegisterParams::from_id(VALID_ID).unwrap().with_host(""),
Err(RegisterError::InvalidHost(..))
));
assert!(matches!(
RegisterParams::from_id(VALID_ID)
.unwrap()
.with_host("myhost "),
Err(RegisterError::InvalidHost(..))
));
}
#[test]
fn test_with_port() {
let mut params = RegisterParams::from_id(VALID_ID).unwrap();
// Any 16-bytes value will do for the port
let port = 6677;
params = params.with_port(port).unwrap();
assert_eq!(params.port, Some(port as u16));
// Going over u16::MAX will make this fail
let port = u16::MAX as u64 + 1;
assert!(matches!(
params.with_port(port),
Err(RegisterError::InvalidPort(..))
));
}
#[test]
fn test_try_from_json_string() {
let ok = [
format!("{}@host:80", VALID_ID),
format!("{}@host", VALID_ID),
VALID_ID.to_string(),
];
let wrong_id = ["", "id@host:80", "@host:80", "@:80"];
let wrong_host = [
format!("{}@", VALID_ID),
format!("{}@ ", VALID_ID),
format!("{}@ host", VALID_ID),
format!("{}@:80", VALID_ID),
];
let wrong_port = [
format!("{}@host:", VALID_ID),
format!("{}@host:port", VALID_ID),
];
for s in ok {
let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]);
let p = RegisterParams::try_from(v);
assert!(matches!(p, Ok(..)));
}
for s in wrong_id {
let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]);
let p = RegisterParams::try_from(v);
assert!(matches!(p, Err(RegisterError::InvalidId(..))));
}
for s in wrong_host {
let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]);
let p = RegisterParams::try_from(v);
assert!(matches!(p, Err(RegisterError::InvalidHost(..))));
}
for s in wrong_port {
let v = serde_json::Value::Array(vec![serde_json::Value::String(s.to_string())]);
let p = RegisterParams::try_from(v);
assert!(matches!(p, Err(RegisterError::InvalidPort(..))));
}
}
#[test]
fn test_try_from_json_array() {
let id = json!(VALID_ID);
let number_id = json!(0);
let host = json!("host");
let number_host = json!(1);
let port = json!(80);
let string_port = json!("80");
for v in [vec![&id, &host, &port], vec![&id, &host], vec![&id]] {
let p = RegisterParams::try_from(json!(v));
assert!(matches!(p, Ok(..)));
}
// Wrong id
let p = RegisterParams::try_from(json!(vec![&number_id, &host, &port]));
assert!(matches!(p, Err(RegisterError::InvalidId(..))));
// Wrong host
let p = RegisterParams::try_from(json!(vec![&id, &number_host, &port]));
assert!(matches!(p, Err(RegisterError::InvalidHost(..))));
// Wrong port
let p = RegisterParams::try_from(json!(vec![&id, &host, &string_port]));
assert!(matches!(p, Err(RegisterError::InvalidPort(..))));
// Wrong param count (params should be 1-3)
let p = RegisterParams::try_from(json!(vec![&id, &host, &port, &id]));
assert!(matches!(p, Err(RegisterError::InvalidFormat(..))));
}
#[test]
fn test_try_from_other_json() {
// Unexpected json object (it must be either String or Array)
let p = RegisterParams::try_from(json!(true));
assert!(matches!(p, Err(RegisterError::InvalidFormat(..))));
}
}
mod get_appointment_command {
use super::*;
#[test]
fn test_try_from_array() {
let id = json!(VALID_ID);
let wrong_id =
json!("050dea894c967319407265764aba31bdef75d463f96800f34dd6df61380d82dfc0");
let number_id = json!(0);
let locator = json!("c69517f00d9482e6b1c41639f9bdfd5c");
let wrong_locator =
json!("c69517f00d9482e6b1c41639f9bdfd5cc69517f00d9482e6b1c41639f9bdfd5c");
let number_locator = json!(1);
// Valid params
let p = GetAppointmentParams::try_from(json!(vec![&id, &locator]));
assert!(matches!(p, Ok(..)));
// Wrong params
// Id is a hex string but the format is wrong (wrong prefix)
let p = GetAppointmentParams::try_from(json!(vec![&wrong_id, &locator]));
assert!(matches!(p, Err(GetAppointmentError::InvalidId(..))));
// Ud is not a hex string
let p = GetAppointmentParams::try_from(json!(vec![&number_id, &wrong_locator]));
assert!(matches!(p, Err(GetAppointmentError::InvalidId(..))));
// Locator is a hex string but not properly formatted (wrong length)
let p = GetAppointmentParams::try_from(json!(vec![&id, &wrong_locator]));
assert!(matches!(p, Err(GetAppointmentError::InvalidLocator(..))));
// Locator is not a hex string
let p = GetAppointmentParams::try_from(json!(vec![&id, &number_locator]));
assert!(matches!(p, Err(GetAppointmentError::InvalidLocator(..))));
}
#[test]
fn test_try_from_other_json() {
// Unexpected json object (it must be either String or Array)
let p = RegisterParams::try_from(json!(true));
assert!(matches!(p, Err(RegisterError::InvalidFormat(..))));
}
#[test]
fn test_wrong_param_count() {
// The param count for get_appointment must be 2.
let params_vec = [vec![], vec![1], vec![1, 2, 3]];
for params in params_vec {
let p = GetAppointmentParams::try_from(json!(params));
assert!(matches!(p, Err(..)));
}
}
}
}