breakmancer 0.9.0

Drop a breakpoint into any shell.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
//! Network and encryption protocol.
//!
//! # Summary
//!
//! The breakmancer protocol consists of a cryptographic handshake
//! followed by a sequence of command/response messages.
//! This implements a secured reverse shell.
//!
//! The protocol follows a client/server model, although in this case the
//! "client" is usually run on what would normally be termed a server
//! (likely a CI/CD server), and
//! the "server" is probably running on the user's development laptop; this is a
//! *reverse* shell, after all. Due to this ambiguity, more specific
//! functional terms will be used here instead.
//!
//! The user first runs `breakmancer start` to start a session on their
//! development machine; that process will be termed the "Controller". It
//! begins listening on a port and prints a `breakmancer break` command to
//! run on the machine to be debugged.
//!
//! The user then copies this break command into the script that needs
//! debugging. When that line of the script runs, the new breakmancer
//! process (we'll call this the "Breakpoint") reaches back to the Controller,
//! performs a handshake, and waits for further instructions.
//!
//! The user is then presented with a simple shell-like environment in the
//! Controller session. They can enter commands to be run. These commands
//! are sent to the Breakpoint, which executes them and streams the output
//! back to the Controller for display.
//!
//! # Constraints
//!
//! The Breakpoint may be in a script running in a public environment,
//! such as a CI runner for an open source project. This means it may
//! not always be possible to keep secrets on that side. The dual-pub
//! protocol therefore does not rely on provisioning a private key or
//! a shared secret on the Breakpoint side.
//!
//! Instead, dual-pub requires that the Breakpoint's output is visible
//! more or less in realtime. This allows the Breakpoint to generate
//! an ephemeral keypair and print the public key; the user can then
//! copy this (or rather a digest of it) to the Controller.
//!
//! # Goals
//!
//! - The Breakpoint must only execute commands specified by the Controller,
//!   without alteration (mutation, replay, reordering, etc.) It must not
//!   be possible for another party to impersonate the Controller, even an
//!   on-path attacker.
//! - The Controller must be assured that all outputs are truly from the
//!   Breakpoint. No party should be able to impersonate the Breakpoint.
//! - Only the Breakpoint should be able to read or infer the contents
//!   of commands. This includes defense against:
//!     - Timing attacks, e.g. inference of text via keystroke timing
//!       during interactive input. (Protocol is currently line-oriented,
//!       so this isn't a significant concern.)
//!     - Plaintext length leakage, to the extent feasible. This would be
//!       a concern for messages in both directions.
//!         - **TODO:** Implement padding.
//! - Confidentiality of traffic must be maintained even if it is stored
//!   and later attacked with a quantum computer.
//! - The Breakpoint may run in a low-confidentiality environment and may
//!   not have secure secrets storage.
//!
//! # Non-goals
//!
//! - Availability, in the face of an adversary.
//!
//! # Cryptographic protocol
//!
//! At a high level, the Controller and Breakpoint use X-Wing to send
//! each other secrets during an initial handshake, then combine those
//! to generate a pair of XChaCha20-Poly1305 stream keys for all
//! subsequent messages.
//!
//! Both parties generate their X-Wing keypairs at startup, and the
//! user is enlisted to copy digests of the public keys in both
//! directions in order to establish trust. (In the CLI we call them
//! "verification strings"; in the code we say "IDs", for brevity.)
//!
//! Details:
//!
//! - The Controller creates an X-Wing keypair `ctrl_pub`,
//!   `ctrl_priv` as well as a digest `ctrl_pub_digest`.
//!     - The digest is a BLAKE2b digest with `breakmancer.dual-pub.pubkey-digest.controller`
//!       as the `key` param, and 12 byte digest size. The digest is
//!       Base58-encoded (using the Bitcoin alphabet) for presentation.
//!     - The role of the digest is to allow provisioning each party with
//!       the other's public key in a trusted manner without requiring a user to
//!       copy and paste a kilobyte+ of text.
//! - The Breakpoint does the same: `break_pub`, `break_priv`, and
//!   `break_pub_digest` (with `breakpoint` instead of `controller` in the
//!   `key` param).
//! - The digests are communicated to the counterparties out-of-band.
//! - During the handshake over the network:
//!     - Each party sends the other their full public key. The parties then
//!       compute the digest for the received key and compare it to the trusted
//!       one received out-of-band.
//!     - Each party uses X-Wing to encapsulate a secret for the other. The
//!       ciphertexts are shared across the network and decapsulated. The
//!       resulting shared secrets are `setup_secret_part1` (sent by
//!       the Controller) and `setup_secret_part2` (sent by the Breakpoint).
//!     - Each party now computes the stream keys:
//!         - Concatenate `ctrl_pub`, `break_pub`, `setup_secret_part1`,
//!           `setup_secret_part2`.
//!         - Feed this into BLAKE2b with a key of `breakmancer.dual-pub.stream-keys`
//!           and a digest length of 64.
//!         - Split the digest into two 32-byte values.
//!         - These are now the XChaCha20-Poly1305 stream keys
//!           `ctrl_stream_key` and `break_stream_key`. The first is used by the
//!           Controller to send messages, the latter by the Breakpoint.
//!     - Each party initializes a libsodium `crypto_secretstream` with their respective
//!       transmit key and sends the initializing header to the other party.
//!     - The handshake is now complete.
//! - All subsequent messages are encrypted using `crypto_secretstream`.
//!
//! # Network layer
//!
//! Communication occurs over TCP. Messages are sent length-prefixed
//! (4-byte little-endian).
//!
//! # Insecure message layer
//!
//! Each raw message is a CBOR map with one key (naming the message type)
//! and a type-specific value. There are five types: `BreakpointHello`,
//! `ControllerHello`, `BreakpointSetupFinalize`, `ControllerSetupFinalize`,
//! and `EncryptedMsg`. Several of these can contain
//! encrypted message data, a `crypto_secretstream` message as bytes.
//!
//! # Secure message layer
//!
//! When an encrypted message is decrypted, it again takes the form of a
//! CBOR map of one key. The types of these are: `BreakpointIntro`,
//! `Command`, `OutputLine`, `Finished`, `TerminateCmd`, and `ExitBreak`.
//!
//! # Application protocol
//!
//! This includes some recap of the cryptographic protocol.
//!
//! Any time there is a protocol error (including a message type that is
//! received in an unexpected context), network fault (such as a closed
//! TCP connection), or cryptography failure (such as a public key that
//! doesn't match the expected digest) that party will drop the
//! connection and start over at the beginning of the handshake.
//!
//! ## Initial, out-of-band setup
//!
//! - The developer runs the `start` command on their workstation. This
//!   is the "Controller" process. The Controller creates a fresh keypair.
//! - The Controller outputs a `break` command to be run in the system
//!   being inspected. When that command runs, it is the "Breakpoint"
//!   party. The command contains `ctrl_pub_digest` and the address of the
//!   Controller.
//! - When the Breakpoint starts, it also creates a fresh keypair. It then
//!   connects to the Controller and prints `break_pub_digest` to stdout.
//!
//! ## Handshake
//!
//! Due to the need to exchange public keys before secrets can be exchanged,
//! the full handshake requires four messages:
//!
//! - The Breakpoint sends a `BreakpointHello` containing `protocol`
//!   with value (`"dual-pub"`), `break_pub`, and `expecting_controller_id`.
//!     - The Controller can reject the connection early if the digest
//!       doesn't match. This is *not* a security measure; it only
//!       allows early, non-interactive detection of a handshake that
//!       is doomed to failure.
//!     - A rejection here may be indicated by sending a `HandshakeRejected`
//!       message giving the reason (and indicating whether the failure is
//!       permanent, which it would be at this stage). Or, the controller
//!       could simply close the connection.
//! - The Controller asks for `break_pub_digest` as input and validates
//!   `break_pub` against this. It then sends a `ControllerHello` containing
//!   `ctrl_pub` and `setup_secret_part1`.
//!     - A digest mismatch here could again result in a `HandshakeRejected`
//!       message, although this one should be non-permanent.
//! - The Breakpoint validates `ctrl_pub`, decapsulates the secret, derives the
//!   stream keys, and responds with a `BreakpointSetupFinalize` containing
//!   `setup_secret_part2`, a `cipher_header` for its transmit stream, and an
//!   `encrypted_intro` (encrypted message containing a `BreakpointIntro`).
//!     - The intro just contains information about *which* breakpoint is
//!       running, in case there are multiple.
//! - The Controller decapsulates the second secret and derives stream keys as
//!   well, then sends a `ControllerSetupFinalize` with its own `cipher_header`.
//!
//! At this point, both parties have a secure channel, and all future
//! messages are sent encrypted and wrapped in `EncryptedMsg`-type
//! messages.
//!
//! Any message not in this strict order is an error and requires starting a
//! fresh connection.
//!
//! ## Main loop: Controller
//!
//! Each time the user enters a command. the Controller sends a `Command`
//! message (a command string plus a sequence number that increments for
//! each command). It then switches to waiting for:
//!
//! - `OutputLine` messages bearing lines of stdout or stderr for
//!   display (expecting a matching sequence number.)
//! - A `Finished` message, at which point it increments the command
//!   sequence number and goes back to waiting for user input.
//! - An interrupt from the user, in which case it sends a `TerminateCmd`
//!   (with sequence number) and waits for more output or the command to
//!   finish.
//!
//! Instead of entering a command, the user can also choose to ask the
//! breakpoint to exit and continue script execution (via `ExitBreak`
//! message), or to do that but also exit the controller entirely.
//!
//! ## Main loop: Breakpoint
//!
//! The Breakpoint waits in a loop for `Command` and `ExitBreak`
//! messages.
//!
//! When a `Command` is received, the Breakpoint executes the command and
//! streams back stdout and stderr lines as `OutputLine` messages. If a
//! `TerminateCmd` is received, the Breakpoint checks the sequence number
//! and ends the child process. Either way, once the command finishes, the
//! Breakpoint sends a `Finished` message with the command's exit code and
//! returns to the main loop to wait.
//!
//! If an `ExitBreak` is received in the main loop, the Breakpoint process
//! exits.

