isakmp 0.1.0

Protocol implementation of isakmp
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
//! Definitions of ISAKMP messages and types
//!
//! For more information, take a look at following documents:
//! - https://www.rfc-editor.org/rfc/rfc2407.html
//! - https://www.rfc-editor.org/rfc/rfc2408.html
//! - https://www.rfc-editor.org/rfc/rfc2409.html
//! - https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml

use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;
use zerocopy::network_endian::*;
use zerocopy::AsBytes;
use zerocopy::FromBytes;
use zerocopy::FromZeroes;
use zerocopy::Unaligned;

/// The header of an ISAKMP message
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.1
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct Header {
    /// Cookie of entity that initiated SA establishment, SA notification, or SA deletion.
    pub initiator_cookie: U64,
    /// Cookie of entity that is responding to an SA establishment request, SA notification,
    /// or SA deletion.
    pub responder_cookie: U64,
    /// The type of the next payload
    ///
    /// See [PayloadType] for all payloads
    pub next_payload: u8,
    /// Major and Minor Version
    ///
    /// 0 1 2 3 4 5 6 7
    /// -+-+-+-+-+-+-+-
    ///  MjVer ! MnVer
    ///
    /// Major Version (4 bits) - indicates the major version of the ISAKMP protocol in use
    /// Minor Version (4 bites) - indicates the minor version of the ISAKMP protocol in use
    pub version: u8,
    /// indicates the type of exchange being used.
    /// This dictates the message and payload orderings in the ISAKMP exchanges.
    pub exchange_type: u8,
    /// indicates specific options that are set for the ISAKMP exchange
    pub flags: u8,
    /// Unique Message Identifier used to identify protocol state during Phase 2 negotiations.
    /// This value is randomly generated by the initiator of the Phase 2 negotiation.
    /// In the event of simultaneous SA establishments (i.e.  collisions), the value of this field
    /// will likely be different because they are independently generated and, thus, two security
    /// associations will progress toward establishment. However, it is unlikely there will be
    /// absolute simultaneous establishments.
    ///
    /// During Phase 1 negotiations, the value MUST be set to 0.
    pub message_id: U32,
    /// Length of total message (header + payloads) in octets.
    ///
    /// Encryption can expand the size of an ISAKMP message.
    pub length: U32,
}

/// Type of the next payload
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.1
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum PayloadType {
    None = 0,
    SecurityAssociation = 1,
    Proposal = 2,
    Transform = 3,
    KeyExchange = 4,
    Identification = 5,
    Certificate = 6,
    CertificateRequest = 7,
    Hash = 8,
    Signature = 9,
    Nonce = 10,
    Notification = 11,
    Delete = 12,
    VendorID = 13,
}

/// Other payload types of [PayloadType] that can't be defined by Rusts enum
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum PayloadTypeOther {
    /// 14-127: RESERVED
    Reserved,
    /// 128-255: Private USE
    PrivateUse,
}

impl TryFrom<u8> for PayloadType {
    type Error = PayloadTypeOther;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        Ok(match value {
            0 => PayloadType::None,
            1 => PayloadType::SecurityAssociation,
            2 => PayloadType::Proposal,
            3 => PayloadType::Transform,
            4 => PayloadType::KeyExchange,
            5 => PayloadType::Identification,
            6 => PayloadType::Certificate,
            7 => PayloadType::CertificateRequest,
            8 => PayloadType::Hash,
            9 => PayloadType::Signature,
            10 => PayloadType::Nonce,
            11 => PayloadType::Notification,
            12 => PayloadType::Delete,
            13 => PayloadType::VendorID,
            14..128 => return Err(PayloadTypeOther::Reserved),
            _ => return Err(PayloadTypeOther::PrivateUse),
        })
    }
}

/// Type of the exchanged being used
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.1
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum ExchangeType {
    None = 0,
    Base = 1,
    IdentityProtection = 2,
    AuthenticationOnly = 3,
    Aggressive = 4,
    Informational = 5,
}

/// Other payload types of [PayloadType] that can't be defined by Rusts enum
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum ExchangeTypeOther {
    ///  6-31: ISAKMP Future Use
    IsakmpFutureUse,
    /// 32-239: DOI Specific Use
    DOISpecificUse,
    /// 240-255: Private USE
    PrivateUse,
}

impl TryFrom<u8> for ExchangeType {
    type Error = ExchangeTypeOther;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        Ok(match value {
            0 => ExchangeType::None,
            1 => ExchangeType::Base,
            2 => ExchangeType::IdentityProtection,
            3 => ExchangeType::AuthenticationOnly,
            4 => ExchangeType::Aggressive,
            5 => ExchangeType::Informational,
            6..32 => return Err(ExchangeTypeOther::IsakmpFutureUse),
            32..240 => return Err(ExchangeTypeOther::DOISpecificUse),
            _ => return Err(ExchangeTypeOther::PrivateUse),
        })
    }
}

/// A generic header that is used for all payload types
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.2
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct GenericPayloadHeader {
    /// Identifier for the payload type of the next payload in the message.
    /// If the current payload is the last in the message, then this field will be 0.
    /// This field provides the "chaining" capability.
    pub next_payload: u8,
    /// Unused, set to 0
    pub reserved: u8,
    /// Length in octets of the current payload, including the generic payload header.
    pub payload_length: U16,
}

