Skip to main content

lakers_ead_zeroconf/
lib.rs

1#![no_std]
2
3use lakers_shared::{Crypto as CryptoTrait, *};
4
5// ---- initiator side (device)
6
7#[derive(Default, PartialEq, Copy, Clone, Debug)]
8pub enum EADInitiatorProtocolState {
9    #[default]
10    NonInitialized,
11    Start,
12    WaitEAD2,
13    Completed, // TODO[ead]: check if it is really ok to consider Completed after processing EAD_2
14    Error,
15}
16
17#[derive(PartialEq, Debug, Copy, Clone)]
18pub struct EADInitiatorState {
19    pub protocol_state: EADInitiatorProtocolState,
20    pub(crate) id_u: EdhocMessageBuffer, // identifier of the device (U), equivalent to ID_CRED_I in EDHOC
21    pub(crate) g_w: BytesP256ElemLen,    // public key of the enrollment server (W)
22    pub(crate) loc_w: EdhocMessageBuffer, // address of the enrollment server (W)
23    pub(crate) prk: BytesHashLen,
24    pub(crate) voucher: BytesMac,
25}
26
27impl EADInitiatorState {
28    pub fn new(id_u: EdhocMessageBuffer, g_w: BytesP256ElemLen, loc_w: EdhocMessageBuffer) -> Self {
29        EADInitiatorState {
30            protocol_state: EADInitiatorProtocolState::Start,
31            id_u,
32            g_w,
33            loc_w,
34            prk: [0u8; SHA256_DIGEST_LEN],
35            voucher: [0u8; MAC_LENGTH],
36        }
37    }
38}
39
40// shared mutable global state for EAD
41// NOTE: this is not thread-safe
42static mut EAD_INITIATOR_GLOBAL_STATE: EADInitiatorState = EADInitiatorState {
43    protocol_state: EADInitiatorProtocolState::Start,
44    // FIXME: lots of wasted bytes in id_u and loc_w.
45    // they could both be &[u8], but that would require a <'a> lifetime which clashes with the static lifetime of the global state
46    id_u: EdhocMessageBuffer {
47        content: [0u8; MAX_MESSAGE_SIZE_LEN],
48        len: 0,
49    },
50    g_w: [0u8; P256_ELEM_LEN],
51    loc_w: EdhocMessageBuffer {
52        content: [0u8; MAX_MESSAGE_SIZE_LEN],
53        len: 0,
54    },
55    prk: [0u8; SHA256_DIGEST_LEN],
56    voucher: [0u8; MAC_LENGTH],
57};
58pub fn ead_initiator_get_global_state() -> &'static EADInitiatorState {
59    unsafe { &EAD_INITIATOR_GLOBAL_STATE }
60}
61pub fn ead_initiator_get_global_state_own() -> EADInitiatorState {
62    unsafe { EAD_INITIATOR_GLOBAL_STATE }
63}
64pub fn ead_initiator_set_global_state(new_state: EADInitiatorState) {
65    unsafe {
66        EAD_INITIATOR_GLOBAL_STATE = new_state;
67    }
68}
69
70pub fn i_prepare_ead_1<Crypto: CryptoTrait>(
71    crypto: &mut Crypto,
72    x: &BytesP256ElemLen,
73    ss: u8,
74) -> Option<EADItem> {
75    let state = ead_initiator_get_global_state();
76    if state.protocol_state != EADInitiatorProtocolState::Start {
77        return None;
78    }
79
80    // PRK = EDHOC-Extract(salt, IKM)
81    let prk = compute_prk(crypto, x, &state.g_w);
82
83    // plaintext = (ID_U: bstr)
84    let encoded_id_u = encode_id_u(&state.id_u);
85    let enc_id = encrypt_enc_id(crypto, &prk, &encoded_id_u, ss);
86    let value = Some(encode_ead_1_value(&state.loc_w, &enc_id));
87
88    let ead_1 = EADItem {
89        label: EAD_ZEROCONF_LABEL,
90        is_critical: true,
91        value,
92    };
93
94    ead_initiator_set_global_state(EADInitiatorState {
95        protocol_state: EADInitiatorProtocolState::WaitEAD2,
96        prk,
97        ..ead_initiator_get_global_state_own()
98    });
99
100    Some(ead_1)
101}
102
103pub fn i_process_ead_2<Crypto: CryptoTrait>(
104    crypto: &mut Crypto,
105    ead_2: EADItem,
106    cred_v_u8: &[u8],
107    h_message_1: &BytesHashLen,
108) -> Result<(), ()> {
109    if ead_2.label != EAD_ZEROCONF_LABEL || ead_2.value.is_none() {
110        return Err(());
111    }
112    let mut ead_2_value: BytesEncodedVoucher = Default::default();
113    ead_2_value[..].copy_from_slice(&ead_2.value.unwrap().content[..ENCODED_VOUCHER_LEN]);
114
115    let state = ead_initiator_get_global_state();
116
117    // TODO: this conversion can be avoided if we change the type of cred_v to &[u8] troughout the code
118    let mut cred_v = EdhocMessageBuffer::new();
119    cred_v.fill_with_slice(cred_v_u8).unwrap();
120
121    match verify_voucher(crypto, &ead_2_value, h_message_1, &cred_v, &state.prk) {
122        Ok(voucher) => {
123            ead_initiator_set_global_state(EADInitiatorState {
124                protocol_state: EADInitiatorProtocolState::Completed,
125                voucher,
126                ..ead_initiator_get_global_state_own()
127            });
128            Ok(())
129        }
130        Err(_) => {
131            ead_initiator_set_global_state(EADInitiatorState {
132                protocol_state: EADInitiatorProtocolState::Error,
133                ..ead_initiator_get_global_state_own()
134            });
135            Err(())
136        }
137    }
138}
139
140pub fn i_prepare_ead_3() -> Option<EADItem> {
141    Some(EADItem::new())
142}
143
144fn verify_voucher<Crypto: CryptoTrait>(
145    crypto: &mut Crypto,
146    received_voucher: &BytesEncodedVoucher,
147    h_message_1: &BytesHashLen,
148    cred_v: &EdhocMessageBuffer,
149    prk: &BytesHashLen,
150) -> Result<BytesMac, ()> {
151    let prepared_voucher = &prepare_voucher(crypto, h_message_1, cred_v, prk);
152    if received_voucher == prepared_voucher {
153        let mut voucher_mac: BytesMac = Default::default();
154        voucher_mac[..MAC_LENGTH].copy_from_slice(&prepared_voucher[1..1 + MAC_LENGTH]);
155        return Ok(voucher_mac);
156    } else {
157        return Err(());
158    }
159}
160
161fn encode_id_u(id_u: &EdhocMessageBuffer) -> EdhocMessageBuffer {
162    // plaintext = (ID_U: bstr)
163    let mut plaintext = EdhocMessageBuffer::new();
164    plaintext.content[0] = CBOR_MAJOR_BYTE_STRING + id_u.len as u8;
165    plaintext.content[1..1 + id_u.len].copy_from_slice(id_u.as_slice());
166    plaintext.len = 1 + id_u.len;
167
168    plaintext
169}
170
171fn encrypt_enc_id<Crypto: CryptoTrait>(
172    crypto: &mut Crypto,
173    prk: &BytesHashLen,
174    plaintext: &EdhocMessageBuffer,
175    ss: u8,
176) -> EdhocMessageBuffer {
177    let (k_1, iv_1) = compute_k_1_iv_1(crypto, &prk);
178
179    // external_aad = (SS: int)
180    let enc_structure = encode_enc_structure(ss);
181
182    // ENC_ID = 'ciphertext' of COSE_Encrypt0
183    crypto.aes_ccm_encrypt_tag_8(&k_1, &iv_1, &enc_structure[..], plaintext)
184}
185
186fn compute_prk<Crypto: CryptoTrait>(
187    crypto: &mut Crypto,
188    a: &BytesP256ElemLen,
189    g_b: &BytesP256ElemLen,
190) -> BytesHashLen {
191    // NOTE: salt should be h'' (the zero-length byte string), but crypto backends are hardcoded to salts of size SHA256_DIGEST_LEN (32).
192    //       nevertheless, using a salt of HashLen zeros works as well (see RFC 5869, Section 2.2).
193    let salt: BytesHashLen = [0u8; SHA256_DIGEST_LEN];
194    let g_ab = crypto.p256_ecdh(a, g_b);
195    crypto.hkdf_extract(&salt, &g_ab)
196}
197
198fn compute_k_1_iv_1<Crypto: CryptoTrait>(
199    crypto: &mut Crypto,
200    prk: &BytesHashLen,
201) -> (BytesCcmKeyLen, BytesCcmIvLen) {
202    // K_1 = EDHOC-Expand(PRK, info = (0, h'', AES_CCM_KEY_LEN), length)
203    let mut k_1: BytesCcmKeyLen = [0x00; AES_CCM_KEY_LEN];
204    let k_1_buf = edhoc_kdf_expand(
205        crypto,
206        prk,
207        EAD_ZEROCONF_INFO_K_1_LABEL,
208        &[0x00; MAX_KDF_CONTEXT_LEN],
209        0,
210        AES_CCM_KEY_LEN,
211    );
212    k_1[..].copy_from_slice(&k_1_buf[..AES_CCM_KEY_LEN]);
213
214    // IV_1 = EDHOC-Expand(PRK, info = (1, h'', AES_CCM_IV_LEN), length)
215    let mut iv_1: BytesCcmIvLen = [0x00; AES_CCM_IV_LEN];
216    let iv_1_buf = edhoc_kdf_expand(
217        crypto,
218        prk,
219        EAD_ZEROCONF_INFO_IV_1_LABEL,
220        &[0x00; MAX_KDF_CONTEXT_LEN],
221        0,
222        AES_CCM_IV_LEN,
223    );
224    iv_1[..].copy_from_slice(&iv_1_buf[..AES_CCM_IV_LEN]);
225
226    (k_1, iv_1)
227}
228
229fn encode_enc_structure(ss: u8) -> [u8; EAD_ZEROCONF_ENC_STRUCTURE_LEN] {
230    let mut encrypt0: Bytes8 = [0x00; 8];
231    encrypt0[0] = 0x45u8; // 'E'
232    encrypt0[1] = 0x6eu8; // 'n'
233    encrypt0[2] = 0x63u8; // 'c'
234    encrypt0[3] = 0x72u8; // 'r'
235    encrypt0[4] = 0x79u8; // 'y'
236    encrypt0[5] = 0x70u8; // 'p'
237    encrypt0[6] = 0x74u8; // 't'
238    encrypt0[7] = 0x30u8; // '0'
239
240    let mut enc_structure: [u8; EAD_ZEROCONF_ENC_STRUCTURE_LEN] =
241        [0x00; EAD_ZEROCONF_ENC_STRUCTURE_LEN];
242
243    // encode Enc_structure from rfc9052 Section 5.3
244    enc_structure[0] = CBOR_MAJOR_ARRAY | 3 as u8; // 3 is the fixed number of elements in the array
245    enc_structure[1] = CBOR_MAJOR_TEXT_STRING | encrypt0.len() as u8;
246    enc_structure[2..2 + encrypt0.len()].copy_from_slice(&encrypt0[..]);
247    enc_structure[encrypt0.len() + 2] = CBOR_MAJOR_BYTE_STRING | 0x00 as u8; // 0 for zero-length byte string (empty Header)
248    enc_structure[encrypt0.len() + 3] = CBOR_MAJOR_BYTE_STRING | 0x01 as u8; // 1 for the `ss` value
249    enc_structure[encrypt0.len() + 4] = ss;
250
251    enc_structure
252}
253
254// TODO: consider moving this to a new 'edhoc crypto primnitives' module
255fn edhoc_kdf_expand<Crypto: CryptoTrait>(
256    crypto: &mut Crypto,
257    prk: &BytesHashLen,
258    label: u8,
259    context: &BytesMaxContextBuffer,
260    context_len: usize,
261    length: usize,
262) -> BytesMaxBuffer {
263    let (info, info_len) = encode_info(label, context, context_len, length);
264    let output = crypto.hkdf_expand(prk, &info, info_len, length);
265    output
266}
267
268fn encode_ead_1_value(
269    loc_w: &EdhocMessageBuffer,
270    enc_id: &EdhocMessageBuffer,
271) -> EdhocMessageBuffer {
272    let mut output = EdhocMessageBuffer::new();
273
274    output.content[0] = CBOR_BYTE_STRING;
275    // put length at output.content[1] after other sizes are known
276
277    output.content[2] = CBOR_TEXT_STRING;
278    output.content[3] = loc_w.len as u8;
279    output.content[4..4 + loc_w.len].copy_from_slice(loc_w.as_slice());
280
281    output.content[4 + loc_w.len] = CBOR_MAJOR_BYTE_STRING + enc_id.len as u8;
282    output.content[5 + loc_w.len..5 + loc_w.len + enc_id.len].copy_from_slice(enc_id.as_slice());
283
284    output.len = 5 + loc_w.len + enc_id.len;
285    output.content[1] = (output.len - 2) as u8;
286
287    output
288}
289
290// ---- responder side (authenticator)
291
292#[derive(Default, PartialEq, Copy, Clone, Debug)]
293pub enum EADResponderProtocolState {
294    #[default]
295    Start,
296    ProcessedEAD1,
297    WaitEAD3,
298    Completed,
299    Error,
300}
301
302pub struct EADResponderState {
303    pub protocol_state: EADResponderProtocolState,
304    pub(crate) voucher_response: Option<EdhocMessageBuffer>,
305}
306
307impl EADResponderState {
308    pub fn new() -> Self {
309        EADResponderState {
310            protocol_state: EADResponderProtocolState::Start,
311            voucher_response: None,
312        }
313    }
314}
315
316// shared mutable global state for EAD
317// NOTE: this is not thread-safe
318static mut EAD_RESPONDER_GLOBAL_STATE: EADResponderState = EADResponderState {
319    protocol_state: EADResponderProtocolState::Start,
320    voucher_response: None,
321};
322pub fn ead_responder_get_global_state() -> &'static EADResponderState {
323    unsafe { &EAD_RESPONDER_GLOBAL_STATE }
324}
325pub fn ead_responder_set_global_state(new_state: EADResponderState) {
326    unsafe {
327        EAD_RESPONDER_GLOBAL_STATE = new_state;
328    }
329}
330
331pub fn r_process_ead_1<Crypto: CryptoTrait>(
332    crypto: &mut Crypto,
333    ead_1: &EADItem,
334    message_1: &EdhocMessageBuffer,
335) -> Result<(), EDHOCError> {
336    let opaque_state: Option<EdhocMessageBuffer> = None; // TODO: receive as parameter
337
338    if ead_1.label != EAD_ZEROCONF_LABEL || ead_1.value.is_none() {
339        return Err(EDHOCError::EADError);
340    }
341
342    let (loc_w, _enc_id) = parse_ead_1_value(&ead_1.value.unwrap())?;
343    let voucher_request = encode_voucher_request(message_1, &opaque_state);
344
345    // TODO:
346    // - implement voucher_response = send_voucher_request(&loc_w, &voucher_request);
347    // - save voucher_response in global state
348    let voucher_response = mock_send_voucher_request(crypto, &loc_w, &voucher_request);
349
350    if let Ok(voucher_response) = voucher_response {
351        ead_responder_set_global_state(EADResponderState {
352            protocol_state: EADResponderProtocolState::ProcessedEAD1,
353            voucher_response: Some(voucher_response),
354        });
355        return Ok(());
356    } else {
357        ead_responder_set_global_state(EADResponderState {
358            protocol_state: EADResponderProtocolState::Error,
359            voucher_response: None,
360        });
361        return Err(EDHOCError::EADError);
362    }
363}
364
365// FIXME: this and the other *_prepare_* functions should return a `Result<>`
366//        but then how to handle in the ead-none case, when it should return `None`?
367//        that would require a lot of boilerplate on the calling side
368pub fn r_prepare_ead_2() -> Option<EADItem> {
369    let state = ead_responder_get_global_state();
370
371    if let Some(voucher_response) = state.voucher_response {
372        // FIXME: we probably don't want to parse the voucher response here, but rather receive only the 'voucher' part, already parsed
373        let (_message_1, voucher, _opaque_state) =
374            parse_voucher_response(&voucher_response).unwrap();
375
376        let voucher_value = voucher[..].try_into().unwrap();
377
378        ead_responder_set_global_state(EADResponderState {
379            protocol_state: EADResponderProtocolState::Completed,
380            voucher_response: None,
381        });
382
383        Some(EADItem {
384            label: EAD_ZEROCONF_LABEL,
385            is_critical: true,
386            value: Some(voucher_value),
387        })
388    } else {
389        ead_responder_set_global_state(EADResponderState {
390            protocol_state: EADResponderProtocolState::Error,
391            voucher_response: None,
392        });
393
394        None
395    }
396}
397
398pub fn r_process_ead_3(_ead_3: EADItem) -> Result<(), ()> {
399    // TODO: maybe retrive CRED_U from a Credential Database
400
401    // state.protocol_state = EADResponderProtocolState::Completed;
402
403    Ok(())
404}
405
406fn parse_voucher_response(
407    voucher_response: &EdhocMessageBuffer,
408) -> Result<
409    (
410        EdhocMessageBuffer,
411        BytesEncodedVoucher,
412        Option<EdhocMessageBuffer>,
413    ),
414    EDHOCError,
415> {
416    let mut decoder = CBORDecoder::new(voucher_response.as_slice());
417
418    let array_size = decoder.array()?;
419    if !(2..=3).contains(&array_size) {
420        return Err(EDHOCError::EADError);
421    }
422
423    let message_1: EdhocMessageBuffer = decoder.bytes()?.try_into().unwrap();
424    let voucher: BytesEncodedVoucher = decoder
425        .bytes_sized(ENCODED_VOUCHER_LEN)?
426        .try_into()
427        .unwrap();
428
429    if array_size == 3 {
430        let opaque_state: EdhocMessageBuffer = decoder.bytes()?.try_into().unwrap();
431        return Ok((message_1, voucher, Some(opaque_state)));
432    } else {
433        return Ok((message_1, voucher, None));
434    }
435}
436
437fn parse_ead_1_value(
438    value: &EdhocMessageBuffer,
439) -> Result<(EdhocMessageBuffer, EdhocMessageBuffer), EDHOCError> {
440    let mut outer_decoder = CBORDecoder::new(value.as_slice());
441    let voucher_info_seq = outer_decoder.bytes()?;
442    let mut seq_decoder = CBORDecoder::new(voucher_info_seq);
443    Ok((
444        seq_decoder.str()?.try_into().unwrap(),
445        seq_decoder.bytes()?.try_into().unwrap(),
446    ))
447}
448
449pub fn encode_voucher_request(
450    message_1: &EdhocMessageBuffer,
451    opaque_state: &Option<EdhocMessageBuffer>,
452) -> EdhocMessageBuffer {
453    let mut output = EdhocMessageBuffer::new();
454
455    output.content[1] = CBOR_BYTE_STRING;
456    output.content[2] = message_1.len as u8;
457    output.content[3..3 + message_1.len].copy_from_slice(message_1.as_slice());
458
459    if let Some(opaque_state) = opaque_state {
460        output.content[0] = CBOR_MAJOR_ARRAY | 2;
461
462        output.content[3 + message_1.len] = CBOR_BYTE_STRING;
463        output.content[4 + message_1.len] = opaque_state.len as u8;
464        output.content[5 + message_1.len..5 + message_1.len + opaque_state.len]
465            .copy_from_slice(opaque_state.as_slice());
466
467        output.len = 5 + message_1.len + opaque_state.len;
468    } else {
469        output.content[0] = CBOR_MAJOR_ARRAY | 1;
470        output.len = 3 + message_1.len;
471    }
472
473    output
474}
475
476// ---- enrollment server side (W)
477// core functions + mock implementation
478
479#[derive(PartialEq, Debug, Copy, Clone)]
480pub struct MockEADServerState {
481    pub(crate) cred_v: EdhocMessageBuffer, // identifier of the device (U), equivalent to ID_CRED_I in EDHOC
482    pub(crate) w: BytesP256ElemLen,        // public key of the enrollment server (W)
483    pub acl: Option<EdhocMessageBuffer>, // access control list, each device identified by an u8 kid
484}
485impl MockEADServerState {
486    pub fn new(cred_v: &[u8], w: BytesP256ElemLen, acl: Option<EdhocMessageBuffer>) -> Self {
487        let cred_v: EdhocMessageBuffer = cred_v.try_into().unwrap();
488        MockEADServerState { cred_v, w, acl }
489    }
490    pub fn authorized(self, kid: u8) -> bool {
491        if let Some(acl) = self.acl {
492            acl.content.contains(&kid)
493        } else {
494            // if no acl then allow it
495            true
496        }
497    }
498}
499static mut MOCK_EAD_SERVER_GLOBAL_STATE: MockEADServerState = MockEADServerState {
500    cred_v: EdhocMessageBuffer {
501        content: [0u8; MAX_MESSAGE_SIZE_LEN],
502        len: 0,
503    },
504    w: [0; P256_ELEM_LEN],
505    acl: None,
506};
507pub fn mock_ead_server_get_global_state() -> &'static MockEADServerState {
508    unsafe { &MOCK_EAD_SERVER_GLOBAL_STATE }
509}
510pub fn mock_ead_server_get_global_state_own() -> MockEADServerState {
511    unsafe { MOCK_EAD_SERVER_GLOBAL_STATE }
512}
513pub fn mock_ead_server_set_global_state(new_state: MockEADServerState) {
514    unsafe {
515        MOCK_EAD_SERVER_GLOBAL_STATE = new_state;
516    }
517}
518
519fn mock_send_voucher_request<Crypto: CryptoTrait>(
520    crypto: &mut Crypto,
521    _loc_w: &EdhocMessageBuffer,
522    voucher_request: &EdhocMessageBuffer,
523) -> Result<EdhocMessageBuffer, EDHOCError> {
524    let server_state = mock_ead_server_get_global_state();
525
526    handle_voucher_request(
527        crypto,
528        voucher_request,
529        &server_state.cred_v,
530        &server_state.w,
531    )
532}
533
534fn handle_voucher_request<Crypto: CryptoTrait>(
535    crypto: &mut Crypto,
536    vreq: &EdhocMessageBuffer,
537    cred_v: &EdhocMessageBuffer,
538    w: &BytesP256ElemLen, // TODO: have w be in the state of W
539) -> Result<EdhocMessageBuffer, EDHOCError> {
540    let (message_1, opaque_state) = parse_voucher_request(vreq)?;
541
542    let (_method, _suites_i, _suites_i_len, g_x, _c_i, ead_1) = parse_message_1(&message_1)?;
543
544    // compute hash
545    let mut message_1_buf: BytesMaxBuffer = [0x00; MAX_BUFFER_LEN];
546    message_1_buf[..message_1.len].copy_from_slice(message_1.as_slice());
547    let h_message_1 = crypto.sha256_digest(&message_1_buf, message_1.len);
548
549    let prk = compute_prk(crypto, w, &g_x);
550
551    let (_loc_w, enc_id) = parse_ead_1_value(&ead_1.unwrap().value.unwrap())?;
552    let id_u_encoded = decrypt_enc_id(crypto, &prk, &enc_id, EDHOC_SUPPORTED_SUITES[0])?;
553    let id_u = decode_id_u(id_u_encoded)?;
554
555    let server_state = mock_ead_server_get_global_state();
556    if server_state.acl.is_none() || server_state.authorized(id_u.content[3]) {
557        let voucher = prepare_voucher(crypto, &h_message_1, cred_v, &prk);
558        let voucher_response = encode_voucher_response(&message_1, &voucher, &opaque_state);
559        Ok(voucher_response)
560    } else {
561        Err(EDHOCError::EADError)
562    }
563}
564
565fn decode_id_u(id_u_bstr: EdhocMessageBuffer) -> Result<EdhocMessageBuffer, EDHOCError> {
566    // id_u is encoded as bstr
567    let mut decoder = CBORDecoder::new(id_u_bstr.as_slice());
568    let id_u: EdhocMessageBuffer = decoder.bytes()?.try_into().unwrap();
569    Ok(id_u)
570}
571
572fn decrypt_enc_id<Crypto: CryptoTrait>(
573    crypto: &mut Crypto,
574    prk: &BytesHashLen,
575    enc_id: &EdhocMessageBuffer,
576    ss: u8,
577) -> Result<EdhocMessageBuffer, EDHOCError> {
578    let (k_1, iv_1) = compute_k_1_iv_1(crypto, &prk);
579
580    // external_aad = (SS: int)
581    let enc_structure = encode_enc_structure(ss);
582
583    // ENC_ID = 'ciphertext' of COSE_Encrypt0
584    crypto.aes_ccm_decrypt_tag_8(&k_1, &iv_1, &enc_structure[..], &enc_id)
585}
586
587fn prepare_voucher<Crypto: CryptoTrait>(
588    crypto: &mut Crypto,
589    h_message_1: &BytesHashLen,
590    cred_v: &EdhocMessageBuffer,
591    prk: &BytesP256ElemLen,
592) -> BytesEncodedVoucher {
593    let voucher_input = encode_voucher_input(&h_message_1, &cred_v);
594    let voucher_mac = compute_voucher_mac(crypto, &prk, &voucher_input);
595    encode_voucher(&voucher_mac)
596}
597
598fn parse_voucher_request(
599    vreq: &EdhocMessageBuffer,
600) -> Result<(EdhocMessageBuffer, Option<EdhocMessageBuffer>), EDHOCError> {
601    let mut decoder = CBORDecoder::new(vreq.as_slice());
602    let array_size = decoder.array()?;
603    if array_size != 1 && array_size != 2 {
604        return Err(EDHOCError::EADError);
605    }
606
607    let message_1: EdhocMessageBuffer = decoder.bytes()?.try_into().unwrap();
608
609    if array_size == 2 {
610        let opaque_state: EdhocMessageBuffer = decoder.bytes()?.try_into().unwrap();
611        Ok((message_1, Some(opaque_state)))
612    } else {
613        Ok((message_1, None))
614    }
615}
616
617fn encode_voucher_input(
618    h_message_1: &BytesHashLen,
619    cred_v: &EdhocMessageBuffer,
620) -> EdhocMessageBuffer {
621    let mut voucher_input = EdhocMessageBuffer::new();
622
623    voucher_input.content[0] = CBOR_BYTE_STRING;
624    voucher_input.content[1] = SHA256_DIGEST_LEN as u8;
625    voucher_input.content[2..2 + SHA256_DIGEST_LEN]
626        .copy_from_slice(&h_message_1[..SHA256_DIGEST_LEN]);
627
628    voucher_input.content[2 + SHA256_DIGEST_LEN] = CBOR_BYTE_STRING;
629    voucher_input.content[3 + SHA256_DIGEST_LEN] = cred_v.len as u8;
630    voucher_input.content[4 + SHA256_DIGEST_LEN..4 + SHA256_DIGEST_LEN + cred_v.len]
631        .copy_from_slice(cred_v.as_slice());
632
633    voucher_input.len = 4 + SHA256_DIGEST_LEN + cred_v.len;
634
635    voucher_input
636}
637
638fn compute_voucher_mac<Crypto: CryptoTrait>(
639    crypto: &mut Crypto,
640    prk: &BytesHashLen,
641    voucher_input: &EdhocMessageBuffer,
642) -> BytesMac {
643    let mut voucher_mac: BytesMac = [0x00; MAC_LENGTH];
644
645    let mut context = [0x00; MAX_KDF_CONTEXT_LEN];
646    context[..voucher_input.len].copy_from_slice(voucher_input.as_slice());
647
648    let voucher_mac_buf = edhoc_kdf_expand(crypto, prk, 2, &context, voucher_input.len, MAC_LENGTH);
649    voucher_mac[..MAC_LENGTH].copy_from_slice(&voucher_mac_buf[..MAC_LENGTH]);
650
651    voucher_mac
652}
653
654fn encode_voucher(voucher_mac: &BytesMac) -> BytesEncodedVoucher {
655    let mut voucher: BytesEncodedVoucher = Default::default();
656    voucher[0] = CBOR_MAJOR_BYTE_STRING + MAC_LENGTH as u8;
657    voucher[1..1 + MAC_LENGTH].copy_from_slice(&voucher_mac[..MAC_LENGTH]);
658
659    voucher
660}
661
662fn encode_voucher_response(
663    message_1: &EdhocMessageBuffer,
664    voucher: &BytesEncodedVoucher,
665    opaque_state: &Option<EdhocMessageBuffer>,
666) -> EdhocMessageBuffer {
667    let mut output = EdhocMessageBuffer::new();
668
669    output.content[1] = CBOR_BYTE_STRING;
670    output.content[2] = message_1.len as u8;
671    output.content[3..3 + message_1.len].copy_from_slice(message_1.as_slice());
672
673    output.content[3 + message_1.len] = CBOR_MAJOR_BYTE_STRING + ENCODED_VOUCHER_LEN as u8;
674    output.content[4 + message_1.len..4 + message_1.len + ENCODED_VOUCHER_LEN]
675        .copy_from_slice(&voucher[..]);
676
677    if let Some(opaque_state) = opaque_state {
678        output.content[0] = CBOR_MAJOR_ARRAY | 3;
679
680        output.content[4 + message_1.len + ENCODED_VOUCHER_LEN] = CBOR_BYTE_STRING;
681        output.content[5 + message_1.len + ENCODED_VOUCHER_LEN] = opaque_state.len as u8;
682        output.content[6 + message_1.len + ENCODED_VOUCHER_LEN
683            ..6 + message_1.len + ENCODED_VOUCHER_LEN + opaque_state.len]
684            .copy_from_slice(opaque_state.as_slice());
685
686        output.len = 6 + message_1.len + ENCODED_VOUCHER_LEN + opaque_state.len;
687    } else {
688        output.content[0] = CBOR_MAJOR_ARRAY | 2;
689        output.len = 4 + message_1.len + ENCODED_VOUCHER_LEN;
690    }
691
692    output
693}
694
695#[cfg(test)]
696mod test_vectors {
697    use hexlit::hex;
698    use lakers_shared::*;
699
700    // inputs
701    // U
702    pub const ID_U_TV: &[u8] = &hex!("a104412b");
703    pub const ID_U_ENCODED_TV: &[u8] = &hex!("44a104412b");
704    pub const X_TV: BytesP256ElemLen =
705        hex!("368ec1f69aeb659ba37d5a8d45b21bdc0299dceaa8ef235f3ca42ce3530f9525");
706    pub const G_X_TV: &[u8] =
707        &hex!("8af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6");
708
709    // V
710    pub const CRED_V_TV: &[u8] = &hex!("a2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072");
711
712    // W
713    pub const W_TV: &[u8] =
714        &hex!("4E5E15AB35008C15B89E91F9F329164D4AACD53D9923672CE0019F9ACD98573F");
715    pub const G_W_TV: &[u8] =
716        &hex!("FFA4F102134029B3B156890B88C9D9619501196574174DCB68A07DB0588E4D41");
717    pub const LOC_W_TV: &[u8] = &hex!("636F61703A2F2F656E726F6C6C6D656E742E736572766572"); // coap://enrollment.server
718
719    // computed artifacts
720    // EAD_1
721    pub const SS_TV: u8 = 2;
722    pub const ENC_ID_TV: &[u8] = &hex!("da9784962883c96ed01ff122c3");
723    pub const PRK_TV: &[u8] =
724        &hex!("d40f1601b577dbe7827bb3a20e0d16f7231c3a25225c1ed733f9094050d59666");
725    pub const K_1_TV: &[u8] = &hex!("6f2a9112801a5011aa33576b5c7862ad");
726    pub const IV_1_TV: &[u8] = &hex!("d31bc0d128349f290e79f0bde3");
727    pub const EAD1_VALUE_TV: &[u8] = &hex!(
728        "58287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3"
729    );
730    pub const MESSAGE_1_WITH_EAD_TV: &[u8] = &hex!("0382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3");
731
732    // VREQ
733    pub const VOUCHER_REQUEST_TV: &[u8] = &hex!("8158520382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3");
734
735    // VRES
736    pub const VOUCHER_RESPONSE_TV: &[u8] = &hex!("8258520382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c34948c783671337f75bd5");
737    pub const H_MESSAGE_1_TV: &[u8] =
738        &hex!("a1004dfd2c64777980d9c84f100f93a9cac511ae38f56b2210530c945d186c24");
739    pub const VOUCHER_INPUT_TV: &[u8] = &hex!("5820a1004dfd2c64777980d9c84f100f93a9cac511ae38f56b2210530c945d186c24585fa2026b6578616d706c652e65647508a101a501020241322001215820bbc34960526ea4d32e940cad2a234148ddc21791a12afbcbac93622046dd44f02258204519e257236b2a0ce2023f0931f1f386ca7afda64fcde0108c224c51eabf6072");
740    pub const VOUCHER_MAC_TV: &[u8] = &hex!("c783671337f75bd5");
741    pub const VOUCHER_TV: &[u8] = &hex!("48c783671337f75bd5");
742
743    // EAD_2
744    pub const EAD2_VALUE_TV: &[u8] = &hex!("48c783671337f75bd5");
745
746    // ---- Traces for stateless operation (prefixed with SLO)
747    // VREQ
748    pub const SLO_OPAQUE_STATE_TV: &[u8] =
749        &hex!("827819666538303a3a623833343a643630623a373936663a38646530198bed");
750    pub const SLO_VOUCHER_REQUEST_TV: &[u8] = &hex!("8258520382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c3581f827819666538303a3a623833343a643630623a373936663a38646530198bed");
751
752    // VRES
753    pub const SLO_VOUCHER_RESPONSE_TV: &[u8] = &hex!("8358520382060258208af6f430ebe18d34184017a9a11bf511c8dff8f834730b96c1b7c8dbca2fc3b6370158287818636f61703a2f2f656e726f6c6c6d656e742e7365727665724dda9784962883c96ed01ff122c34948c783671337f75bd5581f827819666538303a3a623833343a643630623a373936663a38646530198bed");
754}
755
756#[cfg(test)]
757mod test_initiator {
758    use super::*;
759    use test_vectors::*;
760
761    use lakers_crypto::default_crypto;
762
763    #[test]
764    fn test_compute_keys() {
765        let k_1_tv: BytesCcmKeyLen = K_1_TV.try_into().unwrap();
766        let iv_1_tv: BytesCcmIvLen = IV_1_TV.try_into().unwrap();
767        let prk_tv: BytesHashLen = PRK_TV.try_into().unwrap();
768
769        let prk_xw = compute_prk(
770            &mut default_crypto(),
771            &X_TV.try_into().unwrap(),
772            &G_W_TV.try_into().unwrap(),
773        );
774        let prk_wx = compute_prk(
775            &mut default_crypto(),
776            &W_TV.try_into().unwrap(),
777            &G_X_TV.try_into().unwrap(),
778        );
779        assert_eq!(prk_xw, prk_tv);
780        assert_eq!(prk_xw, prk_wx);
781
782        let (k_1, iv_1) = compute_k_1_iv_1(&mut default_crypto(), &prk_xw);
783        assert_eq!(k_1, k_1_tv);
784        assert_eq!(iv_1, iv_1_tv);
785    }
786
787    #[test]
788    fn test_encrypt_enc_id() {
789        let enc_id_tv: EdhocMessageBuffer = ENC_ID_TV.try_into().unwrap();
790
791        let enc_id = encrypt_enc_id(
792            &mut default_crypto(),
793            &PRK_TV.try_into().unwrap(),
794            &ID_U_ENCODED_TV.try_into().unwrap(),
795            SS_TV,
796        );
797        assert_eq!(enc_id.content, enc_id_tv.content);
798    }
799
800    #[test]
801    fn test_decrypt_enc_id() {
802        let enc_id_tv: EdhocMessageBuffer = ENC_ID_TV.try_into().unwrap();
803        let mut prk_tv: BytesHashLen = Default::default();
804        prk_tv[..].copy_from_slice(PRK_TV);
805        let id_u_encoded_tv: EdhocMessageBuffer = ID_U_ENCODED_TV.try_into().unwrap();
806
807        let id_u_res = decrypt_enc_id(
808            &mut default_crypto(),
809            &prk_tv,
810            &ENC_ID_TV.try_into().unwrap(),
811            SS_TV,
812        );
813        assert!(id_u_res.is_ok());
814        assert_eq!(id_u_res.unwrap().content, id_u_encoded_tv.content);
815    }
816
817    #[test]
818    fn test_prepare_ead_1() {
819        let ead_1_value_tv: EdhocMessageBuffer = EAD1_VALUE_TV.try_into().unwrap();
820
821        ead_initiator_set_global_state(EADInitiatorState::new(
822            ID_U_TV.try_into().unwrap(),
823            G_W_TV.try_into().unwrap(),
824            LOC_W_TV.try_into().unwrap(),
825        ));
826
827        let ead_1 =
828            i_prepare_ead_1(&mut default_crypto(), &X_TV.try_into().unwrap(), SS_TV).unwrap();
829        assert_eq!(
830            ead_initiator_get_global_state().protocol_state,
831            EADInitiatorProtocolState::WaitEAD2
832        );
833        assert_eq!(ead_1.label, EAD_ZEROCONF_LABEL);
834        assert_eq!(ead_1.is_critical, true);
835        assert_eq!(ead_1.value.unwrap().content, ead_1_value_tv.content);
836    }
837
838    #[test]
839    fn test_verify_voucher() {
840        let voucher_tv = VOUCHER_TV.try_into().unwrap();
841        let h_message_1_tv = H_MESSAGE_1_TV.try_into().unwrap();
842        let cred_v_tv = CRED_V_TV.try_into().unwrap();
843        let prk_tv = PRK_TV.try_into().unwrap();
844        let voucher_mac_tv: BytesMac = VOUCHER_MAC_TV.try_into().unwrap();
845
846        let res = verify_voucher(
847            &mut default_crypto(),
848            &voucher_tv,
849            &h_message_1_tv,
850            &cred_v_tv,
851            &prk_tv,
852        );
853        assert!(res.is_ok());
854        assert_eq!(res.unwrap(), voucher_mac_tv);
855    }
856
857    #[test]
858    fn test_process_ead_2() {
859        let ead_2_value_tv: EdhocMessageBuffer = EAD2_VALUE_TV.try_into().unwrap();
860        let cred_v_tv: &[u8] = CRED_V_TV.try_into().unwrap();
861        let h_message_1_tv = H_MESSAGE_1_TV.try_into().unwrap();
862
863        let ead_2_tv = EADItem {
864            label: EAD_ZEROCONF_LABEL,
865            is_critical: true,
866            value: Some(ead_2_value_tv),
867        };
868
869        let mut state = EADInitiatorState::new(
870            ID_U_TV.try_into().unwrap(),
871            G_W_TV.try_into().unwrap(),
872            LOC_W_TV.try_into().unwrap(),
873        );
874        state.prk = PRK_TV.try_into().unwrap();
875        ead_initiator_set_global_state(state);
876
877        let res = i_process_ead_2(&mut default_crypto(), ead_2_tv, cred_v_tv, &h_message_1_tv);
878        assert!(res.is_ok());
879        assert_eq!(
880            ead_initiator_get_global_state().protocol_state,
881            EADInitiatorProtocolState::Completed
882        );
883    }
884}
885
886#[cfg(test)]
887mod test_responder {
888    use super::*;
889    use test_vectors::*;
890
891    use lakers_crypto::default_crypto;
892
893    #[test]
894    fn test_parse_ead_1_value() {
895        let ead_1_value_tv: EdhocMessageBuffer = EAD1_VALUE_TV.try_into().unwrap();
896        let loc_w_tv: EdhocMessageBuffer = LOC_W_TV.try_into().unwrap();
897        let enc_id_tv: EdhocMessageBuffer = ENC_ID_TV.try_into().unwrap();
898
899        let res = parse_ead_1_value(&ead_1_value_tv);
900        assert!(res.is_ok());
901        let (loc_w, enc_id) = res.unwrap();
902        assert_eq!(loc_w.content, loc_w_tv.content);
903        assert_eq!(enc_id.content, enc_id_tv.content);
904    }
905
906    #[test]
907    fn test_encode_voucher_request() {
908        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
909        let voucher_request_tv: EdhocMessageBuffer = VOUCHER_REQUEST_TV.try_into().unwrap();
910
911        let voucher_request = encode_voucher_request(&message_1_tv, &None);
912        assert_eq!(voucher_request.content, voucher_request_tv.content);
913    }
914
915    #[test]
916    fn test_process_ead_1() {
917        let ead_1_value_tv: EdhocMessageBuffer = EAD1_VALUE_TV.try_into().unwrap();
918        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
919
920        let ead_1 = EADItem {
921            label: EAD_ZEROCONF_LABEL,
922            is_critical: true,
923            value: Some(ead_1_value_tv),
924        };
925
926        ead_responder_set_global_state(EADResponderState::new());
927
928        mock_ead_server_set_global_state(MockEADServerState::new(
929            CRED_V_TV,
930            W_TV.try_into().unwrap(),
931            None,
932        ));
933
934        let res = r_process_ead_1(&mut default_crypto(), &ead_1, &message_1_tv);
935        assert!(res.is_ok());
936        assert_eq!(
937            ead_responder_get_global_state().protocol_state,
938            EADResponderProtocolState::ProcessedEAD1
939        );
940    }
941
942    #[test]
943    fn test_parse_voucher_response() {
944        let voucher_response_tv: EdhocMessageBuffer = VOUCHER_RESPONSE_TV.try_into().unwrap();
945        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
946        let voucher_tv: BytesEncodedVoucher = VOUCHER_TV.try_into().unwrap();
947
948        let res = parse_voucher_response(&voucher_response_tv);
949        assert!(res.is_ok());
950        let (message_1, voucher, opaque_state) = res.unwrap();
951        assert_eq!(message_1.content, message_1_tv.content);
952        assert_eq!(voucher, voucher_tv);
953        assert!(opaque_state.is_none());
954    }
955
956    #[test]
957    fn test_r_prepare_ead_2() {
958        let voucher_response_tv: EdhocMessageBuffer = VOUCHER_RESPONSE_TV.try_into().unwrap();
959        let ead_2_value_tv: EdhocMessageBuffer = EAD2_VALUE_TV.try_into().unwrap();
960
961        ead_responder_set_global_state(EADResponderState {
962            voucher_response: Some(voucher_response_tv),
963            ..EADResponderState::new()
964        });
965
966        let ead_2 = r_prepare_ead_2().unwrap();
967        assert_eq!(
968            ead_responder_get_global_state().protocol_state,
969            EADResponderProtocolState::Completed
970        );
971        assert_eq!(ead_2.label, EAD_ZEROCONF_LABEL);
972        assert_eq!(ead_2.is_critical, true);
973        assert_eq!(ead_2.value.unwrap().content, ead_2_value_tv.content);
974    }
975}
976
977#[cfg(test)]
978mod test_enrollment_server {
979    use super::*;
980    use test_vectors::*;
981
982    use lakers_crypto::default_crypto;
983
984    #[test]
985    fn test_encode_voucher_input() {
986        let h_message_1_tv: BytesHashLen = H_MESSAGE_1_TV.try_into().unwrap();
987        let cred_v_tv: EdhocMessageBuffer = CRED_V_TV.try_into().unwrap();
988        let voucher_input_tv: EdhocMessageBuffer = VOUCHER_INPUT_TV.try_into().unwrap();
989
990        let voucher_input = encode_voucher_input(&h_message_1_tv, &cred_v_tv);
991        assert_eq!(voucher_input.content, voucher_input_tv.content);
992    }
993
994    #[test]
995    fn test_compute_voucher_mac() {
996        let prk_tv: BytesHashLen = PRK_TV.try_into().unwrap();
997        let voucher_input_tv: EdhocMessageBuffer = VOUCHER_INPUT_TV.try_into().unwrap();
998        let voucher_mac_tv: BytesMac = VOUCHER_MAC_TV.try_into().unwrap();
999
1000        let voucher_mac = compute_voucher_mac(&mut default_crypto(), &prk_tv, &voucher_input_tv);
1001        assert_eq!(voucher_mac, voucher_mac_tv);
1002    }
1003
1004    #[test]
1005    fn test_prepare_voucher() {
1006        let h_message_1: BytesHashLen = H_MESSAGE_1_TV.try_into().unwrap();
1007        let cred_v: EdhocMessageBuffer = CRED_V_TV.try_into().unwrap();
1008        let prk: BytesHashLen = PRK_TV.try_into().unwrap();
1009        let voucher_tv: BytesEncodedVoucher = VOUCHER_TV.try_into().unwrap();
1010
1011        let voucher = prepare_voucher(&mut default_crypto(), &h_message_1, &cred_v, &prk);
1012        assert_eq!(voucher, voucher_tv);
1013    }
1014
1015    #[test]
1016    fn test_encode_voucher_response() {
1017        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
1018        let voucher_tv: BytesEncodedVoucher = VOUCHER_TV.try_into().unwrap();
1019        let opaque_state_tv: EdhocMessageBuffer = SLO_OPAQUE_STATE_TV.try_into().unwrap();
1020        let voucher_response_tv: EdhocMessageBuffer = SLO_VOUCHER_RESPONSE_TV.try_into().unwrap();
1021
1022        let voucher_response =
1023            encode_voucher_response(&message_1_tv, &voucher_tv, &Some(opaque_state_tv));
1024        assert_eq!(voucher_response.content, voucher_response_tv.content);
1025    }
1026
1027    #[test]
1028    fn test_parse_voucher_request() {
1029        let voucher_request_tv: EdhocMessageBuffer = VOUCHER_REQUEST_TV.try_into().unwrap();
1030        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
1031
1032        let voucher_request = parse_voucher_request(&voucher_request_tv);
1033        assert!(voucher_request.is_ok());
1034        let (message_1, opaque_state) = voucher_request.unwrap();
1035        assert_eq!(message_1.content, message_1_tv.content);
1036        assert!(opaque_state.is_none());
1037    }
1038
1039    #[test]
1040    fn test_handle_voucher_request() {
1041        let voucher_request_tv: EdhocMessageBuffer = VOUCHER_REQUEST_TV.try_into().unwrap();
1042        let cred_v_tv: EdhocMessageBuffer = CRED_V_TV.try_into().unwrap();
1043        let w_tv: BytesP256ElemLen = W_TV.try_into().unwrap();
1044        let g_x_tv: BytesP256ElemLen = G_X_TV.try_into().unwrap();
1045        let voucher_response_tv: EdhocMessageBuffer = VOUCHER_RESPONSE_TV.try_into().unwrap();
1046
1047        mock_ead_server_set_global_state(MockEADServerState::new(
1048            CRED_V_TV,
1049            W_TV.try_into().unwrap(),
1050            None,
1051        ));
1052
1053        let res = handle_voucher_request(
1054            &mut default_crypto(),
1055            &voucher_request_tv,
1056            &cred_v_tv,
1057            &w_tv,
1058        );
1059        assert!(res.is_ok());
1060        let voucher_response = res.unwrap();
1061        assert_eq!(voucher_response.content, voucher_response_tv.content);
1062    }
1063}
1064
1065#[cfg(test)]
1066mod test_stateless_operation {
1067    use super::*;
1068    use test_vectors::*;
1069
1070    use lakers_crypto::default_crypto;
1071
1072    #[test]
1073    fn slo_test_encode_voucher_request() {
1074        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
1075        let opaque_state_tv: EdhocMessageBuffer = SLO_OPAQUE_STATE_TV.try_into().unwrap();
1076        let voucher_request_tv: EdhocMessageBuffer = SLO_VOUCHER_REQUEST_TV.try_into().unwrap();
1077
1078        let voucher_request = encode_voucher_request(&message_1_tv, &Some(opaque_state_tv));
1079        assert_eq!(voucher_request.content, voucher_request_tv.content);
1080    }
1081
1082    #[test]
1083    fn slo_test_parse_voucher_response() {
1084        let voucher_response_tv: EdhocMessageBuffer = SLO_VOUCHER_RESPONSE_TV.try_into().unwrap();
1085        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
1086        let voucher_tv: BytesEncodedVoucher = VOUCHER_TV.try_into().unwrap();
1087        let opaque_state_tv: EdhocMessageBuffer = SLO_OPAQUE_STATE_TV.try_into().unwrap();
1088
1089        let res = parse_voucher_response(&voucher_response_tv);
1090        assert!(res.is_ok());
1091        let (message_1, voucher, opaque_state) = res.unwrap();
1092        assert_eq!(message_1.content, message_1_tv.content);
1093        assert_eq!(voucher, voucher_tv);
1094        assert_eq!(opaque_state.unwrap().content, opaque_state_tv.content);
1095    }
1096
1097    #[test]
1098    fn slo_test_parse_voucher_request() {
1099        let voucher_request_tv: EdhocMessageBuffer = SLO_VOUCHER_REQUEST_TV.try_into().unwrap();
1100        let message_1_tv: EdhocMessageBuffer = MESSAGE_1_WITH_EAD_TV.try_into().unwrap();
1101        let opaque_state_tv: EdhocMessageBuffer = SLO_OPAQUE_STATE_TV.try_into().unwrap();
1102
1103        let voucher_request = parse_voucher_request(&voucher_request_tv);
1104        assert!(voucher_request.is_ok());
1105        let (message_1, opaque_state) = voucher_request.unwrap();
1106        assert_eq!(message_1.content, message_1_tv.content);
1107        assert_eq!(opaque_state.unwrap().content, opaque_state_tv.content);
1108    }
1109
1110    #[test]
1111    fn slo_test_handle_voucher_request() {
1112        let voucher_request_tv: EdhocMessageBuffer = SLO_VOUCHER_REQUEST_TV.try_into().unwrap();
1113        let cred_v_tv: EdhocMessageBuffer = CRED_V_TV.try_into().unwrap();
1114        let w_tv: BytesP256ElemLen = W_TV.try_into().unwrap();
1115        let g_x_tv: BytesP256ElemLen = G_X_TV.try_into().unwrap();
1116        let voucher_response_tv: EdhocMessageBuffer = SLO_VOUCHER_RESPONSE_TV.try_into().unwrap();
1117
1118        mock_ead_server_set_global_state(MockEADServerState::new(
1119            CRED_V_TV,
1120            W_TV.try_into().unwrap(),
1121            None,
1122        ));
1123
1124        let res = handle_voucher_request(
1125            &mut default_crypto(),
1126            &voucher_request_tv,
1127            &cred_v_tv,
1128            &w_tv,
1129        );
1130        assert!(res.is_ok());
1131        let voucher_response = res.unwrap();
1132        assert_eq!(voucher_response.content, voucher_response_tv.content);
1133    }
1134}