krill 0.16.0

Resource Public Key Infrastructure (RPKI) daemon
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
//! Identifiers for components of a storage key.

use std::{error, fmt, mem};
use std::borrow::Cow;
use std::fmt::Write as _;
use std::str::FromStr;
use rpki::ca::idexchange::Handle;
use rpki::crypto::keys::KeyIdentifier;


//------------ Ident ---------------------------------------------------------

/// An identifier for a component of a storage key.
///
/// Idents are strings with a severly limited set of allowed characters.
/// Specifically, only ASCII letters and digits, plus, dash, underscore, and
/// periods are allowed. They can never be empty and they cannot start with
/// a period.
///
/// This type is an unsized type and needs to be used behind some kind of
/// pointer.
#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(transparent)]
pub struct Ident(str);

impl Ident {
    /// Creates a new ident from a byte slice.
    pub const fn from_bytes(bytes: &[u8]) -> Result<&Self, IdentError> {
        if let Err(err) = Self::check_bytes(bytes) {
            return Err(err)
        }

        // Safety: We just checked.
        Ok(unsafe { Self::from_bytes_unchecked(bytes) })
    }

    /// Creates a new ident form a string slice.
    pub const fn from_str(s: &str) -> Result<&Self, IdentError> {
        Self::from_bytes(s.as_bytes())
    }

    /// Creates a ident from the given string slice or panics.
    ///
    /// This function should be used to create segment constants.
    pub const fn make(s: &str) -> &Self {
        match Self::from_str(s) {
            Ok(some) => some,
            Err(_) => panic!("invalid storage identifier")
        }
    }

    /// Creates a new ident from a bytes slice without checking.
    ///
    /// # Safety
    ///
    /// The bytes slice must not be empty and must only contain valid
    /// characters.
    pub const unsafe fn from_bytes_unchecked(s: &[u8]) -> &Self {
        // SAFETY: Self has #repr(transparent)
        unsafe { mem::transmute(s) }
    }

    /// Creates a new boxed ident from a boxed byte slice.
    pub fn from_box(bytes: Box<[u8]>) -> Result<Box<Self>, IdentError> {
        Self::check_bytes(&bytes)?;

        // Safety: We just checked.
        Ok(unsafe { Self::from_box_unchecked(bytes) })
    }

    /// Creates a new boxed ident from a boxed slice without checking.
    ///
    /// # Safety
    ///
    /// The slice must not be empty and must only contain valid characters.
    pub const unsafe fn from_box_unchecked(s: Box<[u8]>) -> Box<Self> {
        // SAFETY: Self has #repr(transparent)
        unsafe { mem::transmute(s) }
    }

    /// Creates a new boxed ident from an owned string.
    pub fn boxed_from_string(s: String) -> Result<Box<Self>, IdentError> {
        Self::check_bytes(s.as_bytes())?;

        // Safety: We just checked.
        Ok(unsafe { Self::boxed_from_string_unchecked(s) })
    }

    /// Creates a new boxed ident from an owned string without checking..
    ///
    /// # Safety
    ///
    /// The string must not be empty and must only contain valid characters.
    pub unsafe fn boxed_from_string_unchecked(s: String) -> Box<Self> {
        unsafe { Self::from_box_unchecked(s.into_boxed_str().into()) }
    }

    /// Checks that `bytes` contains a valid ident.
    const fn check_bytes(mut bytes: &[u8]) -> Result<(), IdentError> {
        let Some(first) = bytes.first() else {
            return Err(IdentError(IdentErrorEnum::Empty))
        };
        if *first == b'.' {
            return Err(IdentError(IdentErrorEnum::LeadingDot))
        }

        while let Some((head, tail)) = bytes.split_first() {
            if !head.is_ascii_alphanumeric()
                && *head != b'+' && *head != b'-' 
                && *head != b'_' && *head != b'.'
            {
                return Err(
                    IdentError(IdentErrorEnum::IllegalCharacter(*head))
                )
            }
            bytes = tail;
        }
        Ok(())
    }

    /// Returns a builder to construct an ident from parts.
    ///
    /// The ident given through `start` will form the first part of the
    /// built ident.
    pub fn builder(start: impl Into<Box<Ident>>) -> IdentBuilder {
        IdentBuilder::new(start)
    }
}

impl Ident {
    // We don’t offer `len` and such. Previously, the scope was a sequence
    // of idents and `len` was used to check if it was a single or multi
    // element scope. So just to be sure to not break something, we leave it
    // out.

    /// Converts the ident to a byte slice.
    pub const fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }

    /// Converts the ident to a string slice.
    pub const fn as_str(&self) -> &str {
        &self.0
    }

    /// Returns a boxed ident equal to this ident.
    pub fn to_boxed(&self) -> Box<Self> {
        // Safety: we already have the correct content.
        unsafe { Ident::from_box_unchecked(Box::from(self.as_bytes())) }
    }
}