/// A shortened form of a DataAttribute.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.3
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct DataAttributeShort {
    /// Unique identifier for each type of attribute.
    /// These attributes are defined as part of the DOI-specific information.
    ///
    /// The most significant bit indicates whether the data attribute follow the TLV format
    /// or the shortened TV format. In this case, it should be 1.
    pub attribute_type: U16,
    /// Value of the attribute associated with the DOI-specific Attribute Type.
    pub attribute_value: U16,
}

/// The static part of a variable data attribute.
/// By reading the static fields, you can obtain the missing `attribute_value` field which comes
/// directly after the `attribute_length` field.
///
/// For more information take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.3
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticDataAttributeLong {
    /// Unique identifier for each type of attribute.
    /// These attributes are defined as part of the DOI-specific information.
    ///
    /// The most significant bit indicates whether the data attribute follow the TLV format
    /// or the shortened TV format. In this case, it should be 0.
    pub attribute_type: U16,
    /// Length in octets of the Attribute Value
    pub attribute_length: U16,
}

/// Variable part of [StaticDataAttributeLong]
#[derive(Debug, Clone)]
pub struct VariableDataAttributeLong {
    /// Value of the attributes
    pub attribute_value: Vec<u8>,
}

/// The Security Association Payload is used to negotiate security
/// attributes and to indicate the Domain of Interpretation (DOI) and
/// Situation under which the negotiation is taking place
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.4
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticSecurityAssociationPayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    /// Domain of interpretation
    pub doi: U32,
}

/// The variable part of the [StaticSecurityAssociationPayload]
#[derive(Debug, Clone)]
pub struct VariableSecurityAssociationPayload {
    /// A DOI-specific field that identifies the situation under which this negotiation
    /// is taking place.
    pub situation: Vec<u8>,
}

/// The Proposal Payload contains information used during Security Association negotiation.
/// The proposal consists of security mechanisms, or transforms, to be used to secure the
/// communications channel
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.5
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticProposalPayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    /// Identifies the Proposal number for the current payload
    pub proposal_no: u8,
    /// Specifies the protocol identifier for the current negotiation
    pub protocol_id: u8,
    /// Length in octets of the SPI as defined by the Protocol-Id.  In the case of ISAKMP, the
    /// Initiator and Responder cookie pair from the ISAKMP Header is the ISAKMP SPI, therefore,
    /// the SPI Size is irrelevant and MAY be from zero (0) to sixteen (16).  If the SPI Size is
    /// non-zero, the content of the SPI field MUST be ignored. If the SPI Size is not a multiple of
    /// 4 octets it will have some impact on the SPI field and the alignment of all payloads in
    /// the message. The Domain of Interpretation (DOI) will dictate the SPI Size
    /// for other protocols.
    pub spi_size: u8,
    /// Specifies the number of transforms for the Proposal. Each of these is contained in
    /// a Transform payload.
    pub no_of_transforms: u8,
}

/// The variable part of the [StaticProposalPayload]
#[derive(Debug, Clone)]
pub struct VariableProposalPayload {
    /// The sending entity's SPI. In the event the SPI Size is not a multiple of 4 octets,
    /// there is no padding applied to the payload, however, it can be applied
    /// at the end of the message.
    pub spi: Vec<u8>,
}

/// The Transform Payload contains information used during Security Association negotiation.
/// The Transform payload consists of a specific security mechanism, or transforms, to be used to
/// secure the communications channel. The Transform payload also contains the security association
/// attributes associated with the specific transform. These SA attributes are DOI-specific.
///
/// # Alignment
/// If the SA Attributes are not aligned on 4-byte boundaries, then subsequent payloads will not be
/// aligned and any padding will be added at the end of the message to make the message 4-octet
/// aligned.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.6
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticTransformPayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    /// Identifies the Transform number for the current payload. If there is more than one transform
    /// proposed for a specific protocol within the Proposal payload, then each Transform payload
    /// has a unique Transform number
    pub transform_no: u8,
    /// Specifies the Transform identifier for the protocol within the current proposal.
    /// These transforms are defined by the DOI and are dependent on the protocol being negotiated.
    pub transform_id: u8,
    /// Unused, set to 0
    pub reserved: U16,
}

/// Variable part of [StaticTransformPayload]
#[derive(Debug, Clone)]
pub struct VariableTransformPayload {
    /// This field contains the security association attributes as defined for the transform given
    /// in the Transform-Id field.
    pub sa_attributes: Vec<u8>,
}

/// The Key Exchange Payload supports a variety of key exchange techniques.
/// Example key exchanges are [Oakley](https://datatracker.ietf.org/doc/html/rfc2408#ref-Oakley),
/// Diffie-Hellman, the enhanced Diffie-Hellman key exchange described in X9.42
/// [ANSI](https://datatracker.ietf.org/doc/html/rfc2408#ref-ANSI), and the RSA-based key exchange
/// used by PGP.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.7
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticKeyExchangePayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
}

/// The variable part of the [StaticKeyExchangePayload]
#[derive(Debug, Clone)]
pub struct VariableKeyExchangePayload {
    /// Key Exchange Data (variable length) - Data required to generate a session key.
    /// The interpretation of this data is specified by the DOI and the associated
    /// Key Exchange algorithm. This field may also contain pre-placed key indicators.
    pub key_exchange_data: Vec<u8>,
}

