natnet_decode/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
//! Decode `NatNet` messages from raw bytes.
//!
//! [`NatNet`](http://www.optitrack.com/downloads/developer-tools.html#natnet-sdk)
//! is the software solution supporting [`OptiTrack`](http://www.optitrack.com/)
//! many offerings. Since motion tracking data is multicast it can be decoded
//! in any language and this crate is a Rust (unofficial!) implementation.
//!
//! # Note on `NatNet` versions
//! The raw byte interface in `NatNet` has been through many revisions. Many
//! data fields are optional going backwards. Because of this it is assumed
//! that users have at least version `>=2.5.0`, fields added after this is
//! marked optional so that this crate can support several versions. It is
//! currently up to the user to decide which version to use.
//!
//! # Example
//! ```rust,ignore
//! use natnet_decode::NatNet;
//! use semver::Version;
//!
//! let mut data = Vec::new();
//! // Fill data here
//! // ...
//!
//! // We can then decode this:
//! let parsed = NatNet::unpack_with(&Version::parse("2.5.0").unpack(), &mut data.as_ref());
//! // Do stuff with parsed
//! println!("{:?}", parsed);
//! ```
//!
//! # Acknowledgement
//! This crate is heavily inspired by
//! [python-optirx](https://bitbucket.org/astanin/python-optirx/overview) and
//! test data is borrowed with permission.
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;
// External imports
use byteorder::{ReadBytesExt, LittleEndian};
use semver::Version;
// Imports from standard library
use std::error::Error as StdError;
use std::fmt;
use std::io::BufRead;
use std::result;
// Local imports
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;
/// A result type for errors
pub type Result<T> = result::Result<T, ParseError>;
/// Errors caused during message parsing
#[derive(Debug)]
pub enum ParseError {
/// Something unexpected happened
///
/// This error is returned when End-Of-Data marker is not
/// as expected, this most likely mean that all data parsed
/// is garbage and that there is a version mismatch.
UnknownError,
/// Unknown message received from Motive
///
/// The number included is the message ID given by `NatNet`.
UnknownResponse(u16),
/// Problem reading bytes from input source
///
/// This error is caused by an IO error on the given input source
/// the cause of the IO error is returned so that users can
/// inspect the cause.
IO(std::io::Error),
/// Problem converting C-String in Motive
///
/// There was a problem converting the bytes that Motive considers
/// a string into what Rust considers a String.
StringError,
/// There were not enough bytes in the source to parse a complete message
///
/// This is most likely caused by a mismatch in versions.
NotEnoughBytes,
}
/// C-like Enum representing the different possible messages coming from `NatNet`
/// Updated for `2.10.0`
#[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,
}
/// Parser for `NatNet` data
///
/// This is the main entry point to unpack/parse `NatNet` data.
#[derive(Clone, Debug)]
pub struct NatNet {
ver: Version,
}
impl NatNet {
/// Create a new `NatNet` parser with the given version
///
/// This will create a new parser that utilizes the given version
/// for subsequent `unpack` calls
pub fn new<V: Into<Version>>(ver: V) -> NatNet {
NatNet { ver: ver.into() }
}
/// Unpack a message from `NatNet` using a specified version
///
/// This will try to unpack a message coming from a NatNet application
/// assuming the message uses the given version
pub fn unpack_with<B: BufRead>(ver: &Version, bytes: &mut B) -> Result<NatNetResponse> {
// First 4 bytes contains `msg_id` and number of bytes in message
// according to `PacketClient.cpp` line 609:615
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 the message is a command response it can either be a
// i32 response code or a response string, line: 147
_ 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)),
}
}
/// Unpack only `NatNetMsgType` messages
///
/// This method unpacks only messages of the requested type. The function
/// will consume the header of any `NatNet` message to check if it is the
/// correct message and unpack only if it is. This method can be useful when
/// needing to unpack only sender messages if `NatNet` version is unknown.
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
}
/// Unpack a message from `NatNet`
pub fn unpack<B: BufRead>(&self, bytes: &mut B) -> Result<NatNetResponse> {
NatNet::unpack_with(&self.ver, bytes)
}
/// Unpack only `NatNetMsgType` messages
///
/// This method unpacks only messages of the requested type. The function
/// will consume the header of any `NatNet` message to check if it is the
/// correct message and unpack only if it is. This method can be useful when
/// needing to unpack only sender messages if `NatNet` version is unknown.
pub fn unpack_type<B: BufRead>(&self,
t: NatNetMsgType,
bytes: &mut B)
-> Option<Result<NatNetResponse>> {
NatNet::unpack_type_with(t, &self.ver, bytes)
}
}
// Private trait used to unpack underlying data
trait Unpack<T> {
/// Unpack the type `T` from the `BufRead` source
fn unpack<B: BufRead>(ver: &Version, bytes: &mut B) -> Result<T>;
}
// From io error for ParseError
impl From<std::io::Error> for ParseError {
/// Convert an IO error into a `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 {
/// Convert a `std::ffi::NulError` into a `ParseError::StringError`
fn from(_: std::ffi::NulError) -> ParseError {
// FIXME: `StringError` should contain the cause
ParseError::StringError
}
}
impl fmt::Display for ParseError {
/// Format `ParseError` in human readable fashion
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,
}
}
}
/// Helper function to read a C-String from raw bytes
fn read_cstring<B: BufRead>(bytes: &mut B) -> Result<String> {
// Maximum size for a String is 256, ref: line 631
let mut str_buf = Vec::with_capacity(256);
try!(bytes.read_until(b'\0', &mut str_buf));
// Remove null byte from end of `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);
// FIXME: Return more descriptive error here
Err(ParseError::StringError)
}
}
}