c2-utopia/lib/OSC/OSCMessage.cpp

792 lines
22 KiB
C++

/*
Written by Yotam Mann, The Center for New Music and Audio Technologies,
University of California, Berkeley. Copyright (c) 2012, The Regents of
the University of California (Regents).
Permission to use, copy, modify, distribute, and distribute modified versions
of this software and its documentation without fee and without a signed
licensing agreement, is hereby granted, provided that the above copyright
notice, this paragraph and the following two paragraphs appear in all copies,
modifications, and distributions.
IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING
OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED
HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
For bug reports and feature requests please email me at yotam@cnmat.berkeley.edu
*/
#include "OSCMessage.h"
#include "OSCMatch.h"
#include "OSCTiming.h"
extern osctime_t zerotime;
/*=============================================================================
CONSTRUCTORS / DESTRUCTOR
=============================================================================*/
//constructor with address
OSCMessage::OSCMessage(const char * _address){
setupMessage();
setAddress(_address);
}
//constructor with nothing
//just a placeholder since the message is invalid
OSCMessage::OSCMessage(){
setupMessage();
error = INVALID_OSC;
}
//variable length constructor
//for example OSCMessage msg("/address", "isf", 1, "two", 3.0);
/*
OSCMessage::OSCMessage(const char * _address, char * types, ... ){
setupMessage(_address);
}
*/
//sets up a new message
void OSCMessage::setupMessage(){
address = NULL;
//setup the attributes
dataCount = 0;
error = OSC_OK;
//setup the space for data
data = NULL;
//setup for filling the message
incomingBuffer = NULL;
incomingBufferSize = 0;
incomingBufferFree = 0;
clearIncomingBuffer();
//set the decode state
decodeState = STANDBY;
}
//DESTRUCTOR
OSCMessage::~OSCMessage(){
//free everything that needs to be freed
//free the address
free(address);
//free the data
empty();
//free the filling buffer
free(incomingBuffer);
}
OSCMessage& OSCMessage::empty(){
error = OSC_OK;
//free each of the data in the array
for (int i = 0; i < dataCount; i++){
const auto datum = getOSCData(i);
//explicitly destruct the data
//datum->~OSCData();
delete datum;
}
//and free the array
free(data);
data = NULL;
dataCount = 0;
decodeState = STANDBY;
clearIncomingBuffer();
return *this;
}
//COPY
OSCMessage::OSCMessage(OSCMessage * msg){
//start with a message with the same address
setupMessage();
setAddress(msg->address);
//add each of the data to the other message
for (int i = 0; i < msg->dataCount; i++){
add(msg->data[i]);
}
}
/*=============================================================================
GETTING DATA
=============================================================================*/
OSCData * OSCMessage::getOSCData(int position){
if (position < dataCount){
const auto datum = data[position];
return datum;
} else {
error = INDEX_OUT_OF_BOUNDS;
return nullptr;
}
}
int32_t OSCMessage::getInt(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getInt();
} else {
return 0;
}
}
osctime_t OSCMessage::getTime(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getTime();
} else {
return zerotime;
}
}
float OSCMessage::getFloat(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getFloat();
} else {
return 0.0f;
}
}
double OSCMessage::getDouble(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getDouble();
} else {
return 0.0;
}
}
bool OSCMessage::getBoolean(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getBoolean();
} else {
return false;
}
}
int OSCMessage::getString(int position, char * buffer){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getString(buffer, datum->bytes);
} else {
return -1;
}
}
int OSCMessage::getString(int position, char * buffer, int bufferSize){
const auto datum = getOSCData(position);
if (!hasError()){
//the number of bytes to copy is the smaller between the buffer size and the datum's byte length
int copyBytes = bufferSize < datum->bytes? bufferSize : datum->bytes;
return datum->getString(buffer, copyBytes);
} else {
return -1;
}
}
int OSCMessage::getString(int position, char * buffer, int bufferSize, int offset, int size){
const auto datum = getOSCData(position);
if (!hasError()){
//the number of bytes to copy is the smaller between the buffer size and the datum's byte length
int copyBytes = bufferSize < datum->bytes? bufferSize : datum->bytes;
return datum->getString(buffer, copyBytes, offset, size);
} else {
return -1;
}
}
int OSCMessage::getBlob(int position, uint8_t * buffer){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getBlob(buffer);
} else {
return -1;
}
}
int OSCMessage::getBlob(int position, uint8_t * buffer, int bufferSize){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getBlob(buffer, bufferSize);
} else {
return -1;
}
}
int OSCMessage::getBlob(int position, uint8_t * buffer, int bufferSize, int offset, int size){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getBlob(buffer, bufferSize, offset, size);
} else {
return -1;
}
}
const uint8_t* OSCMessage::getBlob(int position) {
const auto datum = getOSCData(position);
if(!hasError()) {
return datum->getBlob();
} else {
return nullptr;
}
}
uint32_t OSCMessage::getBlobLength(int position)
{
const auto datum = getOSCData(position);
if (!hasError()){
return datum->getBlobLength();
} else {
return 0;
}
}
char OSCMessage::getType(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->type;
} else {
return '\0';
}
}
int OSCMessage::getDataLength(int position){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->bytes;
} else {
return 0;
}
}
/*=============================================================================
TESTING DATA
=============================================================================*/
bool OSCMessage::testType(int position, char type){
const auto datum = getOSCData(position);
if (!hasError()){
return datum->type == type;
} else {
return false;
}
}
bool OSCMessage::isInt(int position){
return testType(position, 'i');
}
bool OSCMessage::isTime(int position){
return testType(position, 't');
}
bool OSCMessage::isFloat(int position){
return testType(position, 'f');
}
bool OSCMessage::isBlob(int position){
return testType(position, 'b');
}
bool OSCMessage::isChar(int position){
return testType(position, 'c');
}
bool OSCMessage::isString(int position){
return testType(position, 's');
}
bool OSCMessage::isDouble(int position){
return testType(position, 'd');
}
bool OSCMessage::isBoolean(int position){
return testType(position, 'T') || testType(position, 'F');
}
/*=============================================================================
PATTERN MATCHING
=============================================================================*/
int OSCMessage::match(const char * pattern, int addr_offset){
int pattern_offset;
int address_offset;
int ret = osc_match(address + addr_offset, pattern, &pattern_offset, &address_offset);
char * next = (char *) (address + addr_offset + pattern_offset);
if (ret==3){
return pattern_offset;
} else if (pattern_offset > 0 && *next == '/'){
return pattern_offset;
} else {
return 0;
}
}
bool OSCMessage::fullMatch( const char * pattern, int addr_offset){
int pattern_offset;
int address_offset;
int ret = osc_match(address + addr_offset, pattern, &pattern_offset, &address_offset );
return (ret==3);
}
bool OSCMessage::dispatch(const char * pattern, void (*callback)(OSCMessage &), int addr_offset){
if (fullMatch(pattern, addr_offset)){
callback(*this);
return true;
} else {
return false;
}
}
bool OSCMessage::route(const char * pattern, void (*callback)(OSCMessage &, int), int initial_offset){
int match_offset = match(pattern, initial_offset);
if (match_offset>0){
callback(*this, match_offset + initial_offset);
return true;
} else {
return false;
}
}
/*=============================================================================
ADDRESS
=============================================================================*/
int OSCMessage::getAddress(char * buffer, int offset){
int result = strlen(address);
if (result > offset)
strcpy(buffer, address+offset);
else
*buffer = 0;
return result - offset; // could be negative!
}
int OSCMessage::getAddress(char * buffer, int offset, int len){
int result = strlen(address);
if (result > offset)
{
strncpy(buffer, address+offset, len); // N.B. NOT guaranteed to null-terminate! So...
buffer[len-1] = 0; // ...prevent strlen() blowing up
}
else
*buffer = 0;
return strlen(buffer);
}
const char* OSCMessage::getAddress(){
return address;
}
int OSCMessage::getAddressLength(int offset)
{
int result = (int) strlen(address) - offset;
if (result < 0) // offset past end!
result = 0; // do the best we can
return result;
}
OSCMessage& OSCMessage::setAddress(const char * _address){
//free the previous address
free(address); // are we sure address was allocated?
//copy the address
char * addressMemory = (char *) malloc( (strlen(_address) + 1) * sizeof(char) );
if (addressMemory == NULL){
error = ALLOCFAILED;
address = NULL;
} else {
strcpy(addressMemory, _address);
address = addressMemory;
}
return *this;
}
/*=============================================================================
SIZE
=============================================================================*/
#ifdef SLOWpadcalculation
int OSCMessage::padSize(int _bytes){
int space = (_bytes + 3) / 4;
space *= 4;
return space - _bytes;
}
#else
static inline int padSize(int bytes) { return (4- (bytes&03))&3; }
#endif
//returns the number of OSCData in the OSCMessage
int OSCMessage::size(){
return dataCount;
}
int OSCMessage::bytes(){
int messageSize = 0;
//send the address
int addrLen = strlen(address) + 1;
messageSize += addrLen;
//padding amount
int addrPad = padSize(addrLen);
messageSize += addrPad;
//add the comma separator
messageSize += 1;
//add the types
messageSize += dataCount;
//pad the types
int typePad = padSize(dataCount + 1); //for the comma
if (typePad == 0){
typePad = 4; // to make sure the type string is null terminated
}
messageSize+=typePad;
//then the data
for (int i = 0; i < dataCount; i++){
const auto datum = getOSCData(i);
messageSize+=datum->bytes;
messageSize += padSize(datum->bytes);
}
return messageSize;
}
/*=============================================================================
ERROR HANDLING
=============================================================================*/
bool OSCMessage::hasError(){
if(error != OSC_OK) return true;
//test each of the data
for (int i = 0; i < dataCount; i++){
if(getOSCData(i)->error) return true;
}
return false;
}
OSCErrorCode OSCMessage::getError(){
return error;
}
/*=============================================================================
SENDING
=============================================================================*/
OSCMessage& OSCMessage::send(Print &p){
//don't send a message with errors
if (hasError()){
return *this;
}
uint8_t nullChar = '\0';
//send the address
int addrLen = strlen(address) + 1;
//padding amount
int addrPad = padSize(addrLen);
//write it to the stream
p.write((uint8_t *) address, addrLen);
//add the padding
while(addrPad--){
p.write(nullChar);
}
//add the comma separator
p.write((uint8_t) ',');
//add the types
#ifdef PAULSSUGGESTION
// Paul suggested buffering on the stack
// to improve performance. The problem is this could exhaust the stack
// for long complex messages
{
uint8_t typstr[dataCount];
for (int i = 0; i < dataCount; i++){
typstr[i] = getType(i);
}
p.write(typstr,dataCount);
}
#else
for (int i = 0; i < dataCount; i++){
p.write((uint8_t) getType(i));
}
#endif
//pad the types
int typePad = padSize(dataCount + 1); // 1 is for the comma
if (typePad == 0){
typePad = 4; // This is because the type string has to be null terminated
}
while(typePad--){
p.write(nullChar);
}
//write the data
for (int i = 0; i < dataCount; i++){
const auto datum = getOSCData(i);
if ((datum->type == 's') || (datum->type == 'b')){
p.write(datum->data.b, datum->bytes);
int dataPad = padSize(datum->bytes);
while(dataPad--){
p.write(nullChar);
}
} else if (datum->type == 'd'){
double d = BigEndian(datum->data.d);
uint8_t * ptr = (uint8_t *) &d;
p.write(ptr, 8);
} else if (datum->type == 't'){
osctime_t time = datum->data.time;
uint32_t d = BigEndian(time.seconds);
uint8_t * ptr = (uint8_t *) &d;
p.write(ptr, 4);
d = BigEndian(time.fractionofseconds);
ptr = (uint8_t *) &d;
p.write(ptr, 4);
} else if (datum->type == 'T' || datum->type == 'F')
{ }
else { // float or int
uint32_t i = BigEndian(datum->data.i);
uint8_t * ptr = (uint8_t *) &i;
p.write(ptr, datum->bytes);
}
}
return *this;
}
/*=============================================================================
FILLING
=============================================================================*/
OSCMessage& OSCMessage::fill(uint8_t incomingByte){
decode(incomingByte);
return *this;
}
OSCMessage& OSCMessage::fill(uint8_t * incomingBytes, int length){
while (length--){
decode(*incomingBytes++);
}
return *this;
}
/*=============================================================================
DECODING
=============================================================================*/
void OSCMessage::decodeAddress(){
setAddress((char *) incomingBuffer);
//change the error from invalid message
error = OSC_OK;
clearIncomingBuffer();
}
void OSCMessage::decodeType(uint8_t incomingByte){
char type = incomingByte;
add(type);
}
void OSCMessage::decodeData(uint8_t incomingByte){
//get the first OSCData to re-set
for (int i = 0; i < dataCount; i++){
const auto datum = getOSCData(i);
if (datum->error == INVALID_OSC){
//set the contents of datum with the data received
switch (datum->type){
case 'i':
if (incomingBufferSize == 4){
//parse the buffer as an int
union {
int32_t i;
uint8_t b[4];
} u;
memcpy(u.b, incomingBuffer, 4);
int32_t dataVal = BigEndian(u.i);
set(i, dataVal);
clearIncomingBuffer();
}
break;
case 'f':
if (incomingBufferSize == 4){
//parse the buffer as a float
union {
float f;
uint8_t b[4];
} u;
memcpy(u.b, incomingBuffer, 4);
float dataVal = BigEndian(u.f);
set(i, dataVal);
clearIncomingBuffer();
}
break;
case 'd':
if (incomingBufferSize == 8){
//parse the buffer as a double
union {
double d;
uint8_t b[8];
} u;
memcpy(u.b, incomingBuffer, 8);
double dataVal = BigEndian(u.d);
set(i, dataVal);
clearIncomingBuffer();
}
break;
case 't':
if (incomingBufferSize == 8){
//parse the buffer as a timetag
union {
osctime_t t;
uint8_t b[8];
} u;
memcpy(u.b, incomingBuffer, 8);
u.t.seconds = BigEndian(u.t.seconds);
u.t.fractionofseconds = BigEndian(u.t.fractionofseconds);
set(i, u.t);
clearIncomingBuffer();
}
break;
case 's':
if (incomingByte == 0){
char * str = (char *) incomingBuffer;
set(i, str);
clearIncomingBuffer();
decodeState = DATA_PADDING;
}
break;
case 'b':
if (incomingBufferSize > 4){
//compute the expected blob size
union {
uint32_t i;
uint8_t b[4];
} u;
memcpy(u.b, incomingBuffer, 4);
uint32_t blobLength = BigEndian(u.i);
if (incomingBufferSize == (int)(blobLength + 4)){
set(i, incomingBuffer + 4, blobLength);
clearIncomingBuffer();
decodeState = DATA_PADDING;
}
}
break;
}
//break out of the for loop once we've selected the first invalid message
break;
}
}
}
//does not validate the incoming OSC for correctness
void OSCMessage::decode(uint8_t incomingByte){
addToIncomingBuffer(incomingByte);
switch (decodeState){
case STANDBY:
if (incomingByte == '/'){
decodeState = ADDRESS;
}
break;
case ADDRESS:
if (incomingByte == 0){
//end of the address
//decode the address
decodeAddress();
//next state
decodeState = ADDRESS_PADDING;
}
break;
case ADDRESS_PADDING:
//it does not count the padding
if (incomingByte==','){
//next state
decodeState = TYPES;
clearIncomingBuffer();
}
break;
case TYPES:
if (incomingByte != 0){
//next state
decodeType(incomingByte);
} else {
decodeState = TYPES_PADDING;
}
//FALL THROUGH to test if it should go to the data state
case TYPES_PADDING: {
//compute the padding size for the types
//to determine the start of the data section
int typePad = padSize(dataCount + 1); // 1 is the comma
if (typePad == 0){
typePad = 4; // to make sure it will be null terminated
}
if (incomingBufferSize == (typePad + dataCount)){
clearIncomingBuffer();
decodeState = DATA;
}
}
break;
case DATA:
decodeData(incomingByte);
break;
case DATA_PADDING:{
//get the last valid data
for (int i = dataCount - 1; i >= 0; i--){
const auto datum = getOSCData(i);
if (datum->error == OSC_OK){
//compute the padding size for the data
int dataPad = padSize(datum->bytes);
// if there is no padding required, switch back to DATA, and don't clear the incomingBuffer because it holds next data
if (dataPad == 0){
decodeState = DATA;
}
else if (incomingBufferSize == dataPad){
clearIncomingBuffer();
decodeState = DATA;
}
break;
}
}
}
break;
case DONE:
break; // TODO: is this correct? - was missing from original code, it did this by default
}
}
/*=============================================================================
INCOMING BUFFER MANAGEMENT
=============================================================================*/
#define OSCPREALLOCATEIZE 16
void OSCMessage::addToIncomingBuffer(uint8_t incomingByte){
//realloc some space for the new byte and stick it on the end
if(incomingBufferFree>0)
{
incomingBuffer[incomingBufferSize++] = incomingByte;
incomingBufferFree--;
}
else
{
incomingBuffer = (uint8_t *) realloc ( incomingBuffer, incomingBufferSize + 1 + OSCPREALLOCATEIZE);
if (incomingBuffer != NULL){
incomingBuffer[incomingBufferSize++] = incomingByte;
incomingBufferFree = OSCPREALLOCATEIZE;
} else {
error = ALLOCFAILED;
}
}
}
void OSCMessage::clearIncomingBuffer(){
incomingBuffer = (uint8_t *) realloc ( incomingBuffer, OSCPREALLOCATEIZE);
if (incomingBuffer != NULL){
incomingBufferFree = OSCPREALLOCATEIZE;
} else {
error = ALLOCFAILED;
incomingBuffer = NULL;
}
incomingBufferSize = 0;
}