rustmo-server 0.1.2

Talk to your code with Rustmo via Alexa
Documentation
use std::borrow::BorrowMut;
use std::io::Read;

use hyper::server::{Fresh, Handler, Request, Response};
use regex::Regex;
use serde_xml_rs::from_reader;

use crate::RustmoDevice;
use crate::virtual_device::{VirtualDevice, VirtualDeviceError, VirtualDeviceState};

#[derive(Debug, Deserialize)]
pub(crate) struct BinaryState {
    #[serde(rename = "BinaryState")]
    pub(crate) binary_state: u8,
}

#[derive(Debug, Deserialize)]
pub(crate) struct UpnpBody {
    #[serde(rename = "GetBinaryState")]
    pub(crate) get_binary_state: Option<BinaryState>,

    #[serde(rename = "SetBinaryState")]
    pub(crate) set_binary_state: Option<BinaryState>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct UpnpEnvelope {
    #[serde(rename = "Body")]
    pub(crate) body: UpnpBody,
}

pub(crate) struct DeviceHttpServerHandler {
    device: RustmoDevice,
}

unsafe impl Sync for DeviceHttpServerHandler {}

unsafe impl Send for DeviceHttpServerHandler {}

impl Handler for DeviceHttpServerHandler {
    fn handle<'r, 'k>(&'r self, mut request: Request<'r, 'k>, mut response: Response<'r, Fresh>) {
        println!(
            "REQUEST: http://{}{} from {}",
            self.device.ip_address.to_string(),
            request.uri.to_string(),
            request.remote_addr.to_string()
        );
        let body = match request.uri.to_string().as_str() {
            "/setup.xml" => Some(self.handle_setup()),
            "/eventservice.xml" => Some(self.handle_eventservice()),
            "/metainfoservice.xml" => Some(self.handle_metainfoservice()),
            "/upnp/control/basicevent1" => Some(self.handle_basicevent(
                request.borrow_mut(),
                &mut self.device.virtual_device.lock().unwrap(),
            )),
            _ => {
                println!("Unrecognized request: {:?}", request.uri);
                *response.status_mut() = hyper::status::StatusCode::NotFound;
                None
            }
        };

        if let Some(data) = body {
            *response.status_mut() = hyper::status::StatusCode::Ok;
            response
                .headers_mut()
                .append_raw("CONTENT-TYPE", "text/xml".to_string().into_bytes());
            response.send(data.as_slice()).unwrap();
        }
    }
}

impl DeviceHttpServerHandler {
    pub(crate) fn new(device: RustmoDevice) -> Self {
        DeviceHttpServerHandler { device }
    }

    fn handle_basicevent<'r, 'b>(
        &self,
        request: &mut Request<'r, 'b>,
        virtual_device: &mut Box<dyn VirtualDevice>,
    ) -> Vec<u8> {
        let re = Regex::new("^\".*[#](.*)\"$").unwrap();
        let action =
            String::from_utf8(request.headers.get_raw("SOAPACTION").unwrap()[0].clone()).unwrap();
        let action = re
            .captures(action.as_str())
            .unwrap()
            .get(1)
            .unwrap()
            .as_str();

        let mut content = String::new();
        request.read_to_string(&mut content).unwrap();

        let envelope: UpnpEnvelope = from_reader(content.as_bytes()).unwrap();
        let get_or_set;
        let on_off = match action {
            "GetBinaryState" => {
                println!(
                    "GET_BINARY_STATE: {} by {}",
                    self.device.name,
                    request.remote_addr.ip().to_string()
                );

                get_or_set = "Get";
                virtual_device.check_is_on()
            }
            "SetBinaryState" => {
                get_or_set = "Set";
                match envelope.body.set_binary_state {
                    Some(state) => {
                        if state.binary_state == 1 {
                            println!(
                                "TURN_ON: {} by {}",
                                self.device.name,
                                request.remote_addr.ip().to_string()
                            );
                            virtual_device.turn_on()
                        } else {
                            println!(
                                "TURN_OFF: {} by {}",
                                self.device.name,
                                request.remote_addr.ip().to_string()
                            );
                            virtual_device.turn_off()
                        }
                    }
                    None => Err(VirtualDeviceError::new(
                        "No BinaryState data for SetBinaryState",
                    )),
                }
            }
            capture => {
                println!(
                    "ERROR:  Unknown capture value: /{}/ from /{}/",
                    capture, action
                );
                return vec![];
            }
        };

        match on_off {
            Ok(state) => DeviceHttpServerHandler::make_basicevent_response(state, get_or_set),
            Err(e) => {
                println!("ERROR:  Problem with {}: {}", self.device.name, e.0);
                return vec![];
            }
        }
    }