use blake2::{
    digest::{consts::U12, Mac},
    Blake2bMac, Blake2bMac512,
};
use clap::ValueEnum;
use crypto_secretstream as secret_stream;
use kem::{Decapsulate, Encapsulate};
use rand::rngs::OsRng;
use rust_base58::{FromBase58, ToBase58};
use serde::{Deserialize, Serialize};
use strum_macros::IntoStaticStr;
use subtle::ConstantTimeEq;

use crate::transport::{Transport, TransportCaller, TransportListener};

/// General purpose enum for identifying the parties in the protocol.
#[derive(Clone)]
pub enum Party {
    Controller,
    Breakpoint,
}

// TODO: Move AuthVersion to cli module.

/// Authentication protocol version that a set of sessions will use.
#[derive(ValueEnum, Clone, Debug)]
pub enum AuthVersion {
    // Details of dual-pub:
    //
    // Authenticates both Controller to Breakpoint, and vice versa,
    // with each party generating public keys. The keys are X-Wing
    // (X25519 + ML-KEM) to provide post-quantum security. Each party
    // provides an ID string to the other party, out of
    // band, in order to validate the public keys.
    //
    // This requires that the user be able to observe streaming logs
    // from the Breakpoint, which is how the Breakpoint communicates
    // its ID string. (The Controller's ID string
    // is provided to the Breakpoint in its invocation in the script.)
    //
    // (This is a non-doc comment because the doc comment gets used
    // for the CLI, and this is more info than is needed for that.)
    /// Dual public key auth -- requires streaming access to breakpoint logs.
    DualPub,
}

/// Wrapper representing an X-Wing keypair.
#[derive(Clone)]
pub struct XWingKeypair {
    pub public_key: x_wing::EncapsulationKey,
    pub private_key: x_wing::DecapsulationKey,
}

impl XWingKeypair {
    /// Create a new, random keypair.
    #[allow(clippy::new_without_default)]
    pub fn new() -> XWingKeypair {
        let (decaps, encaps) = x_wing::generate_key_pair_from_os_rng();
        XWingKeypair {
            public_key: encaps,
            private_key: decaps,
        }
    }

    /// Derive the keypair from the private key.
    pub fn from_decaps(decaps: &x_wing::DecapsulationKey) -> XWingKeypair {
        XWingKeypair {
            public_key: decaps.encapsulation_key(),
            private_key: decaps.clone(),
        }
    }
}

/// IDs are public key digests using Blake2b with output of 12
/// bytes/96 bits.
///
/// Digest entropy chosen to be sufficient that it would take an
/// interactive attacker 1000 years to compute a matching key if they
/// could make 1 trillion guesses per second simultaneously on 1
/// million computers. (In practice, keys should normally live hours
/// to days.)
///
/// That would be about 2^95 guesses; the closest number of bytes
/// would be 12.
type Blake2KeyDigests = Blake2bMac<U12>;

