Repository: openwsn-berkeley/lakers (language: python)

Issue:
Allow applications to pick their C_x
Applications may need to pick their own C_x (as opposed to the EDHOC library picking one at random, as it was before) for three reasons:

* The library doesn't manage the set of contexts, so it can't know which C_x are already in use and which are not.
* The C_x may be used for other purposes (eg. OSCORE contexts), and might be namespaced there, which also only the application may know. (Silly example, but the application may use the OSCORE context identifiers that correspond to negative C_x for use with ACE-OSCORE profile, and only those with positive C_x for EDHOC).
* When processing of message 1 and creation of message 2 are split over time (as is necessary when implementing an EDHOC server on the coap-handler interface), it is practical to already decide on a C_R at processing time, store the responder, and dig it up when creating the response -- but that requires the C_R to be decided already at storing time.

This will likely need follow-ups for similarly delegating the choice of {ID_,}CRED_peer to the callers (resolving #99 issues from), but those are separate enough that I'd do them in a separate PR.

These are API breaking changes. They're split in two because I did C_R first, and kept them split to ease review especially on the CI side; when ACK'ed, I'd like to squash them.
Allow applications to pick their C_x
Applications may need to pick their own C_x (as opposed to the EDHOC library picking one at random, as it was before) for three reasons:

* The library doesn't manage the set of contexts, so it can't know which C_x are already in use and which are not.
* The C_x may be used for other purposes (eg. OSCORE contexts), and might be namespaced there, which also only the application may know. (Silly example, but the application may use the OSCORE context identifiers that correspond to negative C_x for use with ACE-OSCORE profile, and only those with positive C_x for EDHOC).
* When processing of message 1 and creation of message 2 are split over time (as is necessary when implementing an EDHOC server on the coap-handler interface), it is practical to already decide on a C_R at processing time, store the responder, and dig it up when creating the response -- but that requires the C_R to be decided already at storing time.

This will likely need follow-ups for similarly delegating the choice of {ID_,}CRED_peer to the callers (resolving #99 issues from), but those are separate enough that I'd do them in a separate PR.

These are API breaking changes. They're split in two because I did C_R first, and kept them split to ease review especially on the CI side; when ACK'ed, I'd like to squash them.


Discussion / hints:
Thanks @chrysn, it looks good. Left a minor comment. As for merging, maybe we could merge it directly into the [remove-hacspec](https://github.com/openwsn-berkeley/edhoc-rs/tree/remove-hacspec) branch I just pushed upstream.
This is now only one commit atop #106. Rebasing was a bit of a hassle, but worked in a mixed cherry-pick and manual application approach. Looks like remove-hacspec is in a stable enough shape to rebase onto, at any rate, and things are *much* easier on that!

From my PoV, this is done once CI says Go.
Great! Merged into `remove-hacspec`.
Thanks @chrysn, it looks good. Left a minor comment. As for merging, maybe we could merge it directly into the [remove-hacspec](https://github.com/openwsn-berkeley/edhoc-rs/tree/remove-hacspec) branch I just pushed upstream.
This is now only one commit atop #106. Rebasing was a bit of a hassle, but worked in a mixed cherry-pick and manual application approach. Looks like remove-hacspec is in a stable enough shape to rebase onto, at any rate, and things are *much* easier on that!

From my PoV, this is done once CI says Go.
Great! Merged into `remove-hacspec`.

Fix patch:
diff --git a/examples/coap/Cargo.toml b/examples/coap/Cargo.toml
index 83a0941f..a7542246 100644
--- a/examples/coap/Cargo.toml
+++ b/examples/coap/Cargo.toml
@@ -8,3 +8,11 @@ edhoc-rs = { path = "../../lib", features = [ "crypto-hacspec" ] }
 hexlit = "0.5.3"
 coap = { version = "0.13" }
 coap-lite = { version = "0.11.3" }
+
+std-embedded-nal = "^0.1.2"
+embedded-nal-minimal-coapserver = "0.3.1"
+embedded-nal = "0.6"
+coap-message = "0.2.3"
+coap-handler = "0.1.5"
+coap-handler-implementations = "0.4.2"
+coap-numbers = "0.2.3"
diff --git a/examples/coap/src/bin/coapserver-coaphandler.rs b/examples/coap/src/bin/coapserver-coaphandler.rs
new file mode 100644
index 00000000..232ed551
--- /dev/null
+++ b/examples/coap/src/bin/coapserver-coaphandler.rs
@@ -0,0 +1,159 @@
+use edhoc_rs::*;
+use hexlit::hex;
+use std::net::UdpSocket;
+
+use embedded_nal::UdpFullStack;
+
+const ID_CRED_I: &[u8] = &hex!("a104412b");
+const ID_CRED_R: &[u8] = &hex!("a104410a");
+const CRED_I: &[u8] = &hex!("A2027734322D35302D33312D46462D45462D33372D33322D333908A101A5010202412B2001215820AC75E9ECE3E50BFC8ED60399889522405C47BF16DF96660A41298CB4307F7EB62258206E5DE611388A4B8A8211334AC7D37ECB52A387D257E6DB3C2A93DF21FF3AFFC8");
+const G_I: &[u8] = &hex!("ac75e9ece3e50bfc8ed60399889522405c47bf16df96660a41298cb4307f7eb6"); // not used
+const _G_I_Y_COORD: &[u8] =
+    &hex!("6e5de611388a4b8a8211334ac7d37ecb52a387d257e6db3c2a93df21ff3affc8"); // not used
+const CRED_R: &[u8] = &hex!("A2026008A101A5010202410A2001215820BBC34960526EA4D32E940CAD2A234148DDC21791A12AFBCBAC93622046DD44F02258204519E257236B2A0CE2023F0931F1F386CA7AFDA64FCDE0108C224C51EABF6072");
+const R: &[u8] = &hex!("72cc4761dbd4c78f758931aa589d348d1ef874a7e303ede2f140dcf3e6aa4aac");
+
+#[derive(Default, Debug)]
+struct EdhocHandler {
+    connections: Vec<(u8, EdhocResponder<'static>)>,
+}
+
+impl EdhocHandler {
+    fn connection_by_c_r(&mut self, c_r: u8) -> Option<&mut EdhocResponder<'static>> {
+        self.connections
+            .iter_mut()
+            .filter(|(current_c_r, _)| current_c_r == &c_r)
+            .map(|(_, responder)| responder)
+            .next()
+    }
+
+    fn new_c_r(&self) -> u8 {
+        // FIXME: We'll need to do better, but a) that'll be more practical when we can do more
+        // than u8, and b) that'll best be coordinated with a storage that not only stores EDHOC
+        // contexts but also OSCORE ones.
+        let result = self.connections.len();
+        if result >= 24 {
+            panic!("Contexts exceeded");
+        }
+        result as _
+    }
+}
+
+enum EdhocResponse {
+    OkSend2 { c_r: u8 },
+    Message3Processed,
+}
+
+impl coap_handler::Handler for EdhocHandler {
+    type RequestData = EdhocResponse;
+    fn extract_request_data(
+        &mut self,
+        request: &impl coap_message::ReadableMessage,
+    ) -> Self::RequestData {
+        let starts_with_true = request.payload().get(0) == Some(&0xf5);
+
+        if starts_with_true {
+            let state = EdhocState::default();
+            let mut responder = EdhocResponder::new(state, &R, &CRED_R, Some(&CRED_I));
+
+            let error = responder
+                .process_message_1(&request.payload()[1..].try_into().expect("wrong length"));
+
+            if error.is_ok() {
+                let c_r = self.new_c_r();
+                // save edhoc connection
+                self.connections.push((c_r, responder));
+                EdhocResponse::OkSend2 { c_r }
+            } else {
+                panic!("How to respond to non-OK?")
+            }
+        } else {
+            // potentially message 3
+            let c_r_rcvd = request.payload()[0];
+            let mut responder = self.connection_by_c_r(c_r_rcvd).expect("No such C_R found");
+
+            println!("Found state with connection identifier {:?}", c_r_rcvd);
+            let prk_out = responder
+                .process_message_3(&request.payload()[1..].try_into().expect("wrong length"));
+
+            if prk_out.is_err() {
+                println!("EDHOC processing error: {:?}", prk_out);
+                // FIXME remove state from edhoc_connections
+                panic!("Handler can't just not respond");
+            }
+
+            println!("EDHOC exchange successfully completed");
+            println!("PRK_out: {:02x?}", prk_out);
+
+            let mut _oscore_secret = responder.edhoc_exporter(0u8, &[], 16).unwrap(); // label is 0
+            println!("OSCORE secret: {:02x?}", _oscore_secret);
+            let mut _oscore_salt = responder.edhoc_exporter(1u8, &[], 8).unwrap(); // label is 1
+            println!("OSCORE salt: {:02x?}", _oscore_salt);
+
+            // context of key update is a test vector from draft-ietf-lake-traces
+            let prk_out_new = responder.edhoc_key_update(&[
+                0xa0, 0x11, 0x58, 0xfd, 0xb8, 0x20, 0x89, 0x0c, 0xd6, 0xbe, 0x16, 0x96, 0x02, 0xb8,
+                0xbc, 0xea,
+            ]);
+            println!("PRK_out after key update: {:02x?}?", prk_out_new);
+
+            _oscore_secret = responder.edhoc_exporter(0u8, &[], 16).unwrap(); // label is 0
+            println!("OSCORE secret after key update: {:02x?}", _oscore_secret);
+            _oscore_salt = responder.edhoc_exporter(1u8, &[], 8).unwrap(); // label is 1
+            println!("OSCORE salt after key update: {:02x?}", _oscore_salt);
+
+            EdhocResponse::Message3Processed
+        }
+    }
+    fn estimate_length(&mut self, _: &Self::RequestData) -> usize {
+        200
+    }
+    fn build_response(
+        &mut self,
+        response: &mut impl coap_message::MutableWritableMessage,
+        req: Self::RequestData,
+    ) {
+        response.set_code(coap_numbers::code::CHANGED.try_into().ok().unwrap());
+        match req {
+            EdhocResponse::OkSend2 { c_r } => {
+                let responder = self.connection_by_c_r(c_r).unwrap();
+                let message_2 = responder.prepare_message_2(c_r).unwrap();
+                response.set_payload(&message_2.content[..message_2.len]);
+            }
+            EdhocResponse::Message3Processed => (), // "send empty ack back"?
+        };
+    }
+}
+
+fn build_handler() -> impl coap_handler::Handler {
+    use coap_handler_implementations::{HandlerBuilder, ReportingHandlerBuilder};
+
+    let mut edhoc: EdhocHandler = Default::default();
+
+    coap_handler_implementations::new_dispatcher()
+        .at_with_attributes(&[".well-known", "edhoc"], &[], edhoc)
+        .with_wkc()
+}
+
+fn main_on_stack<S: UdpFullStack>(stack: &mut S) {
+    let mut sock = stack.socket().expect("Can't create a socket");
+
+    let mut handler = build_handler();
+
+    stack.bind(&mut sock, 5683).expect("Can't bind to port");
+
+    loop {
+        match embedded_nal_minimal_coapserver::poll(stack, &mut sock, &mut handler) {
+            Err(embedded_nal::nb::Error::WouldBlock) => {
+                // See <https://github.com/rust-embedded-community/embedded-nal/issues/47>
+                std::thread::sleep(std::time::Duration::from_millis(50));
+            }
+            e => e.expect("UDP error during send/receive"),
+        }
+    }
+}
+
+fn main() {
+    let mut stack = std_embedded_nal::Stack::default();
+    main_on_stack(&mut stack);
+}


Test patch:
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index d323dff0..f5645312 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -185,6 +185,9 @@ jobs:
     - name: Build client
       run: cargo build --bin coapclient
 
+    - name: Build coap-handler based server
+      run: cargo build --bin coapserver-coaphandler
+
 
   release:
     runs-on: ubuntu-latest