hickory_proto/dnssec/rdata/
sig.rs

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
// Copyright 2015-2023 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// https://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! signature record for signing queries, updates, and responses
use std::fmt;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use super::DNSSECRData;
use crate::{
    dnssec::Algorithm,
    error::{ProtoError, ProtoResult},
    rr::{Name, RData, RecordData, RecordDataDecodable, RecordType, SerialNumber},
    serialize::binary::{
        BinDecodable, BinDecoder, BinEncodable, BinEncoder, Restrict, RestrictedMath,
    },
};

/// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4), Domain Name System Security Extensions, March 1999
///
/// NOTE: RFC 2535 was obsoleted with 4034+, with the exception of the
///  usage for UPDATE, which is what this implementation is for.
///
/// ```text
/// 4.1 SIG RDATA Format
///
///  The RDATA portion of a SIG RR is as shown below.  The integrity of
///  the RDATA information is protected by the signature field.
///
///  1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
///  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |        type covered           |  algorithm    |     labels    |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                         original TTL                          |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                      signature expiration                     |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |                      signature inception                      |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/// |            key  tag           |                               |
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+         signer's name         +
/// |                                                               /
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-/
/// /                                                               /
/// /                            signature                          /
/// /                                                               /
/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
///
/// ```
/// [RFC 2931](https://tools.ietf.org/html/rfc2931), DNS Request and Transaction Signatures, September 2000
///
/// NOTE: 2931 updates SIG0 to clarify certain particulars...
///
/// ```text
/// RFC 2931                       DNS SIG(0)                 September 2000
///
/// 3. The SIG(0) Resource Record
///
///    The structure of and type number of SIG resource records (RRs) is
///    given in [RFC 2535] Section 4.1.  However all of Section 4.1.8.1 and
///    the parts of Sections 4.2 and 4.3 related to SIG(0) should be
///    considered replaced by the material below.  Any conflict between [RFC
///    2535] and this document concerning SIG(0) RRs should be resolved in
///    favor of this document.
///
///    For all transaction SIG(0)s, the signer field MUST be a name of the
///    originating host and there MUST be a KEY RR at that name with the
///    public key corresponding to the private key used to calculate the
///    signature.  (The host domain name used may be the inverse IP address
///    mapping name for an IP address of the host if the relevant KEY is
///    stored there.)
///
///    For all SIG(0) RRs, the owner name, class, TTL, and original TTL, are
///    meaningless.  The TTL fields SHOULD be zero and the CLASS field
///    SHOULD be ANY.  To conserve space, the owner name SHOULD be root (a
///    single zero octet).  When SIG(0) authentication on a response is
///    desired, that SIG RR MUST be considered the highest priority of any
///    additional information for inclusion in the response. If the SIG(0)
///    RR cannot be added without causing the message to be truncated, the
///    server MUST alter the response so that a SIG(0) can be included.
///    This response consists of only the question and a SIG(0) record, and
///    has the TC bit set and RCODE 0 (NOERROR).  The client should at this
///    point retry the request using TCP.
///
/// 3.1 Calculating Request and Transaction SIGs
///
///    A DNS request may be optionally signed by including one SIG(0)s at
///    the end of the query additional information section.  Such a SIG is
///    identified by having a "type covered" field of zero. It signs the
///    preceding DNS request message including DNS header but not including
///    the UDP/IP header and before the request RR counts have been adjusted
///    for the inclusions of the request SIG(0).
///
///    It is calculated by using a "data" (see [RFC 2535], Section 4.1.8) of
///    (1) the SIG's RDATA section entirely omitting (not just zeroing) the
///    signature subfield itself, (2) the DNS query messages, including DNS
///    header, but not the UDP/IP header and before the reply RR counts have
///    been adjusted for the inclusion of the SIG(0).  That is
///
///       data = RDATA | request - SIG(0)
///
///    where "|" is concatenation and RDATA is the RDATA of the SIG(0) being
///    calculated less the signature itself.
///
///    Similarly, a SIG(0) can be used to secure a response and the request
///    that produced it.  Such transaction signatures are calculated by
///    using a "data" of (1) the SIG's RDATA section omitting the signature
///    itself, (2) the entire DNS query message that produced this response,
///    including the query's DNS header but not its UDP/IP header, and (3)
///    the entire DNS response message, including DNS header but not the
///    UDP/IP header and before the response RR counts have been adjusted
///    for the inclusion of the SIG(0).
///
///    That is
///
///       data = RDATA | full query | response - SIG(0)
///
///    where "|" is concatenation and RDATA is the RDATA of the SIG(0) being
///    calculated less the signature itself.
///
///    Verification of a response SIG(0) (which is signed by the server host
///    key, not the zone key) by the requesting resolver shows that the
///    query and response were not tampered with in transit, that the
///    response corresponds to the intended query, and that the response
///    comes from the queried server.
///
///    In the case of a DNS message via TCP, a SIG(0) on the first data
///    packet is calculated with "data" as above and for each subsequent
///    packet, it is calculated as follows:
///
///       data = RDATA | DNS payload - SIG(0) | previous packet
///
///    where "|" is concatenations, RDATA is as above, and previous packet
///    is the previous DNS payload including DNS header and the SIG(0) but
///    not the TCP/IP header.  Support of SIG(0) for TCP is OPTIONAL.  As an
///    alternative, TSIG may be used after, if necessary, setting up a key
///    with TKEY [RFC 2930].
///
///    Except where needed to authenticate an update, TKEY, or similar
///    privileged request, servers are not required to check a request
///    SIG(0).
///
///    Note: requests and responses can either have a single TSIG or one
///    SIG(0) but not both a TSIG and a SIG(0).
///
/// 3.2 Processing Responses and SIG(0) RRs
///
///    If a SIG RR is at the end of the additional information section of a
///    response and has a type covered of zero, it is a transaction
///    signature covering the response and the query that produced the
///    response.  For TKEY responses, it MUST be checked and the message
///    rejected if the checks fail unless otherwise specified for the TKEY
///    mode in use.  For all other responses, it MAY be checked and the
///    message rejected if the checks fail.
///
///    If a response's SIG(0) check succeed, such a transaction
///    authentication SIG does NOT directly authenticate the validity any
///    data-RRs in the message.  However, it authenticates that they were
///    sent by the queried server and have not been diddled.  (Only a proper
///    SIG(0) RR signed by the zone or a key tracing its authority to the
///    zone or to static resolver configuration can directly authenticate
///
///    data-RRs, depending on resolver policy.) If a resolver or server does
///    not implement transaction and/or request SIGs, it MUST ignore them
///    without error where they are optional and treat them as failing where
///    they are required.
///
/// 3.3 SIG(0) Lifetime and Expiration
///
///    The inception and expiration times in SIG(0)s are for the purpose of
///    resisting replay attacks.  They should be set to form a time bracket
///    such that messages outside that bracket can be ignored.  In IP
///    networks, this time bracket should not normally extend further than 5
///    minutes into the past and 5 minutes into the future.
/// ```
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct SIG {
    type_covered: RecordType,
    algorithm: Algorithm,
    num_labels: u8,
    original_ttl: u32,
    sig_expiration: u32,
    sig_inception: u32,
    key_tag: u16,
    signer_name: Name,
    sig: Vec<u8>,
}