#[cfg(test)]
const DIGEST_BYTE_LEN: usize = 12;
const DIGEST_MIN_CHARS: usize = 12;
const DIGEST_MAX_CHARS: usize = 17;

/// ID string (verification digest) for either party.
pub trait IdString: Sized {
    /// Export the wrapped ID string.
    ///
    /// This should not be compared with a simple equality check; use
    /// `ids_match` instead, which provides additional security and
    /// usability support.
    fn id_string(&self) -> String;
}

/// This is a Base58 digest of the controller's public key (along with
/// some domain separation).
#[derive(Clone)]
pub struct ControllerId(String);

impl ControllerId {
    /// Compute the ID string for the controller.
    pub fn from_key(public_key: &x_wing::EncapsulationKey) -> ControllerId {
        ControllerId(pubkey_digest(public_key, "controller"))
    }

    /// Load a controller ID string, assuming the format is valid.
    pub fn from_received_string(id_string: &str) -> Result<ControllerId, String> {
        validate_id_format(id_string)?;
        Ok(ControllerId(id_string.to_owned()))
    }
}

impl IdString for ControllerId {
    fn id_string(&self) -> String {
        self.0.to_owned()
    }
}

/// This is a Base58 digest of the breakpoint's public key (along with
/// some domain separation).
#[derive(Clone)]
pub struct BreakpointId(String);

impl BreakpointId {
    /// Compute the ID string for the breakpoint.
    pub fn from_key(public_key: &x_wing::EncapsulationKey) -> BreakpointId {
        BreakpointId(pubkey_digest(public_key, "breakpoint"))
    }

    /// Load a breakpoint ID string, assuming the format is valid.
    pub fn from_received_string(id_string: &str) -> Result<BreakpointId, String> {
        validate_id_format(id_string)?;
        Ok(BreakpointId(id_string.to_owned()))
    }
}

impl IdString for BreakpointId {
    fn id_string(&self) -> String {
        self.0.to_owned()
    }
}

/// Check that a received ID is well-formed (Base58 in the range of
/// expected lengths).
fn validate_id_format(id: &str) -> Result<(), String> {
    id.from_base58()
        .map_err(|err| format!("Not valid Base58: {err}"))?;

    // Now that we know it's using the Base58 alphabet, we can freely
    // conflate bytes and characters.
    if id.len() < DIGEST_MIN_CHARS {
        Err(format!(
            "Too short (less than {DIGEST_MIN_CHARS} characters long)"
        ))
    } else if id.len() > DIGEST_MAX_CHARS {
        Err(format!(
            "Too long (more than {DIGEST_MAX_CHARS} characters long)"
        ))
    } else {
        Ok(())
    }
}

/// Internal function for computing either the controller or breakpoint digest.
fn pubkey_digest(public_key: &x_wing::EncapsulationKey, party: &str) -> String {
    let pub_raw = public_key.as_bytes();
    // Domain separation provided here, via the `key` parameter
    let domain = format!("breakmancer.dual-pub.pubkey-digest.{party}");

    let mut hasher =
        Blake2KeyDigests::new_with_salt_and_personal(domain.as_bytes(), &[], &[]).unwrap();
    hasher.update(&pub_raw);
    let digest = hasher.finalize().into_bytes();

    digest.to_base58()
}

/// Verify an ID by comparing it to a trusted value.
///
/// `trusted_id` is the ID that was received out of band and is
/// trusted. `received_key_id` is the digest of a public key that we
/// received over the network.
///
/// The comparison is performed in constant time.
///
/// Some cleanup is performed -- whitespace is trimmed, and lookalike
/// characters that are not part of the Base58 alphabet are changed to
/// the expected in-alphabet character.
fn ids_match<D: IdString>(trusted_id: &D, received_key_id: &D) -> bool {
    let trusted_digest = trusted_id.id_string();
    let received_key_digest = received_key_id.id_string();

    // If someone typed this, automatically correct Base58
    // lookalikes. Also, trim whitespace.
    let trusted_digest = trusted_digest.replace(['l', 'I'], "1").trim().to_string();

    trusted_digest
        .as_bytes()
        .ct_eq(received_key_digest.as_bytes())
        .into()
}

/// The protocol version that's implemented here.
///
/// (Expecting to later understand multiple protocol versions.)
const PROTO_DUAL_PUB: &str = "dual-pub";

//************************//
//                        //
//  Unencrypted messages  //
//                        //
//************************//

/// Message sent any time during handshake to tell counterparty why
/// the handshake is being rejected and that the connection will be
/// shut down.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct HandshakeRejected {
    /// Human-readable reason for rejection.
    reason: String,
    /// Whether the other party should consider this a permanent
    /// rejection (and should not re-attempt a connection with the
    /// same parameters).
    permanent: bool,
}

/// Initial message sent from breakpoint to controller.
///
/// Confirms protocol and delivers breakpoint's public key.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct BreakpointHello {
    /// Protocol to use.
    ///
    /// The breakpoint will have been provisioned with a list of
    /// protocols acceptable to the controller, and this is the protocol
    /// the breakpoint has chosen to use.
    protocol: String,

    /// The breakpoint's public key.
    #[serde(with = "serde_bytes")]
    break_pub: [u8; x_wing::ENCAPSULATION_KEY_SIZE],

    /// The ID string that the breakpoint expects from the
    /// controller. (Intended only for early cooperative termination,
    /// not for security.)
    expecting_controller_id: String,
}

/// Controller's first message back to the breakpoint.
///
/// Delivers the controller's full public key and its contribution to
/// the setup secret.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ControllerHello {
    /// The controller's public key.
    #[serde(with = "serde_bytes")]
    ctrl_pub: [u8; x_wing::ENCAPSULATION_KEY_SIZE],

    /// The controller's half of the setup secret, encapsulated for
    /// the breakpoint's public key.
    #[serde(with = "serde_bytes")]
    setup_secret_part1: [u8; x_wing::CIPHERTEXT_SIZE],
}

/// Breakpoint's second message.
///
/// This completes setup for the breakpoint->controller direction.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct BreakpointSetupFinalize {
    /// The breakpoint's half of the setup secret, encapsulated for
    /// the controller's public key.
    #[serde(with = "serde_bytes")]
    setup_secret_part2: [u8; x_wing::CIPHERTEXT_SIZE],

    /// Starts the secure channel from the breakpoint.
    ///
    /// This is an `XChaCha20-Poly1305` header for `crypto_secretstream`.
    #[serde(with = "serde_bytes")]
    cipher_header: [u8; secret_stream::Header::BYTES],

    /// Encrypted [`BreakpointIntro`], the first `SecureMsg` in the stream.
    ///
    /// This must be decrypted so that cipher stream synchronization
    /// is maintained.
    encrypted_intro: Vec<u8>,
}

/// Controller's second message.
///
/// This completes setup for the controller->breakpoint direction.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ControllerSetupFinalize {
    /// Starts the secure channel from the controller.
    ///
    /// This is an `XChaCha20-Poly1305` header for `crypto_secretstream`.
    #[serde(with = "serde_bytes")]
    cipher_header: [u8; secret_stream::Header::BYTES],
}

/// Contains an encrypted [`SecureMsg`].
///
/// A stream of these messages forms the basis of the secure channel.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct EncryptedMsg {
    #[serde(with = "serde_bytes")]
    encrypted: Vec<u8>,
}

/// A message sent insecurely over the network.
///
/// These messages do not have confidentiality or integrity protection
/// except where they specifically contain encrypted fields.
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Serialize, Deserialize, IntoStaticStr)]
enum InsecureMsg {
    HandshakeRejected(HandshakeRejected),
    BreakpointHello(BreakpointHello),
    ControllerHello(ControllerHello),
    BreakpointSetupFinalize(BreakpointSetupFinalize),
    ControllerSetupFinalize(ControllerSetupFinalize),
    EncryptedMsg(EncryptedMsg),
}

//***************************************//
//                                       //
//  Secure messages (all are encrypted)  //
//                                       //
//***************************************//

/// Embedded in the `BreakpointHello`, this tells the controller about
/// the breakpoint.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BreakpointIntro {
    /// Which breakpoint is calling back (if option is in use.)
    pub which: Option<String>,
}

/// A command sent by the controller for the breakpoint to execute.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Command {
    /// Code to execute on the breakpoint.
    pub command: String,
    /// Sequence ID for this command.
    pub seq: u32,
}

/// One line of output from a command, sent by breakpoint to controller.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct OutputLine {
    #[serde(with = "serde_bytes")]
    pub bytes: Vec<u8>,
    pub gender: OutputType,
    /// Must match the most recent [`Command::seq`].
    pub seq: u32,
}

#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub enum OutputType {
    Stdout,
    Stderr,
}

/// Final results of a command, sent by breakpoint to controller.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Finished {
    /// This is a `std::process::ExitStatus`
    pub exit_code: Option<i32>,
    /// Must match the most recent [`Command::seq`].
    pub seq: u32,
}

/// Sent by controller to tell breakpoint to terminate a command that is
/// in-process.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct TerminateCmd {
    /// Sequence number of the command we're going to try to terminate.
    pub seq: u32,
}

/// Controller tells the breakpoint to exit and resume normal script
/// execution.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ExitBreak {}

/// All message types sent over the secure channel.
#[derive(Debug, PartialEq, Serialize, Deserialize, IntoStaticStr)]
pub enum SecureMsg {
    BreakpointIntro(BreakpointIntro),
    Command(Command),
    OutputLine(OutputLine),
    Finished(Finished),
    TerminateCmd(TerminateCmd),
    /// This variant is special-cased to end the connection when sent or received.
    ExitBreak(ExitBreak),
}

//*****************************************//
//                                         //
//  Message encryption and key derivation  //
//                                         //
//*****************************************//

/// Create the pair of keys that will be used for secure
/// communication.
///
/// Accepts the public keys of both parties and the secrets that each
/// party has encapsulated for the other via the setup messages.
///
/// The first returned key is for the [`crypto_secretstream`] of messages from
/// the controller to the breakpoint; the second is for the other
/// direction.
fn derive_stream_keys_dual_pub(
    pubkey_controller: &x_wing::EncapsulationKey,
    pubkey_breakpoint: &x_wing::EncapsulationKey,
    setup_secret_controller: &x_wing::SharedSecret,
    setup_secret_breakpoint: &x_wing::SharedSecret,
) -> (secret_stream::Key, secret_stream::Key) {
    let mut in_raw = Vec::new();
    in_raw.extend(&pubkey_controller.as_bytes());
    in_raw.extend(&pubkey_breakpoint.as_bytes());
    in_raw.extend(*setup_secret_controller);
    in_raw.extend(*setup_secret_breakpoint);

    let key_len = secret_stream::Key::BYTES;
    // Hash input is public keys followed by setup secrets. Key is a
    // domain separator.
    let domain = "breakmancer.dual-pub.stream-keys";
    let mut hasher =
        Blake2bMac512::new_with_salt_and_personal(domain.as_bytes(), &[], &[]).unwrap();
    hasher.update(&in_raw);
    let key_bytes = hasher.finalize().into_bytes();
    assert_eq!(key_bytes.len(), 2 * key_len);

    // Split output into two halves, as stream keys
    let controller_stream_key = secret_stream::Key::try_from(&key_bytes[0..key_len]).unwrap();
    let breakpoint_stream_key = secret_stream::Key::try_from(&key_bytes[key_len..]).unwrap();

    (controller_stream_key, breakpoint_stream_key)
}

/// Serialize data to CBOR, at any layer of protocol.
///
/// This standardizes our approach to data serialization and encoding.
fn serialize<D>(data: &D) -> Vec<u8>
where
    D: Serialize,
{
    // Note that serde_cbor serializes structs as maps of strings to
    // values by default. This is important—if the other party
    // includes additional, unexpected keys, we can skip over and
    // ignore them.
    serde_cbor::to_vec(&data).unwrap()
}

/// Deserialize data from CBOR, at any layer of protocol.
///
/// This standardizes our approach to data serialization and encoding.
fn deserialize<'a, T>(bytes: &'a [u8]) -> Result<T, String>
where
    T: Deserialize<'a>,
{
    serde_cbor::from_slice(bytes).map_err(|err| format!("{err}"))
}

/// Encrypt a [`SecureMsg`] for transport.
///
/// This must be done immediately before sending the message in
/// order to ensure that the cipher state stays in sync.
fn encrypt_message(msg: &SecureMsg, cipher_tx: &mut secret_stream::PushStream) -> EncryptedMsg {
    let mut in_place_buffer = serialize(msg);
    cipher_tx
        .push(&mut in_place_buffer, &[], secret_stream::Tag::Message)
        .unwrap();
    EncryptedMsg {
        encrypted: in_place_buffer,
    }
}

