56 const void* getData()
const noexcept {
return input.getData(); }
59 size_t getDataSize()
const noexcept {
return input.getDataSize(); }
62 uint64 getPosition() {
return (
uint64) input.getPosition(); }
65 bool setPosition (
int64 pos) {
return input.setPosition (pos); }
68 int64 getTotalLength() {
return input.getTotalLength(); }
71 bool isExhausted() {
return input.isExhausted(); }
77 return input.readIntBigEndian();
83 return (
uint64) input.readInt64BigEndian();
89 return input.readFloatBigEndian();
97 auto s = input.readString();
100 if (
static_cast<const char*
> (getData()) [
posEnd - 1] !=
'\0')
101 throw OSCFormatError (
"OSC input stream exhausted before finding null terminator of string");
117 auto bytesRead = input.readIntoMemoryBlock (blob, (ssize_t)
blobDataSize);
126 return OSCColour::fromInt32 ((
uint32) input.readIntBigEndian());
132 return OSCTimeTag (
uint64 (input.readInt64BigEndian()));
137 return OSCAddress (readString());
142 return OSCAddressPattern (readString());
152 if (input.readByte() !=
',')
153 throw OSCFormatError (
"OSC input stream format error: expected type tag string");
158 throw OSCFormatError (
"OSC input stream exhausted while reading type tag string");
160 const OSCType type = input.readByte();
165 if (! OSCTypes::isSupportedType (type))
166 throw OSCFormatError (
"OSC input stream format error: encountered unsupported type tag");
182 case OSCTypes::int32:
return OSCArgument (readInt32());
183 case OSCTypes::float32:
return OSCArgument (
readFloat32());
184 case OSCTypes::string:
return OSCArgument (readString());
185 case OSCTypes::blob:
return OSCArgument (
readBlob());
186 case OSCTypes::colour:
return OSCArgument (
readColour());
191 throw OSCInternalError (
"OSC input stream: internal error while reading message argument");
203 for (
auto& type : types)
218 if (readString() !=
"#bundle")
219 throw OSCFormatError (
"OSC input stream format error: bundle does not start with string '#bundle'");
223 size_t bytesRead = 16;
224 auto pos = getPosition();
230 auto newPos = getPosition();
246 throw OSCFormatError (
"OSC input stream format error: invalid bundle element size");
256 auto firstContentChar =
static_cast<const char*
> (getData()) [getPosition()];
261 throw OSCFormatError (
"OSC input stream: invalid bundle element content");
265 MemoryInputStream input;
270 size_t numZeros = ~(bytesRead - 1) & 0x03;
274 if (isExhausted() || input.readByte() != 0)
275 throw OSCFormatError (
"OSC input stream format error: missing padding zeros");
288 if (getPosition() -
begin != size)
289 throw OSCFormatError (
"OSC input stream format error: wrong element content size encountered while reading");
299 if (getPosition() -
begin != size)
300 throw OSCFormatError (
"OSC input stream format error: wrong element content size encountered while reading");
308 throw OSCFormatError (message);
329 bool connectToPort (
int portNumber)
336 if (! socket->bindToPort (portNumber))
355 if (socket !=
nullptr)
359 if (socket.willDeleteObject())
421 void handleBuffer (
const char* data,
size_t dataSize)
423 OSCInputStream
inStream (data, dataSize);
427 auto content =
inStream.readElementWithKnownSize (dataSize);
431 callRealtimeListeners (content);
433 if (content.isMessage())
434 callRealtimeListenersWithAddress (content.getMessage());
438 if (listeners.size() > 0 || listenersWithAddress.size() > 0)
443 NullCheckedInvocation::invoke (formatErrorHandler, data, (
int) dataSize);
450 formatErrorHandler = handler;
457 int bufferSize = 65535;
463 auto ready = socket->waitUntilReady (
true, 100);
471 auto bytesRead = (
size_t) socket->read (
oscBuffer.getData(), bufferSize,
false);
474 handleBuffer (
oscBuffer.getData(), bytesRead);
479 template <
typename ListenerType>
484 for (
auto& i : array)
492 template <
typename ListenerType>
496 for (
int i = 0; i < array.size(); ++i)
503 array.swap (i, array.size() - 1);
511 void handleMessage (
const Message& msg)
override
513 if (
auto*
callbackMessage =
dynamic_cast<const CallbackMessage*
> (&msg))
517 callListeners (content);
519 if (content.isMessage())
520 callListenersWithAddress (content.getMessage());
525 void callListeners (
const OSCBundle::Element& content)
527 using OSCListener = OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>;
529 if (content.isMessage())
531 auto&& message = content.getMessage();
532 listeners.call ([&] (
OSCListener&
l) {
l.oscMessageReceived (message); });
534 else if (content.isBundle())
536 auto&& bundle = content.getBundle();
537 listeners.call ([&] (
OSCListener&
l) {
l.oscBundleReceived (bundle); });
541 void callRealtimeListeners (
const OSCBundle::Element& content)
543 using OSCListener = OSCReceiver::Listener<OSCReceiver::RealtimeCallback>;
545 if (content.isMessage())
547 auto&& message = content.getMessage();
548 realtimeListeners.call ([&] (
OSCListener&
l) {
l.oscMessageReceived (message); });
550 else if (content.isBundle())
552 auto&& bundle = content.getBundle();
553 realtimeListeners.call ([&] (
OSCListener&
l) {
l.oscBundleReceived (bundle); });
558 void callListenersWithAddress (
const OSCMessage& message)
560 for (
auto& entry : listenersWithAddress)
561 if (
auto* listener = entry.second)
562 if (message.getAddressPattern().matches (entry.first))
563 listener->oscMessageReceived (message);
566 void callRealtimeListenersWithAddress (
const OSCMessage& message)
568 for (
auto& entry : realtimeListenersWithAddress)
569 if (
auto* listener = entry.second)
570 if (message.getAddressPattern().matches (entry.first))
571 listener->oscMessageReceived (message);
603 return pimpl->connectToPort (portNumber);
608 return pimpl->connectToSocket (
socket);
613 return pimpl->disconnect();
658 pimpl->registerFormatErrorHandler (handler);
673 void runTest()
override
675 beginTest (
"reading OSC addresses");
677 const char buffer[16] = {
678 '/',
't',
'e',
's',
't',
'/',
'f',
'a',
679 'd',
'e',
'r',
'7',
'\0',
'\0',
'\0',
'\0' };
683 OSCInputStream
inStream (buffer,
sizeof (buffer));
684 OSCAddress address =
inStream.readAddress();
687 expectEquals (address.toString(), String (
"/test/fader7"));
693 OSCInputStream
inStream (buffer, 15);
698 OSCInputStream
inStream (buffer, 12);
703 OSCInputStream
inStream (buffer + 4, 12);
708 beginTest (
"reading OSC address patterns");
710 const char buffer[20] = {
711 '/',
'*',
'/',
'*',
'p',
'u',
't',
'/',
712 'f',
'a',
'd',
'e',
'r',
'[',
'0',
'-',
713 '9',
']',
'\0',
'\0' };
717 OSCInputStream
inStream (buffer,
sizeof (buffer));
721 OSCInputStream
inStream (buffer,
sizeof (buffer));
722 OSCAddressPattern
ap =
inStream.readAddressPattern();
725 expectEquals (
ap.toString(), String (
"/*/*put/fader[0-9]"));
726 expect (
ap.containsWildcards());
732 OSCInputStream
inStream (buffer, 19);
737 OSCInputStream
inStream (buffer, 16);
742 OSCInputStream
inStream (buffer + 4, 16);
747 beginTest (
"reading OSC time tags");
750 char buffer[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
751 OSCInputStream
inStream (buffer,
sizeof (buffer));
753 OSCTimeTag tag =
inStream.readTimeTag();
754 expect (tag.isImmediately());
757 char buffer[8] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
758 OSCInputStream
inStream (buffer,
sizeof (buffer));
760 OSCTimeTag tag =
inStream.readTimeTag();
761 expect (! tag.isImmediately());
764 beginTest (
"reading OSC arguments");
776 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
777 'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0' };
782 0x00, 0x00, 0x00, 0x05,
783 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00 };
790 OSCArgument
arg =
inStream.readArgument (OSCTypes::int32);
792 expect (
inStream.getPosition() == 4);
793 expect (
arg.isInt32());
799 OSCArgument
arg =
inStream.readArgument (OSCTypes::float32);
801 expect (
inStream.getPosition() == 4);
802 expect (
arg.isFloat32());
808 OSCArgument
arg =
inStream.readArgument (OSCTypes::string);
811 expect (
arg.isString());
817 OSCArgument
arg =
inStream.readArgument (OSCTypes::blob);
820 expect (
arg.isBlob());
841 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
842 'o',
'r',
'l',
'd',
'!',
'\0' };
850 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
851 'o',
'r',
'l',
'd',
'!',
'\0',
'x',
'x' };
860 const uint8 rawData[] = { 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
869 const uint8 rawData[] = { 0x00, 0x00, 0x00, 0x12, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
878 beginTest (
"reading OSC messages (type tag string)");
882 const char data[] = {
883 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
884 ',',
'\0',
'\0',
'\0' };
886 OSCInputStream
inStream (data,
sizeof (data));
889 expect (msg.getAddressPattern().toString() ==
"/test");
890 expect (msg.size() == 0);
895 const char data[] = {
896 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
897 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
898 'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0' };
900 OSCInputStream
inStream (data,
sizeof (data));
907 const char data[] = {
'/',
't',
'e',
's',
't',
'\0',
'\0',
'\0' };
909 OSCInputStream
inStream (data,
sizeof (data));
916 const char data[] = {
'/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
',',
'\0',
'\0',
'\0' };
917 OSCInputStream
inStream (data,
sizeof (data) - 1);
924 const char data[] = {
'/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
',',
'i',
'\0',
'\0' };
925 OSCInputStream
inStream (data,
sizeof (data));
932 const char data[] = {
'/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
',',
'i',
'f',
'\0' };
933 OSCInputStream
inStream (data,
sizeof (data));
939 beginTest (
"reading OSC messages (contents)");
952 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
953 ',',
'i',
'f',
's',
'b',
'\0',
'\0',
'\0',
954 0xFF, 0xFF, 0xF8, 0x21,
955 0x43, 0xAC, 0xCE, 0x66,
956 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0',
957 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00
960 OSCInputStream
inStream (data,
sizeof (data));
964 expectEquals (msg.getAddressPattern().toString(), String (
"/test"));
965 expectEquals (msg.size(), 4);
967 expectEquals (msg[0].getType(), OSCTypes::int32);
968 expectEquals (msg[1].getType(), OSCTypes::float32);
969 expectEquals (msg[2].getType(), OSCTypes::string);
970 expectEquals (msg[3].getType(), OSCTypes::blob);
972 expectEquals (msg[0].getInt32(),
testInt);
973 expectEquals (msg[1].getFloat32(),
testFloat);
974 expectEquals (msg[2].getString(),
testString);
975 expect (msg[3].getBlob() ==
testBlob);
978 beginTest (
"reading OSC messages (handling of corrupted messages)");
983 OSCInputStream
inStream (
nullptr, 0);
995 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
996 ',',
'i',
'f',
's',
'b',
997 0xFF, 0xFF, 0xF8, 0x21,
998 0x43, 0xAC, 0xCE, 0x66,
999 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0',
1000 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00
1003 OSCInputStream
inStream (data,
sizeof (data));
1009 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
1010 ',',
'i',
'f',
's',
'b',
'\0',
'\0',
'\0',
1011 0xFF, 0xFF, 0xF8, 0x21,
1012 0x43, 0xAC, 0xCE, 0x66,
1013 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0'
1016 OSCInputStream
inStream (data,
sizeof (data));
1021 beginTest (
"reading OSC messages (handling messages without type tag strings)");
1025 uint8 data[] = {
'/',
't',
'e',
's',
't',
'\0',
'\0',
'\0' };
1027 OSCInputStream
inStream (data,
sizeof (data));
1033 '/',
't',
'e',
's',
't',
'\0',
'\0',
'\0',
1034 0xFF, 0xFF, 0xF8, 0x21,
1035 0x43, 0xAC, 0xCE, 0x66,
1036 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0',
1037 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00
1040 OSCInputStream
inStream (data,
sizeof (data));
1045 beginTest (
"reading OSC bundles");
1050 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1051 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
1054 OSCInputStream
inStream (data,
sizeof (data));
1055 OSCBundle bundle =
inStream.readBundle();
1057 expect (bundle.getTimeTag().isImmediately());
1058 expect (bundle.size() == 0);
1071 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1072 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1074 0x00, 0x00, 0x00, 0x34,
1076 '/',
't',
'e',
's',
't',
'/',
'1',
'\0',
1077 ',',
'i',
'f',
's',
'b',
'\0',
'\0',
'\0',
1078 0xFF, 0xFF, 0xF8, 0x21,
1079 0x43, 0xAC, 0xCE, 0x66,
1080 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0',
1081 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00,
1083 0x00, 0x00, 0x00, 0x0C,
1085 '/',
't',
'e',
's',
't',
'/',
'2',
'\0',
1086 ',',
'\0',
'\0',
'\0',
1088 0x00, 0x00, 0x00, 0x10,
1090 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1091 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
1094 OSCInputStream
inStream (data,
sizeof (data));
1095 OSCBundle bundle =
inStream.readBundle();
1097 expect (bundle.getTimeTag().isImmediately());
1098 expect (bundle.size() == 3);
1100 OSCBundle::Element* elements = bundle.begin();
1102 expect (elements[0].isMessage());
1103 expect (elements[0].getMessage().getAddressPattern().
toString() ==
"/test/1");
1104 expect (elements[0].getMessage().
size() == 4);
1105 expect (elements[0].getMessage()[0].isInt32());
1106 expect (elements[0].getMessage()[1].isFloat32());
1107 expect (elements[0].getMessage()[2].isString());
1108 expect (elements[0].getMessage()[3].isBlob());
1109 expectEquals (elements[0].getMessage()[0].getInt32(),
testInt);
1110 expectEquals (elements[0].getMessage()[1].getFloat32(),
testFloat);
1111 expectEquals (elements[0].getMessage()[2].getString(),
testString);
1112 expect (elements[0].getMessage()[3].getBlob() ==
testBlob);
1114 expect (elements[1].isMessage());
1115 expect (elements[1].getMessage().getAddressPattern().
toString() ==
"/test/2");
1116 expect (elements[1].getMessage().
size() == 0);
1118 expect (elements[2].isBundle());
1119 expect (! elements[2].getBundle().getTimeTag().isImmediately());
1126 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1127 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1129 0x00, 0x00, 0x00, 0x34,
1131 '/',
't',
'e',
's',
't',
'/',
'1',
'\0',
1132 ',',
's',
'\0',
'\0',
1133 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0'
1136 OSCInputStream
inStream (data,
sizeof (data));
1142 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1143 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1145 0x00, 0x00, 0x00, 0x08,
1147 '/',
't',
'e',
's',
't',
'/',
'1',
'\0',
1148 ',',
's',
'\0',
'\0',
1149 'H',
'e',
'l',
'l',
'o',
',',
' ',
'W',
'o',
'r',
'l',
'd',
'!',
'\0',
'\0',
'\0'
1152 OSCInputStream
inStream (data,
sizeof (data));
1158 '#',
'b',
'u',
'n',
'd',
'l',
'e',
'\0',
1159 0x00, 0x00, 0x00, 0x00
1162 OSCInputStream
inStream (data,
sizeof (data));
1168 '#',
'b',
'u',
'n',
'x',
'l',
'e',
'\0',
1169 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1172 OSCInputStream
inStream (data,
sizeof (data));
1178 '#',
'b',
'u',
'n',
'd',
'l',
'e',
1179 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1182 OSCInputStream
inStream (data,
sizeof (data));
A wrapper for a datagram (UDP) socket.
MessageListener subclasses can post and receive Message objects.
void postMessage(Message *message) const
Sends a message to the message queue, for asynchronous delivery to this listener later on.
The base class for objects that can be sent to a MessageListener.
A class for receiving only those OSC messages from an OSCReceiver that match a given OSC address.
A class for receiving OSC data from an OSCReceiver.
A class for receiving OSC data.
bool connect(int portNumber)
Connects to the specified UDP port using a datagram socket, and starts listening to OSC packets arriv...
~OSCReceiver()
Destructor.
bool connectToSocket(DatagramSocket &socketToUse)
Connects to a UDP datagram socket that is already set up, and starts listening to OSC packets arrivin...
OSCReceiver()
Creates an OSCReceiver.
bool disconnect()
Disconnects from the currently used UDP port.
std::function< void(const char *data, int dataSize)> FormatErrorHandler
An error handler function for OSC format errors that can be called by the OSCReceiver.
void registerFormatErrorHandler(FormatErrorHandler handler)
Installs a custom error handler which is called in case the receiver encounters a stream it cannot pa...
void addListener(Listener< MessageLoopCallback > *listenerToAdd)
Adds a listener that listens to OSC messages and bundles.
void removeListener(Listener< MessageLoopCallback > *listenerToRemove)
Removes a previously-registered listener.
bool waitForThreadToExit(int timeOutMilliseconds) const
Waits for the thread to stop.
bool startThread()
Attempts to start a new thread with default ('Priority::normal') priority.
bool threadShouldExit() const
Checks whether the thread has been told to stop running.
void signalThreadShouldExit()
Sets a flag to tell the thread it should stop.
This is a base class for classes that perform a unit test.
#define expectDoesNotThrow(expr)
Checks that the result of an expression does not throw an exception.
#define expectThrowsType(expr, type)
Checks that the result of an expression throws an exception of a certain type.
signed int int32
A platform-independent 32-bit signed integer type.
Array< OSCType > OSCTypeList
The type used for OSC type tag strings.
unsigned long long uint64
A platform-independent 64-bit unsigned integer type.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
char OSCType
The type used for OSC type tags.
unsigned int uint32
A platform-independent 32-bit unsigned integer type.
unsigned char uint8
A platform-independent 8-bit unsigned integer type.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
long long int64
A platform-independent 64-bit integer type.
std::u16string toString(NumberT value)
convert an number to an UTF-16 string