extern crate tokio_io;
extern crate bytes;
use std::io;
use tokio_io::codec::{Encoder, Decoder};
use bytes::{BytesMut, BufMut, Buf, IntoBuf, BigEndian};
pub const DEFAULT_OPC_PORT: usize = 7890;
const MAX_MESSAGE_SIZE: usize = 0xffff;
const SYS_EXCLUSIVE: u8 = 0xff;
const SET_PIXEL_COLORS: u8 = 0x00;
const BROADCAST_CHANNEL: u8 = 0;
#[derive (Clone, Debug, PartialEq)]
pub enum Command {
SetPixelColors {
pixels: Vec<[u8; 3]>,
},
SystemExclusive {
id: [u8; 2],
data: Vec<u8>,
},
}
#[derive (Clone, Debug, PartialEq)]
pub struct Message {
pub channel: u8,
pub command: Command,
}
impl Message {
pub fn from_pixels(ch: u8, pixels: &[[u8; 3]]) -> Message {
Message {
channel: ch,
command: Command::SetPixelColors { pixels: pixels.to_owned() },
}
}
pub fn from_data(ch: u8, id: &[u8; 2], data: &[u8]) -> Message {
Message {
channel: ch,
command: Command::SystemExclusive {
id: id.to_owned(),
data: data.to_owned(),
},
}
}
pub fn len(&self) -> usize {
match self.command {
Command::SetPixelColors { ref pixels } => pixels.len() * 3,
Command::SystemExclusive { id: _, ref data } => data.len() + 2,
}
}
pub fn is_valid(&self) -> bool {
self.len() <= MAX_MESSAGE_SIZE
}
pub fn is_broadcast(&self) -> bool {
self.channel == BROADCAST_CHANNEL
}
}
pub struct OpcCodec;
impl Decoder for OpcCodec {
type Item = Message;
type Error = io::Error;
fn decode(&mut self, src: &mut BytesMut) -> io::Result<Option<Self::Item>> {
let (msg, length) = {
let mut src = src.clone();
if src.len() < 4 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid Message Command"));
};
let mut buf = src.split_to(4).into_buf();
let (channel, command) = (buf.get_u8(), buf.get_u8());
let length = buf.get_u16::<BigEndian>() as usize;
if src.len() < length {
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid Message Command"));
}
let mut buf = src.split_to(length).into_buf();
let msg = match command {
SET_PIXEL_COLORS => {
let pixels: Vec<_> = buf.bytes()[..length - (length % 3)]
.chunks(3)
.map(|chunk| [chunk[0], chunk[1], chunk[2]])
.collect();
Message {
channel: channel,
command: Command::SetPixelColors { pixels: pixels },
}
}
SYS_EXCLUSIVE => {
Message {
channel: channel,
command: Command::SystemExclusive {
id: [buf.get_u8(), buf.get_u8()],
data: buf.collect(),
},
}
}
_ => {
return Err(io::Error::new(io::ErrorKind::InvalidData,
"Invalid Message Command"))
}
};
(msg, length + 4)
};
src.split_to(length);
Ok(Some(msg))
}
}
impl Encoder for OpcCodec {
type Item = Message;
type Error = io::Error;
fn encode(&mut self, msg: Self::Item, dst: &mut BytesMut) -> io::Result<()> {
let ser_len = msg.len();
dst.reserve(4 + ser_len);
match msg.command {
Command::SetPixelColors { pixels } => {
dst.put_slice(&[msg.channel, SET_PIXEL_COLORS]);
dst.put_u16::<BigEndian>(ser_len as u16);
for pixel in pixels {
dst.put_slice(&pixel);
}
}
Command::SystemExclusive { id, data } => {
dst.put_slice(&[msg.channel, SYS_EXCLUSIVE]);
dst.put_u16::<BigEndian>(ser_len as u16);
dst.put_slice(&id);
dst.put_slice(&data);
}
}
Ok(())
}
}
#[test]
fn should_roundtrip_pixel_command() {
let mut codec = OpcCodec;
let mut buf = BytesMut::new();
let test_msg = Message {
channel: 4,
command: Command::SetPixelColors { pixels: vec![[9; 3]; 10] },
};
assert!(codec.encode(test_msg.clone(), &mut buf).is_ok());
let recv_msg = codec.decode(&mut buf.into()).unwrap().unwrap();
assert_eq!(test_msg, recv_msg);
}
#[test]
fn server_roundtrip_system_command() {
let mut codec = OpcCodec;
let mut buf = BytesMut::new();
let test_msg = Message {
channel: 4,
command: Command::SystemExclusive {
id: [0; 2],
data: vec![8; 10],
},
};
assert!(codec.encode(test_msg.clone(), &mut buf).is_ok());
let recv_msg = codec.decode(&mut buf.into()).unwrap().unwrap();
assert_eq!(test_msg, recv_msg);
}