/// Decrypt an encrypted message.
///
/// This must be done immediately after receiving the message in
/// order to ensure that the cipher state stays in sync.
fn decrypt_message(
    msg: &EncryptedMsg,
    cipher_rx: &mut secret_stream::PullStream,
) -> Result<SecureMsg, String> {
    let mut in_place_buffer = msg.encrypted.clone();
    let tag = cipher_rx
        .pull(&mut in_place_buffer, &[])
        .map_err(|err| format!("Unable to decrypt message: {err}"))?;

    // We don't use Final in this protocol -- we just close the connection when we're done.
    if tag != secret_stream::Tag::Message {
        Err(format!("Unexpected tag in decryption stream: {tag:?}"))?;
    }

    deserialize(&in_place_buffer).map_err(|err| format!("Could not parse message data: {err}"))
}

//****************************************//
//                                        //
//  Secured connection abstraction layer  //
//                                        //
//****************************************//

/// Send a message over the insecure channel.
async fn send_insecure(transport: &mut Transport, msg: &InsecureMsg) -> Result<(), String> {
    transport.send_raw(&serialize(msg)).await
}

/// Receive a message over the insecure channel.
async fn receive_insecure(transport: &mut Transport) -> Result<Option<InsecureMsg>, String> {
    let raw = match transport.receive_raw().await? {
        None => return Ok(None),
        Some(raw) => raw,
    };
    deserialize(&raw).map_err(|err| format!("Could not parse message body: {err}"))
}

/// A secured connection to the other party (breakpoint or controller).
pub struct Connection {
    /// Underlying transport for connection.
    transport: Transport,

    /// Cipher stream for encrypting messages to be sent over the transport.
    cipher_tx: secret_stream::PushStream,

    /// Cipher stream for decrypting messages received over the transport.
    cipher_rx: secret_stream::PullStream,
}

impl Connection {
    /// Send a message over the secured channel.
    pub async fn send(&mut self, msg: SecureMsg) -> Result<(), String> {
        let encrypted = InsecureMsg::EncryptedMsg(encrypt_message(&msg, &mut self.cipher_tx));
        send_insecure(&mut self.transport, &encrypted).await
    }

    /// Receive a message over the secured channel.
    ///
    /// Ok(None) signals that the channel is now closed.
    pub async fn recv(&mut self) -> Result<Option<SecureMsg>, String> {
        match receive_insecure(&mut self.transport).await? {
            None => Ok(None),
            Some(InsecureMsg::EncryptedMsg(enc)) => {
                Ok(Some(decrypt_message(&enc, &mut self.cipher_rx)?))
            }
            Some(clear_msg) => Err(format!("Unexpected insecure message type: {clear_msg:?}")),
        }
    }
}

//*******************************//
//                               //
//  Controller connection setup  //
//                               //
//*******************************//

/// Create a new controller connection and complete when the
/// breakpoint's hello has been received.
///
/// This does not complete the connection setup; the caller is
/// responsible for either dropping the returned
/// [`ControllerReceivedHello`] or calling its
/// `verify_breakpoint_and_complete_setup` method.
pub async fn controller_open_connection(
    controller_keypair: XWingKeypair,
    transport_listener: &mut TransportListener,
) -> Result<ControllerReceivedHello, String> {
    let mut transport = transport_listener.accept_connection().await?;

    // Receive Breakpoint's public key
    let breakpoint_hello = match receive_insecure(&mut transport).await {
        Ok(None) => Err(String::from("Breakpoint hung up before BreakpointHello"))?,
        Ok(Some(InsecureMsg::BreakpointHello(hello))) => hello,
        Ok(Some(wrong)) => Err(format!(
            "Unexpected message type when waiting for BreakpointHello: {}",
            Into::<&str>::into(wrong)
        ))?,
        Err(err) => Err(format!(
            "Couldn't receive data when waiting for a BreakpointHello: {err}"
        ))?,
    };
    if breakpoint_hello.protocol != PROTO_DUAL_PUB {
        Err(format!(
            "Unexpected protocol field from breakpoint: {}",
            breakpoint_hello.protocol
        ))?;
    }
    let breakpoint_pub_key: x_wing::EncapsulationKey = (&breakpoint_hello.break_pub).into();
    let breakpoint_expecting_id =
        ControllerId::from_received_string(&breakpoint_hello.expecting_controller_id)?;

    // The Breakpoint gives us an opportunity to close the connection
    // early on verification mismatch instead of requiring an
    // interactive step.
    let ctrl_id = ControllerId::from_key(&controller_keypair.public_key);
    if !ids_match(&ctrl_id, &breakpoint_expecting_id) {
        // We specifically do *not* include the received ID. While
        // that might help the user debug a mis-paste or mis-type, it
        // also makes it very easy for someone to do the dangerous
        // thing of copying the string from the error message and
        // pasting it back in.
        let err_msg = "Breakpoint sent wrong expected controller verification string, \
                       exiting early. (Breakpoint had outdated connection string?)";
        send_insecure(
            &mut transport,
            &InsecureMsg::HandshakeRejected(HandshakeRejected {
                reason: String::from(
                    "Controller doesn't match expected verification string sent by breakpoint.",
                ),
                permanent: true,
            }),
        )
        .await
        .map_err(|err| format!("Unable to send rejection message. Rejection was: {err}"))?;
        Err(err_msg)?;
    }

    Ok(ControllerReceivedHello {
        controller_keypair,
        transport,
        breakpoint_pub_unverified: breakpoint_pub_key,
    })
}

/// State where the controller has received a hello from the
/// breakpoint.
///
/// This represents a partially set-up connection requiring some
/// interaction with the user before setup can complete.
pub struct ControllerReceivedHello {
    controller_keypair: XWingKeypair,
    transport: Transport,
    /// Unverified public key for a breakpoint, received over the
    /// network.
    breakpoint_pub_unverified: x_wing::EncapsulationKey,
}