    fn make_basicevent_response(state: VirtualDeviceState, get_or_set: &str) -> Vec<u8> {
        let soap = format!(
            "<s:Envelope xmlns:s='http://schemas.xmlsoap.org/soap/envelope/'
                        s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
                <s:Body>
                    <u:{action}BinaryStateResponse xmlns:u='urn:Belkin:service:basicevent:1'>
                        <BinaryState>{state}</BinaryState>
                    </u:{action}BinaryStateResponse>
                </s:Body>
            </s:Envelope>",
            action = get_or_set,
            state = match state {
                VirtualDeviceState::On => 1,
                VirtualDeviceState::Off => 0,
            }
        );

        soap.as_bytes().to_vec()
    }

    fn handle_setup(&self) -> Vec<u8> {
        println!("SETUP: {}", self.device.name);
        format!(
            "<root>
                <device>
                    <deviceType>urn:Belkin:device:controllee:1</deviceType>
                    <friendlyName>{device_name}</friendlyName>
                    <manufacturer>Belkin International Inc.</manufacturer>
                    <modelName>Socket</modelName>
                    <modelNumber>3.1415</modelNumber>
                    <modelDescription>Belkin Plugin Socket 1.0</modelDescription>
                    <UDN>uuid:{uuid}</UDN>
                    <serialNumber>221517K0101769</serialNumber>
                    <binaryState>0</binaryState>
                    <serviceList>
                        <service>
                            <serviceType>urn:Belkin:service:basicevent:1</serviceType>
                            <serviceId>urn:Belkin:serviceId:basicevent1</serviceId>
                            <controlURL>/upnp/control/basicevent1</controlURL>
                            <eventSubURL>/upnp/event/basicevent1</eventSubURL>
                            <SCPDURL>/eventservice.xml</SCPDURL>
                        </service>
                    </serviceList>
                </device>
            </root>",
            device_name = self.device.name,
            uuid = self.device.uuid
        )
            .as_bytes()
            .to_vec()
    }

    fn handle_eventservice(&self) -> Vec<u8> {
        println!("EVENTSERVICE: {}", self.device.name);
        "<scpd xmlns='urn:Belkin:service-1-0'>
            <actionList>
                <action>
                    <name>SetBinaryState</name>
                    <argumentList>
                        <argument>
                            <retval/>
                            <name>BinaryState</name>
                            <relatedStateVariable>BinaryState</relatedStateVariable>
                            <direction>in</direction>
                        </argument>
                    </argumentList>
                </action>
                <action>
                    <name>GetBinaryState</name>
                    <argumentList>
                        <argument>
                            <retval/>
                            <name>BinaryState</name>
                            <relatedStateVariable>BinaryState</relatedStateVariable>
                            <direction>out</direction>
                        </argument>
                    </argumentList>
                </action>
            </actionList>
            <serviceStateTable>
                <stateVariable sendEvents='yes'>
                    <name>BinaryState</name>
                    <dataType>Boolean</dataType>
                    <defaultValue>0</defaultValue>
                </stateVariable>
                <stateVariable sendEvents='yes'>
                    <name>level</name>
                    <dataType>string</dataType>
                    <defaultValue>0</defaultValue>
                </stateVariable>
            </serviceStateTable>
        </scpd>"
            .to_string()
            .into_bytes()
    }

    fn handle_metainfoservice(&self) -> Vec<u8> {
        println!("NETAINFO: {}", self.device.name);
        "<scpd xmlns='urn:Belkin:service-1-0'>
            <specVersion>
                <major>1</major>
                <minor>0</minor>
            </specVersion>
            <actionList>
                <action>
                    <name>GetMetaInfo</name>
                    <argumentList>
                        <retval/>
                        <name>GetMetaInfo</name>
                        <relatedStateVariable>MetaInfo</relatedStateVariable>
                        <direction>in</direction>
                    </argumentList>
                </action>
            </actionList>
            <serviceStateTable>
                <stateVariable sendEvents='yes'>
                    <name>MetaInfo</name>
                    <dataType>string</dataType>
                    <defaultValue>0</defaultValue>
                </stateVariable>
            </serviceStateTable>
        </scpd>"
            .to_string()
            .into_bytes()
    }
}