impl SIG {
    /// Creates a new SIG record data, used for both RRSIG and SIG(0) records.
    ///
    /// # Arguments
    ///
    /// * `type_covered` - The `RecordType` which this signature covers, should be NULL for SIG(0).
    /// * `algorithm` - The `Algorithm` used to generate the `signature`.
    /// * `num_labels` - The number of labels in the name, should be less 1 for *.name labels,
    ///                  see `Name::num_labels()`.
    /// * `original_ttl` - The TTL for the RRSet stored in the zone, should be 0 for SIG(0).
    /// * `sig_expiration` - Timestamp at which this signature is no longer valid, very important to
    ///                      keep this low, < +5 minutes to limit replay attacks.
    /// * `sig_inception` - Timestamp when this signature was generated.
    /// * `key_tag` - See the key_tag generation in `rr::dnssec::Signer::key_tag()`.
    /// * `signer_name` - Domain name of the server which was used to generate the signature.
    /// * `sig` - signature stored in this record.
    ///
    /// # Return value
    ///
    /// The new SIG record data.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        type_covered: RecordType,
        algorithm: Algorithm,
        num_labels: u8,
        original_ttl: u32,
        sig_expiration: u32,
        sig_inception: u32,
        key_tag: u16,
        signer_name: Name,
        sig: Vec<u8>,
    ) -> Self {
        Self {
            type_covered,
            algorithm,
            num_labels,
            original_ttl,
            sig_expiration,
            sig_inception,
            key_tag,
            signer_name,
            sig,
        }
    }

    /// Add actual signature value to existing SIG record data.
    ///
    /// # Arguments
    ///
    /// * `signature` - signature to be stored in this record.
    ///
    /// # Return value
    ///
    /// The new SIG record data.
    pub fn set_sig(self, signature: Vec<u8>) -> Self {
        Self {
            type_covered: self.type_covered,
            algorithm: self.algorithm,
            num_labels: self.num_labels,
            original_ttl: self.original_ttl,
            sig_expiration: self.sig_expiration,
            sig_inception: self.sig_inception,
            key_tag: self.key_tag,
            signer_name: self.signer_name,
            sig: signature,
        }
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.1), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.1 Type Covered Field
    ///
    ///  The "type covered" is the type of the other RRs covered by this SIG.
    /// ```
    pub fn type_covered(&self) -> RecordType {
        self.type_covered
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.2), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.2 Algorithm Number Field
    ///
    ///  This octet is as described in section 3.2.
    /// ```
    pub fn algorithm(&self) -> Algorithm {
        self.algorithm
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.3), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.3 Labels Field
    ///
    ///  The "labels" octet is an unsigned count of how many labels there are
    ///  in the original SIG RR owner name not counting the null label for
    ///  root and not counting any initial "*" for a wildcard.  If a secured
    ///  retrieval is the result of wild card substitution, it is necessary
    ///  for the resolver to use the original form of the name in verifying
    ///  the digital signature.  This field makes it easy to determine the
    ///  original form.
    ///
    ///  If, on retrieval, the RR appears to have a longer name than indicated
    ///  by "labels", the resolver can tell it is the result of wildcard
    ///  substitution.  If the RR owner name appears to be shorter than the
    ///  labels count, the SIG RR must be considered corrupt and ignored.  The
    ///  maximum number of labels allowed in the current DNS is 127 but the
    ///  entire octet is reserved and would be required should DNS names ever
    ///  be expanded to 255 labels.  The following table gives some examples.
    ///  The value of "labels" is at the top, the retrieved owner name on the
    ///  left, and the table entry is the name to use in signature
    ///  verification except that "bad" means the RR is corrupt.
    ///
    ///  labels= |  0  |   1  |    2   |      3   |      4   |
    ///  --------+-----+------+--------+----------+----------+
    ///         .|   . | bad  |  bad   |    bad   |    bad   |
    ///        d.|  *. |   d. |  bad   |    bad   |    bad   |
    ///      c.d.|  *. | *.d. |   c.d. |    bad   |    bad   |
    ///    b.c.d.|  *. | *.d. | *.c.d. |   b.c.d. |    bad   |
    ///  a.b.c.d.|  *. | *.d. | *.c.d. | *.b.c.d. | a.b.c.d. |
    /// ```
    pub fn num_labels(&self) -> u8 {
        self.num_labels
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.4), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.4 Original TTL Field
    ///
    ///  The "original TTL" field is included in the RDATA portion to avoid
    ///  (1) authentication problems that caching servers would otherwise
    ///  cause by decrementing the real TTL field and (2) security problems
    ///  that unscrupulous servers could otherwise cause by manipulating the
    ///  real TTL field.  This original TTL is protected by the signature
    ///  while the current TTL field is not.
    ///
    ///  NOTE:  The "original TTL" must be restored into the covered RRs when
    ///  the signature is verified (see Section 8).  This generally implies
    ///  that all RRs for a particular type, name, and class, that is, all the
    ///  RRs in any particular RRset, must have the same TTL to start with.
    /// ```
    pub fn original_ttl(&self) -> u32 {
        self.original_ttl
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.5), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.5 Signature Expiration and Inception Fields
    ///
    ///  The SIG is valid from the "signature inception" time until the
    ///  "signature expiration" time.  Both are unsigned numbers of seconds
    ///  since the start of 1 January 1970, GMT, ignoring leap seconds.  (See
    ///  also Section 4.4.)  Ring arithmetic is used as for DNS SOA serial
    ///  numbers [RFC 1982] which means that these times can never be more
    ///  than about 68 years in the past or the future.  This means that these
    ///  times are ambiguous modulo ~136.09 years.  However there is no
    ///  security flaw because keys are required to be changed to new random
    ///  keys by [RFC 2541] at least every five years.  This means that the
    ///  probability that the same key is in use N*136.09 years later should
    ///  be the same as the probability that a random guess will work.
    ///
    ///  A SIG RR may have an expiration time numerically less than the
    ///  inception time if the expiration time is near the 32 bit wrap around
    ///  point and/or the signature is long lived.
    ///
    ///  (To prevent misordering of network requests to update a zone
    ///  dynamically, monotonically increasing "signature inception" times may
    ///  be necessary.)
    ///
    ///  A secure zone must be considered changed for SOA serial number
    ///  purposes not only when its data is updated but also when new SIG RRs
    ///  are inserted (ie, the zone or any part of it is re-signed).
    /// ```
    ///
    /// [RFC 2535](https://datatracker.ietf.org/doc/html/rfc2535#appendix-C), Appendix B: Changes from RFC 2065, 3.
    ///
    /// ```text
    /// (3b) clarifying that signature expiration and inception use
    /// serial number ring arithmetic
    /// ```
    pub fn sig_expiration(&self) -> SerialNumber {
        SerialNumber(self.sig_expiration)
    }

    /// see [`Self::sig_expiration`]
    pub fn sig_inception(&self) -> SerialNumber {
        SerialNumber(self.sig_inception)
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.6), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.6 Key Tag Field
    ///
    ///  The "key Tag" is a two octet quantity that is used to efficiently
    ///  select between multiple keys which may be applicable and thus check
    ///  that a public key about to be used for the computationally expensive
    ///  effort to check the signature is possibly valid.  For algorithm 1
    ///  (MD5/RSA) as defined in [RFC 2537], it is the next to the bottom two
    ///  octets of the public key modulus needed to decode the signature
    ///  field.  That is to say, the most significant 16 of the least
    ///  significant 24 bits of the modulus in network (big endian) order. For
    ///  all other algorithms, including private algorithms, it is calculated
    ///  as a simple checksum of the KEY RR as described in Appendix C.
    /// ```
    pub fn key_tag(&self) -> u16 {
        self.key_tag
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.7), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.7 Signer's Name Field
    ///
    ///  The "signer's name" field is the domain name of the signer generating
    ///  the SIG RR.  This is the owner name of the public KEY RR that can be
    ///  used to verify the signature.  It is frequently the zone which
    ///  contained the RRset being authenticated.  Which signers should be
    ///  authorized to sign what is a significant resolver policy question as
    ///  discussed in Section 6. The signer's name may be compressed with
    ///  standard DNS name compression when being transmitted over the
    ///  network.
    /// ```
    pub fn signer_name(&self) -> &Name {
        &self.signer_name
    }

    /// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-4.1.8), Domain Name System Security Extensions, March 1999
    ///
    /// ```text
    /// 4.1.8 Signature Field
    ///
    ///  The actual signature portion of the SIG RR binds the other RDATA
    ///  fields to the RRset of the "type covered" RRs with that owner name
    ///  and class.  This covered RRset is thereby authenticated.  To
    ///  accomplish this, a data sequence is constructed as follows:
    ///
    ///  data = RDATA | RR(s)...
    ///
    ///  where "|" is concatenation,
    ///
    ///  RDATA is the wire format of all the RDATA fields in the SIG RR itself
    ///  (including the canonical form of the signer's name) before but not
    ///  including the signature, and
    ///
    ///  RR(s) is the RRset of the RR(s) of the type covered with the same
    ///  owner name and class as the SIG RR in canonical form and order as
    ///  defined in Section 8.
    ///
    ///  How this data sequence is processed into the signature is algorithm
    ///  dependent.  These algorithm dependent formats and procedures are
    ///  described in separate documents (Section 3.2).
    ///
    ///  SIGs SHOULD NOT be included in a zone for any "meta-type" such as
    ///  ANY, AXFR, etc. (but see section 5.6.2 with regard to IXFR).
    /// ```
    pub fn sig(&self) -> &[u8] {
        &self.sig
    }
}