impl ControllerReceivedHello {
    /// Given a breakpoint ID received out-of-band,
    /// finish the setup process.
    ///
    /// If this completes successfully, returns the secured connection
    /// as well as the decrypted breakpoint introduction message.
    pub async fn verify_breakpoint_and_complete_setup(
        mut self,
        expected_breakpoint_id: &BreakpointId,
    ) -> Result<(Connection, BreakpointIntro), String> {
        let received_id = BreakpointId::from_key(&self.breakpoint_pub_unverified);
        if !ids_match(expected_breakpoint_id, &received_id) {
            // We specifically do *not* include the received ID. While
            // that might help the user debug a mis-paste or mis-type,
            // it also makes it very easy for someone to do the
            // dangerous thing of copying the string from the error
            // message and pasting it back in.
            let err_msg = "Breakpoint verification string did not match. \
                 Is it possible that you have multiple breakpoints attempting \
                 to connect here at the same time and entered the verification \
                 string from the wrong one?";
            send_insecure(
                &mut self.transport,
                &InsecureMsg::HandshakeRejected(HandshakeRejected {
                    reason: String::from(
                        "Breakpoint sent a public key that doesn't match \
                         the verification string the user entered.",
                    ),
                    permanent: false, // could be a mis-paste or a typo
                }),
            )
            .await
            .map_err(|err| format!("Unable to send rejection message. Rejection was: {err}"))?;
            return Err(err_msg.to_string());
        }
        let breakpoint_pubkey = self.breakpoint_pub_unverified; // now verified!

        // We can now trust the breakpoint's public key, which means
        // we can send them our half of the setup secret. They'll also
        // need the controller's public key so they can send us
        // *their* portion of the setup secret.

        let (controller_secret_encap, controller_secret) = breakpoint_pubkey
            .encapsulate(&mut rand::rngs::OsRng)
            .map_err(|err| format!("Failed to create controller secret: {err}"))?;

        send_insecure(
            &mut self.transport,
            &InsecureMsg::ControllerHello(ControllerHello {
                ctrl_pub: self.controller_keypair.public_key.as_bytes(),
                setup_secret_part1: controller_secret_encap.as_bytes(),
            }),
        )
        .await
        .map_err(|err| format!("Couldn't respond to breakpoint's hello: {err}"))?;

        // The breakpoint now can send us its own half of the setup
        // secret, after which both parties will have enough
        // information to compute the stream secrets. So the
        // breakpoint also sends us the beginning of its stream,
        // including a first message.

        let breakpoint_finalize = match receive_insecure(&mut self.transport).await {
            Ok(None) => Err(String::from(
                "Breakpoint hung up before BreakpointSetupFinalize",
            ))?,
            Ok(Some(InsecureMsg::BreakpointSetupFinalize(finalize))) => finalize,
            Ok(Some(wrong)) => Err(format!(
                "Unexpected message type when waiting for BreakpointSetupFinalize: {}",
                Into::<&str>::into(wrong)
            ))?,
            Err(err) => Err(format!(
                "Couldn't receive data when waiting for a BreakpointSetupFinalize: {err}"
            ))?,
        };

        let breakpoint_secret_encap: x_wing::Ciphertext =
            (&breakpoint_finalize.setup_secret_part2).into();
        let breakpoint_secret = self
            .controller_keypair
            .private_key
            .decapsulate(&breakpoint_secret_encap)
            .map_err(|err| {
                format!("Unable to decrypt breakpoint's half of the setup secret: {err}")
            })?;

        let (controller_stream_key, breakpoint_stream_key) = derive_stream_keys_dual_pub(
            &self.controller_keypair.public_key,
            &breakpoint_pubkey,
            &controller_secret,
            &breakpoint_secret,
        );

        // The breakpoint has already included a message for us! We'll
        // set up the receive stream and decrypt it now, before
        // setting up the transmit stream.

        let mut rx_stream = secret_stream::PullStream::init(
            secret_stream::Header::from(breakpoint_finalize.cipher_header),
            &breakpoint_stream_key,
        );

        let maybe_intro = decrypt_message(
            &EncryptedMsg {
                encrypted: breakpoint_finalize.encrypted_intro,
            },
            &mut rx_stream,
        )
        .map_err(|err| format!("Could not decrypt breakpoint introduction: {err}"))?;
        let breakpoint_intro = match maybe_intro {
            SecureMsg::BreakpointIntro(intro) => intro,
            _ => Err("Unexpected message type for breakpoint intro.")?,
        };

        // The last step is setting up the transmit stream and sending
        // the start of it to the breakpoint.

        let (tx_header, tx_stream) = secret_stream::PushStream::init(OsRng, &controller_stream_key);

        send_insecure(
            &mut self.transport,
            &InsecureMsg::ControllerSetupFinalize(ControllerSetupFinalize {
                cipher_header: *tx_header.as_ref(),
            }),
        )
        .await
        .map_err(|err| format!("Unable to send controller setup finalization: {err}"))?;

        // Now both parties should have a fully secured connection.

        let secured_conn = Connection {
            transport: self.transport,
            cipher_tx: tx_stream,
            cipher_rx: rx_stream,
        };

        Ok((secured_conn, breakpoint_intro))
    }
}

//*******************************//
//                               //
//  Breakpoint connection setup  //
//                               //
//*******************************//

/// Error occurring during breakpoint's connection setup.
#[derive(Debug)]
pub struct BreakpointConnectError {
    /// Error message string.
    pub message: String,
    /// Whether this should be considered a permanent error,
    /// indicating that the same connection parameters should not be
    /// retried.
    pub permanent: bool,
}

/// By default, an error shouldn't be considered a permanent error.
impl From<String> for BreakpointConnectError {
    fn from(message: String) -> Self {
        Self {
            message,
            permanent: false,
        }
    }
}

/// Create a new breakpoint connection, completing once a hello has
/// been sent to the controller and the breakpoint is ready to show
/// its ID to the user.
#[allow(clippy::too_many_lines)]
pub async fn breakpoint_open_connection(
    breakpoint_keypair: &XWingKeypair,
    transport_caller: &TransportCaller,
    expected_controller_id: &ControllerId,
    which: Option<String>,
) -> Result<BreakpointProducedId, BreakpointConnectError> {
    let mut transport = transport_caller.new_connection().await?;

    // Send a hello to the controller with our public key. This will
    // need to be verified with the help of the user.

    send_insecure(
        &mut transport,
        &InsecureMsg::BreakpointHello(BreakpointHello {
            protocol: PROTO_DUAL_PUB.to_owned(),
            break_pub: breakpoint_keypair.public_key.as_bytes(),
            expecting_controller_id: expected_controller_id.id_string(),
        }),
    )
    .await
    .map_err(|err| format!("Unable to send hello: {err}"))?;

    let breakpoint_id = BreakpointId::from_key(&breakpoint_keypair.public_key);

    Ok(BreakpointProducedId {
        breakpoint_keypair: breakpoint_keypair.clone(),
        expected_controller_id: expected_controller_id.clone(),
        which,
        transport,
        breakpoint_id,
    })
}