impl Ident {
    /// Constructs an ident from a number of parts and an optional extension.
    pub fn _from_parts<const N: usize>(
        parts: [&Self; N], extension: Option<&Self>
    ) -> Box<Ident> {
        const { assert!(N > 0) };
        let mut s: String = parts.into_iter().map(Ident::as_str).collect();
        if let Some(extension) = extension {
            s.push('.');
            s.push_str(extension.as_str())
        }

        // Safety: We composed the string out of idents and ensured that
        //         there is at least one (always non-empty) ident before
        //         the period.
        unsafe { Ident::boxed_from_string_unchecked(s) }
    }
}

impl Ident {
    /// Creates an ident from a handle.
    pub fn from_handle<T>(src: &Handle<T>) -> Cow<'_, Self> {
        // The only character allowed in a handle that we can’t have in an
        // ident is a slash but we do have a character that is allowed in
        // idents that isn’t allowed in handles: the plus. So, if we have a
        // slash, we can just do a simple replace if necessary.
        //
        // XXX Krill previously also allowed backslashes. For now we also
        //     replace them with a plus, but we need some sort of migration
        //     strategy to get rid of them.
        let res = if src.as_str().contains(['/', '\\']) {
            // Safety: This assumes that the handle only contains good
            //         characters after replacing.
            Cow::Owned(unsafe {
                Ident::boxed_from_string_unchecked(
                    src.as_str().replace(['/', '\\'], "+")
                )
            })
        }
        else {
            // Safety: This assumes that handle only contains good characters.
            Cow::Borrowed(unsafe {
                Ident::from_bytes_unchecked(src.as_ref())
            })
        };
        debug_assert!(Ident::check_bytes(res.as_bytes()).is_ok());
        res
    }

    /// Creates a handle from an ident if it can.
    ///
    /// If the identifier isn’t a valid handle, returns `None`.
    pub fn to_handle<T>(&self) -> Option<Handle<T>> {
        // XXX Because we currently replace both slashes and backslashes with
        //     plusses, we can’t reliably convert them back. For now we
        //     skip such handles.
        //
        //     We really need to get a, hrm, handle on this situation.
        Handle::from_str(self.as_str()).ok()
    }

    /// Creates an ident from a key identifier.
    pub fn from_key_identifier(src: KeyIdentifier) -> Box<Ident> {
        unsafe {
            Ident::boxed_from_string_unchecked(format!("{src}"))
        }
    }

    /// Creates an ident from the decimal representation of a `u64`.
    pub fn from_u64(src: u64) -> Box<Ident> {
        unsafe {
            Ident::boxed_from_string_unchecked(src.to_string())
        }
    }

    /// Creates an ident from the decimal representation of a `u64`.
    pub fn from_i64(src: i64) -> Box<Ident> {
        unsafe {
            Ident::boxed_from_string_unchecked(src.to_string())
        }
    }

    /// Creates an ident from a string, replacing it if necessary.
    ///
    /// This creates a unique value for any string. A string that is a valid
    /// ident and does not start with an underscore remains what it is. An
    /// empty string becomes a sole underscore. Any other string is replaced
    /// with the hex representation of its octets prefixed with an underscore.
    ///
    pub fn from_str_or_replace(src: &str) -> Cow<'_, Self> {
        if src.is_empty() {
            return Cow::Borrowed(const { Ident::make("_") })
        }
        if !src.starts_with('_') && let Ok(ident) = Self::from_str(src) {
            return Cow::Borrowed(ident)
        }
        let mut res = Vec::with_capacity(src.len() + 1);
        res.push(b'_');
        for ch in src.as_bytes() {
            res.extend_from_slice(&rpki::util::hex::encode_u8(*ch));
        }
        // Safety: only hex digits and underscore
        Cow::Owned(unsafe {
            Ident::from_box_unchecked(
                res.into_boxed_slice()
            )
        })
    }
}


//--- Clone

impl Clone for Box<Ident> {
    fn clone(&self) -> Self {
        // Safety: src is a valid ident.
        unsafe {
            Ident::from_box_unchecked(Box::from(self.as_bytes()))
        }
    }
}


//--- From

impl<'a> From<&'a Ident> for Box<Ident> {
    fn from(src: &'a Ident) -> Self {
        // Safety: src is a valid ident.
        unsafe {
            Ident::from_box_unchecked(Box::from(src.as_bytes()))
        }
    }
}

impl From<Box<Ident>> for Box<str> {
    fn from(src: Box<Ident>) -> Self {
        // Safety: Ident is a transparent wrapper around a str.
        unsafe { mem::transmute(src) }
    }
}


//--- AsRef

impl AsRef<[u8]> for Ident {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}

impl AsRef<str> for Ident {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}


//--- ToOwned

impl ToOwned for Ident {
    type Owned = Box<Ident>;

