diff --git a/CMakeLists.txt b/CMakeLists.txt index f320e74..6813ce1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,4 @@ + cmake_minimum_required (VERSION 2.6) PROJECT(emokit) @@ -22,10 +23,11 @@ ENDIF() MESSAGE(STATUS ${LIBS}) INCLUDE_DIRECTORIES(${HIDAPI_INCLUDE_DIR} ${Mcrypt_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/include) +ADD_SUBDIRECTORY(include/oscpack/) ADD_SUBDIRECTORY(src) ADD_SUBDIRECTORY(examples/emokitd) ADD_SUBDIRECTORY(examples/contact) -# ADD_SUBDIRECTORY(examples/emokit_osc) +ADD_SUBDIRECTORY(examples/emokit_osc) ########################################## ## PKGCONFIG file ### diff --git a/INSTALL b/INSTALL index 09b1ad3..47e0b1a 100644 --- a/INSTALL +++ b/INSTALL @@ -1,9 +1,11 @@ +Installation instructions for Linux (assuming ubuntu/debian packages) + CMAKE Download : http://www.cmake.org/cmake/resources/software.html -Go in the download folder and execute : +Go in the download folder and use the following commands : tar xzvf cmake-2.8.10.2.tar.gz -cd cmake-2.8.10.2.tar.gz +cd cmake-2.8.10.2.tar.gz/ ./bootstrap make sudo make install @@ -14,10 +16,10 @@ First install the dependencies : sudo apt-get install libmcrypt-dev libmhash-dev git Download : http://sourceforge.net/projects/mcrypt/files/latest/download -Go in the download folder and execute : +Go in the download folder and use the following commands : tar xzvf mcrypt-2.6.8.tar.gz -cd mcrypt-2.6.8.tar.gz +cd mcrypt-2.6.8.tar.gz/ ./configure make make install @@ -29,10 +31,10 @@ sudo apt-get install libudev-dev libusb-1.0-0-dev libtool Download : https://github.com/signal11/hidapi/archive/master.zip IT WILL NOT WORK if you downloaded from: hidapi-0.7.0.zip from https://github.com/signal11/hidapi/downloads or the command git clone git://github.com/signal11/hidapi.git -Extract the zip file and use the following commands : +Go in the download folder and use the following commands : unzip hidapid-master.zip -cd hidapid-master +cd hidapid-master/ ./bootstrap ./configure make @@ -41,9 +43,10 @@ sudo make install EMOKIT Download https://github.com/openyou/emokit/archive/master.zip +Go in the download folder and use the following commands : unzip emokit-master.zip -cd emokit-master +cd emokit-master/ cmake . make diff --git a/README.md b/README.md index 995b2ff..9b763ea 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ -Emokit -====== +Summary +======= + +Modified version by Gael Grosch for his bachelor project at EPFL (original can be found at openyou/emokit). The main contributions are some stability fixes and the adition of signal processing libraries. + + +Original info on Emokit +======================= Reverse engineering and original code written by diff --git a/examples/emokit_osc/CMakeLists.txt b/examples/emokit_osc/CMakeLists.txt index eced6b1..3170d31 100644 --- a/examples/emokit_osc/CMakeLists.txt +++ b/examples/emokit_osc/CMakeLists.txt @@ -1,11 +1,16 @@ - LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}) -FIND_PACKAGE(oscpack) -IF(NOT oscpack_FOUND) - MESSAGE(STATUS "oscpack not found, not building emokit osc server") -ENDIF() -INCLUDE_DIRECTORIES(${oscpack_INCLUDE_DIR}) +INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/include) ADD_EXECUTABLE(emokit_osc emokit_osc.cpp) -ADD_DEPENDENCIES(emokit_osc epoc) -TARGET_LINK_LIBRARIES(emokit_osc ${oscpack_LIBS} epoc ${LIBS}) \ No newline at end of file +ADD_DEPENDENCIES(emokit_osc emokit) +TARGET_LINK_LIBRARIES(emokit_osc oscpack emokit ${LIBS}) + +SET_TARGET_PROPERTIES(emokit_osc PROPERTIES +INSTALL_RPATH_USE_LINK_PATH TRUE +INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib +BUILD_WITH_INSTALL_RPATH TRUE ) + +INSTALL (TARGETS emokit_osc +RUNTIME DESTINATION bin +) + diff --git a/examples/emokit_osc/Findoscpack.cmake b/examples/emokit_osc/Findoscpack.cmake deleted file mode 100644 index 5f2ff3c..0000000 --- a/examples/emokit_osc/Findoscpack.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# - Find Mcrypt (a cross platform RPC lib/tool) -# This module defines -# Mcrypt_INCLUDE_DIR, where to find Mcrypt headers -# Mcrypt_LIBS, Mcrypt libraries -# Mcrypt_FOUND, If false, do not try to use Mcrypt - -find_path(oscpack_INCLUDE_DIR oscpack/osc/OscTypes.h PATHS - /usr/local/include - /opt/local/include - ) - -#find_library can't seem to find a 64-bit binary if the 32-bit isn't there - -set(oscpack_LIB_PATHS /usr/local/lib /opt/local/lib /usr/lib64) -find_library(oscpack_LIB NAMES oscpack PATHS ${oscpack_LIB_PATHS}) - -if (oscpack_LIB AND oscpack_INCLUDE_DIR) - set(oscpack_FOUND TRUE) - set(oscpack_LIBS ${oscpack_LIB}) -else () - set(oscpack_FOUND FALSE) -endif () - -if (oscpack_FOUND) - if (NOT oscpack_FIND_QUIETLY) - message(STATUS "Found oscpack: ${oscpack_LIBS}") - endif () -else () - if (oscpack_FIND_REQUIRED) - message(FATAL_ERROR "Could NOT find oscpack library.") - endif () - message(STATUS "oscpack NOT found.") -endif () - -mark_as_advanced( - oscpack_LIB - oscpack_INCLUDE_DIR - ) diff --git a/examples/emokit_osc/emokit_osc.cpp b/examples/emokit_osc/emokit_osc.cpp index 7da7184..039cf9f 100644 --- a/examples/emokit_osc/emokit_osc.cpp +++ b/examples/emokit_osc/emokit_osc.cpp @@ -7,87 +7,74 @@ #include #include #include -#include "oscpack/osc/OscOutboundPacketStream.h" -#include "oscpack/ip/UdpSocket.h" -#include "libepoc.h" +#include "../../include/oscpack/osc/OscOutboundPacketStream.h" +#include "../../include/oscpack/ip/UdpSocket.h" -#define ADDRESS "127.0.0.1" -#define PORT 9997 - -#define OUTPUT_BUFFER_SIZE 4096 - -void sigproc(int i) +extern "C" { - std::cout << "closing epoc and quitting" << std::endl; - exit(0); +#include "emokit/emokit.h" } -int main(int argc, char* argv[]) -{ - signal(SIGINT, sigproc); -#ifndef WIN32 - signal(SIGQUIT, sigproc); -#endif - UdpTransmitSocket transmitSocket( IpEndpointName( ADDRESS, PORT ) ); - - char buffer[OUTPUT_BUFFER_SIZE]; +#define ADDRESS "127.0.0.1" +#define PORT 9997 +#define OUTPUT_BUFFER_SIZE 4096 + +int main(int argc, char **argv) +{ + + emokit_device* d; + d = emokit_create(); - FILE *input; - FILE *output; - enum headset_type type; - - char raw_frame[32]; - struct epoc_frame frame; - epoc_device* d; - uint8_t data[32]; - if (argc < 2) + std::cout << "Current epoc devices connected " << emokit_get_count(d, EMOKIT_VID, EMOKIT_PID) << "\n"; + if(emokit_open(d, EMOKIT_VID, EMOKIT_PID, 1) != 0) { - fputs("Missing argument\nExpected: epocd [consumer|research|special]\n", stderr); - return 1; - } - - if(strcmp(argv[1], "research") == 0) - type = RESEARCH_HEADSET; - else if(strcmp(argv[1], "consumer") == 0) - type = CONSUMER_HEADSET; - else if(strcmp(argv[1], "special") == 0) - type = SPECIAL_HEADSET; - else { - fputs("Bad headset type argument\nExpected: epocd [consumer|research|special] source [dest]\n", stderr); + std::cout << "CANNOT CONNECT\n"; return 1; } - - epoc_init(type); - d = epoc_create(); - printf("Current epoc devices connected: %d\n", epoc_get_count(d, EPOC_VID, EPOC_PID)); - if(epoc_open(d, EPOC_VID, EPOC_PID, 0) != 0) - { - printf("CANNOT CONNECT\n"); - return 1; - } + UdpTransmitSocket transmitSocket( IpEndpointName( ADDRESS, PORT ) ); + char buffer[OUTPUT_BUFFER_SIZE]; + char raw_frame[32]; + struct emokit_frame frame; + + std::cout << "Connected\n"; while(1) { - if(epoc_read_data(d, data) > 0) + std::cout << "Starting read\n"; + if(emokit_read_data(d) > 0) { - epoc_get_next_frame(&frame, data); + struct emokit_frame c; + c = emokit_get_next_frame(d); + osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE ); - p << osc::BeginBundleImmediate - << osc::BeginMessage( "/epoc/channels" ) - << frame.F3 << frame.FC6 << frame.P7 << frame.T8 << frame.F7 << frame.F8 << frame.T7 << frame.P8 << frame.AF4 << frame.F4 << frame.AF3 << frame.O2 << frame.O1 << frame.FC5 << osc::EndMessage - << osc::BeginMessage( "/epoc/gyro" ) - << frame.gyroX << frame.gyroY << osc::EndMessage - << osc::EndBundle; - + p << osc::BeginMessage( "/emokit/channels" ) + << c.F3 << c.FC6 << c.P7 << c.T8 << c.F7 << c.F8 + << c.T7 << c.P8 << c.AF4 << c.F4 << c.AF3 << c.O2 + << c.O1 << c.FC5 + << osc::EndMessage; transmitSocket.Send( p.Data(), p.Size() ); + + osc::OutboundPacketStream q( buffer, OUTPUT_BUFFER_SIZE ); + q << osc::BeginMessage( "/emokit/gyro" ) + << (int)frame.gyroX << (int)frame.gyroY << osc::EndMessage; + transmitSocket.Send( q.Data(), q.Size() ); + + osc::OutboundPacketStream info( buffer, OUTPUT_BUFFER_SIZE ); + info << osc::BeginMessage( "/emokit/info" ) + << (int)c.battery + << c.cq.F3 << c.cq.FC6 << c.cq.P7 << c.cq.T8 << c.cq.F7 << c.cq.F8 + << c.cq.T7 << c.cq.P8 << c.cq.AF4 << c.cq.F4 << c.cq.AF3 << c.cq.O2 + << c.cq.O1 << c.cq.FC5 + << osc::EndMessage; + transmitSocket.Send( info.Data(), info.Size() ); + } } - epoc_close(d); - epoc_delete(d); + fflush(stdout); + emokit_close(d); + emokit_delete(d); return 0; - } - diff --git a/examples/emokit_osc/processing/emokit_osc/emokit_osc.pde b/examples/emokit_osc/processing/emokit_osc/emokit_osc.pde new file mode 100644 index 0000000..28cbf02 --- /dev/null +++ b/examples/emokit_osc/processing/emokit_osc/emokit_osc.pde @@ -0,0 +1,59 @@ +/** + * This example is yet to be improved. It does not handle exceptions and may prove faulty. + */ + +//Import OSC libs +import oscP5.*; +import netP5.*; + +//OSC +OscP5 oscP5; + +int[] vals; + + +void setup() { + //Graphical + size(100, 300); + smooth(); + noStroke(); + //osc + oscP5 = new OscP5(this, 9997); + + vals = new int[14]; + for (int i = 0; i < vals.length; i++) { + vals[i] = 0; + } + +} + +//Main function, draw frame +void draw() { + background(255); + fill(0); + text("Levels : \n", 40, 30); + for (int i=0 ; i<14 ; i++){ + text(vals[i], 40, 75+i*12); + } +} + + +//If new OSC value, update happens here +void oscEvent(OscMessage msg) { + if(msg.checkAddrPattern("/emokit/channels")){ + println("1) Channel readings:"); + for(int i=0 ; i<14 ; i++){ + vals[i] = msg.get(i).intValue(); + println("[" + i + "] : " + vals[i]); + } + println(); + } else if(msg.checkAddrPattern("/emokit/gyro")){ + println("2) Gyro:"); + println("x: " + msg.get(0).intValue() + " ; y: " + msg.get(1).intValue()); + println(); + } else + if (msg.checkAddrPattern("/emokit/infos")) { + //Todo + } +} + diff --git a/examples/emokitd/emokitd.c b/examples/emokitd/emokitd.c index d39204f..37905d8 100644 --- a/examples/emokitd/emokitd.c +++ b/examples/emokitd/emokitd.c @@ -18,7 +18,7 @@ int main(int argc, char **argv) d = emokit_create(); printf("Current epoc devices connected: %d\n", emokit_get_count(d, EMOKIT_VID, EMOKIT_PID)); - int r = emokit_open(d, EMOKIT_VID, EMOKIT_PID, 0); + int r = emokit_open(d, EMOKIT_VID, EMOKIT_PID, 1); if(r != 0) { printf("CANNOT CONNECT: %d\n", r); diff --git a/include/oscpack/CHANGES b/include/oscpack/CHANGES new file mode 100644 index 0000000..804df95 --- /dev/null +++ b/include/oscpack/CHANGES @@ -0,0 +1,68 @@ +September 28, 2005 +------------------ + +Compared to the previous official snapshot (November 2004) the +current version of oscpack includes a re-written set of network +classes and some changes to the syntax of the networking code. It no +longer uses threads, which means that you don't need to use sleep() +if you are writing a simple single-threaded server, or you need to +spawn your own threads in a more complex application. + +The list below summarises the changes if you are porting code from +the previous release. + + - there are no longer any threads in oscpack. if you need to + set up an asynchronous listener you can create your own thread + and call Run on an instance of SocketReceiveMultiplexer or + UdpListeningReceiveSocket (see ip/UdpSocket.h) yourself. + + - host byte order is now used for network (IP) addresses + + - functions which used to take two parameters + now take an instance of IpEndpointName (see + ip/IpEndpointName.h) this class has a number of convenient + constructors for converting numbers and strings to internet + addresses. For example there is one which takes a string and + another that take the dotted address components as separate + parameters. + + - The UdpTransmitPort class, formerly in UdpTransmitPort.h, is + now called UdpTransmitSocket, which is simply a convenience + class derived from UdpSocket (see ip/UdpSocket.h). Where you + used to use the constructor UdpTransmitPort( address, port) now + you can use UdpTransmitSocket( IpEndpointName( address, port ) + ) or you can any of the other possible ctors to IpEndpointName + () (see above). The Send() method is unchanged. + + - The packet listener base class is now located in + ip/PacketListener.h instead of PacketListenerPort.h. The + ProcessPacket method now has an additional parameter indicating + the remote endpoint + + - The preferred way to set up listeners is with + SocketReceiveMultiplexer (in ip/UdpSocket.h), this also allows + attaching periodic timers. For simple applications which only + listen to a single socket with no timers you can use + UdpListeningReceiveSocket (also in UdpSocket.h) See + osc/OscReceiveTest.cpp or osc/OscDump.cpp for examples of this. + This is more or less equivalent to the UdpPacketListenerPort + object in the old oscpack versions except that you need to + explicitly call Run() before it will start receiving packets + and it runs in the same thread, not a separate thread so Run() + won't usually return. + + - Explicit calls to InitializeNetworking() and + TerminateNetworking() are no longer required for simple + applications (more complex windows applications should + instantiate NetworkInitializer in main() or WinMain (see + ip/NetworkingUtils.h/.cpp) + + - The OscPacketListener base class (OscPacketListener.h) was + added to make traversing OSC packets easier, it handles bundle + traversal automatically so you only need to process messages in + your derived classes. + + - On Windows be sure to link with ws2_32.lib or you will see + a linker error about WSAEventSelect not being found. Also you + will need to link with winmm.lib for timeGetTime() + diff --git a/include/oscpack/CMakeLists.txt b/include/oscpack/CMakeLists.txt new file mode 100644 index 0000000..d398832 --- /dev/null +++ b/include/oscpack/CMakeLists.txt @@ -0,0 +1,34 @@ +cmake_minimum_required(VERSION 2.6) +PROJECT(OSC) + +INCLUDE_DIRECTORIES(${OSC_SOURCE_DIR}) + + +IF(WIN32) +set(SystemTypePath ip/win32) +ELSE(WIN32) +set(SystemTypePath ip/posix) +ENDIF(WIN32) + +ADD_LIBRARY(oscpack + +ip/IpEndpointName.cpp +${SystemTypePath}/NetworkingUtils.cpp +${SystemTypePath}/UdpSocket.cpp + +osc/OscPrintReceivedElements.cpp +osc/OscReceivedElements.cpp +osc/OscTypes.cpp +osc/OscOutboundPacketStream.cpp +) + +INSTALL (TARGETS oscpack + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib + ) + +INSTALL(FILES + ${LIBEMOKIT_HEADERS} + DESTINATION include/liboscpack + ) + diff --git a/include/oscpack/LICENSE b/include/oscpack/LICENSE new file mode 100644 index 0000000..23c9609 --- /dev/null +++ b/include/oscpack/LICENSE @@ -0,0 +1,28 @@ +oscpack -- Open Sound Control packet manipulation library +http://www.audiomulch.com/~rossb/code/oscpack + +Copyright (c) 2004 Ross Bencina + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Any person wishing to distribute modifications to the Software is +requested to send the modifications to the original developer so that +they can be incorporated into the canonical version. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR +ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/include/oscpack/README b/include/oscpack/README new file mode 100644 index 0000000..61964ce --- /dev/null +++ b/include/oscpack/README @@ -0,0 +1,80 @@ +oscpack -- Open Sound Control packet manipulation library +http://www.audiomulch.com/~rossb/code/oscpack + +Copyright (c) 2004-2005 Ross Bencina + +A simple C++ library for packing and unpacking OSC packets. + + +Oscpack is simply a set of C++ classes for packing and unpacking OSC packets. +Oscpack includes a minimal set of UDP networking classes for windows and posix +which are sufficient for writing many OSC applications and servers, but you are +encouraged to use another networking framework if it better suits your needs. +Oscpack is not an OSC application framework, it doesn't include infrastructure for +constructing or routing OSC namespaces, just classes for easily constructing, +sending, receiving and parsing OSC packets. The library should also be easy to use +for other transport methods (eg serial). + +The key goals of the oscpack library are: + + - to be a simple and complete implementation of OSC + - to be portable to a wide variety of platforms + - to allow easy development of robust OSC applications + (for example it should be impossible to crash a server + by sending it malformed packets, and difficult to + create malformed packets.) + +Here's a summary of the key files: + +osc/OscReceivedElements -- classes for parsing a packet +osc/OscPrintRecievedElements -- iostream << operators for printing packet elements +osc/OscOutboundPacket -- a class for packing messages into a packet +osc/OscPacketListener -- base class for listening to OSC packets on a UdpSocket +tests/OscUnitTests -- unit test program for the OSC modules +tests/OscSendTests -- examples of how to send messages +tests/OscReceiveTest -- example of how to receive the messages sent by OSCSendTests +examples/OscDump -- a program that prints received OSC packets + + + +Building +-------- + +In general the idea is that you will embed this source code in your projects as you +see fit. The Makefile has an install rule for building a shared library and +installing headers in usr/local. + +The Makefile works for Linux and MaxOS X except that if you are on a big endian +machine such as PowerPC Macintosh you need to edit the line which sets the +endianness to OSC_HOST_BIG_ENDIAN (see the makefile comment for details) or it won't +work. If you want to build and install liboscpack as a library on OS X you also need +to edit the $(LIBFILENAME) rule by commenting out the Linux case and uncommenting +the OS X case since OS X uses different gcc flags for shared libraries. + +On Windows there is a batch file for doing a simple test build with MinGW gcc called +make.MinGW32.bat. This will build the test executables and oscdump in ./bin and run +the unit tests. + +-- + + +If you fix anything or write a set of TCP send/recieve classes +please consider sending me a patch. Thanks :) + +For more information about Open Sound Control, see: +http://www.cnmat.berkeley.edu/OpenSoundControl/ + + +Thanks to Till Bovermann for helping with POSIX networking code and +Mac compatibility, and to Martin Kaltenbrunner and the rest of the +reacTable team for giving me a reason to finish this library. Thanks +to Merlijn Blaauw for reviewing the interfaces. Thanks to Xavier Oliver +for additional help with Linux builds and POSIX implementation details. + +Portions developed at the Music Technology Group, Audiovisual Institute, +University Pompeu Fabra, Barcelona, during my stay as a visiting +researcher, November 2004 - September 2005. + +See the file LICENSE for information about distributing and using this code. + + diff --git a/include/oscpack/TODO b/include/oscpack/TODO new file mode 100644 index 0000000..309ae68 --- /dev/null +++ b/include/oscpack/TODO @@ -0,0 +1,55 @@ +TODO: + + - consider adding the local endpoint name to PacketListener::PacketReceived() params + + - consider adding ListenerThread class to support old seperate thread listener functionality, something like: + + class UdpSocketListenerThread{ + public: + UdpSocketListenerThread( UdpSocket& socket, Listener *listener ); + UdpSocketListenerThread( UdpSocketReceiveMultiplexer *mux ); + ~UdpSocketListenerThread(); + + void Run(); + void Stop(); + }; + + - provide some kind of automatic endianness configuration (hopefully there + are gcc symbols for this) + + - work out a way to make the parsing classes totally safe. at a minimum this + means adding functions to test for invalid float/doublevalues, + making sure the iterators never pass the end of the message, ... + (passing end of message can happen if: + - too many args in type tags + a. typetags overflow message size + b. args fulfilling typetags overflow message size + - strings too long or not terminated correctly + - blobs too long or not terminated correctly + + if the message was fully checked during construction, the end() iterator + could be moved back until only arguments which fit withing size() may + be interated (this could be none). A flag could be set to indicate that + something was wrong. + + - other packet badness could include: + - time tags too far into the future (the scheduler should deal with + that i guess). + - message address patterns which aren't correctly terminated + + - improve the ability to parse messages without tags (SC uses methods which + get the data and advance the iterator in one step.) + - Check* could be modified to do this - ie if typetags are not present + it could check that reading the field won't escape the message size + and return the data, or return false if some consistency + constraint is violated. + (or alternately drop support for messages without type tags) + + + - add a method to discard an inprogress message if it gets half + constructed and the buffer is full in OutboundPacket + + - write a stress testing app which can send garbage packets to try to flush out other bugs in the parsing code. + + + diff --git a/include/oscpack/examples/OscDump.cpp b/include/oscpack/examples/OscDump.cpp new file mode 100644 index 0000000..d837a19 --- /dev/null +++ b/include/oscpack/examples/OscDump.cpp @@ -0,0 +1,83 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* + OscDump prints incoming Osc packets. Unlike the Berkeley dumposc program + OscDump uses a different printing format which indicates the type of each + message argument. +*/ + + +#include + +#include "osc/OscReceivedElements.h" +#include "osc/OscPrintReceivedElements.h" + +#include "ip/UdpSocket.h" +#include "ip/PacketListener.h" + + +class OscDumpPacketListener : public PacketListener{ +public: + virtual void ProcessPacket( const char *data, int size, + const IpEndpointName& remoteEndpoint ) + { + std::cout << osc::ReceivedPacket( data, size ); + } +}; + +int main(int argc, char* argv[]) +{ + if( argc >= 2 && strcmp( argv[1], "-h" ) == 0 ){ + std::cout << "usage: OscDump [port]\n"; + return 0; + } + + int port = 7000; + + if( argc >= 2 ) + port = atoi( argv[1] ); + + OscDumpPacketListener listener; + UdpListeningReceiveSocket s( + IpEndpointName( IpEndpointName::ANY_ADDRESS, port ), + &listener ); + + std::cout << "listening for input on port " << port << "...\n"; + std::cout << "press ctrl-c to end\n"; + + s.RunUntilSigInt(); + + std::cout << "finishing.\n"; + + return 0; +} + + diff --git a/include/oscpack/examples/SimpleReceive.cpp b/include/oscpack/examples/SimpleReceive.cpp new file mode 100644 index 0000000..6fd2a65 --- /dev/null +++ b/include/oscpack/examples/SimpleReceive.cpp @@ -0,0 +1,74 @@ +/* + Example of two different ways to process received OSC messages using oscpack. + Receives the messages from the SimpleSend.cpp example. +*/ + +#include + +#include "osc/OscReceivedElements.h" +#include "osc/OscPacketListener.h" +#include "ip/UdpSocket.h" + + +#define PORT 7000 + +class ExamplePacketListener : public osc::OscPacketListener { +protected: + + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) + { + try{ + // example of parsing single messages. osc::OsckPacketListener + // handles the bundle traversal. + + if( strcmp( m.AddressPattern(), "/test1" ) == 0 ){ + // example #1 -- argument stream interface + osc::ReceivedMessageArgumentStream args = m.ArgumentStream(); + bool a1; + osc::int32 a2; + float a3; + const char *a4; + args >> a1 >> a2 >> a3 >> a4 >> osc::EndMessage; + + std::cout << "received '/test1' message with arguments: " + << a1 << " " << a2 << " " << a3 << " " << a4 << "\n"; + + }else if( strcmp( m.AddressPattern(), "/test2" ) == 0 ){ + // example #2 -- argument iterator interface, supports + // reflection for overloaded messages (eg you can call + // (*arg)->IsBool() to check if a bool was passed etc). + osc::ReceivedMessage::const_iterator arg = m.ArgumentsBegin(); + bool a1 = (arg++)->AsBool(); + int a2 = (arg++)->AsInt32(); + float a3 = (arg++)->AsFloat(); + const char *a4 = (arg++)->AsString(); + if( arg != m.ArgumentsEnd() ) + throw osc::ExcessArgumentException(); + + std::cout << "received '/test2' message with arguments: " + << a1 << " " << a2 << " " << a3 << " " << a4 << "\n"; + } + }catch( osc::Exception& e ){ + // any parsing errors such as unexpected argument types, or + // missing arguments get thrown as exceptions. + std::cout << "error while parsing message: " + << m.AddressPattern() << ": " << e.what() << "\n"; + } + } +}; + +int main(int argc, char* argv[]) +{ + ExamplePacketListener listener; + UdpListeningReceiveSocket s( + IpEndpointName( IpEndpointName::ANY_ADDRESS, PORT ), + &listener ); + + std::cout << "press ctrl-c to end\n"; + + s.RunUntilSigInt(); + + return 0; +} + diff --git a/include/oscpack/examples/SimpleSend.cpp b/include/oscpack/examples/SimpleSend.cpp new file mode 100644 index 0000000..3781a30 --- /dev/null +++ b/include/oscpack/examples/SimpleSend.cpp @@ -0,0 +1,30 @@ +/* + Simple example of sending an OSC message using oscpack. +*/ + +#include "osc/OscOutboundPacketStream.h" +#include "ip/UdpSocket.h" + + +#define ADDRESS "127.0.0.1" +#define PORT 7000 + +#define OUTPUT_BUFFER_SIZE 1024 + +int main(int argc, char* argv[]) +{ + UdpTransmitSocket transmitSocket( IpEndpointName( ADDRESS, PORT ) ); + + char buffer[OUTPUT_BUFFER_SIZE]; + osc::OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE ); + + p << osc::BeginBundleImmediate + << osc::BeginMessage( "/test1" ) + << true << 23 << (float)3.1415 << "hello" << osc::EndMessage + << osc::BeginMessage( "/test2" ) + << true << 24 << (float)10.8 << "world" << osc::EndMessage + << osc::EndBundle; + + transmitSocket.Send( p.Data(), p.Size() ); +} + diff --git a/include/oscpack/ip/IpEndpointName.cpp b/include/oscpack/ip/IpEndpointName.cpp new file mode 100644 index 0000000..33fdd98 --- /dev/null +++ b/include/oscpack/ip/IpEndpointName.cpp @@ -0,0 +1,81 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "IpEndpointName.h" + +#include + +#include "NetworkingUtils.h" + + +unsigned long IpEndpointName::GetHostByName( const char *s ) +{ + return ::GetHostByName(s); +} + + +void IpEndpointName::AddressAsString( char *s ) const +{ + if( address == ANY_ADDRESS ){ + sprintf( s, "" ); + }else{ + sprintf( s, "%d.%d.%d.%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF) ); + } +} + + +void IpEndpointName::AddressAndPortAsString( char *s ) const +{ + if( port == ANY_PORT ){ + if( address == ANY_ADDRESS ){ + sprintf( s, ":" ); + }else{ + sprintf( s, "%d.%d.%d.%d:", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF) ); + } + }else{ + if( address == ANY_ADDRESS ){ + sprintf( s, ":%d", port ); + }else{ + sprintf( s, "%d.%d.%d.%d:%d", + (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), + (int)((address >> 8) & 0xFF), + (int)(address & 0xFF), + (int)port ); + } + } +} diff --git a/include/oscpack/ip/IpEndpointName.h b/include/oscpack/ip/IpEndpointName.h new file mode 100644 index 0000000..c7b078e --- /dev/null +++ b/include/oscpack/ip/IpEndpointName.h @@ -0,0 +1,74 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_IPENDPOINTNAME_H +#define INCLUDED_IPENDPOINTNAME_H + + +class IpEndpointName{ + static unsigned long GetHostByName( const char *s ); +public: + static const unsigned long ANY_ADDRESS = 0xFFFFFFFF; + static const int ANY_PORT = -1; + + IpEndpointName() + : address( ANY_ADDRESS ), port( ANY_PORT ) {} + IpEndpointName( int port_ ) + : address( ANY_ADDRESS ), port( port_ ) {} + IpEndpointName( unsigned long ipAddress_, int port_ ) + : address( ipAddress_ ), port( port_ ) {} + IpEndpointName( const char *addressName, int port_=ANY_PORT ) + : address( GetHostByName( addressName ) ) + , port( port_ ) {} + IpEndpointName( int addressA, int addressB, int addressC, int addressD, int port_=ANY_PORT ) + : address( ( (addressA << 24) | (addressB << 16) | (addressC << 8) | addressD ) ) + , port( port_ ) {} + + // address and port are maintained in host byte order here + unsigned long address; + int port; + + enum { ADDRESS_STRING_LENGTH=17 }; + void AddressAsString( char *s ) const; + + enum { ADDRESS_AND_PORT_STRING_LENGTH=23}; + void AddressAndPortAsString( char *s ) const; +}; + +inline bool operator==( const IpEndpointName& lhs, const IpEndpointName& rhs ) +{ + return (lhs.address == rhs.address && lhs.port == rhs.port ); +} + +inline bool operator!=( const IpEndpointName& lhs, const IpEndpointName& rhs ) +{ + return !(lhs == rhs); +} + +#endif /* INCLUDED_IPENDPOINTNAME_H */ diff --git a/include/oscpack/ip/NetworkingUtils.h b/include/oscpack/ip/NetworkingUtils.h new file mode 100644 index 0000000..0d6901c --- /dev/null +++ b/include/oscpack/ip/NetworkingUtils.h @@ -0,0 +1,49 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_NETWORKINGUTILS_H +#define INCLUDED_NETWORKINGUTILS_H + + +// in general NetworkInitializer is only used internally, but if you're +// application creates multiple sockets from different threads at runtime you +// should instantiate one of these in main just to make sure the networking +// layer is initialized. +class NetworkInitializer{ +public: + NetworkInitializer(); + ~NetworkInitializer(); +}; + + +// return ip address of host name in host byte order +unsigned long GetHostByName( const char *name ); + + +#endif /* INCLUDED_NETWORKINGUTILS_H */ diff --git a/include/oscpack/ip/PacketListener.h b/include/oscpack/ip/PacketListener.h new file mode 100644 index 0000000..6647209 --- /dev/null +++ b/include/oscpack/ip/PacketListener.h @@ -0,0 +1,43 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_PACKETLISTENER_H +#define INCLUDED_PACKETLISTENER_H + + +class IpEndpointName; + +class PacketListener{ +public: + virtual ~PacketListener() {} + virtual void ProcessPacket( const char *data, int size, + const IpEndpointName& remoteEndpoint ) = 0; +}; + +#endif /* INCLUDED_PACKETLISTENER_H */ diff --git a/include/oscpack/ip/TimerListener.h b/include/oscpack/ip/TimerListener.h new file mode 100644 index 0000000..82b1181 --- /dev/null +++ b/include/oscpack/ip/TimerListener.h @@ -0,0 +1,40 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_TIMERLISTENER_H +#define INCLUDED_TIMERLISTENER_H + + +class TimerListener{ +public: + virtual ~TimerListener() {} + virtual void TimerExpired() = 0; +}; + +#endif /* INCLUDED_TIMERLISTENER_H */ diff --git a/include/oscpack/ip/UdpSocket.h b/include/oscpack/ip/UdpSocket.h new file mode 100644 index 0000000..6d9c26d --- /dev/null +++ b/include/oscpack/ip/UdpSocket.h @@ -0,0 +1,158 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_UDPSOCKET_H +#define INCLUDED_UDPSOCKET_H + +#ifndef INCLUDED_NETWORKINGUTILITIES_H +#include "NetworkingUtils.h" +#endif /* INCLUDED_NETWORKINGUTILITIES_H */ + +#ifndef INCLUDED_IPENDPOINTNAME_H +#include "IpEndpointName.h" +#endif /* INCLUDED_IPENDPOINTNAME_H */ + + +class PacketListener; +class TimerListener; + +class UdpSocket; + +class SocketReceiveMultiplexer{ + class Implementation; + Implementation *impl_; + + friend class UdpSocket; + +public: + SocketReceiveMultiplexer(); + ~SocketReceiveMultiplexer(); + + // only call the attach/detach methods _before_ calling Run + + // only one listener per socket, each socket at most once + void AttachSocketListener( UdpSocket *socket, PacketListener *listener ); + void DetachSocketListener( UdpSocket *socket, PacketListener *listener ); + + void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ); + void AttachPeriodicTimerListener( + int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ); + void DetachPeriodicTimerListener( TimerListener *listener ); + + void Run(); // loop and block processing messages indefinitely + void RunUntilSigInt(); + void Break(); // call this from a listener to exit once the listener returns + void AsynchronousBreak(); // call this from another thread or signal handler to exit the Run() state +}; + + +class UdpSocket{ + class Implementation; + Implementation *impl_; + + friend class SocketReceiveMultiplexer::Implementation; + +public: + + // ctor throws std::runtime_error if there's a problem + // initializing the socket. + UdpSocket(); + virtual ~UdpSocket(); + + // the socket is created in an unbound, unconnected state + // such a socket can only be used to send to an arbitrary + // address using SendTo(). To use Send() you need to first + // connect to a remote endpoint using Connect(). To use + // ReceiveFrom you need to first bind to a local endpoint + // using Bind(). + + // retrieve the local endpoint name when sending to 'to' + IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const; + + // Connect to a remote endpoint which is used as the target + // for calls to Send() + void Connect( const IpEndpointName& remoteEndpoint ); + void Send( const char *data, int size ); + void SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ); + + + // Bind a local endpoint to receive incoming data. Endpoint + // can be 'any' for the system to choose an endpoint + void Bind( const IpEndpointName& localEndpoint ); + bool IsBound() const; + + int ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ); +}; + + +// convenience classes for transmitting and receiving +// they just call Connect and/or Bind in the ctor. +// note that you can still use a receive socket +// for transmitting etc + +class UdpTransmitSocket : public UdpSocket{ +public: + UdpTransmitSocket( const IpEndpointName& remoteEndpoint ) + { Connect( remoteEndpoint ); } +}; + + +class UdpReceiveSocket : public UdpSocket{ +public: + UdpReceiveSocket( const IpEndpointName& localEndpoint ) + { Bind( localEndpoint ); } +}; + + +// UdpListeningReceiveSocket provides a simple way to bind one listener +// to a single socket without having to manually set up a SocketReceiveMultiplexer + +class UdpListeningReceiveSocket : public UdpSocket{ + SocketReceiveMultiplexer mux_; + PacketListener *listener_; +public: + UdpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener *listener ) + : listener_( listener ) + { + Bind( localEndpoint ); + mux_.AttachSocketListener( this, listener_ ); + } + + ~UdpListeningReceiveSocket() + { mux_.DetachSocketListener( this, listener_ ); } + + // see SocketReceiveMultiplexer above for the behaviour of these methods... + void Run() { mux_.Run(); } + void RunUntilSigInt() { mux_.RunUntilSigInt(); } + void Break() { mux_.Break(); } + void AsynchronousBreak() { mux_.AsynchronousBreak(); } +}; + + +#endif /* INCLUDED_UDPSOCKET_H */ diff --git a/include/oscpack/ip/posix/NetworkingUtils.cpp b/include/oscpack/ip/posix/NetworkingUtils.cpp new file mode 100644 index 0000000..3b1da79 --- /dev/null +++ b/include/oscpack/ip/posix/NetworkingUtils.cpp @@ -0,0 +1,57 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "ip/NetworkingUtils.h" + +#include +#include +#include +#include +#include + + + +NetworkInitializer::NetworkInitializer() {} + +NetworkInitializer::~NetworkInitializer() {} + + +unsigned long GetHostByName( const char *name ) +{ + unsigned long result = 0; + + struct hostent *h = gethostbyname( name ); + if( h ){ + struct in_addr a; + memcpy( &a, h->h_addr_list[0], h->h_length ); + result = ntohl(a.s_addr); + } + + return result; +} diff --git a/include/oscpack/ip/posix/UdpSocket.cpp b/include/oscpack/ip/posix/UdpSocket.cpp new file mode 100644 index 0000000..735169b --- /dev/null +++ b/include/oscpack/ip/posix/UdpSocket.cpp @@ -0,0 +1,546 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "ip/UdpSocket.h" + +#include +#include +#include +#include +#include +#include +#include +#include // for memset + +#include +#include +#include +#include +#include +#include +#include +#include +#include // for sockaddr_in + +#include "ip/PacketListener.h" +#include "ip/TimerListener.h" + + +#if defined(__APPLE__) && !defined(_SOCKLEN_T) +// pre system 10.3 didn have socklen_t +typedef ssize_t socklen_t; +#endif + + +static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint ) +{ + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + sockAddr.sin_family = AF_INET; + + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) + ? INADDR_ANY + : htonl( endpoint.address ); + + sockAddr.sin_port = + (endpoint.port == IpEndpointName::ANY_PORT) + ? 0 + : htons( endpoint.port ); +} + + +static IpEndpointName IpEndpointNameFromSockaddr( const struct sockaddr_in& sockAddr ) +{ + return IpEndpointName( + (sockAddr.sin_addr.s_addr == INADDR_ANY) + ? IpEndpointName::ANY_ADDRESS + : ntohl( sockAddr.sin_addr.s_addr ), + (sockAddr.sin_port == 0) + ? IpEndpointName::ANY_PORT + : ntohs( sockAddr.sin_port ) + ); +} + + +class UdpSocket::Implementation{ + bool isBound_; + bool isConnected_; + + int socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + +public: + + Implementation() + : isBound_( false ) + , isConnected_( false ) + , socket_( -1 ) + { + if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == -1 ){ + throw std::runtime_error("unable to create udp socket\n"); + } + + memset( &sendToAddr_, 0, sizeof(sendToAddr_) ); + sendToAddr_.sin_family = AF_INET; + } + + ~Implementation() + { + if (socket_ != -1) close(socket_); + } + + IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const + { + assert( isBound_ ); + + // first connect the socket to the remote server + + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName( connectSockAddr, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + // get the address + + struct sockaddr_in sockAddr; + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + socklen_t length = sizeof(sockAddr); + if (getsockname(socket_, (struct sockaddr *)&sockAddr, &length) < 0) { + throw std::runtime_error("unable to getsockname\n"); + } + + if( isConnected_ ){ + // reconnect to the connected address + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + }else{ + // unconnect from the remote address + + struct sockaddr_in unconnectSockAddr; + memset( (char *)&unconnectSockAddr, 0, sizeof(unconnectSockAddr ) ); + unconnectSockAddr.sin_family = AF_UNSPEC; + // address fields are zero + int connectResult = connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr)); + if ( connectResult < 0 && errno != EAFNOSUPPORT ) { + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } + + return IpEndpointNameFromSockaddr( sockAddr ); + } + + void Connect( const IpEndpointName& remoteEndpoint ) + { + SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + isConnected_ = true; + } + + void Send( const char *data, int size ) + { + assert( isConnected_ ); + + send( socket_, data, size, 0 ); + } + + void SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) + { + sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address ); + sendToAddr_.sin_port = htons( remoteEndpoint.port ); + + sendto( socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) ); + } + + void Bind( const IpEndpointName& localEndpoint ) + { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName( bindSockAddr, localEndpoint ); + + if (bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } + + isBound_ = true; + } + + bool IsBound() const { return isBound_; } + + int ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) + { + assert( isBound_ ); + + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + int result = recvfrom(socket_, data, size, 0, + (struct sockaddr *) &fromAddr, (socklen_t*)&fromAddrLen); + if( result < 0 ) + return 0; + + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); + + return result; + } + + int Socket() { return socket_; } +}; + +UdpSocket::UdpSocket() +{ + impl_ = new Implementation(); +} + +UdpSocket::~UdpSocket() +{ + delete impl_; +} + +IpEndpointName UdpSocket::LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const +{ + return impl_->LocalEndpointFor( remoteEndpoint ); +} + +void UdpSocket::Connect( const IpEndpointName& remoteEndpoint ) +{ + impl_->Connect( remoteEndpoint ); +} + +void UdpSocket::Send( const char *data, int size ) +{ + impl_->Send( data, size ); +} + +void UdpSocket::SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) +{ + impl_->SendTo( remoteEndpoint, data, size ); +} + +void UdpSocket::Bind( const IpEndpointName& localEndpoint ) +{ + impl_->Bind( localEndpoint ); +} + +bool UdpSocket::IsBound() const +{ + return impl_->IsBound(); +} + +int UdpSocket::ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) +{ + return impl_->ReceiveFrom( remoteEndpoint, data, size ); +} + + +struct AttachedTimerListener{ + AttachedTimerListener( int id, int p, TimerListener *tl ) + : initialDelayMs( id ) + , periodMs( p ) + , listener( tl ) {} + int initialDelayMs; + int periodMs; + TimerListener *listener; +}; + + +static bool CompareScheduledTimerCalls( + const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs ) +{ + return lhs.first < rhs.first; +} + + +SocketReceiveMultiplexer *multiplexerInstanceToAbortWithSigInt_ = 0; + +extern "C" /*static*/ void InterruptSignalHandler( int ); +/*static*/ void InterruptSignalHandler( int ) +{ + multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak(); + signal( SIGINT, SIG_DFL ); +} + + +class SocketReceiveMultiplexer::Implementation{ + std::vector< std::pair< PacketListener*, UdpSocket* > > socketListeners_; + std::vector< AttachedTimerListener > timerListeners_; + + volatile bool break_; + int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer + + double GetCurrentTimeMs() const + { + struct timeval t; + + gettimeofday( &t, 0 ); + + return ((double)t.tv_sec*1000.) + ((double)t.tv_usec / 1000.); + } + +public: + Implementation() + { + if( pipe(breakPipe_) != 0 ) + throw std::runtime_error( "creation of asynchronous break pipes failed\n" ); + } + + ~Implementation() + { + close( breakPipe_[0] ); + close( breakPipe_[1] ); + } + + void AttachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + assert( std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ) == socketListeners_.end() ); + // we don't check that the same socket has been added multiple times, even though this is an error + socketListeners_.push_back( std::make_pair( listener, socket ) ); + } + + void DetachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = + std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ); + assert( i != socketListeners_.end() ); + + socketListeners_.erase( i ); + } + + void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( periodMilliseconds, periodMilliseconds, listener ) ); + } + + void AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ) ); + } + + void DetachPeriodicTimerListener( TimerListener *listener ) + { + std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + while( i != timerListeners_.end() ){ + if( i->listener == listener ) + break; + ++i; + } + + assert( i != timerListeners_.end() ); + + timerListeners_.erase( i ); + } + + void Run() + { + break_ = false; + + // configure the master fd_set for select() + + fd_set masterfds, tempfds; + FD_ZERO( &masterfds ); + FD_ZERO( &tempfds ); + + // in addition to listening to the inbound sockets we + // also listen to the asynchronous break pipe, so that AsynchronousBreak() + // can break us out of select() from another thread. + FD_SET( breakPipe_[0], &masterfds ); + int fdmax = breakPipe_[0]; + + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i ){ + + if( fdmax < i->second->impl_->Socket() ) + fdmax = i->second->impl_->Socket(); + FD_SET( i->second->impl_->Socket(), &masterfds ); + } + + + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector< std::pair< double, AttachedTimerListener > > timerQueue_; + for( std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + i != timerListeners_.end(); ++i ) + timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) ); + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + + const int MAX_BUFFER_SIZE = 4098; + char *data = new char[ MAX_BUFFER_SIZE ]; + IpEndpointName remoteEndpoint; + + struct timeval timeout; + + while( !break_ ){ + tempfds = masterfds; + + struct timeval *timeoutPtr = 0; + if( !timerQueue_.empty() ){ + double timeoutMs = timerQueue_.front().first - GetCurrentTimeMs(); + if( timeoutMs < 0 ) + timeoutMs = 0; + + // 1000000 microseconds in a second + timeout.tv_sec = (long)(timeoutMs * .001); + timeout.tv_usec = (long)((timeoutMs - (timeout.tv_sec * 1000)) * 1000); + timeoutPtr = &timeout; + } + + if( select( fdmax + 1, &tempfds, 0, 0, timeoutPtr ) < 0 && errno != EINTR ){ + throw std::runtime_error("select failed\n"); + } + + if ( FD_ISSET( breakPipe_[0], &tempfds ) ){ + // clear pending data from the asynchronous break pipe + char c; + read( breakPipe_[0], &c, 1 ); + } + + if( break_ ) + break; + + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i ){ + + if( FD_ISSET( i->second->impl_->Socket(), &tempfds ) ){ + + int size = i->second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE ); + if( size > 0 ){ + i->first->ProcessPacket( data, size, remoteEndpoint ); + if( break_ ) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for( std::vector< std::pair< double, AttachedTimerListener > >::iterator i = timerQueue_.begin(); + i != timerQueue_.end() && i->first <= currentTimeMs; ++i ){ + + i->second.listener->TimerExpired(); + if( break_ ) + break; + + i->first += i->second.periodMs; + resort = true; + } + if( resort ) + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + } + + delete [] data; + } + + void Break() + { + break_ = true; + } + + void AsynchronousBreak() + { + break_ = true; + + // Send a termination message to the asynchronous break pipe, so select() will return + write( breakPipe_[1], "!", 1 ); + } +}; + + + +SocketReceiveMultiplexer::SocketReceiveMultiplexer() +{ + impl_ = new Implementation(); +} + +SocketReceiveMultiplexer::~SocketReceiveMultiplexer() +{ + delete impl_; +} + +void SocketReceiveMultiplexer::AttachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->AttachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::DetachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->DetachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::DetachPeriodicTimerListener( TimerListener *listener ) +{ + impl_->DetachPeriodicTimerListener( listener ); +} + +void SocketReceiveMultiplexer::Run() +{ + impl_->Run(); +} + +void SocketReceiveMultiplexer::RunUntilSigInt() +{ + assert( multiplexerInstanceToAbortWithSigInt_ == 0 ); /* at present we support only one multiplexer instance running until sig int */ + multiplexerInstanceToAbortWithSigInt_ = this; + signal( SIGINT, InterruptSignalHandler ); + impl_->Run(); + signal( SIGINT, SIG_DFL ); + multiplexerInstanceToAbortWithSigInt_ = 0; +} + +void SocketReceiveMultiplexer::Break() +{ + impl_->Break(); +} + +void SocketReceiveMultiplexer::AsynchronousBreak() +{ + impl_->AsynchronousBreak(); +} + diff --git a/include/oscpack/ip/win32/NetworkingUtils.cpp b/include/oscpack/ip/win32/NetworkingUtils.cpp new file mode 100644 index 0000000..071a758 --- /dev/null +++ b/include/oscpack/ip/win32/NetworkingUtils.cpp @@ -0,0 +1,88 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "ip/NetworkingUtils.h" + +#include // this must come first to prevent errors with MSVC7 +#include +#include +#include + + +static LONG initCount_ = 0; +static bool winsockInitialized_ = false; + +NetworkInitializer::NetworkInitializer() +{ + if( InterlockedIncrement( &initCount_ ) == 1 ){ + // there is a race condition here if one thread tries to access + // the library while another is still initializing it. + // i can't think of an easy way to fix it so i'm telling you here + // incase you need to init the library from two threads at once. + // this is why the header file advises to instantiate one of these + // in main() so that the initialization happens globally + + // initialize winsock + WSAData wsaData; + int nCode = WSAStartup(MAKEWORD(1, 1), &wsaData); + if( nCode != 0 ){ + //std::cout << "WSAStartup() failed with error code " << nCode << "\n"; + }else{ + winsockInitialized_ = true; + } + } +} + + +NetworkInitializer::~NetworkInitializer() +{ + if( InterlockedDecrement( &initCount_ ) == 0 ){ + if( winsockInitialized_ ){ + WSACleanup(); + winsockInitialized_ = false; + } + } +} + + +unsigned long GetHostByName( const char *name ) +{ + NetworkInitializer networkInitializer; + + unsigned long result = 0; + + struct hostent *h = gethostbyname( name ); + if( h ){ + struct in_addr a; + memcpy( &a, h->h_addr_list[0], h->h_length ); + result = ntohl(a.s_addr); + } + + return result; +} diff --git a/include/oscpack/ip/win32/UdpSocket.cpp b/include/oscpack/ip/win32/UdpSocket.cpp new file mode 100644 index 0000000..8e64749 --- /dev/null +++ b/include/oscpack/ip/win32/UdpSocket.cpp @@ -0,0 +1,521 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "ip/UdpSocket.h" + +#include // this must come first to prevent errors with MSVC7 +#include +#include // for timeGetTime() + +#include +#include +#include +#include +#include + +#include "ip/NetworkingUtils.h" +#include "ip/PacketListener.h" +#include "ip/TimerListener.h" + + +typedef int socklen_t; + + +static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint ) +{ + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + sockAddr.sin_family = AF_INET; + + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) + ? INADDR_ANY + : htonl( endpoint.address ); + + sockAddr.sin_port = + (endpoint.port == IpEndpointName::ANY_PORT) + ? (short)0 + : htons( (short)endpoint.port ); +} + + +static IpEndpointName IpEndpointNameFromSockaddr( const struct sockaddr_in& sockAddr ) +{ + return IpEndpointName( + (sockAddr.sin_addr.s_addr == INADDR_ANY) + ? IpEndpointName::ANY_ADDRESS + : ntohl( sockAddr.sin_addr.s_addr ), + (sockAddr.sin_port == 0) + ? IpEndpointName::ANY_PORT + : ntohs( sockAddr.sin_port ) + ); +} + + +class UdpSocket::Implementation{ + NetworkInitializer networkInitializer_; + + bool isBound_; + bool isConnected_; + + SOCKET socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + +public: + + Implementation() + : isBound_( false ) + , isConnected_( false ) + , socket_( INVALID_SOCKET ) + { + if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET ){ + throw std::runtime_error("unable to create udp socket\n"); + } + + memset( &sendToAddr_, 0, sizeof(sendToAddr_) ); + sendToAddr_.sin_family = AF_INET; + } + + ~Implementation() + { + if (socket_ != INVALID_SOCKET) closesocket(socket_); + } + + IpEndpointName LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const + { + assert( isBound_ ); + + // first connect the socket to the remote server + + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName( connectSockAddr, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + // get the address + + struct sockaddr_in sockAddr; + memset( (char *)&sockAddr, 0, sizeof(sockAddr ) ); + socklen_t length = sizeof(sockAddr); + if (getsockname(socket_, (struct sockaddr *)&sockAddr, &length) < 0) { + throw std::runtime_error("unable to getsockname\n"); + } + + if( isConnected_ ){ + // reconnect to the connected address + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + }else{ + // unconnect from the remote address + + struct sockaddr_in unconnectSockAddr; + SockaddrFromIpEndpointName( unconnectSockAddr, IpEndpointName() ); + + if( connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr)) < 0 + && WSAGetLastError() != WSAEADDRNOTAVAIL ){ + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } + + return IpEndpointNameFromSockaddr( sockAddr ); + } + + void Connect( const IpEndpointName& remoteEndpoint ) + { + SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint ); + + if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + isConnected_ = true; + } + + void Send( const char *data, int size ) + { + assert( isConnected_ ); + + send( socket_, data, size, 0 ); + } + + void SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) + { + sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address ); + sendToAddr_.sin_port = htons( (short)remoteEndpoint.port ); + + sendto( socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) ); + } + + void Bind( const IpEndpointName& localEndpoint ) + { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName( bindSockAddr, localEndpoint ); + + if (bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } + + isBound_ = true; + } + + bool IsBound() const { return isBound_; } + + int ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) + { + assert( isBound_ ); + + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + int result = recvfrom(socket_, data, size, 0, + (struct sockaddr *) &fromAddr, (socklen_t*)&fromAddrLen); + if( result < 0 ) + return 0; + + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); + + return result; + } + + SOCKET& Socket() { return socket_; } +}; + +UdpSocket::UdpSocket() +{ + impl_ = new Implementation(); +} + +UdpSocket::~UdpSocket() +{ + delete impl_; +} + +IpEndpointName UdpSocket::LocalEndpointFor( const IpEndpointName& remoteEndpoint ) const +{ + return impl_->LocalEndpointFor( remoteEndpoint ); +} + +void UdpSocket::Connect( const IpEndpointName& remoteEndpoint ) +{ + impl_->Connect( remoteEndpoint ); +} + +void UdpSocket::Send( const char *data, int size ) +{ + impl_->Send( data, size ); +} + +void UdpSocket::SendTo( const IpEndpointName& remoteEndpoint, const char *data, int size ) +{ + impl_->SendTo( remoteEndpoint, data, size ); +} + +void UdpSocket::Bind( const IpEndpointName& localEndpoint ) +{ + impl_->Bind( localEndpoint ); +} + +bool UdpSocket::IsBound() const +{ + return impl_->IsBound(); +} + +int UdpSocket::ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, int size ) +{ + return impl_->ReceiveFrom( remoteEndpoint, data, size ); +} + + +struct AttachedTimerListener{ + AttachedTimerListener( int id, int p, TimerListener *tl ) + : initialDelayMs( id ) + , periodMs( p ) + , listener( tl ) {} + int initialDelayMs; + int periodMs; + TimerListener *listener; +}; + + +static bool CompareScheduledTimerCalls( + const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs ) +{ + return lhs.first < rhs.first; +} + + +SocketReceiveMultiplexer *multiplexerInstanceToAbortWithSigInt_ = 0; + +extern "C" /*static*/ void InterruptSignalHandler( int ); +/*static*/ void InterruptSignalHandler( int ) +{ + multiplexerInstanceToAbortWithSigInt_->AsynchronousBreak(); + signal( SIGINT, SIG_DFL ); +} + + +class SocketReceiveMultiplexer::Implementation{ + NetworkInitializer networkInitializer_; + + std::vector< std::pair< PacketListener*, UdpSocket* > > socketListeners_; + std::vector< AttachedTimerListener > timerListeners_; + + volatile bool break_; + HANDLE breakEvent_; + + double GetCurrentTimeMs() const + { + return timeGetTime(); // FIXME: bad choice if you want to run for more than 40 days + } + +public: + Implementation() + { + breakEvent_ = CreateEvent( NULL, FALSE, FALSE, NULL ); + } + + ~Implementation() + { + CloseHandle( breakEvent_ ); + } + + void AttachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + assert( std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ) == socketListeners_.end() ); + // we don't check that the same socket has been added multiple times, even though this is an error + socketListeners_.push_back( std::make_pair( listener, socket ) ); + } + + void DetachSocketListener( UdpSocket *socket, PacketListener *listener ) + { + std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = + std::find( socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket) ); + assert( i != socketListeners_.end() ); + + socketListeners_.erase( i ); + } + + void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( periodMilliseconds, periodMilliseconds, listener ) ); + } + + void AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) + { + timerListeners_.push_back( AttachedTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ) ); + } + + void DetachPeriodicTimerListener( TimerListener *listener ) + { + std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + while( i != timerListeners_.end() ){ + if( i->listener == listener ) + break; + ++i; + } + + assert( i != timerListeners_.end() ); + + timerListeners_.erase( i ); + } + + void Run() + { + break_ = false; + + // prepare the window events which we use to wake up on incoming data + // we use this instead of select() primarily to support the AsyncBreak() + // mechanism. + + std::vector events( socketListeners_.size() + 1, 0 ); + int j=0; + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i, ++j ){ + + HANDLE event = CreateEvent( NULL, FALSE, FALSE, NULL ); + WSAEventSelect( i->second->impl_->Socket(), event, FD_READ ); // note that this makes the socket non-blocking which is why we can safely call RecieveFrom() on all sockets below + events[j] = event; + } + + + events[ socketListeners_.size() ] = breakEvent_; // last event in the collection is the break event + + + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector< std::pair< double, AttachedTimerListener > > timerQueue_; + for( std::vector< AttachedTimerListener >::iterator i = timerListeners_.begin(); + i != timerListeners_.end(); ++i ) + timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) ); + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + + const int MAX_BUFFER_SIZE = 4098; + char *data = new char[ MAX_BUFFER_SIZE ]; + IpEndpointName remoteEndpoint; + + while( !break_ ){ + + double currentTimeMs = GetCurrentTimeMs(); + + DWORD waitTime = INFINITE; + if( !timerQueue_.empty() ){ + + waitTime = (DWORD)( timerQueue_.front().first >= currentTimeMs + ? timerQueue_.front().first - currentTimeMs + : 0 ); + } + + DWORD waitResult = WaitForMultipleObjects( (DWORD)socketListeners_.size() + 1, &events[0], FALSE, waitTime ); + if( break_ ) + break; + + if( waitResult != WAIT_TIMEOUT ){ + for( int i = waitResult - WAIT_OBJECT_0; i < (int)socketListeners_.size(); ++i ){ + int size = socketListeners_[i].second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE ); + if( size > 0 ){ + socketListeners_[i].first->ProcessPacket( data, size, remoteEndpoint ); + if( break_ ) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for( std::vector< std::pair< double, AttachedTimerListener > >::iterator i = timerQueue_.begin(); + i != timerQueue_.end() && i->first <= currentTimeMs; ++i ){ + + i->second.listener->TimerExpired(); + if( break_ ) + break; + + i->first += i->second.periodMs; + resort = true; + } + if( resort ) + std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + } + + delete [] data; + + // free events + j = 0; + for( std::vector< std::pair< PacketListener*, UdpSocket* > >::iterator i = socketListeners_.begin(); + i != socketListeners_.end(); ++i, ++j ){ + + WSAEventSelect( i->second->impl_->Socket(), events[j], 0 ); // remove association between socket and event + CloseHandle( events[j] ); + unsigned long enableNonblocking = 0; + ioctlsocket( i->second->impl_->Socket(), FIONBIO, &enableNonblocking ); // make the socket blocking again + } + } + + void Break() + { + break_ = true; + } + + void AsynchronousBreak() + { + break_ = true; + SetEvent( breakEvent_ ); + } +}; + + + +SocketReceiveMultiplexer::SocketReceiveMultiplexer() +{ + impl_ = new Implementation(); +} + +SocketReceiveMultiplexer::~SocketReceiveMultiplexer() +{ + delete impl_; +} + +void SocketReceiveMultiplexer::AttachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->AttachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::DetachSocketListener( UdpSocket *socket, PacketListener *listener ) +{ + impl_->DetachSocketListener( socket, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::AttachPeriodicTimerListener( int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) +{ + impl_->AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ); +} + +void SocketReceiveMultiplexer::DetachPeriodicTimerListener( TimerListener *listener ) +{ + impl_->DetachPeriodicTimerListener( listener ); +} + +void SocketReceiveMultiplexer::Run() +{ + impl_->Run(); +} + +void SocketReceiveMultiplexer::RunUntilSigInt() +{ + assert( multiplexerInstanceToAbortWithSigInt_ == 0 ); /* at present we support only one multiplexer instance running until sig int */ + multiplexerInstanceToAbortWithSigInt_ = this; + signal( SIGINT, InterruptSignalHandler ); + impl_->Run(); + signal( SIGINT, SIG_DFL ); + multiplexerInstanceToAbortWithSigInt_ = 0; +} + +void SocketReceiveMultiplexer::Break() +{ + impl_->Break(); +} + +void SocketReceiveMultiplexer::AsynchronousBreak() +{ + impl_->AsynchronousBreak(); +} + diff --git a/include/oscpack/make.MinGW32.bat b/include/oscpack/make.MinGW32.bat new file mode 100644 index 0000000..fb9d851 --- /dev/null +++ b/include/oscpack/make.MinGW32.bat @@ -0,0 +1,19 @@ +del bin\OscUnitTests.exe +del bin\OscDump.exe +del bin\OscSendTests.exe +del bin\OscReceiveTest.exe +mkdir bin + +g++ tests\OscUnitTests.cpp osc\OscTypes.cpp osc\OscReceivedElements.cpp osc\OscPrintReceivedElements.cpp osc\OscOutboundPacketStream.cpp -Wall -I. -lws2_32 -o bin\OscUnitTests.exe + +g++ examples\OscDump.cpp osc\OscTypes.cpp osc\OscReceivedElements.cpp osc\OscPrintReceivedElements.cpp ip\win32\NetworkingUtils.cpp ip\win32\UdpSocket.cpp -Wall -I. -lws2_32 -lwinmm -o bin\OscDump.exe + +g++ examples\SimpleSend.cpp osc\OscTypes.cpp osc\OscOutboundPacketStream.cpp ip\win32\NetworkingUtils.cpp ip\win32\UdpSocket.cpp ip\IpEndpointName.cpp -Wall -I. -lws2_32 -lwinmm -o bin\SimpleSend.exe + +g++ examples\SimpleReceive.cpp osc\OscTypes.cpp osc\OscReceivedElements.cpp ip\win32\NetworkingUtils.cpp ip\win32\UdpSocket.cpp -Wall -I. -lws2_32 -lwinmm -o bin\SimpleReceive.exe + +g++ tests\OscSendTests.cpp osc\OscTypes.cpp osc\OscOutboundPacketStream.cpp ip\win32\NetworkingUtils.cpp ip\win32\UdpSocket.cpp ip\IpEndpointName.cpp -Wall -I. -lws2_32 -lwinmm -o bin\OscSendTests.exe + +g++ tests\OscReceiveTest.cpp osc\OscTypes.cpp osc\OscReceivedElements.cpp ip\win32\NetworkingUtils.cpp ip\win32\UdpSocket.cpp -Wall -I. -lws2_32 -lwinmm -o bin\OscReceiveTest.exe + +.\bin\OscUnitTests.exe \ No newline at end of file diff --git a/include/oscpack/osc/MessageMappingOscPacketListener.h b/include/oscpack/osc/MessageMappingOscPacketListener.h new file mode 100644 index 0000000..017bf05 --- /dev/null +++ b/include/oscpack/osc/MessageMappingOscPacketListener.h @@ -0,0 +1,73 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H +#define INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H + +#include +#include + +#include "OscPacketListener.h" + + + +namespace osc{ + +template< class T > +class MessageMappingOscPacketListener : public OscPacketListener{ +public: + typedef void (T::*function_type)(const osc::ReceivedMessage&, const IpEndpointName&); + +protected: + void RegisterMessageFunction( const char *addressPattern, function_type f ) + { + functions_.insert( std::make_pair( addressPattern, f ) ); + } + + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) + { + typename function_map_type::iterator i = functions_.find( m.AddressPattern() ); + if( i != functions_.end() ) + (dynamic_cast(this)->*(i->second))( m, remoteEndpoint ); + } + +private: + struct cstr_compare{ + bool operator()( const char *lhs, const char *rhs ) const + { return strcmp( lhs, rhs ) < 0; } + }; + + typedef std::map function_map_type; + function_map_type functions_; +}; + +} // namespace osc + +#endif /* INCLUDED_MESSAGEMAPPINGOSCPACKETLISTENER_H */ \ No newline at end of file diff --git a/include/oscpack/osc/OscException.h b/include/oscpack/osc/OscException.h new file mode 100644 index 0000000..cd8d567 --- /dev/null +++ b/include/oscpack/osc/OscException.h @@ -0,0 +1,54 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSC_EXCEPTION_H +#define INCLUDED_OSC_EXCEPTION_H + +#include + +namespace osc{ + +class Exception : public std::exception { + const char *what_; + +public: + Exception() throw() {} + Exception( const Exception& src ) throw() + : what_( src.what_ ) {} + Exception( const char *w ) throw() + : what_( w ) {} + Exception& operator=( const Exception& src ) throw() + { what_ = src.what_; return *this; } + virtual ~Exception() throw() {} + virtual const char* what() const throw() { return what_; } +}; + +} // namespace osc + +#endif /* INCLUDED_OSC_EXCEPTION_H */ diff --git a/include/oscpack/osc/OscHostEndianness.h b/include/oscpack/osc/OscHostEndianness.h new file mode 100644 index 0000000..ba1f3f2 --- /dev/null +++ b/include/oscpack/osc/OscHostEndianness.h @@ -0,0 +1,73 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef OSC_HOSTENDIANNESS_H +#define OSC_HOSTENDIANNESS_H + +/* + Make sure either OSC_HOST_LITTLE_ENDIAN or OSC_HOST_BIG_ENDIAN is defined + + If you know a way to enhance the detection below for Linux and/or MacOSX + please let me know! I've tried a few things which don't work. +*/ + +#if defined(OSC_HOST_LITTLE_ENDIAN) || defined(OSC_HOST_BIG_ENDIAN) + +// you can define one of the above symbols from the command line +// then you don't have to edit this file. + +#elif defined(__WIN32__) || defined(WIN32) + +// assume that __WIN32__ is only defined on little endian systems + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN + +#elif defined(__APPLE__) + +#if defined(__LITTLE_ENDIAN__) +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN +#else +#define OSC_HOST_BIG_ENDIAN 1 +#undef OSC_HOST_LITTLE_ENDIAN +#endif + +#else + +#define OSC_HOST_LITTLE_ENDIAN 1 +#undef OSC_HOST_BIG_ENDIAN +#endif +#endif /* OSC_HOSTENDIANNESS_H */ + +#if (!defined(OSC_HOST_LITTLE_ENDIAN) && !defined(OSC_HOST_BIG_ENDIAN)) +#error please edit OSCHostEndianness.h to configure endianness + +#endif + diff --git a/include/oscpack/osc/OscOutboundPacketStream.cpp b/include/oscpack/osc/OscOutboundPacketStream.cpp new file mode 100644 index 0000000..75b1800 --- /dev/null +++ b/include/oscpack/osc/OscOutboundPacketStream.cpp @@ -0,0 +1,639 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscOutboundPacketStream.h" + +#include +#include +#include + +#if defined(__WIN32__) || defined(WIN32) +#include // for alloca +#endif + +#include "OscHostEndianness.h" + + +namespace osc{ + +static void FromInt32( char *p, int32 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast(p) = x; +#endif +} + + +static void FromUInt32( char *p, uint32 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint32 i; + char c[4]; + } u; + + u.i = x; + + p[3] = u.c[0]; + p[2] = u.c[1]; + p[1] = u.c[2]; + p[0] = u.c[3]; +#else + *reinterpret_cast(p) = x; +#endif +} + + +static void FromInt64( char *p, int64 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast(p) = x; +#endif +} + + +static void FromUInt64( char *p, uint64 x ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint64 i; + char c[8]; + } u; + + u.i = x; + + p[7] = u.c[0]; + p[6] = u.c[1]; + p[5] = u.c[2]; + p[4] = u.c[3]; + p[3] = u.c[4]; + p[2] = u.c[5]; + p[1] = u.c[6]; + p[0] = u.c[7]; +#else + *reinterpret_cast(p) = x; +#endif +} + + +static inline long RoundUp4( long x ) +{ + return ((x-1) & (~0x03L)) + 4; +} + + +OutboundPacketStream::OutboundPacketStream( char *buffer, unsigned long capacity ) + : data_( buffer ) + , end_( data_ + capacity ) + , typeTagsCurrent_( end_ ) + , messageCursor_( data_ ) + , argumentCurrent_( data_ ) + , elementSizePtr_( 0 ) + , messageIsInProgress_( false ) +{ + +} + + +OutboundPacketStream::~OutboundPacketStream() +{ + +} + + +char *OutboundPacketStream::BeginElement( char *beginPtr ) +{ + if( elementSizePtr_ == 0 ){ + + elementSizePtr_ = reinterpret_cast(data_); + + return beginPtr; + + }else{ + // store an offset to the old element size ptr in the element size slot + // we store an offset rather than the actual pointer to be 64 bit clean. + *reinterpret_cast(beginPtr) = + (uint32)(reinterpret_cast(elementSizePtr_) - data_); + + elementSizePtr_ = reinterpret_cast(beginPtr); + + return beginPtr + 4; + } +} + + +void OutboundPacketStream::EndElement( char *endPtr ) +{ + assert( elementSizePtr_ != 0 ); + + if( elementSizePtr_ == reinterpret_cast(data_) ){ + + elementSizePtr_ = 0; + + }else{ + // while building an element, an offset to the containing element's + // size slot is stored in the elements size slot (or a ptr to data_ + // if there is no containing element). We retrieve that here + uint32 *previousElementSizePtr = + (uint32*)(data_ + *reinterpret_cast(elementSizePtr_)); + + // then we store the element size in the slot, note that the element + // size does not include the size slot, hence the - 4 below. + uint32 elementSize = + (endPtr - reinterpret_cast(elementSizePtr_)) - 4; + FromUInt32( reinterpret_cast(elementSizePtr_), elementSize ); + + // finally, we reset the element size ptr to the containing element + elementSizePtr_ = previousElementSizePtr; + } +} + + +bool OutboundPacketStream::ElementSizeSlotRequired() const +{ + return (elementSizePtr_ != 0); +} + + +void OutboundPacketStream::CheckForAvailableBundleSpace() +{ + unsigned long required = Size() + ((ElementSizeSlotRequired())?4:0) + 16; + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::CheckForAvailableMessageSpace( const char *addressPattern ) +{ + // plus 4 for at least four bytes of type tag + unsigned long required = Size() + ((ElementSizeSlotRequired())?4:0) + + RoundUp4(strlen(addressPattern) + 1) + 4; + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::CheckForAvailableArgumentSpace( long argumentLength ) +{ + // plus three for extra type tag, comma and null terminator + unsigned long required = (argumentCurrent_ - data_) + argumentLength + + RoundUp4( (end_ - typeTagsCurrent_) + 3 ); + + if( required > Capacity() ) + throw OutOfBufferMemoryException(); +} + + +void OutboundPacketStream::Clear() +{ + typeTagsCurrent_ = end_; + messageCursor_ = data_; + argumentCurrent_ = data_; + elementSizePtr_ = 0; + messageIsInProgress_ = false; +} + + +unsigned int OutboundPacketStream::Capacity() const +{ + return end_ - data_; +} + + +unsigned int OutboundPacketStream::Size() const +{ + unsigned int result = argumentCurrent_ - data_; + if( IsMessageInProgress() ){ + // account for the length of the type tag string. the total type tag + // includes an initial comma, plus at least one terminating \0 + result += RoundUp4( (end_ - typeTagsCurrent_) + 2 ); + } + + return result; +} + + +const char *OutboundPacketStream::Data() const +{ + return data_; +} + + +bool OutboundPacketStream::IsReady() const +{ + return (!IsMessageInProgress() && !IsBundleInProgress()); +} + + +bool OutboundPacketStream::IsMessageInProgress() const +{ + return messageIsInProgress_; +} + + +bool OutboundPacketStream::IsBundleInProgress() const +{ + return (elementSizePtr_ != 0); +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BundleInitiator& rhs ) +{ + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + CheckForAvailableBundleSpace(); + + messageCursor_ = BeginElement( messageCursor_ ); + + memcpy( messageCursor_, "#bundle\0", 8 ); + FromUInt64( messageCursor_ + 8, rhs.timeTag ); + + messageCursor_ += 16; + argumentCurrent_ = messageCursor_; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BundleTerminator& rhs ) +{ + (void) rhs; + + if( !IsBundleInProgress() ) + throw BundleNotInProgressException(); + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + EndElement( messageCursor_ ); + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const BeginMessage& rhs ) +{ + if( IsMessageInProgress() ) + throw MessageInProgressException(); + + CheckForAvailableMessageSpace( rhs.addressPattern ); + + messageCursor_ = BeginElement( messageCursor_ ); + + strcpy( messageCursor_, rhs.addressPattern ); + unsigned long rhsLength = strlen(rhs.addressPattern); + messageCursor_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *messageCursor_++ = '\0'; + ++i; + } + + argumentCurrent_ = messageCursor_; + typeTagsCurrent_ = end_; + + messageIsInProgress_ = true; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const MessageTerminator& rhs ) +{ + (void) rhs; + + if( !IsMessageInProgress() ) + throw MessageNotInProgressException(); + + int typeTagsCount = end_ - typeTagsCurrent_; + + if( typeTagsCount ){ + + char *tempTypeTags = (char*)alloca(typeTagsCount); + memcpy( tempTypeTags, typeTagsCurrent_, typeTagsCount ); + + // slot size includes comma and null terminator + int typeTagSlotSize = RoundUp4( typeTagsCount + 2 ); + + uint32 argumentsSize = argumentCurrent_ - messageCursor_; + + memmove( messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize ); + + messageCursor_[0] = ','; + // copy type tags in reverse (really forward) order + for( int i=0; i < typeTagsCount; ++i ) + messageCursor_[i+1] = tempTypeTags[ (typeTagsCount-1) - i ]; + + char *p = messageCursor_ + 1 + typeTagsCount; + for( int i=0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i ) + *p++ = '\0'; + + typeTagsCurrent_ = end_; + + // advance messageCursor_ for next message + messageCursor_ += typeTagSlotSize + argumentsSize; + + }else{ + // send an empty type tags string + memcpy( messageCursor_, ",\0\0\0", 4 ); + + // advance messageCursor_ for next message + messageCursor_ += 4; + } + + argumentCurrent_ = messageCursor_; + + EndElement( messageCursor_ ); + + messageIsInProgress_ = false; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( bool rhs ) +{ + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG); + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const NilType& rhs ) +{ + (void) rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = NIL_TYPE_TAG; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const InfinitumType& rhs ) +{ + (void) rhs; + CheckForAvailableArgumentSpace(0); + + *(--typeTagsCurrent_) = INFINITUM_TYPE_TAG; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( int32 rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = INT32_TYPE_TAG; + FromInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( float rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = FLOAT_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + float f; + char c[4]; + } u; + + u.f = rhs; + + argumentCurrent_[3] = u.c[0]; + argumentCurrent_[2] = u.c[1]; + argumentCurrent_[1] = u.c[2]; + argumentCurrent_[0] = u.c[3]; +#else + *reinterpret_cast(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( char rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = CHAR_TYPE_TAG; + FromInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const RgbaColor& rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const MidiMessage& rhs ) +{ + CheckForAvailableArgumentSpace(4); + + *(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs ); + argumentCurrent_ += 4; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( int64 rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = INT64_TYPE_TAG; + FromInt64( argumentCurrent_, rhs ); + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const TimeTag& rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG; + FromUInt64( argumentCurrent_, rhs ); + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( double rhs ) +{ + CheckForAvailableArgumentSpace(8); + + *(--typeTagsCurrent_) = DOUBLE_TYPE_TAG; + +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + double f; + char c[8]; + } u; + + u.f = rhs; + + argumentCurrent_[7] = u.c[0]; + argumentCurrent_[6] = u.c[1]; + argumentCurrent_[5] = u.c[2]; + argumentCurrent_[4] = u.c[3]; + argumentCurrent_[3] = u.c[4]; + argumentCurrent_[2] = u.c[5]; + argumentCurrent_[1] = u.c[6]; + argumentCurrent_[0] = u.c[7]; +#else + *reinterpret_cast(argumentCurrent_) = rhs; +#endif + + argumentCurrent_ += 8; + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const char *rhs ) +{ + CheckForAvailableArgumentSpace( RoundUp4(strlen(rhs) + 1) ); + + *(--typeTagsCurrent_) = STRING_TYPE_TAG; + strcpy( argumentCurrent_, rhs ); + unsigned long rhsLength = strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const Symbol& rhs ) +{ + CheckForAvailableArgumentSpace( RoundUp4(strlen(rhs) + 1) ); + + *(--typeTagsCurrent_) = SYMBOL_TYPE_TAG; + strcpy( argumentCurrent_, rhs ); + unsigned long rhsLength = strlen(rhs); + argumentCurrent_ += rhsLength + 1; + + // zero pad to 4-byte boundary + unsigned long i = rhsLength + 1; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + + +OutboundPacketStream& OutboundPacketStream::operator<<( const Blob& rhs ) +{ + CheckForAvailableArgumentSpace( 4 + RoundUp4(rhs.size) ); + + *(--typeTagsCurrent_) = BLOB_TYPE_TAG; + FromUInt32( argumentCurrent_, rhs.size ); + argumentCurrent_ += 4; + + memcpy( argumentCurrent_, rhs.data, rhs.size ); + argumentCurrent_ += rhs.size; + + // zero pad to 4-byte boundary + unsigned long i = rhs.size; + while( i & 0x3 ){ + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; +} + +} // namespace osc + + diff --git a/include/oscpack/osc/OscOutboundPacketStream.h b/include/oscpack/osc/OscOutboundPacketStream.h new file mode 100644 index 0000000..317e4b2 --- /dev/null +++ b/include/oscpack/osc/OscOutboundPacketStream.h @@ -0,0 +1,142 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCOUTBOUNDPACKET_H +#define INCLUDED_OSCOUTBOUNDPACKET_H + +#include "OscTypes.h" +#include "OscException.h" + + +namespace osc{ + +class OutOfBufferMemoryException : public Exception{ +public: + OutOfBufferMemoryException( const char *w="out of buffer memory" ) + : Exception( w ) {} +}; + +class BundleNotInProgressException : public Exception{ +public: + BundleNotInProgressException( + const char *w="call to EndBundle when bundle is not in progress" ) + : Exception( w ) {} +}; + +class MessageInProgressException : public Exception{ +public: + MessageInProgressException( + const char *w="opening or closing bundle or message while message is in progress" ) + : Exception( w ) {} +}; + +class MessageNotInProgressException : public Exception{ +public: + MessageNotInProgressException( + const char *w="call to EndMessage when message is not in progress" ) + : Exception( w ) {} +}; + + +class OutboundPacketStream{ +public: + OutboundPacketStream( char *buffer, unsigned long capacity ); + ~OutboundPacketStream(); + + void Clear(); + + unsigned int Capacity() const; + + // invariant: size() is valid even while building a message. + unsigned int Size() const; + + const char *Data() const; + + // indicates that all messages have been closed with a matching EndMessage + // and all bundles have been closed with a matching EndBundle + bool IsReady() const; + + bool IsMessageInProgress() const; + bool IsBundleInProgress() const; + + OutboundPacketStream& operator<<( const BundleInitiator& rhs ); + OutboundPacketStream& operator<<( const BundleTerminator& rhs ); + + OutboundPacketStream& operator<<( const BeginMessage& rhs ); + OutboundPacketStream& operator<<( const MessageTerminator& rhs ); + + OutboundPacketStream& operator<<( bool rhs ); + OutboundPacketStream& operator<<( const NilType& rhs ); + OutboundPacketStream& operator<<( const InfinitumType& rhs ); + OutboundPacketStream& operator<<( int32 rhs ); + +#ifndef x86_64 + OutboundPacketStream& operator<<( int rhs ) + { *this << (int32)rhs; return *this; } +#endif + + OutboundPacketStream& operator<<( float rhs ); + OutboundPacketStream& operator<<( char rhs ); + OutboundPacketStream& operator<<( const RgbaColor& rhs ); + OutboundPacketStream& operator<<( const MidiMessage& rhs ); + OutboundPacketStream& operator<<( int64 rhs ); + OutboundPacketStream& operator<<( const TimeTag& rhs ); + OutboundPacketStream& operator<<( double rhs ); + OutboundPacketStream& operator<<( const char* rhs ); + OutboundPacketStream& operator<<( const Symbol& rhs ); + OutboundPacketStream& operator<<( const Blob& rhs ); + +private: + + char *BeginElement( char *beginPtr ); + void EndElement( char *endPtr ); + + bool ElementSizeSlotRequired() const; + void CheckForAvailableBundleSpace(); + void CheckForAvailableMessageSpace( const char *addressPattern ); + void CheckForAvailableArgumentSpace( long argumentLength ); + + char *data_; + char *end_; + + char *typeTagsCurrent_; // stored in reverse order + char *messageCursor_; + char *argumentCurrent_; + + // elementSizePtr_ has two special values: 0 indicates that a bundle + // isn't open, and elementSizePtr_==data_ indicates that a bundle is + // open but that it doesn't have a size slot (ie the outermost bundle) + uint32 *elementSizePtr_; + + bool messageIsInProgress_; +}; + +} // namespace osc + +#endif /* INCLUDED_OSC_OUTBOUND_PACKET_H */ diff --git a/include/oscpack/osc/OscPacketListener.h b/include/oscpack/osc/OscPacketListener.h new file mode 100644 index 0000000..bc322b7 --- /dev/null +++ b/include/oscpack/osc/OscPacketListener.h @@ -0,0 +1,72 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCPACKETLISTENER_H +#define INCLUDED_OSCPACKETLISTENER_H + +#include "OscReceivedElements.h" +#include "../ip/PacketListener.h" + + +namespace osc{ + +class OscPacketListener : public PacketListener{ +protected: + virtual void ProcessBundle( const osc::ReceivedBundle& b, + const IpEndpointName& remoteEndpoint ) + { + // ignore bundle time tag for now + + for( ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); ++i ){ + if( i->IsBundle() ) + ProcessBundle( ReceivedBundle(*i), remoteEndpoint ); + else + ProcessMessage( ReceivedMessage(*i), remoteEndpoint ); + } + } + + virtual void ProcessMessage( const osc::ReceivedMessage& m, + const IpEndpointName& remoteEndpoint ) = 0; + +public: + virtual void ProcessPacket( const char *data, int size, + const IpEndpointName& remoteEndpoint ) + { + osc::ReceivedPacket p( data, size ); + if( p.IsBundle() ) + ProcessBundle( ReceivedBundle(p), remoteEndpoint ); + else + ProcessMessage( ReceivedMessage(p), remoteEndpoint ); + } +}; + +} // namespace osc + +#endif /* INCLUDED_OSCPACKETLISTENER_H */ diff --git a/include/oscpack/osc/OscPrintReceivedElements.cpp b/include/oscpack/osc/OscPrintReceivedElements.cpp new file mode 100644 index 0000000..44488c2 --- /dev/null +++ b/include/oscpack/osc/OscPrintReceivedElements.cpp @@ -0,0 +1,240 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscPrintReceivedElements.h" + +#include +#include +#include +#include + +namespace osc{ + + +std::ostream& operator<<( std::ostream & os, + const ReceivedMessageArgument& arg ) +{ + switch( arg.TypeTag() ){ + case TRUE_TYPE_TAG: + os << "bool:true"; + break; + + case FALSE_TYPE_TAG: + os << "bool:false"; + break; + + case NIL_TYPE_TAG: + os << "(Nil)"; + break; + + case INFINITUM_TYPE_TAG: + os << "(Infinitum)"; + break; + + case INT32_TYPE_TAG: + os << "int32:" << arg.AsInt32Unchecked(); + break; + + case FLOAT_TYPE_TAG: + os << "float32:" << arg.AsFloatUnchecked(); + break; + + case CHAR_TYPE_TAG: + { + char s[2] = {0}; + s[0] = arg.AsCharUnchecked(); + os << "char:'" << s << "'"; + } + break; + + case RGBA_COLOR_TYPE_TAG: + { + uint32 color = arg.AsRgbaColorUnchecked(); + + os << "RGBA:0x" + << std::hex << std::setfill('0') + << std::setw(2) << (int)((color>>24) & 0xFF) + << std::setw(2) << (int)((color>>16) & 0xFF) + << std::setw(2) << (int)((color>>8) & 0xFF) + << std::setw(2) << (int)(color & 0xFF) + << std::setfill(' '); + os.unsetf(std::ios::basefield); + } + break; + + case MIDI_MESSAGE_TYPE_TAG: + { + uint32 m = arg.AsMidiMessageUnchecked(); + os << "midi (port, status, data1, data2):<<" + << std::hex << std::setfill('0') + << "0x" << std::setw(2) << (int)((m>>24) & 0xFF) + << " 0x" << std::setw(2) << (int)((m>>16) & 0xFF) + << " 0x" << std::setw(2) << (int)((m>>8) & 0xFF) + << " 0x" << std::setw(2) << (int)(m & 0xFF) + << std::setfill(' ') << ">>"; + os.unsetf(std::ios::basefield); + } + break; + + case INT64_TYPE_TAG: + os << "int64:" << arg.AsInt64Unchecked(); + break; + + case TIME_TAG_TYPE_TAG: + { + os << "OSC-timetag:" << arg.AsTimeTagUnchecked(); + + std::time_t t = + (unsigned long)( arg.AsTimeTagUnchecked() >> 32 ); + + // strip trailing newline from string returned by ctime + const char *timeString = std::ctime( &t ); + size_t len = strlen( timeString ); + char *s = new char[ len + 1 ]; + strcpy( s, timeString ); + if( len ) + s[ len - 1 ] = '\0'; + + os << " " << s; + } + break; + + case DOUBLE_TYPE_TAG: + os << "double:" << arg.AsDoubleUnchecked(); + break; + + case STRING_TYPE_TAG: + os << "OSC-string:`" << arg.AsStringUnchecked() << "'"; + break; + + case SYMBOL_TYPE_TAG: + os << "OSC-string (symbol):`" << arg.AsSymbolUnchecked() << "'"; + break; + + case BLOB_TYPE_TAG: + { + unsigned long size; + const void *data; + arg.AsBlobUnchecked( data, size ); + os << "OSC-blob:<<" << std::hex << std::setfill('0'); + unsigned char *p = (unsigned char*)data; + for( unsigned long i = 0; i < size; ++i ){ + os << "0x" << std::setw(2) << int(p[i]); + if( i != size-1 ) + os << ' '; + } + os.unsetf(std::ios::basefield); + os << ">>" << std::setfill(' '); + } + break; + + default: + os << "unknown"; + } + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m ) +{ + + os << "[" << m.AddressPattern(); + bool first = true; + + for( ReceivedMessage::const_iterator i = m.ArgumentsBegin(); + i != m.ArgumentsEnd(); ++i ){ + if( first ){ + os << " "; + first = false; + }else{ + os << ", "; + } + + os << *i; + } + + os << "]"; + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b ) +{ + static int indent = 0; + + for( int j=0; j < indent; ++j ) + os << " "; + os << "{ ( "; + if( b.TimeTag() == 1 ) + os << "immediate"; + else + os << b.TimeTag(); + os << " )\n"; + + ++indent; + + for( ReceivedBundle::const_iterator i = b.ElementsBegin(); + i != b.ElementsEnd(); ++i ){ + if( i->IsBundle() ){ + ReceivedBundle b(*i); + os << b << "\n"; + }else{ + ReceivedMessage m(*i); + for( int j=0; j < indent; ++j ) + os << " "; + os << m << "\n"; + } + } + + --indent; + + for( int j=0; j < indent; ++j ) + os << " "; + os << "}"; + + return os; +} + + +std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p ) +{ + if( p.IsBundle() ){ + ReceivedBundle b(p); + os << b << "\n"; + }else{ + ReceivedMessage m(p); + os << m << "\n"; + } + + return os; +} + +} // namespace osc diff --git a/include/oscpack/osc/OscPrintReceivedElements.h b/include/oscpack/osc/OscPrintReceivedElements.h new file mode 100644 index 0000000..c42cfa5 --- /dev/null +++ b/include/oscpack/osc/OscPrintReceivedElements.h @@ -0,0 +1,49 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCPRINTRECEIVEDELEMENTS_H +#define INCLUDED_OSCPRINTRECEIVEDELEMENTS_H + +#include + +#ifndef INCLUDED_OSCRECEIVEDELEMENTS_H +#include "OscReceivedElements.h" +#endif /* INCLUDED_OSCRECEIVEDELEMENTS_H */ + + +namespace osc{ + +std::ostream& operator<<( std::ostream & os, const ReceivedPacket& p ); +std::ostream& operator<<( std::ostream & os, const ReceivedMessageArgument& arg ); +std::ostream& operator<<( std::ostream & os, const ReceivedMessage& m ); +std::ostream& operator<<( std::ostream & os, const ReceivedBundle& b ); + +} // namespace osc + +#endif /* INCLUDED_OSCPRINTRECEIVEDELEMENTS_H */ diff --git a/include/oscpack/osc/OscReceivedElements.cpp b/include/oscpack/osc/OscReceivedElements.cpp new file mode 100644 index 0000000..326fd62 --- /dev/null +++ b/include/oscpack/osc/OscReceivedElements.cpp @@ -0,0 +1,722 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscReceivedElements.h" + +#include + +#include "OscHostEndianness.h" + + +namespace osc{ + + +// return the first 4 byte boundary after the end of a str4 +// be careful about calling this version if you don't know whether +// the string is terminated correctly. +static inline const char* FindStr4End( const char *p ) +{ + if( p[0] == '\0' ) // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + + while( *p ) + p += 4; + + return p + 1; +} + + +// return the first 4 byte boundary after the end of a str4 +// returns 0 if p == end or if the string is unterminated +static inline const char* FindStr4End( const char *p, const char *end ) +{ + if( p >= end ) + return 0; + + if( p[0] == '\0' ) // special case for SuperCollider integer address pattern + return p + 4; + + p += 3; + end -= 1; + + while( p < end && *p ) + p += 4; + + if( *p ) + return 0; + else + return p + 1; +} + + +static inline unsigned long RoundUp4( unsigned long x ) +{ + unsigned long remainder = x & 0x3UL; + if( remainder ) + return x + (4 - remainder); + else + return x; +} + + +static inline int32 ToInt32( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(int32*)p; +#endif +} + + +static inline uint32 ToUInt32( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint32 i; + char c[4]; + } u; + + u.c[0] = p[3]; + u.c[1] = p[2]; + u.c[2] = p[1]; + u.c[3] = p[0]; + + return u.i; +#else + return *(uint32*)p; +#endif +} + + +int64 ToInt64( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int64 i; + char c[4]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(int64*)p; +#endif +} + + +uint64 ToUInt64( const char *p ) +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::uint64 i; + char c[4]; + } u; + + u.c[0] = p[7]; + u.c[1] = p[6]; + u.c[2] = p[5]; + u.c[3] = p[4]; + u.c[4] = p[3]; + u.c[5] = p[2]; + u.c[6] = p[1]; + u.c[7] = p[0]; + + return u.i; +#else + return *(uint64*)p; +#endif +} + +//------------------------------------------------------------------------------ + +bool ReceivedPacket::IsBundle() const +{ + return (Size() > 0 && Contents()[0] == '#'); +} + +//------------------------------------------------------------------------------ + +bool ReceivedBundleElement::IsBundle() const +{ + return (Size() > 0 && Contents()[0] == '#'); +} + + +int32 ReceivedBundleElement::Size() const +{ + return ToUInt32( size_ ); +} + +//------------------------------------------------------------------------------ + +bool ReceivedMessageArgument::AsBool() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TRUE_TYPE_TAG ) + return true; + else if( *typeTag_ == FALSE_TYPE_TAG ) + return false; + else + throw WrongArgumentTypeException(); +} + + +bool ReceivedMessageArgument::AsBoolUnchecked() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TRUE_TYPE_TAG ) + return true; + else + return false; +} + + +int32 ReceivedMessageArgument::AsInt32() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == INT32_TYPE_TAG ) + return AsInt32Unchecked(); + else + throw WrongArgumentTypeException(); +} + + +int32 ReceivedMessageArgument::AsInt32Unchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + osc::int32 i; + char c[4]; + } u; + + u.c[0] = argument_[3]; + u.c[1] = argument_[2]; + u.c[2] = argument_[1]; + u.c[3] = argument_[0]; + + return u.i; +#else + return *(int32*)argument_; +#endif +} + + +float ReceivedMessageArgument::AsFloat() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == FLOAT_TYPE_TAG ) + return AsFloatUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +float ReceivedMessageArgument::AsFloatUnchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + float f; + char c[4]; + } u; + + u.c[0] = argument_[3]; + u.c[1] = argument_[2]; + u.c[2] = argument_[1]; + u.c[3] = argument_[0]; + + return u.f; +#else + return *(float*)argument_; +#endif +} + + +char ReceivedMessageArgument::AsChar() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == CHAR_TYPE_TAG ) + return AsCharUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +char ReceivedMessageArgument::AsCharUnchecked() const +{ + return (char)ToInt32( argument_ ); +} + + +uint32 ReceivedMessageArgument::AsRgbaColor() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == RGBA_COLOR_TYPE_TAG ) + return AsRgbaColorUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint32 ReceivedMessageArgument::AsRgbaColorUnchecked() const +{ + return ToUInt32( argument_ ); +} + + +uint32 ReceivedMessageArgument::AsMidiMessage() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == MIDI_MESSAGE_TYPE_TAG ) + return AsMidiMessageUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint32 ReceivedMessageArgument::AsMidiMessageUnchecked() const +{ + return ToUInt32( argument_ ); +} + + +int64 ReceivedMessageArgument::AsInt64() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == INT64_TYPE_TAG ) + return AsInt64Unchecked(); + else + throw WrongArgumentTypeException(); +} + + +int64 ReceivedMessageArgument::AsInt64Unchecked() const +{ + return ToInt64( argument_ ); +} + + +uint64 ReceivedMessageArgument::AsTimeTag() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == TIME_TAG_TYPE_TAG ) + return AsTimeTagUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +uint64 ReceivedMessageArgument::AsTimeTagUnchecked() const +{ + return ToUInt64( argument_ ); +} + + +double ReceivedMessageArgument::AsDouble() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == DOUBLE_TYPE_TAG ) + return AsDoubleUnchecked(); + else + throw WrongArgumentTypeException(); +} + + +double ReceivedMessageArgument::AsDoubleUnchecked() const +{ +#ifdef OSC_HOST_LITTLE_ENDIAN + union{ + double d; + char c[8]; + } u; + + u.c[0] = argument_[7]; + u.c[1] = argument_[6]; + u.c[2] = argument_[5]; + u.c[3] = argument_[4]; + u.c[4] = argument_[3]; + u.c[5] = argument_[2]; + u.c[6] = argument_[1]; + u.c[7] = argument_[0]; + + return u.d; +#else + return *(double*)argument_; +#endif +} + + +const char* ReceivedMessageArgument::AsString() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == STRING_TYPE_TAG ) + return argument_; + else + throw WrongArgumentTypeException(); +} + + +const char* ReceivedMessageArgument::AsSymbol() const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == SYMBOL_TYPE_TAG ) + return argument_; + else + throw WrongArgumentTypeException(); +} + + +void ReceivedMessageArgument::AsBlob( const void*& data, unsigned long& size ) const +{ + if( !typeTag_ ) + throw MissingArgumentException(); + else if( *typeTag_ == BLOB_TYPE_TAG ) + AsBlobUnchecked( data, size ); + else + throw WrongArgumentTypeException(); +} + + +void ReceivedMessageArgument::AsBlobUnchecked( const void*& data, unsigned long& size ) const +{ + size = ToUInt32( argument_ ); + data = (void*)(argument_+4); +} + +//------------------------------------------------------------------------------ + +void ReceivedMessageArgumentIterator::Advance() +{ + if( !value_.typeTag_ ) + return; + + switch( *value_.typeTag_++ ){ + case '\0': + // don't advance past end + --value_.typeTag_; + break; + + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + + // zero length + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + value_.argument_ += 4; + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + value_.argument_ += 8; + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + // we use the unsafe function FindStr4End(char*) here because all of + // the arguments have already been validated in + // ReceivedMessage::Init() below. + + value_.argument_ = FindStr4End( value_.argument_ ); + break; + + case BLOB_TYPE_TAG: + { + uint32 blobSize = ToUInt32( value_.argument_ ); + value_.argument_ = value_.argument_ + 4 + RoundUp4( blobSize ); + } + break; + + default: // unknown type tag + // don't advance + --value_.typeTag_; + break; + + + // not handled: + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + } +} + +//------------------------------------------------------------------------------ + +ReceivedMessage::ReceivedMessage( const ReceivedPacket& packet ) + : addressPattern_( packet.Contents() ) +{ + Init( packet.Contents(), packet.Size() ); +} + + +ReceivedMessage::ReceivedMessage( const ReceivedBundleElement& bundleElement ) + : addressPattern_( bundleElement.Contents() ) +{ + Init( bundleElement.Contents(), bundleElement.Size() ); +} + + +bool ReceivedMessage::AddressPatternIsUInt32() const +{ + return (addressPattern_[0] == '\0'); +} + + +uint32 ReceivedMessage::AddressPatternAsUInt32() const +{ + return ToUInt32( addressPattern_ ); +} + + +void ReceivedMessage::Init( const char *message, unsigned long size ) +{ + if( size == 0 ) + throw MalformedMessageException( "zero length messages not permitted" ); + + if( (size & 0x03L) != 0 ) + throw MalformedMessageException( "message size must be multiple of four" ); + + const char *end = message + size; + + typeTagsBegin_ = FindStr4End( addressPattern_, end ); + if( typeTagsBegin_ == 0 ){ + // address pattern was not terminated before end + throw MalformedMessageException( "unterminated address pattern" ); + } + + if( typeTagsBegin_ == end ){ + // message consists of only the address pattern - no arguments or type tags. + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + }else{ + if( *typeTagsBegin_ != ',' ) + throw MalformedMessageException( "type tags not present" ); + + if( *(typeTagsBegin_ + 1) == '\0' ){ + // zero length type tags + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + + }else{ + // check that all arguments are present and well formed + + arguments_ = FindStr4End( typeTagsBegin_, end ); + if( arguments_ == 0 ){ + throw MalformedMessageException( "type tags were not terminated before end of message" ); + } + + ++typeTagsBegin_; // advance past initial ',' + + const char *typeTag = typeTagsBegin_; + const char *argument = arguments_; + + do{ + switch( *typeTag ){ + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + + // zero length + break; + + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument += 4; + if( argument > end ) + throw MalformedMessageException( "arguments exceed message size" ); + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument += 8; + if( argument > end ) + throw MalformedMessageException( "arguments exceed message size" ); + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + if( argument == end ) + throw MalformedMessageException( "arguments exceed message size" ); + argument = FindStr4End( argument, end ); + if( argument == 0 ) + throw MalformedMessageException( "unterminated string argument" ); + break; + + case BLOB_TYPE_TAG: + { + if( argument + 4 > end ) + MalformedMessageException( "arguments exceed message size" ); + + uint32 blobSize = ToUInt32( argument ); + argument = argument + 4 + RoundUp4( blobSize ); + if( argument > end ) + MalformedMessageException( "arguments exceed message size" ); + } + break; + + default: + throw MalformedMessageException( "unknown type tag" ); + + // not handled: + // [ Indicates the beginning of an array. The tags following are for + // data in the Array until a close brace tag is reached. + // ] Indicates the end of an array. + } + + }while( *++typeTag != '\0' ); + typeTagsEnd_ = typeTag; + } + } +} + +//------------------------------------------------------------------------------ + +ReceivedBundle::ReceivedBundle( const ReceivedPacket& packet ) + : elementCount_( 0 ) +{ + Init( packet.Contents(), packet.Size() ); +} + + +ReceivedBundle::ReceivedBundle( const ReceivedBundleElement& bundleElement ) + : elementCount_( 0 ) +{ + Init( bundleElement.Contents(), bundleElement.Size() ); +} + + +void ReceivedBundle::Init( const char *bundle, unsigned long size ) +{ + if( size < 16 ) + throw MalformedBundleException( "packet too short for bundle" ); + + if( (size & 0x03L) != 0 ) + throw MalformedBundleException( "bundle size must be multiple of four" ); + + if( bundle[0] != '#' + || bundle[1] != 'b' + || bundle[2] != 'u' + || bundle[3] != 'n' + || bundle[4] != 'd' + || bundle[5] != 'l' + || bundle[6] != 'e' + || bundle[7] != '\0' ) + throw MalformedBundleException( "bad bundle address pattern" ); + + end_ = bundle + size; + + timeTag_ = bundle + 8; + + const char *p = timeTag_ + 8; + + while( p < end_ ){ + if( p + 4 > end_ ) + throw MalformedBundleException( "packet too short for elementSize" ); + + uint32 elementSize = ToUInt32( p ); + if( (elementSize & 0x03L) != 0 ) + throw MalformedBundleException( "bundle element size must be multiple of four" ); + + p += 4 + elementSize; + if( p > end_ ) + throw MalformedBundleException( "packet too short for bundle element" ); + + ++elementCount_; + } + + if( p != end_ ) + throw MalformedBundleException( "bundle contents " ); +} + + +uint64 ReceivedBundle::TimeTag() const +{ + return ToUInt64( timeTag_ ); +} + + +} // namespace osc + diff --git a/include/oscpack/osc/OscReceivedElements.h b/include/oscpack/osc/OscReceivedElements.h new file mode 100644 index 0000000..f438757 --- /dev/null +++ b/include/oscpack/osc/OscReceivedElements.h @@ -0,0 +1,486 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCRECEIVEDELEMENTS_H +#define INCLUDED_OSCRECEIVEDELEMENTS_H + +#include "OscTypes.h" +#include "OscException.h" + + +namespace osc{ + + +class MalformedMessageException : public Exception{ +public: + MalformedMessageException( const char *w="malformed message" ) + : Exception( w ) {} +}; + +class MalformedBundleException : public Exception{ +public: + MalformedBundleException( const char *w="malformed bundle" ) + : Exception( w ) {} +}; + +class WrongArgumentTypeException : public Exception{ +public: + WrongArgumentTypeException( const char *w="wrong argument type" ) + : Exception( w ) {} +}; + +class MissingArgumentException : public Exception{ +public: + MissingArgumentException( const char *w="missing argument" ) + : Exception( w ) {} +}; + +class ExcessArgumentException : public Exception{ +public: + ExcessArgumentException( const char *w="too many arguments" ) + : Exception( w ) {} +}; + + +class ReceivedPacket{ +public: + ReceivedPacket( const char *contents, int32 size ) + : contents_( contents ) + , size_( size ) {} + + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const; + + int32 Size() const { return size_; } + const char *Contents() const { return contents_; } + +private: + const char *contents_; + int32 size_; +}; + + +class ReceivedBundleElement{ +public: + ReceivedBundleElement( const char *size ) + : size_( size ) {} + + friend class ReceivedBundleElementIterator; + + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const; + + int32 Size() const; + const char *Contents() const { return size_ + 4; } + +private: + const char *size_; +}; + + +class ReceivedBundleElementIterator{ +public: + ReceivedBundleElementIterator( const char *sizePtr ) + : value_( sizePtr ) {} + + ReceivedBundleElementIterator operator++() + { + Advance(); + return *this; + } + + ReceivedBundleElementIterator operator++(int) + { + ReceivedBundleElementIterator old( *this ); + Advance(); + return old; + } + + const ReceivedBundleElement& operator*() const { return value_; } + + const ReceivedBundleElement* operator->() const { return &value_; } + + friend bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ); + +private: + ReceivedBundleElement value_; + + void Advance() { value_.size_ = value_.Contents() + value_.Size(); } + + bool IsEqualTo( const ReceivedBundleElementIterator& rhs ) const + { + return value_.size_ == rhs.value_.size_; + } +}; + +inline bool operator==(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ) +{ + return lhs.IsEqualTo( rhs ); +} + +inline bool operator!=(const ReceivedBundleElementIterator& lhs, + const ReceivedBundleElementIterator& rhs ) +{ + return !( lhs == rhs ); +} + + +class ReceivedMessageArgument{ +public: + ReceivedMessageArgument( const char *typeTag, const char *argument ) + : typeTag_( typeTag ) + , argument_( argument ) {} + + friend class ReceivedMessageArgumentIterator; + + const char TypeTag() const { return *typeTag_; } + + // the unchecked methods below don't check whether the argument actually + // is of the specified type. they should only be used if you've already + // checked the type tag or the associated IsType() method. + + bool IsBool() const + { return *typeTag_ == TRUE_TYPE_TAG || *typeTag_ == FALSE_TYPE_TAG; } + bool AsBool() const; + bool AsBoolUnchecked() const; + + bool IsNil() const { return *typeTag_ == NIL_TYPE_TAG; } + bool IsInfinitum() const { return *typeTag_ == INFINITUM_TYPE_TAG; } + + bool IsInt32() const { return *typeTag_ == INT32_TYPE_TAG; } + int32 AsInt32() const; + int32 AsInt32Unchecked() const; + + bool IsFloat() const { return *typeTag_ == FLOAT_TYPE_TAG; } + float AsFloat() const; + float AsFloatUnchecked() const; + + bool IsChar() const { return *typeTag_ == CHAR_TYPE_TAG; } + char AsChar() const; + char AsCharUnchecked() const; + + bool IsRgbaColor() const { return *typeTag_ == RGBA_COLOR_TYPE_TAG; } + uint32 AsRgbaColor() const; + uint32 AsRgbaColorUnchecked() const; + + bool IsMidiMessage() const { return *typeTag_ == MIDI_MESSAGE_TYPE_TAG; } + uint32 AsMidiMessage() const; + uint32 AsMidiMessageUnchecked() const; + + bool IsInt64() const { return *typeTag_ == INT64_TYPE_TAG; } + int64 AsInt64() const; + int64 AsInt64Unchecked() const; + + bool IsTimeTag() const { return *typeTag_ == TIME_TAG_TYPE_TAG; } + uint64 AsTimeTag() const; + uint64 AsTimeTagUnchecked() const; + + bool IsDouble() const { return *typeTag_ == DOUBLE_TYPE_TAG; } + double AsDouble() const; + double AsDoubleUnchecked() const; + + bool IsString() const { return *typeTag_ == STRING_TYPE_TAG; } + const char* AsString() const; + const char* AsStringUnchecked() const { return argument_; } + + bool IsSymbol() const { return *typeTag_ == SYMBOL_TYPE_TAG; } + const char* AsSymbol() const; + const char* AsSymbolUnchecked() const { return argument_; } + + bool IsBlob() const { return *typeTag_ == BLOB_TYPE_TAG; } + void AsBlob( const void*& data, unsigned long& size ) const; + void AsBlobUnchecked( const void*& data, unsigned long& size ) const; + +private: + const char *typeTag_; + const char *argument_; +}; + + +class ReceivedMessageArgumentIterator{ +public: + ReceivedMessageArgumentIterator( const char *typeTags, const char *arguments ) + : value_( typeTags, arguments ) {} + + ReceivedMessageArgumentIterator operator++() + { + Advance(); + return *this; + } + + ReceivedMessageArgumentIterator operator++(int) + { + ReceivedMessageArgumentIterator old( *this ); + Advance(); + return old; + } + + const ReceivedMessageArgument& operator*() const { return value_; } + + const ReceivedMessageArgument* operator->() const { return &value_; } + + friend bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ); + +private: + ReceivedMessageArgument value_; + + void Advance(); + + bool IsEqualTo( const ReceivedMessageArgumentIterator& rhs ) const + { + return value_.typeTag_ == rhs.value_.typeTag_; + } +}; + +inline bool operator==(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ) +{ + return lhs.IsEqualTo( rhs ); +} + +inline bool operator!=(const ReceivedMessageArgumentIterator& lhs, + const ReceivedMessageArgumentIterator& rhs ) +{ + return !( lhs == rhs ); +} + + +class ReceivedMessageArgumentStream{ + friend class ReceivedMessage; + ReceivedMessageArgumentStream( const ReceivedMessageArgumentIterator& begin, + const ReceivedMessageArgumentIterator& end ) + : p_( begin ) + , end_( end ) {} + + ReceivedMessageArgumentIterator p_, end_; + +public: + + // end of stream + bool Eos() const { return p_ == end_; } + + ReceivedMessageArgumentStream& operator>>( bool& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsBool(); + return *this; + } + + // not sure if it would be useful to stream Nil and Infinitum + // for now it's not possible + + ReceivedMessageArgumentStream& operator>>( int32& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt32(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( float& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsFloat(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( char& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsChar(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( RgbaColor& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsRgbaColor(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( MidiMessage& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsMidiMessage(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( int64& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsInt64(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( TimeTag& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsTimeTag(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( double& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsDouble(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( Blob& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + (*p_++).AsBlob( rhs.data, rhs.size ); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( const char*& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs = (*p_++).AsString(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( Symbol& rhs ) + { + if( Eos() ) + throw MissingArgumentException(); + + rhs.value = (*p_++).AsSymbol(); + return *this; + } + + ReceivedMessageArgumentStream& operator>>( MessageTerminator& rhs ) + { + if( !Eos() ) + throw ExcessArgumentException(); + + return *this; + } +}; + + +class ReceivedMessage{ + void Init( const char *bundle, unsigned long size ); +public: + explicit ReceivedMessage( const ReceivedPacket& packet ); + explicit ReceivedMessage( const ReceivedBundleElement& bundleElement ); + + const char *AddressPattern() const { return addressPattern_; } + + // Support for non-standad SuperCollider integer address patterns: + bool AddressPatternIsUInt32() const; + uint32 AddressPatternAsUInt32() const; + + unsigned long ArgumentCount() const { return static_cast(typeTagsEnd_ - typeTagsBegin_); } + + const char *TypeTags() const { return typeTagsBegin_; } + + + typedef ReceivedMessageArgumentIterator const_iterator; + + ReceivedMessageArgumentIterator ArgumentsBegin() const + { + return ReceivedMessageArgumentIterator( typeTagsBegin_, arguments_ ); + } + + ReceivedMessageArgumentIterator ArgumentsEnd() const + { + return ReceivedMessageArgumentIterator( typeTagsEnd_, 0 ); + } + + ReceivedMessageArgumentStream ArgumentStream() const + { + return ReceivedMessageArgumentStream( ArgumentsBegin(), ArgumentsEnd() ); + } + +private: + const char *addressPattern_; + const char *typeTagsBegin_; + const char *typeTagsEnd_; + const char *arguments_; +}; + + +class ReceivedBundle{ + void Init( const char *message, unsigned long size ); +public: + explicit ReceivedBundle( const ReceivedPacket& packet ); + explicit ReceivedBundle( const ReceivedBundleElement& bundleElement ); + + uint64 TimeTag() const; + + unsigned long ElementCount() const { return elementCount_; } + + typedef ReceivedBundleElementIterator const_iterator; + + ReceivedBundleElementIterator ElementsBegin() const + { + return ReceivedBundleElementIterator( timeTag_ + 8 ); + } + + ReceivedBundleElementIterator ElementsEnd() const + { + return ReceivedBundleElementIterator( end_ ); + } + +private: + const char *timeTag_; + const char *end_; + unsigned long elementCount_; +}; + + +} // namespace osc + + +#endif /* INCLUDED_OSCRECEIVEDELEMENTS_H */ diff --git a/include/oscpack/osc/OscTypes.cpp b/include/oscpack/osc/OscTypes.cpp new file mode 100644 index 0000000..889ab43 --- /dev/null +++ b/include/oscpack/osc/OscTypes.cpp @@ -0,0 +1,40 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscTypes.h" + +namespace osc{ + +BundleInitiator BeginBundleImmediate(1); +BundleTerminator EndBundle; +MessageTerminator EndMessage; +NilType Nil; +InfinitumType Infinitum; + +} // namespace osc diff --git a/include/oscpack/osc/OscTypes.h b/include/oscpack/osc/OscTypes.h new file mode 100644 index 0000000..81549b5 --- /dev/null +++ b/include/oscpack/osc/OscTypes.h @@ -0,0 +1,178 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCTYPES_H +#define INCLUDED_OSCTYPES_H + + +namespace osc{ + +// basic types + +#if defined(__BORLANDC__) || defined(_MSC_VER) + +typedef __int64 int64; +typedef unsigned __int64 uint64; + +#else + +typedef long long int64; +typedef unsigned long long uint64; + +#endif + + + +#ifdef x86_64 + +typedef signed int int32; +typedef unsigned int uint32; + +#else + +typedef signed long int32; +typedef unsigned long uint32; + +#endif + + + +enum TypeTagValues { + TRUE_TYPE_TAG = 'T', + FALSE_TYPE_TAG = 'F', + NIL_TYPE_TAG = 'N', + INFINITUM_TYPE_TAG = 'I', + INT32_TYPE_TAG = 'i', + FLOAT_TYPE_TAG = 'f', + CHAR_TYPE_TAG = 'c', + RGBA_COLOR_TYPE_TAG = 'r', + MIDI_MESSAGE_TYPE_TAG = 'm', + INT64_TYPE_TAG = 'h', + TIME_TAG_TYPE_TAG = 't', + DOUBLE_TYPE_TAG = 'd', + STRING_TYPE_TAG = 's', + SYMBOL_TYPE_TAG = 'S', + BLOB_TYPE_TAG = 'b' +}; + + + +// i/o manipulators used for streaming interfaces + +struct BundleInitiator{ + explicit BundleInitiator( uint64 timeTag_ ) : timeTag( timeTag_ ) {} + uint64 timeTag; +}; + +extern BundleInitiator BeginBundleImmediate; + +inline BundleInitiator BeginBundle( uint64 timeTag=1 ) +{ + return BundleInitiator(timeTag); +} + + +struct BundleTerminator{ +}; + +extern BundleTerminator EndBundle; + +struct BeginMessage{ + explicit BeginMessage( const char *addressPattern_ ) : addressPattern( addressPattern_ ) {} + const char *addressPattern; +}; + +struct MessageTerminator{ +}; + +extern MessageTerminator EndMessage; + + +// osc specific types. they are defined as structs so they can be used +// as separately identifiable types with the streaming operators. + +struct NilType{ +}; + +extern NilType Nil; + + +struct InfinitumType{ +}; + +extern InfinitumType Infinitum; + +struct RgbaColor{ + RgbaColor() {} + explicit RgbaColor( uint32 value_ ) : value( value_ ) {} + uint32 value; + + operator uint32() const { return value; } +}; + + +struct MidiMessage{ + MidiMessage() {} + explicit MidiMessage( uint32 value_ ) : value( value_ ) {} + uint32 value; + + operator uint32() const { return value; } +}; + + +struct TimeTag{ + TimeTag() {} + explicit TimeTag( uint64 value_ ) : value( value_ ) {} + uint64 value; + + operator uint64() const { return value; } +}; + + +struct Symbol{ + Symbol() {} + explicit Symbol( const char* value_ ) : value( value_ ) {} + const char* value; + + operator const char *() const { return value; } +}; + + +struct Blob{ + Blob() {} + explicit Blob( const void* data_, unsigned long size_ ) + : data( data_ ), size( size_ ) {} + const void* data; + unsigned long size; +}; + +} // namespace osc + + +#endif /* INCLUDED_OSCTYPES_H */ diff --git a/include/oscpack/tests/OscReceiveTest.cpp b/include/oscpack/tests/OscReceiveTest.cpp new file mode 100644 index 0000000..81350e7 --- /dev/null +++ b/include/oscpack/tests/OscReceiveTest.cpp @@ -0,0 +1,261 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscReceiveTest.h" + +#include +#include + +#include "osc/OscReceivedElements.h" + +#include "ip/UdpSocket.h" +#include "osc/OscPacketListener.h" + + +namespace osc{ + +class OscReceiveTestPacketListener : public OscPacketListener{ +protected: + + void ProcessMessage( const osc::ReceivedMessage& m, const IpEndpointName& remoteEndpoint ) + { + // a more complex scheme involving std::map or some other method of + // processing address patterns could be used here + // (see MessageMappingOscPacketListener.h for example). however, the main + // purpose of this example is to illustrate and test different argument + // parsing methods + + try { + // argument stream, and argument iterator, used in different + // examples below. + ReceivedMessageArgumentStream args = m.ArgumentStream(); + ReceivedMessage::const_iterator arg = m.ArgumentsBegin(); + + if( strcmp( m.AddressPattern(), "/test1" ) == 0 ){ + + // example #1: + // parse an expected format using the argument stream interface: + bool a1; + osc::int32 a2; + float a3; + const char *a4; + args >> a1 >> a2 >> a3 >> a4 >> osc::EndMessage; + + std::cout << "received '/test1' message with arguments: " + << a1 << " " << a2 << " " << a3 << " " << a4 << "\n"; + + }else if( strcmp( m.AddressPattern(), "/test2" ) == 0 ){ + + // example #2: + // parse an expected format using the argument iterator interface + // this is a more complicated example of doing the same thing + // as above. + bool a1 = (arg++)->AsBool(); + int a2 = (arg++)->AsInt32(); + float a3 = (arg++)->AsFloat(); + const char *a4 = (arg++)->AsString(); + if( arg != m.ArgumentsEnd() ) + throw ExcessArgumentException(); + + std::cout << "received '/test2' message with arguments: " + << a1 << " " << a2 << " " << a3 << " " << a4 << "\n"; + + }else if( strcmp( m.AddressPattern(), "/test3" ) == 0 ){ + + // example #3: + // parse a variable argument format using the argument iterator + // interface. this is where it is necessary to use + // argument iterators instead of streams. + // When messages may contain arguments of varying type, you can + // use the argument iterator interface to query the types at + // runtime. this is more flexible that the argument stream + // interface, which requires each argument to have a fixed type + + if( arg->IsBool() ){ + bool a = (arg++)->AsBoolUnchecked(); + std::cout << "received '/test3' message with bool argument: " + << a << "\n"; + }else if( arg->IsInt32() ){ + int a = (arg++)->AsInt32Unchecked(); + std::cout << "received '/test3' message with int32 argument: " + << a << "\n"; + }else if( arg->IsFloat() ){ + float a = (arg++)->AsFloatUnchecked(); + std::cout << "received '/test3' message with float argument: " + << a << "\n"; + }else if( arg->IsString() ){ + const char *a = (arg++)->AsStringUnchecked(); + std::cout << "received '/test3' message with string argument: '" + << a << "'\n"; + }else{ + std::cout << "received '/test3' message with unexpected argument type\n"; + } + + if( arg != m.ArgumentsEnd() ) + throw ExcessArgumentException(); + + + }else if( strcmp( m.AddressPattern(), "/no_arguments" ) == 0 ){ + + args >> osc::EndMessage; + std::cout << "received '/no_arguments' message\n"; + + }else if( strcmp( m.AddressPattern(), "/a_bool" ) == 0 ){ + + bool a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_bool' message: " << a << "\n"; + + }else if( strcmp( m.AddressPattern(), "/nil" ) == 0 ){ + + std::cout << "received '/nil' message\n"; + + }else if( strcmp( m.AddressPattern(), "/inf" ) == 0 ){ + + std::cout << "received '/inf' message\n"; + + }else if( strcmp( m.AddressPattern(), "/an_int" ) == 0 ){ + + osc::int32 a; + args >> a >> osc::EndMessage; + std::cout << "received '/an_int' message: " << a << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_float" ) == 0 ){ + + float a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_float' message: " << a << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_char" ) == 0 ){ + + char a; + args >> a >> osc::EndMessage; + char s[2] = {0}; + s[0] = a; + std::cout << "received '/a_char' message: '" << s << "'\n"; + + }else if( strcmp( m.AddressPattern(), "/an_rgba_color" ) == 0 ){ + + osc::RgbaColor a; + args >> a >> osc::EndMessage; + std::cout << "received '/an_rgba_color' message: " << a.value << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_midi_message" ) == 0 ){ + + osc::MidiMessage a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_midi_message' message: " << a.value << "\n"; + + }else if( strcmp( m.AddressPattern(), "/an_int64" ) == 0 ){ + + osc::int64 a; + args >> a >> osc::EndMessage; + std::cout << "received '/an_int64' message: " << a << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_time_tag" ) == 0 ){ + + osc::TimeTag a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_time_tag' message: " << a.value << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_double" ) == 0 ){ + + double a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_double' message: " << a << "\n"; + + }else if( strcmp( m.AddressPattern(), "/a_string" ) == 0 ){ + + const char *a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_string' message: '" << a << "'\n"; + + }else if( strcmp( m.AddressPattern(), "/a_symbol" ) == 0 ){ + + osc::Symbol a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_symbol' message: '" << a.value << "'\n"; + + }else if( strcmp( m.AddressPattern(), "/a_blob" ) == 0 ){ + + osc::Blob a; + args >> a >> osc::EndMessage; + std::cout << "received '/a_blob' message\n"; + + }else{ + std::cout << "unrecognised address pattern: " + << m.AddressPattern() << "\n"; + } + + }catch( Exception& e ){ + std::cout << "error while parsing message: " + << m.AddressPattern() << ": " << e.what() << "\n"; + } + } +}; + + +void RunReceiveTest( int port ) +{ + osc::OscReceiveTestPacketListener listener; + UdpListeningReceiveSocket s( + IpEndpointName( IpEndpointName::ANY_ADDRESS, port ), + &listener ); + + std::cout << "listening for input on port " << port << "...\n"; + std::cout << "press ctrl-c to end\n"; + + s.RunUntilSigInt(); + + std::cout << "finishing.\n"; +} + +} // namespace osc + +#ifndef NO_OSC_TEST_MAIN + +int main(int argc, char* argv[]) +{ + if( argc >= 2 && strcmp( argv[1], "-h" ) == 0 ){ + std::cout << "usage: OscReceiveTest [port]\n"; + return 0; + } + + int port = 7000; + + if( argc >= 2 ) + port = atoi( argv[1] ); + + osc::RunReceiveTest( port ); + + return 0; +} + +#endif /* NO_OSC_TEST_MAIN */ + diff --git a/include/oscpack/tests/OscReceiveTest.h b/include/oscpack/tests/OscReceiveTest.h new file mode 100644 index 0000000..9540f84 --- /dev/null +++ b/include/oscpack/tests/OscReceiveTest.h @@ -0,0 +1,40 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef INCLUDED_OSCRECEIVETEST_H +#define INCLUDED_OSCRECEIVETEST_H + +namespace osc{ + +void RunReceiveTest( int port ); + +} // namespace osc + +#endif /* INCLUDED_OSCSENDTESTS_H */ diff --git a/include/oscpack/tests/OscSendTests.cpp b/include/oscpack/tests/OscSendTests.cpp new file mode 100644 index 0000000..16a6db6 --- /dev/null +++ b/include/oscpack/tests/OscSendTests.cpp @@ -0,0 +1,213 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscSendTests.h" + +#include +#include + +#include "osc/OscOutboundPacketStream.h" + +#include "ip/UdpSocket.h" +#include "ip/IpEndpointName.h" + +#define IP_MTU_SIZE 1536 + +namespace osc{ + +void RunSendTests( const IpEndpointName& host ) +{ + char buffer[IP_MTU_SIZE]; + osc::OutboundPacketStream p( buffer, IP_MTU_SIZE ); + UdpTransmitSocket socket( host ); + + p.Clear(); + p << osc::BeginMessage( "/test1" ) + << true << 23 << (float)3.1415 << "hello" << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + // test1 message with too few arguments + p.Clear(); + p << osc::BeginMessage( "/test1" ) + << true << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + // test1 message with too many arguments + p.Clear(); + p << osc::BeginMessage( "/test1" ) + << true << 23 << (float)3.1415 << "hello" << 42 << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + // test1 message with wrong argument type + p.Clear(); + p << osc::BeginMessage( "/test1" ) + << true << 1.0 << (float)3.1415 << "hello" << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + p.Clear(); + p << osc::BeginMessage( "/test2" ) + << true << 23 << (float)3.1415 << "hello" << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + // send four /test3 messages, each with a different type of argument + p.Clear(); + p << osc::BeginMessage( "/test3" ) + << true << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + p.Clear(); + p << osc::BeginMessage( "/test3" ) + << 23 << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + p.Clear(); + p << osc::BeginMessage( "/test3" ) + << (float)3.1415 << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + p.Clear(); + p << osc::BeginMessage( "/test3" ) + << "hello" << osc::EndMessage; + socket.Send( p.Data(), p.Size() ); + + + // send a bundle + p.Clear(); + p << osc::BeginBundle(); + + p << osc::BeginMessage( "/no_arguments" ) + << osc::EndMessage; + + p << osc::BeginMessage( "/a_bool" ) + << true << osc::EndMessage; + + p << osc::BeginMessage( "/a_bool" ) + << false << osc::EndMessage; + + p << osc::BeginMessage( "/a_bool" ) + << (bool)1234 << osc::EndMessage; + + p << osc::BeginMessage( "/nil" ) + << osc::Nil << osc::EndMessage; + + p << osc::BeginMessage( "/inf" ) + << osc::Infinitum << osc::EndMessage; + + p << osc::BeginMessage( "/an_int" ) << 1234 << osc::EndMessage; + + p << osc::BeginMessage( "/a_float" ) + << 3.1415926f << osc::EndMessage; + + p << osc::BeginMessage( "/a_char" ) + << 'c' << osc::EndMessage; + + p << osc::BeginMessage( "/an_rgba_color" ) + << osc::RgbaColor(0x22334455) << osc::EndMessage; + + p << osc::BeginMessage( "/a_midi_message" ) + << MidiMessage(0x7F) << osc::EndMessage; + + p << osc::BeginMessage( "/an_int64" ) + << (int64)(0xFFFFFFF) << osc::EndMessage; + + p << osc::BeginMessage( "/a_time_tag" ) + << osc::TimeTag(0xFFFFFFFUL) << osc::EndMessage; + + p << osc::BeginMessage( "/a_double" ) + << (double)3.1415926 << osc::EndMessage; + + p << osc::BeginMessage( "/a_string" ) + << "hello world" << osc::EndMessage; + + p << osc::BeginMessage( "/a_symbol" ) + << osc::Symbol("foobar") << osc::EndMessage; + + // blob + { + char blobData[] = "abcd"; + + p << osc::BeginMessage( "/a_blob" ) + << osc::Blob( blobData, 4 ) + << osc::EndMessage; + } + + p << osc::EndBundle; + socket.Send( p.Data(), p.Size() ); + + + + // nested bundles, and multiple messages in bundles... + p.Clear(); + p << osc::BeginBundle( 1234 ) + << osc::BeginMessage( "/an_int" ) << 1 << osc::EndMessage + << osc::BeginMessage( "/an_int" ) << 2 << osc::EndMessage + << osc::BeginMessage( "/an_int" ) << 3 << osc::EndMessage + << osc::BeginMessage( "/an_int" ) << 4 << osc::EndMessage + << osc::BeginBundle( 12345 ) + << osc::BeginMessage( "/an_int" ) << 5 << osc::EndMessage + << osc::BeginMessage( "/an_int" ) << 6 << osc::EndMessage + << osc::EndBundle + << osc::EndBundle; + + socket.Send( p.Data(), p.Size() ); +} + +} // namespace osc + +#ifndef NO_OSC_TEST_MAIN + +int main(int argc, char* argv[]) +{ + if( argc >= 2 && strcmp( argv[1], "-h" ) == 0 ){ + std::cout << "usage: OscSendTests [hostname [port]]\n"; + return 0; + } + + char *hostName = "localhost"; + int port = 7000; + + if( argc >= 2 ) + hostName = argv[1]; + + if( argc >= 3 ) + port = atoi( argv[2] ); + + + IpEndpointName host( hostName, port ); + + char hostIpAddress[ IpEndpointName::ADDRESS_STRING_LENGTH ]; + host.AddressAsString( hostIpAddress ); + + std::cout << "sending test messages to " << hostName + << " (" << hostIpAddress << ") on port " << port << "...\n"; + + osc::RunSendTests( host ); +} + +#endif /* NO_OSC_TEST_MAIN */ diff --git a/include/oscpack/tests/OscSendTests.h b/include/oscpack/tests/OscSendTests.h new file mode 100644 index 0000000..f328422 --- /dev/null +++ b/include/oscpack/tests/OscSendTests.h @@ -0,0 +1,39 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCSENDTESTS_H +#define INCLUDED_OSCSENDTESTS_H + +namespace osc{ + +void RunSendTests( unsigned long address, int port ); + +} // namespace osc + +#endif /* INCLUDED_OSCSENDTESTS_H */ diff --git a/include/oscpack/tests/OscUnitTests.cpp b/include/oscpack/tests/OscUnitTests.cpp new file mode 100644 index 0000000..dcf452c --- /dev/null +++ b/include/oscpack/tests/OscUnitTests.cpp @@ -0,0 +1,409 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#include "OscUnitTests.h" + +#include +#include + +#include "osc/OscReceivedElements.h" +#include "osc/OscPrintReceivedElements.h" +#include "osc/OscOutboundPacketStream.h" + + +namespace osc{ + +static int passCount_=0, failCount_=0; + +void PrintTestSummary() +{ + std::cout << (passCount_+failCount_) << " tests run, " << passCount_ << " passed, " << failCount_ << " failed.\n"; +} + +void pass_equality( const char *slhs, const char *srhs, const char *file, int line ) +{ + ++passCount_; + std::cout << file << "(" << line << "): PASSED : " << slhs << " == " << srhs << "\n"; +} + +void fail_equality( const char *slhs, const char *srhs, const char *file, int line ) +{ + ++failCount_; + std::cout << file << "(" << line << "): FAILED : " << slhs << " != " << srhs << "\n"; +} + +template +void assertEqual_( const T& lhs, const T& rhs, const char *slhs, const char *srhs, const char *file, int line ) +{ + if( lhs == rhs ) + pass_equality( slhs, srhs, file, line ); + else + fail_equality( slhs, srhs, file, line ); +} + +template +void assertEqual_( const T* lhs, const T* rhs, const char *slhs, const char *srhs, const char *file, int line ) +{ + if( lhs == rhs ) + pass_equality( slhs, srhs, file, line ); + else + fail_equality( slhs, srhs, file, line ); +} + +template <> +void assertEqual_( const char* lhs, const char* rhs, const char *slhs, const char *srhs, const char *file, int line ) +{ + if( strcmp( lhs, rhs ) == 0 ) + pass_equality( slhs, srhs, file, line ); + else + fail_equality( slhs, srhs, file, line ); +} + + +#define assertEqual( a, b ) assertEqual_( (a), (b), #a, #b, __FILE__, __LINE__ ) + +//--------------------------------------------------------------------------- +char * AllocateAligned4( unsigned long size ) +{ + char *s = new char[ size + 4 ]; //allocate on stack to get 4 byte alignment + return (char*)((long)(s-1) & (~0x03L)) + 4; +} + +// allocate a 4 byte aligned copy of s +char * NewMessageBuffer( const char *s, unsigned long length ) +{ + char *p = AllocateAligned4( length ); + memcpy( p, s, length ); + return p; +} + +void test1() +{ + const char s[] = "/test\0\0\0,fiT\0\0\0\0\0\0\0\0\0\0\0A"; + char *buffer = NewMessageBuffer( s, sizeof(s)-1 ); + + // test argument iterator interface + bool unexpectedExceptionCaught = false; + try{ + ReceivedMessage m( ReceivedPacket(buffer, sizeof(s)-1) ); + + assertEqual( strcmp( m.AddressPattern(), "/test" ), 0 ); + assertEqual( strcmp( m.TypeTags(), "fiT" ), 0 ); + + ReceivedMessage::const_iterator i = m.ArgumentsBegin(); + ++i; + ++i; + ++i; + assertEqual( i, m.ArgumentsEnd() ); + + i = m.ArgumentsBegin(); + float f = (i++)->AsFloat(); + (void)f; + int n = (i++)->AsInt32(); + (void)n; + bool b = (i++)->AsBool(); + (void)b; + + i = m.ArgumentsBegin(); + bool exceptionThrown = false; + try{ + int n = (i++)->AsInt32(); + (void)n; + }catch( Exception& ){ + exceptionThrown = true; + } + assertEqual( exceptionThrown, true ); + + }catch( Exception& e ){ + std::cout << "unexpected exception: " << e.what() << "\n"; + unexpectedExceptionCaught = true; + } + assertEqual( unexpectedExceptionCaught, false ); + + + // test argument stream interface + unexpectedExceptionCaught = false; + try{ + ReceivedMessage m( ReceivedPacket(buffer, sizeof(s)-1) ); + ReceivedMessageArgumentStream args = m.ArgumentStream(); + assertEqual( args.Eos(), false ); + + float f; + long n; + bool b; + args >> f >> n >> b; + + (void) f; + (void) n; + (void) b; + + assertEqual( args.Eos(), true ); + + }catch( Exception& e ){ + std::cout << "unexpected exception: " << e.what() << "\n"; + unexpectedExceptionCaught = true; + } + assertEqual( unexpectedExceptionCaught, false ); +} + +//--------------------------------------------------------------------------- + + +#define TEST2_PRINT( ss )\ + {\ + const char s[] = ss;\ + ReceivedPacket p( NewMessageBuffer( s, sizeof(s)-1 ), sizeof(s)-1 ); \ + ReceivedMessage m( p );\ + std::cout << m << "\n";\ + } + +void test2() +{ + bool unexpectedExceptionCaught = false; + try{ + // 012301230 1 2 3 + TEST2_PRINT( "/no_args\0\0\0\0" ); + + // 012301230 1 2 3 01 2 3 + TEST2_PRINT( "/no_args\0\0\0\0,\0\0\0" ); + + // 01230123 012 3 0 1 2 3 + TEST2_PRINT( "/an_int\0,i\0\0\0\0\0A" ); + // 012301230 1 2 3 012 3 0 1 2 3 + TEST2_PRINT( "/a_float\0\0\0\0,f\0\0\0\0\0\0" ); + // 0123012301 2 3 012 3 012301230123 + TEST2_PRINT( "/a_string\0\0\0,s\0\0hello world\0" ); + // 01230123 012 3 0 1 2 3 0 1 2 3 + TEST2_PRINT( "/a_blob\0,b\0\0\0\0\0\x4\x0\x1\x2\x3" ); + + // 0123012301 2 3 012 3 0 1 2 3 0 1 2 3 + TEST2_PRINT( "/an_int64\0\0\0,h\0\0\0\0\0\0\0\0\0\x1" ); + // 01230123012 3 012 3 0 1 2 3 0 1 2 3 + TEST2_PRINT( "/a_timetag\0\0,t\0\0\0\0\0\0\0\0\0\x1" ); + // 0123012301 2 3 012 3 0 1 2 3 0 1 2 3 + TEST2_PRINT( "/a_double\0\0\0,d\0\0\0\0\0\0\0\0\0\0" ); + // 0123012301 2 3 012 3 012301230123 + TEST2_PRINT( "/a_symbol\0\0\0,S\0\0hello world\0" ); + // 01230123 012 3 0 1 2 3 + TEST2_PRINT( "/a_char\0,c\0\0\0\0\0A" ); + // 012301230 1 2 3 012 3 0 1 2 3 + TEST2_PRINT( "/a_color\0\0\0\0,r\0\0\0\0\0\0" ); + // 012301230123012 3 012 3 0 1 2 3 + TEST2_PRINT( "/a_midimessage\0\0,m\0\0\0\0\0\0" ); + // 01230123 012 3 + TEST2_PRINT( "/a_bool\0,T\0\0" ); + // 01230123 012 3 + TEST2_PRINT( "/a_bool\0,F\0\0" ); + // 01230 1 2 3 012 3 + TEST2_PRINT( "/Nil\0\0\0\0,N\0\0" ); + // 01230 1 2 3 012 3 + TEST2_PRINT( "/Inf\0\0\0\0,I\0\0" ); + + TEST2_PRINT( "/test\0\0\0,fiT\0\0\0\0\0\0\0\0\0\0\0A" ); + + bool exceptionThrown = false; + try{ + TEST2_PRINT( "/a_char\0,x\0\0\0\0\0A" ); // unknown type tag 'x' + }catch( Exception& ){ + exceptionThrown = true; + } + assertEqual( exceptionThrown, true ); + + }catch( Exception& e ){ + std::cout << "unexpected exception: " << e.what() << "\n"; + unexpectedExceptionCaught = true; + } + assertEqual( unexpectedExceptionCaught, false ); +} + +//----------------------------------------------------------------------- + +// pack a message and then unpack it and check that the result is the same +// also print each message +// repeat the process inside a bundle + +#define TEST_PACK_UNPACK0( addressPattern, argument, value, recieveGetter ) \ + { \ + memset( buffer, 0x74, bufferSize ); \ + OutboundPacketStream ps( buffer, bufferSize ); \ + ps << BeginMessage( addressPattern ) \ + << argument \ + << EndMessage;\ + assertEqual( ps.IsReady(), true );\ + ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) );\ + std::cout << m << "\n";\ + assertEqual( m.ArgumentsBegin()-> recieveGetter () , value );\ + } \ + { \ + memset( buffer, 0x74, bufferSize ); \ + OutboundPacketStream ps( buffer, bufferSize ); \ + ps << BeginBundle( 1234 ) \ + << BeginMessage( addressPattern ) \ + << argument \ + << EndMessage \ + << EndBundle;\ + assertEqual( ps.IsReady(), true );\ + ReceivedBundle b( ReceivedPacket(ps.Data(), ps.Size()) );\ + ReceivedMessage m( *b.ElementsBegin() );\ + std::cout << m << "\n";\ + assertEqual( m.ArgumentsBegin()-> recieveGetter () , value );\ + } + +#define TEST_PACK_UNPACK( addressPattern, argument, type, recieveGetter ) \ + { \ + memset( buffer, 0x74, bufferSize ); \ + OutboundPacketStream ps( buffer, bufferSize ); \ + ps << BeginMessage( addressPattern ) \ + << argument \ + << EndMessage;\ + assertEqual( ps.IsReady(), true );\ + ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) );\ + std::cout << m << "\n";\ + assertEqual( m.ArgumentsBegin()-> recieveGetter () , ( type ) argument );\ + } \ + { \ + memset( buffer, 0x74, bufferSize ); \ + OutboundPacketStream ps( buffer, bufferSize ); \ + ps << BeginBundle( 1234 ) \ + << BeginMessage( addressPattern ) \ + << argument \ + << EndMessage \ + << EndBundle;\ + assertEqual( ps.IsReady(), true );\ + ReceivedBundle b( ReceivedPacket(ps.Data(), ps.Size()) );\ + ReceivedMessage m( *b.ElementsBegin() );\ + std::cout << m << "\n";\ + assertEqual( m.ArgumentsBegin()-> recieveGetter () , ( type ) argument );\ + } + +void test3() +{ + int bufferSize = 1000; + char *buffer = AllocateAligned4( bufferSize ); + +// single message tests + // empty message + { + memset( buffer, 0x74, bufferSize ); + OutboundPacketStream ps( buffer, bufferSize ); + ps << BeginMessage( "/no_arguments" ) + << EndMessage; + assertEqual( ps.IsReady(), true ); + ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) ); + std::cout << m << "\n";\ + } + + TEST_PACK_UNPACK( "/a_bool", true, bool, AsBool ); + TEST_PACK_UNPACK( "/a_bool", false, bool, AsBool ); + TEST_PACK_UNPACK( "/a_bool", (bool)1, bool, AsBool ); + + TEST_PACK_UNPACK0( "/nil", Nil, true, IsNil ); + TEST_PACK_UNPACK0( "/inf", Infinitum, true, IsInfinitum ); + + TEST_PACK_UNPACK( "/an_int", (int32)1234, int32, AsInt32 ); + + TEST_PACK_UNPACK( "/a_float", 3.1415926f, float, AsFloat ); + + TEST_PACK_UNPACK( "/a_char", 'c', char, AsChar ); + + TEST_PACK_UNPACK( "/an_rgba_color", RgbaColor(0x22334455), uint32, AsRgbaColor ); + + TEST_PACK_UNPACK( "/a_midi_message", MidiMessage(0x7F), uint32, AsMidiMessage ); + + TEST_PACK_UNPACK( "/an_int64", (int64)(0xFFFFFFFF), int64, AsInt64 ); + + TEST_PACK_UNPACK( "/a_time_tag", TimeTag(0xFFFFFFFF), uint64, AsTimeTag ); + + TEST_PACK_UNPACK( "/a_double", (double)3.1415926, double, AsDouble ); + + // blob + { + char blobData[] = "abcd"; + memset( buffer, 0x74, bufferSize ); + OutboundPacketStream ps( buffer, bufferSize ); + ps << BeginMessage( "/a_blob" ) + << Blob( blobData, 4 ) + << EndMessage; + assertEqual( ps.IsReady(), true ); + ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) ); + std::cout << m << "\n"; + + const void *value; + unsigned long size; + m.ArgumentsBegin()->AsBlob( value, size ); + assertEqual( size, (unsigned long)4 ); + assertEqual( (memcmp( value, blobData, 4 ) == 0), true ); + } + + + TEST_PACK_UNPACK( "/a_string", "hello world", const char*, AsString ); + + TEST_PACK_UNPACK( "/a_symbol", Symbol("foobar"), const char*, AsSymbol ); + + + // nested bundles, and multiple messages in bundles... + + { + memset( buffer, 0x74, bufferSize ); + OutboundPacketStream ps( buffer, bufferSize ); + ps << BeginBundle() + << BeginMessage( "/message_one" ) << 1 << 2 << 3 << 4 << EndMessage + << BeginMessage( "/message_two" ) << 1 << 2 << 3 << 4 << EndMessage + << BeginMessage( "/message_three" ) << 1 << 2 << 3 << 4 << EndMessage + << BeginMessage( "/message_four" ) << 1 << 2 << 3 << 4 << EndMessage + << EndBundle; + assertEqual( ps.IsReady(), true ); + ReceivedBundle b( ReceivedPacket(ps.Data(), ps.Size()) ); + std::cout << b << "\n"; + } +} + + +void RunUnitTests() +{ + test1(); + test2(); + test3(); + PrintTestSummary(); +} + +} // namespace osc + + +#ifndef NO_OSC_TEST_MAIN + +int main(int argc, char* argv[]) +{ + (void)argc; + (void)argv; + + osc::RunUnitTests(); +} + +#endif diff --git a/include/oscpack/tests/OscUnitTests.h b/include/oscpack/tests/OscUnitTests.h new file mode 100644 index 0000000..c8a9f2c --- /dev/null +++ b/include/oscpack/tests/OscUnitTests.h @@ -0,0 +1,39 @@ +/* + oscpack -- Open Sound Control packet manipulation library + http://www.audiomulch.com/~rossb/oscpack + + Copyright (c) 2004-2005 Ross Bencina + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + Any person wishing to distribute modifications to the Software is + requested to send the modifications to the original developer so that + they can be incorporated into the canonical version. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +#ifndef INCLUDED_OSCUNITTESTS_H +#define INCLUDED_OSCUNITTESTS_H + +namespace osc{ + +void RunUnitTests(); + +} // namespace osc + +#endif /* INCLUDED_OSCUNITTESTS_H */