#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
extern crate byteorder;
#[macro_use]
extern crate log;
extern crate nalgebra;
extern crate semver;
mod force_plate;
mod frame;
mod marker;
pub mod model;
mod rigid_body;
mod sender;
mod skeleton;
mod messages;
use byteorder::{ReadBytesExt, LittleEndian};
use semver::Version;
use std::error::Error as StdError;
use std::fmt;
use std::io::BufRead;
use std::result;
pub use force_plate::ForcePlate;
pub use frame::FrameOfData;
pub use marker::{Marker, LabeledMarker};
pub use messages::{NatNetResponse, NatNetRequest};
pub use rigid_body::RigidBody;
pub use sender::Sender;
pub use skeleton::Skeleton;
pub type Result<T> = result::Result<T, ParseError>;
#[derive(Debug)]
pub enum ParseError {
UnknownError,
UnknownResponse(u16),
IO(std::io::Error),
StringError,
NotEnoughBytes,
}
#[derive(Clone, PartialEq, Debug, PartialOrd)]
pub enum NatNetMsgType {
Ping = 0,
PingResponse = 1,
Request = 2,
Response = 3,
RequestModelDef = 4,
ModelDef = 5,
RequestFrameOfData = 6,
FrameOfData = 7,
MessageString = 8,
UnrecognizedRequest = 100,
}
#[derive(Clone, Debug)]
pub struct NatNet {
ver: Version,
}
impl NatNet {
pub fn new<V: Into<Version>>(ver: V) -> NatNet {
NatNet { ver: ver.into() }
}
pub fn unpack_with<B: BufRead>(ver: &Version, bytes: &mut B) -> Result<NatNetResponse> {
let msg_id = try!(bytes.read_u16::<LittleEndian>());
let num_bytes = try!(bytes.read_u16::<LittleEndian>());
NatNet::unpack_rest(msg_id, num_bytes, ver, bytes)
}
fn unpack_rest<B: BufRead>(msg_id: u16,
num_bytes: u16,
ver: &Version,
bytes: &mut B)
-> Result<NatNetResponse> {
debug!("Unpacking `NatNet` message with type: {}, size: {}",
msg_id,
num_bytes);
match msg_id {
_ if msg_id == NatNetMsgType::FrameOfData as u16 => {
Ok(NatNetResponse::FrameOfData(try!(FrameOfData::unpack(ver, bytes))))
}
_ if msg_id == NatNetMsgType::ModelDef as u16 => {
let num_models = try!(bytes.read_i32::<LittleEndian>());
let mut models = Vec::with_capacity(num_models as usize);
for _ in 0..num_models {
models.push(try!(model::DataSet::unpack(ver, bytes)));
}
Ok(NatNetResponse::ModelDef(models))
}
_ if msg_id == NatNetMsgType::PingResponse as u16 => {
Ok(NatNetResponse::Ping(try!(Sender::unpack(ver, bytes))))
}
_ if msg_id == NatNetMsgType::MessageString as u16 => {
Ok(NatNetResponse::MessageString(try!(read_cstring(bytes))))
}
_ if msg_id == NatNetMsgType::Response as u16 && num_bytes == 4 => {
Ok(NatNetResponse::Response(try!(bytes.read_i32::<LittleEndian>())))
}
_ if msg_id == NatNetMsgType::Response as u16 => {
Ok(NatNetResponse::ResponseString(try!(read_cstring(bytes))))
}
_ if msg_id == NatNetMsgType::UnrecognizedRequest as u16 => {
Ok(NatNetResponse::UnrecognizedRequest)
}
_ => Err(ParseError::UnknownResponse(msg_id)),
}
}
pub fn unpack_type_with<B: BufRead>(t: NatNetMsgType,
ver: &Version,
bytes: &mut B)
-> Option<Result<NatNetResponse>> {
let msg_id = bytes.read_u16::<LittleEndian>();
let num_bytes = bytes.read_u16::<LittleEndian>();
trace!("Trying to unpack {:?}", t);
if let Ok(msg_id) = msg_id {
if let Ok(num_bytes) = num_bytes {
if msg_id == t as u16 {
trace!("Correct message found");
return Some(NatNet::unpack_rest(msg_id, num_bytes, ver, bytes));
}
}
}
None
}
pub fn unpack<B: BufRead>(&self, bytes: &mut B) -> Result<NatNetResponse> {
NatNet::unpack_with(&self.ver, bytes)
}
pub fn unpack_type<B: BufRead>(&self,
t: NatNetMsgType,
bytes: &mut B)
-> Option<Result<NatNetResponse>> {
NatNet::unpack_type_with(t, &self.ver, bytes)
}
}
trait Unpack<T> {
fn unpack<B: BufRead>(ver: &Version, bytes: &mut B) -> Result<T>;
}
impl From<std::io::Error> for ParseError {
fn from(err: std::io::Error) -> ParseError {
match err.kind() {
std::io::ErrorKind::UnexpectedEof => ParseError::NotEnoughBytes,
_ => ParseError::IO(err),
}
}
}
impl From<std::ffi::NulError> for ParseError {
fn from(_: std::ffi::NulError) -> ParseError {
ParseError::StringError
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ParseError::UnknownError => {
write!(f,
"An unknown error occurred (most likely caused by version mismatch)")
}
ParseError::UnknownResponse(ref resp) => {
write!(f, "Got an unknown message from NatNet with ID: {}", resp)
}
ParseError::IO(ref err) => write!(f, "IO error: {}", err),
ParseError::StringError => write!(f, "Error parsing C-String from NatNet"),
ParseError::NotEnoughBytes => {
write!(f, "Not enough bytes in source to parse complete message")
}
}
}
}
impl StdError for ParseError {
fn description(&self) -> &str {
match *self {
ParseError::UnknownError => "Unknown error occurred",
ParseError::UnknownResponse(_) => "Unknown message ID",
ParseError::IO(ref err) => err.description(),
ParseError::StringError => "Problem parsing C-String from NatNet",
ParseError::NotEnoughBytes => "Not enough bytes in source",
}
}
fn cause(&self) -> Option<&StdError> {
match *self {
ParseError::IO(ref err) => Some(err),
_ => None,
}
}
}
fn read_cstring<B: BufRead>(bytes: &mut B) -> Result<String> {
let mut str_buf = Vec::with_capacity(256);
try!(bytes.read_until(b'\0', &mut str_buf));
str_buf.pop();
match try!(std::ffi::CString::new(str_buf)).into_string() {
Ok(s) => Ok(s),
Err(err) => {
let reason = err.utf8_error();
error!("Could not convert C-String '{:?}' into String, reason: {:?}",
err.into_cstring(),
reason);
Err(ParseError::StringError)
}
}
}