impl BinEncodable for SIG {
    /// [RFC 4034](https://tools.ietf.org/html/rfc4034#section-6), DNSSEC Resource Records, March 2005
    ///
    /// This is accurate for all currently known name records.
    ///
    /// ```text
    /// 6.2.  Canonical RR Form
    ///
    ///    For the purposes of DNS security, the canonical form of an RR is the
    ///    wire format of the RR where:
    ///
    ///    ...
    ///
    ///    3.  if the type of the RR is NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
    ///        HINFO, MINFO, MX, HINFO, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
    ///        SRV, DNAME, A6, RRSIG, or (rfc6840 removes NSEC), all uppercase
    ///        US-ASCII letters in the DNS names contained within the RDATA are replaced
    ///        by the corresponding lowercase US-ASCII letters;
    /// ```
    fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> {
        let is_canonical_names = encoder.is_canonical_names();

        self.type_covered().emit(encoder)?;
        self.algorithm().emit(encoder)?;
        encoder.emit(self.num_labels())?;
        encoder.emit_u32(self.original_ttl())?;
        encoder.emit_u32(self.sig_expiration().0)?;
        encoder.emit_u32(self.sig_inception().0)?;
        encoder.emit_u16(self.key_tag())?;
        self.signer_name()
            .emit_with_lowercase(encoder, is_canonical_names)?;
        encoder.emit_vec(self.sig())?;
        Ok(())
    }
}