/// The Identification Payload contains DOI-specific data used to exchange identification
/// information. This information is used for determining the identities of communicating peers and
/// may be used for determining authenticity of information.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.8
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticIdentificationPayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    /// Specifies the type of Identification being used.
    pub id_type: u8,
}

/// The variable part of the [StaticIdentificationPayload]
#[derive(Debug, Clone)]
pub struct VariableIdentificationPayload {
    /// Contains identity information. The values for this field are DOI-specific and the format is
    /// specified by the ID Type field
    pub identification_data: Vec<u8>,
}

///  The Certificate Payload provides a means to transport certificates or other certificate-related
/// information via ISAKMP and can appear in any ISAKMP message. Certificate payloads SHOULD be
/// included in an exchange whenever an appropriate directory service (e.g.  Secure DNS
/// [DNSSEC](https://datatracker.ietf.org/doc/html/rfc2408#ref-DNSSEC)) is not available to
/// distribute certificates. The Certificate payload MUST be accepted at any point
/// during an exchange.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.9
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticCertificatePayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    ///  This field indicates the type of certificate or certificate-related information contained
    /// in the Certificate Data field.
    ///
    /// Can be interpreted with [CertificateEncoding]
    pub certificate_encoding: u8,
}

/// This field indicates the type of certificate or certificate-related information contained in
/// the Certificate Data field.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum CertificateEncoding {
    None = 0,
    PKCS7WrappedX509Certificate = 1,
    PGPCertificate = 2,
    DNSSignedKey = 3,
    X509CertificateSignature = 4,
    X509CertificateKeyExchange = 5,
    KerberosTokens = 6,
    CertificateRevocationList = 7,
    AuthorityRevocationList = 8,
    SPKICertificate = 9,
    X509CertificateAttribute = 10,
}

/// Other uses of [CertificateEncoding]
#[derive(Debug, Clone)]
pub enum CertificateEncodingOther {
    /// 11 - 255: Reserved
    Reserved,
}

impl TryFrom<u8> for CertificateEncoding {
    type Error = CertificateEncodingOther;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        Ok(match value {
            0 => CertificateEncoding::None,
            1 => CertificateEncoding::PKCS7WrappedX509Certificate,
            2 => CertificateEncoding::PGPCertificate,
            3 => CertificateEncoding::DNSSignedKey,
            4 => CertificateEncoding::X509CertificateSignature,
            5 => CertificateEncoding::X509CertificateKeyExchange,
            6 => CertificateEncoding::KerberosTokens,
            7 => CertificateEncoding::CertificateRevocationList,
            8 => CertificateEncoding::AuthorityRevocationList,
            9 => CertificateEncoding::SPKICertificate,
            10 => CertificateEncoding::X509CertificateAttribute,
            _ => return Err(CertificateEncodingOther::Reserved),
        })
    }
}

/// The variable part of the [StaticCertificatePayload]
#[derive(Debug, Clone)]
pub struct VariableCertificatePayload {
    /// Actual encoding of certificate data. The type of certificate is indicated by the Certificate
    /// Encoding field.
    pub certificate_data: Vec<u8>,
}

/// The Certificate Request Payload provides a means to request certificates via ISAKMP and can
/// appear in any message.  Certificate Request payloads SHOULD be included in an exchange whenever
/// an appropriate directory service
/// (e.g. Secure DNS [DNSSEC](https://datatracker.ietf.org/doc/html/rfc2408#ref-DNSSEC)) is not
/// available to distribute certificates.
/// The Certificate Request payload MUST be accepted at any point during the exchange.
/// The responder to the Certificate Request payload MUST send its certificate, if certificates are
/// supported, based on the values contained in the payload. If multiple certificates are required,
/// then multiple Certificate Request payloads SHOULD be transmitted.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.10
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticCertificateRequestPayload {
    /// Generic header
    pub generic_payload_header: GenericPayloadHeader,
    ///  This field indicates the type of certificate or certificate-related information contained
    /// in the Certificate Data field.
    ///
    /// Can be interpreted with [CertificateEncoding]
    pub certificate_encoding: u8,
}

/// The variable part of the [StaticCertificateRequestPayload]
#[derive(Debug, Clone)]
pub struct VariableCertificateRequestPayload {
    /// Contains an encoding of an acceptable certificate authority for the type of certificate
    /// requested.
    ///
    /// As an example, for an X.509 certificate this field would contain the Distinguished Name
    /// encoding of the Issuer Name of an X.509 certificate authority acceptable to the sender of
    /// this payload. This would be included to assist the responder in determining how much of the
    /// certificate chain would need to be sent in response to this request. If there is no specific
    /// certificate authority requested, this field SHOULD not be included.
    ///
    /// Can be interpreted with [CertificateEncoding]
    pub certificate_authority: Vec<u8>,
}

/// The Hash Payload contains data generated by the hash function (selected during the
/// SA establishment exchange), over some part of the message and/or ISAKMP state.
/// This payload may be used to verify the integrity of the data in an ISAKMP message or for
/// authentication of the negotiating entities.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.11
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticHashPayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
}

