use crate::api::schemas::crypto::{PublicKey, Signature};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignedPreKey {
pub key_id: i32,
pub public_key: PublicKey,
pub signature: Signature,
}
impl From<crate::domain::keys::SignedPreKey> for SignedPreKey {
fn from(k: crate::domain::keys::SignedPreKey) -> Self {
Self {
key_id: k.key_id,
public_key: k.public_key.into(),
signature: k.signature.into(),
}
}
}
impl TryFrom<SignedPreKey> for crate::domain::keys::SignedPreKey {
type Error = String;
fn try_from(schema: SignedPreKey) -> Result<Self, Self::Error> {
Ok(Self {
key_id: schema.key_id,
public_key: crate::domain::crypto::PublicKey::try_from(schema.public_key)?,
signature: crate::domain::crypto::Signature::try_from(schema.signature)?,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OneTimePreKey {
pub key_id: i32,
pub public_key: PublicKey,
}
impl From<crate::domain::keys::OneTimePreKey> for OneTimePreKey {
fn from(k: crate::domain::keys::OneTimePreKey) -> Self {
Self {
key_id: k.key_id,
public_key: k.public_key.into(),
}
}
}
impl TryFrom<OneTimePreKey> for crate::domain::keys::OneTimePreKey {
type Error = String;
fn try_from(schema: OneTimePreKey) -> Result<Self, Self::Error> {
Ok(Self {
key_id: schema.key_id,
public_key: crate::domain::crypto::PublicKey::try_from(schema.public_key)?,
})
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PreKeyUpload {
pub identity_key: Option<PublicKey>,
pub registration_id: Option<i32>,
pub signed_pre_key: SignedPreKey,
pub one_time_pre_keys: Vec<OneTimePreKey>,
}
impl PreKeyUpload {
pub fn validate(&self) -> Result<(), String> {
if self.identity_key.is_some() && self.registration_id.is_none() {
return Err("registrationId is required when identityKey is provided".into());
}
let mut unique_ids = std::collections::HashSet::with_capacity(self.one_time_pre_keys.len());
for pk in &self.one_time_pre_keys {
if !unique_ids.insert(pk.key_id) {
return Err(format!("Duplicate prekey ID: {}", pk.key_id));
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::schemas::crypto::PublicKey as SchemaPublicKey;
fn mock_upload() -> PreKeyUpload {
PreKeyUpload {
identity_key: None,
registration_id: None,
signed_pre_key: SignedPreKey {
key_id: 1,
public_key: SchemaPublicKey("A".repeat(44)),
signature: crate::api::schemas::crypto::Signature("B".repeat(88)),
},
one_time_pre_keys: vec![],
}
}
#[test]
fn test_upload_validation_standard_refill() {
let upload = mock_upload();
assert!(upload.validate().is_ok());
}
#[test]
fn test_upload_validation_takeover_valid() {
let mut upload = mock_upload();
upload.identity_key = Some(SchemaPublicKey("C".repeat(44)));
upload.registration_id = Some(456);
assert!(upload.validate().is_ok());
}
#[test]
fn test_upload_validation_takeover_missing_id() {
let mut upload = mock_upload();
upload.identity_key = Some(SchemaPublicKey("C".repeat(44)));
upload.registration_id = None;
let res = upload.validate();
assert!(res.is_err());
assert_eq!(res.unwrap_err(), "registrationId is required when identityKey is provided");
}
#[test]
fn test_upload_validation_duplicate_keys() {
let mut upload = mock_upload();
upload.one_time_pre_keys = vec![
OneTimePreKey { key_id: 1, public_key: SchemaPublicKey("A".repeat(44)) },
OneTimePreKey { key_id: 1, public_key: SchemaPublicKey("B".repeat(44)) },
];
let res = upload.validate();
assert!(res.is_err());
assert_eq!(res.unwrap_err(), "Duplicate prekey ID: 1");
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PreKeyBundle {
pub registration_id: i32,
pub identity_key: PublicKey,
pub signed_pre_key: SignedPreKey,
pub one_time_pre_key: Option<OneTimePreKey>,
}
impl From<crate::domain::keys::PreKeyBundle> for PreKeyBundle {
fn from(b: crate::domain::keys::PreKeyBundle) -> Self {
Self {
registration_id: b.registration_id,
identity_key: b.identity_key.into(),
signed_pre_key: b.signed_pre_key.into(),
one_time_pre_key: b.one_time_pre_key.map(Into::into),
}
}
}