impl<'r> RecordDataDecodable<'r> for SIG {
    fn read_data(decoder: &mut BinDecoder<'r>, length: Restrict<u16>) -> ProtoResult<Self> {
        let start_idx = decoder.index();

        // TODO should we verify here? or elsewhere...
        let type_covered = RecordType::read(decoder)?;
        let algorithm = Algorithm::read(decoder)?;
        let num_labels = decoder.read_u8()?.unverified(/*technically valid as any u8*/);
        let original_ttl = decoder.read_u32()?.unverified(/*valid as any u32*/);
        let sig_expiration = decoder.read_u32()?.unverified(/*valid as any u32, in practice should be in the future*/);
        let sig_inception = decoder.read_u32()?.unverified(/*valid as any u32, in practice should be before expiration*/);
        let key_tag = decoder.read_u16()?.unverified(/*valid as any u16*/);
        let signer_name = Name::read(decoder)?;

        // read the signature, this will vary buy key size
        let sig_len = length
        .map(|u| u as usize)
        .checked_sub(decoder.index() - start_idx)
        .map_err(|_| ProtoError::from("invalid rdata length in SIG"))?
        .unverified(/*used only as length safely*/);
        let sig = decoder
        .read_vec(sig_len)?
        .unverified(/*will fail in usage if invalid*/);

        Ok(Self::new(
            type_covered,
            algorithm,
            num_labels,
            original_ttl,
            sig_expiration,
            sig_inception,
            key_tag,
            signer_name,
            sig,
        ))
    }
}