/// The variable part of the [StaticHashPayload]
#[derive(Debug, Clone)]
pub struct VariableHashPayload {
    /// Data that results from applying the hash routine to the ISAKMP message and/or state.
    pub hash_data: Vec<u8>,
}

/// The Signature Payload contains data generated by the digital signature function (selected during
/// the SA establishment exchange), over some part of the message and/or ISAKMP state.
/// This payload is used to verify the integrity of the data in the ISAKMP message, and may be of
/// use for non-repudiation services.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.12
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticSignaturePayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
}

/// The variable part of the [StaticSignaturePayload]
#[derive(Debug, Clone)]
pub struct VariableSignaturePayload {
    /// Data that results from applying the digital signature function to the
    /// ISAKMP message and/or state.
    pub signature_data: Vec<u8>,
}
/// The Nonce Payload contains random data used to guarantee liveness during an exchange and protect
/// against replay attacks. If nonces are used by a particular key exchange, the use of the
/// Nonce payload will be dictated by the key exchange. The nonces may be transmitted as part of
/// the key exchange data, or as a separate payload. However, this is defined by the key exchange,
/// not by ISAKMP.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.13
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticNoncePayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
}

/// The variable part of the [StaticNoncePayload]
#[derive(Debug, Clone)]
pub struct VariableNoncePayload {
    /// Contains the random data generated by the transmitting entity.
    pub nonce_data: Vec<u8>,
}

/// The Notification Payload can contain both ISAKMP and DOI-specific data and is used to transmit
/// informational data, such as error conditions, to an ISAKMP peer. It is possible to send multiple
/// Notification payloads in a single ISAKMP message.
///
///  Notification which occurs during, or is concerned with, a Phase 1 negotiation is identified by
/// the Initiator and Responder cookie pair in the ISAKMP Header.  The Protocol Identifier,
/// in this case, is ISAKMP and the SPI value is 0 because the cookie pair in the ISAKMP Header
/// identifies the ISAKMP SA. If the notification takes place prior to the completed exchange of
/// keying information, then the notification will be unprotected.
///
/// Notification which occurs during, or is concerned with, a Phase 2 negotiation is identified by
/// the Initiator and Responder cookie pair in the ISAKMP Header and the Message ID and SPI
/// associated with the current negotiation. One example for this type of notification is to
/// indicate why a proposal was rejected.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.14
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticNotificationPayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
    /// Domain of Interpretation
    pub doi: U32,
    /// Specifies the protocol identifier for the current notification.
    ///
    /// Examples might include ISAKMP, IPSEC ESP, IPSEC AH, OSPF, TLS, etc.
    pub protocol_id: u8,
    /// Length in octets of the SPI as defined by the Protocol-Id. In the case of ISAKMP,
    /// the Initiator and Responder cookie pair from the ISAKMP Header is the ISAKMP SPI,
    /// therefore, the SPI Size is irrelevant and MAY be from zero (0) to sixteen (16).
    /// If the SPI Size is non-zero, the content of the SPI field MUST be ignored.
    /// The Domain of Interpretation (DOI) will dictate the SPI Size for other protocols.
    pub spi_size: u8,
    /// Notify Message Type
    pub notify_message_type: U16,
}

/// Notification information can be error messages specifying why an SA could not be established.
/// It can also be status data that a process managing an SA database wishes to communicate with a
/// peer process.
///
/// For example, a secure front end or security gateway may use the Notify message to synchronize
/// SA communication. The table below lists the Nofitication messages and
/// their corresponding values.
///
/// Values in the Private Use range are expected to be DOI-specific values.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
#[repr(u16)]
#[allow(missing_docs)]
pub enum NotifyMessageType {
    Invalid = 0,
    // Error types
    InvalidPayloadType = 1,
    DOINotSupported = 2,
    SituationNotSupported = 3,
    InvalidCookie = 4,
    InvalidMajorVersion = 5,
    InvalidMinorVersion = 6,
    InvalidExchangeType = 7,
    InvalidFlags = 8,
    InvalidMessageId = 9,
    InvalidProtocolId = 10,
    InvalidSPI = 11,
    InvalidTransformId = 12,
    AttributesNotSupported = 13,
    NoProposalChosen = 14,
    BadProposalChosen = 15,
    PayloadMalformed = 16,
    InvalidKeyInformation = 17,
    InvalidIdInformation = 18,
    InvalidCertEncoding = 19,
    InvalidCertificate = 20,
    CertTypeUnsupported = 21,
    InvalidCertAuthority = 22,
    InvalidHashInformation = 23,
    AuthenticationFailed = 24,
    InvalidSignature = 25,
    AddressNotification = 26,
    NotifySALifetime = 27,
    CertificateUnavailable = 28,
    UnsupportedExchangeType = 29,
    UnequalPayloadLengths = 30,
    // Notify Messages
    Connected = 16384,
}

/// Other types of [NotifyMessageType]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
pub enum NotifyMessageTypeOther {
    /// 31 - 8191
    Reserved,
    /// 8192 - 16383
    PrivateUse,
    /// 16385 - 24575
    Reserved2,
    /// 24576 - 32767
    DOISpecific,
    /// 32768 - 40959
    PrivateUse2,
    /// 40960 - 65535
    Reserved3,
}