    fn to_owned(&self) -> Self::Owned {
        self.to_boxed()
    }
}


//--- Display

impl fmt::Display for Ident {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}


//------------ IdentBuilder --------------------------------------------------

/// Construct an ident from parts.
#[derive(Clone, Debug)]
pub struct IdentBuilder {
    /// The content so far.
    ///
    /// This is guaranteed to contain a valid ident at all times.
    content: String,
}

impl IdentBuilder {
    /// Creates a new builder starting with the given ident.
    ///
    /// Because an ident can never be empty, we always start with something.
    pub fn new(start: impl Into<Box<Ident>>) -> Self {
        IdentBuilder {
            content: Box::<str>::from(start.into()).into()
        }
    }

    /// Adds an ident to the end of the builder.
    pub fn push_ident(mut self, ident: &Ident) -> Self {
        self.content.push_str(ident.as_str());
        self
    }

    /// Adds a single dot to the end of the builder.
    ///
    /// This is a separate function since idents can’t start with a dot.
    pub fn push_dot(mut self) -> Self {
        self.content.push('.');
        self
    }

    /// Adds a handle to the end of the builder.
    pub fn push_handle<T>(mut self, src: &Handle<T>) -> Self {
        // XXX See Ident::from_handle for notes on the whole backslash
        //     malarky.
        let mut parts = src.as_str().split(['/', '\\']);

        // First part goes in directly. Empty handle shouldn’t happen, so
        // we can deal with it in whatever way we see fit.
        //
        // Empty string means the handle started with a slash and we don’t
        // need to treat it specially.
        match parts.next() {
            Some(part) => self.content.push_str(part),
            None => return self,
        }

        // Remaining parts go in with a plus in front. Empty strings mean
        // multiple subsequent slashes, so we can just pretend-append them.
        for part in parts {
            self.content.push('+');
            self.content.push_str(part);
        }

        self
    }

    /// Adds the hex representation of the key identifier to the builder.
    pub fn push_key_identifier(mut self, key: KeyIdentifier) -> Self {
        write!(self.content, "{key}").expect("format to string failed");
        self
    }

    /// Adds the decimal representation of a `u64` to the builder.
    pub fn push_u64(mut self, value: u64) -> Self {
        // This can only fail if we run out of memory, so panicking is okay.
        write!(self.content, "{value}").expect("format to string failed");
        self
    }

    /// Adds the decimal representation of a `i64` to the builder.
    pub fn push_i64(mut self, value: i64) -> Self {
        // This can only fail if we run out of memory, so panicking is okay.
        write!(self.content, "{value}").expect("format to string failed");
        self
    }

    /// Adds a string replacing it if it isn’t a valid ident.
    ///
    /// This creates a unique value for any string. A string that is a valid
    /// ident and does not start with a plus remains what it is. Any
    /// other string is replaced with the hex representation of its octets
    /// prefixed with a plus.
    ///
    /// Nothing will be appended if the string is empty.
    pub fn push_converted_str(mut self, s: &str) -> Self {
        if s.is_empty() {
            return self
        }

        if !s.starts_with("+") && Ident::check_bytes(s.as_bytes()).is_ok() {
            self.content.push_str(s);
            return self
        }
        
        self.content.push('+');
        for ch in s.as_bytes() {
            let hex = rpki::util::hex::encode_u8(*ch);
            self.content.push_str(
                // Safety: the function only returns ASCII hex digits.
                unsafe { std::str::from_utf8_unchecked(&hex) }
            );
        }

        self
    }

    /// Adds a dot followed by an ident and returns the resulting ident.
    pub fn finish_with_extension(self, ident: &Ident) -> Box<Ident> {
        self.push_dot().push_ident(ident).finish()
    }

    /// Finalises building and returns the resulting ident.
    pub fn finish(self) -> Box<Ident> {
        // Safety: We have a valid ident at all times.
        unsafe { Ident::boxed_from_string_unchecked(self.content) }
    }
}


//------------ IdentError ----------------------------------------------------

/// An error happened while parsing an [`Ident`].
#[derive(Clone, Copy, Debug)]
pub struct IdentError(IdentErrorEnum);

#[derive(Clone, Copy, Debug)]
enum IdentErrorEnum {
    Empty,
    LeadingDot,
    IllegalCharacter(u8)
}

impl fmt::Display for IdentError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::IdentErrorEnum::*;

        match self.0 {
            Empty => f.write_str("empty storage identifier"),
            LeadingDot => f.write_str("leading period"),
            IllegalCharacter(n) => {
                match char::from_u32(n.into()) {
                    Some(ch) => {
                        write!(f,
                            "storage identifier with illegal character '{ch}'"
                        )
                    },
                    None => {
                        write!(f,
                            "storage identifier with illegal character \
                             0x{n:02x}'"
                        )
                    }
                }
            }
        }
    }
}

impl error::Error for IdentError { }