signed_crypto/lib.rs
1// Copyright 2026, Kehan Pan, All rights reserved.
2//
3// Cryptographic scheme inspired by:
4// https://github.com/google/openrtb-doubleclick/blob/master/doubleclick-core/src/main/java/com/google/doubleclick/crypto/DoubleClickCrypto.java
5
6//! # signed-crypto
7//!
8//! A Rust library for encrypted payloads with built-in integrity verification.
9//!
10//! ## Package Format
11//!
12//! Encrypted payloads follow this structure:
13//!
14//! ```text
15//! initVector:16 || E(payload:?) || I(signature:4)
16//! ```
17//!
18//! where:
19//! - `initVector` = `timestamp:8 || serverId:8`
20//! - `E(payload)` = AES-256/CTR64 encryption with encryption key
21//! - `I(signature)` = First 4 bytes of HMAC-SHA256(integrityKey, payload || initVector)
22//!
23//! ## Example
24//!
25//! ```rust
26//! use signed_crypto::{Crypto, Keys};
27//!
28//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
29//! // WARNING: Never use all-zero keys in production!
30//! // Generate secure random keys using a cryptographic RNG.
31//! let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
32//! let crypto = Crypto::new(keys);
33//!
34//! // Encrypt
35//! let payload = b"Hello, world!";
36//! let mut pkg = crypto.init_plain_data(payload.len(), None)?;
37//! crypto.set_payload(&mut pkg, payload)?;
38//! let encrypted = crypto.encrypt(&pkg)?;
39//!
40//! // Decrypt
41//! let decrypted = crypto.decrypt(&encrypted)?;
42//! assert_eq!(crypto.payload(&decrypted), Some(payload.as_slice()));
43//! # Ok(())
44//! # }
45//! ```
46
47use aes::cipher::{KeyIvInit, StreamCipher};
48use base64::{engine::general_purpose::URL_SAFE, Engine as _};
49use byteorder::{BigEndian, ByteOrder};
50use hmac::{Hmac, Mac};
51use sha2::Sha256;
52use thiserror::Error;
53use time::{Duration, OffsetDateTime};
54
55type HmacSha256 = Hmac<Sha256>;
56type Aes256Ctr64BE = ctr::Ctr64BE<aes::Aes256>;
57
58const UNIX_EPOCH: OffsetDateTime = time::OffsetDateTime::UNIX_EPOCH;
59
60/// Holds the encryption and integrity keys.
61///
62/// Both keys must be exactly 32 bytes (256 bits).
63///
64/// # Fields
65///
66/// * `encryption_key` - AES-256 encryption key
67/// * `integrity_key` - HMAC-SHA256 integrity key
68#[derive(Clone, Debug)]
69pub struct Keys {
70 /// AES-256 encryption key (32 bytes)
71 pub encryption_key: [u8; 32],
72 /// HMAC-SHA256 integrity key (32 bytes)
73 pub integrity_key: [u8; 32],
74}
75
76impl Keys {
77 /// Creates a new `Keys` instance from raw byte slices.
78 ///
79 /// Both keys must be exactly 32 bytes.
80 ///
81 /// # Errors
82 ///
83 /// Returns [`CryptoError::InvalidKey`] if either key is not 32 bytes.
84 ///
85 /// # Example
86 ///
87 /// ```rust
88 /// use signed_crypto::Keys;
89 ///
90 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
91 /// // WARNING: Never use all-zero keys in production!
92 /// // Generate secure random keys using a cryptographic RNG.
93 /// let enc_key = [0u8; 32];
94 /// let int_key = [0u8; 32];
95 /// let keys = Keys::new(&enc_key, &int_key)?;
96 /// # Ok(())
97 /// # }
98 /// ```
99 pub fn new(encryption_key: &[u8], integrity_key: &[u8]) -> Result<Self, CryptoError> {
100 let encryption_key: [u8; 32] = encryption_key
101 .try_into()
102 .map_err(|_| CryptoError::InvalidKey)?;
103 let integrity_key: [u8; 32] = integrity_key
104 .try_into()
105 .map_err(|_| CryptoError::InvalidKey)?;
106
107 Ok(Self {
108 encryption_key,
109 integrity_key,
110 })
111 }
112}
113
114/// Errors that can occur during cryptographic operations.
115#[derive(Error, Debug)]
116pub enum CryptoError {
117 /// Key is not exactly 32 bytes.
118 #[error("invalid key")]
119 InvalidKey,
120 /// HMAC signature verification failed.
121 #[error("invalid signature")]
122 InvalidSign,
123 /// Initialization vector is invalid.
124 #[error("invalid init vector")]
125 InvalidInitVector,
126 /// Data is too short to be a valid package.
127 #[error("data too short")]
128 DataTooShort,
129 /// Payload size does not match expected size.
130 #[error("payload size mismatch")]
131 PayloadSizeMismatch,
132 /// Base64 decoding failed.
133 #[error("decode error: {0}")]
134 DecodeError(#[from] base64::DecodeError),
135}
136
137/// Main cryptographic operations instance.
138///
139/// Holds the keys and provides methods for encryption, decryption,
140/// and metadata extraction.
141///
142/// # Example
143///
144/// ```rust
145/// use signed_crypto::{Crypto, Keys};
146///
147/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
148/// // WARNING: Never use all-zero keys in production!
149/// let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
150/// let crypto = Crypto::new(keys);
151/// # Ok(())
152/// # }
153/// ```
154pub struct Crypto {
155 /// The encryption and integrity keys.
156 pub keys: Keys,
157}
158
159impl Crypto {
160 /// Creates a new `Crypto` instance.
161 ///
162 /// # Example
163 ///
164 /// ```rust
165 /// use signed_crypto::{Crypto, Keys};
166 ///
167 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168 /// // WARNING: Never use all-zero keys in production!
169 /// let keys = Keys::new(&[0u8; 32], &[0u8; 32])?;
170 /// let crypto = Crypto::new(keys);
171 /// # Ok(())
172 /// # }
173 /// ```
174 pub fn new(keys: Keys) -> Self {
175 Self { keys }
176 }
177
178 /// Offset of the initialization vector in a package.
179 pub const IV_BASE: usize = 0;
180 /// Size of the initialization vector in bytes.
181 pub const IV_SIZE: usize = 16;
182 /// Offset of the timestamp within the IV.
183 pub const IV_TIME_OFFSET: usize = 0;
184 /// Size of the timestamp in bytes.
185 pub const IV_TIME_SIZE: usize = 8;
186 /// Offset of the server ID within the IV.
187 pub const IV_SERVER_ID_OFFSET: usize = 8;
188 /// Size of the server ID in bytes.
189 pub const IV_SERVER_ID_SIZE: usize = 8;
190 /// Size of the HMAC signature in bytes.
191 pub const SIGNATURE_SIZE: usize = 4;
192 /// Offset where the payload begins.
193 pub const PAYLOAD_BASE: usize = Crypto::IV_BASE + Crypto::IV_SIZE;
194 /// Total overhead size (IV + signature) in bytes.
195 pub const OVERHEAD_SIZE: usize = Crypto::IV_SIZE + Crypto::SIGNATURE_SIZE;
196
197 /// Decodes a URL-safe Base64 encoded string.
198 ///
199 /// # Example
200 ///
201 /// ```rust
202 /// use signed_crypto::{Crypto, Keys};
203 ///
204 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
205 /// // WARNING: Never use all-zero keys in production!
206 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
207 /// let encoded = "SGVsbG8=";
208 /// let decoded = crypto.decode(encoded)?;
209 /// # Ok(())
210 /// # }
211 /// ```
212 #[inline]
213 pub fn decode<T>(&self, data: T) -> Result<Vec<u8>, CryptoError>
214 where
215 T: AsRef<[u8]>,
216 {
217 URL_SAFE
218 .decode(data)
219 .map(|v| v.to_vec())
220 .map_err(|e| e.into())
221 }
222
223 /// Encodes data as a URL-safe Base64 string.
224 ///
225 /// # Example
226 ///
227 /// ```rust
228 /// use signed_crypto::{Crypto, Keys};
229 ///
230 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
231 /// // WARNING: Never use all-zero keys in production!
232 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
233 /// let data = b"Hello";
234 /// let encoded = crypto.encode(data);
235 /// # Ok(())
236 /// # }
237 /// ```
238 #[inline]
239 pub fn encode<T>(&self, data: T) -> String
240 where
241 T: AsRef<[u8]>,
242 {
243 URL_SAFE.encode(data)
244 }
245
246 /// Decrypts a package and verifies the HMAC signature.
247 ///
248 /// # Errors
249 ///
250 /// Returns [`CryptoError::InvalidSign`] if signature verification fails.
251 ///
252 /// # Example
253 ///
254 /// ```rust
255 /// use signed_crypto::{Crypto, Keys};
256 ///
257 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
258 /// // WARNING: Never use all-zero keys in production!
259 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
260 /// let mut pkg = crypto.init_plain_data(5, None)?;
261 /// crypto.set_payload(&mut pkg, b"Hello")?;
262 /// let encrypted = crypto.encrypt(&pkg)?;
263 /// let decrypted = crypto.decrypt(&encrypted)?;
264 /// # Ok(())
265 /// # }
266 /// ```
267 #[inline]
268 pub fn decrypt(&self, cipher_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
269 if cipher_data.len() < Self::OVERHEAD_SIZE {
270 return Err(CryptoError::DataTooShort);
271 }
272
273 let mut data = cipher_data.to_vec();
274 let data_size = data.len();
275
276 self.xor_payload(&mut data)?;
277
278 let confirmation_signature = self.hmac_signature(&data)?;
279 let integrity_signature = self.read_i32(&data, data_size - Self::SIGNATURE_SIZE);
280 self.write_i32(
281 &mut data,
282 data_size - Self::SIGNATURE_SIZE,
283 confirmation_signature,
284 );
285
286 if confirmation_signature != integrity_signature {
287 return Err(CryptoError::InvalidSign);
288 }
289
290 Ok(data)
291 }
292
293 /// Encrypts a package in-place.
294 ///
295 /// # Example
296 ///
297 /// ```rust
298 /// use signed_crypto::{Crypto, Keys};
299 ///
300 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
301 /// // WARNING: Never use all-zero keys in production!
302 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
303 /// let mut pkg = crypto.init_plain_data(5, None)?;
304 /// crypto.set_payload(&mut pkg, b"Hello")?;
305 /// let encrypted = crypto.encrypt(&pkg)?;
306 /// # Ok(())
307 /// # }
308 /// ```
309 #[inline]
310 pub fn encrypt(&self, plain_data: &[u8]) -> Result<Vec<u8>, CryptoError> {
311 if plain_data.len() < Self::OVERHEAD_SIZE {
312 return Err(CryptoError::DataTooShort);
313 }
314
315 let mut data = plain_data.to_vec();
316 let data_size = data.len();
317 let signature = self.hmac_signature(&data)?;
318 self.write_i32(&mut data, data_size - Self::SIGNATURE_SIZE, signature);
319
320 self.xor_payload(&mut data)?;
321
322 Ok(data)
323 }
324
325 /// Creates a custom initialization vector.
326 ///
327 /// # Arguments
328 ///
329 /// * `timestamp` - The timestamp to embed
330 /// * `server_id` - The server ID to embed
331 ///
332 /// # Example
333 ///
334 /// ```rust
335 /// use signed_crypto::{Crypto, Keys};
336 /// use time::OffsetDateTime;
337 ///
338 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
339 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
340 /// let iv = crypto.create_init_vector(OffsetDateTime::now_utc(), 12345);
341 /// # Ok(())
342 /// # }
343 /// ```
344 #[inline]
345 pub fn create_init_vector(&self, timestamp: OffsetDateTime, server_id: i64) -> Vec<u8> {
346 let timestamp = (timestamp.unix_timestamp_nanos() / 1_000) as i64; // microseconds
347 let mut iv = vec![0; Self::IV_SIZE];
348 self.write_i64(&mut iv, Self::IV_TIME_OFFSET, timestamp);
349 self.write_i64(&mut iv, Self::IV_SERVER_ID_OFFSET, server_id);
350 iv
351 }
352
353 /// Extracts the timestamp from a package's initialization vector.
354 ///
355 /// Returns `None` if the data is too short.
356 ///
357 /// # Example
358 ///
359 /// ```rust
360 /// use signed_crypto::{Crypto, Keys};
361 /// use time::OffsetDateTime;
362 ///
363 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
364 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
365 /// let mut pkg = crypto.init_plain_data(5, None)?;
366 /// crypto.set_payload(&mut pkg, b"Hello")?;
367 /// let encrypted = crypto.encrypt(&pkg)?;
368 /// let ts = crypto.timestamp(&encrypted).unwrap();
369 /// # Ok(())
370 /// # }
371 /// ```
372 #[inline]
373 pub fn timestamp(&self, data: &[u8]) -> Option<OffsetDateTime> {
374 if data.len() < Self::IV_SIZE {
375 return None;
376 }
377 let ts = self.read_i64(data, Self::IV_BASE + Self::IV_TIME_OFFSET);
378 Some(
379 UNIX_EPOCH
380 .checked_add(Duration::microseconds(ts))
381 .unwrap_or(UNIX_EPOCH),
382 )
383 }
384
385 /// Extracts the server ID from a package's initialization vector.
386 ///
387 /// Returns `None` if the data is too short.
388 ///
389 /// # Example
390 ///
391 /// ```rust
392 /// use signed_crypto::{Crypto, Keys};
393 ///
394 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
395 /// // WARNING: Never use all-zero keys in production!
396 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
397 /// let mut pkg = crypto.init_plain_data(5, None)?;
398 /// crypto.set_payload(&mut pkg, b"Hello")?;
399 /// let encrypted = crypto.encrypt(&pkg)?;
400 /// let server_id = crypto.server_id(&encrypted).unwrap();
401 /// # Ok(())
402 /// # }
403 /// ```
404 #[inline]
405 pub fn server_id(&self, data: &[u8]) -> Option<i64> {
406 if data.len() < Self::IV_SIZE {
407 return None;
408 }
409 Some(self.read_i64(data, Self::IV_BASE + Self::IV_SERVER_ID_OFFSET))
410 }
411
412 /// Extracts the payload from a package without decryption.
413 ///
414 /// Returns `None` if the data is too short.
415 ///
416 /// # Example
417 ///
418 /// ```rust
419 /// use signed_crypto::{Crypto, Keys};
420 ///
421 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
422 /// // WARNING: Never use all-zero keys in production!
423 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
424 /// let mut pkg = crypto.init_plain_data(5, None)?;
425 /// crypto.set_payload(&mut pkg, b"Hello")?;
426 /// let payload = crypto.payload(&pkg).unwrap();
427 /// assert_eq!(payload, b"Hello");
428 /// # Ok(())
429 /// # }
430 /// ```
431 #[inline]
432 pub fn payload<'a>(&self, data: &'a [u8]) -> Option<&'a [u8]> {
433 if data.len() < Self::OVERHEAD_SIZE {
434 return None;
435 }
436 Some(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE])
437 }
438
439 /// Initializes a plain data package buffer.
440 ///
441 /// If `iv` is `None`, generates a random IV with current timestamp.
442 ///
443 /// # Arguments
444 ///
445 /// * `payload_size` - Size of the payload in bytes
446 /// * `iv` - Optional custom initialization vector
447 ///
448 /// # Example
449 ///
450 /// ```rust
451 /// use signed_crypto::{Crypto, Keys};
452 ///
453 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
454 /// // WARNING: Never use all-zero keys in production!
455 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
456 /// let pkg = crypto.init_plain_data(10, None)?;
457 /// # Ok(())
458 /// # }
459 /// ```
460 #[inline]
461 pub fn init_plain_data(
462 &self,
463 payload_size: usize,
464 iv: Option<&[u8]>,
465 ) -> Result<Vec<u8>, CryptoError> {
466 let mut plain_data = vec![0; Self::OVERHEAD_SIZE + payload_size];
467 if let Some(iv) = iv {
468 plain_data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE].copy_from_slice(iv);
469 } else {
470 let now = (OffsetDateTime::now_utc().unix_timestamp_nanos() / 1_000) as i64;
471 self.write_i64(&mut plain_data, Self::IV_TIME_OFFSET, now);
472 self.write_i64(
473 &mut plain_data,
474 Self::IV_SERVER_ID_OFFSET,
475 rand::random::<i64>(),
476 );
477 }
478
479 Ok(plain_data)
480 }
481
482 /// Sets the payload in a plain data package buffer.
483 ///
484 /// # Errors
485 ///
486 /// Returns [`CryptoError::PayloadSizeMismatch`] if the payload size
487 /// does not match the expected size.
488 ///
489 /// # Example
490 ///
491 /// ```rust
492 /// use signed_crypto::{Crypto, Keys};
493 ///
494 /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
495 /// // WARNING: Never use all-zero keys in production!
496 /// let crypto = Crypto::new(Keys::new(&[0u8; 32], &[0u8; 32])?);
497 /// let mut pkg = crypto.init_plain_data(5, None)?;
498 /// crypto.set_payload(&mut pkg, b"Hello")?;
499 /// # Ok(())
500 /// # }
501 /// ```
502 #[inline]
503 pub fn set_payload(&self, plain_data: &mut [u8], payload: &[u8]) -> Result<(), CryptoError> {
504 if payload.len() != plain_data.len() - Self::OVERHEAD_SIZE {
505 return Err(CryptoError::PayloadSizeMismatch);
506 }
507 plain_data[Self::PAYLOAD_BASE..Self::PAYLOAD_BASE + payload.len()].copy_from_slice(payload);
508 Ok(())
509 }
510
511 #[inline]
512 fn read_i32(&self, data: &[u8], offset: usize) -> i32 {
513 BigEndian::read_i32(&data[offset..offset + 4])
514 }
515
516 #[inline]
517 fn read_i64(&self, data: &[u8], offset: usize) -> i64 {
518 BigEndian::read_i64(&data[offset..offset + 8])
519 }
520
521 #[inline]
522 fn write_i32(&self, data: &mut [u8], offset: usize, value: i32) {
523 BigEndian::write_i32(&mut data[offset..offset + 4], value);
524 }
525
526 #[inline]
527 fn write_i64(&self, data: &mut [u8], offset: usize, value: i64) {
528 BigEndian::write_i64(&mut data[offset..offset + 8], value);
529 }
530
531 #[inline]
532 fn xor_payload(&self, data: &mut [u8]) -> Result<(), CryptoError> {
533 let iv: &[u8; 16] = &data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]
534 .try_into()
535 .map_err(|_| CryptoError::InvalidInitVector)?;
536
537 let mut cipher = Aes256Ctr64BE::new(&self.keys.encryption_key.into(), iv.into());
538 let data_size = data.len();
539 cipher.apply_keystream(&mut data[Self::PAYLOAD_BASE..data_size - Self::SIGNATURE_SIZE]);
540
541 Ok(())
542 }
543
544 #[inline]
545 fn hmac_signature(&self, data: &[u8]) -> Result<i32, CryptoError> {
546 let mut mac = HmacSha256::new_from_slice(&self.keys.integrity_key)
547 .map_err(|_| CryptoError::InvalidKey)?;
548
549 mac.update(&data[Self::PAYLOAD_BASE..data.len() - Self::SIGNATURE_SIZE]);
550 mac.update(&data[Self::IV_BASE..Self::IV_BASE + Self::IV_SIZE]);
551
552 let b = mac.finalize().into_bytes();
553
554 Ok(self.read_i32(&b, 0))
555 }
556}
557
558#[cfg(test)]
559mod tests {
560 use super::*;
561 use base64::prelude::*;
562
563 static TEST_ENCRYPTION_KEY: &str = "sIxwz7yw62yrfoLGt12lIHKuYrK/S5kLuApI2BQe7Ac=";
564 static TEST_INTEGRITY_KEY: &str = "v3fsVcMBMMHYzRhi7SpM0sdqwzvAxM6KPTu9OtVod5I=";
565
566 fn create_keys() -> Keys {
567 Keys::new(
568 &BASE64_STANDARD.decode(TEST_ENCRYPTION_KEY).unwrap(),
569 &BASE64_STANDARD.decode(TEST_INTEGRITY_KEY).unwrap(),
570 )
571 .unwrap()
572 }
573
574 #[test]
575 fn test_decode() {
576 let crypto = Crypto::new(create_keys());
577 let encoded = "aGVsbG8sIHdvcmxk";
578 let decoded = crypto.decode(encoded).unwrap();
579 assert_eq!(decoded, b"hello, world");
580 }
581
582 #[test]
583 fn test_encode() {
584 let crypto = Crypto::new(create_keys());
585 let data = b"hello, world";
586 let encoded = crypto.encode(data);
587 assert_eq!(encoded, "aGVsbG8sIHdvcmxk");
588 }
589
590 #[test]
591 fn test_decrypt() {
592 let crypto = Crypto::new(create_keys());
593 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
594 let iv = crypto.create_init_vector(timestamp, 123456789);
595 let payload = "https://example.com".as_bytes();
596
597 let mut plain_data = crypto.init_plain_data(payload.len(), Some(&iv)).unwrap();
598 crypto.set_payload(&mut plain_data, payload).unwrap();
599 let encrypted_data = crypto.encrypt(&plain_data).unwrap();
600
601 assert_eq!(crypto.timestamp(&iv), Some(timestamp));
602 assert_eq!(crypto.server_id(&iv), Some(123456789));
603 assert_eq!(
604 crypto.payload(&encrypted_data).unwrap().len(),
605 payload.len()
606 );
607 assert_ne!(crypto.payload(&encrypted_data), Some(payload));
608
609 let decrypted_data = crypto.decrypt(&encrypted_data).unwrap();
610 assert_eq!(crypto.timestamp(&decrypted_data), Some(timestamp));
611 assert_eq!(crypto.server_id(&decrypted_data), Some(123456789));
612 assert_eq!(crypto.payload(&decrypted_data), Some(payload));
613
614 let mut encrypted_data_invalid_sign = encrypted_data.clone();
615 crypto.write_i32(
616 &mut encrypted_data_invalid_sign,
617 encrypted_data.len() - Crypto::SIGNATURE_SIZE,
618 123456789,
619 );
620 assert!(matches!(
621 crypto.decrypt(&encrypted_data_invalid_sign),
622 Err(CryptoError::InvalidSign)
623 ));
624 assert_ne!(crypto.payload(&encrypted_data_invalid_sign), Some(payload))
625 }
626
627 #[test]
628 fn test_create_init_vector() {
629 let crypto = Crypto::new(create_keys());
630 let timestamp = OffsetDateTime::UNIX_EPOCH + Duration::seconds(1);
631 let iv = crypto.create_init_vector(timestamp, 123456789);
632 assert_eq!(iv.len(), Crypto::IV_SIZE);
633 assert_eq!(crypto.read_i64(&iv, Crypto::IV_TIME_OFFSET), 1_000_000);
634 assert_eq!(crypto.read_i64(&iv, Crypto::IV_SERVER_ID_OFFSET), 123456789);
635 assert_eq!(crypto.timestamp(&iv), Some(timestamp));
636 assert_eq!(crypto.server_id(&iv), Some(123456789));
637 }
638
639 #[test]
640 fn test_init_plain_data() {
641 let crypto = Crypto::new(create_keys());
642 let payload = "https://example.com".as_bytes();
643
644 let mut plain_data = crypto.init_plain_data(payload.len(), None).unwrap();
645 crypto.set_payload(&mut plain_data, payload).unwrap();
646
647 assert_eq!(plain_data.len(), Crypto::OVERHEAD_SIZE + payload.len());
648 assert_eq!(crypto.payload(&plain_data), Some(payload));
649 }
650
651 #[test]
652 fn test_init_plain_data_empty_payload() {
653 let crypto = Crypto::new(create_keys());
654 let payload = "".as_bytes();
655
656 let mut plain_data = crypto.init_plain_data(0, None).unwrap();
657 crypto.set_payload(&mut plain_data, payload).unwrap();
658 assert_eq!(crypto.payload(&plain_data), Some(payload));
659 }
660}