impl TryFrom<u16> for NotifyMessageType {
    type Error = NotifyMessageTypeOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        Ok(match value {
            0 => NotifyMessageType::Invalid,
            1 => NotifyMessageType::InvalidPayloadType,
            2 => NotifyMessageType::DOINotSupported,
            3 => NotifyMessageType::SituationNotSupported,
            4 => NotifyMessageType::InvalidCookie,
            5 => NotifyMessageType::InvalidMajorVersion,
            6 => NotifyMessageType::InvalidMinorVersion,
            7 => NotifyMessageType::InvalidExchangeType,
            8 => NotifyMessageType::InvalidFlags,
            9 => NotifyMessageType::InvalidMessageId,
            10 => NotifyMessageType::InvalidProtocolId,
            11 => NotifyMessageType::InvalidSPI,
            12 => NotifyMessageType::InvalidTransformId,
            13 => NotifyMessageType::AttributesNotSupported,
            14 => NotifyMessageType::NoProposalChosen,
            15 => NotifyMessageType::BadProposalChosen,
            16 => NotifyMessageType::PayloadMalformed,
            17 => NotifyMessageType::InvalidKeyInformation,
            18 => NotifyMessageType::InvalidIdInformation,
            19 => NotifyMessageType::InvalidCertEncoding,
            20 => NotifyMessageType::InvalidCertificate,
            21 => NotifyMessageType::CertTypeUnsupported,
            22 => NotifyMessageType::InvalidCertAuthority,
            23 => NotifyMessageType::InvalidHashInformation,
            24 => NotifyMessageType::AuthenticationFailed,
            25 => NotifyMessageType::InvalidSignature,
            26 => NotifyMessageType::AddressNotification,
            27 => NotifyMessageType::NotifySALifetime,
            28 => NotifyMessageType::CertificateUnavailable,
            29 => NotifyMessageType::UnsupportedExchangeType,
            30 => NotifyMessageType::UnequalPayloadLengths,
            31..8192 => return Err(NotifyMessageTypeOther::Reserved),
            8192..16384 => return Err(NotifyMessageTypeOther::PrivateUse),
            16384 => NotifyMessageType::Connected,
            16385..24576 => return Err(NotifyMessageTypeOther::Reserved2),
            24576..32768 => return Err(NotifyMessageTypeOther::PrivateUse2),
            _ => return Err(NotifyMessageTypeOther::Reserved3),
        })
    }
}

/// The variable part of the [StaticNotificationPayload]
#[derive(Debug, Clone)]
pub struct VariableNotificationPayload {
    /// Security Parameter Index
    pub spi: Vec<u8>,
    /// Informational or error data transmitted in addition to the Notify Message Type.
    /// Values for this field are DOI-specific.
    pub notification_data: Vec<u8>,
}

/// The Delete Payload contains a protocol-specific security association identifier that the sender
/// has removed from its security association database and is, therefore, no longer valid.
/// It is possible to send multiple SPIs in a Delete payload, however, each SPI MUST be for the
/// same protocol. Mixing of Protocol Identifiers MUST NOT be performed with the Delete payload.
///
/// Deletion which is concerned with an ISAKMP SA will contain a Protocol-Id of ISAKMP and the SPIs
/// are the initiator and responder cookies from the ISAKMP Header. Deletion which is concerned
/// with a Protocol SA, such as ESP or AH, will contain the Protocol-Id of that
/// protocol (e.g.  ESP, AH) and the SPI is the sending entity's SPI(s).
///
///  NOTE: The Delete Payload is not a request for the responder to delete an SA, but an advisory
/// from the initiator to the responder. If the responder chooses to ignore the message, the next
/// communication from the responder to the initiator, using that security association, will fail.
///  A responder is not expected to acknowledge receipt of a Delete payload.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.15
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticDeletePayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
    /// Domain of interpretation
    pub doi: U32,
    /// ISAKMP can establish security associations for various protocols, including ISAKMP and IPSEC.
    /// This field identifies which security association database to apply the delete request.
    pub protocol_id: u8,
    ///  Length in octets of the SPI as defined by the Protocol-Id. In the case of ISAKMP,
    /// the Initiator and Responder cookie pair is the ISAKMP SPI. In this case, the SPI
    /// Size would be 16 octets for each SPI being deleted.
    pub spi_size: u8,
    /// The number of SPIs contained in the Delete payload.
    /// The size of each SPI is defined by the SPI Size field.
    pub no_of_spis: U16,
}

/// The variable part of the [StaticDeletePayload]
#[derive(Debug, Clone)]
pub struct VariableDeletePayload {
    /// Identifies the specific security association(s) to delete. Values for this field are DOI
    /// and protocol specific.
    ///
    /// The length of this field is determined by the SPI Size and # of SPIs fields.
    pub security_parameter_indexes: Vec<u8>,
}