/// Represents a partially set up connection from the breakpoint side.
pub struct BreakpointProducedId {
    breakpoint_keypair: XWingKeypair,
    expected_controller_id: ControllerId,
    which: Option<String>,
    transport: Transport,
    /// String to be displayed to the user so they can paste it into
    /// the controller.
    pub breakpoint_id: BreakpointId,
}

impl BreakpointProducedId {
    /// Call this once `breakpoint_id` has
    /// been printed or otherwise conveyed to the user.
    ///
    /// Completes the connection.
    pub async fn continue_after_id_printed(mut self) -> Result<Connection, BreakpointConnectError> {
        // Once the controller has verified the breakpoint's public key,
        // it responds with its own public key (which we'll need to
        // verify) as well as its half of the setup secret.

        let controller_hello = match receive_insecure(&mut self.transport).await {
            Ok(None) => Err(String::from("Controller hung up before ControllerHello"))?,

            Ok(Some(InsecureMsg::ControllerHello(hello))) => hello,
            Ok(Some(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent }))) => {
                Err(BreakpointConnectError {
                    message: format!("Controller rejected connection: {reason}"),
                    permanent,
                })?
            }
            Ok(Some(wrong)) => Err(format!(
                "Unexpected message type when waiting for ControllerHello: {}",
                Into::<&str>::into(wrong)
            ))?,
            Err(err) => Err(format!(
                "Couldn't receive data when waiting for a ControllerHello: {err}"
            ))?,
        };

        let unverified_controller_pubkey: x_wing::EncapsulationKey =
            (&controller_hello.ctrl_pub).into();
        let received_id = ControllerId::from_key(&unverified_controller_pubkey);
        if !ids_match(&self.expected_controller_id, &received_id) {
            // We specifically do *not* include the received ID. While
            // that might help the user debug a mis-paste or mis-type,
            // it also makes it very easy for someone to do the
            // dangerous thing of copying the string from the error
            // message and pasting it back in.
            Err("Controller verification string did not match. \
                 Is it possible that the controller has since been \
                 restarted? (This would mean it is using a new set of keys.)"
                .to_string())?;
        }
        let controller_pubkey = unverified_controller_pubkey; // now verified!

        let controller_secret_encap: x_wing::Ciphertext =
            (&controller_hello.setup_secret_part1).into();
        let controller_secret = self
            .breakpoint_keypair
            .private_key
            .decapsulate(&controller_secret_encap)
            .map_err(|err| {
                format!("Unable to decrypt controller's half of the setup secret: {err}")
            })?;

        // We can now trust the controller's public key, which means we
        // can send them our half of the setup secret and derive the full
        // shared secret for ourselves, and that in turn means we can go
        // ahead and include an encrypted introduction message.

        let (breakpoint_secret_encap, breakpoint_secret) = controller_pubkey
            .encapsulate(&mut rand::rngs::OsRng)
            .map_err(|err| format!("Failed to create breakpoint secret: {err}"))?;

        let (controller_stream_key, breakpoint_stream_key) = derive_stream_keys_dual_pub(
            &controller_pubkey,
            &self.breakpoint_keypair.public_key,
            &controller_secret,
            &breakpoint_secret,
        );

        let (tx_header, mut tx_stream) =
            secret_stream::PushStream::init(OsRng, &breakpoint_stream_key);

        let intro = &SecureMsg::BreakpointIntro(BreakpointIntro {
            which: self.which.clone(),
        });
        send_insecure(
            &mut self.transport,
            &InsecureMsg::BreakpointSetupFinalize(BreakpointSetupFinalize {
                setup_secret_part2: breakpoint_secret_encap.as_bytes(),
                cipher_header: *tx_header.as_ref(),
                encrypted_intro: encrypt_message(intro, &mut tx_stream).encrypted,
            }),
        )
        .await
        .map_err(|err| format!("Couldn't send breakpoint setup finalize: {err}"))?;

        // Finally, we just need the controller's setup finalize message,
        // which will contain the stream header in the other direction.

        let controller_finalize = match receive_insecure(&mut self.transport).await {
            Ok(None) => Err(String::from(
                "Controller hung up before ControllerSetupFinalize",
            ))?,
            Ok(Some(InsecureMsg::ControllerSetupFinalize(setup))) => setup,
            Ok(Some(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent }))) => {
                Err(BreakpointConnectError {
                    message: format!("Controller rejected connection: {reason}"),
                    permanent,
                })?
            }
            Ok(Some(wrong)) => Err(format!(
                "Unexpected message type when waiting for ControllerSetupFinalize: {}",
                Into::<&str>::into(wrong)
            ))?,
            Err(err) => Err(format!(
                "Couldn't receive data when waiting for a ControllerSetupFinalize: {err}"
            ))?,
        };
        let rx_stream = secret_stream::PullStream::init(
            secret_stream::Header::from(controller_finalize.cipher_header),
            &controller_stream_key,
        );

        // Now both parties should have a fully secured connection.

        Ok(Connection {
            transport: self.transport,
            cipher_tx: tx_stream,
            cipher_rx: rx_stream,
        })
    }
}

#[cfg(test)]
mod tests {
    use crate::test_utils::all_variant_mapping;

    use super::*;

    /// Pinning test for digests.
    #[test]
    fn pubkey_digest() {
        let privkey = x_wing::DecapsulationKey::from([7u8; 32]);
        let pubkey = privkey.encapsulation_key();
        let digest_c = ControllerId::from_key(&pubkey);
        let digest_b = BreakpointId::from_key(&pubkey);

        // Note that Base58 does not produce the same output length
        // for a given input length -- it is content-dependent.
        assert_eq!(&digest_c.id_string(), "5TQGToHGhhPzq1dng");
        assert_eq!(&digest_b.id_string(), "59ARWFefSuMVrE9QG");
    }

    /// Validate bounds on ID string size.
    #[test]
    fn id_size_bounds() {
        // The shortest Base58 output is from all-zeroes.
        assert_eq!([0u8; DIGEST_BYTE_LEN].to_base58().len(), DIGEST_MIN_CHARS);

        // The longest output will be from an input with no leading zero bytes.
        assert_eq!([100u8; DIGEST_BYTE_LEN].to_base58().len(), DIGEST_MAX_CHARS);

        // Confirm we're using the right constant
        let hasher = Blake2KeyDigests::new_with_salt_and_personal(&[], &[], &[]).unwrap();
        let hash_out = hasher.finalize();
        assert_eq!(hash_out.into_bytes().len(), DIGEST_BYTE_LEN);
    }

