verdigris 0.2.1

Browser application to explore, learn and debug CoAP
//! A GUI component that connects a CoAP WebSocket and allows placing messages on it

use yew::prelude::*;
use yew::agent::Bridge;

use crate::coapws;
use crate::binentry;
use crate::message_editor;

use coap_message::heapmessage::HeapMessage;
use crate::coapwsmessage;

use coap_message::{MinimalWritableMessage, ReadableMessage, MessageOption};

use coapwsmessage::{CoAPWSMessageR, CoAPWSMessageW};

pub struct CoAPWSMessageExchanger {
    connection: Box<dyn Bridge<coapws::Connection>>,
    events: Vec<Event>,
    link: ComponentLink<Self>,
    out_next_token: Vec<u8>,
    out_message: HeapMessage,
    out_message_editorstate: message_editor::EditorState,
    server_uri: String,
}

enum Event {
    Text(String),
    Received(CoAPWSMessageR<Box<[u8]>>),
}

fn format_message<M>(
    f: &mut std::fmt::Formatter,
    m: &M)
-> Result<(), std::fmt::Error>
where
    M: ReadableMessage
{
    coap_numbers::code::format_dotted(m.code().into(), f)?;
    if let Some(s) =coap_numbers::code::to_name(m.code().into()) {
        write!(f, " ({})", s)?;
    }
    let option_to_name = |optnum: u16| {
        match coap_numbers::code::classify(m.code().into()) {
            coap_numbers::code::Range::Signaling => coap_numbers::signaling_option::to_name(m.code().into(), optnum.into()),
            _ => coap_numbers::option::to_name(optnum.into()),
        }
    };
    for o in m.options() {
        write!(f, "; {} ({}) {:?}", o.number(), option_to_name(o.number()).unwrap_or("unknown"), o.value())?;
    }
    match std::str::from_utf8(m.payload()) {
        Ok("") => {}
        Ok(x) => {
            write!(f, "; Payload \"{}\"", x)?;
        }
        Err(_) => {
            write!(f, "; Payload {:?}", m.payload())?;
        }
    }
    Ok(())
}

impl std::fmt::Display for Event {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        match self {
            Event::Text(s) => f.write_str(&s),
            Event::Received(m) => {
                write!(f, "Received on {}: ",
                    match m.token() {
                        &[] => "empty token".to_string(),
                        t => hex::encode(t),
                    }
                )?;
                format_message(f, m)?;
                Ok(())
            }
        }
    }
}


fn build_getwkc() -> HeapMessage {
    use coap_numbers::option::*;
    use coap_numbers::code::*;

    let mut msg = HeapMessage::new();

    msg.set_code(GET);
    msg.add_option(URI_PATH, b".well-known");
    msg.add_option(URI_PATH, b"core");

    msg
}

fn build_observetime() -> HeapMessage {
    use coap_numbers::option::*;
    use coap_numbers::code::*;

    let mut msg = HeapMessage::new();

    msg.set_code(GET);
    msg.add_option(OBSERVE, b"");
    msg.add_option(URI_PATH, b"time");

    msg
}

fn build_proxyget() -> HeapMessage {
    use coap_numbers::option::*;
    use coap_numbers::code::*;

    let mut msg = HeapMessage::new();

    msg.set_code(GET);
    msg.add_option(URI_HOST, b"coap.me");
    msg.add_option(URI_PATH, b".well-known");
    msg.add_option(URI_PATH, b"core");
    msg.add_option(PROXY_SCHEME, b"coap");
    // Request a large block -- the default one (3) ends just at a Unicode boundary and thus makes
    // an ugly dump rather than nice text
    msg.add_option(BLOCK2, &[5]);

    msg
}

static DEFAULT_SERVER: &'static str = "ws://localhost:8683/.well-known/coap";

impl Component for CoAPWSMessageExchanger {
    type Message = ExchangerMsg;
    type Properties = ();

    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
        let mut events = Vec::new();
        events.push(Event::Text("Startup...".to_string()));

        let out_next_token = vec![0, 0, 0, 1];

        let mut connection = coapws::Connection::bridge(link.callback(|o| ExchangerMsg::Socket(o)));
        connection.send(coapws::Input::Initialize { uri: DEFAULT_SERVER.to_string() });