/// The Vendor ID Payload contains a vendor defined constant. The constant is used by vendors to
/// identify and recognize remote instances of their implementations. This mechanism allows a vendor
/// to experiment with new features while maintaining backwards compatibility.
/// This is not a general extension facility of ISAKMP.
///
/// The Vendor ID payload is not an announcement from the sender that it will send private
/// payload types. A vendor sending the Vendor ID MUST not make any assumptions about private
/// payloads that it may send unless a Vendor ID is received as well. Multiple Vendor ID payloads
/// MAY be sent. An implementation is NOT REQUIRED to understand any Vendor ID payloads.
/// An implementation is NOT REQUIRED to send any Vendor ID payload at all. If a private payload
/// was sent without prior agreement to send it, a compliant implementation may reject a
/// proposal with a notify message of type INVALID-PAYLOAD-TYPE.
///
/// If a Vendor ID payload is sent, it MUST be sent during the Phase 1 negotiation. Reception of a
/// familiar Vendor ID payload in the Phase 1 negotiation allows an implementation to make use of
/// Private USE payload numbers (128-255) for vendor specific extensions
/// during Phase 2 negotiations. The definition of "familiar" is left to implementations
/// to determine. Some vendors may wish to implement another vendor's extension prior to
/// standardization. However, this practice SHOULD not be widespread and vendors should work
/// towards standardization instead.
///
/// The vendor defined constant MUST be unique. The choice of hash and text to hash is left to the
/// vendor to decide. As an example, vendors could generate their vendor id by taking a plain
/// (non-keyed) hash of a string containing the product name, and the version of the product.
///
/// A hash is used instead of a vendor registry to avoid local cryptographic policy problems with
/// having a list of "approved" products, to keep away from maintaining a list of vendors, and to
/// allow classified products to avoid having to appear on any list.
///
/// For instance:
///    "Example Company IPsec.  Version 97.1"
///
/// (not including the quotes) has MD5 hash:
///    48544f9b1fe662af98b9b39e50c01a5a, when using MD5file.
///
/// Vendors may include all of the hash, or just a portion of it, as the payload length will bound
/// the data. There are no security implications of this hash, so its choice is arbitrary.
///
/// For more information, take a look at:
/// https://datatracker.ietf.org/doc/html/rfc2408#section-3.16
#[derive(Debug, FromBytes, FromZeroes, AsBytes, Unaligned, Copy, Clone)]
#[repr(packed)]
pub struct StaticVendorIDPayload {
    /// Generic payload
    pub generic_payload_header: GenericPayloadHeader,
}

/// The variable part of the [StaticVendorIDPayload]
#[derive(Debug, Clone)]
pub struct VariableVendorIDPayload {
    /// Hash of the vendor string plus version (as described above).
    pub vendor_id: Vec<u8>,
}

/// The Domain of Interpretation is a 32-bit value which identifies the
/// context in which the Security Association payload is to be evaluated.
/// Requests for assignments of new domain of interpretation identifiers
/// must be accompanied by a public specification, such as an Internet RFC.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
#[repr(u32)]
#[allow(missing_docs)]
pub enum DomainOfInterpretation {
    ISAKMP = 0,
    IPSEC = 1,
    GDOI = 2,
}

/// Invalid domain of interpretation received
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
#[error("Invalid domain of interpretation received: {0}")]
pub struct InvalidDomainOfInterpretation(pub u32);

impl TryFrom<u32> for DomainOfInterpretation {
    type Error = InvalidDomainOfInterpretation;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(DomainOfInterpretation::ISAKMP),
            1 => Ok(DomainOfInterpretation::IPSEC),
            2 => Ok(DomainOfInterpretation::GDOI),
            _ => Err(InvalidDomainOfInterpretation(value)),
        }
    }
}

/// All available encryption algorithms
///
/// Taken from https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)] // Base
#[derive(strum::EnumIter, strum::Display)] // Enumerate over variants + display implementation
#[derive(Serialize, Deserialize)] // Serialization
#[repr(u16)]
#[allow(non_camel_case_types, missing_docs)]
pub enum EncryptionAlgorithm {
    Reserved = 0,
    DES_CBC = 1,
    IDEA_CBC = 2,
    BlowfishCBC = 3,
    RC5_R16_B64_CBC = 4,
    TrippleDES_CBC = 5,
    CAST_CBC = 6,
    AES_CBC = 7,
    CAMELLIA_CBC = 8,
}

/// Other variants of [EncryptionAlgorithm]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum EncryptionAlgorithmOther {
    /// 9 - 65000
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 65001 - 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for EncryptionAlgorithm {
    type Error = EncryptionAlgorithmOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(EncryptionAlgorithm::Reserved),
            1 => Ok(EncryptionAlgorithm::DES_CBC),
            2 => Ok(EncryptionAlgorithm::IDEA_CBC),
            3 => Ok(EncryptionAlgorithm::BlowfishCBC),
            4 => Ok(EncryptionAlgorithm::RC5_R16_B64_CBC),
            5 => Ok(EncryptionAlgorithm::TrippleDES_CBC),
            6 => Ok(EncryptionAlgorithm::CAST_CBC),
            7 => Ok(EncryptionAlgorithm::AES_CBC),
            8 => Ok(EncryptionAlgorithm::CAMELLIA_CBC),
            9..65001 => Err(EncryptionAlgorithmOther::Unassigned(value)),
            _ => Err(EncryptionAlgorithmOther::PrivateUse(value)),
        }
    }
}

/// Available Hash algorithms
///
/// Taken from https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)] // Base
#[derive(strum::EnumIter, strum::Display)] // Enumerate over variants + display implementation
#[derive(Serialize, Deserialize)] // Serialization
#[repr(u16)]
#[allow(missing_docs)]
pub enum HashAlgorithm {
    Reserved = 0,
    MD5 = 1,
    SHA = 2,
    Tiger = 3,
    SHA2_256 = 4,
    SHA2_384 = 5,
    SHA2_512 = 6,
}

