From a021c24d17a01c8516fb014ddc14d7ca2279a8da Mon Sep 17 00:00:00 2001 From: Joakim Lundberg <joakim@joakimlundberg.com> Date: Tue, 30 Jan 2018 23:08:20 +0100 Subject: [PATCH] Test of Cargo build system --- utils/xoap/Cargo.toml | 27 ++ utils/xoap/README.md | 73 ++++ utils/xoap/Xargo.toml | 9 + utils/xoap/src/lib.rs | 23 ++ utils/xoap/src/message/header.rs | 251 ++++++++++++ utils/xoap/src/message/packet.rs | 608 ++++++++++++++++++++++++++++++ utils/xoap/src/message/request.rs | 87 +++++ 7 files changed, 1078 insertions(+) create mode 100644 utils/xoap/Cargo.toml create mode 100644 utils/xoap/README.md create mode 100644 utils/xoap/Xargo.toml create mode 100644 utils/xoap/src/lib.rs create mode 100644 utils/xoap/src/message/header.rs create mode 100644 utils/xoap/src/message/packet.rs create mode 100644 utils/xoap/src/message/request.rs diff --git a/utils/xoap/Cargo.toml b/utils/xoap/Cargo.toml new file mode 100644 index 0000000..0f30954 --- /dev/null +++ b/utils/xoap/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "xoap" +version = "0.1.0" +description = "A CoAP library for bare-metal applications" +readme = "README.md" +documentation = "" +repository = "" +license = "MIT OR Apache-2.0" +authors = ["Joakim Lundberg <joakim@joakimlundberg.com>"] +keywords = ["CoAP", "xoap"] + +[dependencies] +#alloc-cortex-m = {} + +[dependencies.smoltcp] +version = "0.4" +default-features = false +features = ["alloc"] + +[dependencies.nb] +git = "https://github.com/japaric/nb" + +[dependencies.cast] +default-features = false +version = "0.2.2" + + diff --git a/utils/xoap/README.md b/utils/xoap/README.md new file mode 100644 index 0000000..718e891 --- /dev/null +++ b/utils/xoap/README.md @@ -0,0 +1,73 @@ +# xoap +[](./LICENSE) + +A fast and stable [Constrained Application Protocol(CoAP)](https://tools.ietf.org/html/rfc7252) library implemented in Rust. + +built from +[Documentation](http://covertness.github.io/coap-rs/coap/index.html) + +## Installation + +First add this to your `Cargo.toml`: + +```toml +[dependencies] +xoap = "0.5" +``` + +Then, add this to your crate root: + +```rust +extern crate xoap; +``` + +## Example + +### Server: +```rust +extern crate xoap; + +use std::io; +use xoap::{CoAPServer, CoAPResponse, CoAPRequest}; + +fn request_handler(req: CoAPRequest) -> Option<CoAPResponse> { + println!("Receive request: {:?}", req); + + // Return the auto-generated response + req.response +} + +fn main() { + let addr = "127.0.0.1:5683"; + + let mut server = CoAPServer::new(addr).unwrap(); + server.handle(request_handler).unwrap(); + + println!("Server up on {}", addr); + println!("Press any key to stop..."); + io::stdin().read_line(&mut String::new()).unwrap(); + + println!("Server shutdown"); +} +``` + +### Client: +```rust +extern crate coap; + +use coap::{CoAPClient, CoAPResponse}; + +fn main() { + let url = "coap://127.0.0.1:5683/Rust"; + println!("Client request: {}", url); + + let response: CoAPResponse = CoAPClient::request(url).unwrap(); + println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); +} +``` + +## Benchmark +```bash +$ cargo run --example server +$ cargo bench +``` diff --git a/utils/xoap/Xargo.toml b/utils/xoap/Xargo.toml new file mode 100644 index 0000000..fbb3999 --- /dev/null +++ b/utils/xoap/Xargo.toml @@ -0,0 +1,9 @@ +[dependencies.core] +stage = 0 + +[dependencies.alloc] +stage = 0 + +[dependencies.compiler_builtins] +stage = 1 + diff --git a/utils/xoap/src/lib.rs b/utils/xoap/src/lib.rs new file mode 100644 index 0000000..483e4a0 --- /dev/null +++ b/utils/xoap/src/lib.rs @@ -0,0 +1,23 @@ +//! Implementation of the [CoAP Protocol][spec]. +//! +//! This library provides both a client interface (`CoAPClient`) +//! and a server interface (`CoAPServer`). +//! +//! [spec]: https://tools.ietf.org/html/rfc7252 +//! + +#![deny(missing_docs)] +#![deny(warnings)] +#[allow(deprecated)] +#![feature(collections)] +#![no_std] +#![allow(unused_mut)] + +//#[macro_use] +extern crate smoltcp; +extern crate nb; +extern crate cast; +extern crate alloc_cortex_m; +#[macro_use] +extern crate collections; + diff --git a/utils/xoap/src/message/header.rs b/utils/xoap/src/message/header.rs new file mode 100644 index 0000000..9482561 --- /dev/null +++ b/utils/xoap/src/message/header.rs @@ -0,0 +1,251 @@ +#[derive(Default, Debug, RustcEncodable, RustcDecodable)] +pub struct HeaderRaw { + ver_type_tkl: u8, + code: u8, + message_id: u16, +} + +#[derive(Debug)] +pub struct Header { + ver_type_tkl: u8, + pub code: MessageClass, + message_id: u16, +} + +#[derive(Debug, PartialEq)] +pub enum MessageClass { + Empty, + RequestType(Requests), + ResponseType(Responses), + Reserved, +} + +#[derive(Debug, PartialEq)] +pub enum Requests { + Get, + Post, + Put, + Delete, +} + +#[derive(Debug, PartialEq)] +pub enum Responses { + // 200 Codes + Created, + Deleted, + Valid, + Changed, + Content, + + // 400 Codes + BadRequest, + Unauthorized, + BadOption, + Forbidden, + NotFound, + MethodNotAllowed, + NotAcceptable, + PreconditionFailed, + RequestEntityTooLarge, + UnsupportedContentFormat, + + // 500 Codes + InternalServerError, + NotImplemented, + BadGateway, + ServiceUnavailable, + GatewayTimeout, + ProxyingNotSupported, +} + +#[derive(PartialEq, Eq, Debug)] +pub enum MessageType { + Confirmable, + NonConfirmable, + Acknowledgement, + Reset, + Invalid, +} + +impl Header { + pub fn new() -> Header { + return Header::from_raw(&HeaderRaw::default()); + } + + pub fn from_raw(raw: &HeaderRaw) -> Header { + return Header { + ver_type_tkl: raw.ver_type_tkl, + code: code_to_class(&raw.code), + message_id: raw.message_id, + }; + } + + pub fn to_raw(&self) -> HeaderRaw { + return HeaderRaw { + ver_type_tkl: self.ver_type_tkl, + code: class_to_code(&self.code), + message_id: self.message_id, + }; + } + + #[inline] + pub fn set_version(&mut self, v: u8) { + let type_tkl = 0x3F & self.ver_type_tkl; + self.ver_type_tkl = v << 6 | type_tkl; + } + + #[inline] + pub fn get_version(&self) -> u8 { + return self.ver_type_tkl >> 6; + } + + #[inline] + pub fn set_type(&mut self, t: MessageType) { + let tn = match t { + MessageType::Confirmable => 0, + MessageType::NonConfirmable => 1, + MessageType::Acknowledgement => 2, + MessageType::Reset => 3, + _ => unreachable!(), + }; + + let ver_tkl = 0xCF & self.ver_type_tkl; + self.ver_type_tkl = tn << 4 | ver_tkl; + } + + #[inline] + pub fn get_type(&self) -> MessageType { + let tn = (0x30 & self.ver_type_tkl) >> 4; + match tn { + 0 => MessageType::Confirmable, + 1 => MessageType::NonConfirmable, + 2 => MessageType::Acknowledgement, + 3 => MessageType::Reset, + _ => MessageType::Invalid, + } + } + + #[inline] + pub fn set_token_length(&mut self, tkl: u8) { + assert_eq!(0xF0 & tkl, 0); + + let ver_type = 0xF0 & self.ver_type_tkl; + self.ver_type_tkl = tkl | ver_type; + } + + #[inline] + pub fn get_token_length(&self) -> u8 { + return 0x0F & self.ver_type_tkl; + } + + pub fn set_code(&mut self, code: &str) { + let code_vec: Vec<&str> = code.split('.').collect(); + assert_eq!(code_vec.len(), 2); + + let class_code = code_vec[0].parse::<u8>().unwrap(); + let detail_code = code_vec[1].parse::<u8>().unwrap(); + assert_eq!(0xF8 & class_code, 0); + assert_eq!(0xE0 & detail_code, 0); + + self.code = code_to_class(&(class_code << 5 | detail_code)); + } + + pub fn get_code(&self) -> String { + class_to_str(&self.code) + } + + #[inline] + pub fn set_message_id(&mut self, message_id: u16) { + self.message_id = message_id; + } + + #[inline] + pub fn get_message_id(&self) -> u16 { + return self.message_id; + } +} + +pub fn class_to_code(class: &MessageClass) -> u8 { + return match *class { + MessageClass::Empty => 0x00, + + MessageClass::RequestType(Requests::Get) => 0x01, + MessageClass::RequestType(Requests::Post) => 0x02, + MessageClass::RequestType(Requests::Put) => 0x03, + MessageClass::RequestType(Requests::Delete) => 0x04, + + MessageClass::ResponseType(Responses::Created) => 0x41, + MessageClass::ResponseType(Responses::Deleted) => 0x42, + MessageClass::ResponseType(Responses::Valid) => 0x43, + MessageClass::ResponseType(Responses::Changed) => 0x44, + MessageClass::ResponseType(Responses::Content) => 0x45, + + MessageClass::ResponseType(Responses::BadRequest) => 0x80, + MessageClass::ResponseType(Responses::Unauthorized) => 0x81, + MessageClass::ResponseType(Responses::BadOption) => 0x82, + MessageClass::ResponseType(Responses::Forbidden) => 0x83, + MessageClass::ResponseType(Responses::NotFound) => 0x84, + MessageClass::ResponseType(Responses::MethodNotAllowed) => 0x85, + MessageClass::ResponseType(Responses::NotAcceptable) => 0x86, + MessageClass::ResponseType(Responses::PreconditionFailed) => 0x8C, + MessageClass::ResponseType(Responses::RequestEntityTooLarge) => 0x8D, + MessageClass::ResponseType(Responses::UnsupportedContentFormat) => 0x8F, + + MessageClass::ResponseType(Responses::InternalServerError) => 0x90, + MessageClass::ResponseType(Responses::NotImplemented) => 0x91, + MessageClass::ResponseType(Responses::BadGateway) => 0x92, + MessageClass::ResponseType(Responses::ServiceUnavailable) => 0x93, + MessageClass::ResponseType(Responses::GatewayTimeout) => 0x94, + MessageClass::ResponseType(Responses::ProxyingNotSupported) => 0x95, + + _ => 0xFF, + } as u8; +} + +pub fn code_to_class(code: &u8) -> MessageClass { + match *code { + 0x00 => MessageClass::Empty, + + 0x01 => MessageClass::RequestType(Requests::Get), + 0x02 => MessageClass::RequestType(Requests::Post), + 0x03 => MessageClass::RequestType(Requests::Put), + 0x04 => MessageClass::RequestType(Requests::Delete), + + 0x41 => MessageClass::ResponseType(Responses::Created), + 0x42 => MessageClass::ResponseType(Responses::Deleted), + 0x43 => MessageClass::ResponseType(Responses::Valid), + 0x44 => MessageClass::ResponseType(Responses::Changed), + 0x45 => MessageClass::ResponseType(Responses::Content), + + 0x80 => MessageClass::ResponseType(Responses::BadRequest), + 0x81 => MessageClass::ResponseType(Responses::Unauthorized), + 0x82 => MessageClass::ResponseType(Responses::BadOption), + 0x83 => MessageClass::ResponseType(Responses::Forbidden), + 0x84 => MessageClass::ResponseType(Responses::NotFound), + 0x85 => MessageClass::ResponseType(Responses::MethodNotAllowed), + 0x86 => MessageClass::ResponseType(Responses::NotAcceptable), + 0x8C => MessageClass::ResponseType(Responses::PreconditionFailed), + 0x8D => MessageClass::ResponseType(Responses::RequestEntityTooLarge), + 0x8F => MessageClass::ResponseType(Responses::UnsupportedContentFormat), + + 0x90 => MessageClass::ResponseType(Responses::InternalServerError), + 0x91 => MessageClass::ResponseType(Responses::NotImplemented), + 0x92 => MessageClass::ResponseType(Responses::BadGateway), + 0x93 => MessageClass::ResponseType(Responses::ServiceUnavailable), + 0x94 => MessageClass::ResponseType(Responses::GatewayTimeout), + 0x95 => MessageClass::ResponseType(Responses::ProxyingNotSupported), + + _ => MessageClass::Reserved, + } +} + +pub fn code_to_str(code: &u8) -> String { + let class_code = (0xE0 & code) >> 5; + let detail_code = 0x1F & code; + + return format!("{}.{:02}", class_code, detail_code); +} + +pub fn class_to_str(class: &MessageClass) -> String { + return code_to_str(&class_to_code(class)); +} diff --git a/utils/xoap/src/message/packet.rs b/utils/xoap/src/message/packet.rs new file mode 100644 index 0000000..9a25fbe --- /dev/null +++ b/utils/xoap/src/message/packet.rs @@ -0,0 +1,608 @@ +//use bincode; +use alloc::BTreeMap; +use alloc::LinkedList; + +//use num::FromPrimitive; + +use message::header; + +macro_rules! u8_to_unsigned_be { + ($src:ident, $start:expr, $end:expr, $t:ty) => ({ + (0 .. $end - $start + 1).rev().fold(0, |acc, i| acc | $src[$start+i] as $t << i * 8) + }) +} + +#[derive(PartialEq, Eq, Debug)] +pub enum CoAPOption { + IfMatch, + UriHost, + ETag, + IfNoneMatch, + Observe, + UriPort, + LocationPath, + UriPath, + ContentFormat, + MaxAge, + UriQuery, + Accept, + LocationQuery, + Block2, + Block1, + ProxyUri, + ProxyScheme, + Size1, +} + +enum_from_primitive! { +#[derive(PartialEq, Eq, Debug)] +pub enum ContentFormat { + TextPlain = 0, + ApplicationLinkFormat = 40, + ApplicationXML = 41, + ApplicationOctetStream = 42, + ApplicationEXI = 47, + ApplicationJSON = 50, +} +} + +#[derive(Debug)] +pub enum PackageError { + InvalidHeader, + InvalidPacketLength, +} + +#[derive(Debug)] +pub enum ParseError { + InvalidHeader, + InvalidTokenLength, + InvalidOptionDelta, + InvalidOptionLength, +} + +#[derive(Debug)] +pub struct Packet { + pub header: header::Header, + token: Vec<u8>, + options: BTreeMap<usize, LinkedList<Vec<u8>>>, + pub payload: Vec<u8>, +} + +impl Packet { + pub fn new() -> Packet { + Packet { + header: header::Header::new(), + token: Vec::new(), + options: BTreeMap::new(), + payload: Vec::new(), + } + } + + pub fn set_token(&mut self, token: Vec<u8>) { + self.header.set_token_length(token.len() as u8); + self.token = token; + } + + pub fn get_token(&self) -> &Vec<u8> { + return &self.token; + } + + pub fn set_option(&mut self, tp: CoAPOption, value: LinkedList<Vec<u8>>) { + let num = Self::get_option_number(tp); + self.options.insert(num, value); + } + + pub fn set_content_format(&mut self, cf: ContentFormat) { + let content_format = cf as u16; + let msb = (content_format >> 8) as u8; + let lsb = (content_format & 0xFF) as u8; + + let content_format: Vec<u8> = vec![msb, lsb]; + self.add_option(CoAPOption::ContentFormat, content_format); + } + + pub fn set_payload(&mut self, payload: Vec<u8>) { + self.payload = payload; + } + + pub fn add_option(&mut self, tp: CoAPOption, value: Vec<u8>) { + let num = Self::get_option_number(tp); + match self.options.get_mut(&num) { + Some(list) => { + list.push_back(value); + return; + } + None => (), + }; + + let mut list = LinkedList::new(); + list.push_back(value); + self.options.insert(num, list); + } + + pub fn get_option(&self, tp: CoAPOption) -> Option<LinkedList<Vec<u8>>> { + let num = Self::get_option_number(tp); + match self.options.get(&num) { + Some(options) => Some(options.clone()), + None => None, + } + } + + pub fn get_content_format(&self) -> Option<ContentFormat> { + if let Some(list) = self.get_option(CoAPOption::ContentFormat) { + if let Some(vector) = list.front() { + let msb = vector[0] as u16; + let lsb = vector[1] as u16; + let number = (msb << 8) + lsb; + + return ContentFormat::from_u16(number); + } + } + + None + } + + /// Decodes a byte slice and construct the equivalent Packet. + pub fn from_bytes(buf: &[u8]) -> Result<Packet, ParseError> { + let header_result: bincode::DecodingResult<header::HeaderRaw> = bincode::decode(buf); + match header_result { + Ok(raw_header) => { + let header = header::Header::from_raw(&raw_header); + let token_length = header.get_token_length(); + let options_start: usize = 4 + token_length as usize; + + if token_length > 8 { + return Err(ParseError::InvalidTokenLength); + } + + if options_start > buf.len() { + return Err(ParseError::InvalidTokenLength); + } + + let token = buf[4..options_start].to_vec(); + + let mut idx = options_start; + let mut options_number = 0; + let mut options: BTreeMap<usize, LinkedList<Vec<u8>>> = BTreeMap::new(); + while idx < buf.len() { + let byte = buf[idx]; + + if byte == 255 || idx > buf.len() { + break; + } + + let mut delta = (byte >> 4) as usize; + let mut length = (byte & 0xF) as usize; + + idx += 1; + + // Check for special delta characters + match delta { + 13 => { + if idx >= buf.len() { + return Err(ParseError::InvalidOptionLength); + } + delta = buf[idx] as usize + 13; + idx += 1; + } + 14 => { + if idx + 1 >= buf.len() { + return Err(ParseError::InvalidOptionLength); + } + + delta = (u16::from_be(u8_to_unsigned_be!(buf, idx, idx + 1, u16)) + 269) + as usize; + idx += 2; + } + 15 => { + return Err(ParseError::InvalidOptionDelta); + } + _ => {} + }; + + // Check for special length characters + match length { + 13 => { + if idx >= buf.len() { + return Err(ParseError::InvalidOptionLength); + } + + length = buf[idx] as usize + 13; + idx += 1; + } + 14 => { + if idx + 1 >= buf.len() { + return Err(ParseError::InvalidOptionLength); + } + + length = (u16::from_be(u8_to_unsigned_be!(buf, idx, idx + 1, u16)) + + 269) as usize; + idx += 2; + } + 15 => { + return Err(ParseError::InvalidOptionLength); + } + _ => {} + }; + + options_number += delta; + + let end = idx + length; + if end > buf.len() { + return Err(ParseError::InvalidOptionLength); + } + let options_value = buf[idx..end].to_vec(); + + if options.contains_key(&options_number) { + let mut options_list = options.get_mut(&options_number).unwrap(); + options_list.push_back(options_value); + } else { + let mut list = LinkedList::new(); + list.push_back(options_value); + options.insert(options_number, list); + } + + idx += length; + } + + let mut payload = Vec::new(); + if idx < buf.len() { + payload = buf[(idx + 1)..buf.len()].to_vec(); + } + + + Ok(Packet { + header: header, + token: token, + options: options, + payload: payload, + }) + } + Err(_) => Err(ParseError::InvalidHeader), + } + } + + /// Returns a vector of bytes representing the Packet. + pub fn to_bytes(&self) -> Result<Vec<u8>, PackageError> { + let mut options_delta_length = 0; + let mut options_bytes: Vec<u8> = Vec::new(); + for (number, value_list) in self.options.iter() { + for value in value_list.iter() { + let mut header: Vec<u8> = Vec::with_capacity(1 + 2 + 2); + let delta = number - options_delta_length; + + let mut byte: u8 = 0; + if delta <= 12 { + byte |= (delta << 4) as u8; + } else if delta < 269 { + byte |= 13 << 4; + } else { + byte |= 14 << 4; + } + if value.len() <= 12 { + byte |= value.len() as u8; + } else if value.len() < 269 { + byte |= 13; + } else { + byte |= 14; + } + header.push(byte); + + if delta > 12 && delta < 269 { + header.push((delta - 13) as u8); + } else if delta >= 269 { + let fix = (delta - 269) as u16; + header.push((fix >> 8) as u8); + header.push((fix & 0xFF) as u8); + } + + if value.len() > 12 && value.len() < 269 { + header.push((value.len() - 13) as u8); + } else if value.len() >= 269 { + let fix = (value.len() - 269) as u16; + header.push((fix >> 8) as u8); + header.push((fix & 0xFF) as u8); + } + + options_delta_length += delta; + + options_bytes.reserve(header.len() + value.len()); + unsafe { + use std::ptr; + let buf_len = options_bytes.len(); + ptr::copy( + header.as_ptr(), + options_bytes.as_mut_ptr().offset(buf_len as isize), + header.len(), + ); + ptr::copy( + value.as_ptr(), + options_bytes + .as_mut_ptr() + .offset((buf_len + header.len()) as isize), + value.len(), + ); + options_bytes.set_len(buf_len + header.len() + value.len()); + } + } + } + + let mut buf_length = 4 + self.payload.len() + self.token.len(); + if self.header.code != header::MessageClass::Empty && self.payload.len() != 0 { + buf_length += 1; + } + buf_length += options_bytes.len(); + + if buf_length > 1280 { + return Err(PackageError::InvalidPacketLength); + } + + let mut buf: Vec<u8> = Vec::with_capacity(buf_length); + let header_result: bincode::EncodingResult<()> = bincode::encode_into( + &self.header.to_raw(), + &mut buf, + bincode::SizeLimit::Infinite, + ); + match header_result { + Ok(_) => { + buf.reserve(self.token.len() + options_bytes.len()); + unsafe { + use std::ptr; + let buf_len = buf.len(); + ptr::copy( + self.token.as_ptr(), + buf.as_mut_ptr().offset(buf_len as isize), + self.token.len(), + ); + ptr::copy( + options_bytes.as_ptr(), + buf.as_mut_ptr() + .offset((buf_len + self.token.len()) as isize), + options_bytes.len(), + ); + buf.set_len(buf_len + self.token.len() + options_bytes.len()); + } + + if self.header.code != header::MessageClass::Empty && self.payload.len() != 0 { + buf.push(0xFF); + buf.reserve(self.payload.len()); + unsafe { + use std::ptr; + let buf_len = buf.len(); + ptr::copy( + self.payload.as_ptr(), + buf.as_mut_ptr().offset(buf.len() as isize), + self.payload.len(), + ); + buf.set_len(buf_len + self.payload.len()); + } + } + Ok(buf) + } + Err(_) => Err(PackageError::InvalidHeader), + } + } + + fn get_option_number(tp: CoAPOption) -> usize { + match tp { + CoAPOption::IfMatch => 1, + CoAPOption::UriHost => 3, + CoAPOption::ETag => 4, + CoAPOption::IfNoneMatch => 5, + CoAPOption::Observe => 6, + CoAPOption::UriPort => 7, + CoAPOption::LocationPath => 8, + CoAPOption::UriPath => 11, + CoAPOption::ContentFormat => 12, + CoAPOption::MaxAge => 14, + CoAPOption::UriQuery => 15, + CoAPOption::Accept => 17, + CoAPOption::LocationQuery => 20, + CoAPOption::Block2 => 23, + CoAPOption::Block1 => 27, + CoAPOption::ProxyUri => 35, + CoAPOption::ProxyScheme => 39, + CoAPOption::Size1 => 60, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use super::super::header; + use std::collections::LinkedList; + + #[test] + fn test_decode_packet_with_options() { + let buf = [ + 0x44, + 0x01, + 0x84, + 0x9e, + 0x51, + 0x55, + 0x77, + 0xe8, + 0xb2, + 0x48, + 0x69, + 0x04, + 0x54, + 0x65, + 0x73, + 0x74, + 0x43, + 0x61, + 0x3d, + 0x31, + ]; + let packet = Packet::from_bytes(&buf); + assert!(packet.is_ok()); + let packet = packet.unwrap(); + assert_eq!(packet.header.get_version(), 1); + assert_eq!(packet.header.get_type(), header::MessageType::Confirmable); + assert_eq!(packet.header.get_token_length(), 4); + assert_eq!( + packet.header.code, + header::MessageClass::RequestType(header::Requests::Get) + ); + assert_eq!(packet.header.get_message_id(), 33950); + assert_eq!(*packet.get_token(), vec![0x51, 0x55, 0x77, 0xE8]); + assert_eq!(packet.options.len(), 2); + + let uri_path = packet.get_option(CoAPOption::UriPath); + assert!(uri_path.is_some()); + let uri_path = uri_path.unwrap(); + let mut expected_uri_path = LinkedList::new(); + expected_uri_path.push_back("Hi".as_bytes().to_vec()); + expected_uri_path.push_back("Test".as_bytes().to_vec()); + assert_eq!(uri_path, expected_uri_path); + + let uri_query = packet.get_option(CoAPOption::UriQuery); + assert!(uri_query.is_some()); + let uri_query = uri_query.unwrap(); + let mut expected_uri_query = LinkedList::new(); + expected_uri_query.push_back("a=1".as_bytes().to_vec()); + assert_eq!(uri_query, expected_uri_query); + } + + #[test] + fn test_decode_packet_with_payload() { + let buf = [ + 0x64, + 0x45, + 0x13, + 0xFD, + 0xD0, + 0xE2, + 0x4D, + 0xAC, + 0xFF, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + ]; + let packet = Packet::from_bytes(&buf); + assert!(packet.is_ok()); + let packet = packet.unwrap(); + assert_eq!(packet.header.get_version(), 1); + assert_eq!( + packet.header.get_type(), + header::MessageType::Acknowledgement + ); + assert_eq!(packet.header.get_token_length(), 4); + assert_eq!( + packet.header.code, + header::MessageClass::ResponseType(header::Responses::Content) + ); + assert_eq!(packet.header.get_message_id(), 5117); + assert_eq!(*packet.get_token(), vec![0xD0, 0xE2, 0x4D, 0xAC]); + assert_eq!(packet.payload, "Hello".as_bytes().to_vec()); + } + + #[test] + fn test_encode_packet_with_options() { + let mut packet = Packet::new(); + packet.header.set_version(1); + packet.header.set_type(header::MessageType::Confirmable); + packet.header.code = header::MessageClass::RequestType(header::Requests::Get); + packet.header.set_message_id(33950); + packet.set_token(vec![0x51, 0x55, 0x77, 0xE8]); + packet.add_option(CoAPOption::UriPath, b"Hi".to_vec()); + packet.add_option(CoAPOption::UriPath, b"Test".to_vec()); + packet.add_option(CoAPOption::UriQuery, b"a=1".to_vec()); + assert_eq!( + packet.to_bytes().unwrap(), + vec![ + 0x44, + 0x01, + 0x84, + 0x9e, + 0x51, + 0x55, + 0x77, + 0xe8, + 0xb2, + 0x48, + 0x69, + 0x04, + 0x54, + 0x65, + 0x73, + 0x74, + 0x43, + 0x61, + 0x3d, + 0x31, + ] + ); + } + + #[test] + fn test_encode_packet_with_payload() { + let mut packet = Packet::new(); + packet.header.set_version(1); + packet.header.set_type(header::MessageType::Acknowledgement); + packet.header.code = header::MessageClass::ResponseType(header::Responses::Content); + packet.header.set_message_id(5117); + packet.set_token(vec![0xD0, 0xE2, 0x4D, 0xAC]); + packet.payload = "Hello".as_bytes().to_vec(); + assert_eq!( + packet.to_bytes().unwrap(), + vec![ + 0x64, + 0x45, + 0x13, + 0xFD, + 0xD0, + 0xE2, + 0x4D, + 0xAC, + 0xFF, + 0x48, + 0x65, + 0x6C, + 0x6C, + 0x6F, + ] + ); + } + + #[test] + fn test_encode_decode_content_format() { + let mut packet = Packet::new(); + packet.set_content_format(ContentFormat::ApplicationJSON); + assert_eq!( + ContentFormat::ApplicationJSON, + packet.get_content_format().unwrap() + ) + } + + #[test] + fn test_decode_empty_content_format() { + let packet = Packet::new(); + assert!(packet.get_content_format().is_none()); + } + + #[test] + fn test_malicious_packet() { + use rand; + use quickcheck::{QuickCheck, StdGen, TestResult}; + + fn run(x: Vec<u8>) -> TestResult { + match Packet::from_bytes(&x[..]) { + Ok(packet) => TestResult::from_bool( + packet.get_token().len() == packet.header.get_token_length() as usize, + ), + Err(_) => TestResult::passed(), + } + } + QuickCheck::new() + .tests(10000) + .gen(StdGen::new(rand::thread_rng(), 1500)) + .quickcheck(run as fn(Vec<u8>) -> TestResult) + } +} diff --git a/utils/xoap/src/message/request.rs b/utils/xoap/src/message/request.rs new file mode 100644 index 0000000..8ccff79 --- /dev/null +++ b/utils/xoap/src/message/request.rs @@ -0,0 +1,87 @@ +use message::IsMessage; +use message::response::CoAPResponse; +use message::packet::Packet; +use message::header::Header; +//use std::net::SocketAddr; + +#[derive(Debug)] +pub struct CoAPRequest { + pub message: Packet, + pub response: Option<CoAPResponse>, + pub source: Option<SocketAddr>, +} + +impl CoAPRequest { + pub fn new() -> CoAPRequest { + CoAPRequest { + response: None, + message: Packet::new(), + source: None, + } + } + + pub fn from_packet(packet: Packet, source: &SocketAddr) -> CoAPRequest { + CoAPRequest { + response: CoAPResponse::new(&packet), + message: packet, + source: Some(source.clone()), + } + } +} + +impl IsMessage for CoAPRequest { + fn get_message(&self) -> &Packet { + &self.message + } + fn get_mut_message(&mut self) -> &mut Packet { + &mut self.message + } + fn get_header(&self) -> &Header { + &self.message.header + } + fn get_mut_header(&mut self) -> &mut Header { + &mut self.message.header + } +} + +#[cfg(test)] +mod test { + use super::*; + use message::packet::{Packet, CoAPOption}; + use message::header::MessageType; + use message::IsMessage; + use std::net::SocketAddr; + use std::str::FromStr; + + #[test] + fn test_request_create() { + + let mut packet = Packet::new(); + let mut request1 = CoAPRequest::new(); + + packet.set_token(vec![0x17, 0x38]); + request1.set_token(vec![0x17, 0x38]); + + packet.add_option(CoAPOption::UriPath, b"test-interface".to_vec()); + request1.add_option(CoAPOption::UriPath, b"test-interface".to_vec()); + + packet.header.set_message_id(42); + request1.set_message_id(42); + + packet.header.set_version(2); + request1.set_version(2); + + packet.header.set_type(MessageType::Confirmable); + request1.set_type(MessageType::Confirmable); + + packet.header.set_code("0.04"); + request1.set_code("0.04"); + + let request2 = CoAPRequest::from_packet(packet, + &SocketAddr::from_str("127.0.0.1:1234").unwrap()); + + assert_eq!(request1.message.to_bytes().unwrap(), + request2.message.to_bytes().unwrap()); + + } +} \ No newline at end of file -- GitLab