        Self { connection, events, out_message: build_getwkc(), out_next_token, link, out_message_editorstate: Default::default(), server_uri: DEFAULT_SERVER.to_string() }
    }

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            ExchangerMsg::Socket(coapws::Output::Connected) => {
                self.events.push(Event::Text("Ready to send requests".to_string()));
            }
            ExchangerMsg::Socket(coapws::Output::Message(m)) | ExchangerMsg::Socket(coapws::Output::SignalingInfo(m)) => {
                self.events.push(Event::Received(m));
            }
            ExchangerMsg::Socket(m) => {
                self.events.push(Event::Text(format!("Non-message event received: {:?}", m)));
            }
            ExchangerMsg::Edit(e) => {
                message_editor::apply(e, &mut self.out_message, &mut self.out_message_editorstate);
            }
            ExchangerMsg::ChangeToken(v) => {
                self.out_next_token = v;
            }
            ExchangerMsg::Submit => {
                let mut serialized = CoAPWSMessageW::new(&self.out_next_token);
                serialized.set_from_message(&self.out_message);

                self.connection.send(coapws::Input::Message(serialized));

                let new_token_tail = self.out_next_token
                    .pop()
                    .unwrap_or(0)
                    .wrapping_add(1);
                self.out_next_token.push(new_token_tail);
            }
            ExchangerMsg::ChangeToPreset(f) => {
                self.out_message = f();
            }
            ExchangerMsg::SetServer(u) => {
                self.server_uri = u;
            }
            ExchangerMsg::Reconnect => {
                let mut connection = coapws::Connection::bridge(self.link.callback(|o| ExchangerMsg::Socket(o)));
                connection.send(coapws::Input::Initialize { uri: self.server_uri.clone() });
                self.connection = connection
            }
        }
        true
    }

    fn change(&mut self, _: Self::Properties) -> ShouldRender {
        true
    }

    fn view(&self) -> Html {
        html! { <>
            <h1>{"CoAP over WebSockets: Message test tool"}</h1>
            <section>
                <h2> { "Server" } </h2>
                <p><label> { "Server URI:" } <input
                        type="text"
                        onchange=self.link.callback(move |v| ExchangerMsg::SetServer(match v {
                                yew::events::ChangeData::Value(u) => u,
                                _ => unreachable!("Text input has value"),
                            }))
                        value=self.server_uri
                    /></label></p>
                <p><button
                    onclick=self.link.callback(|_| ExchangerMsg::Reconnect)
                    > { "(Re)connect" } </button></p>
            </section>
            <section>
                <h2> {"Send messages"} </h2>
                <p> { "Presets:" }
                    <button onclick=self.link.callback(|_| ExchangerMsg::ChangeToPreset(build_getwkc))> {"Get /.well-known/core"} </button>
                    <button onclick=self.link.callback(|_| ExchangerMsg::ChangeToPreset(build_observetime))> {"Observe /time"} </button>
                    <button onclick=self.link.callback(|_| ExchangerMsg::ChangeToPreset(build_proxyget))> {"Proxy for coap.me"} </button>
                </p>
                <p><label> {"Token:"}
                    <binentry::BinaryEntry
                        onchange=self.link.callback(|v| ExchangerMsg::ChangeToken(v))
                        value=self.out_next_token.clone()
                    /></label>
                </p>
                { message_editor::render(
                        &self.out_message,
                        &self.out_message_editorstate,
                        &self.link,
                        |x| ExchangerMsg::Edit(x),
                        )}
                <button onclick=self.link.callback(|_| ExchangerMsg::Submit)> { "submit" } </button>
            </section>
            
            <section>
                <h2> { "Messages received" } </h2>
                <ul>
                    { for self.events.iter().map(|t| html!(<li>{t}</li>)) }
                </ul>
            </section>
        </>
        }
    }
}

#[derive(Debug)]
pub enum ExchangerMsg {
    Socket(coapws::Output),

    Edit(message_editor::EditMsg),
    ChangeToken(Vec<u8>),
    Submit,
    ChangeToPreset(fn() -> HeapMessage),

    SetServer(String),
    Reconnect,
}