impl RecordData for SIG {
    fn try_from_rdata(data: RData) -> Result<Self, RData> {
        match data {
            RData::DNSSEC(DNSSECRData::SIG(csync)) => Ok(csync),
            _ => Err(data),
        }
    }

    fn try_borrow(data: &RData) -> Option<&Self> {
        match data {
            RData::DNSSEC(DNSSECRData::SIG(csync)) => Some(csync),
            _ => None,
        }
    }

    fn record_type(&self) -> RecordType {
        RecordType::SIG
    }

    fn into_rdata(self) -> RData {
        RData::DNSSEC(DNSSECRData::SIG(self))
    }
}

/// specifically for outputting the RData for an RRSIG, with signer_name in canonical form
#[allow(clippy::too_many_arguments)]
pub fn emit_pre_sig(
    encoder: &mut BinEncoder<'_>,
    type_covered: RecordType,
    algorithm: Algorithm,
    num_labels: u8,
    original_ttl: u32,
    sig_expiration: SerialNumber,
    sig_inception: SerialNumber,
    key_tag: u16,
    signer_name: &Name,
) -> ProtoResult<()> {
    type_covered.emit(encoder)?;
    algorithm.emit(encoder)?;
    encoder.emit(num_labels)?;
    encoder.emit_u32(original_ttl)?;
    encoder.emit_u32(sig_expiration.0)?;
    encoder.emit_u32(sig_inception.0)?;
    encoder.emit_u16(key_tag)?;
    signer_name.emit_as_canonical(encoder, true)?;
    Ok(())
}

