use crate::WireError;
use crate::codec::{WireDecode, WireEncode, read_u64_le, read_u128_le};
use crate::requests::users::login_register::{BIND_SECRET_BYTES, BindSecret};
use crate::version::ClientVersionInfo;
use bytes::{BufMut, BytesMut};
use secrecy::ExposeSecret;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SessionIdentity {
pub client_id: u128,
pub session: u64,
pub metadata_watermark: u64,
}
impl SessionIdentity {
pub const ENCODED_SIZE: usize = 32;
}
impl WireEncode for SessionIdentity {
fn encoded_size(&self) -> usize {
Self::ENCODED_SIZE
}
fn encode(&self, buf: &mut BytesMut) {
buf.put_u128_le(self.client_id);
buf.put_u64_le(self.session);
buf.put_u64_le(self.metadata_watermark);
}
}
impl WireDecode for SessionIdentity {
fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
Ok((
Self {
client_id: read_u128_le(buf, 0)?,
session: read_u64_le(buf, 16)?,
metadata_watermark: read_u64_le(buf, 24)?,
},
Self::ENCODED_SIZE,
))
}
}
#[derive(Debug, Clone)]
pub struct BindSessionRequest {
pub version_info: ClientVersionInfo,
pub identity: SessionIdentity,
pub bind_secret: BindSecret,
}
impl WireEncode for BindSessionRequest {
fn encoded_size(&self) -> usize {
self.version_info.encoded_size() + self.identity.encoded_size() + BIND_SECRET_BYTES
}
fn encode(&self, buf: &mut BytesMut) {
self.version_info.encode(buf);
self.identity.encode(buf);
buf.put_slice(self.bind_secret.expose_secret());
}
}
impl WireDecode for BindSessionRequest {
fn decode(buf: &[u8]) -> Result<(Self, usize), WireError> {
let (version_info, prefix_len) = ClientVersionInfo::decode(buf)?;
let (identity, identity_len) = SessionIdentity::decode(&buf[prefix_len..])?;
let secret_pos = prefix_len + identity_len;
let (bind_secret, _) = BindSecret::decode(&buf[secret_pos..])?;
Ok((
Self {
version_info,
identity,
bind_secret,
},
secret_pos + BIND_SECRET_BYTES,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_and_reject_truncation() {
let request = SessionIdentity {
client_id: u128::MAX,
session: 7,
metadata_watermark: 11,
};
let bytes = request.to_bytes();
assert_eq!(
SessionIdentity::decode(&bytes).unwrap(),
(request, bytes.len())
);
for end in 0..bytes.len() {
assert!(
SessionIdentity::decode(&bytes[..end]).is_err(),
"accepted truncation at {end}"
);
}
}
#[test]
fn bind_credential_roundtrips_and_rejects_every_truncated_frame() {
let request = BindSessionRequest {
version_info: ClientVersionInfo {
protocol_version: crate::IGGY_PROTOCOL_VERSION,
sdk_name: crate::WireName::new("test").unwrap(),
sdk_version: crate::WireName::new("0.0.1").unwrap(),
},
identity: SessionIdentity {
client_id: u128::MAX,
session: 7,
metadata_watermark: 11,
},
bind_secret: BindSecret::new(Box::new([0x5a; BIND_SECRET_BYTES])),
};
let bytes = request.to_bytes();
let (decoded, consumed) = BindSessionRequest::decode(&bytes).unwrap();
assert_eq!(consumed, bytes.len());
assert_eq!(decoded.identity, request.identity);
assert_eq!(decoded.version_info, request.version_info);
assert_eq!(
decoded.bind_secret.expose_secret(),
request.bind_secret.expose_secret()
);
for end in 0..bytes.len() {
assert!(
BindSessionRequest::decode(&bytes[..end]).is_err(),
"accepted truncated binding at {end}"
);
}
}
}