/* 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; }