/// [RFC 2535](https://tools.ietf.org/html/rfc2535#section-7.2), Domain Name System Security Extensions, March 1999
///
/// ```text
/// 7.2 Presentation of SIG RRs
///
///    A data SIG RR may be represented as a single logical line in a zone
///    data file [RFC 1033] but there are some special considerations as
///    described below.  (It does not make sense to include a transaction or
///    request authenticating SIG RR in a file as they are a transient
///    authentication that covers data including an ephemeral transaction
///    number and so must be calculated in real time.)
///
///    There is no particular problem with the signer, covered type, and
///    times.  The time fields appears in the form YYYYMMDDHHMMSS where YYYY
///    is the year, the first MM is the month number (01-12), DD is the day
///    of the month (01-31), HH is the hour in 24 hours notation (00-23),
///    the second MM is the minute (00-59), and SS is the second (00-59).
///
///    The original TTL field appears as an unsigned integer.
///
///    If the original TTL, which applies to the type signed, is the same as
///    the TTL of the SIG RR itself, it may be omitted.  The date field
///    which follows it is larger than the maximum possible TTL so there is
///    no ambiguity.
///
///    The "labels" field appears as an unsigned integer.
///
///    The key tag appears as an unsigned number.
///
///    However, the signature itself can be very long.  It is the last data
///    field and is represented in base 64 (see Appendix A) and may be
///    divided up into any number of white space separated substrings, down
///    to single base 64 digits, which are concatenated to obtain the full
///    signature.  These substrings can be split between lines using the
///    standard parenthesis.
///
///   foo.nil.    SIG NXT 1 2 ( ;type-cov=NXT, alg=1, labels=2
///     19970102030405 ;signature expiration
///     19961211100908 ;signature inception
///     2143           ;key identifier
///     foo.nil.       ;signer
///     AIYADP8d3zYNyQwW2EM4wXVFdslEJcUx/fxkfBeH1El4ixPFhpfHFElxbvKoWmvjDTCm
///     fiYy2X+8XpFjwICHc398kzWsTMKlxovpz2FnCTM= ;signature (640 bits)
/// ```
impl fmt::Display for SIG {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        write!(f, "{ty_covered} {alg} {num_labels} {original_ttl} {expire} {inception} {tag} {signer} {sig}",
            ty_covered = self.type_covered,
            alg = self.algorithm,
            num_labels = self.num_labels,
            original_ttl = self.original_ttl,
            expire = self.sig_expiration,
            inception = self.sig_inception,
            tag = self.key_tag,
            signer = self.signer_name,
            sig = data_encoding::BASE64.encode(&self.sig)
        )
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::dbg_macro, clippy::print_stdout)]

    use super::*;

    #[test]
    fn test() {
        use std::str::FromStr;

        let rdata = SIG::new(
            RecordType::NULL,
            Algorithm::RSASHA256,
            0,
            0,
            2,
            1,
            5,
            Name::from_str("www.example.com").unwrap(),
            vec![
                0, 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, 29, 31,
            ], // 32 bytes for SHA256
        );

        let mut bytes = Vec::new();
        let mut encoder: BinEncoder<'_> = BinEncoder::new(&mut bytes);
        assert!(rdata.emit(&mut encoder).is_ok());
        let bytes = encoder.into_bytes();

        println!("bytes: {bytes:?}");

        let mut decoder: BinDecoder<'_> = BinDecoder::new(bytes);
        let restrict = Restrict::new(bytes.len() as u16);
        let read_rdata = SIG::read_data(&mut decoder, restrict).expect("Decoding error");
        assert_eq!(rdata, read_rdata);
    }
}