JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_OSCReceiver.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
12
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
15
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
18
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21 DISCLAIMED.
22
23 ==============================================================================
24*/
25
26namespace juce
27{
28
29namespace
30{
31 //==============================================================================
42 class OSCInputStream
43 {
44 public:
50 OSCInputStream (const void* sourceData, size_t sourceDataSize)
52 {}
53
54 //==============================================================================
56 const void* getData() const noexcept { return input.getData(); }
57
59 size_t getDataSize() const noexcept { return input.getDataSize(); }
60
62 uint64 getPosition() { return (uint64) input.getPosition(); }
63
65 bool setPosition (int64 pos) { return input.setPosition (pos); }
66
68 int64 getTotalLength() { return input.getTotalLength(); }
69
71 bool isExhausted() { return input.isExhausted(); }
72
73 //==============================================================================
74 int32 readInt32()
75 {
76 checkBytesAvailable (4, "OSC input stream exhausted while reading int32");
77 return input.readIntBigEndian();
78 }
79
81 {
82 checkBytesAvailable (8, "OSC input stream exhausted while reading uint64");
83 return (uint64) input.readInt64BigEndian();
84 }
85
86 float readFloat32()
87 {
88 checkBytesAvailable (4, "OSC input stream exhausted while reading float");
89 return input.readFloatBigEndian();
90 }
91
92 String readString()
93 {
94 checkBytesAvailable (4, "OSC input stream exhausted while reading string");
95
96 auto posBegin = (size_t) getPosition();
97 auto s = input.readString();
98 auto posEnd = (size_t) getPosition();
99
100 if (static_cast<const char*> (getData()) [posEnd - 1] != '\0')
101 throw OSCFormatError ("OSC input stream exhausted before finding null terminator of string");
102
103 size_t bytesRead = posEnd - posBegin;
104 readPaddingZeros (bytesRead);
105
106 return s;
107 }
108
109 MemoryBlock readBlob()
110 {
111 checkBytesAvailable (4, "OSC input stream exhausted while reading blob");
112
113 auto blobDataSize = input.readIntBigEndian();
114 checkBytesAvailable ((blobDataSize + 3) % 4, "OSC input stream exhausted before reaching end of blob");
115
116 MemoryBlock blob;
117 auto bytesRead = input.readIntoMemoryBlock (blob, (ssize_t) blobDataSize);
118 readPaddingZeros (bytesRead);
119
120 return blob;
121 }
122
123 OSCColour readColour()
124 {
125 checkBytesAvailable (4, "OSC input stream exhausted while reading colour");
126 return OSCColour::fromInt32 ((uint32) input.readIntBigEndian());
127 }
128
129 OSCTimeTag readTimeTag()
130 {
131 checkBytesAvailable (8, "OSC input stream exhausted while reading time tag");
132 return OSCTimeTag (uint64 (input.readInt64BigEndian()));
133 }
134
135 OSCAddress readAddress()
136 {
137 return OSCAddress (readString());
138 }
139
140 OSCAddressPattern readAddressPattern()
141 {
142 return OSCAddressPattern (readString());
143 }
144
145 //==============================================================================
147 {
149
150 checkBytesAvailable (4, "OSC input stream exhausted while reading type tag string");
151
152 if (input.readByte() != ',')
153 throw OSCFormatError ("OSC input stream format error: expected type tag string");
154
155 for (;;)
156 {
157 if (isExhausted())
158 throw OSCFormatError ("OSC input stream exhausted while reading type tag string");
159
160 const OSCType type = input.readByte();
161
162 if (type == 0)
163 break; // encountered null terminator. list is complete.
164
165 if (! OSCTypes::isSupportedType (type))
166 throw OSCFormatError ("OSC input stream format error: encountered unsupported type tag");
167
168 typeList.add (type);
169 }
170
171 auto bytesRead = (size_t) typeList.size() + 2;
172 readPaddingZeros (bytesRead);
173
174 return typeList;
175 }
176
177 //==============================================================================
178 OSCArgument readArgument (OSCType type)
179 {
180 switch (type)
181 {
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());
187
188 default:
189 // You supplied an invalid OSCType when calling readArgument! This should never happen.
191 throw OSCInternalError ("OSC input stream: internal error while reading message argument");
192 }
193 }
194
195 //==============================================================================
196 OSCMessage readMessage()
197 {
198 auto ap = readAddressPattern();
199 auto types = readTypeTagString();
200
201 OSCMessage msg (ap);
202
203 for (auto& type : types)
204 msg.addArgument (readArgument (type));
205
206 return msg;
207 }
208
209 //==============================================================================
211 {
212 // maxBytesToRead is only passed in here in case this bundle is a nested
213 // bundle, so we know when to consider the next element *not* part of this
214 // bundle anymore (but part of the outer bundle) and return.
215
216 checkBytesAvailable (16, "OSC input stream exhausted while reading bundle");
217
218 if (readString() != "#bundle")
219 throw OSCFormatError ("OSC input stream format error: bundle does not start with string '#bundle'");
220
221 OSCBundle bundle (readTimeTag());
222
223 size_t bytesRead = 16; // already read "#bundle" and timeTag
224 auto pos = getPosition();
225
226 while (! isExhausted() && bytesRead < maxBytesToRead)
227 {
228 bundle.addElement (readElement());
229
230 auto newPos = getPosition();
231 bytesRead += (size_t) (newPos - pos);
232 pos = newPos;
233 }
234
235 return bundle;
236 }
237
238 //==============================================================================
239 OSCBundle::Element readElement()
240 {
241 checkBytesAvailable (4, "OSC input stream exhausted while reading bundle element size");
242
243 auto elementSize = (size_t) readInt32();
244
245 if (elementSize < 4)
246 throw OSCFormatError ("OSC input stream format error: invalid bundle element size");
247
249 }
250
251 //==============================================================================
252 OSCBundle::Element readElementWithKnownSize (size_t elementSize)
253 {
254 checkBytesAvailable ((int64) elementSize, "OSC input stream exhausted while reading bundle element content");
255
256 auto firstContentChar = static_cast<const char*> (getData()) [getPosition()];
257
258 if (firstContentChar == '/') return OSCBundle::Element (readMessageWithCheckedSize (elementSize));
259 if (firstContentChar == '#') return OSCBundle::Element (readBundleWithCheckedSize (elementSize));
260
261 throw OSCFormatError ("OSC input stream: invalid bundle element content");
262 }
263
264 private:
265 MemoryInputStream input;
266
267 //==============================================================================
268 void readPaddingZeros (size_t bytesRead)
269 {
270 size_t numZeros = ~(bytesRead - 1) & 0x03;
271
272 while (numZeros > 0)
273 {
274 if (isExhausted() || input.readByte() != 0)
275 throw OSCFormatError ("OSC input stream format error: missing padding zeros");
276
277 --numZeros;
278 }
279 }
280
281 OSCBundle readBundleWithCheckedSize (size_t size)
282 {
283 auto begin = (size_t) getPosition();
284 auto maxBytesToRead = size - 4; // we've already read 4 bytes (the bundle size)
285
286 OSCBundle bundle (readBundle (maxBytesToRead));
287
288 if (getPosition() - begin != size)
289 throw OSCFormatError ("OSC input stream format error: wrong element content size encountered while reading");
290
291 return bundle;
292 }
293
294 OSCMessage readMessageWithCheckedSize (size_t size)
295 {
296 auto begin = (size_t) getPosition();
297 auto message = readMessage();
298
299 if (getPosition() - begin != size)
300 throw OSCFormatError ("OSC input stream format error: wrong element content size encountered while reading");
301
302 return message;
303 }
304
305 void checkBytesAvailable (int64 requiredBytes, const char* message)
306 {
307 if (input.getNumBytesRemaining() < requiredBytes)
308 throw OSCFormatError (message);
309 }
310 };
311
312} // namespace
313
314
315//==============================================================================
316struct OSCReceiver::Pimpl : private Thread,
317 private MessageListener
318{
320 {
321 }
322
323 ~Pimpl() override
324 {
325 disconnect();
326 }
327
328 //==============================================================================
329 bool connectToPort (int portNumber)
330 {
331 if (! disconnect())
332 return false;
333
334 socket.setOwned (new DatagramSocket (false));
335
336 if (! socket->bindToPort (portNumber))
337 return false;
338
339 startThread();
340 return true;
341 }
342
343 bool connectToSocket (DatagramSocket& newSocket)
344 {
345 if (! disconnect())
346 return false;
347
348 socket.setNonOwned (&newSocket);
349 startThread();
350 return true;
351 }
352
353 bool disconnect()
354 {
355 if (socket != nullptr)
356 {
358
359 if (socket.willDeleteObject())
360 socket->shutdown();
361
362 waitForThreadToExit (10000);
363 socket.reset();
364 }
365
366 return true;
367 }
368
369 //==============================================================================
371 {
372 listeners.add (listenerToAdd);
373 }
374
376 {
377 realtimeListeners.add (listenerToAdd);
378 }
379
382 {
383 addListenerWithAddress (listenerToAdd, addressToMatch, listenersWithAddress);
384 }
385
387 {
388 addListenerWithAddress (listenerToAdd, addressToMatch, realtimeListenersWithAddress);
389 }
390
392 {
393 listeners.remove (listenerToRemove);
394 }
395
397 {
398 realtimeListeners.remove (listenerToRemove);
399 }
400
402 {
403 removeListenerWithAddress (listenerToRemove, listenersWithAddress);
404 }
405
407 {
408 removeListenerWithAddress (listenerToRemove, realtimeListenersWithAddress);
409 }
410
411 //==============================================================================
413 {
415
416 // the payload of the message. Can be either an OSCMessage or an OSCBundle.
417 OSCBundle::Element content;
418 };
419
420 //==============================================================================
421 void handleBuffer (const char* data, size_t dataSize)
422 {
423 OSCInputStream inStream (data, dataSize);
424
425 try
426 {
427 auto content = inStream.readElementWithKnownSize (dataSize);
428
429 // realtime listeners should receive the OSC content first - and immediately
430 // on this thread:
431 callRealtimeListeners (content);
432
433 if (content.isMessage())
434 callRealtimeListenersWithAddress (content.getMessage());
435
436 // now post the message that will trigger the handleMessage callback
437 // dealing with the non-realtime listeners.
438 if (listeners.size() > 0 || listenersWithAddress.size() > 0)
439 postMessage (new CallbackMessage (content));
440 }
441 catch (const OSCFormatError&)
442 {
443 NullCheckedInvocation::invoke (formatErrorHandler, data, (int) dataSize);
444 }
445 }
446
447 //==============================================================================
448 void registerFormatErrorHandler (OSCReceiver::FormatErrorHandler handler)
449 {
450 formatErrorHandler = handler;
451 }
452
453private:
454 //==============================================================================
455 void run() override
456 {
457 int bufferSize = 65535;
458 HeapBlock<char> oscBuffer (bufferSize);
459
460 while (! threadShouldExit())
461 {
462 jassert (socket != nullptr);
463 auto ready = socket->waitUntilReady (true, 100);
464
465 if (ready < 0 || threadShouldExit())
466 return;
467
468 if (ready == 0)
469 continue;
470
471 auto bytesRead = (size_t) socket->read (oscBuffer.getData(), bufferSize, false);
472
473 if (bytesRead >= 4)
474 handleBuffer (oscBuffer.getData(), bytesRead);
475 }
476 }
477
478 //==============================================================================
479 template <typename ListenerType>
480 void addListenerWithAddress (ListenerType* listenerToAdd,
481 OSCAddress address,
483 {
484 for (auto& i : array)
485 if (address == i.first && listenerToAdd == i.second)
486 return;
487
488 array.add (std::make_pair (address, listenerToAdd));
489 }
490
491 //==============================================================================
492 template <typename ListenerType>
493 void removeListenerWithAddress (ListenerType* listenerToRemove,
495 {
496 for (int i = 0; i < array.size(); ++i)
497 {
498 if (listenerToRemove == array.getReference (i).second)
499 {
500 // aarrgh... can't simply call array.remove (i) because this
501 // requires a default c'tor to be present for OSCAddress...
502 // luckily, we don't care about methods preserving element order:
503 array.swap (i, array.size() - 1);
504 array.removeLast();
505 break;
506 }
507 }
508 }
509
510 //==============================================================================
511 void handleMessage (const Message& msg) override
512 {
513 if (auto* callbackMessage = dynamic_cast<const CallbackMessage*> (&msg))
514 {
515 auto& content = callbackMessage->content;
516
517 callListeners (content);
518
519 if (content.isMessage())
520 callListenersWithAddress (content.getMessage());
521 }
522 }
523
524 //==============================================================================
525 void callListeners (const OSCBundle::Element& content)
526 {
527 using OSCListener = OSCReceiver::Listener<OSCReceiver::MessageLoopCallback>;
528
529 if (content.isMessage())
530 {
531 auto&& message = content.getMessage();
532 listeners.call ([&] (OSCListener& l) { l.oscMessageReceived (message); });
533 }
534 else if (content.isBundle())
535 {
536 auto&& bundle = content.getBundle();
537 listeners.call ([&] (OSCListener& l) { l.oscBundleReceived (bundle); });
538 }
539 }
540
541 void callRealtimeListeners (const OSCBundle::Element& content)
542 {
543 using OSCListener = OSCReceiver::Listener<OSCReceiver::RealtimeCallback>;
544
545 if (content.isMessage())
546 {
547 auto&& message = content.getMessage();
548 realtimeListeners.call ([&] (OSCListener& l) { l.oscMessageReceived (message); });
549 }
550 else if (content.isBundle())
551 {
552 auto&& bundle = content.getBundle();
553 realtimeListeners.call ([&] (OSCListener& l) { l.oscBundleReceived (bundle); });
554 }
555 }
556
557 //==============================================================================
558 void callListenersWithAddress (const OSCMessage& message)
559 {
560 for (auto& entry : listenersWithAddress)
561 if (auto* listener = entry.second)
562 if (message.getAddressPattern().matches (entry.first))
563 listener->oscMessageReceived (message);
564 }
565
566 void callRealtimeListenersWithAddress (const OSCMessage& message)
567 {
568 for (auto& entry : realtimeListenersWithAddress)
569 if (auto* listener = entry.second)
570 if (message.getAddressPattern().matches (entry.first))
571 listener->oscMessageReceived (message);
572 }
573
574 //==============================================================================
577
580
582 OSCReceiver::FormatErrorHandler formatErrorHandler { nullptr };
583
585};
586
587//==============================================================================
588OSCReceiver::OSCReceiver (const String& threadName) : pimpl (new Pimpl (threadName))
589{
590}
591
593{
594}
595
597{
598 pimpl.reset();
599}
600
601bool OSCReceiver::connect (int portNumber)
602{
603 return pimpl->connectToPort (portNumber);
604}
605
607{
608 return pimpl->connectToSocket (socket);
609}
610
612{
613 return pimpl->disconnect();
614}
615
620
625
630
635
640
645
650
655
657{
658 pimpl->registerFormatErrorHandler (handler);
659}
660
661
662//==============================================================================
663//==============================================================================
664#if JUCE_UNIT_TESTS
665
667{
668public:
670 : UnitTest ("OSCInputStream class", UnitTestCategories::osc)
671 {}
672
673 void runTest() override
674 {
675 beginTest ("reading OSC addresses");
676 {
677 const char buffer[16] = {
678 '/', 't', 'e', 's', 't', '/', 'f', 'a',
679 'd', 'e', 'r', '7', '\0', '\0', '\0', '\0' };
680
681 // reading a valid osc address:
682 {
683 OSCInputStream inStream (buffer, sizeof (buffer));
684 OSCAddress address = inStream.readAddress();
685
686 expect (inStream.getPosition() == sizeof (buffer));
687 expectEquals (address.toString(), String ("/test/fader7"));
688 }
689
690 // check various possible failures:
691 {
692 // zero padding is present, but size is not modulo 4:
693 OSCInputStream inStream (buffer, 15);
694 expectThrowsType (inStream.readAddress(), OSCFormatError)
695 }
696 {
697 // zero padding is missing:
698 OSCInputStream inStream (buffer, 12);
699 expectThrowsType (inStream.readAddress(), OSCFormatError)
700 }
701 {
702 // pattern does not start with a forward slash:
703 OSCInputStream inStream (buffer + 4, 12);
704 expectThrowsType (inStream.readAddress(), OSCFormatError)
705 }
706 }
707
708 beginTest ("reading OSC address patterns");
709 {
710 const char buffer[20] = {
711 '/', '*', '/', '*', 'p', 'u', 't', '/',
712 'f', 'a', 'd', 'e', 'r', '[', '0', '-',
713 '9', ']', '\0', '\0' };
714
715 // reading a valid osc address pattern:
716 {
717 OSCInputStream inStream (buffer, sizeof (buffer));
718 expectDoesNotThrow (inStream.readAddressPattern());
719 }
720 {
721 OSCInputStream inStream (buffer, sizeof (buffer));
722 OSCAddressPattern ap = inStream.readAddressPattern();
723
724 expect (inStream.getPosition() == sizeof (buffer));
725 expectEquals (ap.toString(), String ("/*/*put/fader[0-9]"));
726 expect (ap.containsWildcards());
727 }
728
729 // check various possible failures:
730 {
731 // zero padding is present, but size is not modulo 4:
732 OSCInputStream inStream (buffer, 19);
733 expectThrowsType (inStream.readAddressPattern(), OSCFormatError)
734 }
735 {
736 // zero padding is missing:
737 OSCInputStream inStream (buffer, 16);
738 expectThrowsType (inStream.readAddressPattern(), OSCFormatError)
739 }
740 {
741 // pattern does not start with a forward slash:
742 OSCInputStream inStream (buffer + 4, 16);
743 expectThrowsType (inStream.readAddressPattern(), OSCFormatError)
744 }
745 }
746
747 beginTest ("reading OSC time tags");
748
749 {
750 char buffer[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
751 OSCInputStream inStream (buffer, sizeof (buffer));
752
753 OSCTimeTag tag = inStream.readTimeTag();
754 expect (tag.isImmediately());
755 }
756 {
757 char buffer[8] = { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
758 OSCInputStream inStream (buffer, sizeof (buffer));
759
760 OSCTimeTag tag = inStream.readTimeTag();
761 expect (! tag.isImmediately());
762 }
763
764 beginTest ("reading OSC arguments");
765
766 {
767 // test data:
768 int testInt = -2015;
769 const uint8 testIntRepresentation[] = { 0xFF, 0xFF, 0xF8, 0x21 }; // big endian two's complement
770
771 float testFloat = 345.6125f;
772 const uint8 testFloatRepresentation[] = { 0x43, 0xAC, 0xCE, 0x66 }; // big endian IEEE 754
773
774 String testString = "Hello, World!";
775 const char testStringRepresentation[] = {
776 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W',
777 'o', 'r', 'l', 'd', '!', '\0', '\0', '\0' }; // padded to size % 4 == 0
778
779 const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
780 const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
781 const uint8 testBlobRepresentation[] = {
782 0x00, 0x00, 0x00, 0x05,
783 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x00, 0x00 }; // size prefixed + padded to size % 4 == 0
784
785 // read:
786 {
787 {
788 // int32:
789 OSCInputStream inStream (testIntRepresentation, sizeof (testIntRepresentation));
790 OSCArgument arg = inStream.readArgument (OSCTypes::int32);
791
792 expect (inStream.getPosition() == 4);
793 expect (arg.isInt32());
794 expectEquals (arg.getInt32(), testInt);
795 }
796 {
797 // float32:
799 OSCArgument arg = inStream.readArgument (OSCTypes::float32);
800
801 expect (inStream.getPosition() == 4);
802 expect (arg.isFloat32());
803 expectEquals (arg.getFloat32(), testFloat);
804 }
805 {
806 // string:
808 OSCArgument arg = inStream.readArgument (OSCTypes::string);
809
810 expect (inStream.getPosition() == sizeof (testStringRepresentation));
811 expect (arg.isString());
812 expectEquals (arg.getString(), testString);
813 }
814 {
815 // blob:
816 OSCInputStream inStream (testBlobRepresentation, sizeof (testBlobRepresentation));
817 OSCArgument arg = inStream.readArgument (OSCTypes::blob);
818
819 expect (inStream.getPosition() == sizeof (testBlobRepresentation));
820 expect (arg.isBlob());
821 expect (arg.getBlob() == testBlob);
822 }
823 }
824
825 // read invalid representations:
826
827 {
828 // not enough bytes
829 {
830 const uint8 rawData[] = { 0xF8, 0x21 };
831
832 OSCInputStream inStream (rawData, sizeof (rawData));
833
834 expectThrowsType (inStream.readArgument (OSCTypes::int32), OSCFormatError);
835 expectThrowsType (inStream.readArgument (OSCTypes::float32), OSCFormatError);
836 }
837
838 // test string not being padded to multiple of 4 bytes:
839 {
840 const char rawData[] = {
841 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W',
842 'o', 'r', 'l', 'd', '!', '\0' }; // padding missing
843
844 OSCInputStream inStream (rawData, sizeof (rawData));
845
846 expectThrowsType (inStream.readArgument (OSCTypes::string), OSCFormatError);
847 }
848 {
849 const char rawData[] = {
850 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W',
851 'o', 'r', 'l', 'd', '!', '\0', 'x', 'x' }; // padding with non-zero chars
852
853 OSCInputStream inStream (rawData, sizeof (rawData));
854
855 expectThrowsType (inStream.readArgument (OSCTypes::string), OSCFormatError);
856 }
857
858 // test blob not being padded to multiple of 4 bytes:
859 {
860 const uint8 rawData[] = { 0x00, 0x00, 0x00, 0x05, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; // padding missing
861
862 OSCInputStream inStream (rawData, sizeof (rawData));
863
864 expectThrowsType (inStream.readArgument (OSCTypes::blob), OSCFormatError);
865 }
866
867 // test blob having wrong size
868 {
869 const uint8 rawData[] = { 0x00, 0x00, 0x00, 0x12, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
870
871 OSCInputStream inStream (rawData, sizeof (rawData));
872
873 expectThrowsType (inStream.readArgument (OSCTypes::blob), OSCFormatError);
874 }
875 }
876 }
877
878 beginTest ("reading OSC messages (type tag string)");
879 {
880 {
881 // valid empty message
882 const char data[] = {
883 '/', 't', 'e', 's', 't', '\0', '\0', '\0',
884 ',', '\0', '\0', '\0' };
885
886 OSCInputStream inStream (data, sizeof (data));
887
888 auto msg = inStream.readMessage();
889 expect (msg.getAddressPattern().toString() == "/test");
890 expect (msg.size() == 0);
891 }
892
893 {
894 // invalid message: no type tag string
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' };
899
900 OSCInputStream inStream (data, sizeof (data));
901
902 expectThrowsType (inStream.readMessage(), OSCFormatError);
903 }
904
905 {
906 // invalid message: no type tag string and also empty
907 const char data[] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0' };
908
909 OSCInputStream inStream (data, sizeof (data));
910
911 expectThrowsType (inStream.readMessage(), OSCFormatError);
912 }
913
914 // invalid message: wrong padding
915 {
916 const char data[] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0', ',', '\0', '\0', '\0' };
917 OSCInputStream inStream (data, sizeof (data) - 1);
918
919 expectThrowsType (inStream.readMessage(), OSCFormatError);
920 }
921
922 // invalid message: says it contains an arg, but doesn't
923 {
924 const char data[] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0', ',', 'i', '\0', '\0' };
925 OSCInputStream inStream (data, sizeof (data));
926
927 expectThrowsType (inStream.readMessage(), OSCFormatError);
928 }
929
930 // invalid message: binary size does not match size deducted from type tag string
931 {
932 const char data[] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0', ',', 'i', 'f', '\0' };
933 OSCInputStream inStream (data, sizeof (data));
934
935 expectThrowsType (inStream.readMessage(), OSCFormatError);
936 }
937 }
938
939 beginTest ("reading OSC messages (contents)");
940 {
941 // valid non-empty message.
942
943 {
944 int32 testInt = -2015;
945 float testFloat = 345.6125f;
946 String testString = "Hello, World!";
947
948 const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
949 const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
950
951 uint8 data[] = {
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
958 };
959
960 OSCInputStream inStream (data, sizeof (data));
961
962 auto msg = inStream.readMessage();
963
964 expectEquals (msg.getAddressPattern().toString(), String ("/test"));
965 expectEquals (msg.size(), 4);
966
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);
971
972 expectEquals (msg[0].getInt32(), testInt);
973 expectEquals (msg[1].getFloat32(), testFloat);
974 expectEquals (msg[2].getString(), testString);
975 expect (msg[3].getBlob() == testBlob);
976 }
977 }
978 beginTest ("reading OSC messages (handling of corrupted messages)");
979 {
980 // invalid messages
981
982 {
983 OSCInputStream inStream (nullptr, 0);
984 expectThrowsType (inStream.readMessage(), OSCFormatError);
985 }
986
987 {
988 const uint8 data[] = { 0x00 };
989 OSCInputStream inStream (data, 0);
990 expectThrowsType (inStream.readMessage(), OSCFormatError);
991 }
992
993 {
994 uint8 data[] = {
995 '/', 't', 'e', 's', 't', '\0', '\0', '\0',
996 ',', 'i', 'f', 's', 'b', // type tag string not padded
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
1001 };
1002
1003 OSCInputStream inStream (data, sizeof (data));
1004 expectThrowsType (inStream.readMessage(), OSCFormatError);
1005 }
1006
1007 {
1008 uint8 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' // rest of message cut off
1014 };
1015
1016 OSCInputStream inStream (data, sizeof (data));
1017 expectThrowsType (inStream.readMessage(), OSCFormatError);
1018 }
1019 }
1020
1021 beginTest ("reading OSC messages (handling messages without type tag strings)");
1022 {
1023
1024 {
1025 uint8 data[] = { '/', 't', 'e', 's', 't', '\0', '\0', '\0' };
1026
1027 OSCInputStream inStream (data, sizeof (data));
1028 expectThrowsType (inStream.readMessage(), OSCFormatError);
1029 }
1030
1031 {
1032 uint8 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
1038 };
1039
1040 OSCInputStream inStream (data, sizeof (data));
1041 expectThrowsType (inStream.readMessage(), OSCFormatError);
1042 }
1043 }
1044
1045 beginTest ("reading OSC bundles");
1046 {
1047 // valid bundle (empty)
1048 {
1049 uint8 data[] = {
1050 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1051 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
1052 };
1053
1054 OSCInputStream inStream (data, sizeof (data));
1055 OSCBundle bundle = inStream.readBundle();
1056
1057 expect (bundle.getTimeTag().isImmediately());
1058 expect (bundle.size() == 0);
1059 }
1060
1061 // valid bundle (containing both messages and other bundles)
1062
1063 {
1064 int32 testInt = -2015;
1065 float testFloat = 345.6125f;
1066 String testString = "Hello, World!";
1067 const uint8 testBlobData[] = { 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
1068 const MemoryBlock testBlob (testBlobData, sizeof (testBlobData));
1069
1070 uint8 data[] = {
1071 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1072 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1073
1074 0x00, 0x00, 0x00, 0x34,
1075
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,
1082
1083 0x00, 0x00, 0x00, 0x0C,
1084
1085 '/', 't', 'e', 's', 't', '/', '2', '\0',
1086 ',', '\0', '\0', '\0',
1087
1088 0x00, 0x00, 0x00, 0x10,
1089
1090 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1091 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
1092 };
1093
1094 OSCInputStream inStream (data, sizeof (data));
1095 OSCBundle bundle = inStream.readBundle();
1096
1097 expect (bundle.getTimeTag().isImmediately());
1098 expect (bundle.size() == 3);
1099
1100 OSCBundle::Element* elements = bundle.begin();
1101
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);
1113
1114 expect (elements[1].isMessage());
1115 expect (elements[1].getMessage().getAddressPattern().toString() == "/test/2");
1116 expect (elements[1].getMessage().size() == 0);
1117
1118 expect (elements[2].isBundle());
1119 expect (! elements[2].getBundle().getTimeTag().isImmediately());
1120 }
1121
1122 // invalid bundles.
1123
1124 {
1125 uint8 data[] = {
1126 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1127 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1128
1129 0x00, 0x00, 0x00, 0x34, // wrong bundle element size (too large)
1130
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'
1134 };
1135
1136 OSCInputStream inStream (data, sizeof (data));
1137 expectThrowsType (inStream.readBundle(), OSCFormatError);
1138 }
1139
1140 {
1141 uint8 data[] = {
1142 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1143 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
1144
1145 0x00, 0x00, 0x00, 0x08, // wrong bundle element size (too small)
1146
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'
1150 };
1151
1152 OSCInputStream inStream (data, sizeof (data));
1153 expectThrowsType (inStream.readBundle(), OSCFormatError);
1154 }
1155
1156 {
1157 uint8 data[] = {
1158 '#', 'b', 'u', 'n', 'd', 'l', 'e', '\0',
1159 0x00, 0x00, 0x00, 0x00 // incomplete time tag
1160 };
1161
1162 OSCInputStream inStream (data, sizeof (data));
1163 expectThrowsType (inStream.readBundle(), OSCFormatError);
1164 }
1165
1166 {
1167 uint8 data[] = {
1168 '#', 'b', 'u', 'n', 'x', 'l', 'e', '\0', // wrong initial string
1169 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1170 };
1171
1172 OSCInputStream inStream (data, sizeof (data));
1173 expectThrowsType (inStream.readBundle(), OSCFormatError);
1174 }
1175
1176 {
1177 uint8 data[] = {
1178 '#', 'b', 'u', 'n', 'd', 'l', 'e', // padding missing from string
1179 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1180 };
1181
1182 OSCInputStream inStream (data, sizeof (data));
1183 expectThrowsType (inStream.readBundle(), OSCFormatError);
1184 }
1185 }
1186 }
1187};
1188
1190
1191#endif
1192
1193} // namespace juce
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.
An OSC address.
An OSC bundle element.
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...
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.
The JUCE String class!
Definition juce_String.h:53
Encapsulates a thread.
Definition juce_Thread.h:43
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.
T data(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
#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.
T make_pair(T... args)
JUCE Namespace.
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...
Definition juce_Memory.h:88
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.
T reset(T... args)
T size(T... args)
socket
std::u16string toString(NumberT value)
convert an number to an UTF-16 string
Exception type thrown when the OSC module fails to parse something because of a data format not compa...
typedef size_t