/// Other variant of [HashAlgorithm]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum HashAlgorithmOther {
    /// 7 - 65000
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 65001 - 65535
    #[error("Unassigned({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for HashAlgorithm {
    type Error = HashAlgorithmOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(HashAlgorithm::Reserved),
            1 => Ok(HashAlgorithm::MD5),
            2 => Ok(HashAlgorithm::SHA),
            3 => Ok(HashAlgorithm::Tiger),
            4 => Ok(HashAlgorithm::SHA2_256),
            5 => Ok(HashAlgorithm::SHA2_384),
            6 => Ok(HashAlgorithm::SHA2_512),
            7..65001 => Err(HashAlgorithmOther::Unassigned(value)),
            _ => Err(HashAlgorithmOther::PrivateUse(value)),
        }
    }
}

/// Available Authentication methods
///
/// Some of the variants were marked as reserved, but used before, so they were included to
/// retrieve as much information as possible
///
/// Taken from https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)] // Base
#[derive(strum::EnumIter, strum::Display)] // Enumerate over variants + display implementation
#[derive(Serialize, Deserialize)] // Serialization
#[repr(u16)]
#[allow(missing_docs)]
pub enum AuthenticationMethod {
    Reserved = 0,
    PreSharedKey = 1,
    DSSSignatures = 2,
    RSASignatures = 3,
    EncryptionWithRSA = 4,
    RevisedEncryptionWithRSA = 5,
    EncryptionWithElGamal = 6,
    RevisedEncryptionWithElGamal = 7,
    ECDSASignatures = 8,
    ECDSAWithSHA256OnP256Curve = 9,
    ECDSAWithSHA384OnP384Curve = 10,
    ECDSAWithSHA512OnP512Curve = 11,
    HybridMode = 64221,
    XAUTH = 65001,
}

/// Other variants of [AuthenticationMethod]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum AuthenticationMethodOther {
    /// 12 - 65000
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 65001 - 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for AuthenticationMethod {
    type Error = AuthenticationMethodOther;
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(AuthenticationMethod::Reserved),
            1 => Ok(AuthenticationMethod::PreSharedKey),
            2 => Ok(AuthenticationMethod::DSSSignatures),
            3 => Ok(AuthenticationMethod::RSASignatures),
            4 => Ok(AuthenticationMethod::EncryptionWithRSA),
            5 => Ok(AuthenticationMethod::RevisedEncryptionWithRSA),
            6 => Ok(AuthenticationMethod::EncryptionWithElGamal),
            7 => Ok(AuthenticationMethod::RevisedEncryptionWithElGamal),
            8 => Ok(AuthenticationMethod::ECDSASignatures),
            9 => Ok(AuthenticationMethod::ECDSAWithSHA256OnP256Curve),
            10 => Ok(AuthenticationMethod::ECDSAWithSHA384OnP384Curve),
            11 => Ok(AuthenticationMethod::ECDSAWithSHA512OnP512Curve),
            12..64221 => Err(AuthenticationMethodOther::Unassigned(value)),
            64221 => Ok(AuthenticationMethod::HybridMode),
            64222..65001 => Err(AuthenticationMethodOther::Unassigned(value)),
            65001 => Ok(AuthenticationMethod::XAUTH),
            _ => Err(AuthenticationMethodOther::PrivateUse(value)),
        }
    }
}

/// Available Group Descriptions
///
/// Taken from https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)] // Base
#[derive(strum::EnumIter, strum::Display)] // Enumerate over variants + display implementation
#[derive(Serialize, Deserialize)] // Serialization
#[repr(u16)]
#[allow(missing_docs, non_camel_case_types)]
pub enum GroupDescription {
    Reserved = 0,
    MODP_768 = 1,
    MODP_1024 = 2,
    EC2N_GF2_155 = 3,
    EC2N_GF2_185 = 4,
    MODP_1536 = 5,
    MODP_2048 = 14,
    MODP_3072 = 15,
    MODP_4096 = 16,
    MODP_6144 = 17,
    MODP_8192 = 18,
    ECP_Random_256 = 19,
    ECP_Random_384 = 20,
    ECP_Random_521 = 21,
    MODP_1024_160_PrimeOrderSubgroup = 22,
    MODP_2048_224_PrimeOrderSubgroup = 23,
    MODP_2048_256_PrimeOrderSubgroup = 24,
    ECP_Random_192 = 25,
    ECP_Random_224 = 26,
    ECP_Brainpool_224 = 27,
    ECP_Brainpool_256 = 28,
    ECP_Brainpool_384 = 29,
    ECP_Brainpool_512 = 30,
}

