use serde_json::{Value, json};
use crate::network::auth::{self, AuthError};
pub const DEFAULT_MESSAGE_TTL_MS: u64 = 14 * 24 * 60 * 60 * 1000;
pub use crate::network::auth::namespace;
pub const METHOD_STORE: &str = "store";
pub const METHOD_RETRIEVE: &str = "retrieve";
pub const METHOD_GET_SWARM: &str = "get_snodes_for_pubkey";
pub const METHOD_OXEND_REQUEST: &str = "oxend_request";
pub const METHOD_GET_N_SERVICE_NODES: &str = "get_n_service_nodes";
#[derive(Clone, Debug)]
pub struct StoreParams<'a> {
pub pubkey: &'a str,
pub pubkey_ed25519: Option<&'a str>,
pub namespace: i32,
pub timestamp_ms: u64,
pub ttl_ms: u64,
pub data_base64: &'a str,
pub signing_key: Option<&'a [u8]>,
}
pub fn build_store_params(p: &StoreParams<'_>) -> Result<Value, AuthError> {
let mut m = serde_json::Map::new();
m.insert("pubkey".to_string(), json!(p.pubkey));
m.insert("data".to_string(), json!(p.data_base64));
m.insert("ttl".to_string(), json!(p.ttl_ms));
m.insert("timestamp".to_string(), json!(p.timestamp_ms));
if let Some(sk) = p.signing_key {
let sig = auth::sign_request_b64(METHOD_STORE, p.namespace, p.timestamp_ms, sk)?;
if let Some(pk_ed) = p.pubkey_ed25519 {
m.insert("pubkey_ed25519".to_string(), json!(pk_ed));
}
m.insert("sig_timestamp".to_string(), json!(p.timestamp_ms));
if p.namespace != 0 {
m.insert("namespace".to_string(), json!(p.namespace));
}
m.insert("signature".to_string(), json!(sig));
} else if p.namespace != 0 {
m.insert("namespace".to_string(), json!(p.namespace));
}
Ok(Value::Object(m))
}
#[derive(Clone, Debug)]
pub struct RetrieveParams<'a> {
pub pubkey: &'a str,
pub pubkey_ed25519: &'a str,
pub namespace: i32,
pub timestamp_ms: u64,
pub last_hash: Option<&'a str>,
pub max_size: Option<u32>,
pub signing_key: &'a [u8],
}
pub fn build_retrieve_params(p: &RetrieveParams<'_>) -> Result<Value, AuthError> {
let sig = auth::sign_request_b64(METHOD_RETRIEVE, p.namespace, p.timestamp_ms, p.signing_key)?;
let mut m = serde_json::Map::new();
m.insert("pubkey".to_string(), json!(p.pubkey));
m.insert("pubkey_ed25519".to_string(), json!(p.pubkey_ed25519));
m.insert("timestamp".to_string(), json!(p.timestamp_ms));
if p.namespace != 0 {
m.insert("namespace".to_string(), json!(p.namespace));
}
if let Some(h) = p.last_hash {
m.insert("last_hash".to_string(), json!(h));
}
if let Some(n) = p.max_size {
m.insert("max_size".to_string(), json!(n));
}
m.insert("signature".to_string(), json!(sig));
Ok(Value::Object(m))
}
pub fn build_get_swarm_params(pubkey: &str) -> Value {
json!({ "pubkey": pubkey })
}
pub fn build_oxend_request(endpoint: &str, inner: Value) -> Value {
json!({
"endpoint": endpoint,
"params": inner,
})
}
pub fn build_get_n_service_nodes_params() -> Value {
json!({
"active_only": true,
"fields": [
"public_ip",
"storage_port",
"storage_lmq_port",
"pubkey_x25519",
"pubkey_ed25519",
"swarm_id",
"storage_server_version",
],
})
}
pub fn wrap_rpc_envelope(method: &str, params: Value) -> Value {
json!({ "method": method, "params": params })
}
#[cfg(test)]
mod tests {
use super::*;
fn sk() -> [u8; 32] {
[42u8; 32]
}
#[test]
fn test_store_params_unauthenticated_shape() {
let v = build_store_params(&StoreParams {
pubkey: "05aaaa",
pubkey_ed25519: None,
namespace: 0,
timestamp_ms: 1_700_000_000_000,
ttl_ms: DEFAULT_MESSAGE_TTL_MS,
data_base64: "AAAA",
signing_key: None,
})
.unwrap();
assert_eq!(v["pubkey"], "05aaaa");
assert_eq!(v["data"], "AAAA");
assert_eq!(v["ttl"], DEFAULT_MESSAGE_TTL_MS);
assert_eq!(v["timestamp"], 1_700_000_000_000u64);
assert!(v.get("signature").is_none());
assert!(v.get("sig_timestamp").is_none());
assert!(v.get("namespace").is_none());
}
#[test]
fn test_store_params_authenticated_shape() {
let seed = sk();
let v = build_store_params(&StoreParams {
pubkey: "03bbbb",
pubkey_ed25519: Some("ffff"),
namespace: 11,
timestamp_ms: 1_700_000_000_000,
ttl_ms: 60_000,
data_base64: "ZZZZ",
signing_key: Some(&seed),
})
.unwrap();
assert_eq!(v["pubkey"], "03bbbb");
assert_eq!(v["pubkey_ed25519"], "ffff");
assert_eq!(v["data"], "ZZZZ");
assert_eq!(v["ttl"], 60_000u64);
assert_eq!(v["timestamp"], 1_700_000_000_000u64);
assert_eq!(v["sig_timestamp"], 1_700_000_000_000u64);
assert_eq!(v["namespace"], 11);
assert!(v["signature"].is_string());
}
#[test]
fn test_store_params_auth_omits_namespace_when_zero() {
let seed = sk();
let v = build_store_params(&StoreParams {
pubkey: "05aaaa",
pubkey_ed25519: Some("ffff"),
namespace: 0,
timestamp_ms: 1,
ttl_ms: 1,
data_base64: "X",
signing_key: Some(&seed),
})
.unwrap();
assert!(v.get("namespace").is_none());
assert!(v["signature"].is_string());
}
#[test]
fn test_retrieve_params_shape() {
let seed = sk();
let v = build_retrieve_params(&RetrieveParams {
pubkey: "05cccc",
pubkey_ed25519: "dddd",
namespace: 0,
timestamp_ms: 1_700_000_000_000,
last_hash: Some("lastHashValue"),
max_size: Some(409_600),
signing_key: &seed,
})
.unwrap();
assert_eq!(v["pubkey"], "05cccc");
assert_eq!(v["pubkey_ed25519"], "dddd");
assert_eq!(v["timestamp"], 1_700_000_000_000u64);
assert_eq!(v["last_hash"], "lastHashValue");
assert_eq!(v["max_size"], 409_600);
assert!(v.get("namespace").is_none());
assert!(v["signature"].is_string());
}
#[test]
fn test_retrieve_params_with_namespace() {
let seed = sk();
let v = build_retrieve_params(&RetrieveParams {
pubkey: "03eeee",
pubkey_ed25519: "ffff",
namespace: -10,
timestamp_ms: 1,
last_hash: None,
max_size: None,
signing_key: &seed,
})
.unwrap();
assert_eq!(v["namespace"], -10);
assert!(v.get("last_hash").is_none());
assert!(v.get("max_size").is_none());
}
#[test]
fn test_get_swarm_params_shape() {
let v = build_get_swarm_params("05deadbeef");
assert_eq!(v, json!({ "pubkey": "05deadbeef" }));
}
#[test]
fn test_oxend_request_wraps_params() {
let v = build_oxend_request("ons_resolve", json!({"type": 0, "name_hash": "abc"}));
assert_eq!(v["endpoint"], "ons_resolve");
assert_eq!(v["params"]["type"], 0);
assert_eq!(v["params"]["name_hash"], "abc");
}
#[test]
fn test_get_n_service_nodes_params_shape() {
let v = build_get_n_service_nodes_params();
assert_eq!(v["active_only"], true);
let fields = v["fields"].as_array().unwrap();
assert!(
fields
.iter()
.any(|f| f.as_str() == Some("public_ip"))
);
assert!(
fields
.iter()
.any(|f| f.as_str() == Some("pubkey_ed25519"))
);
assert!(
fields
.iter()
.any(|f| f.as_str() == Some("pubkey_x25519"))
);
assert!(
fields
.iter()
.any(|f| f.as_str() == Some("storage_port"))
);
}
#[test]
fn test_wrap_rpc_envelope_shape() {
let v = wrap_rpc_envelope("store", json!({"pubkey": "05x"}));
assert_eq!(v["method"], "store");
assert_eq!(v["params"]["pubkey"], "05x");
}
}