1use pb_mapper_core::checksum::AesKeyType;
19use pb_mapper_core::codec::{Aes256GcmDeCodec, Aes256GcmEnCodec};
20use pb_mapper_core::error::Result;
21
22use crate::secure::{HeaderProtocol, protocol_error};
23
24pub const DATA_PROTOCOL_V2: u16 = 2;
26
27#[derive(Clone, Copy)]
30pub struct DataCodec {
31 key: AesKeyType,
32 protocol: Option<u16>,
33}
34
35impl DataCodec {
36 pub fn legacy(key: AesKeyType) -> Self {
38 Self {
39 key,
40 protocol: None,
41 }
42 }
43
44 pub fn from_response(
47 key: Option<AesKeyType>,
48 protocol: Option<u16>,
49 offered_v2: bool,
50 ) -> Result<Option<Self>> {
51 if protocol.is_some()
52 && (protocol != Some(DATA_PROTOCOL_V2) || !offered_v2 || key.is_none())
53 {
54 return Err(protocol_error("invalid data protocol selection"));
55 }
56 Ok(key.map(|key| Self { key, protocol }))
57 }
58
59 pub fn negotiate(key: AesKeyType, offer: Option<u16>, header: HeaderProtocol) -> Result<Self> {
61 Self::validate_offer(offer, header)?;
62 Ok(Self {
63 key,
64 protocol: offer,
65 })
66 }
67
68 pub fn validate_offer(offer: Option<u16>, header: HeaderProtocol) -> Result<()> {
70 if offer.is_some() && (offer != Some(DATA_PROTOCOL_V2) || header != HeaderProtocol::V2) {
71 return Err(protocol_error("unsupported data protocol offer"));
72 }
73 Ok(())
74 }
75
76 pub fn key(self) -> AesKeyType {
78 self.key
79 }
80
81 pub fn protocol(self) -> Option<u16> {
83 self.protocol
84 }
85
86 pub fn endpoint_codecs(self) -> Result<(Aes256GcmDeCodec, Aes256GcmEnCodec)> {
88 self.codecs(false)
89 }
90
91 pub fn relay_codecs(self) -> Result<(Aes256GcmDeCodec, Aes256GcmEnCodec)> {
93 self.codecs(true)
94 }
95
96 fn codecs(self, relay: bool) -> Result<(Aes256GcmDeCodec, Aes256GcmEnCodec)> {
97 if self.protocol.is_none() {
98 return Ok((
99 crate::get_decodec(&self.key)?,
100 crate::get_encodec(&self.key)?,
101 ));
102 }
103 let (read_prefix, write_prefix) = if relay {
106 ([0; 4], [0, 0, 0, 1])
107 } else {
108 ([0, 0, 0, 1], [0; 4])
109 };
110 Ok((
111 Aes256GcmDeCodec::try_new_data_v2(&self.key, read_prefix)
112 .map_err(|_| protocol_error("invalid data-v2 read key"))?,
113 Aes256GcmEnCodec::try_new_data_v2(&self.key, write_prefix)
114 .map_err(|_| protocol_error("invalid data-v2 write key"))?,
115 ))
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::command::{MessageSerializer, PbConnRequest, PbConnResponse};
123 use pb_mapper_core::codec::{Decryptor, Encryptor};
124
125 fn seal(codec: &mut Aes256GcmEnCodec, bytes: &[u8]) -> Vec<u8> {
126 let mut bytes = bytes.to_vec();
127 let tag = codec.encrypt(&mut bytes).unwrap();
128 bytes.extend_from_slice(tag.as_ref());
129 bytes
130 }
131
132 #[test]
133 fn nonce_prefixes_separate_directions_and_keys_separate_relay_legs() {
134 let mut ciphertexts = Vec::new();
135 for secret in [[41; 32], [42; 32]] {
136 let codec = DataCodec::negotiate(secret, Some(2), HeaderProtocol::V2).unwrap();
137 let (mut endpoint_read, mut endpoint_write) = codec.endpoint_codecs().unwrap();
138 let (mut relay_read, mut relay_write) = codec.relay_codecs().unwrap();
139 let first = seal(&mut endpoint_write, b"same bytes");
140 let second = seal(&mut relay_write, b"same bytes");
141 assert_eq!(
142 relay_read.decrypt(&mut first.clone()).unwrap(),
143 b"same bytes"
144 );
145 assert_eq!(
146 endpoint_read.decrypt(&mut second.clone()).unwrap(),
147 b"same bytes"
148 );
149 assert!(relay_read.decrypt(&mut first.clone()).is_err());
151 ciphertexts.extend([first, second]);
152 }
153 for (i, left) in ciphertexts.iter().enumerate() {
154 for right in &ciphertexts[i + 1..] {
155 assert_ne!(left, right);
156 }
157 }
158 }
159
160 #[test]
161 fn data_v2_wire_vectors_reuse_the_leg_key_with_direction_prefixes() {
162 let codec = DataCodec::negotiate([41; 32], Some(2), HeaderProtocol::V2).unwrap();
165 let (_, mut endpoint) = codec.endpoint_codecs().unwrap();
166 let (_, mut relay) = codec.relay_codecs().unwrap();
167 for (writer, expected) in [
168 (
169 &mut endpoint,
170 "c510ef0e480d4056924b6e20a9b85e00b219337e7a0a7efa4764",
171 ),
172 (
173 &mut relay,
174 "11bab01eb9f5caa4320d07b0ca94af01960d51d3873d8e7a09be",
175 ),
176 ] {
177 let actual: String = seal(writer, b"same bytes")
178 .iter()
179 .map(|byte| format!("{byte:02x}"))
180 .collect();
181 assert_eq!(actual, expected);
182 }
183 }
184
185 #[test]
186 fn data_selection_requires_an_authenticated_supported_offer() {
187 assert!(DataCodec::negotiate([0; 32], Some(2), HeaderProtocol::Legacy).is_err());
188 assert!(DataCodec::negotiate([0; 32], Some(3), HeaderProtocol::V2).is_err());
189 for (key, selected, offered) in [
190 (None, Some(2), true),
191 (Some([0; 32]), Some(3), true),
192 (Some([0; 32]), Some(2), false),
193 ] {
194 assert!(DataCodec::from_response(key, selected, offered).is_err());
195 }
196 let old = DataCodec::from_response(Some([1; 32]), None, true)
197 .unwrap()
198 .unwrap();
199 assert_eq!(old.protocol(), None);
200 assert!(
201 DataCodec::from_response(None, None, true)
202 .unwrap()
203 .is_none()
204 );
205 }
206
207 #[test]
208 fn legacy_json_ignores_offers_and_new_readers_accept_absent_selections() {
209 #[derive(serde::Deserialize)]
210 enum OldRequest {
211 Subcribe { key: String },
212 Stream { key: String, dst_id: u32 },
213 }
214 for request in [
215 PbConnRequest::Subcribe {
216 key: "compat".into(),
217 data_protocol: Some(2),
218 },
219 PbConnRequest::Stream {
220 key: "compat".into(),
221 dst_id: 7,
222 server_generation: 0,
223 data_protocol: Some(2),
224 },
225 ] {
226 match serde_json::from_slice::<OldRequest>(&request.encode().unwrap()).unwrap() {
227 OldRequest::Subcribe { key } => assert_eq!(key, "compat"),
228 OldRequest::Stream { key, dst_id } => {
229 assert_eq!(key, "compat");
230 assert_eq!(dst_id, 7);
231 }
232 }
233 }
234 let response = PbConnResponse::decode(br#"{"Stream":{"codec_key":null}}"#).unwrap();
235 assert!(matches!(
236 response,
237 PbConnResponse::Stream {
238 codec_key: None,
239 data_protocol: None
240 }
241 ));
242 let request = PbConnRequest::decode(br#"{"Subcribe":{"key":"compat"}}"#).unwrap();
243 assert!(matches!(
244 request,
245 PbConnRequest::Subcribe {
246 data_protocol: None,
247 ..
248 }
249 ));
250 let response = PbConnResponse::Stream {
251 codec_key: None,
252 data_protocol: None,
253 }
254 .encode()
255 .unwrap();
256 assert_eq!(response, br#"{"Stream":{"codec_key":null}}"#);
257 }
258}