old_crypto_rs/vic.rs
1//! VIC cipher implementation.
2//!
3//! The VIC cipher is a sophisticated pencil-and-paper cipher used by Soviet spy Reino Häyhänen
4//! in the 1950s. It combines a straddling checkerboard with two transposition steps.
5//!
6//! Full description & test vectors: <http://www.quadibloc.com/crypto/pp1324.htm>
7//! Additional information in [Kahn on Codes, 1984](https://www.goodreads.com/book/show/457215.Kahn_on_Codes)
8//! ISBN: 978-0-02-560640-1
9//!
10use crate::Block;
11use crate::transposition::{Transposition, IrregularTransposition};
12use crate::straddling::StraddlingCheckerboard;
13use crate::helpers::{to_numeric, SC_ALPHABET};
14
15use eyre::Result;
16
17/// VIC cipher implementation combining straddling checkerboard and transposition ciphers.
18///
19/// The VIC cipher uses a complex key derivation system and three main components:
20/// - A straddling checkerboard for initial encoding
21/// - A first regular transposition
22/// - A second irregular transposition
23///
24#[derive(Debug)]
25pub struct VicCipher {
26 // First transposition
27 firsttp: Transposition,
28 // Second transposition
29 secondtp: IrregularTransposition,
30 // Straddling Checkerboard
31 pub sc: StraddlingCheckerboard,
32}
33
34impl VicCipher {
35 /// Creates a new VIC cipher instance with the specified key material.
36 ///
37 /// This constructor performs the complex key derivation process used in the VIC cipher,
38 /// which involves expanding the key material into three separate keys:
39 /// - A key for the first regular transposition
40 /// - A key for the second irregular transposition
41 /// - A key for the straddling checkerboard
42 ///
43 /// # Arguments
44 ///
45 /// * `persn` - Personal number used for the straddling checkerboard (typically 2 digits)
46 /// * `ind` - Indicator string containing at least 5 digits used in key derivation
47 /// * `phrase` - Key phrase that must be at least 20 characters long, used for key expansion
48 /// * `imsg` - Initial message number as a string of digits
49 ///
50 /// # Returns
51 ///
52 /// Returns `Ok(VicCipher)` if the cipher was successfully constructed, or `Err(String)`
53 /// if any of the key material is invalid or if the transposition or straddling checkerboard
54 /// construction fails.
55 ///
56 /// # Examples
57 ///
58 /// ```
59 /// # use old_crypto_rs::VicCipher;
60 /// let cipher = VicCipher::new(
61 /// "89",
62 /// "741776",
63 /// "IDREAMOFJEANNIEWITHT",
64 /// "77651"
65 /// ).unwrap();
66 /// ```
67 ///
68 pub fn new(persn: &str, ind: &str, phrase: &str, imsg: &str) -> Result<Self> {
69 let imsg_int = str2int(imsg);
70 let ikey5 = str2int(&ind[..5]);
71
72 let expanded = expand_key(phrase, &imsg_int, &ikey5);
73
74 // First transposition is regular, using 'second' as key
75 let firsttp = Transposition::new(&String::from_utf8_lossy(&expanded.second))?;
76
77 // Second transposition is irregular, using 'third' as key
78 let secondtp = IrregularTransposition::new(&String::from_utf8_lossy(&expanded.third))?;
79
80 // Straddling Checkerboard using 'sckey' (converted to letters) and 'persn'
81 let sc_key_str: String = expanded.sckey.iter().map(|&v| (b'0' + v) as char).collect();
82 let sc = StraddlingCheckerboard::new_with_freq(&sc_key_str, persn, "ATONESIR", SC_ALPHABET)?;
83
84 Ok(VicCipher {
85 firsttp,
86 secondtp,
87 sc,
88 })
89 }
90}
91
92/// Intermediate structure holding expanded key material.
93///
94/// This structure contains the derived keys used for the two transpositions
95/// and the straddling checkerboard.
96///
97#[derive(Debug)]
98struct ExpandedKey {
99 /// Key for the first (regular) transposition
100 second: Vec<u8>,
101 /// Key for the second (irregular) transposition
102 third: Vec<u8>,
103 /// Key for the straddling checkerboard
104 sckey: Vec<u8>,
105}
106
107/// Expands the key material into the three keys needed for the VIC cipher.
108///
109/// This function performs the complex key derivation process using chain addition
110/// and modular arithmetic to generate the transposition and checkerboard keys.
111///
112/// # Arguments
113///
114/// * `phrase` - Key phrase (at least 20 characters) split into two parts
115/// * `imsg` - Initial message number as byte array
116/// * `ikey5` - First 5 digits of indicator as byte array
117///
118/// # Returns
119///
120/// Returns an `ExpandedKey` structure containing all derived key material.
121///
122fn expand_key(phrase: &str, imsg: &[u8], ikey5: &[u8]) -> ExpandedKey {
123 let ph1: Vec<u8> = to_numeric(&phrase[..10]).into_iter().map(|x| (x as u8 + 1) % 10).collect();
124 let ph2: Vec<u8> = to_numeric(&phrase[10..20]).into_iter().map(|x| (x as u8 + 1) % 10).collect();
125
126 let mut first = submod10(imsg, ikey5);
127 first = chainadd_extend(&first, 5);
128
129 addmod10_inplace(&mut first, &ph1);
130 let second = first_encode(&first, &ph2);
131
132 let mut r = second.clone();
133 for _ in 0..5 {
134 chainadd_inplace(&mut r);
135 }
136
137 // In VIC, the key for the second transposition and the SC is derived
138 // from the 5th iteration of chain addition.
139 let third = r.clone();
140 let r_str: String = r.iter().map(|&b| (b + b'0') as char).collect();
141 let sckey = to_numeric(&r_str);
142
143 ExpandedKey {
144 second,
145 third,
146 sckey,
147 }
148}
149
150
151/// Converts a string of digits to a vector of integers.
152///
153/// # Arguments
154///
155/// * `str` - String containing ASCII digits ('0'-'9')
156///
157/// # Returns
158///
159/// Returns a vector of bytes where each byte is the numeric value (0-9) of the digit.
160///
161#[inline]
162fn str2int(str: &str) -> Vec<u8> {
163 str.bytes().map(|b| b - b'0').collect()
164}
165
166/// Adds two vectors element-wise modulo 10 in-place.
167///
168/// Each element in `a` is replaced with `(a[i] + b[i]) % 10`. The operation
169/// stops when either vector is exhausted.
170///
171/// # Arguments
172///
173/// * `a` - Mutable slice that will be modified with the result
174/// * `b` - Slice to add to `a`
175///
176#[inline]
177fn addmod10_inplace(a: &mut [u8], b: &[u8]) {
178 for (x, y) in a.iter_mut().zip(b) {
179 *x = (*x + *y) % 10;
180 }
181}
182
183#[inline]
184fn submod10(a: &[u8], b: &[u8]) -> Vec<u8> {
185 a.iter().zip(b).map(|(x, y)| (x + 10 - y) % 10).collect()
186}
187
188/// Performs chain addition in-place on a vector.
189///
190/// Chain addition adds each element to its right neighbor (wrapping around at the end)
191/// and stores the result modulo 10 in the original position.
192///
193/// # Arguments
194///
195/// * `a` - Mutable slice to perform chain addition on
196///
197fn chainadd_inplace(a: &mut [u8]) {
198 let l = a.len();
199 if l < 2 { return; }
200 let first = a[0];
201 for i in 0..l - 1 {
202 a[i] = (a[i] + a[i + 1]) % 10;
203 }
204 a[l - 1] = (a[l - 1] + first) % 10;
205}
206
207/// Extends a vector using chain addition.
208///
209/// Each new element is the sum of the element at current index and its successor.
210///
211/// # Arguments
212///
213/// * `a` - Initial slice
214/// * `n` - Number of elements to add
215///
216fn chainadd_extend(a: &[u8], n: usize) -> Vec<u8> {
217 let mut res = Vec::with_capacity(a.len() + n);
218 res.extend_from_slice(a);
219 for i in 0..n {
220 let sum = (res[i] + res[i+1]) % 10;
221 res.push(sum);
222 }
223 res
224}
225
226/// In the original VIC cipher, in order to confuse the adversary even more, plaintext is cut
227/// around the middle, and the two parts are swapped with a marker in between.
228///
229/// (cf. Kahn on Codes)
230///
231// let ml = pt.len() / 2;
232// let intv = rand::rng().random_range(1..=(ml / 2));
233// let ml = ml - intv;
234/// Example:
235/// "ABCDEFGH" is split around the middle and becomes "EFGH-ABCD".
236///
237fn split_plaintext(pt: &[u8], ml: usize) -> Vec<u8> {
238 let mut beg = pt[0..ml].to_vec();
239 let mut res = pt[ml..].to_vec();
240 res.append(&mut vec![b'-' as u8]);
241 res.append(&mut beg);
242 res
243}
244
245/// Expands a 5-element vector to 10 elements using chain addition.
246///
247/// The result contains the original 5 elements followed by 5 elements
248/// generated by chain addition.
249///
250/// # Arguments
251///
252/// * `a` - Input slice (typically 5 elements)
253///
254/// # Returns
255///
256/// Returns a 10-element vector.
257///
258#[inline]
259#[cfg(test)]
260fn expand5to10(a: &[u8]) -> Vec<u8> {
261 chainadd_extend(a, 5)
262}
263
264/// Encodes vector `a` using vector `b` as a lookup table.
265///
266/// Each element in `a` is used as an index (after adjustment) into vector `b`.
267///
268/// # Arguments
269///
270/// * `a` - Vector of indices
271/// * `b` - Lookup table vector
272///
273/// # Returns
274///
275/// Returns a vector where each element is `b[((a[i] + 10) % 10) - 1]`.
276///
277#[inline]
278fn first_encode(a: &[u8], b: &[u8]) -> Vec<u8> {
279 a.iter().map(|&v| b[((v as i32 + 9) % 10) as usize]).collect()
280}
281
282impl Block for VicCipher {
283 fn block_size(&self) -> usize {
284 1
285 }
286
287 /// Encrypts plaintext using the VIC cipher.
288 ///
289 /// The encryption process consists of three steps:
290 /// 1. Encode using the straddling checkerboard
291 /// 2. Apply the first (regular) transposition
292 /// 3. Apply the second (irregular) transposition
293 ///
294 /// # Arguments
295 ///
296 /// * `dst` - Destination buffer for ciphertext (must be large enough)
297 /// * `src` - Source plaintext as bytes
298 ///
299 /// # Returns
300 ///
301 /// Returns the number of bytes written to the destination buffer.
302 ///
303 fn encrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
304 // VIC Encipherment:
305 // 0. Split plaintext around the middle and swap halves (with marker).
306 // 1. Straddling Checkerboard
307 // 2. First Transposition (regular)
308 // 3. Second Transposition (irregular)
309 //
310 let split = if src.len() < 2 {
311 src.to_vec()
312 } else {
313 use rand;
314 use rand::RngExt;
315 let mid = src.len() / 2;
316 let delta = src.len() / 3;
317 let min_ml = mid.saturating_sub(delta);
318 let max_ml = (mid + delta).min(src.len() - 1);
319 let ml = if min_ml == max_ml {
320 min_ml
321 } else {
322 rand::rng().random_range(min_ml..=max_ml)
323 };
324 split_plaintext(src, ml)
325 };
326
327 let mut buf_sc = vec![0u8; split.len() * 3]; // Straddling can expand
328 let sc_len = self.sc.encrypt(&mut buf_sc, &split);
329
330 let mut buf_tp1 = vec![0u8; sc_len];
331 let tp1_len = self.firsttp.encrypt(&mut buf_tp1, &buf_sc[..sc_len]);
332
333 self.secondtp.encrypt(dst, &buf_tp1[..tp1_len])
334 }
335
336 /// Decrypts ciphertext using the VIC cipher.
337 ///
338 /// The decryption process reverses the encryption steps:
339 /// 1. Reverse the second (irregular) transposition
340 /// 2. Reverse the first (regular) transposition
341 /// 3. Decode using the straddling checkerboard
342 ///
343 /// # Arguments
344 ///
345 /// * `dst` - Destination buffer for plaintext (must be large enough)
346 /// * `src` - Source ciphertext as bytes
347 ///
348 /// # Returns
349 ///
350 /// Returns the number of bytes written to the destination buffer.
351 ///
352 fn decrypt(&self, dst: &mut [u8], src: &[u8]) -> usize {
353 // VIC Decipherment (Reverse of Encipherment):
354 // 1. Second Transposition (irregular)
355 // 2. First Transposition (regular)
356 // 3. Straddling Checkerboard
357 // 4. Unsplit plaintext by removing marker and swapping halves back.
358 //
359 let mut buf_tp2 = vec![0u8; src.len()];
360 let tp2_len = self.secondtp.decrypt(&mut buf_tp2, src);
361
362 let mut buf_tp1 = vec![0u8; tp2_len];
363 let tp1_len = self.firsttp.decrypt(&mut buf_tp1, &buf_tp2[..tp2_len]);
364
365 let mut buf_sc = vec![0u8; tp1_len];
366 let sc_len = self.sc.decrypt(&mut buf_sc, &buf_tp1[..tp1_len]);
367 let sc_plain = &buf_sc[..sc_len];
368
369 if let Some(dash_pos) = sc_plain.iter().position(|&b| b == b'-') {
370 let after = &sc_plain[dash_pos + 1..];
371 let before = &sc_plain[..dash_pos];
372 let mut out_len = 0;
373
374 if out_len + after.len() <= dst.len() {
375 dst[..after.len()].copy_from_slice(after);
376 out_len += after.len();
377 } else {
378 let n = dst.len().saturating_sub(out_len);
379 dst[..n].copy_from_slice(&after[..n]);
380 return out_len + n;
381 }
382
383 if out_len + before.len() <= dst.len() {
384 dst[out_len..out_len + before.len()].copy_from_slice(before);
385 out_len += before.len();
386 } else {
387 let n = dst.len().saturating_sub(out_len);
388 dst[out_len..out_len + n].copy_from_slice(&before[..n]);
389 out_len += n;
390 }
391
392 out_len
393 } else {
394 let n = sc_plain.len().min(dst.len());
395 dst[..n].copy_from_slice(&sc_plain[..n]);
396 n
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use rstest::rstest;
405 use crate::helpers::to_numeric;
406
407 #[test]
408 fn test_new_cipher() {
409 let _c = VicCipher::new("89", "741776", "IDREAMOFJEANNIEWITHT", "77651").unwrap();
410 }
411
412 #[rstest]
413 #[case("IDREAMOFJE", vec![6, 2, 0, 3, 1, 8, 9, 5, 7, 4])]
414 #[case("ANNIEWITHT", vec![1, 6, 7, 4, 2, 0, 5, 8, 3, 9])]
415 fn test_to_numeric_one(#[case] s: &str, #[case] r: Vec<u8>) {
416 let res: Vec<u8> = to_numeric(s).into_iter().map(|x| (x as u8 + 1) % 10).collect();
417 assert_eq!(res, r);
418 }
419
420 #[rstest]
421 #[case(vec![8, 6, 1, 5, 4], vec![2, 0, 9, 5, 2], vec![0, 6, 0, 0, 6])]
422 #[case(vec![7, 7, 6, 5, 1], vec![7, 4, 1, 7, 7], vec![4, 1, 7, 2, 8])]
423 fn test_addmod10(#[case] mut a: Vec<u8>, #[case] b: Vec<u8>, #[case] c: Vec<u8>) {
424 addmod10_inplace(&mut a, &b);
425 assert_eq!(a, c);
426 }
427
428 #[rstest]
429 #[case(vec![8, 6, 1, 5, 4], vec![2, 0, 9, 5, 2], vec![6, 6, 2, 0, 2])]
430 #[case(vec![7, 7, 6, 5, 1], vec![7, 4, 1, 7, 7], vec![0, 3, 5, 8, 4])]
431 fn test_submod10(#[case] a: Vec<u8>, #[case] b: Vec<u8>, #[case] c: Vec<u8>) {
432 assert_eq!(submod10(&a, &b), c);
433 }
434
435 #[rstest]
436 #[case(vec![8, 6, 1, 5, 4], vec![4, 7, 6, 9, 2])]
437 #[case(vec![7, 7, 6, 5, 1], vec![4, 3, 1, 6, 8])]
438 fn test_chainadd_inplace(#[case] mut a: Vec<u8>, #[case] b: Vec<u8>) {
439 chainadd_inplace(&mut a);
440 assert_eq!(a, b);
441 }
442
443 #[rstest]
444 #[case(vec![8, 6, 1, 5, 4], vec![8, 6, 1, 5, 4, 4, 7, 6, 9, 8])]
445 #[case(vec![7, 7, 6, 5, 1], vec![7, 7, 6, 5, 1, 4, 3, 1, 6, 5])]
446 #[case(vec![0, 3, 5, 8, 4], vec![0, 3, 5, 8, 4, 3, 8, 3, 2, 7])]
447 fn test_expand5to10(#[case] a: Vec<u8>, #[case] b: Vec<u8>) {
448 assert_eq!(expand5to10(&a), b);
449 }
450
451 #[test]
452 fn test_first_encode() {
453 let r1 = vec![6, 5, 5, 1, 5, 1, 7, 8, 9, 1];
454 let r2 = vec![1, 6, 7, 4, 2, 0, 5, 8, 3, 9];
455 let r = vec![0, 2, 2, 1, 2, 1, 5, 8, 3, 1];
456 assert_eq!(first_encode(&r1, &r2), r);
457 }
458
459 #[test]
460 fn test_vic_cipher_full() {
461 let c = VicCipher::new("89", "741776", "IDREAMOFJEANNIEWITHT", "77651").unwrap();
462
463 let pt = "HELLOWORLD";
464 let mut ct = vec![0u8; 100];
465 let ct_actual_len = c.encrypt(&mut ct, pt.as_bytes());
466
467 let ct_trimmed = &ct[..ct_actual_len];
468
469 // Last digit of ind "741776" is 6.
470 // imsg is "77651".
471 // ct_trimmed should have imsg inserted at index 6.
472 //
473 assert!(ct_trimmed.len() >= 11);
474
475 let mut decrypted = vec![0u8; 100];
476 let dec_len = c.decrypt(&mut decrypted, ct_trimmed);
477
478 let res = String::from_utf8_lossy(&decrypted[..dec_len]).to_string();
479 assert_eq!(res, pt);
480 }
481
482 #[test]
483 fn test_vic_wikipedia_example() {
484 // From Wikipedia:
485 // Phrase: IDREAMOFJEANNIEWITHT
486 // Date: 13 Sept 1944 -> 1391944 (7 digits)
487 // Personal Number: 6
488 // Indicator: 74177 (first 5 digits)
489
490 // Let's see how Wikipedia maps this to our New arguments.
491 // persn: "60" (Personal number 6, usually represented as 2 digits for SC)
492 // ind: "74177"
493 // phrase: "IDREAMOFJEANNIEWITHT"
494 // imsg: "1391944"
495
496 // Step 1: Subtraction modulo 10
497 // G = 1 3 9 1 9 (first 5 of imsg)
498 // H = 7 4 1 7 7 (ikey5)
499 // J = 4 9 8 4 2 (G - H mod 10)
500 //
501 let imsg = str2int("1391944");
502 let ikey5 = str2int("74177");
503 let j = submod10(&imsg[..5], &ikey5);
504 assert_eq!(j, vec![4, 9, 8, 4, 2]);
505
506 // Step 2: Chain addition to 10 digits
507 // 4 9 8 4 2
508 // 3 7 2 6 5 (4+9=13, 9+8=17, 8+4=12, 4+2=6, 2+3=5)
509 // K = 4 9 8 4 2 3 7 2 6 5
510 //
511 let k = expand5to10(&j);
512 assert_eq!(k, vec![4, 9, 8, 4, 2, 3, 7, 2, 6, 5]);
513
514 // Step 3: Add to PH1
515 // Phrase: I D R E A M O F J E | A N N I E W I T H T
516 // PH1: 5 1 9 2 0 6 7 3 8 4
517 // A D E E F I J M O R
518 // 0 1 2 3 4 5 6 7 8 9
519 // A:0 D:1 E:2 E:3 F:4 I:5 J:6 M:7 O:8 R:9
520 // +1 mod 10:
521 // A:1 D:2 E:3 E:4 F:5 I:6 J:7 M:8 O:9 R:0
522 // IDREAMOFJE:
523 // I:6 D:2 R:0 E:3 A:1 M:8 O:9 F:5 J:7 E:4 -> 6 2 0 3 1 8 9 5 7 4?
524 //
525 // A: 0
526 // D: 1
527 // E: 2
528 // E: 3
529 // F: 4
530 // I: 5
531 // J: 6
532 // M: 7
533 // O: 8
534 // R: 9
535 // IDREAMOFJE:
536 // I: 5 -> 6
537 // D: 1 -> 2
538 // R: 9 -> 0
539 // E: 2 -> 3
540 // A: 0 -> 1
541 // M: 7 -> 8
542 // O: 8 -> 9
543 // F: 4 -> 5
544 // J: 6 -> 7
545 // E: 3 -> 4
546 // So ph1 should be [6, 2, 0, 3, 1, 8, 9, 5, 7, 4].
547 //
548 let ph1 = vec![6, 2, 0, 3, 1, 8, 9, 5, 7, 4];
549
550 // L = K + PH1 mod 10
551 // 4 9 8 4 2 3 7 2 6 5
552 // 6 2 0 3 1 8 9 5 7 4
553 // -------------------
554 // 0 1 8 7 3 1 6 7 3 9
555 //
556 let mut l = k.clone();
557 addmod10_inplace(&mut l, &ph1);
558 assert_eq!(l, vec![0, 1, 8, 7, 3, 1, 6, 7, 3, 9]);
559
560 // Step 4: First Encoding
561 // PH2: 1 6 7 4 2 0 5 8 3 9 (ranks of ANNIEWITHT)
562 // M = encode L with PH2
563 // L: 0 1 8 7 3 1 6 7 3 9
564 // PH2: 1 2 3 4 5 6 7 8 9 0 (index 1..10)
565 // 1 6 7 4 2 0 5 8 3 9 (value)
566 //
567 // Note: Wikipedia says "replace each digit in L with the digit below it in the PH2 line"
568 // Digit 0 -> index 10 in PH2 (if 1-based)
569 // Digit 9 -> index 9 in PH2
570 //
571 let ph2 = vec![1, 6, 7, 4, 2, 0, 5, 8, 3, 9];
572 let m = first_encode(&l, &ph2);
573
574 // Wikipedia result for M: 9 1 8 5 7 1 0 5 7 3
575 // L[0]=0 -> PH2[9]=9.
576 // L[1]=1 -> PH2[0]=1.
577 // L[2]=8 -> PH2[7]=8.
578 // L[3]=7 -> PH2[6]=5.
579 // L[4]=3 -> PH2[2]=7.
580 // L[5]=1 -> PH2[0]=1.
581 // L[6]=6 -> PH2[5]=0.
582 // L[7]=7 -> PH2[6]=5.
583 // L[8]=3 -> PH2[2]=7.
584 // L[9]=9 -> PH2[8]=3.
585 //
586 assert_eq!(m, vec![9, 1, 8, 5, 7, 1, 0, 5, 7, 3]);
587
588 // Step 5: Chain addition 5 times
589 //
590 let mut r = m.clone();
591 for _ in 0..5 {
592 chainadd_inplace(&mut r);
593 }
594
595 // Result should be used for second transposition and SC
596 // Wikipedia:
597 // 1st: 0 9 3 2 8 1 5 2 0 2
598 // 2nd: 9 2 5 0 9 6 7 2 2 2
599 // 3rd: 1 7 5 9 5 3 9 4 4 1
600 // 4th: 8 2 4 4 8 2 3 8 5 2
601 // 5th: 0 6 8 2 0 5 1 3 7 0
602 //
603 assert_eq!(r, vec![0, 6, 8, 2, 0, 5, 1, 3, 7, 0]);
604
605 // These digits are used for the second transposition key
606 // And their numerical order for SC key.
607 //
608 let r_str: String = r.iter().map(|&b| (b + b'0') as char).collect();
609 let sckey = to_numeric(&r_str);
610
611 // 0 6 8 2 0 5 1 3 7 0
612 // Ranks (0-based, stable sort):
613 // Pos 0: digit 0 -> rank 0
614 // Pos 1: digit 6 -> rank 7
615 // Pos 2: digit 8 -> rank 9
616 // Pos 3: digit 2 -> rank 4
617 // Pos 4: digit 0 -> rank 1
618 // Pos 5: digit 5 -> rank 6
619 // Pos 6: digit 1 -> rank 3
620 // Pos 7: digit 3 -> rank 5
621 // Pos 8: digit 7 -> rank 8
622 // Pos 9: digit 0 -> rank 2
623 // Ranks: 0 7 9 4 1 6 3 5 8 2
624 //
625 assert_eq!(sckey, vec![0, 7, 9, 4, 1, 6, 3, 5, 8, 2]);
626 }
627
628 #[rstest]
629 #[case(b"ABCDEFGH", 1, b"BCDEFGH-A")]
630 #[case(b"ABCDEFGH", 4, b"EFGH-ABCD")]
631 #[case(b"ABCDEFGH", 7, b"H-ABCDEFG")]
632 fn test_split_plaintext_cases(#[case] pt: &[u8], #[case] ml: usize, #[case] expected: &[u8]) {
633 let out = split_plaintext(pt, ml);
634 assert_eq!(out, expected);
635 }
636
637 #[test]
638 fn test_split_plaintext_invariants() {
639 let pt = b"ABCDEFGHIJKLMNOP";
640 let ml = 6;
641 let out = split_plaintext(pt, ml);
642 assert_eq!(out.len(), pt.len() + 1);
643
644 let dash_positions: Vec<usize> = out.iter().enumerate().filter_map(|(i, &b)| if b == b'-' { Some(i) } else { None }).collect();
645 assert_eq!(dash_positions.len(), 1);
646
647 let dash_pos = dash_positions[0];
648 assert_eq!(dash_pos, pt.len() - ml);
649
650 let mut without_dash = out.clone();
651 without_dash.retain(|&b| b != b'-');
652 let mut expected = pt[ml..].to_vec();
653 expected.extend_from_slice(&pt[..ml]);
654 assert_eq!(without_dash, expected);
655 }
656
657}