/// Other variants of [GroupDescription]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum GroupDescriptionOther {
    /// 31 - 32767
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 32768 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for GroupDescription {
    type Error = GroupDescriptionOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(GroupDescription::Reserved),
            1 => Ok(GroupDescription::MODP_768),
            2 => Ok(GroupDescription::MODP_1024),
            3 => Ok(GroupDescription::EC2N_GF2_155),
            4 => Ok(GroupDescription::EC2N_GF2_185),
            5 => Ok(GroupDescription::MODP_1536),
            6..14 => Err(GroupDescriptionOther::Unassigned(value)),
            14 => Ok(GroupDescription::MODP_2048),
            15 => Ok(GroupDescription::MODP_3072),
            16 => Ok(GroupDescription::MODP_4096),
            17 => Ok(GroupDescription::MODP_6144),
            18 => Ok(GroupDescription::MODP_8192),
            19 => Ok(GroupDescription::ECP_Random_256),
            20 => Ok(GroupDescription::ECP_Random_384),
            21 => Ok(GroupDescription::ECP_Random_521),
            22 => Ok(GroupDescription::MODP_1024_160_PrimeOrderSubgroup),
            23 => Ok(GroupDescription::MODP_2048_224_PrimeOrderSubgroup),
            24 => Ok(GroupDescription::MODP_2048_256_PrimeOrderSubgroup),
            25 => Ok(GroupDescription::ECP_Random_192),
            26 => Ok(GroupDescription::ECP_Random_224),
            27 => Ok(GroupDescription::ECP_Brainpool_224),
            28 => Ok(GroupDescription::ECP_Brainpool_256),
            29 => Ok(GroupDescription::ECP_Brainpool_384),
            30 => Ok(GroupDescription::ECP_Brainpool_512),
            31..32768 => Err(GroupDescriptionOther::Unassigned(value)),
            _ => Err(GroupDescriptionOther::PrivateUse(value)),
        }
    }
}

/// Type of data attributes
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)] // Base
#[derive(strum::EnumIter, strum::Display)] // Enumerate over variants + display implementation
#[derive(Serialize, Deserialize)] // Serialization
#[repr(u16)]
#[allow(missing_docs)]
pub enum AttributeType {
    Reserved = 0,
    EncryptionAlgorithm = 1,
    HashAlgorithm = 2,
    AuthenticationMethod = 3,
    GroupDescription = 4,
    GroupType = 5,
    GroupPrime = 6,
    GroupGeneratorOne = 7,
    GroupGeneratorTwo = 8,
    GroupCurveA = 9,
    GroupCurveB = 10,
    LifeType = 11,
    LifeDuration = 12,
    PRF = 13,
    KeyLength = 14,
    FieldSize = 15,
    GroupOrder = 16,
}

/// Other variants of [AttributeType]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum AttributeTypeOther {
    /// 17 - 16383
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 16384 - 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for AttributeType {
    type Error = AttributeTypeOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(AttributeType::Reserved),
            1 => Ok(AttributeType::EncryptionAlgorithm),
            2 => Ok(AttributeType::HashAlgorithm),
            3 => Ok(AttributeType::AuthenticationMethod),
            4 => Ok(AttributeType::GroupDescription),
            5 => Ok(AttributeType::GroupType),
            6 => Ok(AttributeType::GroupPrime),
            7 => Ok(AttributeType::GroupGeneratorOne),
            8 => Ok(AttributeType::GroupGeneratorTwo),
            9 => Ok(AttributeType::GroupCurveA),
            10 => Ok(AttributeType::GroupCurveB),
            11 => Ok(AttributeType::LifeType),
            12 => Ok(AttributeType::LifeDuration),
            13 => Ok(AttributeType::PRF),
            14 => Ok(AttributeType::KeyLength),
            15 => Ok(AttributeType::FieldSize),
            16 => Ok(AttributeType::GroupOrder),
            17..16384 => Err(AttributeTypeOther::Unassigned(value)),
            _ => Err(AttributeTypeOther::PrivateUse(value)),
        }
    }
}

/// Available group types
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
#[allow(missing_docs)]
#[repr(u16)]
pub enum GroupType {
    Reserved = 0,
    MODP = 1,
    ECP = 2,
    ECP2N = 3,
}

/// Other values for [GroupType]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum GroupTypeOther {
    /// 4 - 65000
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 65001 - 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for GroupType {
    type Error = GroupTypeOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(GroupType::Reserved),
            1 => Ok(GroupType::MODP),
            2 => Ok(GroupType::ECP),
            3 => Ok(GroupType::ECP2N),
            4..65001 => Err(GroupTypeOther::Unassigned(value)),
            _ => Err(GroupTypeOther::PrivateUse(value)),
        }
    }
}

/// Available life types
///
/// For a given "Life Type" the value of the "Life Duration" attribute defines
/// the actual length of the SA life -- either a number of seconds, or a number
/// of kbytes protected.
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy)]
#[allow(missing_docs)]
#[repr(u16)]
pub enum LifeType {
    Reserved = 0,
    Seconds = 1,
    Kilobytes = 2,
}

/// Other values for [LifeType]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Copy, Error)]
pub enum LifeTypeOther {
    /// 4 - 65000
    #[error("Unassigned({0})")]
    Unassigned(u16),
    /// 65001 - 65535
    #[error("PrivateUse({0})")]
    PrivateUse(u16),
}

impl TryFrom<u16> for LifeType {
    type Error = LifeTypeOther;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(LifeType::Reserved),
            1 => Ok(LifeType::Seconds),
            2 => Ok(LifeType::Kilobytes),
            3..65001 => Err(LifeTypeOther::Unassigned(value)),
            _ => Err(LifeTypeOther::PrivateUse(value)),
        }
    }
}