    /// Format checking on ID strings
    #[test]
    fn id_format() {
        validate_id_format("3ZgBvRApjQw64rb8r").unwrap();
    }

    /// Pinning test for key derivation
    #[test]
    fn stream_key_pinning() {
        let ctrl_pair = x_wing::DecapsulationKey::from([20u8; 32]);
        let break_pair = x_wing::DecapsulationKey::from([21u8; 32]);
        let ctrl_secret = x_wing::SharedSecret::from([22u8; 32]);
        let break_secret = x_wing::SharedSecret::from([23u8; 32]);
        let (ctrl_stream_key, break_stream_key) = derive_stream_keys_dual_pub(
            &ctrl_pair.encapsulation_key(),
            &break_pair.encapsulation_key(),
            &ctrl_secret,
            &break_secret,
        );
        assert_eq!(
            hex::encode(ctrl_stream_key.as_ref()),
            "cab71a3c070baa772ece7eb5f108032527bc3e9d6d51839ec4b394cb3afd175f"
        );
        assert_eq!(
            hex::encode(break_stream_key.as_ref()),
            "bc1aeb4d8668dbc18f7fadb2d9f9fa8d3487996016dbbb8795acebaffcfa5b0c"
        );
    }

    /// Test [`InsecureMsg`] de/serialization (including field names)
    /// using pinned values.
    #[tokio::test]
    async fn insecure_message_format_pinning() {
        let examples = all_variant_mapping!(
            InsecureMsg => Vec<u8>:

            InsecureMsg::HandshakeRejected =
                (
                    HandshakeRejected {
                        reason: String::from("not feeling it"),
                        permanent: true,
                    }
                )
                => [
                    b"\xA1qHandshakeRejected\xA2" as &[u8],
                    b"freasonnnot feeling it",
                    b"ipermanent\xF5",
                ].concat(),

            InsecureMsg::BreakpointHello =
                (
                    BreakpointHello {
                        protocol: String::from("SOMEPROTO"),
                        break_pub: [6u8; 1216],
                        expecting_controller_id: String::from("cdigest"),
                    }
                )
                => [
                    b"\xA1oBreakpointHello\xA3" as &[u8],
                    b"hprotocoliSOMEPROTO",
                    b"ibreak_pubY\x04\xC0", &[b'\x06'; 1216],
                    b"wexpecting_controller_idgcdigest",
                ].concat(),

            InsecureMsg::ControllerHello =
                (
                    ControllerHello {
                        ctrl_pub: [6u8; 1216],
                        setup_secret_part1: [4u8; 1120],
                    }
                )
                => [
                    b"\xA1oControllerHello\xA2" as &[u8],
                    b"hctrl_pubY\x04\xC0", &[b'\x06'; 1216],
                    b"rsetup_secret_part1Y\x04\x60", &[b'\x04'; 1120],
                ].concat(),

            InsecureMsg::BreakpointSetupFinalize =
                (
                    BreakpointSetupFinalize {
                        setup_secret_part2: [5u8; 1120],
                        cipher_header: [8u8; 24],
                        encrypted_intro: [7u8; 10].to_vec(),
                    }
                )
                => [
                    b"\xA1wBreakpointSetupFinalize\xA3" as &[u8],
                    b"rsetup_secret_part2Y\x04\x60", &[b'\x05'; 1120],
                    b"mcipher_headerX\x18", &[b'\x08'; 24],
                    b"oencrypted_intro\x8A", &[b'\x07'; 10],
                ].concat(),

            InsecureMsg::ControllerSetupFinalize =
                (
                    ControllerSetupFinalize {
                        cipher_header: [9u8; 24],
                    }
                )
                => [
                    b"\xA1wControllerSetupFinalize\xA1" as &[u8],
                    b"mcipher_headerX\x18", &[b'\x09'; 24],
                ].concat(),

            InsecureMsg::EncryptedMsg =
                (
                    EncryptedMsg {
                        encrypted: [9u8; 3].to_vec(),
                    }
                )
                => b"\xA1lEncryptedMsg\xA1iencrypted\x43\x09\x09\x09".to_vec(),
        );
        for (variant_value, serialized) in examples {
            assert_eq!(
                serialized,
                serialize(&variant_value),
                "Serialized bytes did not match expected value for input {variant_value:?}"
            );
            let deserialized = deserialize(&serialized)
                .map_err(|err| format!("Could not deserialize: {err}; expected {variant_value:?}"))
                .unwrap();
            assert_eq!(
                variant_value, deserialized,
                "Deserialized value did not match expected value {variant_value:?}"
            );
        }
    }

    /// Test [`SecureMsg`] de/serialization (including field names) using
    /// pinned values.
    #[tokio::test]
    async fn secure_message_format_pinning() {
        let examples = all_variant_mapping!(
            SecureMsg => &[u8]:

            SecureMsg::BreakpointIntro =
                (
                    BreakpointIntro {
                        which: Some("loop".to_owned()),
                    }
                )
                => b"\xA1oBreakpointIntro\xA1ewhichdloop",

            SecureMsg::Command =
                (
                    Command {
                        command: "pwd".to_owned(),
                        seq: 5,
                    }
                )
                => b"\xA1gCommand\xA2gcommandcpwdcseq\x05",

            SecureMsg::OutputLine =
                (
                    OutputLine {
                        bytes: b"hello world!".to_vec(),
                        gender: OutputType::Stdout,
                        seq: 7,
                    }
                )
                => b"\xA1jOutputLine\xA3ebytes\x4Chello world!fgenderfStdoutcseq\x07",

            SecureMsg::Finished =
                (
                    Finished {
                        exit_code: Some(1),
                        seq: 9,
                    }
                )
                => b"\xA1hFinished\xA2iexit_code\x01cseq\x09",

            SecureMsg::TerminateCmd =
                (
                    TerminateCmd { seq: 11 }
                )
                => b"\xA1lTerminateCmd\xA1cseq\x0b",

            SecureMsg::ExitBreak =
                (ExitBreak {})
                => b"\xA1iExitBreak\xA0",
        );
        for (variant_value, serialized) in examples {
            assert_eq!(
                serialized,
                serialize(&variant_value),
                "Serialized bytes did not match expected value for input {variant_value:?}"
            );
            let deserialized = deserialize(serialized)
                .map_err(|err| format!("Could not deserialize: {err}; expected {variant_value:?}"))
                .unwrap();
            assert_eq!(
                variant_value, deserialized,
                "Deserialized value did not match expected value {variant_value:?}"
            );
        }
    }
}