Skip to content
Snippets Groups Projects
Commit cda8c9ca authored by Covertness's avatar Covertness Committed by GitHub
Browse files

Merge pull request #9 from jamesmunns/packet-refactor

Packet refactor
parents e1b04ce0 6049d9f6
No related branches found
No related tags found
No related merge requests found
target
Cargo.lock
*.rs.bk
......@@ -33,12 +33,13 @@ extern crate coap;
extern crate coap;
use std::io;
use coap::packet::*;
use coap::{CoAPServer, CoAPClient};
use coap::{CoAPServer, CoAPResponse, CoAPRequest};
fn request_handler(req: Packet, response: Option<Packet>) -> Option<Packet> {
fn request_handler(req: CoAPRequest) -> Option<CoAPResponse> {
println!("Receive request: {:?}", req);
response
// Return the auto-generated response
req.response
}
fn main() {
......@@ -59,15 +60,14 @@ fn main() {
```rust
extern crate coap;
use coap::packet::*;
use coap::CoAPClient;
use coap::{CoAPClient, CoAPResponse};
fn main() {
let url = "coap://127.0.0.1:5683/Rust";
println!("Client request: {}", url);
let response: Packet = CoAPClient::request(url).unwrap();
println!("Server reply: {}", String::from_utf8(response.payload).unwrap());
let response: CoAPResponse = CoAPClient::request(url).unwrap();
println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap());
}
```
......
......@@ -13,11 +13,11 @@ fn bench_client_request(b: &mut Bencher) {
let request = "test";
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.set_type(MessageType::Confirmable);
packet.header.set_code("0.01");
packet.header.set_message_id(1);
packet.set_token(vec!(0x51, 0x55, 0x77, 0xE8));
packet.add_option(OptionType::UriPath, request.to_string().into_bytes());
packet.add_option(CoAPOption::UriPath, request.to_string().into_bytes());
b.iter(|| {
let client = CoAPClient::new(addr).unwrap();
......
extern crate coap;
use std::io::ErrorKind;
use coap::packet::*;
use coap::CoAPClient;
use coap::{CoAPClient, CoAPRequest, IsMessage, MessageType, CoAPOption};
fn main() {
let addr = "127.0.0.1:5683";
let request = "test";
let endpoint = "test";
let client = CoAPClient::new(addr).unwrap();
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.set_code("0.01");
packet.header.set_message_id(1);
packet.set_token(vec!(0x51, 0x55, 0x77, 0xE8));
packet.add_option(OptionType::UriPath, request.to_string().into_bytes());
client.send(&packet).unwrap();
println!("Client request: coap://{}/{}", addr, request);
let mut request = CoAPRequest::new();
request.set_version(1);
request.set_type(MessageType::Confirmable);
request.set_code("0.01");
request.set_message_id(1);
request.set_token(vec![0x51, 0x55, 0x77, 0xE8]);
request.add_option(CoAPOption::UriPath, endpoint.to_string().into_bytes());
client.send(&request).unwrap();
println!("Client request: coap://{}/{}", addr, endpoint);
match client.receive() {
Ok(response) => {
println!("Server reply: {}", String::from_utf8(response.payload).unwrap());
},
println!("Server reply: {}",
String::from_utf8(response.message.payload).unwrap());
}
Err(e) => {
match e.kind() {
ErrorKind::WouldBlock => println!("Request timeout"), // Unix
......
extern crate coap;
use coap::packet::*;
use coap::{CoAPServer, CoAPClient};
use coap::{CoAPServer, CoAPClient, CoAPRequest, CoAPResponse, CoAPOption};
use coap::IsMessage;
fn request_handler(req: Packet, response: Option<Packet>) -> Option<Packet> {
let uri_path = req.get_option(OptionType::UriPath).unwrap();
fn request_handler(request: CoAPRequest) -> Option<CoAPResponse> {
let uri_path = request.get_option(CoAPOption::UriPath).unwrap();
return match response {
Some(mut packet) => {
packet.set_payload(uri_path.front().unwrap().clone());
Some(packet)
},
_ => None
return match request.response {
Some(mut response) => {
response.set_payload(uri_path.front().unwrap().clone());
Some(response)
}
_ => None,
};
}
......@@ -22,6 +22,7 @@ fn main() {
let url = "coap://127.0.0.1:5683/Rust";
println!("Client request: {}", url);
let response: Packet = CoAPClient::request(url).unwrap();
println!("Server reply: {}", String::from_utf8(response.payload).unwrap());
let response: CoAPResponse = CoAPClient::request(url).unwrap();
println!("Server reply: {}",
String::from_utf8(response.message.payload).unwrap());
}
extern crate coap;
use std::io;
use coap::packet::*;
use coap::CoAPServer;
use coap::{CoAPServer, CoAPResponse, CoAPRequest, IsMessage};
fn request_handler(_: Packet, response: Option<Packet>) -> Option<Packet> {
return match response {
Some(mut packet) => {
packet.set_payload(b"OK".to_vec());
Some(packet)
fn request_handler(request: CoAPRequest) -> Option<CoAPResponse> {
return match request.response {
Some(mut message) => {
message.set_payload(b"OK".to_vec());
Some(message)
},
_ => None
};
......
......@@ -4,7 +4,11 @@ use std::time::Duration;
use url::{UrlParser, SchemeType};
use num;
use rand::{thread_rng, random, Rng};
use packet::{Packet, PacketType, OptionType};
use message::packet::{Packet, CoAPOption};
use message::header::MessageType;
use message::response::CoAPResponse;
use message::request::CoAPRequest;
use message::IsMessage;
const DEFAULT_RECEIVE_TIMEOUT: u64 = 5; // 5s
......@@ -46,19 +50,19 @@ impl CoAPClient {
}
/// Execute a request with the coap url and a specific timeout. Default timeout is 5s.
pub fn request_with_timeout(url: &str, timeout: Option<Duration>) -> Result<Packet> {
pub fn request_with_timeout(url: &str, timeout: Option<Duration>) -> Result<CoAPResponse> {
let mut url_parser = UrlParser::new();
url_parser.scheme_type_mapper(Self::coap_scheme_type_mapper);
match url_parser.parse(url) {
Ok(url_params) => {
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.set_code("0.01");
let mut packet = CoAPRequest::new();
packet.set_version(1);
packet.set_type(MessageType::Confirmable);
packet.set_code("0.01");
let message_id = thread_rng().gen_range(0, num::pow(2u32, 16)) as u16;
packet.header.set_message_id(message_id);
packet.set_message_id(message_id);
let mut token: Vec<u8> = vec![1, 1, 1, 1];
for x in token.iter_mut() {
......@@ -74,7 +78,7 @@ impl CoAPClient {
if let Some(path) = url_params.path() {
for p in path.iter() {
packet.add_option(OptionType::UriPath, p.clone().into_bytes().to_vec());
packet.add_option(CoAPOption::UriPath, p.clone().into_bytes().to_vec());
}
};
......@@ -84,7 +88,7 @@ impl CoAPClient {
try!(client.set_receive_timeout(timeout));
match client.receive() {
Ok(receive_packet) => {
if receive_packet.header.get_message_id() == message_id &&
if receive_packet.get_message_id() == message_id &&
*receive_packet.get_token() == token {
return Ok(receive_packet);
} else {
......@@ -99,13 +103,13 @@ impl CoAPClient {
}
/// Execute a request with the coap url.
pub fn request(url: &str) -> Result<Packet> {
pub fn request(url: &str) -> Result<CoAPResponse> {
Self::request_with_timeout(url, Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0)))
}
/// Execute a request.
pub fn send(&self, packet: &Packet) -> Result<()> {
match packet.to_bytes() {
pub fn send(&self, request: &CoAPRequest) -> Result<()> {
match request.message.to_bytes() {
Ok(bytes) => {
let size = try!(self.socket.send_to(&bytes[..], self.peer_addr));
if size == bytes.len() {
......@@ -119,12 +123,12 @@ impl CoAPClient {
}
/// Receive a response.
pub fn receive(&self) -> Result<Packet> {
pub fn receive(&self) -> Result<CoAPResponse> {
let mut buf = [0; 1500];
let (nread, _src) = try!(self.socket.recv_from(&mut buf));
match Packet::from_bytes(&buf[..nread]) {
Ok(packet) => Ok(packet),
Ok(packet) => Ok(CoAPResponse { message: packet }),
Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")),
}
}
......@@ -148,7 +152,8 @@ mod test {
use super::*;
use std::time::Duration;
use std::io::ErrorKind;
use packet::Packet;
use message::request::CoAPRequest;
use message::response::CoAPResponse;
use server::CoAPServer;
#[test]
......@@ -158,7 +163,7 @@ mod test {
assert!(CoAPClient::request("127.0.0.1").is_err());
}
fn request_handler(_: Packet, _: Option<Packet>) -> Option<Packet> {
fn request_handler(_: CoAPRequest) -> Option<CoAPResponse> {
None
}
......
......@@ -27,11 +27,10 @@
//! extern crate coap;
//! use std::io;
//! use coap::packet::*;
//! use coap::{CoAPServer, CoAPClient};
//! use coap::{CoAPServer, CoAPClient, CoAPRequest, CoAPResponse};
//! fn request_handler(req: Packet, resp: Option<Packet>) -> Option<Packet> {
//! println!("Receive request: {:?}", req);
//! fn request_handler(request: CoAPRequest) -> Option<CoAPResponse> {
//! println!("Receive request: {:?}", request);
//! None
//! }
......@@ -54,15 +53,15 @@
//! ```no_run
//! extern crate coap;
//!
//! use coap::packet::*;
//! use coap::message::response::CoAPResponse;
//! use coap::CoAPClient;
//!
//! fn main() {
//! let url = "coap://127.0.0.1:5683/Rust";
//! println!("Client request: {}", url);
//!
//! let response: Packet = CoAPClient::request(url).unwrap();
//! println!("Server reply: {}", String::from_utf8(response.payload).unwrap());
//! let response: CoAPResponse = CoAPClient::request(url).unwrap();
//! println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap());
//! }
//! ```
......@@ -79,9 +78,14 @@ extern crate quickcheck;
#[macro_use]
extern crate log;
pub use server::CoAPServer;
pub use client::CoAPClient;
pub use message::header::MessageType;
pub use message::IsMessage;
pub use message::packet::CoAPOption;
pub use message::request::CoAPRequest;
pub use message::response::CoAPResponse;
pub use server::CoAPServer;
pub mod packet;
pub mod message;
pub mod client;
pub mod server;
#[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));
}
#[cfg(test)]
mod test {
use message::header::*;
#[test]
fn test_header_codes() {
for code in 0..255 {
let class = code_to_class(&code);
let code_str = code_to_str(&code);
let class_str = class_to_str(&class);
// Reserved class could technically be many codes
// so only check valid items
if class != MessageClass::Reserved {
assert_eq!(class_to_code(&class), code);
assert_eq!(code_str, class_str);
}
}
}
}
pub mod header;
pub mod request;
pub mod response;
pub mod packet;
use message::packet::Packet;
use message::header::Header;
use std::collections::LinkedList;
pub trait IsMessage {
fn get_message(&self) -> &Packet;
fn get_mut_message(&mut self) -> &mut Packet;
fn get_header(&self) -> &Header;
fn get_mut_header(&mut self) -> &mut Header;
fn set_token(&mut self, token: Vec<u8>) {
self.get_mut_message().set_token(token);
}
fn get_token(&self) -> &Vec<u8> {
return self.get_message().get_token();
}
fn set_option(&mut self, tp: packet::CoAPOption, value: LinkedList<Vec<u8>>) {
self.get_mut_message().set_option(tp, value);
}
fn set_payload(&mut self, payload: Vec<u8>) {
self.get_mut_message().set_payload(payload);
}
fn add_option(&mut self, tp: packet::CoAPOption, value: Vec<u8>) {
self.get_mut_message().add_option(tp, value);
}
fn get_option(&self, tp: packet::CoAPOption) -> Option<LinkedList<Vec<u8>>> {
return self.get_message().get_option(tp);
}
fn get_message_id(&self) -> u16 {
return self.get_message().header.get_message_id();
}
fn set_message_id(&mut self, message_id: u16) {
self.get_mut_message().header.set_message_id(message_id);
}
fn set_version(&mut self, v: u8) {
self.get_mut_message().header.set_version(v);
}
fn get_version(&self) -> u8 {
return self.get_message().header.get_version();
}
fn set_type(&mut self, t: header::MessageType) {
self.get_mut_message().header.set_type(t);
}
fn get_type(&self) -> header::MessageType {
return self.get_message().header.get_type();
}
fn get_code(&self) -> String {
return self.get_message().header.get_code();
}
fn set_code(&mut self, code: &str) {
self.get_mut_message().header.set_code(code);
}
}
......@@ -2,6 +2,8 @@ use bincode;
use std::collections::BTreeMap;
use std::collections::LinkedList;
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)
......@@ -9,273 +11,7 @@ macro_rules! u8_to_unsigned_be {
}
#[derive(PartialEq, Eq, Debug)]
pub enum PacketType {
Confirmable,
NonConfirmable,
Acknowledgement,
Reset,
Invalid,
}
#[derive(Default, Debug, RustcEncodable, RustcDecodable)]
pub struct PacketHeaderRaw {
ver_type_tkl: u8,
code: u8,
message_id: u16,
}
#[derive(Debug)]
pub struct PacketHeader {
ver_type_tkl: u8,
pub code: PacketClass,
message_id: u16,
}
#[derive(Debug, PartialEq)]
pub enum PacketClass {
Empty,
Request(Requests),
Response(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,
}
pub fn class_to_code(class: &PacketClass) -> u8 {
return match *class {
PacketClass::Empty => 0x00,
PacketClass::Request(Requests::Get) => 0x01,
PacketClass::Request(Requests::Post) => 0x02,
PacketClass::Request(Requests::Put) => 0x03,
PacketClass::Request(Requests::Delete) => 0x04,
PacketClass::Response(Responses::Created) => 0x41,
PacketClass::Response(Responses::Deleted) => 0x42,
PacketClass::Response(Responses::Valid) => 0x43,
PacketClass::Response(Responses::Changed) => 0x44,
PacketClass::Response(Responses::Content) => 0x45,
PacketClass::Response(Responses::BadRequest) => 0x80,
PacketClass::Response(Responses::Unauthorized) => 0x81,
PacketClass::Response(Responses::BadOption) => 0x82,
PacketClass::Response(Responses::Forbidden) => 0x83,
PacketClass::Response(Responses::NotFound) => 0x84,
PacketClass::Response(Responses::MethodNotAllowed) => 0x85,
PacketClass::Response(Responses::NotAcceptable) => 0x86,
PacketClass::Response(Responses::PreconditionFailed) => 0x8C,
PacketClass::Response(Responses::RequestEntityTooLarge) => 0x8D,
PacketClass::Response(Responses::UnsupportedContentFormat) => 0x8F,
PacketClass::Response(Responses::InternalServerError) => 0x90,
PacketClass::Response(Responses::NotImplemented) => 0x91,
PacketClass::Response(Responses::BadGateway) => 0x92,
PacketClass::Response(Responses::ServiceUnavailable) => 0x93,
PacketClass::Response(Responses::GatewayTimeout) => 0x94,
PacketClass::Response(Responses::ProxyingNotSupported) => 0x95,
_ => 0xFF,
} as u8;
}
pub fn code_to_class(code: &u8) -> PacketClass {
match *code {
0x00 => PacketClass::Empty,
0x01 => PacketClass::Request(Requests::Get),
0x02 => PacketClass::Request(Requests::Post),
0x03 => PacketClass::Request(Requests::Put),
0x04 => PacketClass::Request(Requests::Delete),
0x41 => PacketClass::Response(Responses::Created),
0x42 => PacketClass::Response(Responses::Deleted),
0x43 => PacketClass::Response(Responses::Valid),
0x44 => PacketClass::Response(Responses::Changed),
0x45 => PacketClass::Response(Responses::Content),
0x80 => PacketClass::Response(Responses::BadRequest),
0x81 => PacketClass::Response(Responses::Unauthorized),
0x82 => PacketClass::Response(Responses::BadOption),
0x83 => PacketClass::Response(Responses::Forbidden),
0x84 => PacketClass::Response(Responses::NotFound),
0x85 => PacketClass::Response(Responses::MethodNotAllowed),
0x86 => PacketClass::Response(Responses::NotAcceptable),
0x8C => PacketClass::Response(Responses::PreconditionFailed),
0x8D => PacketClass::Response(Responses::RequestEntityTooLarge),
0x8F => PacketClass::Response(Responses::UnsupportedContentFormat),
0x90 => PacketClass::Response(Responses::InternalServerError),
0x91 => PacketClass::Response(Responses::NotImplemented),
0x92 => PacketClass::Response(Responses::BadGateway),
0x93 => PacketClass::Response(Responses::ServiceUnavailable),
0x94 => PacketClass::Response(Responses::GatewayTimeout),
0x95 => PacketClass::Response(Responses::ProxyingNotSupported),
_ => PacketClass::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: &PacketClass) -> String {
return code_to_str(&class_to_code(class));
}
impl PacketHeader {
pub fn new() -> PacketHeader {
return PacketHeader::from_raw(&PacketHeaderRaw::default());
}
pub fn from_raw(raw: &PacketHeaderRaw) -> PacketHeader {
return PacketHeader {
ver_type_tkl: raw.ver_type_tkl,
code: code_to_class(&raw.code),
message_id: raw.message_id,
};
}
pub fn to_raw(&self) -> PacketHeaderRaw {
return PacketHeaderRaw {
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: PacketType) {
let tn = match t {
PacketType::Confirmable => 0,
PacketType::NonConfirmable => 1,
PacketType::Acknowledgement => 2,
PacketType::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) -> PacketType {
let tn = (0x30 & self.ver_type_tkl) >> 4;
match tn {
0 => PacketType::Confirmable,
1 => PacketType::NonConfirmable,
2 => PacketType::Acknowledgement,
3 => PacketType::Reset,
_ => PacketType::Invalid,
}
}
#[inline]
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]
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;
}
}
#[derive(Debug)]
pub enum ParseError {
InvalidHeader,
InvalidTokenLength,
InvalidOptionDelta,
InvalidOptionLength,
}
#[derive(Debug)]
pub enum PackageError {
InvalidHeader,
InvalidPacketLength,
}
#[derive(PartialEq, Eq, Debug)]
pub enum OptionType {
pub enum CoAPOption {
IfMatch,
UriHost,
ETag,
......@@ -296,9 +32,23 @@ pub enum OptionType {
Size1,
}
#[derive(Debug)]
pub enum PackageError {
InvalidHeader,
InvalidPacketLength,
}
#[derive(Debug)]
pub enum ParseError {
InvalidHeader,
InvalidTokenLength,
InvalidOptionDelta,
InvalidOptionLength,
}
#[derive(Debug)]
pub struct Packet {
pub header: PacketHeader,
pub header: header::Header,
token: Vec<u8>,
options: BTreeMap<usize, LinkedList<Vec<u8>>>,
pub payload: Vec<u8>,
......@@ -307,7 +57,7 @@ pub struct Packet {
impl Packet {
pub fn new() -> Packet {
Packet {
header: PacketHeader::new(),
header: header::Header::new(),
token: Vec::new(),
options: BTreeMap::new(),
payload: Vec::new(),
......@@ -323,7 +73,7 @@ impl Packet {
return &self.token;
}
pub fn set_option(&mut self, tp: OptionType, value: LinkedList<Vec<u8>>) {
pub fn set_option(&mut self, tp: CoAPOption, value: LinkedList<Vec<u8>>) {
let num = Self::get_option_number(tp);
self.options.insert(num, value);
}
......@@ -332,7 +82,7 @@ impl Packet {
self.payload = payload;
}
pub fn add_option(&mut self, tp: OptionType, value: Vec<u8>) {
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) => {
......@@ -347,7 +97,7 @@ impl Packet {
self.options.insert(num, list);
}
pub fn get_option(&self, tp: OptionType) -> Option<LinkedList<Vec<u8>>> {
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()),
......@@ -357,10 +107,10 @@ impl Packet {
/// Decodes a byte slice and construct the equivalent Packet.
pub fn from_bytes(buf: &[u8]) -> Result<Packet, ParseError> {
let header_result: bincode::DecodingResult<PacketHeaderRaw> = bincode::decode(buf);
let header_result: bincode::DecodingResult<header::HeaderRaw> = bincode::decode(buf);
match header_result {
Ok(raw_header) => {
let header = PacketHeader::from_raw(&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;
......@@ -535,7 +285,7 @@ impl Packet {
}
let mut buf_length = 4 + self.payload.len() + self.token.len();
if self.header.code != PacketClass::Empty && self.payload.len() != 0 {
if self.header.code != header::MessageClass::Empty && self.payload.len() != 0 {
buf_length += 1;
}
buf_length += options_bytes.len();
......@@ -564,7 +314,7 @@ impl Packet {
buf.set_len(buf_len + self.token.len() + options_bytes.len());
}
if self.header.code != PacketClass::Empty && self.payload.len() != 0 {
if self.header.code != header::MessageClass::Empty && self.payload.len() != 0 {
buf.push(0xFF);
buf.reserve(self.payload.len());
unsafe {
......@@ -582,71 +332,36 @@ impl Packet {
}
}
fn get_option_number(tp: OptionType) -> usize {
fn get_option_number(tp: CoAPOption) -> usize {
match tp {
OptionType::IfMatch => 1,
OptionType::UriHost => 3,
OptionType::ETag => 4,
OptionType::IfNoneMatch => 5,
OptionType::Observe => 6,
OptionType::UriPort => 7,
OptionType::LocationPath => 8,
OptionType::UriPath => 11,
OptionType::ContentFormat => 12,
OptionType::MaxAge => 14,
OptionType::UriQuery => 15,
OptionType::Accept => 17,
OptionType::LocationQuery => 20,
OptionType::Block2 => 23,
OptionType::Block1 => 27,
OptionType::ProxyUri => 35,
OptionType::ProxyScheme => 39,
OptionType::Size1 => 60,
}
}
}
/// Convert a request to a response
pub fn auto_response(request_packet: &Packet) -> Option<Packet> {
let mut packet = Packet::new();
packet.header.set_version(1);
let response_type = match request_packet.header.get_type() {
PacketType::Confirmable => PacketType::Acknowledgement,
PacketType::NonConfirmable => PacketType::NonConfirmable,
_ => return None,
};
packet.header.set_type(response_type);
packet.header.code = PacketClass::Response(Responses::Content);
packet.header.set_message_id(request_packet.header.get_message_id());
packet.set_token(request_packet.get_token().clone());
packet.payload = request_packet.payload.clone();
Some(packet)
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_header_codes() {
for code in 0..255 {
let class = code_to_class(&code);
let code_str = code_to_str(&code);
let class_str = class_to_str(&class);
// Reserved class could technically be many codes
// so only check valid items
if class != PacketClass::Reserved {
assert_eq!(class_to_code(&class), code);
assert_eq!(code_str, class_str);
}
}
}
#[test]
fn test_decode_packet_with_options() {
let buf = [0x44, 0x01, 0x84, 0x9e, 0x51, 0x55, 0x77, 0xe8, 0xb2, 0x48, 0x69, 0x04, 0x54,
......@@ -655,14 +370,15 @@ mod test {
assert!(packet.is_ok());
let packet = packet.unwrap();
assert_eq!(packet.header.get_version(), 1);
assert_eq!(packet.header.get_type(), PacketType::Confirmable);
assert_eq!(packet.header.get_type(), header::MessageType::Confirmable);
assert_eq!(packet.header.get_token_length(), 4);
assert_eq!(packet.header.code, PacketClass::Request(Requests::Get));
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(OptionType::UriPath);
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();
......@@ -670,7 +386,7 @@ mod test {
expected_uri_path.push_back("Test".as_bytes().to_vec());
assert_eq!(uri_path, expected_uri_path);
let uri_query = packet.get_option(OptionType::UriQuery);
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();
......@@ -686,10 +402,11 @@ mod test {
assert!(packet.is_ok());
let packet = packet.unwrap();
assert_eq!(packet.header.get_version(), 1);
assert_eq!(packet.header.get_type(), PacketType::Acknowledgement);
assert_eq!(packet.header.get_type(),
header::MessageType::Acknowledgement);
assert_eq!(packet.header.get_token_length(), 4);
assert_eq!(packet.header.code,
PacketClass::Response(Responses::Content));
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());
......@@ -699,13 +416,13 @@ mod test {
fn test_encode_packet_with_options() {
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.code = PacketClass::Request(Requests::Get);
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(OptionType::UriPath, b"Hi".to_vec());
packet.add_option(OptionType::UriPath, b"Test".to_vec());
packet.add_option(OptionType::UriQuery, b"a=1".to_vec());
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]);
......@@ -715,8 +432,8 @@ mod test {
fn test_encode_packet_with_payload() {
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Acknowledgement);
packet.header.code = PacketClass::Response(Responses::Content);
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();
......
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());
}
}
use message::IsMessage;
use message::packet::Packet;
use message::header::{Header, MessageType, MessageClass, Responses};
#[derive(Debug)]
pub struct CoAPResponse {
pub message: Packet,
}
impl CoAPResponse {
pub fn new(request: &Packet) -> Option<CoAPResponse> {
let mut packet = Packet::new();
packet.header.set_version(1);
let response_type = match request.header.get_type() {
MessageType::Confirmable => MessageType::Acknowledgement,
MessageType::NonConfirmable => MessageType::NonConfirmable,
_ => return None,
};
packet.header.set_type(response_type);
packet.header.code = MessageClass::ResponseType(Responses::Content);
packet.header.set_message_id(request.header.get_message_id());
packet.set_token(request.get_token().clone());
packet.payload = request.payload.clone();
Some(CoAPResponse { message: packet })
}
}
impl IsMessage for CoAPResponse {
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;
use message::IsMessage;
use message::header::MessageType;
#[test]
fn test_new_response_valid() {
for mtyp in vec![MessageType::Confirmable, MessageType::NonConfirmable] {
let mut packet = Packet::new();
packet.header.set_type(mtyp);
let opt_resp = CoAPResponse::new(&packet);
assert!(opt_resp.is_some());
let response = opt_resp.unwrap();
assert_eq!(packet.payload, response.message.payload);
}
}
#[test]
fn test_new_response_invalid() {
let mut packet = Packet::new();
packet.header.set_type(MessageType::Acknowledgement);
assert!(CoAPResponse::new(&packet).is_none());
}
}
......@@ -5,12 +5,14 @@ use std::net::{ToSocketAddrs, SocketAddr};
use std::sync::mpsc;
use mio::{EventLoop, PollOpt, EventSet, Handler, Sender, Token};
use mio::udp::UdpSocket;
use packet::{Packet, auto_response};
use message::packet::Packet;
use message::request::CoAPRequest;
use message::response::CoAPResponse;
use threadpool::ThreadPool;
const DEFAULT_WORKER_NUM: usize = 4;
pub type TxQueue = mpsc::Sender<CoAPResponse>;
pub type RxQueue = mpsc::Receiver<CoAPResponse>;
type TxQueue = mpsc::Sender<QueuedResponse>;
type RxQueue = mpsc::Receiver<QueuedResponse>;
#[derive(Debug)]
pub enum CoAPServerError {
......@@ -20,21 +22,21 @@ pub enum CoAPServerError {
}
#[derive(Debug)]
pub struct CoAPResponse {
struct QueuedResponse {
pub address: SocketAddr,
pub response: Packet,
pub response: CoAPResponse,
}
pub trait CoAPHandler: Sync + Send + Copy {
fn handle(&self, Packet, Option<Packet>) -> Option<Packet>;
fn handle(&self, CoAPRequest) -> Option<CoAPResponse>;
}
impl<F> CoAPHandler for F
where F: Fn(Packet, Option<Packet>) -> Option<Packet>,
where F: Fn(CoAPRequest) -> Option<CoAPResponse>,
F: Sync + Send + Copy
{
fn handle(&self, request: Packet, response: Option<Packet>) -> Option<Packet> {
return self(request, response);
fn handle(&self, request: CoAPRequest) -> Option<CoAPResponse> {
return self(request);
}
}
......@@ -77,17 +79,17 @@ impl<H: CoAPHandler + 'static> Handler for UdpHandler<H> {
Ok(Some((nread, src))) => {
debug!("Handling request from {}", src);
let response_q = self.tx_sender.clone();
self.thread_pool.execute(move || {
match Packet::from_bytes(&buf[..nread]) {
Ok(packet) => {
// Pre-generate a response
let auto_resp = auto_response(&packet);
// Dispatch user handler, if there is a response packet
// send the reply via the TX thread
match coap_handler.handle(packet, auto_resp) {
let rqst = CoAPRequest::from_packet(packet, &src);
match coap_handler.handle(rqst) {
Some(response) => {
debug!("Response: {:?}", response);
response_q.send(CoAPResponse {
response_q.send(QueuedResponse {
address: src,
response: response,
})
......@@ -224,7 +226,7 @@ fn transmit_handler(tx_recv: RxQueue, tx_only: UdpSocket) {
loop {
match tx_recv.recv() {
Ok(q_res) => {
match q_res.response.to_bytes() {
match q_res.response.message.to_bytes() {
Ok(bytes) => {
let _ = tx_only.send_to(&bytes[..], &q_res.address);
}
......@@ -252,18 +254,22 @@ impl Drop for CoAPServer {
#[cfg(test)]
mod test {
use super::*;
use packet::{Packet, PacketType, OptionType};
use client::CoAPClient;
use message::header;
use message::IsMessage;
use message::packet::CoAPOption;
use message::request::CoAPRequest;
use message::response::CoAPResponse;
use super::*;
fn request_handler(req: Packet, response: Option<Packet>) -> Option<Packet> {
let uri_path_list = req.get_option(OptionType::UriPath).unwrap();
fn request_handler(req: CoAPRequest) -> Option<CoAPResponse> {
let uri_path_list = req.get_option(CoAPOption::UriPath).unwrap();
assert!(uri_path_list.len() == 1);
match response {
Some(mut packet) => {
packet.set_payload(uri_path_list.front().unwrap().clone());
Some(packet)
match req.response {
Some(mut response) => {
response.set_payload(uri_path_list.front().unwrap().clone());
Some(response)
}
_ => None,
}
......@@ -275,17 +281,17 @@ mod test {
server.handle(request_handler).unwrap();
let client = CoAPClient::new("127.0.0.1:5683").unwrap();
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.set_code("0.01");
packet.header.set_message_id(1);
packet.set_token(vec![0x51, 0x55, 0x77, 0xE8]);
packet.add_option(OptionType::UriPath, b"test-echo".to_vec());
client.send(&packet).unwrap();
let mut request = CoAPRequest::new();
request.set_version(1);
request.set_type(header::MessageType::Confirmable);
request.set_code("0.01");
request.set_message_id(1);
request.set_token(vec![0x51, 0x55, 0x77, 0xE8]);
request.add_option(CoAPOption::UriPath, b"test-echo".to_vec());
client.send(&request).unwrap();
let recv_packet = client.receive().unwrap();
assert_eq!(recv_packet.payload, b"test-echo".to_vec());
assert_eq!(recv_packet.message.payload, b"test-echo".to_vec());
}
#[test]
......@@ -294,15 +300,15 @@ mod test {
server.handle(request_handler).unwrap();
let client = CoAPClient::new("127.0.0.1:5683").unwrap();
let mut packet = Packet::new();
packet.header.set_version(1);
packet.header.set_type(PacketType::Confirmable);
packet.header.set_code("0.01");
packet.header.set_message_id(1);
packet.add_option(OptionType::UriPath, b"test-echo".to_vec());
let mut packet = CoAPRequest::new();
packet.set_version(1);
packet.set_type(header::MessageType::Confirmable);
packet.set_code("0.01");
packet.set_message_id(1);
packet.add_option(CoAPOption::UriPath, b"test-echo".to_vec());
client.send(&packet).unwrap();
let recv_packet = client.receive().unwrap();
assert_eq!(recv_packet.payload, b"test-echo".to_vec());
assert_eq!(recv_packet.message.payload, b"test-echo".to_vec());
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment