diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..aedca32 --- /dev/null +++ b/.clang-format @@ -0,0 +1,74 @@ +# Tap House Rules — the Tap family house style. Copy verbatim into every *Tap repo. +# 4-space indent (incl. namespaces), aligned declaration/assignment columns, +# attached braces (else/catch break), comma-first ctor initializers, +# left-bound pointers, 120-column limit. Layout only — naming and mandatory +# braces are enforced separately by .clang-tidy (clang-format cannot check +# identifier names, and its brace insertion is not semantically aware). +Language: Cpp +BasedOnStyle: LLVM +Standard: c++20 + +ColumnLimit: 120 +IndentWidth: 4 +AccessModifierOffset: -2 +NamespaceIndentation: All + +PointerAlignment: Left +DerivePointerAlignment: false +BreakBeforeBinaryOperators: NonAssignment +SpaceBeforeCpp11BracedList: false +AlwaysBreakTemplateDeclarations: Yes + +# Braces attach everywhere (including functions); only else/catch break. +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: false + AfterClass: false + AfterStruct: false + AfterNamespace: false + AfterControlStatement: Never + BeforeElse: true + BeforeCatch: true +BreakConstructorInitializers: BeforeComma +PackConstructorInitializers: Never + +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: true +AlignTrailingComments: true + +# Short accessor functions and lambdas may stay inline, but control-flow +# statements never do: every if/for/while is braced AND expanded (see +# .clang-tidy readability-braces-around-statements). +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AllowShortBlocksOnASingleLine: Never + +BreakStringLiterals: false +KeepEmptyLinesAtTheStartOfBlocks: false +InsertNewlineAtEOF: true + +# Include ordering: main header (auto, priority 0) -> C++ standard -> +# third-party -> this project. Regroup enforces it; blank lines between groups. +SortIncludes: CaseSensitive +IncludeBlocks: Regroup +IncludeCategories: + # C++ standard library: with no '/' and no '.' (e.g. ) + - Regex: '^<[[:alnum:]_]+>$' + Priority: 2 + # Other angle-bracket headers (third-party, e.g. ) + - Regex: '^<.*>$' + Priority: 3 + # This project: quoted includes + - Regex: '^".*"$' + Priority: 4 + +# Min-DevKit declarative DSL (Max/Min externals: TapTools, AmbiTap-Max, ...). +# MIN_FUNCTION / MIN_ARGUMENT_FUNCTION expand to a lambda; teach clang-format +# their shape so attribute/message/argument setter bodies format as lambda +# blocks instead of being shredded. Completely inert for repos that don't use +# these macros (the pure-C++ libraries). Requires clang-format >= 15. +Macros: + - 'MIN_FUNCTION=[](const atoms& args, int inlet) -> atoms' + - 'MIN_ARGUMENT_FUNCTION=[](const atom& arg, int index) -> void' diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..6ed3dc7 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,30 @@ +# OscTap-local Tap House Rules — naming DISABLED (documented exception). +# +# OscTap is a drop-in source-compatible continuation of oscpack, so its public +# API deliberately keeps oscpack's original identifiers: PascalCase types and +# methods (ReceivedMessage, OutboundPacketStream, BeginBundle(), AsFloat()) and +# trailing-underscore data members (size_, value_). Renaming them to the house +# snake_case/m_ scheme would break the compatibility that is the library's +# reason to exist, so the naming half of the house rules does NOT apply here. +# See TapHouse STYLE.md "Repo exception — OscTap (drop-in legacy continuation)". +# +# This file therefore intentionally DIVERGES from the canonical TapHouse +# .clang-tidy (it drops readability-identifier-naming) and OscTap's CI runs a +# format-only style gate (.github/workflows/style.yml) rather than the shared +# drift-check.yml, which requires the three configs to be byte-identical. +# +# The LAYOUT half is still fully adopted via the verbatim .clang-format, and +# mandatory braces around every control-flow body are kept below. +# +# NOTE: WarningsAsErrors is intentionally NOT set here so local runs only warn. +Checks: > + -*, + readability-braces-around-statements + +# The library headers live under osctap/; tests under tests/. +HeaderFilterRegex: '.*/(osctap|tests)/.*' + +CheckOptions: + # --- Mandatory braces: brace every control-flow body, even one-liners --- + - key: readability-braces-around-statements.ShortStatementLines + value: '0' diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9a572b4 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Bulk clang-format reformat under the shared Tap house style. +46047f3e3859e7945e5054879d239cb073d80bda diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml new file mode 100644 index 0000000..161f11a --- /dev/null +++ b/.github/workflows/style.yml @@ -0,0 +1,26 @@ +name: Tap House Style + +# Enforces the Tap House Rules LAYOUT half only (.clang-format). +# +# OscTap is a drop-in source-compatible continuation of oscpack and is exempt +# from the house NAMING rules (readability-identifier-naming) — renaming the +# public API would break compatibility. See TapHouse STYLE.md "Repo exception — +# OscTap". Because a naming-exempt repo cannot keep the three shared configs +# byte-identical, this deliberately runs a format-only gate instead of calling +# the shared tap/taphouse drift-check.yml. +on: [push, pull_request] + +jobs: + clang-format: + name: clang-format (layout) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + # Pinned major version: formatting output can differ across releases. + - name: Install clang-format + run: sudo apt-get update -q && sudo apt-get install -y -q clang-format-18 + - name: Check formatting (own sources) + run: | + files=$(find osctap oscpack tests demos examples fuzz android \ + -type f \( -name '*.h' -o -name '*.cpp' -o -name '*.cc' -o -name '*.c' \)) + clang-format-18 --dry-run --Werror $files diff --git a/STYLE.md b/STYLE.md new file mode 100644 index 0000000..ebfcf54 --- /dev/null +++ b/STYLE.md @@ -0,0 +1,122 @@ +# Tap House Rules + +> *The Tap house style — always on tap.* + +The shared house style for the Tap libraries (AmbiTap, SampleRateTap, OscTap, +and future `*Tap` libraries). Anchored to the C++ standard library's own +conventions (per the ISO C++ Core Guidelines "NL" section), with a small set +of deliberate, documented exceptions. + +Two config files enforce this and must be copied verbatim into every repo: + +- **`.clang-format`** — layout (whitespace, braces, alignment, includes). +- **`.clang-tidy`** — identifier naming (`readability-identifier-naming`). + clang-format *cannot* check names; this is what does. + +CI runs `clang-format --dry-run --Werror` and `clang-tidy` so drift can't +return. + +--- + +## 1. Naming + +| Kind | Convention | Example | +|------|-----------|---------| +| Types (class/struct/enum/alias) | `snake_case` | `encoder`, `spsc_ring` | +| Functions / methods | `snake_case` | `push`, `write_available` | +| Variables / parameters / locals | `snake_case` | `frame_count`, `min_capacity` | +| Concepts | `snake_case` (like types) | `sample_type` | +| Template parameters | `PascalCase` — the ONLY leading-capital names | `T`, `S`, `Sample`, `Allocator` | +| Private/protected data members | `m_` + `snake_case` | `m_channels`, `m_order` | +| Public data members (struct fields) | `snake_case`, no prefix | `sample_rate_hz` | +| Constants (namespace/class/static) | `k_` + `snake_case` | `k_smoothing_samples`, `k_cache_line` | +| Enumerators | `snake_case` | `state::filling` | +| Macros | `ALL_CAPS` | `SRT_VERSION_MAJOR`, `TAP_EXPECTS` | + +A leading capital letter means **template parameter** and nothing else. This +is the standard library's own allocation (`CharT`, `Rep`, `Period`, +`Allocator`) and is why concepts are lower-case: they read in type position, +so they look like the types they constrain. + +**Deliberate deviations from strict std:** +- `k_` prefix on constants (std uses bare `snake_case`) — kept for use-site + clarity. Applies to namespace-, class-, and static-scope constants; + `constexpr` *locals* stay bare. +- `m_` prefix on encapsulated data members (std reserves `_`; user code has no + standard convention here) — kept for self-documentation and greppability. + +**Parameters take no prefix** (no `a_`/`an_`). `m_` already prevents any +member/parameter collision, and prefixes would clutter the public signatures +that *are* the library's contract. Lean on `const` and small functions for +input/local clarity. + +**Repo exception — OscTap (drop-in legacy continuation).** OscTap continues +[oscpack](http://www.rossbencina.com/code/oscpack) as a *drop-in +source-compatible* successor: its public API keeps oscpack's original +identifiers — PascalCase types and methods (`ReceivedMessage`, +`OutboundPacketStream`, `BeginBundle()`, `AsFloat()`) and trailing-underscore +data members (`size_`, `value_`). Renaming these to the house `snake_case`/`m_` +scheme would break the source compatibility that is the library's reason to +exist. OscTap therefore adopts the **layout** rules (`.clang-format`) in full +but is **exempt from the naming rules** (`readability-identifier-naming`): it +ships a local `.clang-tidy` that disables that check while keeping mandatory +braces, and its CI runs a format-only style gate instead of the shared +`drift-check.yml`. The exemption is specific to legacy-continuation repos; +greenfield `*Tap` code follows the naming rules above. + +## 2. Layout + +- **Indent:** 4 spaces, including inside namespaces. +- **Braces:** attached everywhere (functions included); only `else` and + `catch` break onto their own line. Every control-flow body is braced *and + expanded* onto its own lines — no single-line `if`/`for`/`while`, even for + guard clauses (braces via clang-tidy `readability-braces-around-statements`; + expansion via `AllowShortBlocksOnASingleLine: Never`). Short accessor + functions and lambdas may still be inline. +- **Brace-init spacing:** no space before a braced-init list — `float x{0.0f}`, + not `x {0.0f}`. +- **Column alignment:** consecutive declarations, assignments, and trailing + comments are aligned. +- **Constructor initializers:** comma-first, one per line, never packed. +- **Pointers/references:** bound to the type — `const float* p`, `T& r`. +- **Member declaration order (per NL.16):** `public` -> `protected` -> + `private`; within a class: types/aliases -> constructors/assignment/ + destructor -> functions -> data members last. +- **Column limit:** 120. +- **`const` placement:** west-const (`const T`, not `T const`) — enforced by + review, not tooling. + +## 3. Files + +- **Extension:** `.h` for headers (family-wide). +- **Header guard:** `#pragma once` (first line after the banner). Universally + supported by GCC/Clang/MSVC; replaces the `#ifndef`/`#define`/`#endif` triple. +- **Per-file banner:** three lines — + ```cpp + /// @file spsc_ring.h + /// @brief Lock-free single-producer single-consumer ring buffer. + // SPDX-License-Identifier: MIT + // Copyright 2025-2026 Timothy Place. + ``` +- **Doc comments:** `///` triple-slash with `@`-style commands + (`@param`, `@return`, `@throws`, `@pre`). Not the `\`-command dialect. +- **Include ordering:** (1) the file's own corresponding header (in a `.cpp`), + (2) C++ standard headers, (3) third-party, (4) this project — enforced by + clang-format `IncludeBlocks: Regroup`, blank line between groups. + +## 4. Safety idioms + +The Tap libraries are header-only, zero-dependency, and target real-time / +embedded use (some build `-fno-exceptions`). We adopt the *vocabulary* of the +GSL but take **no dependency on it**; the helpers below are freestanding. + +- **Contracts:** `TAP_EXPECTS(cond)` / `TAP_ENSURES(cond)` — assert in debug, + clamp or no-op in release, **never throw**. (Generalizes AmbiTap's + `validate.h`.) Not `gsl::Expects` (terminates + adds a dependency). +- **Narrowing:** `narrow_cast(x)` — a documented `static_cast` synonym for + intentional lossy conversions (e.g. Q15/Q31 fixed-point). Not `gsl::narrow` + (throws; unusable under `-fno-exceptions`). +- **Views:** `std::span` (C++20, freestanding-friendly). Never `gsl::span`. +- **Non-null / ownership / bounds:** expressed via `@pre` documentation and + debug asserts, **not** wrapper types. Raw pointers and raw indexing stay in + hot paths for performance; `not_null`/`owner`/`at()` are not used as types. diff --git a/android/osctap_jni.cpp b/android/osctap_jni.cpp index 46e6600..8488699 100644 --- a/android/osctap_jni.cpp +++ b/android/osctap_jni.cpp @@ -20,114 +20,121 @@ catches and surfaces a Java exception instead of aborting. */ -#include #include -#include "osc/OscReceivedElements.h" +#include + #include "osc/OscOutboundPacketStream.h" +#include "osc/OscReceivedElements.h" namespace { -// Append one boxed Java argument (Integer/Float/Boolean/String) as the matching -// OSC argument. Returns false if the type is unsupported. -bool AppendBoxedArg( JNIEnv* env, osctap::OutboundPacketStream& p, jobject arg ) -{ - if( arg == nullptr ) return false; - - jclass integerCls = env->FindClass( "java/lang/Integer" ); - jclass floatCls = env->FindClass( "java/lang/Float" ); - jclass boolCls = env->FindClass( "java/lang/Boolean" ); - jclass stringCls = env->FindClass( "java/lang/String" ); - - if( env->IsInstanceOf( arg, integerCls ) ){ - jint v = env->CallIntMethod( arg, env->GetMethodID( integerCls, "intValue", "()I" ) ); - p << (int32_t)v; - } else if( env->IsInstanceOf( arg, floatCls ) ){ - jfloat v = env->CallFloatMethod( arg, env->GetMethodID( floatCls, "floatValue", "()F" ) ); - p << (float)v; - } else if( env->IsInstanceOf( arg, boolCls ) ){ - jboolean v = env->CallBooleanMethod( arg, env->GetMethodID( boolCls, "booleanValue", "()Z" ) ); - p << (bool)(v == JNI_TRUE); - } else if( env->IsInstanceOf( arg, stringCls ) ){ - const char* s = env->GetStringUTFChars( (jstring)arg, nullptr ); - p << s; // const char* overload -> OSC string - env->ReleaseStringUTFChars( (jstring)arg, s ); - } else { - return false; + // Append one boxed Java argument (Integer/Float/Boolean/String) as the matching + // OSC argument. Returns false if the type is unsupported. + bool AppendBoxedArg(JNIEnv* env, osctap::OutboundPacketStream& p, jobject arg) { + if (arg == nullptr) + return false; + + jclass integerCls = env->FindClass("java/lang/Integer"); + jclass floatCls = env->FindClass("java/lang/Float"); + jclass boolCls = env->FindClass("java/lang/Boolean"); + jclass stringCls = env->FindClass("java/lang/String"); + + if (env->IsInstanceOf(arg, integerCls)) { + jint v = env->CallIntMethod(arg, env->GetMethodID(integerCls, "intValue", "()I")); + p << (int32_t)v; + } + else if (env->IsInstanceOf(arg, floatCls)) { + jfloat v = env->CallFloatMethod(arg, env->GetMethodID(floatCls, "floatValue", "()F")); + p << (float)v; + } + else if (env->IsInstanceOf(arg, boolCls)) { + jboolean v = env->CallBooleanMethod(arg, env->GetMethodID(boolCls, "booleanValue", "()Z")); + p << (bool)(v == JNI_TRUE); + } + else if (env->IsInstanceOf(arg, stringCls)) { + const char* s = env->GetStringUTFChars((jstring)arg, nullptr); + p << s; // const char* overload -> OSC string + env->ReleaseStringUTFChars((jstring)arg, s); + } + else { + return false; + } + return true; } - return true; -} -void ThrowJava( JNIEnv* env, const char* cls, const char* msg ) -{ - jclass c = env->FindClass( cls ); - if( c ) env->ThrowNew( c, msg ); -} + void ThrowJava(JNIEnv* env, const char* cls, const char* msg) { + jclass c = env->FindClass(cls); + if (c) + env->ThrowNew(c, msg); + } } // namespace -extern "C" JNIEXPORT jbyteArray JNICALL -Java_org_osctap_demo_OscTap_buildMessage( JNIEnv* env, jclass, jstring jaddress, jobjectArray args ) -{ - const char* address = env->GetStringUTFChars( jaddress, nullptr ); - char buffer[1024]; - jbyteArray result = nullptr; +extern "C" JNIEXPORT jbyteArray JNICALL Java_org_osctap_demo_OscTap_buildMessage(JNIEnv* env, jclass, jstring jaddress, + jobjectArray args) { + const char* address = env->GetStringUTFChars(jaddress, nullptr); + char buffer[1024]; + jbyteArray result = nullptr; try { - osctap::OutboundPacketStream p( buffer, sizeof(buffer) ); - p << osctap::BeginMessage( address ); - - const jsize n = args ? env->GetArrayLength( args ) : 0; - for( jsize i = 0; i < n; ++i ){ - jobject a = env->GetObjectArrayElement( args, i ); - if( !AppendBoxedArg( env, p, a ) ){ - env->ReleaseStringUTFChars( jaddress, address ); - ThrowJava( env, "java/lang/IllegalArgumentException", - "unsupported OSC argument type (use Integer/Float/Boolean/String)" ); + osctap::OutboundPacketStream p(buffer, sizeof(buffer)); + p << osctap::BeginMessage(address); + + const jsize n = args ? env->GetArrayLength(args) : 0; + for (jsize i = 0; i < n; ++i) { + jobject a = env->GetObjectArrayElement(args, i); + if (!AppendBoxedArg(env, p, a)) { + env->ReleaseStringUTFChars(jaddress, address); + ThrowJava(env, "java/lang/IllegalArgumentException", + "unsupported OSC argument type (use Integer/Float/Boolean/String)"); return nullptr; } } p << osctap::EndMessage(); - result = env->NewByteArray( (jsize)p.Size() ); - env->SetByteArrayRegion( result, 0, (jsize)p.Size(), - reinterpret_cast( p.Data() ) ); - } catch( const osctap::Exception& e ) { - ThrowJava( env, "java/lang/IllegalArgumentException", e.what() ); + result = env->NewByteArray((jsize)p.Size()); + env->SetByteArrayRegion(result, 0, (jsize)p.Size(), reinterpret_cast(p.Data())); + } + catch (const osctap::Exception& e) { + ThrowJava(env, "java/lang/IllegalArgumentException", e.what()); } - env->ReleaseStringUTFChars( jaddress, address ); + env->ReleaseStringUTFChars(jaddress, address); return result; } -extern "C" JNIEXPORT jstring JNICALL -Java_org_osctap_demo_OscTap_describe( JNIEnv* env, jclass, jbyteArray packet ) -{ - const jsize n = env->GetArrayLength( packet ); - jbyte* bytes = env->GetByteArrayElements( packet, nullptr ); +extern "C" JNIEXPORT jstring JNICALL Java_org_osctap_demo_OscTap_describe(JNIEnv* env, jclass, jbyteArray packet) { + const jsize n = env->GetArrayLength(packet); + jbyte* bytes = env->GetByteArrayElements(packet, nullptr); std::string out; try { // The parser uses byte-assembly (de)serialization, so the buffer needs // no special alignment. Untrusted input -> wrap in try/catch. - osctap::ReceivedMessage m( osctap::ReceivedPacket( - reinterpret_cast( bytes ), (std::size_t)n ) ); + osctap::ReceivedMessage m(osctap::ReceivedPacket(reinterpret_cast(bytes), (std::size_t)n)); out = m.AddressPattern(); - for( auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a ){ + for (auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a) { out += ' '; - if( a->IsInt32() ) out += std::to_string( a->AsInt32Unchecked() ); - else if( a->IsFloat() ) out += std::to_string( a->AsFloatUnchecked() ); - else if( a->IsString() ) out += std::string("\"") + a->AsStringUnchecked() + "\""; - else if( a->IsBool() ) out += a->AsBoolUnchecked() ? "true" : "false"; - else out += '?'; + if (a->IsInt32()) + out += std::to_string(a->AsInt32Unchecked()); + else if (a->IsFloat()) + out += std::to_string(a->AsFloatUnchecked()); + else if (a->IsString()) + out += std::string("\"") + a->AsStringUnchecked() + "\""; + else if (a->IsBool()) + out += a->AsBoolUnchecked() ? "true" : "false"; + else + out += '?'; } - } catch( const osctap::Exception& e ) { - env->ReleaseByteArrayElements( packet, bytes, JNI_ABORT ); - ThrowJava( env, "java/lang/IllegalArgumentException", e.what() ); + } + catch (const osctap::Exception& e) { + env->ReleaseByteArrayElements(packet, bytes, JNI_ABORT); + ThrowJava(env, "java/lang/IllegalArgumentException", e.what()); return nullptr; } - env->ReleaseByteArrayElements( packet, bytes, JNI_ABORT ); - return env->NewStringUTF( out.c_str() ); + env->ReleaseByteArrayElements(packet, bytes, JNI_ABORT); + return env->NewStringUTF(out.c_str()); } diff --git a/demos/osc_send.cpp b/demos/osc_send.cpp index 76dcd57..ff7598b 100644 --- a/demos/osc_send.cpp +++ b/demos/osc_send.cpp @@ -23,84 +23,102 @@ osc_send 192.168.1.10 9000 /sensor/temp f:21.4 */ -#include "ip/UdpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include #include -namespace { - -bool ParseInt( const char *s, int32_t& out ) -{ - char *end = nullptr; - long v = std::strtol( s, &end, 10 ); - if( end == s || *end != '\0' ) return false; - out = static_cast( v ); - return true; -} +#include "ip/IpEndpointName.h" +#include "ip/UdpSocket.h" +#include "osc/OscOutboundPacketStream.h" -bool ParseFloat( const char *s, float& out ) -{ - char *end = nullptr; - float v = std::strtof( s, &end ); - if( end == s || *end != '\0' ) return false; - out = v; - return true; -} +namespace { -void AppendArg( osctap::OutboundPacketStream& p, const char *tok ) -{ - if( std::strcmp( tok, "T" ) == 0 ) { p << true; return; } - if( std::strcmp( tok, "F" ) == 0 ) { p << false; return; } + bool ParseInt(const char* s, int32_t& out) { + char* end = nullptr; + long v = std::strtol(s, &end, 10); + if (end == s || *end != '\0') + return false; + out = static_cast(v); + return true; + } - if( std::strncmp( tok, "i:", 2 ) == 0 ) { p << (int32_t)std::atoi( tok + 2 ); return; } - if( std::strncmp( tok, "f:", 2 ) == 0 ) { p << (float)std::atof( tok + 2 ); return; } - if( std::strncmp( tok, "s:", 2 ) == 0 ) { const char *s = tok + 2; p << s; return; } + bool ParseFloat(const char* s, float& out) { + char* end = nullptr; + float v = std::strtof(s, &end); + if (end == s || *end != '\0') + return false; + out = v; + return true; + } - // Auto: int, else float, else string. - int32_t i; float f; - if( ParseInt( tok, i ) ) p << i; - else if( ParseFloat( tok, f ) ) p << f; - else p << tok; -} + void AppendArg(osctap::OutboundPacketStream& p, const char* tok) { + if (std::strcmp(tok, "T") == 0) { + p << true; + return; + } + if (std::strcmp(tok, "F") == 0) { + p << false; + return; + } + + if (std::strncmp(tok, "i:", 2) == 0) { + p << (int32_t)std::atoi(tok + 2); + return; + } + if (std::strncmp(tok, "f:", 2) == 0) { + p << (float)std::atof(tok + 2); + return; + } + if (std::strncmp(tok, "s:", 2) == 0) { + const char* s = tok + 2; + p << s; + return; + } + + // Auto: int, else float, else string. + int32_t i; + float f; + if (ParseInt(tok, i)) + p << i; + else if (ParseFloat(tok, f)) + p << f; + else + p << tok; + } } // namespace -int main( int argc, char *argv[] ) -{ - if( argc < 4 ){ +int main(int argc, char* argv[]) { + if (argc < 4) { std::cerr << "usage: osc_send
[args...]\n"; return 2; } - const char *host = argv[1]; - int port = std::atoi( argv[2] ); - const char *address = argv[3]; + const char* host = argv[1]; + int port = std::atoi(argv[2]); + const char* address = argv[3]; - char buffer[1024]; - osctap::OutboundPacketStream p( buffer, sizeof(buffer) ); + char buffer[1024]; + osctap::OutboundPacketStream p(buffer, sizeof(buffer)); try { - p << osctap::BeginMessage( address ); - for( int i = 4; i < argc; ++i ) - AppendArg( p, argv[i] ); + p << osctap::BeginMessage(address); + for (int i = 4; i < argc; ++i) + AppendArg(p, argv[i]); p << osctap::EndMessage(); - } catch( const osctap::Exception& e ) { + } + catch (const osctap::Exception& e) { std::cerr << "failed to build message: " << e.what() << '\n'; return 1; } try { - osctap::UdpTransmitSocket( osctap::IpEndpointName( host, port ) ) - .Send( p.Data(), p.Size() ); - } catch( const std::exception& e ) { + osctap::UdpTransmitSocket(osctap::IpEndpointName(host, port)).Send(p.Data(), p.Size()); + } + catch (const std::exception& e) { std::cerr << "send failed: " << e.what() << '\n'; return 1; } - std::cout << "sent " << p.Size() << " bytes to " << host << ':' << port - << " " << address << '\n'; + std::cout << "sent " << p.Size() << " bytes to " << host << ':' << port << " " << address << '\n'; return 0; } diff --git a/demos/pi5_hub.cpp b/demos/pi5_hub.cpp index 3616d84..d114de1 100644 --- a/demos/pi5_hub.cpp +++ b/demos/pi5_hub.cpp @@ -23,11 +23,6 @@ android 192.168.1.20:9001 */ -#include "ip/UdpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscPacketListener.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include @@ -35,126 +30,136 @@ #include #include -namespace { +#include "ip/IpEndpointName.h" +#include "ip/UdpSocket.h" +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscPacketListener.h" -// Parse "host:port" (port optional -> fallback). IPv4 dotted or hostname. -osctap::IpEndpointName ParseEndpoint( const char *s, int fallbackPort ) -{ - const char *colon = std::strrchr( s, ':' ); - if( !colon ) - return osctap::IpEndpointName( s, fallbackPort ); - std::string host( s, colon - s ); - int port = std::atoi( colon + 1 ); - return osctap::IpEndpointName( host.c_str(), port ? port : fallbackPort ); -} +namespace { -void PrintMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from ) -{ - char who[ osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; - from.AddressAndPortAsString( who ); - std::cout << "[recv " << who << "] " << m.AddressPattern() - << " (" << m.ArgumentCount() << " args)"; - for( auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a ){ - std::cout << ' '; - if( a->IsInt32() ) std::cout << a->AsInt32Unchecked(); - else if( a->IsFloat() ) std::cout << a->AsFloatUnchecked(); - else if( a->IsString() ) std::cout << '"' << a->AsStringUnchecked() << '"'; - else if( a->IsBool() ) std::cout << (a->AsBoolUnchecked() ? "true" : "false"); - else std::cout << '?'; + // Parse "host:port" (port optional -> fallback). IPv4 dotted or hostname. + osctap::IpEndpointName ParseEndpoint(const char* s, int fallbackPort) { + const char* colon = std::strrchr(s, ':'); + if (!colon) + return osctap::IpEndpointName(s, fallbackPort); + std::string host(s, colon - s); + int port = std::atoi(colon + 1); + return osctap::IpEndpointName(host.c_str(), port ? port : fallbackPort); } - std::cout << '\n'; -} -class HubListener : public osctap::OscPacketListener { -public: - HubListener( const osctap::IpEndpointName& pico, const osctap::IpEndpointName& android ) - : pico_( pico ), android_( android ) {} - - // Guard the dispatch: parsing untrusted UDP can throw on a malformed packet. - // Catch it so one bad datagram drops instead of taking the hub down. - void ProcessPacket( const char *data, int size, const osctap::IpEndpointName& from ) override - { - try { - osctap::OscPacketListener::ProcessPacket( data, size, from ); - } catch( const osctap::Exception& e ) { - std::cerr << "[drop] malformed packet (" << e.what() << ")\n"; + void PrintMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from) { + char who[osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; + from.AddressAndPortAsString(who); + std::cout << "[recv " << who << "] " << m.AddressPattern() << " (" << m.ArgumentCount() << " args)"; + for (auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a) { + std::cout << ' '; + if (a->IsInt32()) + std::cout << a->AsInt32Unchecked(); + else if (a->IsFloat()) + std::cout << a->AsFloatUnchecked(); + else if (a->IsString()) + std::cout << '"' << a->AsStringUnchecked() << '"'; + else if (a->IsBool()) + std::cout << (a->AsBoolUnchecked() ? "true" : "false"); + else + std::cout << '?'; } + std::cout << '\n'; } -protected: - void ProcessMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from ) override - { - PrintMessage( m, from ); - - const char *addr = m.AddressPattern(); - char out[256]; - - // Controller -> device: re-address /hub/ to / and forward to Pico. - if( std::strncmp( addr, "/hub/", 5 ) == 0 ){ - osctap::OutboundPacketStream p( out, sizeof(out) ); - p << osctap::BeginMessage( addr + 4 ); // "/hub/led" -> "/led" - for( auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a ) - CopyArg( p, a ); - p << osctap::EndMessage(); - Forward( pico_, p, "Pico" ); + class HubListener : public osctap::OscPacketListener { + public: + HubListener(const osctap::IpEndpointName& pico, const osctap::IpEndpointName& android) + : pico_(pico) + , android_(android) {} + + // Guard the dispatch: parsing untrusted UDP can throw on a malformed packet. + // Catch it so one bad datagram drops instead of taking the hub down. + void ProcessPacket(const char* data, int size, const osctap::IpEndpointName& from) override { + try { + osctap::OscPacketListener::ProcessPacket(data, size, from); + } + catch (const osctap::Exception& e) { + std::cerr << "[drop] malformed packet (" << e.what() << ")\n"; + } } - // Device -> controller: re-address /sensor/ to /ui/ (telemetry). - else if( std::strncmp( addr, "/sensor/", 8 ) == 0 ){ - std::string ui = std::string("/ui/") + (addr + 8); - osctap::OutboundPacketStream p( out, sizeof(out) ); - p << osctap::BeginMessage( ui.c_str() ); - for( auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a ) - CopyArg( p, a ); - p << osctap::EndMessage(); - Forward( android_, p, "Android" ); + + protected: + void ProcessMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from) override { + PrintMessage(m, from); + + const char* addr = m.AddressPattern(); + char out[256]; + + // Controller -> device: re-address /hub/ to / and forward to Pico. + if (std::strncmp(addr, "/hub/", 5) == 0) { + osctap::OutboundPacketStream p(out, sizeof(out)); + p << osctap::BeginMessage(addr + 4); // "/hub/led" -> "/led" + for (auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a) + CopyArg(p, a); + p << osctap::EndMessage(); + Forward(pico_, p, "Pico"); + } + // Device -> controller: re-address /sensor/ to /ui/ (telemetry). + else if (std::strncmp(addr, "/sensor/", 8) == 0) { + std::string ui = std::string("/ui/") + (addr + 8); + osctap::OutboundPacketStream p(out, sizeof(out)); + p << osctap::BeginMessage(ui.c_str()); + for (auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a) + CopyArg(p, a); + p << osctap::EndMessage(); + Forward(android_, p, "Android"); + } } - } -private: - static void CopyArg( osctap::OutboundPacketStream& p, - osctap::ReceivedMessage::const_iterator a ) - { - if( a->IsInt32() ) p << a->AsInt32Unchecked(); - else if( a->IsFloat() ) p << a->AsFloatUnchecked(); - else if( a->IsString() ) p << a->AsStringUnchecked(); - else if( a->IsBool() ) p << a->AsBoolUnchecked(); - } + private: + static void CopyArg(osctap::OutboundPacketStream& p, osctap::ReceivedMessage::const_iterator a) { + if (a->IsInt32()) + p << a->AsInt32Unchecked(); + else if (a->IsFloat()) + p << a->AsFloatUnchecked(); + else if (a->IsString()) + p << a->AsStringUnchecked(); + else if (a->IsBool()) + p << a->AsBoolUnchecked(); + } - void Forward( const osctap::IpEndpointName& to, const osctap::OutboundPacketStream& p, - const char *label ) - { - try { - osctap::UdpTransmitSocket( to ).Send( p.Data(), p.Size() ); - char dst[ osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; - to.AddressAndPortAsString( dst ); - std::cout << " -> " << label << " (" << dst << ")\n"; - } catch( const std::exception& e ) { - std::cerr << " -> " << label << " send failed: " << e.what() << '\n'; + void Forward(const osctap::IpEndpointName& to, const osctap::OutboundPacketStream& p, const char* label) { + try { + osctap::UdpTransmitSocket(to).Send(p.Data(), p.Size()); + char dst[osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; + to.AddressAndPortAsString(dst); + std::cout << " -> " << label << " (" << dst << ")\n"; + } + catch (const std::exception& e) { + std::cerr << " -> " << label << " send failed: " << e.what() << '\n'; + } } - } - osctap::IpEndpointName pico_; - osctap::IpEndpointName android_; -}; + osctap::IpEndpointName pico_; + osctap::IpEndpointName android_; + }; -osctap::UdpListeningReceiveSocket *gSocket = nullptr; -void HandleSigInt( int ) { if( gSocket ) gSocket->AsynchronousBreak(); } + osctap::UdpListeningReceiveSocket* gSocket = nullptr; + void HandleSigInt(int) { + if (gSocket) + gSocket->AsynchronousBreak(); + } } // namespace -int main( int argc, char *argv[] ) -{ - int listenPort = (argc > 1) ? std::atoi( argv[1] ) : 9000; - osctap::IpEndpointName pico = (argc > 2) ? ParseEndpoint( argv[2], 9000 ) - : osctap::IpEndpointName( "192.168.1.50", 9000 ); - osctap::IpEndpointName android = (argc > 3) ? ParseEndpoint( argv[3], 9001 ) - : osctap::IpEndpointName( "192.168.1.20", 9001 ); - - HubListener listener( pico, android ); - osctap::UdpListeningReceiveSocket socket( - osctap::IpEndpointName( osctap::IpEndpointName::ANY_ADDRESS, listenPort ), &listener ); +int main(int argc, char* argv[]) { + int listenPort = (argc > 1) ? std::atoi(argv[1]) : 9000; + osctap::IpEndpointName pico = + (argc > 2) ? ParseEndpoint(argv[2], 9000) : osctap::IpEndpointName("192.168.1.50", 9000); + osctap::IpEndpointName android = + (argc > 3) ? ParseEndpoint(argv[3], 9001) : osctap::IpEndpointName("192.168.1.20", 9001); + + HubListener listener(pico, android); + osctap::UdpListeningReceiveSocket socket(osctap::IpEndpointName(osctap::IpEndpointName::ANY_ADDRESS, listenPort), + &listener); gSocket = &socket; - std::signal( SIGINT, HandleSigInt ); + std::signal(SIGINT, HandleSigInt); std::cout << "OscTap Pi 5 hub listening on UDP " << listenPort << " (Ctrl-C to stop)\n"; socket.Run(); diff --git a/demos/tcp_send.cpp b/demos/tcp_send.cpp index b60f614..be6e25b 100644 --- a/demos/tcp_send.cpp +++ b/demos/tcp_send.cpp @@ -20,83 +20,102 @@ tcp_send 127.0.0.1 9000 /chat s:hello T */ -#include "ip/TcpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include #include -namespace { - -bool ParseInt( const char *s, int32_t& out ) -{ - char *end = nullptr; - long v = std::strtol( s, &end, 10 ); - if( end == s || *end != '\0' ) return false; - out = static_cast( v ); - return true; -} +#include "ip/IpEndpointName.h" +#include "ip/TcpSocket.h" +#include "osc/OscOutboundPacketStream.h" -bool ParseFloat( const char *s, float& out ) -{ - char *end = nullptr; - float v = std::strtof( s, &end ); - if( end == s || *end != '\0' ) return false; - out = v; - return true; -} +namespace { -void AppendArg( osctap::OutboundPacketStream& p, const char *tok ) -{ - if( std::strcmp( tok, "T" ) == 0 ) { p << true; return; } - if( std::strcmp( tok, "F" ) == 0 ) { p << false; return; } + bool ParseInt(const char* s, int32_t& out) { + char* end = nullptr; + long v = std::strtol(s, &end, 10); + if (end == s || *end != '\0') + return false; + out = static_cast(v); + return true; + } - if( std::strncmp( tok, "i:", 2 ) == 0 ) { p << (int32_t)std::atoi( tok + 2 ); return; } - if( std::strncmp( tok, "f:", 2 ) == 0 ) { p << (float)std::atof( tok + 2 ); return; } - if( std::strncmp( tok, "s:", 2 ) == 0 ) { const char *s = tok + 2; p << s; return; } + bool ParseFloat(const char* s, float& out) { + char* end = nullptr; + float v = std::strtof(s, &end); + if (end == s || *end != '\0') + return false; + out = v; + return true; + } - int32_t i; float f; - if( ParseInt( tok, i ) ) p << i; - else if( ParseFloat( tok, f ) ) p << f; - else p << tok; -} + void AppendArg(osctap::OutboundPacketStream& p, const char* tok) { + if (std::strcmp(tok, "T") == 0) { + p << true; + return; + } + if (std::strcmp(tok, "F") == 0) { + p << false; + return; + } + + if (std::strncmp(tok, "i:", 2) == 0) { + p << (int32_t)std::atoi(tok + 2); + return; + } + if (std::strncmp(tok, "f:", 2) == 0) { + p << (float)std::atof(tok + 2); + return; + } + if (std::strncmp(tok, "s:", 2) == 0) { + const char* s = tok + 2; + p << s; + return; + } + + int32_t i; + float f; + if (ParseInt(tok, i)) + p << i; + else if (ParseFloat(tok, f)) + p << f; + else + p << tok; + } } // namespace -int main( int argc, char *argv[] ) -{ - if( argc < 4 ){ +int main(int argc, char* argv[]) { + if (argc < 4) { std::cerr << "usage: tcp_send
[args...]\n"; return 2; } - const char *host = argv[1]; - const int port = std::atoi( argv[2] ); - const char *address = argv[3]; + const char* host = argv[1]; + const int port = std::atoi(argv[2]); + const char* address = argv[3]; - char buffer[1024]; - osctap::OutboundPacketStream p( buffer, sizeof(buffer) ); + char buffer[1024]; + osctap::OutboundPacketStream p(buffer, sizeof(buffer)); try { - p << osctap::BeginMessage( address ); - for( int i = 4; i < argc; ++i ) - AppendArg( p, argv[i] ); + p << osctap::BeginMessage(address); + for (int i = 4; i < argc; ++i) + AppendArg(p, argv[i]); p << osctap::EndMessage(); - } catch( const osctap::Exception& e ) { + } + catch (const osctap::Exception& e) { std::cerr << "failed to build message: " << e.what() << '\n'; return 1; } try { - osctap::TcpTransmitSocket client( osctap::IpEndpointName( host, port ) ); - client.Send( p.Data(), p.Size() ); - } catch( const std::exception& e ) { + osctap::TcpTransmitSocket client(osctap::IpEndpointName(host, port)); + client.Send(p.Data(), p.Size()); + } + catch (const std::exception& e) { std::cerr << "send failed: " << e.what() << '\n'; return 1; } - std::cout << "sent " << p.Size() << " bytes to " << host << ':' << port - << " " << address << " (TCP)\n"; + std::cout << "sent " << p.Size() << " bytes to " << host << ':' << port << " " << address << " (TCP)\n"; return 0; } diff --git a/demos/tcp_server.cpp b/demos/tcp_server.cpp index 5187a05..9fb31c2 100644 --- a/demos/tcp_server.cpp +++ b/demos/tcp_server.cpp @@ -12,62 +12,67 @@ tcp_server [port] (default port 9000) */ -#include "ip/TcpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscPacketListener.h" - #include #include #include #include +#include "ip/IpEndpointName.h" +#include "ip/TcpSocket.h" +#include "osc/OscPacketListener.h" + namespace { -class PrintingListener : public osctap::OscPacketListener { - // Untrusted input: parsing can throw on a malformed frame. Catch it so one bad - // client can't take the server down. - void ProcessPacket( const char *data, int size, const osctap::IpEndpointName& from ) override - { - try { - osctap::OscPacketListener::ProcessPacket( data, size, from ); - } catch( const osctap::Exception& e ) { - std::cerr << "[drop] malformed packet (" << e.what() << ")\n"; + class PrintingListener : public osctap::OscPacketListener { + // Untrusted input: parsing can throw on a malformed frame. Catch it so one bad + // client can't take the server down. + void ProcessPacket(const char* data, int size, const osctap::IpEndpointName& from) override { + try { + osctap::OscPacketListener::ProcessPacket(data, size, from); + } + catch (const osctap::Exception& e) { + std::cerr << "[drop] malformed packet (" << e.what() << ")\n"; + } } - } -protected: - void ProcessMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from ) override - { - char who[ osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; - from.AddressAndPortAsString( who ); - std::cout << "[recv " << who << "] " << m.AddressPattern() - << " (" << m.ArgumentCount() << " args)"; - for( auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a ){ - std::cout << ' '; - if( a->IsInt32() ) std::cout << a->AsInt32Unchecked(); - else if( a->IsFloat() ) std::cout << a->AsFloatUnchecked(); - else if( a->IsString() ) std::cout << '"' << a->AsStringUnchecked() << '"'; - else if( a->IsBool() ) std::cout << (a->AsBoolUnchecked() ? "true" : "false"); - else std::cout << '?'; + protected: + void ProcessMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName& from) override { + char who[osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; + from.AddressAndPortAsString(who); + std::cout << "[recv " << who << "] " << m.AddressPattern() << " (" << m.ArgumentCount() << " args)"; + for (auto a = m.ArgumentsBegin(); a != m.ArgumentsEnd(); ++a) { + std::cout << ' '; + if (a->IsInt32()) + std::cout << a->AsInt32Unchecked(); + else if (a->IsFloat()) + std::cout << a->AsFloatUnchecked(); + else if (a->IsString()) + std::cout << '"' << a->AsStringUnchecked() << '"'; + else if (a->IsBool()) + std::cout << (a->AsBoolUnchecked() ? "true" : "false"); + else + std::cout << '?'; + } + std::cout << '\n'; } - std::cout << '\n'; - } -}; + }; -osctap::TcpListeningReceiveSocket *gSocket = nullptr; -void HandleSigInt( int ) { if( gSocket ) gSocket->AsynchronousBreak(); } + osctap::TcpListeningReceiveSocket* gSocket = nullptr; + void HandleSigInt(int) { + if (gSocket) + gSocket->AsynchronousBreak(); + } } // namespace -int main( int argc, char *argv[] ) -{ - const int port = (argc > 1) ? std::atoi( argv[1] ) : 9000; +int main(int argc, char* argv[]) { + const int port = (argc > 1) ? std::atoi(argv[1]) : 9000; - PrintingListener listener; - osctap::TcpListeningReceiveSocket socket( - osctap::IpEndpointName( osctap::IpEndpointName::ANY_ADDRESS, port ), &listener ); + PrintingListener listener; + osctap::TcpListeningReceiveSocket socket(osctap::IpEndpointName(osctap::IpEndpointName::ANY_ADDRESS, port), + &listener); gSocket = &socket; - std::signal( SIGINT, HandleSigInt ); + std::signal(SIGINT, HandleSigInt); std::cout << "OscTap TCP server listening on TCP " << port << " (Ctrl-C to stop)\n"; socket.Run(); diff --git a/examples/OscDump.cpp b/examples/OscDump.cpp index 9205427..7f14035 100644 --- a/examples/OscDump.cpp +++ b/examples/OscDump.cpp @@ -41,60 +41,51 @@ message argument. */ - -#include -#include #include +#include +#include #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug namespace std { -using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'. + using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'. } #endif -#include -#include - -#include -#include +#include "ip/PacketListener.h" +#include "ip/UdpSocket.h" +#include "osc/OscPrintReceivedElements.h" +#include "osc/OscReceivedElements.h" using namespace oscpack; // deprecated alias for osctap, intentionally exercised here -class OscDumpPacketListener : public PacketListener{ -public: - virtual void ProcessPacket( const char *data, int size, - const IpEndpointName& remoteEndpoint ) - { - (void) remoteEndpoint; // suppress unused parameter warning +class OscDumpPacketListener : public PacketListener { + public: + virtual void ProcessPacket(const char* data, int size, const IpEndpointName& remoteEndpoint) { + (void)remoteEndpoint; // suppress unused parameter warning - std::cout << oscpack::ReceivedPacket( data, size ); - } + std::cout << oscpack::ReceivedPacket(data, size); + } }; -int main(int argc, char* argv[]) -{ - if( argc >= 2 && std::strcmp( argv[1], "-h" ) == 0 ){ +int main(int argc, char* argv[]) { + if (argc >= 2 && std::strcmp(argv[1], "-h") == 0) { std::cout << "usage: OscDump [port]\n"; return 0; } - int port = 9998; + int port = 9998; - if( argc >= 2 ) - port = std::atoi( argv[1] ); + if (argc >= 2) + port = std::atoi(argv[1]); - OscDumpPacketListener listener; - UdpListeningReceiveSocket s( - IpEndpointName( IpEndpointName::ANY_ADDRESS, port ), - &listener ); + 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"; + std::cout << "listening for input on port " << port << "...\n"; + std::cout << "press ctrl-c to end\n"; - s.Run(); + s.Run(); - std::cout << "finishing.\n"; + std::cout << "finishing.\n"; return 0; } - - diff --git a/examples/SimpleReceive.cpp b/examples/SimpleReceive.cpp index d6df5d3..ff07af2 100644 --- a/examples/SimpleReceive.cpp +++ b/examples/SimpleReceive.cpp @@ -11,68 +11,73 @@ #include #include -#include "osc/OscReceivedElements.h" -#include "osc/OscPacketListener.h" #include "ip/UdpSocket.h" +#include "osc/OscPacketListener.h" +#include "osc/OscReceivedElements.h" using namespace oscpack; // OscTap's deprecated oscpack:: alias, exercised here #define PORT 7000 class ExamplePacketListener : public OscPacketListener { -protected: - void ProcessMessage( const ReceivedMessage& m, - const IpEndpointName& remoteEndpoint ) override - { - (void) remoteEndpoint; + protected: + void ProcessMessage(const ReceivedMessage& m, const IpEndpointName& remoteEndpoint) override { + (void)remoteEndpoint; - try{ + try { // OscPacketListener handles bundle traversal; we just read messages. - if( std::strcmp( m.AddressPattern(), "/test1" ) == 0 ){ + if (std::strcmp(m.AddressPattern(), "/test1") == 0) { // example #1 -- argument-stream interface ReceivedMessageArgumentStream args = m.ArgumentStream(); - bool a1; int32_t a2; float a3; const char *a4; - MessageTerminator end; + bool a1; + int32_t a2; + float a3; + const char* a4; + MessageTerminator end; args >> a1 >> a2 >> a3 >> a4 >> end; - std::cout << "received '/test1' message with arguments: " - << a1 << " " << a2 << " " << a3 << " " << a4 << "\n"; - - }else if( std::strcmp( m.AddressPattern(), "/test2" ) == 0 ){ + std::cout << "received '/test1' message with arguments: " << a1 << " " << a2 << " " << a3 << " " << a4 + << "\n"; + } + else if (std::strcmp(m.AddressPattern(), "/test2") == 0) { // example #2 -- argument-iterator interface (supports reflection, // e.g. arg->IsBool() to check the type of an overloaded argument) 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() ) + 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"; + std::cout << "received '/test2' message with arguments: " << a1 << " " << a2 << " " << a3 << " " << a4 + << "\n"; } - }catch( Exception& e ){ + } + catch (Exception& e) { // parsing errors (wrong/missing argument types) are thrown - std::cout << "error while parsing message: " - << m.AddressPattern() << ": " << e.what() << "\n"; + std::cout << "error while parsing message: " << m.AddressPattern() << ": " << e.what() << "\n"; } } }; -namespace { UdpListeningReceiveSocket* gSocket = nullptr; - void HandleSigInt( int ){ if( gSocket ) gSocket->AsynchronousBreak(); } } +namespace { + UdpListeningReceiveSocket* gSocket = nullptr; + void HandleSigInt(int) { + if (gSocket) + gSocket->AsynchronousBreak(); + } +} // namespace -int main(int argc, char* argv[]) -{ - (void) argc; (void) argv; +int main(int argc, char* argv[]) { + (void)argc; + (void)argv; - ExamplePacketListener listener; - UdpListeningReceiveSocket s( - IpEndpointName( IpEndpointName::ANY_ADDRESS, PORT ), &listener ); + ExamplePacketListener listener; + UdpListeningReceiveSocket s(IpEndpointName(IpEndpointName::ANY_ADDRESS, PORT), &listener); gSocket = &s; - std::signal( SIGINT, HandleSigInt ); + std::signal(SIGINT, HandleSigInt); std::cout << "press ctrl-c to end\n"; s.Run(); diff --git a/examples/SimpleSend.cpp b/examples/SimpleSend.cpp index 2215c35..e6dbb8c 100644 --- a/examples/SimpleSend.cpp +++ b/examples/SimpleSend.cpp @@ -6,8 +6,8 @@ For a typed command-line sender see demos/osc_send.cpp. */ -#include "osc/OscOutboundPacketStream.h" #include "ip/UdpSocket.h" +#include "osc/OscOutboundPacketStream.h" using namespace oscpack; // OscTap's deprecated oscpack:: alias, exercised here @@ -16,24 +16,20 @@ using namespace oscpack; // OscTap's deprecated oscpack:: alias, exercised here #define OUTPUT_BUFFER_SIZE 1024 -int main(int argc, char* argv[]) -{ - (void) argc; // suppress unused parameter warnings - (void) argv; +int main(int argc, char* argv[]) { + (void)argc; // suppress unused parameter warnings + (void)argv; - UdpTransmitSocket transmitSocket( IpEndpointName( ADDRESS, PORT ) ); + UdpTransmitSocket transmitSocket(IpEndpointName(ADDRESS, PORT)); - char buffer[OUTPUT_BUFFER_SIZE]; - OutboundPacketStream p( buffer, OUTPUT_BUFFER_SIZE ); + char buffer[OUTPUT_BUFFER_SIZE]; + OutboundPacketStream p(buffer, OUTPUT_BUFFER_SIZE); - p << BeginBundleImmediate() - << BeginMessage( "/test1" ) - << true << (int32_t)23 << (float)3.1415f << "hello" << EndMessage() - << BeginMessage( "/test2" ) - << true << (int32_t)24 << (float)10.8f << "world" << EndMessage() + p << BeginBundleImmediate() << BeginMessage("/test1") << true << (int32_t)23 << (float)3.1415f << "hello" + << EndMessage() << BeginMessage("/test2") << true << (int32_t)24 << (float)10.8f << "world" << EndMessage() << EndBundle(); - transmitSocket.Send( p.Data(), p.Size() ); + transmitSocket.Send(p.Data(), p.Size()); return 0; } diff --git a/fuzz/fuzz_deframe.cpp b/fuzz/fuzz_deframe.cpp index e7515d0..0a8f73c 100644 --- a/fuzz/fuzz_deframe.cpp +++ b/fuzz/fuzz_deframe.cpp @@ -22,52 +22,55 @@ #include #include -#include "osc/OscStreamFraming.h" #include "osc/OscReceivedElements.h" +#include "osc/OscStreamFraming.h" using namespace oscpack; // Run one reassembled frame through the parser, exactly as a consumer would. -static void HandleFrame( const char* data, uint32_t size ) -{ - if( size == 0 ) +static void HandleFrame(const char* data, uint32_t size) { + if (size == 0) return; // Exactly-sized heap copy: ASan redzones flag any read past the frame length. - std::vector buffer( data, data + size ); - try{ - ReceivedPacket p( buffer.data(), (osc_bundle_element_size_t)size ); - if( p.IsBundle() ){ - ReceivedBundle b( p ); - for( auto it = b.ElementsBegin(); it != b.ElementsEnd(); ++it ) - if( !it->IsBundle() ){ ReceivedMessage m( *it ); (void)m.ArgumentCount(); } - }else{ - ReceivedMessage m( p ); - for( auto it = m.ArgumentsBegin(); it != m.ArgumentsEnd(); ++it ) + std::vector buffer(data, data + size); + try { + ReceivedPacket p(buffer.data(), (osc_bundle_element_size_t)size); + if (p.IsBundle()) { + ReceivedBundle b(p); + for (auto it = b.ElementsBegin(); it != b.ElementsEnd(); ++it) + if (!it->IsBundle()) { + ReceivedMessage m(*it); + (void)m.ArgumentCount(); + } + } + else { + ReceivedMessage m(p); + for (auto it = m.ArgumentsBegin(); it != m.ArgumentsEnd(); ++it) (void)it->TypeTag(); } - }catch( const oscpack::Exception& ){ + } + catch (const oscpack::Exception&) { // Expected: malformed frame rejected by the parser. - }catch( const std::exception& ){ + } + catch (const std::exception&) { // Tolerate std exceptions (e.g. bad_alloc) -- not a memory-safety finding. } } -extern "C" int LLVMFuzzerTestOneInput( const uint8_t* data, size_t size ) -{ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // A small cap so the fuzzer reaches the oversized-frame rejection path quickly. - osctap::OscStreamDeframer deframer( 4096 ); + osctap::OscStreamDeframer deframer(4096); // Feed the byte stream in pseudo-random 1..8-byte chunks (derived from the // data itself) so reassembly across arbitrary read boundaries is exercised. size_t i = 0; - while( i < size ){ - size_t chunk = ( (size_t)data[i] & 0x7 ) + 1; // 1..8 - if( i + chunk > size ) + while (i < size) { + size_t chunk = ((size_t)data[i] & 0x7) + 1; // 1..8 + if (i + chunk > size) chunk = size - i; - const bool ok = deframer.Consume( - reinterpret_cast( data ) + i, chunk, - []( const char* p, uint32_t n ){ HandleFrame( p, n ); } ); - if( !ok ) + const bool ok = deframer.Consume(reinterpret_cast(data) + i, chunk, + [](const char* p, uint32_t n) { HandleFrame(p, n); }); + if (!ok) break; // oversized frame: the real loop drops the connection here i += chunk; } diff --git a/fuzz/fuzz_parse.cpp b/fuzz/fuzz_parse.cpp index c0143a4..97e93ee 100644 --- a/fuzz/fuzz_parse.cpp +++ b/fuzz/fuzz_parse.cpp @@ -21,88 +21,106 @@ #include #include -#include "osc/OscReceivedElements.h" #include "osc/OscPrintReceivedElements.h" +#include "osc/OscReceivedElements.h" using namespace oscpack; -static void WalkMessage( const ReceivedMessage& m ) -{ +static void WalkMessage(const ReceivedMessage& m) { (void)m.AddressPattern(); - if( m.AddressPatternIsUInt32() ) + if (m.AddressPatternIsUInt32()) (void)m.AddressPatternAsUInt32(); - for( ReceivedMessage::const_iterator it = m.ArgumentsBegin(); - it != m.ArgumentsEnd(); ++it ){ + for (ReceivedMessage::const_iterator it = m.ArgumentsBegin(); it != m.ArgumentsEnd(); ++it) { const ReceivedMessageArgument& a = *it; - switch( a.TypeTag() ){ - case INT32_TYPE_TAG: (void)a.AsInt32Unchecked(); break; - case FLOAT_TYPE_TAG: (void)a.AsFloatUnchecked(); break; - case CHAR_TYPE_TAG: (void)a.AsCharUnchecked(); break; - case RGBA_COLOR_TYPE_TAG: (void)a.AsRgbaColorUnchecked(); break; - case MIDI_MESSAGE_TYPE_TAG: (void)a.AsMidiMessageUnchecked(); break; - case INT64_TYPE_TAG: (void)a.AsInt64Unchecked(); break; - case TIME_TAG_TYPE_TAG: (void)a.AsTimeTagUnchecked(); break; - case DOUBLE_TYPE_TAG: (void)a.AsDoubleUnchecked(); break; - case STRING_TYPE_TAG: (void)a.AsStringUnchecked(); break; - case SYMBOL_TYPE_TAG: (void)a.AsSymbolUnchecked(); break; - case BLOB_TYPE_TAG: { - const void* data; - osc_bundle_element_size_t size; - a.AsBlobUnchecked( data, size ); - // Touch every blob byte so an out-of-bounds size is caught by ASan. - const volatile char* p = static_cast( data ); - volatile char sink = 0; - for( osc_bundle_element_size_t i = 0; i < size; ++i ) - sink = p[i]; - (void)sink; - break; - } - default: break; // T/F/N/I and array markers carry no argument data + switch (a.TypeTag()) { + case INT32_TYPE_TAG: + (void)a.AsInt32Unchecked(); + break; + case FLOAT_TYPE_TAG: + (void)a.AsFloatUnchecked(); + break; + case CHAR_TYPE_TAG: + (void)a.AsCharUnchecked(); + break; + case RGBA_COLOR_TYPE_TAG: + (void)a.AsRgbaColorUnchecked(); + break; + case MIDI_MESSAGE_TYPE_TAG: + (void)a.AsMidiMessageUnchecked(); + break; + case INT64_TYPE_TAG: + (void)a.AsInt64Unchecked(); + break; + case TIME_TAG_TYPE_TAG: + (void)a.AsTimeTagUnchecked(); + break; + case DOUBLE_TYPE_TAG: + (void)a.AsDoubleUnchecked(); + break; + case STRING_TYPE_TAG: + (void)a.AsStringUnchecked(); + break; + case SYMBOL_TYPE_TAG: + (void)a.AsSymbolUnchecked(); + break; + case BLOB_TYPE_TAG: { + const void* data; + osc_bundle_element_size_t size; + a.AsBlobUnchecked(data, size); + // Touch every blob byte so an out-of-bounds size is caught by ASan. + const volatile char* p = static_cast(data); + volatile char sink = 0; + for (osc_bundle_element_size_t i = 0; i < size; ++i) + sink = p[i]; + (void)sink; + break; + } + default: + break; // T/F/N/I and array markers carry no argument data } } } -static void WalkBundle( const ReceivedBundle& b ) -{ +static void WalkBundle(const ReceivedBundle& b) { (void)b.TimeTag(); - for( ReceivedBundle::const_iterator it = b.ElementsBegin(); - it != b.ElementsEnd(); ++it ){ - if( it->IsBundle() ) - WalkBundle( ReceivedBundle( *it ) ); + for (ReceivedBundle::const_iterator it = b.ElementsBegin(); it != b.ElementsEnd(); ++it) { + if (it->IsBundle()) + WalkBundle(ReceivedBundle(*it)); else - WalkMessage( ReceivedMessage( *it ) ); + WalkMessage(ReceivedMessage(*it)); } } -extern "C" int LLVMFuzzerTestOneInput( const uint8_t* data, size_t size ) -{ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { // OSC packets are a whole number of 4-byte words. Trim the tail so more // inputs reach the parser; the multiple-of-four rejection itself is already // covered by the unit tests. - size -= ( size & 3 ); - if( size == 0 ) + size -= (size & 3); + if (size == 0) return 0; // Copy into an exactly-sized heap buffer: operator new yields max-aligned // storage (satisfying the big-endian word reads) and, crucially, ASan // places redzones immediately after `size`, so any read past the declared // packet length is flagged. - std::vector buffer( data, data + size ); + std::vector buffer(data, data + size); - try{ - ReceivedPacket p( buffer.data(), (osc_bundle_element_size_t)size ); - if( p.IsBundle() ) - WalkBundle( ReceivedBundle( p ) ); + try { + ReceivedPacket p(buffer.data(), (osc_bundle_element_size_t)size); + if (p.IsBundle()) + WalkBundle(ReceivedBundle(p)); else - WalkMessage( ReceivedMessage( p ) ); + WalkMessage(ReceivedMessage(p)); // Independently exercise the streaming printer, a separate consumer path. std::ostringstream oss; oss << p; - }catch( const oscpack::Exception& ){ + } + catch (const oscpack::Exception&) { // Expected: malformed input is rejected by design. - }catch( const std::exception& ){ + } + catch (const std::exception&) { // Tolerate std exceptions (e.g. bad_alloc) -- not a memory-safety finding. } diff --git a/fuzz/standalone_main.cpp b/fuzz/standalone_main.cpp index c9cd0e1..edbe410 100644 --- a/fuzz/standalone_main.cpp +++ b/fuzz/standalone_main.cpp @@ -13,67 +13,65 @@ fuzz/fuzz_parse.cpp fuzz/standalone_main.cpp -o fuzz_parse_standalone ./fuzz_parse_standalone fuzz/corpus/* */ -#include #include +#include #include #include #include -extern "C" int LLVMFuzzerTestOneInput( const uint8_t* data, size_t size ); +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); -static std::vector ReadFile( const char* path ) -{ +static std::vector ReadFile(const char* path) { std::vector bytes; - FILE* f = std::fopen( path, "rb" ); - if( !f ){ - std::fprintf( stderr, "warning: could not open %s\n", path ); + FILE* f = std::fopen(path, "rb"); + if (!f) { + std::fprintf(stderr, "warning: could not open %s\n", path); return bytes; } uint8_t chunk[4096]; - size_t n; - while( (n = std::fread( chunk, 1, sizeof(chunk), f )) > 0 ) - bytes.insert( bytes.end(), chunk, chunk + n ); - std::fclose( f ); + size_t n; + while ((n = std::fread(chunk, 1, sizeof(chunk), f)) > 0) + bytes.insert(bytes.end(), chunk, chunk + n); + std::fclose(f); return bytes; } -int main( int argc, char** argv ) -{ +int main(int argc, char** argv) { std::vector> seeds; - for( int i = 1; i < argc; ++i ) - seeds.push_back( ReadFile( argv[i] ) ); + for (int i = 1; i < argc; ++i) + seeds.push_back(ReadFile(argv[i])); // 1) Replay every input verbatim (corpus entries and crash repros). - for( const auto& s : seeds ) - LLVMFuzzerTestOneInput( s.data(), s.size() ); + for (const auto& s : seeds) + LLVMFuzzerTestOneInput(s.data(), s.size()); - if( seeds.empty() ){ - std::printf( "no inputs given; pass corpus files to replay/mutate\n" ); + if (seeds.empty()) { + std::printf("no inputs given; pass corpus files to replay/mutate\n"); return 0; } // 2) Bounded deterministic random-mutation loop over the seeds. Fixed seed // so runs are reproducible. - std::mt19937 rng( 0x05CADAB7u ); - const int kIterations = 200000; - for( int iter = 0; iter < kIterations; ++iter ){ - std::vector buf = seeds[ rng() % seeds.size() ]; - if( buf.empty() ) + std::mt19937 rng(0x05CADAB7u); + const int kIterations = 200000; + for (int iter = 0; iter < kIterations; ++iter) { + std::vector buf = seeds[rng() % seeds.size()]; + if (buf.empty()) continue; // a handful of random single-byte flips int flips = 1 + (rng() % 6); - for( int k = 0; k < flips; ++k ) - buf[ rng() % buf.size() ] = (uint8_t)( rng() & 0xFF ); + for (int k = 0; k < flips; ++k) + buf[rng() % buf.size()] = (uint8_t)(rng() & 0xFF); // occasionally truncate to probe short/edge-length handling - if( (rng() & 7) == 0 && buf.size() > 4 ) - buf.resize( rng() % buf.size() ); + if ((rng() & 7) == 0 && buf.size() > 4) + buf.resize(rng() % buf.size()); - LLVMFuzzerTestOneInput( buf.data(), buf.size() ); + LLVMFuzzerTestOneInput(buf.data(), buf.size()); } - std::printf( "standalone fuzz driver: replayed %zu seed(s), ran %d mutations -- no crash\n", - seeds.size(), kIterations ); + std::printf("standalone fuzz driver: replayed %zu seed(s), ran %d mutations -- no crash\n", seeds.size(), + kIterations); return 0; } diff --git a/osctap/ip/AbstractUdpSocket.h b/osctap/ip/AbstractUdpSocket.h index f3afaff..a241aea 100644 --- a/osctap/ip/AbstractUdpSocket.h +++ b/osctap/ip/AbstractUdpSocket.h @@ -39,217 +39,160 @@ #include // size_t -#include "NetworkingUtils.h" #include "IpEndpointName.h" +#include "NetworkingUtils.h" - -namespace osctap -{ -class PacketListener; -class TimerListener; - -namespace detail -{ -template -class SocketReceiveMultiplexer -{ - typename Impl_T::socket_multiplexer_t impl_; - - public: - using implementation_t = Impl_T; - using udp_socket_t = typename Impl_T::udp_socket_t; - - SocketReceiveMultiplexer() = default; - - // only call the attach/detach methods _before_ calling Run - - // only one listener per socket, each socket at most once - void AttachSocketListener( udp_socket_t *socket, PacketListener *listener ) - { - impl_.AttachSocketListener( socket, listener ); - } - void DetachSocketListener( udp_socket_t *socket, PacketListener *listener ) - { - impl_.DetachSocketListener( socket, listener ); - } - - void AttachPeriodicTimerListener( int periodMilliseconds, TimerListener *listener ) - { - impl_.AttachPeriodicTimerListener( periodMilliseconds, listener ); - } - void AttachPeriodicTimerListener( - int initialDelayMilliseconds, int periodMilliseconds, TimerListener *listener ) - { - impl_.AttachPeriodicTimerListener( initialDelayMilliseconds, periodMilliseconds, listener ); - } - void DetachPeriodicTimerListener( TimerListener *listener ) - { - impl_.DetachPeriodicTimerListener( listener ); - } - - void Run() - { - impl_.Run(); - } // loop and block processing messages indefinitely - void Break() - { - impl_.Break(); - } // call this from a listener to exit once the listener returns - void AsynchronousBreak() - { - impl_.AsynchronousBreak(); - } // call this from another thread or signal handler to exit the Run() state -}; - - -template -class UdpSocket{ - protected: - typename Impl_T::udp_socket_t impl_; - - public: - using implementation_t = typename Impl_T::udp_socket_t; - - // Ctor throws std::runtime_error if there's a problem - // initializing the socket. - UdpSocket() = default; - - int LocalPort() const - { - return impl_.LocalPort(); - } - - // Enable broadcast addresses (e.g. x.x.x.255) - // Sets SO_BROADCAST socket option. - void SetEnableBroadcast( bool enableBroadcast ) - { - impl_.SetEnableBroadcast( enableBroadcast ); - } - - // Enable multiple listeners for a single port on same - // network interface* - // Sets SO_REUSEADDR (also SO_REUSEPORT on OS X). - // [*] The exact behavior of SO_REUSEADDR and - // SO_REUSEPORT is undefined for some common cases - // and may have drastically different behavior on different - // operating systems. - void SetAllowReuse( bool allowReuse ) - { - impl_.SetAllowReuse( allowReuse ); - } - - // Join (or later leave) an IPv4 multicast group on this socket so it receives - // datagrams sent to that group address (224.0.0.0 .. 239.255.255.255). Bind() - // the socket to the listening port first; the group is taken from the - // IpEndpointName and the default interface is used. Closing the socket leaves - // any joined groups automatically, so LeaveMulticastGroup() is only needed to - // stop receiving a group while keeping the socket open. - void JoinMulticastGroup( const IpEndpointName& multicastGroup ) - { - impl_.JoinMulticastGroup( multicastGroup ); - } - void LeaveMulticastGroup( const IpEndpointName& multicastGroup ) - { - impl_.LeaveMulticastGroup( multicastGroup ); - } - - - // 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 - { - return impl_.LocalEndpointFor( remoteEndpoint ); - } - - - // Connect to a remote endpoint which is used as the target - // for calls to Send() - void Connect( const IpEndpointName& remoteEndpoint ) - { - impl_.Connect( remoteEndpoint ); - } - void Send( const char *data, std::size_t size ) - { - impl_.Send( data, size ); - } - void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size ) - { - impl_.SendTo( remoteEndpoint, data, 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 ) - { - impl_.Bind( localEndpoint ); - } - bool IsBound() const - { - return impl_.IsBound(); - } - - std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size ) - { - return impl_.ReceiveFrom( remoteEndpoint, data, 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 -template -class UdpTransmitSocket : public UdpSocket{ - public: - UdpTransmitSocket( const IpEndpointName& remoteEndpoint ) - { this->Connect( remoteEndpoint ); } -}; - - -template -class UdpReceiveSocket : public UdpSocket{ - public: - UdpReceiveSocket( const IpEndpointName& localEndpoint ) - { this->Bind( localEndpoint ); } -}; - - -// UdpListeningReceiveSocket provides a simple way to bind one listener -// to a single socket without having to manually set up a SocketReceiveMultiplexer - -template -class UdpListeningReceiveSocket : public UdpSocket{ - SocketReceiveMultiplexer mux_; - PacketListener *listener_; - public: - UdpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener *listener ) - : listener_( listener ) - { - this->Bind( localEndpoint ); - mux_.AttachSocketListener( &this->impl_, listener_ ); - } - - ~UdpListeningReceiveSocket() - { mux_.DetachSocketListener( &this->impl_, listener_ ); } - - // see SocketReceiveMultiplexer above for the behaviour of these methods... - void Run() { mux_.Run(); } - void Break() { mux_.Break(); } - void AsynchronousBreak() { mux_.AsynchronousBreak(); } -}; - -} - - -} - +namespace osctap { + class PacketListener; + class TimerListener; + + namespace detail { + template + class SocketReceiveMultiplexer { + typename Impl_T::socket_multiplexer_t impl_; + + public: + using implementation_t = Impl_T; + using udp_socket_t = typename Impl_T::udp_socket_t; + + SocketReceiveMultiplexer() = default; + + // only call the attach/detach methods _before_ calling Run + + // only one listener per socket, each socket at most once + void AttachSocketListener(udp_socket_t* socket, PacketListener* listener) { + impl_.AttachSocketListener(socket, listener); + } + void DetachSocketListener(udp_socket_t* socket, PacketListener* listener) { + impl_.DetachSocketListener(socket, listener); + } + + void AttachPeriodicTimerListener(int periodMilliseconds, TimerListener* listener) { + impl_.AttachPeriodicTimerListener(periodMilliseconds, listener); + } + void AttachPeriodicTimerListener(int initialDelayMilliseconds, int periodMilliseconds, + TimerListener* listener) { + impl_.AttachPeriodicTimerListener(initialDelayMilliseconds, periodMilliseconds, listener); + } + void DetachPeriodicTimerListener(TimerListener* listener) { impl_.DetachPeriodicTimerListener(listener); } + + void Run() { impl_.Run(); } // loop and block processing messages indefinitely + void Break() { impl_.Break(); } // call this from a listener to exit once the listener returns + void AsynchronousBreak() { + impl_.AsynchronousBreak(); + } // call this from another thread or signal handler to exit the Run() state + }; + + template + class UdpSocket { + protected: + typename Impl_T::udp_socket_t impl_; + + public: + using implementation_t = typename Impl_T::udp_socket_t; + + // Ctor throws std::runtime_error if there's a problem + // initializing the socket. + UdpSocket() = default; + + int LocalPort() const { return impl_.LocalPort(); } + + // Enable broadcast addresses (e.g. x.x.x.255) + // Sets SO_BROADCAST socket option. + void SetEnableBroadcast(bool enableBroadcast) { impl_.SetEnableBroadcast(enableBroadcast); } + + // Enable multiple listeners for a single port on same + // network interface* + // Sets SO_REUSEADDR (also SO_REUSEPORT on OS X). + // [*] The exact behavior of SO_REUSEADDR and + // SO_REUSEPORT is undefined for some common cases + // and may have drastically different behavior on different + // operating systems. + void SetAllowReuse(bool allowReuse) { impl_.SetAllowReuse(allowReuse); } + + // Join (or later leave) an IPv4 multicast group on this socket so it receives + // datagrams sent to that group address (224.0.0.0 .. 239.255.255.255). Bind() + // the socket to the listening port first; the group is taken from the + // IpEndpointName and the default interface is used. Closing the socket leaves + // any joined groups automatically, so LeaveMulticastGroup() is only needed to + // stop receiving a group while keeping the socket open. + void JoinMulticastGroup(const IpEndpointName& multicastGroup) { impl_.JoinMulticastGroup(multicastGroup); } + void LeaveMulticastGroup(const IpEndpointName& multicastGroup) { + impl_.LeaveMulticastGroup(multicastGroup); + } + + // 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 { + return impl_.LocalEndpointFor(remoteEndpoint); + } + + // Connect to a remote endpoint which is used as the target + // for calls to Send() + void Connect(const IpEndpointName& remoteEndpoint) { impl_.Connect(remoteEndpoint); } + void Send(const char* data, std::size_t size) { impl_.Send(data, size); } + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + impl_.SendTo(remoteEndpoint, data, 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) { impl_.Bind(localEndpoint); } + bool IsBound() const { return impl_.IsBound(); } + + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + return impl_.ReceiveFrom(remoteEndpoint, data, 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 + template + class UdpTransmitSocket : public UdpSocket { + public: + UdpTransmitSocket(const IpEndpointName& remoteEndpoint) { this->Connect(remoteEndpoint); } + }; + + template + class UdpReceiveSocket : public UdpSocket { + public: + UdpReceiveSocket(const IpEndpointName& localEndpoint) { this->Bind(localEndpoint); } + }; + + // UdpListeningReceiveSocket provides a simple way to bind one listener + // to a single socket without having to manually set up a SocketReceiveMultiplexer + + template + class UdpListeningReceiveSocket : public UdpSocket { + SocketReceiveMultiplexer mux_; + PacketListener* listener_; + + public: + UdpListeningReceiveSocket(const IpEndpointName& localEndpoint, PacketListener* listener) + : listener_(listener) { + this->Bind(localEndpoint); + mux_.AttachSocketListener(&this->impl_, listener_); + } + + ~UdpListeningReceiveSocket() { mux_.DetachSocketListener(&this->impl_, listener_); } + + // see SocketReceiveMultiplexer above for the behaviour of these methods... + void Run() { mux_.Run(); } + void Break() { mux_.Break(); } + void AsynchronousBreak() { mux_.AsynchronousBreak(); } + }; + + } // namespace detail + +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/IpEndpointName.h b/osctap/ip/IpEndpointName.h index 7e1c22f..27097b3 100644 --- a/osctap/ip/IpEndpointName.h +++ b/osctap/ip/IpEndpointName.h @@ -41,92 +41,80 @@ #include "NetworkingUtils.h" -namespace osctap -{ -class IpEndpointName -{ - static unsigned long GetHostByName( const char *s ) - { - return osctap::GetHostByName(s); - } +namespace osctap { + class IpEndpointName { + static unsigned long GetHostByName(const char* s) { return osctap::GetHostByName(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(unsigned int addressA, unsigned int addressB, unsigned int addressC, unsigned 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; + + bool IsMulticastAddress() const { return ((address >> 24) & 0xFF) >= 224 && ((address >> 24) & 0xFF) <= 239; } + + enum { ADDRESS_STRING_LENGTH = 17 }; + void AddressAsString(char* s) const { + if (address == ANY_ADDRESS) { + std::snprintf(s, ADDRESS_STRING_LENGTH, ""); + } + else { + std::snprintf(s, ADDRESS_STRING_LENGTH, "%d.%d.%d.%d", (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), (int)((address >> 8) & 0xFF), (int)(address & 0xFF)); + } + } + + enum { ADDRESS_AND_PORT_STRING_LENGTH = 23 }; + void AddressAndPortAsString(char* s) const { + if (port == ANY_PORT) { + if (address == ANY_ADDRESS) { + std::snprintf(s, ADDRESS_AND_PORT_STRING_LENGTH, ":"); + } + else { + std::snprintf(s, ADDRESS_AND_PORT_STRING_LENGTH, "%d.%d.%d.%d:", (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), (int)((address >> 8) & 0xFF), (int)(address & 0xFF)); + } + } + else { + if (address == ANY_ADDRESS) { + std::snprintf(s, ADDRESS_AND_PORT_STRING_LENGTH, ":%d", port); + } + else { + std::snprintf(s, ADDRESS_AND_PORT_STRING_LENGTH, "%d.%d.%d.%d:%d", (int)((address >> 24) & 0xFF), + (int)((address >> 16) & 0xFF), (int)((address >> 8) & 0xFF), (int)(address & 0xFF), + (int)port); + } + } + } + }; - 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( unsigned int addressA, unsigned int addressB, unsigned int addressC, unsigned 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; - - bool IsMulticastAddress() const { return ((address >> 24) & 0xFF) >= 224 && ((address >> 24) & 0xFF) <= 239; } - - enum { ADDRESS_STRING_LENGTH=17 }; - void AddressAsString( char *s ) const - { - if( address == ANY_ADDRESS ){ - std::snprintf( s, ADDRESS_STRING_LENGTH, "" ); - }else{ - std::snprintf( s, ADDRESS_STRING_LENGTH, "%d.%d.%d.%d", - (int)((address >> 24) & 0xFF), - (int)((address >> 16) & 0xFF), - (int)((address >> 8) & 0xFF), - (int)(address & 0xFF) ); - } + inline bool operator==(const IpEndpointName& lhs, const IpEndpointName& rhs) { + return (lhs.address == rhs.address && lhs.port == rhs.port); } - enum { ADDRESS_AND_PORT_STRING_LENGTH=23}; - void AddressAndPortAsString( char *s ) const - { - if( port == ANY_PORT ){ - if( address == ANY_ADDRESS ){ - std::snprintf( s, ADDRESS_AND_PORT_STRING_LENGTH, ":" ); - }else{ - std::snprintf( s, ADDRESS_AND_PORT_STRING_LENGTH, "%d.%d.%d.%d:", - (int)((address >> 24) & 0xFF), - (int)((address >> 16) & 0xFF), - (int)((address >> 8) & 0xFF), - (int)(address & 0xFF) ); - } - } - else - { - if( address == ANY_ADDRESS ){ - std::snprintf( s, ADDRESS_AND_PORT_STRING_LENGTH, ":%d", port ); - }else{ - std::snprintf( s, ADDRESS_AND_PORT_STRING_LENGTH, "%d.%d.%d.%d:%d", - (int)((address >> 24) & 0xFF), - (int)((address >> 16) & 0xFF), - (int)((address >> 8) & 0xFF), - (int)(address & 0xFF), - (int)port ); - } - } + inline bool operator!=(const IpEndpointName& lhs, const IpEndpointName& rhs) { + return !(lhs == rhs); } -}; - -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); -} -} +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/NetworkingUtils.h b/osctap/ip/NetworkingUtils.h index 0f3fe08..449fc87 100644 --- a/osctap/ip/NetworkingUtils.h +++ b/osctap/ip/NetworkingUtils.h @@ -1,7 +1,7 @@ #pragma once #if defined(_WIN32) -#include +#include "ip/win32/NetworkingUtils.h" #else -#include +#include "ip/posix/NetworkingUtils.h" #endif diff --git a/osctap/ip/PacketListener.h b/osctap/ip/PacketListener.h index 192cc7d..2c0b988 100644 --- a/osctap/ip/PacketListener.h +++ b/osctap/ip/PacketListener.h @@ -37,17 +37,15 @@ #ifndef INCLUDED_OSCTAP_PACKETLISTENER_H #define INCLUDED_OSCTAP_PACKETLISTENER_H -namespace osctap -{ -class IpEndpointName; - -class PacketListener{ - public: - virtual ~PacketListener() {} - virtual void ProcessPacket( const char *data, int size, - const IpEndpointName& remoteEndpoint ) = 0; -}; -} +namespace osctap { + class IpEndpointName; + + class PacketListener { + public: + virtual ~PacketListener() {} + virtual void ProcessPacket(const char* data, int size, const IpEndpointName& remoteEndpoint) = 0; + }; +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/TcpSocket.h b/osctap/ip/TcpSocket.h index 9de5d66..c2a931b 100644 --- a/osctap/ip/TcpSocket.h +++ b/osctap/ip/TcpSocket.h @@ -19,16 +19,15 @@ #include "posix/TcpSocket.h" #endif -namespace osctap -{ +namespace osctap { #if defined(_WIN32) -using TcpTransmitSocket = win32::TcpTransmitSocket; -using TcpListeningReceiveSocket = win32::TcpListeningReceiveSocket; + using TcpTransmitSocket = win32::TcpTransmitSocket; + using TcpListeningReceiveSocket = win32::TcpListeningReceiveSocket; #else -using TcpTransmitSocket = posix::TcpTransmitSocket; -using TcpListeningReceiveSocket = posix::TcpListeningReceiveSocket; + using TcpTransmitSocket = posix::TcpTransmitSocket; + using TcpListeningReceiveSocket = posix::TcpListeningReceiveSocket; #endif -} +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. namespace oscpack = osctap; diff --git a/osctap/ip/TimerListener.h b/osctap/ip/TimerListener.h index e594b26..f179ff3 100644 --- a/osctap/ip/TimerListener.h +++ b/osctap/ip/TimerListener.h @@ -1,50 +1,49 @@ /* - oscpack -- Open Sound Control (OSC) packet manipulation library + oscpack -- Open Sound Control (OSC) packet manipulation library http://www.rossbencina.com/code/oscpack Copyright (c) 2004-2013 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. - - 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. + 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. + + 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. */ /* - The text above constitutes the entire oscpack license; however, - the oscpack developer(s) also make the following non-binding requests: - - 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. It is also - requested that these non-binding requests be included whenever the - above license is reproduced. + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + 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. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. */ #ifndef INCLUDED_OSCTAP_TIMERLISTENER_H #define INCLUDED_OSCTAP_TIMERLISTENER_H -namespace osctap -{ -class TimerListener{ -public: - virtual ~TimerListener() {} - virtual void TimerExpired() = 0; -}; -} +namespace osctap { + class TimerListener { + public: + virtual ~TimerListener() {} + virtual void TimerExpired() = 0; + }; +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/UdpSocket.h b/osctap/ip/UdpSocket.h index a452103..a0eadf3 100644 --- a/osctap/ip/UdpSocket.h +++ b/osctap/ip/UdpSocket.h @@ -1,6 +1,6 @@ #pragma once #include "AbstractUdpSocket.h" -#include +#include "ip/NetworkingUtils.h" #if defined(_WIN32) #include "win32/UdpSocket.h" @@ -8,21 +8,19 @@ #include "posix/UdpSocket.h" #endif -namespace osctap -{ -namespace detail -{ +namespace osctap { + namespace detail { #if defined(_WIN32) -using Implementation = osctap::win32::Implementation; + using Implementation = osctap::win32::Implementation; #else -using Implementation = osctap::posix::Implementation; + using Implementation = osctap::posix::Implementation; #endif -} + } // namespace detail -using UdpTransmitSocket = detail::UdpTransmitSocket; -using UdpReceiveSocket = detail::UdpReceiveSocket; -using UdpListeningReceiveSocket = detail::UdpListeningReceiveSocket; -} + using UdpTransmitSocket = detail::UdpTransmitSocket; + using UdpReceiveSocket = detail::UdpReceiveSocket; + using UdpListeningReceiveSocket = detail::UdpListeningReceiveSocket; +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/posix/NetworkingUtils.h b/osctap/ip/posix/NetworkingUtils.h index b074768..f72b915 100644 --- a/osctap/ip/posix/NetworkingUtils.h +++ b/osctap/ip/posix/NetworkingUtils.h @@ -1,51 +1,48 @@ #pragma once +#include +#include + #include -#include #include -#include -#include -namespace osctap -{ +#include +namespace osctap { // 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: + public: NetworkInitializer() {} ~NetworkInitializer() {} }; // return ip address of host name in host byte order - inline unsigned long GetHostByName(const char *name) - { - unsigned long result = 0; - - addrinfo hints = {}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - addrinfo* ai{}; - const int err = getaddrinfo(name, nullptr, &hints, &ai); - - if (err != 0) - { - freeaddrinfo(ai); - return 0; - } - - if(ai) - { - auto remote = reinterpret_cast(ai->ai_addr); - // s_addr is a 32-bit network-order address; ntohl takes/returns uint32_t. - result = ntohl(static_cast(remote->sin_addr.s_addr)); - - freeaddrinfo(ai); - } - return result; + inline unsigned long GetHostByName(const char* name) { + unsigned long result = 0; + + addrinfo hints = {}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + addrinfo* ai{}; + const int err = getaddrinfo(name, nullptr, &hints, &ai); + + if (err != 0) { + freeaddrinfo(ai); + return 0; + } + + if (ai) { + auto remote = reinterpret_cast(ai->ai_addr); + // s_addr is a 32-bit network-order address; ntohl takes/returns uint32_t. + result = ntohl(static_cast(remote->sin_addr.s_addr)); + + freeaddrinfo(ai); + } + return result; } -} +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/posix/TcpSocket.h b/osctap/ip/posix/TcpSocket.h index cf6ecdf..b78cf40 100644 --- a/osctap/ip/posix/TcpSocket.h +++ b/osctap/ip/posix/TcpSocket.h @@ -28,272 +28,274 @@ // Reuse the posix socket includes and the SockaddrFromIpEndpointName / // IpEndpointNameFromSockaddr helpers defined in the UDP backend. -#include // complete type before the helpers below use it -#include -#include -#include - -#include // TCP_NODELAY -#include // errno (don't rely on transitive includes) +#include // errno (don't rely on transitive includes) #include #include -namespace osctap -{ -namespace posix -{ - -// --------------------------------------------------------------------------- -// TcpTransmitSocket -- connect to a remote OSC-over-TCP server and send packets. -// -// Each Send() writes one length-prefixed frame (4-byte big-endian count + -// payload), looping over partial writes (a TCP send() may transfer fewer bytes -// than requested). TCP_NODELAY is enabled (Nagle off) -- OSC over TCP without it -// is a classic latency footgun. -// --------------------------------------------------------------------------- -class TcpTransmitSocket -{ -public: - explicit TcpTransmitSocket( const IpEndpointName& remoteEndpoint ) - { - socket_ = ::socket( AF_INET, SOCK_STREAM, 0 ); - if( socket_ == -1 ) - throw std::runtime_error( "unable to create tcp socket\n" ); +#include // TCP_NODELAY + +#include "ip/IpEndpointName.h" // complete type before the helpers below use it +#include "ip/PacketListener.h" +#include "ip/posix/UdpSocket.h" +#include "osc/OscStreamFraming.h" + +namespace osctap { + namespace posix { + + // --------------------------------------------------------------------------- + // TcpTransmitSocket -- connect to a remote OSC-over-TCP server and send packets. + // + // Each Send() writes one length-prefixed frame (4-byte big-endian count + + // payload), looping over partial writes (a TCP send() may transfer fewer bytes + // than requested). TCP_NODELAY is enabled (Nagle off) -- OSC over TCP without it + // is a classic latency footgun. + // --------------------------------------------------------------------------- + class TcpTransmitSocket { + public: + explicit TcpTransmitSocket(const IpEndpointName& remoteEndpoint) { + socket_ = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_ == -1) + throw std::runtime_error("unable to create tcp socket\n"); #ifdef SO_NOSIGPIPE - int noSigpipe = 1; // macOS / BSD: suppress SIGPIPE on send to a closed peer - setsockopt( socket_, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe) ); + int noSigpipe = 1; // macOS / BSD: suppress SIGPIPE on send to a closed peer + setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe)); #endif - int one = 1; - setsockopt( socket_, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one) ); - - struct sockaddr_in addr; - SockaddrFromIpEndpointName( addr, remoteEndpoint ); - if( ::connect( socket_, (struct sockaddr*)&addr, sizeof(addr) ) < 0 ){ - ::close( socket_ ); - socket_ = -1; - throw std::runtime_error( "unable to connect tcp socket\n" ); - } - } - - ~TcpTransmitSocket() { if( socket_ != -1 ) ::close( socket_ ); } - - TcpTransmitSocket( const TcpTransmitSocket& ) = delete; - TcpTransmitSocket& operator=( const TcpTransmitSocket& ) = delete; - - // Send one complete OSC packet, length-prefixed. Blocks until fully written. - void Send( const char* data, std::size_t size ) - { - char header[OSC_STREAM_FRAME_HEADER_SIZE]; - WriteOscStreamFrameHeader( header, (uint32_t)size ); - SendAll( header, OSC_STREAM_FRAME_HEADER_SIZE ); - SendAll( data, size ); - } - - int Socket() const { return socket_; } - -private: - void SendAll( const char* p, std::size_t n ) - { + int one = 1; + setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + + struct sockaddr_in addr; + SockaddrFromIpEndpointName(addr, remoteEndpoint); + if (::connect(socket_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + ::close(socket_); + socket_ = -1; + throw std::runtime_error("unable to connect tcp socket\n"); + } + } + + ~TcpTransmitSocket() { + if (socket_ != -1) + ::close(socket_); + } + + TcpTransmitSocket(const TcpTransmitSocket&) = delete; + TcpTransmitSocket& operator=(const TcpTransmitSocket&) = delete; + + // Send one complete OSC packet, length-prefixed. Blocks until fully written. + void Send(const char* data, std::size_t size) { + char header[OSC_STREAM_FRAME_HEADER_SIZE]; + WriteOscStreamFrameHeader(header, (uint32_t)size); + SendAll(header, OSC_STREAM_FRAME_HEADER_SIZE); + SendAll(data, size); + } + + int Socket() const { return socket_; } + + private: + void SendAll(const char* p, std::size_t n) { #ifdef MSG_NOSIGNAL - const int flags = MSG_NOSIGNAL; // Linux: don't raise SIGPIPE + const int flags = MSG_NOSIGNAL; // Linux: don't raise SIGPIPE #else - const int flags = 0; + const int flags = 0; #endif - std::size_t sent = 0; - while( sent < n ){ - ssize_t r = ::send( socket_, p + sent, n - sent, flags ); - if( r < 0 ){ - if( errno == EINTR ) continue; - throw std::runtime_error( "tcp send failed\n" ); + std::size_t sent = 0; + while (sent < n) { + ssize_t r = ::send(socket_, p + sent, n - sent, flags); + if (r < 0) { + if (errno == EINTR) + continue; + throw std::runtime_error("tcp send failed\n"); + } + sent += (std::size_t)r; + } } - sent += (std::size_t)r; - } - } - - int socket_ = -1; -}; - - -// --------------------------------------------------------------------------- -// TcpListeningReceiveSocket -- listen for OSC-over-TCP clients and dispatch each -// complete packet to a PacketListener. -// -// Single-threaded, select()-based, and connection-aware: it accept()s any number -// of clients and keeps a per-connection OscStreamDeframer (each connection's byte -// stream reassembles independently). Run() blocks; Break()/AsynchronousBreak() -// stop it (the latter via a self-pipe, so it works from another thread or a -// signal handler -- mirroring the UDP multiplexer). -// --------------------------------------------------------------------------- -class TcpListeningReceiveSocket -{ - struct Connection - { - IpEndpointName peer; - OscStreamDeframer deframer; - Connection( const IpEndpointName& p, uint32_t maxFrame ) - : peer( p ), deframer( maxFrame ) {} - }; - -public: - TcpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener* listener, - uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE ) - : listener_( listener ), maxFrameSize_( maxFrameSize ) - { - if( pipe( breakPipe_ ) != 0 ) - throw std::runtime_error( "creation of asynchronous break pipes failed\n" ); - - listenSocket_ = ::socket( AF_INET, SOCK_STREAM, 0 ); - if( listenSocket_ == -1 ){ - close( breakPipe_[0] ); close( breakPipe_[1] ); - throw std::runtime_error( "unable to create tcp socket\n" ); - } - - int reuse = 1; - setsockopt( listenSocket_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse) ); - - struct sockaddr_in addr; - SockaddrFromIpEndpointName( addr, localEndpoint ); - if( ::bind( listenSocket_, (struct sockaddr*)&addr, sizeof(addr) ) < 0 ){ - Cleanup(); - throw std::runtime_error( "unable to bind tcp socket\n" ); - } - if( ::listen( listenSocket_, SOMAXCONN ) < 0 ){ - Cleanup(); - throw std::runtime_error( "unable to listen on tcp socket\n" ); - } - } - - ~TcpListeningReceiveSocket() { Cleanup(); } - - TcpListeningReceiveSocket( const TcpListeningReceiveSocket& ) = delete; - TcpListeningReceiveSocket& operator=( const TcpListeningReceiveSocket& ) = delete; - - // The bound local endpoint (resolves the OS-assigned port when bound to 0). - IpEndpointName LocalEndpointFor( const IpEndpointName& requested ) const - { - struct sockaddr_in addr; - socklen_t len = sizeof(addr); - if( getsockname( listenSocket_, (struct sockaddr*)&addr, &len ) == 0 ) - return IpEndpointName( requested.address, ntohs( addr.sin_port ) ); - return requested; - } - - void Run() - { - break_ = false; - char buf[4096]; - - while( !break_ ){ - fd_set readfds; - FD_ZERO( &readfds ); - FD_SET( listenSocket_, &readfds ); - FD_SET( breakPipe_[0], &readfds ); - int fdmax = listenSocket_ > breakPipe_[0] ? listenSocket_ : breakPipe_[0]; - for( const auto& kv : connections_ ){ - FD_SET( kv.first, &readfds ); - if( kv.first > fdmax ) fdmax = kv.first; + + int socket_ = -1; + }; + + // --------------------------------------------------------------------------- + // TcpListeningReceiveSocket -- listen for OSC-over-TCP clients and dispatch each + // complete packet to a PacketListener. + // + // Single-threaded, select()-based, and connection-aware: it accept()s any number + // of clients and keeps a per-connection OscStreamDeframer (each connection's byte + // stream reassembles independently). Run() blocks; Break()/AsynchronousBreak() + // stop it (the latter via a self-pipe, so it works from another thread or a + // signal handler -- mirroring the UDP multiplexer). + // --------------------------------------------------------------------------- + class TcpListeningReceiveSocket { + struct Connection { + IpEndpointName peer; + OscStreamDeframer deframer; + Connection(const IpEndpointName& p, uint32_t maxFrame) + : peer(p) + , deframer(maxFrame) {} + }; + + public: + TcpListeningReceiveSocket(const IpEndpointName& localEndpoint, PacketListener* listener, + uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE) + : listener_(listener) + , maxFrameSize_(maxFrameSize) { + if (pipe(breakPipe_) != 0) + throw std::runtime_error("creation of asynchronous break pipes failed\n"); + + listenSocket_ = ::socket(AF_INET, SOCK_STREAM, 0); + if (listenSocket_ == -1) { + close(breakPipe_[0]); + close(breakPipe_[1]); + throw std::runtime_error("unable to create tcp socket\n"); + } + + int reuse = 1; + setsockopt(listenSocket_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); + + struct sockaddr_in addr; + SockaddrFromIpEndpointName(addr, localEndpoint); + if (::bind(listenSocket_, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + Cleanup(); + throw std::runtime_error("unable to bind tcp socket\n"); + } + if (::listen(listenSocket_, SOMAXCONN) < 0) { + Cleanup(); + throw std::runtime_error("unable to listen on tcp socket\n"); + } + } + + ~TcpListeningReceiveSocket() { Cleanup(); } + + TcpListeningReceiveSocket(const TcpListeningReceiveSocket&) = delete; + TcpListeningReceiveSocket& operator=(const TcpListeningReceiveSocket&) = delete; + + // The bound local endpoint (resolves the OS-assigned port when bound to 0). + IpEndpointName LocalEndpointFor(const IpEndpointName& requested) const { + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + if (getsockname(listenSocket_, (struct sockaddr*)&addr, &len) == 0) + return IpEndpointName(requested.address, ntohs(addr.sin_port)); + return requested; + } + + void Run() { + break_ = false; + char buf[4096]; + + while (!break_) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket_, &readfds); + FD_SET(breakPipe_[0], &readfds); + int fdmax = listenSocket_ > breakPipe_[0] ? listenSocket_ : breakPipe_[0]; + for (const auto& kv : connections_) { + FD_SET(kv.first, &readfds); + if (kv.first > fdmax) + fdmax = kv.first; + } + + if (select(fdmax + 1, &readfds, 0, 0, 0) < 0) { + if (break_) + break; + if (errno == EINTR) + continue; + throw std::runtime_error("select failed\n"); + } + + if (FD_ISSET(breakPipe_[0], &readfds)) { + char c; + ssize_t r = read(breakPipe_[0], &c, 1); + (void)r; + } + if (break_) + break; + + if (FD_ISSET(listenSocket_, &readfds)) + AcceptConnection(); + + // Collect ready connection fds first; processing may erase entries. + std::vector ready; + for (const auto& kv : connections_) + if (FD_ISSET(kv.first, &readfds)) + ready.push_back(kv.first); + + for (int fd : ready) { + ServiceConnection(fd, buf, sizeof(buf)); + if (break_) + break; + } + } } - if( select( fdmax + 1, &readfds, 0, 0, 0 ) < 0 ){ - if( break_ ) break; - if( errno == EINTR ) continue; - throw std::runtime_error( "select failed\n" ); + void Break() { break_ = true; } + + void AsynchronousBreak() { + break_ = true; + ssize_t r = write(breakPipe_[1], "!", 1); + (void)r; } - if( FD_ISSET( breakPipe_[0], &readfds ) ){ - char c; ssize_t r = read( breakPipe_[0], &c, 1 ); (void)r; + int Socket() const { return listenSocket_; } + + private: + void AcceptConnection() { + struct sockaddr_in peerAddr; + socklen_t len = sizeof(peerAddr); + int conn = ::accept(listenSocket_, (struct sockaddr*)&peerAddr, &len); + if (conn == -1) + return; + int one = 1; + setsockopt(conn, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + connections_.emplace(std::piecewise_construct, std::forward_as_tuple(conn), + std::forward_as_tuple(IpEndpointNameFromSockaddr(peerAddr), maxFrameSize_)); } - if( break_ ) break; - if( FD_ISSET( listenSocket_, &readfds ) ) - AcceptConnection(); + void ServiceConnection(int fd, char* buf, std::size_t bufSize) { + auto it = connections_.find(fd); + if (it == connections_.end()) + return; + + ssize_t n = ::recv(fd, buf, bufSize, 0); + if (n <= 0) { // 0 = peer closed; <0 = error -> drop the connection + CloseConnection(it); + return; + } + + // Reassemble and dispatch every complete packet in this read. The sink + // runs synchronously, so `it` (and its peer) stays valid throughout. + const bool ok = + it->second.deframer.Consume(buf, (std::size_t)n, [&](const char* packet, uint32_t size) { + listener_->ProcessPacket(packet, (int)size, it->second.peer); + }); + + if (!ok) // a frame exceeded maxFrameSize -> protocol violation + CloseConnection(it); + } - // Collect ready connection fds first; processing may erase entries. - std::vector ready; - for( const auto& kv : connections_ ) - if( FD_ISSET( kv.first, &readfds ) ) ready.push_back( kv.first ); + void CloseConnection(std::map::iterator it) { + ::close(it->first); + connections_.erase(it); + } - for( int fd : ready ){ - ServiceConnection( fd, buf, sizeof(buf) ); - if( break_ ) break; + void Cleanup() { + for (auto& kv : connections_) + ::close(kv.first); + connections_.clear(); + if (listenSocket_ != -1) { + ::close(listenSocket_); + listenSocket_ = -1; + } + close(breakPipe_[0]); + close(breakPipe_[1]); } - } - } - - void Break() { break_ = true; } - - void AsynchronousBreak() - { - break_ = true; - ssize_t r = write( breakPipe_[1], "!", 1 ); (void)r; - } - - int Socket() const { return listenSocket_; } - -private: - void AcceptConnection() - { - struct sockaddr_in peerAddr; - socklen_t len = sizeof(peerAddr); - int conn = ::accept( listenSocket_, (struct sockaddr*)&peerAddr, &len ); - if( conn == -1 ) - return; - int one = 1; - setsockopt( conn, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one) ); - connections_.emplace( std::piecewise_construct, - std::forward_as_tuple( conn ), - std::forward_as_tuple( IpEndpointNameFromSockaddr( peerAddr ), maxFrameSize_ ) ); - } - - void ServiceConnection( int fd, char* buf, std::size_t bufSize ) - { - auto it = connections_.find( fd ); - if( it == connections_.end() ) - return; - - ssize_t n = ::recv( fd, buf, bufSize, 0 ); - if( n <= 0 ){ // 0 = peer closed; <0 = error -> drop the connection - CloseConnection( it ); - return; - } - - // Reassemble and dispatch every complete packet in this read. The sink - // runs synchronously, so `it` (and its peer) stays valid throughout. - const bool ok = it->second.deframer.Consume( buf, (std::size_t)n, - [&]( const char* packet, uint32_t size ){ - listener_->ProcessPacket( packet, (int)size, it->second.peer ); - } ); - - if( !ok ) // a frame exceeded maxFrameSize -> protocol violation - CloseConnection( it ); - } - - void CloseConnection( std::map::iterator it ) - { - ::close( it->first ); - connections_.erase( it ); - } - - void Cleanup() - { - for( auto& kv : connections_ ) - ::close( kv.first ); - connections_.clear(); - if( listenSocket_ != -1 ){ ::close( listenSocket_ ); listenSocket_ = -1; } - close( breakPipe_[0] ); - close( breakPipe_[1] ); - } - - int listenSocket_ = -1; - PacketListener* listener_; - uint32_t maxFrameSize_; - std::atomic_bool break_{ false }; - int breakPipe_[2]; - std::map connections_; -}; - -} // namespace posix + + int listenSocket_ = -1; + PacketListener* listener_; + uint32_t maxFrameSize_; + std::atomic_bool break_{false}; + int breakPipe_[2]; + std::map connections_; + }; + + } // namespace posix } // namespace osctap #endif /* INCLUDED_OSCTAP_POSIX_TCPSOCKET_H */ diff --git a/osctap/ip/posix/UdpSocket.h b/osctap/ip/posix/UdpSocket.h index 8760de3..067463d 100644 --- a/osctap/ip/posix/UdpSocket.h +++ b/osctap/ip/posix/UdpSocket.h @@ -35,498 +35,447 @@ requested that these non-binding requests be included whenever the above license is reproduced. */ -#include +#include #include +#include +#include +#include // for memset +#include +#include + +#include +#include +#include +#include // for sockaddr_in #include -#include -#include +#include #include -#include -#include +#include +#include #include #include -#include // for sockaddr_in +#include +#include -#include -#include -#include -#include +#include "ip/AbstractUdpSocket.h" +#include "ip/PacketListener.h" +#include "ip/TimerListener.h" -#include -#include -#include // for memset -#include -#include -#include +namespace osctap { + + namespace posix { + + inline void SockaddrFromIpEndpointName(struct sockaddr_in& sockAddr, const IpEndpointName& endpoint) { + std::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); + } -#include -#include - -namespace osctap -{ - -namespace posix -{ - -inline void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint ) -{ - std::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 ); -} - -inline 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 UdpSocketImplementation{ - bool isBound_{}; - bool isConnected_{}; - - int socket_{}; - struct sockaddr_in connectedAddr_; - struct sockaddr_in sendToAddr_; - int localPort_{}; - -public: - - UdpSocketImplementation() - : 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"); + inline 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 UdpSocketImplementation { + bool isBound_{}; + bool isConnected_{}; + + int socket_{}; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + int localPort_{}; + + public: + UdpSocketImplementation() + : 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"); + } + #ifdef SO_NOSIGPIPE - // macOS / BSD: a send() to an unreachable destination (e.g. an unrouted - // multicast group) raises SIGPIPE and would kill the process. Suppress it - // so send() returns an error instead, mirroring the TCP backend. - int noSigpipe = 1; - setsockopt( socket_, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe) ); + // macOS / BSD: a send() to an unreachable destination (e.g. an unrouted + // multicast group) raises SIGPIPE and would kill the process. Suppress it + // so send() returns an error instead, mirroring the TCP backend. + int noSigpipe = 1; + setsockopt(socket_, SOL_SOCKET, SO_NOSIGPIPE, &noSigpipe, sizeof(noSigpipe)); #endif - std::memset( &sendToAddr_, 0, sizeof(sendToAddr_) ); - sendToAddr_.sin_family = AF_INET; - } + std::memset(&sendToAddr_, 0, sizeof(sendToAddr_)); + sendToAddr_.sin_family = AF_INET; + } - ~UdpSocketImplementation() - { - if (socket_ != -1) close(socket_); - } + ~UdpSocketImplementation() { + if (socket_ != -1) + close(socket_); + } - void SetEnableBroadcast( bool enableBroadcast ) - { - int broadcast = (enableBroadcast) ? 1 : 0; // int on posix - setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); - } + void SetEnableBroadcast(bool enableBroadcast) { + int broadcast = (enableBroadcast) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); + } - void SetAllowReuse( bool allowReuse ) - { - int reuseAddr = (allowReuse) ? 1 : 0; // int on posix - setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); + void SetAllowReuse(bool allowReuse) { + int reuseAddr = (allowReuse) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); #ifdef __APPLE__ - // needed also for OS X - enable multiple listeners for a single port on same network interface - int reusePort = (allowReuse) ? 1 : 0; // int on posix - setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort)); + // needed also for OS X - enable multiple listeners for a single port on same network interface + int reusePort = (allowReuse) ? 1 : 0; // int on posix + setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &reusePort, sizeof(reusePort)); #endif - } - - void JoinMulticastGroup( const IpEndpointName& multicastGroup ) - { - struct ip_mreq mreq; - std::memset( &mreq, 0, sizeof(mreq) ); - mreq.imr_multiaddr.s_addr = htonl( multicastGroup.address ); - mreq.imr_interface.s_addr = INADDR_ANY; // default interface - if( setsockopt( socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq) ) < 0 ) - throw std::runtime_error( "unable to join multicast group\n" ); - } - - void LeaveMulticastGroup( const IpEndpointName& multicastGroup ) - { - struct ip_mreq mreq; - std::memset( &mreq, 0, sizeof(mreq) ); - mreq.imr_multiaddr.s_addr = htonl( multicastGroup.address ); - mreq.imr_interface.s_addr = INADDR_ANY; - if( setsockopt( socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq) ) < 0 ) - throw std::runtime_error( "unable to leave multicast group\n" ); - } - - 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 + void JoinMulticastGroup(const IpEndpointName& multicastGroup) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = htonl(multicastGroup.address); + mreq.imr_interface.s_addr = INADDR_ANY; // default interface + if (setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + throw std::runtime_error("unable to join multicast group\n"); + } - struct sockaddr_in sockAddr; - std::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"); - } + void LeaveMulticastGroup(const IpEndpointName& multicastGroup) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = htonl(multicastGroup.address); + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + throw std::runtime_error("unable to leave multicast group\n"); + } + + 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; + std::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; + std::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( 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"); + } + sockaddr_in local_sock; + socklen_t len = sizeof(local_sock); + getsockname(socket_, (struct sockaddr*)&local_sock, &len); + if (len == sizeof(local_sock)) + localPort_ = ntohs(local_sock.sin_port); - if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { - throw std::runtime_error("unable to connect udp socket\n"); + isConnected_ = true; } - }else{ - // unconnect from the remote address + int LocalPort() const { return localPort_; } + + void Send(const char* data, std::size_t size) { + assert(isConnected_); - struct sockaddr_in unconnectSockAddr; - std::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"); + send(socket_, data, size, 0); } - } - return IpEndpointNameFromSockaddr( sockAddr ); - } + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + sendToAddr_.sin_addr.s_addr = htonl(remoteEndpoint.address); + sendToAddr_.sin_port = htons(remoteEndpoint.port); - void Connect( const IpEndpointName& remoteEndpoint ) - { - SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint ); + sendto(socket_, data, size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_)); + } - if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { - throw std::runtime_error("unable to connect udp socket\n"); - } - sockaddr_in local_sock; - socklen_t len = sizeof(local_sock); - getsockname(socket_, (struct sockaddr *) &local_sock, &len); - if(len == sizeof(local_sock)) - localPort_ = ntohs(local_sock.sin_port); - - isConnected_ = true; - } - - int LocalPort() const - { - return localPort_; - } - - void Send( const char *data, std::size_t size ) - { - assert( isConnected_ ); - - send( socket_, data, size, 0 ); - } - - void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t 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"); - } + void Bind(const IpEndpointName& localEndpoint) { + struct sockaddr_in bindSockAddr; + SockaddrFromIpEndpointName(bindSockAddr, localEndpoint); - isBound_ = true; - - // Read back the actual local port. When the caller binds to port 0 the OS - // assigns one; without this LocalPort() would still report 0 (so a sender - // using LocalPort() would target port 0 and nothing would be delivered). - struct sockaddr_in boundAddr; - socklen_t boundLen = sizeof(boundAddr); - if( getsockname( socket_, (struct sockaddr *)&boundAddr, &boundLen ) == 0 ) - localPort_ = ntohs( boundAddr.sin_port ); - } - - bool IsBound() const { return isBound_; } - - std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size ) - { - assert( isBound_ ); - - struct sockaddr_in fromAddr; - socklen_t fromAddrLen = sizeof(fromAddr); - - ssize_t 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 (std::size_t)result; - } - - int Socket() { return socket_; } -}; - - - -struct AttachedTimerListener{ - AttachedTimerListener( int id, int p, TimerListener *tl ) - : initialDelayMs( id ) - , periodMs( p ) - , listener( tl ) {} - int initialDelayMs; - int periodMs; - TimerListener *listener; -}; - - -inline bool CompareScheduledTimerCalls( - const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs ) -{ - return lhs.first < rhs.first; -} - -template -class SocketReceiveMultiplexerImplementation -{ - std::vector< std::pair< PacketListener*, UdpSocket_T* > > socketListeners_; - std::vector< AttachedTimerListener > timerListeners_; - - std::atomic_bool break_; - int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer - - double GetCurrentTimeMs() const - { - using namespace std::chrono; - using clk = steady_clock; - return duration_cast(clk::now().time_since_epoch()).count(); - } - -public: - SocketReceiveMultiplexerImplementation() - { - if( pipe(breakPipe_) != 0 ) - throw std::runtime_error( "creation of asynchronous break pipes failed\n" ); - } - - ~SocketReceiveMultiplexerImplementation() - { - close( breakPipe_[0] ); - close( breakPipe_[1] ); - } - - void AttachSocketListener( UdpSocket_T *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_T *socket, PacketListener *listener ) - { - auto 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; - } + if (::bind(socket_, (struct sockaddr*)&bindSockAddr, sizeof(bindSockAddr)) < 0) { + throw std::runtime_error("unable to bind udp socket\n"); + } - assert( i != timerListeners_.end() ); + isBound_ = true; - timerListeners_.erase( i ); - } + // Read back the actual local port. When the caller binds to port 0 the OS + // assigns one; without this LocalPort() would still report 0 (so a sender + // using LocalPort() would target port 0 and nothing would be delivered). + struct sockaddr_in boundAddr; + socklen_t boundLen = sizeof(boundAddr); + if (getsockname(socket_, (struct sockaddr*)&boundAddr, &boundLen) == 0) + localPort_ = ntohs(boundAddr.sin_port); + } - void Run() - { - break_ = false; - char *data = 0; + bool IsBound() const { return isBound_; } - try{ + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + assert(isBound_); - // configure the master fd_set for select() + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); - fd_set masterfds, tempfds; - FD_ZERO( &masterfds ); - FD_ZERO( &tempfds ); + ssize_t result = + recvfrom(socket_, data, size, 0, (struct sockaddr*)&fromAddr, (socklen_t*)&fromAddrLen); + if (result < 0) + return 0; - // 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]; + remoteEndpoint.address = ntohl(fromAddr.sin_addr.s_addr); + remoteEndpoint.port = ntohs(fromAddr.sin_port); - for( auto i = socketListeners_.begin(); - i != socketListeners_.end(); ++i ){ + return (std::size_t)result; + } + + int Socket() { return socket_; } + }; + + struct AttachedTimerListener { + AttachedTimerListener(int id, int p, TimerListener* tl) + : initialDelayMs(id) + , periodMs(p) + , listener(tl) {} + int initialDelayMs; + int periodMs; + TimerListener* listener; + }; + + inline bool CompareScheduledTimerCalls(const std::pair& lhs, + const std::pair& rhs) { + return lhs.first < rhs.first; + } + + template + class SocketReceiveMultiplexerImplementation { + std::vector> socketListeners_; + std::vector timerListeners_; - if( fdmax < i->second->Socket() ) - fdmax = i->second->Socket(); - FD_SET( i->second->Socket(), &masterfds ); + std::atomic_bool break_; + int breakPipe_[2]; // [0] is the reader descriptor and [1] the writer + + double GetCurrentTimeMs() const { + using namespace std::chrono; + using clk = steady_clock; + return duration_cast(clk::now().time_since_epoch()).count(); } + public: + SocketReceiveMultiplexerImplementation() { + if (pipe(breakPipe_) != 0) + throw std::runtime_error("creation of asynchronous break pipes failed\n"); + } - // configure the timer queue - double currentTimeMs = GetCurrentTimeMs(); + ~SocketReceiveMultiplexerImplementation() { + close(breakPipe_[0]); + close(breakPipe_[1]); + } - // expiry time ms, listener - std::vector< std::pair< double, AttachedTimerListener > > timerQueue_; - for( auto i = timerListeners_.begin(); - i != timerListeners_.end(); ++i ) - timerQueue_.push_back( std::make_pair( currentTimeMs + i->initialDelayMs, *i ) ); - std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + void AttachSocketListener(UdpSocket_T* 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)); + } - const int MAX_BUFFER_SIZE = 4098; - data = new char[ MAX_BUFFER_SIZE ]; - IpEndpointName remoteEndpoint; + void DetachSocketListener(UdpSocket_T* socket, PacketListener* listener) { + auto i = std::find(socketListeners_.begin(), socketListeners_.end(), std::make_pair(listener, socket)); + assert(i != socketListeners_.end()); - struct timeval timeout; + socketListeners_.erase(i); + } - while( !break_ ){ - tempfds = masterfds; + void AttachPeriodicTimerListener(int periodMilliseconds, TimerListener* listener) { + timerListeners_.push_back(AttachedTimerListener(periodMilliseconds, periodMilliseconds, listener)); + } - struct timeval *timeoutPtr = 0; - if( !timerQueue_.empty() ){ - double timeoutMs = timerQueue_.front().first - GetCurrentTimeMs(); - if( timeoutMs < 0 ) - timeoutMs = 0; + void AttachPeriodicTimerListener(int initialDelayMilliseconds, int periodMilliseconds, + TimerListener* listener) { + timerListeners_.push_back( + AttachedTimerListener(initialDelayMilliseconds, periodMilliseconds, listener)); + } - long timoutSecondsPart = (long)(timeoutMs * .001); - timeout.tv_sec = (time_t)timoutSecondsPart; - // 1000000 microseconds in a second - timeout.tv_usec = (suseconds_t)((timeoutMs - (timoutSecondsPart * 1000)) * 1000); - timeoutPtr = &timeout; + void DetachPeriodicTimerListener(TimerListener* listener) { + std::vector::iterator i = timerListeners_.begin(); + while (i != timerListeners_.end()) { + if (i->listener == listener) + break; + ++i; } - if( select( fdmax + 1, &tempfds, 0, 0, timeoutPtr ) < 0 ){ - if( break_ ){ - break; - }else if( errno == EINTR ){ - // on returning an error, select() doesn't clear tempfds. - // so tempfds would remain all set, which would cause read( breakPipe_[0]... - // below to block indefinitely. therefore if select returns EINTR we restart - // the while() loop instead of continuing on to below. - continue; - }else{ - throw std::runtime_error("select failed\n"); + assert(i != timerListeners_.end()); + + timerListeners_.erase(i); + } + + void Run() { + break_ = false; + char* data = 0; + + try { + // 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 (auto i = socketListeners_.begin(); i != socketListeners_.end(); ++i) { + if (fdmax < i->second->Socket()) + fdmax = i->second->Socket(); + FD_SET(i->second->Socket(), &masterfds); } - } - if( FD_ISSET( breakPipe_[0], &tempfds ) ){ - // clear pending data from the asynchronous break pipe - char c; - read( breakPipe_[0], &c, 1 ); - } + // configure the timer queue + double currentTimeMs = GetCurrentTimeMs(); + + // expiry time ms, listener + std::vector> timerQueue_; + for (auto 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; + 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; + + long timoutSecondsPart = (long)(timeoutMs * .001); + timeout.tv_sec = (time_t)timoutSecondsPart; + // 1000000 microseconds in a second + timeout.tv_usec = (suseconds_t)((timeoutMs - (timoutSecondsPart * 1000)) * 1000); + timeoutPtr = &timeout; + } - if( break_ ) - break; + if (select(fdmax + 1, &tempfds, 0, 0, timeoutPtr) < 0) { + if (break_) { + break; + } + else if (errno == EINTR) { + // on returning an error, select() doesn't clear tempfds. + // so tempfds would remain all set, which would cause read( breakPipe_[0]... + // below to block indefinitely. therefore if select returns EINTR we restart + // the while() loop instead of continuing on to below. + continue; + } + else { + throw std::runtime_error("select failed\n"); + } + } - for( auto i = socketListeners_.begin(); - i != socketListeners_.end(); ++i ){ + if (FD_ISSET(breakPipe_[0], &tempfds)) { + // clear pending data from the asynchronous break pipe + char c; + read(breakPipe_[0], &c, 1); + } - if( FD_ISSET( i->second->Socket(), &tempfds ) ){ + if (break_) + break; - std::size_t size = i->second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE ); + for (auto i = socketListeners_.begin(); i != socketListeners_.end(); ++i) { + if (FD_ISSET(i->second->Socket(), &tempfds)) { + std::size_t size = i->second->ReceiveFrom(remoteEndpoint, data, MAX_BUFFER_SIZE); - if( size > 0 ){ - i->first->ProcessPacket( data, (int)size, remoteEndpoint ); - if( break_ ) + if (size > 0) { + i->first->ProcessPacket(data, (int)size, remoteEndpoint); + if (break_) + break; + } + } + } + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for (std::vector>::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; + } + catch (...) { + if (data) + delete[] data; + throw; } + } - // 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 ){ + void Break() { break_ = true; } - i->second.listener->TimerExpired(); - if( break_ ) - break; + void AsynchronousBreak() { + break_ = true; - i->first += i->second.periodMs; - resort = true; - } - if( resort ) - std::sort( timerQueue_.begin(), timerQueue_.end(), CompareScheduledTimerCalls ); + // Send a termination message to the asynchronous break pipe, so select() will return + write(breakPipe_[1], "!", 1); } + }; - delete [] data; - }catch(...){ - if( data ) - delete [] data; - throw; - } - } - - 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 ); - } -}; - -struct Implementation -{ - using udp_socket_t = osctap::posix::UdpSocketImplementation; - using socket_multiplexer_t = osctap::posix::SocketReceiveMultiplexerImplementation; -}; -} - -} + struct Implementation { + using udp_socket_t = osctap::posix::UdpSocketImplementation; + using socket_multiplexer_t = osctap::posix::SocketReceiveMultiplexerImplementation; + }; + } // namespace posix + +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/win32/NetworkingUtils.h b/osctap/ip/win32/NetworkingUtils.h index b0a296b..2137074 100644 --- a/osctap/ip/win32/NetworkingUtils.h +++ b/osctap/ip/win32/NetworkingUtils.h @@ -34,63 +34,59 @@ requested that these non-binding requests be included whenever the above license is reproduced. */ -#include +#include "ip/NetworkingUtils.h" +// clang-format off +// Winsock ordering is load-bearing: winsock2.h must precede windows.h. +// Guarded so include-regroup can't alphabetize (windows.h < winsock2.h). #include // this must come first to prevent errors with MSVC7 #include // getaddrinfo / freeaddrinfo #include +// clang-format on #include -namespace osctap -{ -class NetworkInitializer -{ - public: - static const NetworkInitializer& instance() - { - static const NetworkInitializer ne; - return ne; +namespace osctap { + class NetworkInitializer { + public: + static const NetworkInitializer& instance() { + static const NetworkInitializer ne; + return ne; + } + + private: + NetworkInitializer() { + WSAData wsaData; + WSAStartup(MAKEWORD(1, 1), &wsaData); + } + + ~NetworkInitializer() { WSACleanup(); } + }; + + inline unsigned long GetHostByName(const char* name) { + NetworkInitializer::instance(); + + unsigned long result = 0; + + // getaddrinfo replaces the deprecated gethostbyname (MSVC C4996); mirrors the + // posix backend. + struct addrinfo hints; + std::memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + + struct addrinfo* ai = nullptr; + if (getaddrinfo(name, nullptr, &hints, &ai) == 0 && ai) { + auto* remote = reinterpret_cast(ai->ai_addr); + result = ntohl(remote->sin_addr.s_addr); + } + if (ai) + freeaddrinfo(ai); + + return result; } - - private: - NetworkInitializer() - { - WSAData wsaData; - WSAStartup(MAKEWORD(1, 1), &wsaData); - } - - ~NetworkInitializer() - { - WSACleanup(); - } -}; - -inline unsigned long GetHostByName( const char *name ) -{ - NetworkInitializer::instance(); - - unsigned long result = 0; - - // getaddrinfo replaces the deprecated gethostbyname (MSVC C4996); mirrors the - // posix backend. - struct addrinfo hints; - std::memset( &hints, 0, sizeof(hints) ); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_DGRAM; - hints.ai_protocol = IPPROTO_UDP; - - struct addrinfo *ai = nullptr; - if( getaddrinfo( name, nullptr, &hints, &ai ) == 0 && ai ){ - auto *remote = reinterpret_cast( ai->ai_addr ); - result = ntohl( remote->sin_addr.s_addr ); - } - if( ai ) - freeaddrinfo( ai ); - - return result; -} -} +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/ip/win32/TcpSocket.h b/osctap/ip/win32/TcpSocket.h index 1393333..a010c7d 100644 --- a/osctap/ip/win32/TcpSocket.h +++ b/osctap/ip/win32/TcpSocket.h @@ -29,282 +29,280 @@ // Reuse the win32 socket includes, NetworkInitializer (WSAStartup), and the // SockaddrFromIpEndpointName / IpEndpointNameFromSockaddr helpers from the UDP // backend. -#include // complete type before the helpers below use it -#include -#include -#include - -#include // TCP_NODELAY, IPPROTO_TCP #include #include -namespace osctap -{ -namespace win32 -{ - -// Mirrors ip/posix/TcpSocket.h on Winsock. The connection-aware server uses -// select() (Winsock supports select() over SOCKETs) with a self-connected UDP -// "break" socket standing in for the posix self-pipe, so AsynchronousBreak() -// works from another thread / signal handler. -// -// NOTE: this backend is built by the windows-latest CI legs and cross-compiled + -// link-checked with MinGW, but -- unlike the posix backend -- it is not yet -// runtime-tested in CI (no Windows runner). The posix backend is the -// runtime-verified reference. - -class TcpTransmitSocket -{ -public: - explicit TcpTransmitSocket( const IpEndpointName& remoteEndpoint ) - { - NetworkInitializer::instance(); - - socket_ = ::socket( AF_INET, SOCK_STREAM, 0 ); - if( socket_ == INVALID_SOCKET ) - throw std::runtime_error( "unable to create tcp socket\n" ); - - int one = 1; - setsockopt( socket_, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, sizeof(one) ); - - struct sockaddr_in addr; - SockaddrFromIpEndpointName( addr, remoteEndpoint ); - if( ::connect( socket_, (struct sockaddr*)&addr, sizeof(addr) ) == SOCKET_ERROR ){ - closesocket( socket_ ); - socket_ = INVALID_SOCKET; - throw std::runtime_error( "unable to connect tcp socket\n" ); - } - } - - ~TcpTransmitSocket() { if( socket_ != INVALID_SOCKET ) closesocket( socket_ ); } - - TcpTransmitSocket( const TcpTransmitSocket& ) = delete; - TcpTransmitSocket& operator=( const TcpTransmitSocket& ) = delete; - - void Send( const char* data, std::size_t size ) - { - char header[OSC_STREAM_FRAME_HEADER_SIZE]; - WriteOscStreamFrameHeader( header, (uint32_t)size ); - SendAll( header, OSC_STREAM_FRAME_HEADER_SIZE ); - SendAll( data, size ); - } - - SOCKET Socket() const { return socket_; } - -private: - void SendAll( const char* p, std::size_t n ) - { - std::size_t sent = 0; - while( sent < n ){ - int r = ::send( socket_, p + sent, (int)( n - sent ), 0 ); - if( r == SOCKET_ERROR ) - throw std::runtime_error( "tcp send failed\n" ); - sent += (std::size_t)r; - } - } - - SOCKET socket_ = INVALID_SOCKET; -}; - - -class TcpListeningReceiveSocket -{ - struct Connection - { - IpEndpointName peer; - OscStreamDeframer deframer; - Connection( const IpEndpointName& p, uint32_t maxFrame ) - : peer( p ), deframer( maxFrame ) {} - }; - -public: - TcpListeningReceiveSocket( const IpEndpointName& localEndpoint, PacketListener* listener, - uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE ) - : listener_( listener ), maxFrameSize_( maxFrameSize ) - { - NetworkInitializer::instance(); - CreateBreakSocket(); - - listenSocket_ = ::socket( AF_INET, SOCK_STREAM, 0 ); - if( listenSocket_ == INVALID_SOCKET ){ - closesocket( breakSocket_ ); - throw std::runtime_error( "unable to create tcp socket\n" ); - } - - int reuse = 1; - setsockopt( listenSocket_, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse) ); - - struct sockaddr_in addr; - SockaddrFromIpEndpointName( addr, localEndpoint ); - if( ::bind( listenSocket_, (struct sockaddr*)&addr, sizeof(addr) ) == SOCKET_ERROR ){ - Cleanup(); - throw std::runtime_error( "unable to bind tcp socket\n" ); - } - if( ::listen( listenSocket_, SOMAXCONN ) == SOCKET_ERROR ){ - Cleanup(); - throw std::runtime_error( "unable to listen on tcp socket\n" ); - } - } - - ~TcpListeningReceiveSocket() { Cleanup(); } - - TcpListeningReceiveSocket( const TcpListeningReceiveSocket& ) = delete; - TcpListeningReceiveSocket& operator=( const TcpListeningReceiveSocket& ) = delete; - - IpEndpointName LocalEndpointFor( const IpEndpointName& requested ) const - { - struct sockaddr_in addr; - socklen_t len = sizeof(addr); - if( getsockname( listenSocket_, (struct sockaddr*)&addr, &len ) == 0 ) - return IpEndpointName( requested.address, ntohs( addr.sin_port ) ); - return requested; - } - - void Run() - { - break_ = false; - char buf[4096]; - - while( !break_ ){ - fd_set readfds; - FD_ZERO( &readfds ); - FD_SET( listenSocket_, &readfds ); - FD_SET( breakSocket_, &readfds ); - for( const auto& kv : connections_ ) - FD_SET( kv.first, &readfds ); - - if( select( 0, &readfds, 0, 0, 0 ) == SOCKET_ERROR ){ - if( break_ ) break; - throw std::runtime_error( "select failed\n" ); +#include // TCP_NODELAY, IPPROTO_TCP + +#include "ip/IpEndpointName.h" // complete type before the helpers below use it +#include "ip/PacketListener.h" +#include "ip/win32/UdpSocket.h" +#include "osc/OscStreamFraming.h" + +namespace osctap { + namespace win32 { + + // Mirrors ip/posix/TcpSocket.h on Winsock. The connection-aware server uses + // select() (Winsock supports select() over SOCKETs) with a self-connected UDP + // "break" socket standing in for the posix self-pipe, so AsynchronousBreak() + // works from another thread / signal handler. + // + // NOTE: this backend is built by the windows-latest CI legs and cross-compiled + + // link-checked with MinGW, but -- unlike the posix backend -- it is not yet + // runtime-tested in CI (no Windows runner). The posix backend is the + // runtime-verified reference. + + class TcpTransmitSocket { + public: + explicit TcpTransmitSocket(const IpEndpointName& remoteEndpoint) { + NetworkInitializer::instance(); + + socket_ = ::socket(AF_INET, SOCK_STREAM, 0); + if (socket_ == INVALID_SOCKET) + throw std::runtime_error("unable to create tcp socket\n"); + + int one = 1; + setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, sizeof(one)); + + struct sockaddr_in addr; + SockaddrFromIpEndpointName(addr, remoteEndpoint); + if (::connect(socket_, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(socket_); + socket_ = INVALID_SOCKET; + throw std::runtime_error("unable to connect tcp socket\n"); + } + } + + ~TcpTransmitSocket() { + if (socket_ != INVALID_SOCKET) + closesocket(socket_); + } + + TcpTransmitSocket(const TcpTransmitSocket&) = delete; + TcpTransmitSocket& operator=(const TcpTransmitSocket&) = delete; + + void Send(const char* data, std::size_t size) { + char header[OSC_STREAM_FRAME_HEADER_SIZE]; + WriteOscStreamFrameHeader(header, (uint32_t)size); + SendAll(header, OSC_STREAM_FRAME_HEADER_SIZE); + SendAll(data, size); + } + + SOCKET Socket() const { return socket_; } + + private: + void SendAll(const char* p, std::size_t n) { + std::size_t sent = 0; + while (sent < n) { + int r = ::send(socket_, p + sent, (int)(n - sent), 0); + if (r == SOCKET_ERROR) + throw std::runtime_error("tcp send failed\n"); + sent += (std::size_t)r; + } + } + + SOCKET socket_ = INVALID_SOCKET; + }; + + class TcpListeningReceiveSocket { + struct Connection { + IpEndpointName peer; + OscStreamDeframer deframer; + Connection(const IpEndpointName& p, uint32_t maxFrame) + : peer(p) + , deframer(maxFrame) {} + }; + + public: + TcpListeningReceiveSocket(const IpEndpointName& localEndpoint, PacketListener* listener, + uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE) + : listener_(listener) + , maxFrameSize_(maxFrameSize) { + NetworkInitializer::instance(); + CreateBreakSocket(); + + listenSocket_ = ::socket(AF_INET, SOCK_STREAM, 0); + if (listenSocket_ == INVALID_SOCKET) { + closesocket(breakSocket_); + throw std::runtime_error("unable to create tcp socket\n"); + } + + int reuse = 1; + setsockopt(listenSocket_, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)); + + struct sockaddr_in addr; + SockaddrFromIpEndpointName(addr, localEndpoint); + if (::bind(listenSocket_, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + Cleanup(); + throw std::runtime_error("unable to bind tcp socket\n"); + } + if (::listen(listenSocket_, SOMAXCONN) == SOCKET_ERROR) { + Cleanup(); + throw std::runtime_error("unable to listen on tcp socket\n"); + } } - if( FD_ISSET( breakSocket_, &readfds ) ){ - char c; recv( breakSocket_, &c, 1, 0 ); + ~TcpListeningReceiveSocket() { Cleanup(); } + + TcpListeningReceiveSocket(const TcpListeningReceiveSocket&) = delete; + TcpListeningReceiveSocket& operator=(const TcpListeningReceiveSocket&) = delete; + + IpEndpointName LocalEndpointFor(const IpEndpointName& requested) const { + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + if (getsockname(listenSocket_, (struct sockaddr*)&addr, &len) == 0) + return IpEndpointName(requested.address, ntohs(addr.sin_port)); + return requested; } - if( break_ ) break; - if( FD_ISSET( listenSocket_, &readfds ) ) - AcceptConnection(); + void Run() { + break_ = false; + char buf[4096]; + + while (!break_) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(listenSocket_, &readfds); + FD_SET(breakSocket_, &readfds); + for (const auto& kv : connections_) + FD_SET(kv.first, &readfds); + + if (select(0, &readfds, 0, 0, 0) == SOCKET_ERROR) { + if (break_) + break; + throw std::runtime_error("select failed\n"); + } + + if (FD_ISSET(breakSocket_, &readfds)) { + char c; + recv(breakSocket_, &c, 1, 0); + } + if (break_) + break; + + if (FD_ISSET(listenSocket_, &readfds)) + AcceptConnection(); + + std::vector ready; + for (const auto& kv : connections_) + if (FD_ISSET(kv.first, &readfds)) + ready.push_back(kv.first); + + for (SOCKET fd : ready) { + ServiceConnection(fd, buf, sizeof(buf)); + if (break_) + break; + } + } + } - std::vector ready; - for( const auto& kv : connections_ ) - if( FD_ISSET( kv.first, &readfds ) ) ready.push_back( kv.first ); + void Break() { break_ = true; } - for( SOCKET fd : ready ){ - ServiceConnection( fd, buf, sizeof(buf) ); - if( break_ ) break; + void AsynchronousBreak() { + break_ = true; + send(breakSocket_, "!", 1, 0); // wake select() } - } - } - - void Break() { break_ = true; } - - void AsynchronousBreak() - { - break_ = true; - send( breakSocket_, "!", 1, 0 ); // wake select() - } - - SOCKET Socket() const { return listenSocket_; } - -private: - void CreateBreakSocket() - { - // A loopback UDP socket connected to itself: writing a byte to it wakes the - // select() loop (the Winsock analogue of the posix self-pipe). - breakSocket_ = ::socket( AF_INET, SOCK_DGRAM, 0 ); - if( breakSocket_ == INVALID_SOCKET ) - throw std::runtime_error( "creation of asynchronous break socket failed\n" ); - - struct sockaddr_in addr; - std::memset( &addr, 0, sizeof(addr) ); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl( INADDR_LOOPBACK ); - addr.sin_port = 0; - - // bind -> read back the assigned port -> connect to self. If any step - // fails, AsynchronousBreak() could never wake Run() (it would hang in - // select()), so treat it as fatal. - socklen_t len = sizeof(addr); - if( bind( breakSocket_, (struct sockaddr*)&addr, sizeof(addr) ) == SOCKET_ERROR - || getsockname( breakSocket_, (struct sockaddr*)&addr, &len ) == SOCKET_ERROR - || connect( breakSocket_, (struct sockaddr*)&addr, sizeof(addr) ) == SOCKET_ERROR ){ - closesocket( breakSocket_ ); - breakSocket_ = INVALID_SOCKET; - throw std::runtime_error( "setup of asynchronous break socket failed\n" ); - } - } - - void AcceptConnection() - { - struct sockaddr_in peerAddr; - socklen_t len = sizeof(peerAddr); - SOCKET conn = ::accept( listenSocket_, (struct sockaddr*)&peerAddr, &len ); - if( conn == INVALID_SOCKET ) - return; - - // Winsock select() works over an fd_set array bounded by FD_SETSIZE - // (default 64). Beyond it, FD_SET silently drops sockets and those - // connections would stall forever -- so refuse new connections at the - // limit instead. (v1 targets a handful of connections; high connection - // counts are a future poll/epoll concern -- see issue #14.) - if( connections_.size() + 2 >= FD_SETSIZE ){ - closesocket( conn ); - return; - } - - int one = 1; - setsockopt( conn, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, sizeof(one) ); - connections_.emplace( std::piecewise_construct, - std::forward_as_tuple( conn ), - std::forward_as_tuple( IpEndpointNameFromSockaddr( peerAddr ), maxFrameSize_ ) ); - } - - void ServiceConnection( SOCKET fd, char* buf, std::size_t bufSize ) - { - auto it = connections_.find( fd ); - if( it == connections_.end() ) - return; - - int n = ::recv( fd, buf, (int)bufSize, 0 ); - if( n <= 0 ){ // 0 = peer closed; SOCKET_ERROR -> drop - CloseConnection( it ); - return; - } - - const bool ok = it->second.deframer.Consume( buf, (std::size_t)n, - [&]( const char* packet, uint32_t size ){ - listener_->ProcessPacket( packet, (int)size, it->second.peer ); - } ); - - if( !ok ) - CloseConnection( it ); - } - - void CloseConnection( std::map::iterator it ) - { - closesocket( it->first ); - connections_.erase( it ); - } - - void Cleanup() - { - for( auto& kv : connections_ ) - closesocket( kv.first ); - connections_.clear(); - if( listenSocket_ != INVALID_SOCKET ){ closesocket( listenSocket_ ); listenSocket_ = INVALID_SOCKET; } - if( breakSocket_ != INVALID_SOCKET ){ closesocket( breakSocket_ ); breakSocket_ = INVALID_SOCKET; } - } - - SOCKET listenSocket_ = INVALID_SOCKET; - SOCKET breakSocket_ = INVALID_SOCKET; - PacketListener* listener_; - uint32_t maxFrameSize_; - std::atomic_bool break_{ false }; - std::map connections_; -}; - -} // namespace win32 + + SOCKET Socket() const { return listenSocket_; } + + private: + void CreateBreakSocket() { + // A loopback UDP socket connected to itself: writing a byte to it wakes the + // select() loop (the Winsock analogue of the posix self-pipe). + breakSocket_ = ::socket(AF_INET, SOCK_DGRAM, 0); + if (breakSocket_ == INVALID_SOCKET) + throw std::runtime_error("creation of asynchronous break socket failed\n"); + + struct sockaddr_in addr; + std::memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + // bind -> read back the assigned port -> connect to self. If any step + // fails, AsynchronousBreak() could never wake Run() (it would hang in + // select()), so treat it as fatal. + socklen_t len = sizeof(addr); + if (bind(breakSocket_, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR + || getsockname(breakSocket_, (struct sockaddr*)&addr, &len) == SOCKET_ERROR + || connect(breakSocket_, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(breakSocket_); + breakSocket_ = INVALID_SOCKET; + throw std::runtime_error("setup of asynchronous break socket failed\n"); + } + } + + void AcceptConnection() { + struct sockaddr_in peerAddr; + socklen_t len = sizeof(peerAddr); + SOCKET conn = ::accept(listenSocket_, (struct sockaddr*)&peerAddr, &len); + if (conn == INVALID_SOCKET) + return; + + // Winsock select() works over an fd_set array bounded by FD_SETSIZE + // (default 64). Beyond it, FD_SET silently drops sockets and those + // connections would stall forever -- so refuse new connections at the + // limit instead. (v1 targets a handful of connections; high connection + // counts are a future poll/epoll concern -- see issue #14.) + if (connections_.size() + 2 >= FD_SETSIZE) { + closesocket(conn); + return; + } + + int one = 1; + setsockopt(conn, IPPROTO_TCP, TCP_NODELAY, (const char*)&one, sizeof(one)); + connections_.emplace(std::piecewise_construct, std::forward_as_tuple(conn), + std::forward_as_tuple(IpEndpointNameFromSockaddr(peerAddr), maxFrameSize_)); + } + + void ServiceConnection(SOCKET fd, char* buf, std::size_t bufSize) { + auto it = connections_.find(fd); + if (it == connections_.end()) + return; + + int n = ::recv(fd, buf, (int)bufSize, 0); + if (n <= 0) { // 0 = peer closed; SOCKET_ERROR -> drop + CloseConnection(it); + return; + } + + const bool ok = + it->second.deframer.Consume(buf, (std::size_t)n, [&](const char* packet, uint32_t size) { + listener_->ProcessPacket(packet, (int)size, it->second.peer); + }); + + if (!ok) + CloseConnection(it); + } + + void CloseConnection(std::map::iterator it) { + closesocket(it->first); + connections_.erase(it); + } + + void Cleanup() { + for (auto& kv : connections_) + closesocket(kv.first); + connections_.clear(); + if (listenSocket_ != INVALID_SOCKET) { + closesocket(listenSocket_); + listenSocket_ = INVALID_SOCKET; + } + if (breakSocket_ != INVALID_SOCKET) { + closesocket(breakSocket_); + breakSocket_ = INVALID_SOCKET; + } + } + + SOCKET listenSocket_ = INVALID_SOCKET; + SOCKET breakSocket_ = INVALID_SOCKET; + PacketListener* listener_; + uint32_t maxFrameSize_; + std::atomic_bool break_{false}; + std::map connections_; + }; + + } // namespace win32 } // namespace osctap #endif /* INCLUDED_OSCTAP_WIN32_TCPSOCKET_H */ diff --git a/osctap/ip/win32/UdpSocket.h b/osctap/ip/win32/UdpSocket.h index ee62614..4d85ca2 100644 --- a/osctap/ip/win32/UdpSocket.h +++ b/osctap/ip/win32/UdpSocket.h @@ -38,9 +38,13 @@ #if !defined(WIN32_LEAN_AND_MEAN) #define WIN32_LEAN_AND_MEAN #endif +// clang-format off +// Winsock ordering is load-bearing: winsock2.h must precede windows.h, and +// mmsystem.h needs windows.h first. Guarded so include-regroup can't sort it. #include // this must come first to prevent errors with MSVC7 #include #include // for timeGetTime() +// clang-format on #ifndef WINCE #include @@ -48,450 +52,397 @@ #include #include -#include // steady_clock for GetCurrentTimeMs() +#include // steady_clock for GetCurrentTimeMs() #include // for memset #include #include +#include "ip/NetworkingUtils.h" +#include "ip/PacketListener.h" +#include "ip/TimerListener.h" -#include -#include -#include +typedef int socklen_t; +namespace osctap { + namespace win32 { + static void SockaddrFromIpEndpointName(struct sockaddr_in& sockAddr, const IpEndpointName& endpoint) { + std::memset((char*)&sockAddr, 0, sizeof(sockAddr)); + sockAddr.sin_family = AF_INET; -typedef int socklen_t; + sockAddr.sin_addr.s_addr = + (endpoint.address == IpEndpointName::ANY_ADDRESS) ? INADDR_ANY : htonl(endpoint.address); -namespace osctap -{ -namespace win32 -{ -static void SockaddrFromIpEndpointName( struct sockaddr_in& sockAddr, const IpEndpointName& endpoint ) -{ - std::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 UdpSocketImplementation{ - - bool isBound_; - bool isConnected_; - - SOCKET socket_; - struct sockaddr_in connectedAddr_; - struct sockaddr_in sendToAddr_; - int localPort_{}; - -public: - - UdpSocketImplementation() - : isBound_( false ) - , isConnected_( false ) - , socket_( INVALID_SOCKET ) - { - NetworkInitializer::instance(); - if( (socket_ = socket( AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET ){ - throw std::runtime_error("unable to create udp socket\n"); + sockAddr.sin_port = (endpoint.port == IpEndpointName::ANY_PORT) ? (short)0 : htons((short)endpoint.port); } - std::memset( &sendToAddr_, 0, sizeof(sendToAddr_) ); - sendToAddr_.sin_family = AF_INET; - } - - ~UdpSocketImplementation() - { - if (socket_ != INVALID_SOCKET) closesocket(socket_); - } - - void SetEnableBroadcast( bool enableBroadcast ) - { - char broadcast = (char)((enableBroadcast) ? 1 : 0); // char on win32 - setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); - } - - void SetAllowReuse( bool allowReuse ) - { - // Note: SO_REUSEADDR is non-deterministic for listening sockets on Win32. See MSDN article: - // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" - // http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx - - char reuseAddr = (char)((allowReuse) ? 1 : 0); // char on win32 - setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); - } - - void JoinMulticastGroup( const IpEndpointName& multicastGroup ) - { - struct ip_mreq mreq; - std::memset( &mreq, 0, sizeof(mreq) ); - mreq.imr_multiaddr.s_addr = htonl( multicastGroup.address ); - mreq.imr_interface.s_addr = INADDR_ANY; // default interface - if( setsockopt( socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq) ) == SOCKET_ERROR ) - throw std::runtime_error( "unable to join multicast group\n" ); - } - - void LeaveMulticastGroup( const IpEndpointName& multicastGroup ) - { - struct ip_mreq mreq; - std::memset( &mreq, 0, sizeof(mreq) ); - mreq.imr_multiaddr.s_addr = htonl( multicastGroup.address ); - mreq.imr_interface.s_addr = INADDR_ANY; - if( setsockopt( socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char*)&mreq, sizeof(mreq) ) == SOCKET_ERROR ) - throw std::runtime_error( "unable to leave multicast group\n" ); - } - - 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"); + 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)); } - // get the address + class UdpSocketImplementation { + bool isBound_; + bool isConnected_; + + SOCKET socket_; + struct sockaddr_in connectedAddr_; + struct sockaddr_in sendToAddr_; + int localPort_{}; + + public: + UdpSocketImplementation() + : isBound_(false) + , isConnected_(false) + , socket_(INVALID_SOCKET) { + NetworkInitializer::instance(); + if ((socket_ = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) { + throw std::runtime_error("unable to create udp socket\n"); + } + + std::memset(&sendToAddr_, 0, sizeof(sendToAddr_)); + sendToAddr_.sin_family = AF_INET; + } - struct sockaddr_in sockAddr; - std::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"); - } + ~UdpSocketImplementation() { + if (socket_ != INVALID_SOCKET) + closesocket(socket_); + } + + void SetEnableBroadcast(bool enableBroadcast) { + char broadcast = (char)((enableBroadcast) ? 1 : 0); // char on win32 + setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast)); + } - if( isConnected_ ){ - // reconnect to the connected address + void SetAllowReuse(bool allowReuse) { + // Note: SO_REUSEADDR is non-deterministic for listening sockets on Win32. See MSDN article: + // "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" + // http://msdn.microsoft.com/en-us/library/ms740621%28VS.85%29.aspx - if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { - throw std::runtime_error("unable to connect udp socket\n"); - } + char reuseAddr = (char)((allowReuse) ? 1 : 0); // char on win32 + setsockopt(socket_, SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof(reuseAddr)); + } - }else{ - // unconnect from the remote address + void JoinMulticastGroup(const IpEndpointName& multicastGroup) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = htonl(multicastGroup.address); + mreq.imr_interface.s_addr = INADDR_ANY; // default interface + if (setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) + == SOCKET_ERROR) + throw std::runtime_error("unable to join multicast group\n"); + } - struct sockaddr_in unconnectSockAddr; - SockaddrFromIpEndpointName( unconnectSockAddr, IpEndpointName() ); + void LeaveMulticastGroup(const IpEndpointName& multicastGroup) { + struct ip_mreq mreq; + std::memset(&mreq, 0, sizeof(mreq)); + mreq.imr_multiaddr.s_addr = htonl(multicastGroup.address); + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) + == SOCKET_ERROR) + throw std::runtime_error("unable to leave multicast group\n"); + } - if( connect(socket_, (struct sockaddr *)&unconnectSockAddr, sizeof(unconnectSockAddr)) < 0 - && WSAGetLastError() != WSAEADDRNOTAVAIL ){ - throw std::runtime_error("unable to un-connect udp socket\n"); - } - } + IpEndpointName LocalEndpointFor(const IpEndpointName& remoteEndpoint) const { + assert(isBound_); - return IpEndpointNameFromSockaddr( sockAddr ); - } + // first connect the socket to the remote server - void Connect( const IpEndpointName& remoteEndpoint ) - { - SockaddrFromIpEndpointName( connectedAddr_, remoteEndpoint ); + struct sockaddr_in connectSockAddr; + SockaddrFromIpEndpointName(connectSockAddr, remoteEndpoint); - if (connect(socket_, (struct sockaddr *)&connectedAddr_, sizeof(connectedAddr_)) < 0) { - throw std::runtime_error("unable to connect udp socket\n"); - } + if (connect(socket_, (struct sockaddr*)&connectSockAddr, sizeof(connectSockAddr)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } - sockaddr_in local_sock; - int len = sizeof(local_sock); - getsockname(socket_, (struct sockaddr *) &local_sock, &len); - if(len == sizeof(local_sock)) - localPort_ = ntohs(local_sock.sin_port); + // get the address - isConnected_ = true; - } + struct sockaddr_in sockAddr; + std::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"); + } - int LocalPort() const - { - return localPort_; - } + if (isConnected_) { + // reconnect to the connected address - void Send( const char *data, std::size_t size ) - { - assert( isConnected_ ); + 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 - send( socket_, data, (int)size, 0 ); - } + struct sockaddr_in unconnectSockAddr; + SockaddrFromIpEndpointName(unconnectSockAddr, IpEndpointName()); - void SendTo( const IpEndpointName& remoteEndpoint, const char *data, std::size_t size ) - { - sendToAddr_.sin_addr.s_addr = htonl( remoteEndpoint.address ); - sendToAddr_.sin_port = htons( (short)remoteEndpoint.port ); + if (connect(socket_, (struct sockaddr*)&unconnectSockAddr, sizeof(unconnectSockAddr)) < 0 + && WSAGetLastError() != WSAEADDRNOTAVAIL) { + throw std::runtime_error("unable to un-connect udp socket\n"); + } + } - sendto( socket_, data, (int)size, 0, (sockaddr*)&sendToAddr_, sizeof(sendToAddr_) ); - } + return IpEndpointNameFromSockaddr(sockAddr); + } - void Bind( const IpEndpointName& localEndpoint ) - { - struct sockaddr_in bindSockAddr; - SockaddrFromIpEndpointName( bindSockAddr, localEndpoint ); + void Connect(const IpEndpointName& remoteEndpoint) { + SockaddrFromIpEndpointName(connectedAddr_, remoteEndpoint); - if (::bind(socket_, (struct sockaddr *)&bindSockAddr, sizeof(bindSockAddr)) < 0) { - throw std::runtime_error("unable to bind udp socket\n"); - } + if (connect(socket_, (struct sockaddr*)&connectedAddr_, sizeof(connectedAddr_)) < 0) { + throw std::runtime_error("unable to connect udp socket\n"); + } + + sockaddr_in local_sock; + int len = sizeof(local_sock); + getsockname(socket_, (struct sockaddr*)&local_sock, &len); + if (len == sizeof(local_sock)) + localPort_ = ntohs(local_sock.sin_port); + + isConnected_ = true; + } + + int LocalPort() const { return localPort_; } + + void Send(const char* data, std::size_t size) { + assert(isConnected_); + + send(socket_, data, (int)size, 0); + } + + void SendTo(const IpEndpointName& remoteEndpoint, const char* data, std::size_t size) { + sendToAddr_.sin_addr.s_addr = htonl(remoteEndpoint.address); + sendToAddr_.sin_port = htons((short)remoteEndpoint.port); + + sendto(socket_, data, (int)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; + + // Read back the actual local port (resolves an OS-assigned port when the + // caller binds to port 0; otherwise LocalPort() would still report 0). + struct sockaddr_in boundAddr; + socklen_t boundLen = sizeof(boundAddr); + if (getsockname(socket_, (struct sockaddr*)&boundAddr, &boundLen) == 0) + localPort_ = ntohs(boundAddr.sin_port); + } + + bool IsBound() const { return isBound_; } - isBound_ = true; - - // Read back the actual local port (resolves an OS-assigned port when the - // caller binds to port 0; otherwise LocalPort() would still report 0). - struct sockaddr_in boundAddr; - socklen_t boundLen = sizeof(boundAddr); - if( getsockname( socket_, (struct sockaddr *)&boundAddr, &boundLen ) == 0 ) - localPort_ = ntohs( boundAddr.sin_port ); - } - - bool IsBound() const { return isBound_; } - - std::size_t ReceiveFrom( IpEndpointName& remoteEndpoint, char *data, std::size_t size ) - { - assert( isBound_ ); - - struct sockaddr_in fromAddr; - socklen_t fromAddrLen = sizeof(fromAddr); - - int result = recvfrom(socket_, data, (int)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_; } -}; - -struct AttachedTimerListener{ - AttachedTimerListener( int id, int p, TimerListener *tl ) - : initialDelayMs( id ) - , periodMs( p ) - , listener( tl ) {} - int initialDelayMs; - int periodMs; - TimerListener *listener; -}; - - -// inline (not static) to match the posix backend: a static function is internal -// to each TU and trips MSVC C4505 in translation units that include this header -// but never instantiate the multiplexer's timer sort (e.g. a transmit-only TU). -inline bool CompareScheduledTimerCalls( - const std::pair< double, AttachedTimerListener > & lhs, const std::pair< double, AttachedTimerListener > & rhs ) -{ - return lhs.first < rhs.first; -} - -template -class SocketReceiveMultiplexerImplementation { - - std::vector< std::pair< PacketListener*, UdpSocket_T* > > socketListeners_; - std::vector< AttachedTimerListener > timerListeners_; - - volatile bool break_; - HANDLE breakEvent_; - - double GetCurrentTimeMs() const - { - // std::chrono::steady_clock (matches the posix backend): monotonic and 64-bit, - // so unlike the old timeGetTime() it does not wrap after ~49 days. - using namespace std::chrono; - return (double)duration_cast( steady_clock::now().time_since_epoch() ).count(); - } - -public: - SocketReceiveMultiplexerImplementation() - { - NetworkInitializer::instance(); - breakEvent_ = CreateEvent( NULL, FALSE, FALSE, NULL ); - } - - ~SocketReceiveMultiplexerImplementation() - { - CloseHandle( breakEvent_ ); - } - - void AttachSocketListener(UdpSocket_T *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_T *socket, PacketListener *listener ) - { - auto 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(auto i = socketListeners_.begin(); - i != socketListeners_.end(); ++i, ++j ){ - - HANDLE event = CreateEvent( NULL, FALSE, FALSE, NULL ); - WSAEventSelect( i->second->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 ); + std::size_t ReceiveFrom(IpEndpointName& remoteEndpoint, char* data, std::size_t size) { + assert(isBound_); - const int MAX_BUFFER_SIZE = 4098; - char *data = new char[ MAX_BUFFER_SIZE ]; - IpEndpointName remoteEndpoint; - - while( !break_ ){ - - currentTimeMs = GetCurrentTimeMs(); // reuse outer (avoid MSVC C4456 shadow) - - DWORD waitTime = INFINITE; - if( !timerQueue_.empty() ){ - - waitTime = (DWORD)( timerQueue_.front().first >= currentTimeMs - ? timerQueue_.front().first - currentTimeMs - : 0 ); + struct sockaddr_in fromAddr; + socklen_t fromAddrLen = sizeof(fromAddr); + + int result = + recvfrom(socket_, data, (int)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; } - 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 ){ - std::size_t size = socketListeners_[i].second->ReceiveFrom( remoteEndpoint, data, MAX_BUFFER_SIZE ); - - if( size > 0 ){ - if (break_) - break; - socketListeners_[i].first->ProcessPacket( data, (int)size, remoteEndpoint ); - if( break_ ) - break; - } + SOCKET& Socket() { return socket_; } + }; + + struct AttachedTimerListener { + AttachedTimerListener(int id, int p, TimerListener* tl) + : initialDelayMs(id) + , periodMs(p) + , listener(tl) {} + int initialDelayMs; + int periodMs; + TimerListener* listener; + }; + + // inline (not static) to match the posix backend: a static function is internal + // to each TU and trips MSVC C4505 in translation units that include this header + // but never instantiate the multiplexer's timer sort (e.g. a transmit-only TU). + inline bool CompareScheduledTimerCalls(const std::pair& lhs, + const std::pair& rhs) { + return lhs.first < rhs.first; } - } - - 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(auto i = socketListeners_.begin(); - i != socketListeners_.end(); ++i, ++j ){ - - WSAEventSelect( i->second->Socket(), events[j], 0 ); // remove association between socket and event - CloseHandle( events[j] ); - unsigned long enableNonblocking = 0; - ioctlsocket( i->second->Socket(), FIONBIO, &enableNonblocking ); // make the socket blocking again - } - } - - void Break() - { - break_ = true; - } - - void AsynchronousBreak() - { - break_ = true; - SetEvent( breakEvent_ ); - } -}; - -struct Implementation -{ - using udp_socket_t = osctap::win32::UdpSocketImplementation; - using socket_multiplexer_t = osctap::win32::SocketReceiveMultiplexerImplementation; -}; -} -} + + template + class SocketReceiveMultiplexerImplementation { + std::vector> socketListeners_; + std::vector timerListeners_; + + volatile bool break_; + HANDLE breakEvent_; + + double GetCurrentTimeMs() const { + // std::chrono::steady_clock (matches the posix backend): monotonic and 64-bit, + // so unlike the old timeGetTime() it does not wrap after ~49 days. + using namespace std::chrono; + return (double)duration_cast(steady_clock::now().time_since_epoch()).count(); + } + + public: + SocketReceiveMultiplexerImplementation() { + NetworkInitializer::instance(); + breakEvent_ = CreateEvent(NULL, FALSE, FALSE, NULL); + } + + ~SocketReceiveMultiplexerImplementation() { CloseHandle(breakEvent_); } + + void AttachSocketListener(UdpSocket_T* 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_T* socket, PacketListener* listener) { + auto 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::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 (auto i = socketListeners_.begin(); i != socketListeners_.end(); ++i, ++j) { + HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL); + WSAEventSelect(i->second->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> timerQueue_; + for (std::vector::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_) { + currentTimeMs = GetCurrentTimeMs(); // reuse outer (avoid MSVC C4456 shadow) + + 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) { + std::size_t size = + socketListeners_[i].second->ReceiveFrom(remoteEndpoint, data, MAX_BUFFER_SIZE); + + if (size > 0) { + if (break_) + break; + socketListeners_[i].first->ProcessPacket(data, (int)size, remoteEndpoint); + if (break_) + break; + } + } + } + + if (break_) + break; + + // execute any expired timers + currentTimeMs = GetCurrentTimeMs(); + bool resort = false; + for (std::vector>::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 (auto i = socketListeners_.begin(); i != socketListeners_.end(); ++i, ++j) { + WSAEventSelect(i->second->Socket(), events[j], 0); // remove association between socket and event + CloseHandle(events[j]); + unsigned long enableNonblocking = 0; + ioctlsocket(i->second->Socket(), FIONBIO, &enableNonblocking); // make the socket blocking again + } + } + + void Break() { break_ = true; } + + void AsynchronousBreak() { + break_ = true; + SetEvent(breakEvent_); + } + }; + + struct Implementation { + using udp_socket_t = osctap::win32::UdpSocketImplementation; + using socket_multiplexer_t = osctap::win32::SocketReceiveMultiplexerImplementation; + }; + } // namespace win32 +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/osc/MessageMappingOscPacketListener.h b/osctap/osc/MessageMappingOscPacketListener.h index 1fb934f..175a1cd 100644 --- a/osctap/osc/MessageMappingOscPacketListener.h +++ b/osctap/osc/MessageMappingOscPacketListener.h @@ -1,38 +1,38 @@ /* - oscpack -- Open Sound Control (OSC) packet manipulation library + oscpack -- Open Sound Control (OSC) packet manipulation library http://www.rossbencina.com/code/oscpack Copyright (c) 2004-2013 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. - - 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. + 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. + + 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. */ /* - The text above constitutes the entire oscpack license; however, - the oscpack developer(s) also make the following non-binding requests: - - 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. It is also - requested that these non-binding requests be included whenever the - above license is reproduced. + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + 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. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. */ #ifndef INCLUDED_OSCTAP_MESSAGEMAPPINGOSCPACKETLISTENER_H #define INCLUDED_OSCTAP_MESSAGEMAPPINGOSCPACKETLISTENER_H @@ -42,44 +42,37 @@ #include "OscPacketListener.h" +namespace osctap { + template + class MessageMappingOscPacketListener : public OscPacketListener { + public: + typedef void (T::*function_type)(const osctap::ReceivedMessage&, const IpEndpointName&); -namespace osctap{ - -template< class T > -class MessageMappingOscPacketListener : public OscPacketListener{ -public: - typedef void (T::*function_type)(const osctap::ReceivedMessage&, const IpEndpointName&); - -protected: - void RegisterMessageFunction( const char *addressPattern, function_type f ) - { - functions_.insert( std::make_pair( addressPattern, f ) ); - } - - virtual void ProcessMessage( const osctap::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 std::strcmp( lhs, rhs ) < 0; } - }; + protected: + void RegisterMessageFunction(const char* addressPattern, function_type f) { + functions_.insert(std::make_pair(addressPattern, f)); + } - typedef std::map function_map_type; - function_map_type functions_; -}; + virtual void ProcessMessage(const osctap::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); + } -} // namespace osctap + private: + struct cstr_compare { + bool operator()(const char* lhs, const char* rhs) const { return std::strcmp(lhs, rhs) < 0; } + }; + typedef std::map function_map_type; + function_map_type functions_; + }; + +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; -#endif /* INCLUDED_OSCTAP_MESSAGEMAPPINGOSCPACKETLISTENER_H */ \ No newline at end of file +#endif /* INCLUDED_OSCTAP_MESSAGEMAPPINGOSCPACKETLISTENER_H */ diff --git a/osctap/osc/OscConfig.h b/osctap/osc/OscConfig.h index 7514707..0341fea 100644 --- a/osctap/osc/OscConfig.h +++ b/osctap/osc/OscConfig.h @@ -56,11 +56,11 @@ else leaves the normal throwing behaviour in place. Force it explicitly by pre-defining OSCTAP_HAS_EXCEPTIONS to 0 or 1 on the command line. */ #ifndef OSCTAP_HAS_EXCEPTIONS -# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined(_MSC_VER) && defined(_CPPUNWIND)) -# define OSCTAP_HAS_EXCEPTIONS 1 -# else -# define OSCTAP_HAS_EXCEPTIONS 0 -# endif +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined(_MSC_VER) && defined(_CPPUNWIND)) +#define OSCTAP_HAS_EXCEPTIONS 1 +#else +#define OSCTAP_HAS_EXCEPTIONS 0 +#endif #endif /* --- OSCTAP_FREESTANDING ---------------------------------------------------- @@ -84,23 +84,25 @@ before including any OscTap header -- it receives the exception's .what() string and must not return. */ #if OSCTAP_HAS_EXCEPTIONS -# define OSCTAP_THROW(EXC) throw EXC +#define OSCTAP_THROW(EXC) throw EXC #else -# if defined(OSCTAP_FATAL_HANDLER) -# define OSCTAP_THROW(EXC) (OSCTAP_FATAL_HANDLER((EXC).what())) -# else -# include // std::abort +#if defined(OSCTAP_FATAL_HANDLER) +#define OSCTAP_THROW(EXC) (OSCTAP_FATAL_HANDLER((EXC).what())) +#else +#include // std::abort namespace osctap { -namespace detail { -// Default fatal handler used when exceptions are disabled and the integrator -// has not supplied OSCTAP_FATAL_HANDLER. Marked [[noreturn]] so the compiler -// knows the post-validation code is unreachable (no spurious "control reaches -// end of non-void function" diagnostics at the former throw sites). -[[noreturn]] inline void OscFatalError(const char* /*what*/) { std::abort(); } -} // namespace detail + namespace detail { + // Default fatal handler used when exceptions are disabled and the integrator + // has not supplied OSCTAP_FATAL_HANDLER. Marked [[noreturn]] so the compiler + // knows the post-validation code is unreachable (no spurious "control reaches + // end of non-void function" diagnostics at the former throw sites). + [[noreturn]] inline void OscFatalError(const char* /*what*/) { + std::abort(); + } + } // namespace detail } // namespace osctap -# define OSCTAP_THROW(EXC) (::osctap::detail::OscFatalError((EXC).what())) -# endif +#define OSCTAP_THROW(EXC) (::osctap::detail::OscFatalError((EXC).what())) +#endif #endif #endif /* INCLUDED_OSCTAP_OSCCONFIG_H */ diff --git a/osctap/osc/OscDebug.h b/osctap/osc/OscDebug.h index a1d27d4..6816860 100644 --- a/osctap/osc/OscDebug.h +++ b/osctap/osc/OscDebug.h @@ -1,49 +1,40 @@ #pragma once -#include "OscReceivedElements.h" #include -namespace osctap -{ +#include "OscReceivedElements.h" -template -auto& debug(Stream& s, const ReceivedMessage& mess) -{ - s << mess.AddressPattern() << " "; - for(auto arg : mess) - { - if(arg.IsString()) - { - s << arg.AsString() << " "; - } - else if(arg.IsInt32()) - { - s << arg.AsInt32() << " "; - } - else if(arg.IsFloat()) - { - s << arg.AsFloat() << " "; - } - else if(arg.IsBool()) - { - s << arg.AsBool() << " "; - } - else if(arg.IsChar()) - { - s << arg.AsChar() << " "; - } - else if(arg.IsInt64()) - { - s << arg.AsInt64() << " "; - } - else if(arg.IsDouble()) - { - s << arg.AsDouble() << " "; - } - } +namespace osctap { - return s; -} -} + template + auto& debug(Stream& s, const ReceivedMessage& mess) { + s << mess.AddressPattern() << " "; + for (auto arg : mess) { + if (arg.IsString()) { + s << arg.AsString() << " "; + } + else if (arg.IsInt32()) { + s << arg.AsInt32() << " "; + } + else if (arg.IsFloat()) { + s << arg.AsFloat() << " "; + } + else if (arg.IsBool()) { + s << arg.AsBool() << " "; + } + else if (arg.IsChar()) { + s << arg.AsChar() << " "; + } + else if (arg.IsInt64()) { + s << arg.AsInt64() << " "; + } + else if (arg.IsDouble()) { + s << arg.AsDouble() << " "; + } + } + + return s; + } +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/osc/OscException.h b/osctap/osc/OscException.h index eb0c0b3..fddd14a 100644 --- a/osctap/osc/OscException.h +++ b/osctap/osc/OscException.h @@ -1,64 +1,65 @@ /* - oscpack -- Open Sound Control (OSC) packet manipulation library + oscpack -- Open Sound Control (OSC) packet manipulation library http://www.rossbencina.com/code/oscpack Copyright (c) 2004-2013 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: + 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. + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. - 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. + 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. */ /* - The text above constitutes the entire oscpack license; however, - the oscpack developer(s) also make the following non-binding requests: + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: - 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. It is also - requested that these non-binding requests be included whenever the - above license is reproduced. + 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. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. */ #ifndef INCLUDED_OSCTAP_OSCEXCEPTION_H #define INCLUDED_OSCTAP_OSCEXCEPTION_H #include -namespace osctap{ +namespace osctap { -class Exception : public std::exception { - const char *what_; - -public: - Exception() throw() {} - Exception( const Exception& src ) throw() - : std::exception( src ) - , what_( src.what_ ) {} - Exception( const char *w ) throw() - : what_( w ) {} - Exception& operator=( const Exception& src ) throw() - { what_ = src.what_; return *this; } - virtual ~Exception() noexcept {} - const char* what() const noexcept override { return what_; } -}; + class Exception : public std::exception { + const char* what_; -} // namespace osctap + public: + Exception() throw() {} + Exception(const Exception& src) throw() + : std::exception(src) + , what_(src.what_) {} + Exception(const char* w) throw() + : what_(w) {} + Exception& operator=(const Exception& src) throw() { + what_ = src.what_; + return *this; + } + virtual ~Exception() noexcept {} + const char* what() const noexcept override { return what_; } + }; +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/osc/OscHostEndianness.h b/osctap/osc/OscHostEndianness.h index e1fc441..0ec5f5a 100644 --- a/osctap/osc/OscHostEndianness.h +++ b/osctap/osc/OscHostEndianness.h @@ -1,38 +1,38 @@ /* - oscpack -- Open Sound Control (OSC) packet manipulation library + oscpack -- Open Sound Control (OSC) packet manipulation library http://www.rossbencina.com/code/oscpack Copyright (c) 2004-2013 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. - - 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. + 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. + + 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. */ /* - The text above constitutes the entire oscpack license; however, - the oscpack developer(s) also make the following non-binding requests: - - 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. It is also - requested that these non-binding requests be included whenever the - above license is reproduced. + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + 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. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. */ #ifndef INCLUDED_OSCTAP_OSCHOSTENDIANNESS_H #define INCLUDED_OSCTAP_OSCHOSTENDIANNESS_H @@ -93,22 +93,16 @@ // gcc defines __LITTLE_ENDIAN__ and __BIG_ENDIAN__ // for others used here see http://sourceforge.net/p/predef/wiki/Endianness/ -#if (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) \ - || (defined(__ARMEL__) && !defined(__ARMEB__)) \ - || (defined(__AARCH64EL__) && !defined(__AARCH64EB__)) \ - || (defined(_MIPSEL) && !defined(_MIPSEB)) \ - || (defined(__MIPSEL) && !defined(__MIPSEB)) \ - || (defined(__MIPSEL__) && !defined(__MIPSEB__)) +#if (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) || (defined(__ARMEL__) && !defined(__ARMEB__)) \ + || (defined(__AARCH64EL__) && !defined(__AARCH64EB__)) || (defined(_MIPSEL) && !defined(_MIPSEB)) \ + || (defined(__MIPSEL) && !defined(__MIPSEB)) || (defined(__MIPSEL__) && !defined(__MIPSEB__)) #define OSC_HOST_LITTLE_ENDIAN 1 #undef OSC_HOST_BIG_ENDIAN -#elif (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) \ - || (defined(__ARMEB__) && !defined(__ARMEL__)) \ - || (defined(__AARCH64EB__) && !defined(__AARCH64EL__)) \ - || (defined(_MIPSEB) && !defined(_MIPSEL)) \ - || (defined(__MIPSEB) && !defined(__MIPSEL)) \ - || (defined(__MIPSEB__) && !defined(__MIPSEL__)) +#elif (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) || (defined(__ARMEB__) && !defined(__ARMEL__)) \ + || (defined(__AARCH64EB__) && !defined(__AARCH64EL__)) || (defined(_MIPSEB) && !defined(_MIPSEL)) \ + || (defined(__MIPSEB) && !defined(__MIPSEL)) || (defined(__MIPSEB__) && !defined(__MIPSEL__)) #define OSC_HOST_BIG_ENDIAN 1 #undef OSC_HOST_LITTLE_ENDIAN @@ -124,4 +118,3 @@ #endif #endif /* INCLUDED_OSCTAP_OSCHOSTENDIANNESS_H */ - diff --git a/osctap/osc/OscOutboundPacketStream.h b/osctap/osc/OscOutboundPacketStream.h index 794884f..92955c1 100644 --- a/osctap/osc/OscOutboundPacketStream.h +++ b/osctap/osc/OscOutboundPacketStream.h @@ -37,641 +37,563 @@ #ifndef INCLUDED_OSCTAP_OSCOUTBOUNDPACKETSTREAM_H #define INCLUDED_OSCTAP_OSCOUTBOUNDPACKETSTREAM_H -#include // size_t - #include -#include // memcpy, memmove, strcpy, strlen #include // ptrdiff_t +#include // size_t +#include // memcpy, memmove, strcpy, strlen #ifndef OSCTAP_FREESTANDING #include #include // std::string operator<< overload (hosted convenience) #endif #include -namespace osctap -{ using string_view = std::string_view; } +namespace osctap { + using string_view = std::string_view; +} -#include "OscTypes.h" +#include "OscConfig.h" // OSCTAP_THROW, OSCTAP_FREESTANDING #include "OscException.h" -#include "OscConfig.h" // OSCTAP_THROW, OSCTAP_FREESTANDING -#include "OscUtilities.h" #include "OscHostEndianness.h" +#include "OscTypes.h" +#include "OscUtilities.h" #if defined(__WIN32__) || defined(WIN32) || defined(_WIN32) #include // for alloca #else -//#include // alloca on Linux (also OSX) +// #include // alloca on Linux (also OSX) #include // alloca on OSX and FreeBSD (and Linux?) #endif - -namespace osctap{ - - -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 ) {} -}; - -struct BeginMessageN -{ - explicit BeginMessageN(osctap::string_view str): - addressPattern{str} - { - - } - - osctap::string_view addressPattern; -}; - - -class OutboundPacketStream{ -public: - OutboundPacketStream( char *buffer, std::size_t capacity ) - : data_( buffer ) - , end_( data_ + capacity ) - , typeTagsCurrent_( end_ ) - , messageCursor_( data_ ) - , argumentCurrent_( data_ ) - , elementSizePtr_( 0 ) - , messageIsInProgress_( false ) - { - // sanity check integer types declared in OscTypes.h - // you'll need to fix OscTypes.h if any of these asserts fail - assert( sizeof(int32_t) == 4 ); - assert( sizeof(uint32_t) == 4 ); - assert( sizeof(int64_t) == 8 ); - assert( sizeof(uint64_t) == 8 ); - } - ~OutboundPacketStream() - { - - } - - void Clear() - { - typeTagsCurrent_ = end_; - messageCursor_ = data_; - argumentCurrent_ = data_; - elementSizePtr_ = 0; - messageIsInProgress_ = false; - } - - std::size_t Capacity() const - { - return end_ - data_; - } - - // invariant: size() is valid even while building a message. - std::size_t Size() const - { - std::size_t 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( static_cast((end_ - typeTagsCurrent_) + 2) ); - } - - return result; - } - - const char *Data() const - { - return data_; - } - - // indicates that all messages have been closed with a matching EndMessage - // and all bundles have been closed with a matching EndBundle - bool IsReady() const - { - return (!IsMessageInProgress() && !IsBundleInProgress()); - } - - bool IsMessageInProgress() const - { - return messageIsInProgress_; - } - bool IsBundleInProgress() const - { - return (elementSizePtr_ != 0); - } - - - template>::value>* = nullptr> - OutboundPacketStream& operator<<( T* rhs ) = delete; - - OutboundPacketStream& operator<<(BundleInitiator rhs ) - { - if( IsMessageInProgress() ) - OSCTAP_THROW( MessageInProgressException() ); - - CheckForAvailableBundleSpace(); - - messageCursor_ = BeginElement( messageCursor_ ); - - std::memcpy( messageCursor_, "#bundle\0", 8 ); - FromUInt64( messageCursor_ + 8, rhs.timeTag ); - - messageCursor_ += 16; - argumentCurrent_ = messageCursor_; - - return *this; - } - - OutboundPacketStream& operator<<(BundleTerminator rhs ) - { - (void) rhs; - - if( !IsBundleInProgress() ) - OSCTAP_THROW( BundleNotInProgressException() ); - if( IsMessageInProgress() ) - OSCTAP_THROW( MessageInProgressException() ); - - EndElement( messageCursor_ ); - - return *this; - } - - - OutboundPacketStream& operator<<(BeginMessage rhs ) - { - if( IsMessageInProgress() ) - OSCTAP_THROW( MessageInProgressException() ); - - std::size_t rhsLength = std::strlen(rhs.addressPattern); - CheckForAvailableMessageSpace( rhsLength ); - - messageCursor_ = BeginElement( messageCursor_ ); - - // memcpy (not strcpy) of the known length incl. terminator: avoids the - // MSVC C4996 'unsafe' deprecation and is a bounded copy. - std::memcpy( messageCursor_, rhs.addressPattern, rhsLength + 1 ); - messageCursor_ += rhsLength + 1; - - // zero pad to 4-byte boundary - std::size_t i = rhsLength + 1; - while( i & 0x3 ){ - *messageCursor_++ = '\0'; - ++i; +namespace osctap { + + 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) {} + }; + + struct BeginMessageN { + explicit BeginMessageN(osctap::string_view str) + : addressPattern{str} {} + + osctap::string_view addressPattern; + }; + + class OutboundPacketStream { + public: + OutboundPacketStream(char* buffer, std::size_t capacity) + : data_(buffer) + , end_(data_ + capacity) + , typeTagsCurrent_(end_) + , messageCursor_(data_) + , argumentCurrent_(data_) + , elementSizePtr_(0) + , messageIsInProgress_(false) { + // sanity check integer types declared in OscTypes.h + // you'll need to fix OscTypes.h if any of these asserts fail + assert(sizeof(int32_t) == 4); + assert(sizeof(uint32_t) == 4); + assert(sizeof(int64_t) == 8); + assert(sizeof(uint64_t) == 8); + } + ~OutboundPacketStream() {} + + void Clear() { + typeTagsCurrent_ = end_; + messageCursor_ = data_; + argumentCurrent_ = data_; + elementSizePtr_ = 0; + messageIsInProgress_ = false; } - argumentCurrent_ = messageCursor_; - typeTagsCurrent_ = end_; + std::size_t Capacity() const { return end_ - data_; } - messageIsInProgress_ = true; + // invariant: size() is valid even while building a message. + std::size_t Size() const { + std::size_t 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(static_cast((end_ - typeTagsCurrent_) + 2)); + } - return *this; - } - OutboundPacketStream& operator<<(BeginMessageN rhs) - { - if( IsMessageInProgress() ) - OSCTAP_THROW( MessageInProgressException() ); + return result; + } - CheckForAvailableMessageSpace( rhs.addressPattern.size() ); + const char* Data() const { return data_; } - messageCursor_ = BeginElement( messageCursor_ ); + // indicates that all messages have been closed with a matching EndMessage + // and all bundles have been closed with a matching EndBundle + bool IsReady() const { return (!IsMessageInProgress() && !IsBundleInProgress()); } - std::memcpy(messageCursor_, rhs.addressPattern.data(), rhs.addressPattern.size()); + bool IsMessageInProgress() const { return messageIsInProgress_; } + bool IsBundleInProgress() const { return (elementSizePtr_ != 0); } - messageCursor_ += rhs.addressPattern.size(); - *messageCursor_++ = '\0'; + template >::value>* = nullptr> + OutboundPacketStream& operator<<(T* rhs) = delete; - // zero pad to 4-byte boundary - std::size_t i = rhs.addressPattern.size() + 1; - while( i & 0x3 ){ - *messageCursor_++ = '\0'; - ++i; - } + OutboundPacketStream& operator<<(BundleInitiator rhs) { + if (IsMessageInProgress()) + OSCTAP_THROW(MessageInProgressException()); - argumentCurrent_ = messageCursor_; - typeTagsCurrent_ = end_; + CheckForAvailableBundleSpace(); - messageIsInProgress_ = true; + messageCursor_ = BeginElement(messageCursor_); - return *this; - } - OutboundPacketStream& operator<<(MessageTerminator rhs ) - { - (void) rhs; + std::memcpy(messageCursor_, "#bundle\0", 8); + FromUInt64(messageCursor_ + 8, rhs.timeTag); - if( !IsMessageInProgress() ) - OSCTAP_THROW( MessageNotInProgressException() ); + messageCursor_ += 16; + argumentCurrent_ = messageCursor_; - std::size_t typeTagsCount = end_ - typeTagsCurrent_; + return *this; + } - if( typeTagsCount ){ + OutboundPacketStream& operator<<(BundleTerminator rhs) { + (void)rhs; - char *tempTypeTags = (char*)alloca(typeTagsCount); - std::memcpy( tempTypeTags, typeTagsCurrent_, typeTagsCount ); + if (!IsBundleInProgress()) + OSCTAP_THROW(BundleNotInProgressException()); + if (IsMessageInProgress()) + OSCTAP_THROW(MessageInProgressException()); - // slot size includes comma and null terminator - std::size_t typeTagSlotSize = RoundUp4( static_cast(typeTagsCount + 2) ); + EndElement(messageCursor_); - std::size_t argumentsSize = argumentCurrent_ - messageCursor_; + return *this; + } - std::memmove( messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize ); + OutboundPacketStream& operator<<(BeginMessage rhs) { + if (IsMessageInProgress()) + OSCTAP_THROW(MessageInProgressException()); - messageCursor_[0] = ','; - // copy type tags in reverse (really forward) order - for( std::size_t i=0; i < typeTagsCount; ++i ) - messageCursor_[i+1] = tempTypeTags[ (typeTagsCount-1) - i ]; + std::size_t rhsLength = std::strlen(rhs.addressPattern); + CheckForAvailableMessageSpace(rhsLength); - char *p = messageCursor_ + 1 + typeTagsCount; - for( std::size_t i=0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i ) - *p++ = '\0'; + messageCursor_ = BeginElement(messageCursor_); + // memcpy (not strcpy) of the known length incl. terminator: avoids the + // MSVC C4996 'unsafe' deprecation and is a bounded copy. + std::memcpy(messageCursor_, rhs.addressPattern, rhsLength + 1); + messageCursor_ += rhsLength + 1; + + // zero pad to 4-byte boundary + std::size_t i = rhsLength + 1; + while (i & 0x3) { + *messageCursor_++ = '\0'; + ++i; + } + + argumentCurrent_ = messageCursor_; typeTagsCurrent_ = end_; - // advance messageCursor_ for next message - messageCursor_ += typeTagSlotSize + argumentsSize; + messageIsInProgress_ = true; + + return *this; + } + OutboundPacketStream& operator<<(BeginMessageN rhs) { + if (IsMessageInProgress()) + OSCTAP_THROW(MessageInProgressException()); + + CheckForAvailableMessageSpace(rhs.addressPattern.size()); + + messageCursor_ = BeginElement(messageCursor_); + + std::memcpy(messageCursor_, rhs.addressPattern.data(), rhs.addressPattern.size()); - }else{ - // send an empty type tags string - std::memcpy( messageCursor_, ",\0\0\0", 4 ); + messageCursor_ += rhs.addressPattern.size(); + *messageCursor_++ = '\0'; + + // zero pad to 4-byte boundary + std::size_t i = rhs.addressPattern.size() + 1; + while (i & 0x3) { + *messageCursor_++ = '\0'; + ++i; + } + + argumentCurrent_ = messageCursor_; + typeTagsCurrent_ = end_; - // advance messageCursor_ for next message - messageCursor_ += 4; + messageIsInProgress_ = true; + + return *this; } + OutboundPacketStream& operator<<(MessageTerminator rhs) { + (void)rhs; - argumentCurrent_ = messageCursor_; + if (!IsMessageInProgress()) + OSCTAP_THROW(MessageNotInProgressException()); - EndElement( messageCursor_ ); + std::size_t typeTagsCount = end_ - typeTagsCurrent_; - messageIsInProgress_ = false; + if (typeTagsCount) { + char* tempTypeTags = (char*)alloca(typeTagsCount); + std::memcpy(tempTypeTags, typeTagsCurrent_, typeTagsCount); - return *this; - } + // slot size includes comma and null terminator + std::size_t typeTagSlotSize = RoundUp4(static_cast(typeTagsCount + 2)); + std::size_t argumentsSize = argumentCurrent_ - messageCursor_; - OutboundPacketStream& operator<<( bool rhs ) - { - CheckForAvailableArgumentSpace(0); + std::memmove(messageCursor_ + typeTagSlotSize, messageCursor_, argumentsSize); - *(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG); + messageCursor_[0] = ','; + // copy type tags in reverse (really forward) order + for (std::size_t i = 0; i < typeTagsCount; ++i) + messageCursor_[i + 1] = tempTypeTags[(typeTagsCount - 1) - i]; - return *this; - } + char* p = messageCursor_ + 1 + typeTagsCount; + for (std::size_t i = 0; i < (typeTagSlotSize - (typeTagsCount + 1)); ++i) + *p++ = '\0'; - OutboundPacketStream& operator<<(InfinitumType rhs ) - { - (void) rhs; - CheckForAvailableArgumentSpace(0); + typeTagsCurrent_ = end_; - *(--typeTagsCurrent_) = INFINITUM_TYPE_TAG; + // advance messageCursor_ for next message + messageCursor_ += typeTagSlotSize + argumentsSize; + } + else { + // send an empty type tags string + std::memcpy(messageCursor_, ",\0\0\0", 4); - return *this; - } + // advance messageCursor_ for next message + messageCursor_ += 4; + } - OutboundPacketStream& operator<<(NilType rhs ) - { - (void) rhs; - CheckForAvailableArgumentSpace(0); + argumentCurrent_ = messageCursor_; - *(--typeTagsCurrent_) = NIL_TYPE_TAG; + EndElement(messageCursor_); - return *this; - } + messageIsInProgress_ = false; + return *this; + } - OutboundPacketStream& operator<<( int32_t rhs ) - { - CheckForAvailableArgumentSpace(4); + OutboundPacketStream& operator<<(bool rhs) { + CheckForAvailableArgumentSpace(0); - *(--typeTagsCurrent_) = INT32_TYPE_TAG; - FromInt32( argumentCurrent_, rhs ); - argumentCurrent_ += 4; + *(--typeTagsCurrent_) = (char)((rhs) ? TRUE_TYPE_TAG : FALSE_TYPE_TAG); - return *this; - } + return *this; + } - OutboundPacketStream& operator<<( float rhs ) - { - CheckForAvailableArgumentSpace(4); + OutboundPacketStream& operator<<(InfinitumType rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); - *(--typeTagsCurrent_) = FLOAT_TYPE_TAG; - FromUInt32( argumentCurrent_, BitCast(rhs) ); - argumentCurrent_ += 4; + *(--typeTagsCurrent_) = INFINITUM_TYPE_TAG; - return *this; - } - OutboundPacketStream& operator<<( char rhs ) - { - CheckForAvailableArgumentSpace(4); + return *this; + } - *(--typeTagsCurrent_) = CHAR_TYPE_TAG; - FromInt32( argumentCurrent_, rhs ); - argumentCurrent_ += 4; + OutboundPacketStream& operator<<(NilType rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); - return *this; - } + *(--typeTagsCurrent_) = NIL_TYPE_TAG; - OutboundPacketStream& operator<<( const RgbaColor& rhs ) - { - CheckForAvailableArgumentSpace(4); + return *this; + } - *(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG; - FromUInt32( argumentCurrent_, rhs ); - argumentCurrent_ += 4; + OutboundPacketStream& operator<<(int32_t rhs) { + CheckForAvailableArgumentSpace(4); - return *this; - } + *(--typeTagsCurrent_) = INT32_TYPE_TAG; + FromInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; - OutboundPacketStream& operator<<( const MidiMessage& rhs ) - { - CheckForAvailableArgumentSpace(4); + return *this; + } - *(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG; - FromUInt32( argumentCurrent_, rhs ); - argumentCurrent_ += 4; + OutboundPacketStream& operator<<(float rhs) { + CheckForAvailableArgumentSpace(4); - return *this; - } + *(--typeTagsCurrent_) = FLOAT_TYPE_TAG; + FromUInt32(argumentCurrent_, BitCast(rhs)); + argumentCurrent_ += 4; + return *this; + } + OutboundPacketStream& operator<<(char rhs) { + CheckForAvailableArgumentSpace(4); - OutboundPacketStream& operator<<( int64_t rhs ) - { - CheckForAvailableArgumentSpace(8); + *(--typeTagsCurrent_) = CHAR_TYPE_TAG; + FromInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; - *(--typeTagsCurrent_) = INT64_TYPE_TAG; - FromInt64( argumentCurrent_, rhs ); - argumentCurrent_ += 8; + return *this; + } - return *this; - } + OutboundPacketStream& operator<<(const RgbaColor& rhs) { + CheckForAvailableArgumentSpace(4); - OutboundPacketStream& operator<<( const TimeTag& rhs ) - { - CheckForAvailableArgumentSpace(8); + *(--typeTagsCurrent_) = RGBA_COLOR_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; - *(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG; - FromUInt64( argumentCurrent_, rhs ); - argumentCurrent_ += 8; + return *this; + } - return *this; - } + OutboundPacketStream& operator<<(const MidiMessage& rhs) { + CheckForAvailableArgumentSpace(4); - OutboundPacketStream& operator<<( double rhs ) - { - CheckForAvailableArgumentSpace(8); + *(--typeTagsCurrent_) = MIDI_MESSAGE_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs); + argumentCurrent_ += 4; - *(--typeTagsCurrent_) = DOUBLE_TYPE_TAG; - FromUInt64( argumentCurrent_, BitCast(rhs) ); - argumentCurrent_ += 8; + return *this; + } - return *this; - } + OutboundPacketStream& operator<<(int64_t rhs) { + CheckForAvailableArgumentSpace(8); - OutboundPacketStream& operator<<( - osctap::string_view rhs) - { - CheckForAvailableArgumentSpace( RoundUp4(static_cast(rhs.size() + 1)) ); + *(--typeTagsCurrent_) = INT64_TYPE_TAG; + FromInt64(argumentCurrent_, rhs); + argumentCurrent_ += 8; - *(--typeTagsCurrent_) = STRING_TYPE_TAG; - if(!rhs.empty()) - std::memcpy( argumentCurrent_, rhs.data(), rhs.size() ); - argumentCurrent_ += rhs.size(); - *argumentCurrent_++ = '\0'; + return *this; + } - // zero pad to 4-byte boundary - std::size_t i = rhs.size() + 1; - while( i & 0x3 ){ - *argumentCurrent_++ = '\0'; - ++i; - } - - return *this; - } - - // A runtime const char* would otherwise bind to operator<<(bool): the - // standard pointer->bool conversion outranks the user-defined conversion to - // string_view, so a C-string pointer was silently serialized as a boolean. - // This overload makes a const char* serialize as an OSC string, as expected. - // (String *literals* still match the more specialised const char(&)[N] - // overload below; this catches decayed/runtime pointers.) Freestanding-safe: - // forwards to the string_view overload, no heap. - OutboundPacketStream& operator<<( const char *rhs ) - { - operator<<(osctap::string_view(rhs)); - return *this; - } + OutboundPacketStream& operator<<(const TimeTag& rhs) { + CheckForAvailableArgumentSpace(8); -#ifndef OSCTAP_FREESTANDING - // Hosted convenience: std::string pulls in (and heap). The - // freestanding profile omits it; pass const char*, a char array, or - // osctap::string_view instead. - OutboundPacketStream& operator<<( - const std::string& rhs) - { - operator<<(osctap::string_view(rhs)); - return *this; - } -#endif + *(--typeTagsCurrent_) = TIME_TAG_TYPE_TAG; + FromUInt64(argumentCurrent_, rhs); + argumentCurrent_ += 8; - template - OutboundPacketStream& operator<<( - const char (&ref)[N]) - { - CheckForAvailableArgumentSpace( RoundUp4(N) ); - - *(--typeTagsCurrent_) = STRING_TYPE_TAG; - std::memcpy( argumentCurrent_, ref, N ); - argumentCurrent_ += N; // already 0-terminated - - // zero pad to 4-byte boundary - std::size_t i = N; - while( i & 0x3 ){ - *argumentCurrent_++ = '\0'; - ++i; - } - - return *this; - } - - - OutboundPacketStream& operator<<( const Symbol& rhs ) - { - CheckForAvailableArgumentSpace( RoundUp4(static_cast(std::strlen(rhs) + 1)) ); - - *(--typeTagsCurrent_) = SYMBOL_TYPE_TAG; - std::size_t rhsLength = std::strlen(rhs); - // memcpy (not strcpy) of the known length incl. terminator: avoids the - // MSVC C4996 'unsafe' deprecation and is a bounded copy. - std::memcpy( argumentCurrent_, rhs, rhsLength + 1 ); - argumentCurrent_ += rhsLength + 1; - - // zero pad to 4-byte boundary - std::size_t i = rhsLength + 1; - while( i & 0x3 ){ - *argumentCurrent_++ = '\0'; - ++i; + return *this; } - return *this; - } + OutboundPacketStream& operator<<(double rhs) { + CheckForAvailableArgumentSpace(8); - OutboundPacketStream& operator<<( const Blob& rhs ) - { - CheckForAvailableArgumentSpace( 4 + RoundUp4(rhs.size) ); + *(--typeTagsCurrent_) = DOUBLE_TYPE_TAG; + FromUInt64(argumentCurrent_, BitCast(rhs)); + argumentCurrent_ += 8; - *(--typeTagsCurrent_) = BLOB_TYPE_TAG; - FromUInt32( argumentCurrent_, rhs.size ); - argumentCurrent_ += 4; + return *this; + } - std::memcpy( argumentCurrent_, rhs.data, rhs.size ); - argumentCurrent_ += rhs.size; + OutboundPacketStream& operator<<(osctap::string_view rhs) { + CheckForAvailableArgumentSpace(RoundUp4(static_cast(rhs.size() + 1))); - // zero pad to 4-byte boundary - unsigned long i = rhs.size; - while( i & 0x3 ){ + *(--typeTagsCurrent_) = STRING_TYPE_TAG; + if (!rhs.empty()) + std::memcpy(argumentCurrent_, rhs.data(), rhs.size()); + argumentCurrent_ += rhs.size(); *argumentCurrent_++ = '\0'; - ++i; + + // zero pad to 4-byte boundary + std::size_t i = rhs.size() + 1; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } + + return *this; } - return *this; - } + // A runtime const char* would otherwise bind to operator<<(bool): the + // standard pointer->bool conversion outranks the user-defined conversion to + // string_view, so a C-string pointer was silently serialized as a boolean. + // This overload makes a const char* serialize as an OSC string, as expected. + // (String *literals* still match the more specialised const char(&)[N] + // overload below; this catches decayed/runtime pointers.) Freestanding-safe: + // forwards to the string_view overload, no heap. + OutboundPacketStream& operator<<(const char* rhs) { + operator<<(osctap::string_view(rhs)); + return *this; + } - OutboundPacketStream& operator<<( const ArrayInitiator& rhs ) - { - (void) rhs; - CheckForAvailableArgumentSpace(0); +#ifndef OSCTAP_FREESTANDING + // Hosted convenience: std::string pulls in (and heap). The + // freestanding profile omits it; pass const char*, a char array, or + // osctap::string_view instead. + OutboundPacketStream& operator<<(const std::string& rhs) { + operator<<(osctap::string_view(rhs)); + return *this; + } +#endif - *(--typeTagsCurrent_) = ARRAY_BEGIN_TYPE_TAG; + template + OutboundPacketStream& operator<<(const char (&ref)[N]) { + CheckForAvailableArgumentSpace(RoundUp4(N)); - return *this; - } + *(--typeTagsCurrent_) = STRING_TYPE_TAG; + std::memcpy(argumentCurrent_, ref, N); + argumentCurrent_ += N; // already 0-terminated - OutboundPacketStream& operator<<( const ArrayTerminator& rhs ) - { - (void) rhs; - CheckForAvailableArgumentSpace(0); + // zero pad to 4-byte boundary + std::size_t i = N; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } - *(--typeTagsCurrent_) = ARRAY_END_TYPE_TAG; + return *this; + } - return *this; - } + OutboundPacketStream& operator<<(const Symbol& rhs) { + CheckForAvailableArgumentSpace(RoundUp4(static_cast(std::strlen(rhs) + 1))); + *(--typeTagsCurrent_) = SYMBOL_TYPE_TAG; + std::size_t rhsLength = std::strlen(rhs); + // memcpy (not strcpy) of the known length incl. terminator: avoids the + // MSVC C4996 'unsafe' deprecation and is a bounded copy. + std::memcpy(argumentCurrent_, rhs, rhsLength + 1); + argumentCurrent_ += rhsLength + 1; -private: + // zero pad to 4-byte boundary + std::size_t i = rhsLength + 1; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } - char *BeginElement( char *beginPtr ) - { - if( elementSizePtr_ == 0 ){ + return *this; + } - elementSizePtr_ = data_; + OutboundPacketStream& operator<<(const Blob& rhs) { + CheckForAvailableArgumentSpace(4 + RoundUp4(rhs.size)); - return beginPtr; + *(--typeTagsCurrent_) = BLOB_TYPE_TAG; + FromUInt32(argumentCurrent_, rhs.size); + argumentCurrent_ += 4; - }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. - // (this temporary offset is overwritten with the real size in - // EndElement; it is written and read back through the same byte order, - // so it never appears on the wire.) - FromUInt32( beginPtr, (uint32_t)( elementSizePtr_ - data_ ) ); + std::memcpy(argumentCurrent_, rhs.data, rhs.size); + argumentCurrent_ += rhs.size; - elementSizePtr_ = beginPtr; + // zero pad to 4-byte boundary + unsigned long i = rhs.size; + while (i & 0x3) { + *argumentCurrent_++ = '\0'; + ++i; + } - return beginPtr + 4; - } - } - void EndElement( char *endPtr ) - { - assert( elementSizePtr_ != 0 ); + return *this; + } - if( elementSizePtr_ == data_ ){ + OutboundPacketStream& operator<<(const ArrayInitiator& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); - elementSizePtr_ = 0; + *(--typeTagsCurrent_) = ARRAY_BEGIN_TYPE_TAG; - }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 - char *previousElementSizePtr = data_ + ToUInt32( elementSizePtr_ ); + return *this; + } - // then we store the element size in the slot. note that the element - // size does not include the size slot, hence the - 4 below. + OutboundPacketStream& operator<<(const ArrayTerminator& rhs) { + (void)rhs; + CheckForAvailableArgumentSpace(0); - std::ptrdiff_t d = endPtr - elementSizePtr_; - // assert( d >= 4 && d <= 0x7FFFFFFF ); // assume packets smaller than 2Gb + *(--typeTagsCurrent_) = ARRAY_END_TYPE_TAG; - uint32_t elementSize = static_cast(d - 4); - FromUInt32( elementSizePtr_, elementSize ); + return *this; + } - // finally, we reset the element size ptr to the containing element - elementSizePtr_ = previousElementSizePtr; - } - } + private: + char* BeginElement(char* beginPtr) { + if (elementSizePtr_ == 0) { + elementSizePtr_ = 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. + // (this temporary offset is overwritten with the real size in + // EndElement; it is written and read back through the same byte order, + // so it never appears on the wire.) + FromUInt32(beginPtr, (uint32_t)(elementSizePtr_ - data_)); + + elementSizePtr_ = beginPtr; + + return beginPtr + 4; + } + } + void EndElement(char* endPtr) { + assert(elementSizePtr_ != 0); + + if (elementSizePtr_ == 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 + char* previousElementSizePtr = data_ + ToUInt32(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. + + std::ptrdiff_t d = endPtr - elementSizePtr_; + // assert( d >= 4 && d <= 0x7FFFFFFF ); // assume packets smaller than 2Gb + + uint32_t elementSize = static_cast(d - 4); + FromUInt32(elementSizePtr_, elementSize); + + // finally, we reset the element size ptr to the containing element + elementSizePtr_ = previousElementSizePtr; + } + } - bool ElementSizeSlotRequired() const - { - return (elementSizePtr_ != 0); - } - void CheckForAvailableBundleSpace() - { - std::size_t required = Size() + ((ElementSizeSlotRequired())?4:0) + 16; + bool ElementSizeSlotRequired() const { return (elementSizePtr_ != 0); } + void CheckForAvailableBundleSpace() { + std::size_t required = Size() + ((ElementSizeSlotRequired()) ? 4 : 0) + 16; - if( required > Capacity() ) - OSCTAP_THROW( OutOfBufferMemoryException() ); - } - void CheckForAvailableMessageSpace( std::size_t addressPatternSize ) - { - // plus 4 for at least four bytes of type tag - std::size_t required = Size() + ((ElementSizeSlotRequired())?4:0) - + RoundUp4(static_cast(addressPatternSize + 1)) + 4; + if (required > Capacity()) + OSCTAP_THROW(OutOfBufferMemoryException()); + } + void CheckForAvailableMessageSpace(std::size_t addressPatternSize) { + // plus 4 for at least four bytes of type tag + std::size_t required = Size() + ((ElementSizeSlotRequired()) ? 4 : 0) + + RoundUp4(static_cast(addressPatternSize + 1)) + 4; - if( required > Capacity() ) - OSCTAP_THROW( OutOfBufferMemoryException() ); - } - void CheckForAvailableArgumentSpace( std::size_t argumentLength ) - { - // plus three for extra type tag, comma and null terminator - std::size_t required = (argumentCurrent_ - data_) + argumentLength - + RoundUp4( static_cast((end_ - typeTagsCurrent_) + 3) ); + if (required > Capacity()) + OSCTAP_THROW(OutOfBufferMemoryException()); + } + void CheckForAvailableArgumentSpace(std::size_t argumentLength) { + // plus three for extra type tag, comma and null terminator + std::size_t required = (argumentCurrent_ - data_) + argumentLength + + RoundUp4(static_cast((end_ - typeTagsCurrent_) + 3)); - if( required > Capacity() ) - OSCTAP_THROW( OutOfBufferMemoryException() ); - } + if (required > Capacity()) + OSCTAP_THROW(OutOfBufferMemoryException()); + } - char * const data_; - char * const end_; + char* const data_; + char* const end_; - char *typeTagsCurrent_; // stored in reverse order - char *messageCursor_; - char *argumentCurrent_; + 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) - char *elementSizePtr_; + // 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) + char* elementSizePtr_; - bool messageIsInProgress_; -}; + bool messageIsInProgress_; + }; } // namespace osctap - // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; diff --git a/osctap/osc/OscPacketListener.h b/osctap/osc/OscPacketListener.h index 125e957..dd13f75 100644 --- a/osctap/osc/OscPacketListener.h +++ b/osctap/osc/OscPacketListener.h @@ -37,72 +37,67 @@ #ifndef INCLUDED_OSCTAP_OSCPACKETLISTENER_H #define INCLUDED_OSCTAP_OSCPACKETLISTENER_H -#include "OscReceivedElements.h" #include "../ip/PacketListener.h" +#include "OscReceivedElements.h" + +namespace osctap { + + class OscPacketListener : public PacketListener { + public: + // Maximum bundle nesting depth accepted by the default ProcessBundle() + // implementation. Bundles nested deeper than this are ignored, to bound + // stack usage when processing untrusted packets (a deeply-nested bundle is + // otherwise valid OSC and would recurse once per level). Configurable for + // the rare application that legitimately nests deeper. + static const unsigned int DEFAULT_MAX_BUNDLE_NESTING_DEPTH = 64; + + void SetMaxBundleNestingDepth(unsigned int depth) { maxBundleNestingDepth_ = depth; } + unsigned int MaxBundleNestingDepth() const { return maxBundleNestingDepth_; } + + protected: + virtual void ProcessBundle(const osctap::ReceivedBundle& b, const IpEndpointName& remoteEndpoint) { + // ignore bundle time tag for now + + // Bound recursion depth so a deeply-nested bundle from an untrusted + // sender cannot exhaust the stack. The guard restores the depth on the + // way out even if ProcessMessage() or element construction throws. + if (bundleNestingDepth_ >= maxBundleNestingDepth_) + return; + + struct DepthGuard { + unsigned int& depth; + explicit DepthGuard(unsigned int& d) + : depth(d) { + ++depth; + } + ~DepthGuard() { --depth; } + } depthGuard(bundleNestingDepth_); + + 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 osctap::ReceivedMessage& m, const IpEndpointName& remoteEndpoint) = 0; -namespace osctap{ - -class OscPacketListener : public PacketListener{ -public: - // Maximum bundle nesting depth accepted by the default ProcessBundle() - // implementation. Bundles nested deeper than this are ignored, to bound - // stack usage when processing untrusted packets (a deeply-nested bundle is - // otherwise valid OSC and would recurse once per level). Configurable for - // the rare application that legitimately nests deeper. - static const unsigned int DEFAULT_MAX_BUNDLE_NESTING_DEPTH = 64; - - void SetMaxBundleNestingDepth( unsigned int depth ) { maxBundleNestingDepth_ = depth; } - unsigned int MaxBundleNestingDepth() const { return maxBundleNestingDepth_; } - -protected: - virtual void ProcessBundle( const osctap::ReceivedBundle& b, - const IpEndpointName& remoteEndpoint ) - { - // ignore bundle time tag for now - - // Bound recursion depth so a deeply-nested bundle from an untrusted - // sender cannot exhaust the stack. The guard restores the depth on the - // way out even if ProcessMessage() or element construction throws. - if( bundleNestingDepth_ >= maxBundleNestingDepth_ ) - return; - - struct DepthGuard{ - unsigned int& depth; - explicit DepthGuard( unsigned int& d ) : depth( d ) { ++depth; } - ~DepthGuard() { --depth; } - } depthGuard( bundleNestingDepth_ ); - - for( ReceivedBundle::const_iterator i = b.ElementsBegin(); - i != b.ElementsEnd(); ++i ){ - if( i->IsBundle() ) - ProcessBundle( ReceivedBundle(*i), remoteEndpoint ); + public: + void ProcessPacket(const char* data, int size, const IpEndpointName& remoteEndpoint) override { + osctap::ReceivedPacket p(data, size); + if (p.IsBundle()) + ProcessBundle(ReceivedBundle(p), remoteEndpoint); else - ProcessMessage( ReceivedMessage(*i), remoteEndpoint ); + ProcessMessage(ReceivedMessage(p), remoteEndpoint); } - } - - virtual void ProcessMessage( const osctap::ReceivedMessage& m, - const IpEndpointName& remoteEndpoint ) = 0; - -public: - void ProcessPacket( const char *data, int size, - const IpEndpointName& remoteEndpoint ) override - { - osctap::ReceivedPacket p( data, size ); - if( p.IsBundle() ) - ProcessBundle( ReceivedBundle(p), remoteEndpoint ); - else - ProcessMessage( ReceivedMessage(p), remoteEndpoint ); - } - -private: - unsigned int bundleNestingDepth_ = 0; - unsigned int maxBundleNestingDepth_ = DEFAULT_MAX_BUNDLE_NESTING_DEPTH; -}; -} // namespace osctap + private: + unsigned int bundleNestingDepth_ = 0; + unsigned int maxBundleNestingDepth_ = DEFAULT_MAX_BUNDLE_NESTING_DEPTH; + }; +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/osc/OscPrintReceivedElements.h b/osctap/osc/OscPrintReceivedElements.h index 82c7a65..bd2af3a 100644 --- a/osctap/osc/OscPrintReceivedElements.h +++ b/osctap/osc/OscPrintReceivedElements.h @@ -37,33 +37,28 @@ #ifndef INCLUDED_OSCTAP_OSCPRINTRECEIVEDELEMENTS_H #define INCLUDED_OSCTAP_OSCPRINTRECEIVEDELEMENTS_H -#include - -#include "OscReceivedElements.h" - #include #include -#include #include +#include +#include -namespace osctap{ +#include "OscReceivedElements.h" -template -Ostream_T& operator<<( Ostream_T & os, const ReceivedPacket& p ); -template -Ostream_T& operator<<( Ostream_T & os, const ReceivedMessageArgument& arg ); -template -Ostream_T& operator<<( Ostream_T & os, const ReceivedMessage& m ); -template -Ostream_T& operator<<( Ostream_T & os, const ReceivedBundle& b ); +namespace osctap { + template + Ostream_T& operator<<(Ostream_T& os, const ReceivedPacket& p); + template + Ostream_T& operator<<(Ostream_T& os, const ReceivedMessageArgument& arg); + template + Ostream_T& operator<<(Ostream_T& os, const ReceivedMessage& m); + template + Ostream_T& operator<<(Ostream_T& os, const ReceivedBundle& b); -template -inline Ostream_T& operator<<( - Ostream_T & os, - const ReceivedMessageArgument& arg ) -{ - switch( arg.TypeTag() ){ + template + inline Ostream_T& operator<<(Ostream_T& os, const ReceivedMessageArgument& arg) { + switch (arg.TypeTag()) { case TRUE_TYPE_TAG: os << "bool:true"; break; @@ -88,72 +83,54 @@ inline Ostream_T& operator<<( 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_t 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_t 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 CHAR_TYPE_TAG: { + char s[2] = {0}; + s[0] = arg.AsCharUnchecked(); + os << "char:'" << s << "'"; + } break; + + case RGBA_COLOR_TYPE_TAG: { + uint32_t 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_t 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_t:" << arg.AsInt64Unchecked(); break; - case TIME_TAG_TYPE_TAG: - { - os << "OSC-timetag:" << arg.AsTimeTagUnchecked() << " "; + case TIME_TAG_TYPE_TAG: { + os << "OSC-timetag:" << arg.AsTimeTagUnchecked() << " "; - std::time_t t = - (unsigned long)( arg.AsTimeTagUnchecked() >> 32 ); + std::time_t t = (unsigned long)(arg.AsTimeTagUnchecked() >> 32); #if defined(_MSC_VER) - // std::ctime is deprecated on MSVC (C4996); use the bounded ctime_s. - char timeBuf[32]; - const char *timeString = - ( ctime_s( timeBuf, sizeof(timeBuf), &t ) == 0 ) ? timeBuf : nullptr; + // std::ctime is deprecated on MSVC (C4996); use the bounded ctime_s. + char timeBuf[32]; + const char* timeString = (ctime_s(timeBuf, sizeof(timeBuf), &t) == 0) ? timeBuf : nullptr; #else - const char *timeString = std::ctime( &t ); + const char* timeString = std::ctime(&t); #endif - // ctime()/ctime_s() can return null on failure; guard before reading. - if( timeString ){ - size_t len = std::strlen( timeString ); + // ctime()/ctime_s() can return null on failure; guard before reading. + if (timeString) { + size_t len = std::strlen(timeString); - // -1 to omit trailing newline from string returned by ctime() - if( len > 1 ) - os.write( timeString, len - 1 ); - } + // -1 to omit trailing newline from string returned by ctime() + if (len > 1) + os.write(timeString, len - 1); } - break; + } break; case DOUBLE_TYPE_TAG: os << "double:" << arg.AsDoubleUnchecked(); @@ -167,22 +144,20 @@ inline Ostream_T& operator<<( os << "OSC-string (symbol):`" << arg.AsSymbolUnchecked() << "'"; break; - case BLOB_TYPE_TAG: - { - const void *data; - osc_bundle_element_size_t size; - arg.AsBlobUnchecked( data, size ); - os << "OSC-blob:<<" << std::hex << std::setfill('0'); - unsigned char *p = (unsigned char*)data; - for( osc_bundle_element_size_t 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(' '); + case BLOB_TYPE_TAG: { + const void* data; + osc_bundle_element_size_t size; + arg.AsBlobUnchecked(data, size); + os << "OSC-blob:<<" << std::hex << std::setfill('0'); + unsigned char* p = (unsigned char*)data; + for (osc_bundle_element_size_t i = 0; i < size; ++i) { + os << "0x" << std::setw(2) << int(p[i]); + if (i != size - 1) + os << ' '; } - break; + os.unsetf(std::ios::basefield); + os << ">>" << std::setfill(' '); + } break; case ARRAY_BEGIN_TYPE_TAG: os << "["; @@ -194,106 +169,101 @@ inline Ostream_T& operator<<( default: os << "unknown"; - } - - return os; -} - - -template -inline Ostream_T& operator<<( Ostream_T& os, const ReceivedMessage& m ) -{ - os << "["; - if( m.AddressPatternIsUInt32() ) - os << m.AddressPatternAsUInt32(); - else - 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; + return os; } - os << "]"; - - return os; -} - - -template -inline Ostream_T& operator<<( Ostream_T & os, const ReceivedBundle& b ) -{ - static thread_local int indent = 0; + template + inline Ostream_T& operator<<(Ostream_T& os, const ReceivedMessage& m) { + os << "["; + if (m.AddressPatternIsUInt32()) + os << m.AddressPatternAsUInt32(); + else + os << m.AddressPattern(); + + bool first = true; + for (ReceivedMessage::const_iterator i = m.ArgumentsBegin(); i != m.ArgumentsEnd(); ++i) { + if (first) { + os << " "; + first = false; + } + else { + os << ", "; + } - // Bound recursion depth so printing an untrusted, deeply-nested bundle - // cannot exhaust the stack. - const int MAX_BUNDLE_PRINT_DEPTH = 64; + os << *i; + } - for( int j=0; j < indent; ++j ) - os << " "; - os << "{ ( "; - if( b.TimeTag() == 1 ) - os << "immediate"; - else - os << b.TimeTag(); - os << " )\n"; + os << "]"; - ++indent; + return os; + } - for( ReceivedBundle::const_iterator i = b.ElementsBegin(); - i != b.ElementsEnd(); ++i ){ - if( i->IsBundle() ){ - if( indent >= MAX_BUNDLE_PRINT_DEPTH ){ - for( int j=0; j < indent; ++j ) + template + inline Ostream_T& operator<<(Ostream_T& os, const ReceivedBundle& b) { + static thread_local int indent = 0; + + // Bound recursion depth so printing an untrusted, deeply-nested bundle + // cannot exhaust the stack. + const int MAX_BUNDLE_PRINT_DEPTH = 64; + + 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()) { + if (indent >= MAX_BUNDLE_PRINT_DEPTH) { + for (int j = 0; j < indent; ++j) + os << " "; + os << "{ ...bundle nesting depth limit reached... }\n"; + } + else { + ReceivedBundle nested(*i); + os << nested << "\n"; + } + } + else { + ReceivedMessage m(*i); + for (int j = 0; j < indent; ++j) os << " "; - os << "{ ...bundle nesting depth limit reached... }\n"; - }else{ - ReceivedBundle nested(*i); - os << nested << "\n"; + os << m << "\n"; } - }else{ - ReceivedMessage m(*i); - for( int j=0; j < indent; ++j ) - os << " "; - os << m << "\n"; } - } - --indent; + --indent; - for( int j=0; j < indent; ++j ) - os << " "; - os << "}"; + for (int j = 0; j < indent; ++j) + os << " "; + os << "}"; - return os; -} + return os; + } + template + inline Ostream_T& operator<<(Ostream_T& os, const ReceivedPacket& p) { + if (p.IsBundle()) { + ReceivedBundle b(p); + os << b << "\n"; + } + else { + ReceivedMessage m(p); + os << m << "\n"; + } -template -inline Ostream_T& operator<<( Ostream_T& os, const ReceivedPacket& p ) -{ - if( p.IsBundle() ){ - ReceivedBundle b(p); - os << b << "\n"; - }else{ - ReceivedMessage m(p); - os << m << "\n"; + return os; } - return os; -} - } // namespace osctap - // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; diff --git a/osctap/osc/OscReceivedElements.h b/osctap/osc/OscReceivedElements.h index 4b699ad..9199725 100644 --- a/osctap/osc/OscReceivedElements.h +++ b/osctap/osc/OscReceivedElements.h @@ -39,1157 +39,1023 @@ #include #include +#include // ptrdiff_t #include // size_t -#include "OscTypes.h" + +#include "OscConfig.h" // OSCTAP_THROW, OSCTAP_FREESTANDING #include "OscException.h" -#include "OscConfig.h" // OSCTAP_THROW, OSCTAP_FREESTANDING +#include "OscTypes.h" #include "OscUtilities.h" -#include // ptrdiff_t #ifndef OSCTAP_FREESTANDING #include // std::vector backs OwnedMessage (hosted-only) #endif +namespace osctap { + + class MalformedPacketException : public Exception { + public: + MalformedPacketException(const char* w = "malformed packet") + : Exception(w) {} + }; + + 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: + // Although the OSC spec is not entirely clear on this, we only support + // packets up to 0x7FFFFFFC bytes long (the maximum 4-byte aligned value + // representable by an int32_t). An exception will be raised if you pass a + // larger value to the ReceivedPacket() constructor. + + ReceivedPacket(const char* contents, osc_bundle_element_size_t size) + : contents_(contents) + , size_(ValidateSize(size)) {} + + ReceivedPacket(const char* contents, std::size_t size) + : contents_(contents) + , size_(ValidateSize((osc_bundle_element_size_t)size)) {} + + ReceivedPacket(const char* contents, int64_t size) + : contents_(contents) + , size_(ValidateSize((osc_bundle_element_size_t)size)) {} + + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const { return (Size() > 0 && Contents()[0] == '#'); } + + osc_bundle_element_size_t Size() const { return size_; } + const char* Contents() const { return contents_; } + + // Non-throwing size validation: returns nullptr if `size` is an acceptable + // packet/element size, else a static error string. The single source of the + // size rules, shared by the throwing ValidateSize() and the non-throwing + // TryValidatePacket(). + static const char* ValidateSizeNoThrow(osc_bundle_element_size_t size) { + // sanity check integer types declared in OscTypes.h + // you'll need to fix OscTypes.h if any of these asserts fail + if (!IsValidElementSizeValue(size)) + return "invalid packet size"; + + if (size == 0) + return "zero length elements not permitted"; + + if (!IsMultipleOf4(size)) + return "element size must be multiple of four"; + + return nullptr; + } -namespace osctap{ - -class MalformedPacketException : public Exception{ - public: - MalformedPacketException( const char *w="malformed packet" ) - : Exception( w ) {} -}; - -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: - // Although the OSC spec is not entirely clear on this, we only support - // packets up to 0x7FFFFFFC bytes long (the maximum 4-byte aligned value - // representable by an int32_t). An exception will be raised if you pass a - // larger value to the ReceivedPacket() constructor. - - ReceivedPacket( const char *contents, osc_bundle_element_size_t size ) - : contents_( contents ) - , size_( ValidateSize(size) ) {} - - ReceivedPacket( const char *contents, std::size_t size ) - : contents_( contents ) - , size_( ValidateSize( (osc_bundle_element_size_t)size ) ) {} - - ReceivedPacket(const char *contents, int64_t size) - : contents_(contents) - , size_(ValidateSize((osc_bundle_element_size_t)size)) {} - - bool IsMessage() const { return !IsBundle(); } - bool IsBundle() const - { - return (Size() > 0 && Contents()[0] == '#'); - } + private: + const char* contents_; + osc_bundle_element_size_t size_; - osc_bundle_element_size_t Size() const { return size_; } - const char *Contents() const { return contents_; } + static osc_bundle_element_size_t ValidateSize(osc_bundle_element_size_t size) { + if (const char* err = ValidateSizeNoThrow(size)) + OSCTAP_THROW(MalformedPacketException(err)); - // Non-throwing size validation: returns nullptr if `size` is an acceptable - // packet/element size, else a static error string. The single source of the - // size rules, shared by the throwing ValidateSize() and the non-throwing - // TryValidatePacket(). - static const char* ValidateSizeNoThrow( osc_bundle_element_size_t size ) - { - // sanity check integer types declared in OscTypes.h - // you'll need to fix OscTypes.h if any of these asserts fail - if( !IsValidElementSizeValue(size) ) - return "invalid packet size"; + return size; + } + }; - if( size == 0 ) - return "zero length elements not permitted"; + class ReceivedBundleElement { + public: + ReceivedBundleElement(const char* sizePtr) + : sizePtr_(sizePtr) {} - if( !IsMultipleOf4(size) ) - return "element size must be multiple of four"; + friend class ReceivedBundleElementIterator; - return nullptr; - } + bool IsMessage() const { return !IsBundle(); } + bool IsBundle() const { return (Size() > 0 && Contents()[0] == '#'); } - private: - const char *contents_; - osc_bundle_element_size_t size_; + osc_bundle_element_size_t Size() const { return ToInt32(sizePtr_); } + const char* Contents() const { return sizePtr_ + osctap::OSC_SIZEOF_INT32; } - static osc_bundle_element_size_t ValidateSize( osc_bundle_element_size_t size ) - { - if( const char* err = ValidateSizeNoThrow( size ) ) - OSCTAP_THROW( MalformedPacketException( err ) ); + private: + const char* sizePtr_; + }; - return size; - } -}; + class ReceivedBundleElementIterator { + public: + ReceivedBundleElementIterator(const char* sizePtr) + : value_(sizePtr) {} + ReceivedBundleElementIterator operator++() { + Advance(); + return *this; + } -class ReceivedBundleElement{ - public: - ReceivedBundleElement( const char *sizePtr ) - : sizePtr_( sizePtr ) {} + ReceivedBundleElementIterator operator++(int) { + ReceivedBundleElementIterator old(*this); + Advance(); + return old; + } - friend class ReceivedBundleElementIterator; + const ReceivedBundleElement& operator*() const { return value_; } - bool IsMessage() const { return !IsBundle(); } - bool IsBundle() const - { - return (Size() > 0 && Contents()[0] == '#'); - } + const ReceivedBundleElement* operator->() const { return &value_; } - osc_bundle_element_size_t Size() const - { - return ToInt32( sizePtr_ ); - } - const char *Contents() const { return sizePtr_ + osctap::OSC_SIZEOF_INT32; } + friend bool operator==(const ReceivedBundleElementIterator& lhs, const ReceivedBundleElementIterator& rhs); - private: - const char *sizePtr_; -}; + private: + ReceivedBundleElement value_; + void Advance() { value_.sizePtr_ = value_.Contents() + value_.Size(); } -class ReceivedBundleElementIterator{ - public: - ReceivedBundleElementIterator( const char *sizePtr ) - : value_( sizePtr ) {} + bool IsEqualTo(const ReceivedBundleElementIterator& rhs) const { + return value_.sizePtr_ == rhs.value_.sizePtr_; + } + }; - ReceivedBundleElementIterator operator++() - { - Advance(); - return *this; + inline bool operator==(const ReceivedBundleElementIterator& lhs, const ReceivedBundleElementIterator& rhs) { + return lhs.IsEqualTo(rhs); } - ReceivedBundleElementIterator operator++(int) - { - ReceivedBundleElementIterator old( *this ); - Advance(); - return old; + inline bool operator!=(const ReceivedBundleElementIterator& lhs, const ReceivedBundleElementIterator& rhs) { + return !(lhs == rhs); } - 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_.sizePtr_ = value_.Contents() + value_.Size(); } - - bool IsEqualTo( const ReceivedBundleElementIterator& rhs ) const - { - return value_.sizePtr_ == rhs.value_.sizePtr_; - } -}; - -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( ) = default; - ReceivedMessageArgument( const char *typeTagPtr, const char *argumentPtr ) - : typeTagPtr_( typeTagPtr ) - , argumentPtr_( argumentPtr ) {} - - friend class ReceivedMessageArgumentIterator; - - char TypeTag() const OSCTAP_REALTIME { return *typeTagPtr_; } - - // 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 *typeTagPtr_ == TRUE_TYPE_TAG || *typeTagPtr_ == FALSE_TYPE_TAG; } - bool AsBool() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == TRUE_TYPE_TAG ) - return true; - else if( *typeTagPtr_ == FALSE_TYPE_TAG ) - return false; - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - bool AsBoolUnchecked() const OSCTAP_REALTIME - { - // Unchecked: assumes a valid bool argument (tag already checked / message - // validated at construction), so it just reads the tag -- throw-free and - // realtime-safe, like the other *Unchecked accessors. - return *typeTagPtr_ == TRUE_TYPE_TAG; - } + class ReceivedMessageArgument { + public: + ReceivedMessageArgument() = default; + ReceivedMessageArgument(const char* typeTagPtr, const char* argumentPtr) + : typeTagPtr_(typeTagPtr) + , argumentPtr_(argumentPtr) {} - bool IsNil() const { return *typeTagPtr_ == NIL_TYPE_TAG; } - bool IsInfinitum() const { return *typeTagPtr_ == INFINITUM_TYPE_TAG; } - - bool IsInt32() const { return *typeTagPtr_ == INT32_TYPE_TAG; } - int32_t AsInt32() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == INT32_TYPE_TAG ) - return AsInt32Unchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - int32_t AsInt32Unchecked() const OSCTAP_REALTIME - { - return ToInt32( argumentPtr_ ); - } + friend class ReceivedMessageArgumentIterator; - bool IsFloat() const { return *typeTagPtr_ == FLOAT_TYPE_TAG; } - float AsFloat() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == FLOAT_TYPE_TAG ) - return AsFloatUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - float AsFloatUnchecked() const OSCTAP_REALTIME - { - return BitCast( ToUInt32( argumentPtr_ ) ); - } + char TypeTag() const OSCTAP_REALTIME { return *typeTagPtr_; } - bool IsChar() const { return *typeTagPtr_ == CHAR_TYPE_TAG; } - char AsChar() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == CHAR_TYPE_TAG ) - return AsCharUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - char AsCharUnchecked() const OSCTAP_REALTIME - { - return (char)ToInt32( argumentPtr_ ); - } + // 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 IsRgbaColor() const { return *typeTagPtr_ == RGBA_COLOR_TYPE_TAG; } - uint32_t AsRgbaColor() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == RGBA_COLOR_TYPE_TAG ) - return AsRgbaColorUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - uint32_t AsRgbaColorUnchecked() const OSCTAP_REALTIME - { - return ToUInt32( argumentPtr_ ); - } + bool IsBool() const { return *typeTagPtr_ == TRUE_TYPE_TAG || *typeTagPtr_ == FALSE_TYPE_TAG; } + bool AsBool() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == TRUE_TYPE_TAG) + return true; + else if (*typeTagPtr_ == FALSE_TYPE_TAG) + return false; + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + bool AsBoolUnchecked() const OSCTAP_REALTIME { + // Unchecked: assumes a valid bool argument (tag already checked / message + // validated at construction), so it just reads the tag -- throw-free and + // realtime-safe, like the other *Unchecked accessors. + return *typeTagPtr_ == TRUE_TYPE_TAG; + } - bool IsMidiMessage() const { return *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG; } - uint32_t AsMidiMessage() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG ) - return AsMidiMessageUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - uint32_t AsMidiMessageUnchecked() const OSCTAP_REALTIME - { - return ToUInt32( argumentPtr_ ); - } + bool IsNil() const { return *typeTagPtr_ == NIL_TYPE_TAG; } + bool IsInfinitum() const { return *typeTagPtr_ == INFINITUM_TYPE_TAG; } + + bool IsInt32() const { return *typeTagPtr_ == INT32_TYPE_TAG; } + int32_t AsInt32() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == INT32_TYPE_TAG) + return AsInt32Unchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + int32_t AsInt32Unchecked() const OSCTAP_REALTIME { return ToInt32(argumentPtr_); } + + bool IsFloat() const { return *typeTagPtr_ == FLOAT_TYPE_TAG; } + float AsFloat() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == FLOAT_TYPE_TAG) + return AsFloatUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + float AsFloatUnchecked() const OSCTAP_REALTIME { return BitCast(ToUInt32(argumentPtr_)); } + + bool IsChar() const { return *typeTagPtr_ == CHAR_TYPE_TAG; } + char AsChar() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == CHAR_TYPE_TAG) + return AsCharUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + char AsCharUnchecked() const OSCTAP_REALTIME { return (char)ToInt32(argumentPtr_); } + + bool IsRgbaColor() const { return *typeTagPtr_ == RGBA_COLOR_TYPE_TAG; } + uint32_t AsRgbaColor() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == RGBA_COLOR_TYPE_TAG) + return AsRgbaColorUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + uint32_t AsRgbaColorUnchecked() const OSCTAP_REALTIME { return ToUInt32(argumentPtr_); } + + bool IsMidiMessage() const { return *typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG; } + uint32_t AsMidiMessage() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == MIDI_MESSAGE_TYPE_TAG) + return AsMidiMessageUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + uint32_t AsMidiMessageUnchecked() const OSCTAP_REALTIME { return ToUInt32(argumentPtr_); } + + bool IsInt64() const { return *typeTagPtr_ == INT64_TYPE_TAG; } + int64_t AsInt64() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == INT64_TYPE_TAG) + return AsInt64Unchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + int64_t AsInt64Unchecked() const OSCTAP_REALTIME { return ToInt64(argumentPtr_); } + + bool IsTimeTag() const { return *typeTagPtr_ == TIME_TAG_TYPE_TAG; } + uint64_t AsTimeTag() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == TIME_TAG_TYPE_TAG) + return AsTimeTagUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + uint64_t AsTimeTagUnchecked() const OSCTAP_REALTIME { return ToUInt64(argumentPtr_); } + + bool IsDouble() const { return *typeTagPtr_ == DOUBLE_TYPE_TAG; } + double AsDouble() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == DOUBLE_TYPE_TAG) + return AsDoubleUnchecked(); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + double AsDoubleUnchecked() const OSCTAP_REALTIME { return BitCast(ToUInt64(argumentPtr_)); } + + bool IsString() const { return *typeTagPtr_ == STRING_TYPE_TAG; } + const char* AsString() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == STRING_TYPE_TAG) + return argumentPtr_; + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + const char* AsStringUnchecked() const OSCTAP_REALTIME { return argumentPtr_; } + + bool IsSymbol() const { return *typeTagPtr_ == SYMBOL_TYPE_TAG; } + const char* AsSymbol() const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == SYMBOL_TYPE_TAG) + return argumentPtr_; + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + const char* AsSymbolUnchecked() const OSCTAP_REALTIME { return argumentPtr_; } + + bool IsBlob() const { return *typeTagPtr_ == BLOB_TYPE_TAG; } + void AsBlob(const void*& data, osc_bundle_element_size_t& size) const { + if (!typeTagPtr_) + OSCTAP_THROW(MissingArgumentException()); + else if (*typeTagPtr_ == BLOB_TYPE_TAG) + AsBlobUnchecked(data, size); + else + OSCTAP_THROW(WrongArgumentTypeException()); + } + void AsBlobUnchecked(const void*& data, osc_bundle_element_size_t& size) const OSCTAP_REALTIME { + // Like the other *Unchecked accessors, this trusts that the message was + // validated at construction: ReceivedMessage::TryInit() bounds-checks every + // blob (valid size AND within the message), so reading the size here without + // re-validating is safe. That makes this throw-free and realtime-safe -- the + // non-throwing blob accessor for the RT read path. + size = (osc_bundle_element_size_t)ToUInt32(argumentPtr_); + data = (const void*)(argumentPtr_ + osctap::OSC_SIZEOF_INT32); + } - bool IsInt64() const { return *typeTagPtr_ == INT64_TYPE_TAG; } - int64_t AsInt64() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == INT64_TYPE_TAG ) - return AsInt64Unchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - int64_t AsInt64Unchecked() const OSCTAP_REALTIME - { - return ToInt64( argumentPtr_ ); - } + bool IsArrayBegin() const { return *typeTagPtr_ == ARRAY_BEGIN_TYPE_TAG; } + bool IsArrayEnd() const { return *typeTagPtr_ == ARRAY_END_TYPE_TAG; } + // Calculate the number of top-level items in the array. Nested arrays count as one item. + // Only valid at array start. Will throw an exception if IsArrayStart() == false. + std::size_t ComputeArrayItemCount() const { + // it is only valid to call ComputeArrayItemCount when the argument is the array start marker + if (!IsArrayBegin()) + OSCTAP_THROW(WrongArgumentTypeException()); + + std::size_t result = 0; + unsigned int level = 0; + const char* typeTag = typeTagPtr_ + 1; + + // iterate through all type tags. note that ReceivedMessage::Init + // has already checked that the message is well formed. + while (*typeTag) { + switch (*typeTag++) { + case ARRAY_BEGIN_TYPE_TAG: + level += 1; + break; + + case ARRAY_END_TYPE_TAG: + if (level == 0) + return result; + level -= 1; + break; + + default: + if (level == 0) // only count items at level 0 + ++result; + } + } - bool IsTimeTag() const { return *typeTagPtr_ == TIME_TAG_TYPE_TAG; } - uint64_t AsTimeTag() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == TIME_TAG_TYPE_TAG ) - return AsTimeTagUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - uint64_t AsTimeTagUnchecked() const OSCTAP_REALTIME - { - return ToUInt64( argumentPtr_ ); - } + return result; + } - bool IsDouble() const { return *typeTagPtr_ == DOUBLE_TYPE_TAG; } - double AsDouble() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == DOUBLE_TYPE_TAG ) - return AsDoubleUnchecked(); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - double AsDoubleUnchecked() const OSCTAP_REALTIME - { - return BitCast( ToUInt64( argumentPtr_ ) ); - } + private: + const char* typeTagPtr_; + const char* argumentPtr_; + }; - bool IsString() const { return *typeTagPtr_ == STRING_TYPE_TAG; } - const char* AsString() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == STRING_TYPE_TAG ) - return argumentPtr_; - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - const char* AsStringUnchecked() const OSCTAP_REALTIME { return argumentPtr_; } - - bool IsSymbol() const { return *typeTagPtr_ == SYMBOL_TYPE_TAG; } - const char* AsSymbol() const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == SYMBOL_TYPE_TAG ) - return argumentPtr_; - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - const char* AsSymbolUnchecked() const OSCTAP_REALTIME { return argumentPtr_; } - - bool IsBlob() const { return *typeTagPtr_ == BLOB_TYPE_TAG; } - void AsBlob( const void*& data, osc_bundle_element_size_t& size ) const - { - if( !typeTagPtr_ ) - OSCTAP_THROW( MissingArgumentException() ); - else if( *typeTagPtr_ == BLOB_TYPE_TAG ) - AsBlobUnchecked( data, size ); - else - OSCTAP_THROW( WrongArgumentTypeException() ); - } - void AsBlobUnchecked( const void*& data, osc_bundle_element_size_t& size ) const OSCTAP_REALTIME - { - // Like the other *Unchecked accessors, this trusts that the message was - // validated at construction: ReceivedMessage::TryInit() bounds-checks every - // blob (valid size AND within the message), so reading the size here without - // re-validating is safe. That makes this throw-free and realtime-safe -- the - // non-throwing blob accessor for the RT read path. - size = (osc_bundle_element_size_t)ToUInt32( argumentPtr_ ); - data = (const void*)( argumentPtr_ + osctap::OSC_SIZEOF_INT32 ); - } + class ReceivedMessageArgumentIterator { + public: + ReceivedMessageArgumentIterator(const char* typeTags, const char* arguments) + : value_(typeTags, arguments) {} - bool IsArrayBegin() const { return *typeTagPtr_ == ARRAY_BEGIN_TYPE_TAG; } - bool IsArrayEnd() const { return *typeTagPtr_ == ARRAY_END_TYPE_TAG; } - // Calculate the number of top-level items in the array. Nested arrays count as one item. - // Only valid at array start. Will throw an exception if IsArrayStart() == false. - std::size_t ComputeArrayItemCount() const - { - // it is only valid to call ComputeArrayItemCount when the argument is the array start marker - if( !IsArrayBegin() ) - OSCTAP_THROW( WrongArgumentTypeException() ); - - std::size_t result = 0; - unsigned int level = 0; - const char *typeTag = typeTagPtr_ + 1; - - // iterate through all type tags. note that ReceivedMessage::Init - // has already checked that the message is well formed. - while( *typeTag ) { - switch( *typeTag++ ) { - case ARRAY_BEGIN_TYPE_TAG: - level += 1; - break; - - case ARRAY_END_TYPE_TAG: - if(level == 0) - return result; - level -= 1; - break; - - default: - if( level == 0 ) // only count items at level 0 - ++result; + ReceivedMessageArgumentIterator operator++() OSCTAP_REALTIME { + Advance(); + return *this; } - } - - return result; - } - private: - const char *typeTagPtr_; - const char *argumentPtr_; -}; + ReceivedMessageArgumentIterator operator++(int) OSCTAP_REALTIME { + ReceivedMessageArgumentIterator old(*this); + Advance(); + return old; + } + const ReceivedMessageArgument& operator*() const OSCTAP_REALTIME { return value_; } -class ReceivedMessageArgumentIterator{ - public: - ReceivedMessageArgumentIterator( const char *typeTags, const char *arguments ) - : value_( typeTags, arguments ) {} + const ReceivedMessageArgument* operator->() const OSCTAP_REALTIME { return &value_; } - ReceivedMessageArgumentIterator operator++() OSCTAP_REALTIME - { - Advance(); - return *this; - } + friend bool operator==(const ReceivedMessageArgumentIterator& lhs, const ReceivedMessageArgumentIterator& rhs); - ReceivedMessageArgumentIterator operator++(int) OSCTAP_REALTIME - { - ReceivedMessageArgumentIterator old( *this ); - Advance(); - return old; - } + private: + ReceivedMessageArgument value_; - const ReceivedMessageArgument& operator*() const OSCTAP_REALTIME { return value_; } + void Advance() OSCTAP_REALTIME { + if (!value_.typeTagPtr_) + return; - const ReceivedMessageArgument* operator->() const OSCTAP_REALTIME { return &value_; } + switch (*value_.typeTagPtr_++) { + case '\0': + // don't advance past end + --value_.typeTagPtr_; + break; - friend bool operator==(const ReceivedMessageArgumentIterator& lhs, - const ReceivedMessageArgumentIterator& rhs ); + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: - private: - ReceivedMessageArgument value_; + // zero length + break; - void Advance() OSCTAP_REALTIME - { - if( !value_.typeTagPtr_ ) - return; + case INT32_TYPE_TAG: + case FLOAT_TYPE_TAG: + case CHAR_TYPE_TAG: + case RGBA_COLOR_TYPE_TAG: + case MIDI_MESSAGE_TYPE_TAG: - switch( *value_.typeTagPtr_++ ){ - case '\0': - // don't advance past end - --value_.typeTagPtr_; - break; + value_.argumentPtr_ += 4; + break; - case TRUE_TYPE_TAG: - case FALSE_TYPE_TAG: - case NIL_TYPE_TAG: - case INFINITUM_TYPE_TAG: + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: - // zero length - break; + value_.argumentPtr_ += 8; + break; - case INT32_TYPE_TAG: - case FLOAT_TYPE_TAG: - case CHAR_TYPE_TAG: - case RGBA_COLOR_TYPE_TAG: - case MIDI_MESSAGE_TYPE_TAG: + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: - value_.argumentPtr_ += 4; - break; + // we use the unsafe function FindStr4End(char*) here because all of + // the arguments have already been validated in + // ReceivedMessage::Init() below. - case INT64_TYPE_TAG: - case TIME_TAG_TYPE_TAG: - case DOUBLE_TYPE_TAG: + value_.argumentPtr_ = FindStr4End(value_.argumentPtr_); + break; - value_.argumentPtr_ += 8; - break; + case BLOB_TYPE_TAG: { + // treat blob size as an unsigned int for the purposes of this calculation + uint32_t blobSize = ToUInt32(value_.argumentPtr_); + value_.argumentPtr_ = value_.argumentPtr_ + osctap::OSC_SIZEOF_INT32 + RoundUp4(blobSize); + } break; - case STRING_TYPE_TAG: - case SYMBOL_TYPE_TAG: + case ARRAY_BEGIN_TYPE_TAG: + case ARRAY_END_TYPE_TAG: - // we use the unsafe function FindStr4End(char*) here because all of - // the arguments have already been validated in - // ReceivedMessage::Init() below. + // [ 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. - value_.argumentPtr_ = FindStr4End( value_.argumentPtr_ ); - break; + // zero length, don't advance argument ptr + break; - case BLOB_TYPE_TAG: - { - // treat blob size as an unsigned int for the purposes of this calculation - uint32_t blobSize = ToUInt32( value_.argumentPtr_ ); - value_.argumentPtr_ = value_.argumentPtr_ + osctap::OSC_SIZEOF_INT32 + RoundUp4( blobSize ); + default: // unknown type tag + // don't advance + --value_.typeTagPtr_; + break; + } } - break; - - case ARRAY_BEGIN_TYPE_TAG: - case ARRAY_END_TYPE_TAG: - - // [ 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. - - // zero length, don't advance argument ptr - break; - - default: // unknown type tag - // don't advance - --value_.typeTagPtr_; - break; - } - } - - bool IsEqualTo( const ReceivedMessageArgumentIterator& rhs ) const OSCTAP_REALTIME - { - return value_.typeTagPtr_ == rhs.value_.typeTagPtr_; - } -}; - -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() ) - OSCTAP_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 - // same goes for array boundaries - - ReceivedMessageArgumentStream& operator>>( int32_t& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); - - rhs = (*p_++).AsInt32(); - return *this; - } - - ReceivedMessageArgumentStream& operator>>( float& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + bool IsEqualTo(const ReceivedMessageArgumentIterator& rhs) const OSCTAP_REALTIME { + return value_.typeTagPtr_ == rhs.value_.typeTagPtr_; + } + }; - rhs = (*p_++).AsFloat(); - return *this; + inline bool operator==(const ReceivedMessageArgumentIterator& lhs, const ReceivedMessageArgumentIterator& rhs) { + return lhs.IsEqualTo(rhs); } - ReceivedMessageArgumentStream& operator>>( char& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); - - rhs = (*p_++).AsChar(); - return *this; + inline bool operator!=(const ReceivedMessageArgumentIterator& lhs, const ReceivedMessageArgumentIterator& rhs) { + return !(lhs == rhs); } - ReceivedMessageArgumentStream& operator>>( RgbaColor& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + class ReceivedMessageArgumentStream { + friend class ReceivedMessage; + ReceivedMessageArgumentStream(const ReceivedMessageArgumentIterator& begin, + const ReceivedMessageArgumentIterator& end) + : p_(begin) + , end_(end) {} - rhs.value = (*p_++).AsRgbaColor(); - return *this; - } + ReceivedMessageArgumentIterator p_, end_; - ReceivedMessageArgumentStream& operator>>( MidiMessage& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + public: + // end of stream + bool Eos() const { return p_ == end_; } - rhs.value = (*p_++).AsMidiMessage(); - return *this; - } + ReceivedMessageArgumentStream& operator>>(bool& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - ReceivedMessageArgumentStream& operator>>( int64_t& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + rhs = (*p_++).AsBool(); + return *this; + } - rhs = (*p_++).AsInt64(); - return *this; - } + // not sure if it would be useful to stream Nil and Infinitum + // for now it's not possible + // same goes for array boundaries - ReceivedMessageArgumentStream& operator>>( TimeTag& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + ReceivedMessageArgumentStream& operator>>(int32_t& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - rhs.value = (*p_++).AsTimeTag(); - return *this; - } + rhs = (*p_++).AsInt32(); + return *this; + } - ReceivedMessageArgumentStream& operator>>( double& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + ReceivedMessageArgumentStream& operator>>(float& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - rhs = (*p_++).AsDouble(); - return *this; - } + rhs = (*p_++).AsFloat(); + return *this; + } - ReceivedMessageArgumentStream& operator>>( Blob& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + ReceivedMessageArgumentStream& operator>>(char& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - (*p_++).AsBlob( rhs.data, rhs.size ); - return *this; - } + rhs = (*p_++).AsChar(); + return *this; + } - ReceivedMessageArgumentStream& operator>>( const char*& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + ReceivedMessageArgumentStream& operator>>(RgbaColor& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - rhs = (*p_++).AsString(); - return *this; - } + rhs.value = (*p_++).AsRgbaColor(); + return *this; + } - ReceivedMessageArgumentStream& operator>>( Symbol& rhs ) - { - if( Eos() ) - OSCTAP_THROW( MissingArgumentException() ); + ReceivedMessageArgumentStream& operator>>(MidiMessage& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - rhs.value = (*p_++).AsSymbol(); - return *this; - } + rhs.value = (*p_++).AsMidiMessage(); + return *this; + } - ReceivedMessageArgumentStream& operator>>( MessageTerminator& rhs ) - { - (void) rhs; // suppress unused parameter warning + ReceivedMessageArgumentStream& operator>>(int64_t& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - if( !Eos() ) - OSCTAP_THROW( ExcessArgumentException() ); + rhs = (*p_++).AsInt64(); + return *this; + } - return *this; - } -}; - - -class ReceivedMessage{ - public: - // Non-throwing parse + structural validation. Sets all boundary members and - // returns nullptr on success, or a static error string on malformed input. - // Use on no-exceptions / untrusted-input paths: - // ReceivedMessage m; - // if( m.TryInit(data, size) == nullptr ) { /* read m */ } - // This is the single source of truth: the throwing Init() below delegates - // here, as does the non-throwing Validate() / TryValidatePacket() gate for - // untrusted input on no-exceptions builds. - const char* TryInit( const char *message, osc_bundle_element_size_t size ) - { - addressPattern_ = message; - size_ = size; - - if( !IsValidElementSizeValue(size) ) - return "invalid message size"; - - if( size == 0 ) - return "zero length messages not permitted"; - - if( !IsMultipleOf4(size) ) - return "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 - return "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_ != ',' ) - return "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 ){ - return "type tags were not terminated before end of message"; - } - - ++typeTagsBegin_; // advance past initial ',' - - const char *typeTag = typeTagsBegin_; - const char *argument = arguments_; - unsigned int arrayLevel = 0; - - do{ - switch( *typeTag ){ - case TRUE_TYPE_TAG: - case FALSE_TYPE_TAG: - case NIL_TYPE_TAG: - case INFINITUM_TYPE_TAG: - // zero length - break; + ReceivedMessageArgumentStream& operator>>(TimeTag& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - // [ 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. - case ARRAY_BEGIN_TYPE_TAG: - ++arrayLevel; - // (zero length argument data) - break; + rhs.value = (*p_++).AsTimeTag(); + return *this; + } - case ARRAY_END_TYPE_TAG: - if( arrayLevel == 0 ) - return "array close tag ']' without matching open tag '['"; - --arrayLevel; - // (zero length argument data) - break; + ReceivedMessageArgumentStream& operator>>(double& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - 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 ) - return "arguments exceed message size"; - argument += 4; - if( argument > end ) - return "arguments exceed message size"; - break; + rhs = (*p_++).AsDouble(); + return *this; + } - case INT64_TYPE_TAG: - case TIME_TAG_TYPE_TAG: - case DOUBLE_TYPE_TAG: + ReceivedMessageArgumentStream& operator>>(Blob& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - if( argument == end ) - return "arguments exceed message size"; - argument += 8; - if( argument > end ) - return "arguments exceed message size"; - break; + (*p_++).AsBlob(rhs.data, rhs.size); + return *this; + } - case STRING_TYPE_TAG: - case SYMBOL_TYPE_TAG: + ReceivedMessageArgumentStream& operator>>(const char*& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - if( argument == end ) - return "arguments exceed message size"; - argument = FindStr4End( argument, end ); - if( argument == 0 ) - return "unterminated string argument"; - break; + rhs = (*p_++).AsString(); + return *this; + } - case BLOB_TYPE_TAG: - { - if( argument + osctap::OSC_SIZEOF_INT32 > end ) - return "arguments exceed message size"; + ReceivedMessageArgumentStream& operator>>(Symbol& rhs) { + if (Eos()) + OSCTAP_THROW(MissingArgumentException()); - // treat blob size as an unsigned int for the purposes of this calculation - uint32_t blobSize = ToUInt32( argument ); - if( !IsValidElementSizeValue( (osc_bundle_element_size_t)blobSize ) ) - return "invalid blob size"; - - // Compare sizes rather than advancing the pointer first: a huge - // blobSize must not be allowed to overflow the pointer (or RoundUp4) - // and thereby slip past the bounds check. blobData <= end is - // guaranteed by the check above. - const char *blobData = argument + osctap::OSC_SIZEOF_INT32; - if( RoundUp4( blobSize ) > (uint32_t)(end - blobData) ) - return "arguments exceed message size"; - - argument = blobData + RoundUp4( blobSize ); - } - break; + rhs.value = (*p_++).AsSymbol(); + return *this; + } - default: - return "unknown type tag"; - } + ReceivedMessageArgumentStream& operator>>(MessageTerminator& rhs) { + (void)rhs; // suppress unused parameter warning - }while( *++typeTag != '\0' ); - typeTagsEnd_ = typeTag; + if (!Eos()) + OSCTAP_THROW(ExcessArgumentException()); - if( arrayLevel != 0 ) - return "array was not terminated before end of message (expected ']' end of array tag)"; + return *this; } + }; + + class ReceivedMessage { + public: + // Non-throwing parse + structural validation. Sets all boundary members and + // returns nullptr on success, or a static error string on malformed input. + // Use on no-exceptions / untrusted-input paths: + // ReceivedMessage m; + // if( m.TryInit(data, size) == nullptr ) { /* read m */ } + // This is the single source of truth: the throwing Init() below delegates + // here, as does the non-throwing Validate() / TryValidatePacket() gate for + // untrusted input on no-exceptions builds. + const char* TryInit(const char* message, osc_bundle_element_size_t size) { + addressPattern_ = message; + size_ = size; + + if (!IsValidElementSizeValue(size)) + return "invalid message size"; + + if (size == 0) + return "zero length messages not permitted"; + + if (!IsMultipleOf4(size)) + return "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 + return "unterminated address pattern"; + } - // These invariants should be guaranteed by the above code. - // we depend on them in the implementation of ArgumentCount() + if (typeTagsBegin_ == end) { + // message consists of only the address pattern - no arguments or type tags. + typeTagsBegin_ = 0; + typeTagsEnd_ = 0; + arguments_ = 0; + } + else { + if (*typeTagsBegin_ != ',') + return "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) { + return "type tags were not terminated before end of message"; + } + + ++typeTagsBegin_; // advance past initial ',' + + const char* typeTag = typeTagsBegin_; + const char* argument = arguments_; + unsigned int arrayLevel = 0; + + do { + switch (*typeTag) { + case TRUE_TYPE_TAG: + case FALSE_TYPE_TAG: + case NIL_TYPE_TAG: + case INFINITUM_TYPE_TAG: + // zero length + break; + + // [ 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. + case ARRAY_BEGIN_TYPE_TAG: + ++arrayLevel; + // (zero length argument data) + break; + + case ARRAY_END_TYPE_TAG: + if (arrayLevel == 0) + return "array close tag ']' without matching open tag '['"; + --arrayLevel; + // (zero length argument data) + 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) + return "arguments exceed message size"; + argument += 4; + if (argument > end) + return "arguments exceed message size"; + break; + + case INT64_TYPE_TAG: + case TIME_TAG_TYPE_TAG: + case DOUBLE_TYPE_TAG: + + if (argument == end) + return "arguments exceed message size"; + argument += 8; + if (argument > end) + return "arguments exceed message size"; + break; + + case STRING_TYPE_TAG: + case SYMBOL_TYPE_TAG: + + if (argument == end) + return "arguments exceed message size"; + argument = FindStr4End(argument, end); + if (argument == 0) + return "unterminated string argument"; + break; + + case BLOB_TYPE_TAG: { + if (argument + osctap::OSC_SIZEOF_INT32 > end) + return "arguments exceed message size"; + + // treat blob size as an unsigned int for the purposes of this calculation + uint32_t blobSize = ToUInt32(argument); + if (!IsValidElementSizeValue((osc_bundle_element_size_t)blobSize)) + return "invalid blob size"; + + // Compare sizes rather than advancing the pointer first: a huge + // blobSize must not be allowed to overflow the pointer (or RoundUp4) + // and thereby slip past the bounds check. blobData <= end is + // guaranteed by the check above. + const char* blobData = argument + osctap::OSC_SIZEOF_INT32; + if (RoundUp4(blobSize) > (uint32_t)(end - blobData)) + return "arguments exceed message size"; + + argument = blobData + RoundUp4(blobSize); + } break; + + default: + return "unknown type tag"; + } + + } while (*++typeTag != '\0'); + typeTagsEnd_ = typeTag; + + if (arrayLevel != 0) + return "array was not terminated before end of message (expected ']' end of array tag)"; + } + + // These invariants should be guaranteed by the above code. + // we depend on them in the implementation of ArgumentCount() #ifndef NDEBUG - std::ptrdiff_t argumentCount = typeTagsEnd_ - typeTagsBegin_; - assert( argumentCount >= 0 ); - assert( argumentCount <= OSC_INT32_MAX ); + std::ptrdiff_t argumentCount = typeTagsEnd_ - typeTagsBegin_; + assert(argumentCount >= 0); + assert(argumentCount <= OSC_INT32_MAX); #endif - } - - return nullptr; - } + } - // Throwing wrapper used by the constructors (preserves the original API). - void Init( const char *message, osc_bundle_element_size_t size ) - { - if( const char* err = TryInit( message, size ) ) - OSCTAP_THROW( MalformedMessageException( err ) ); - } - public: - // Default-constructs an empty (invalid) message for use with the non-throwing - // TryInit() below. Reading it before a successful TryInit() is undefined. - ReceivedMessage() - : addressPattern_( nullptr ), typeTagsBegin_( nullptr ) - , typeTagsEnd_( nullptr ), arguments_( nullptr ), size_( 0 ) {} - - explicit ReceivedMessage( const ReceivedPacket& packet ) - : addressPattern_( packet.Contents() ), size_{packet.Size()} - { - Init( packet.Contents(), packet.Size() ); - } - explicit ReceivedMessage( const ReceivedBundleElement& bundleElement ) - : addressPattern_( bundleElement.Contents() ), size_{bundleElement.Size()} - { - Init( bundleElement.Contents(), bundleElement.Size() ); - } + return nullptr; + } - // Non-throwing structural validation of a message body, without retaining the - // parsed object. nullptr == well-formed. - static const char* Validate( const char *message, osc_bundle_element_size_t size ) - { - ReceivedMessage m; - return m.TryInit( message, size ); - } - const char *AddressPattern() const OSCTAP_REALTIME { return addressPattern_; } + // Throwing wrapper used by the constructors (preserves the original API). + void Init(const char* message, osc_bundle_element_size_t size) { + if (const char* err = TryInit(message, size)) + OSCTAP_THROW(MalformedMessageException(err)); + } - // Support for non-standard SuperCollider integer address patterns: - bool AddressPatternIsUInt32() const - { - return (addressPattern_[0] == '\0'); - } - uint32_t AddressPatternAsUInt32() const - { - return ToUInt32( addressPattern_ ); - } + public: + // Default-constructs an empty (invalid) message for use with the non-throwing + // TryInit() below. Reading it before a successful TryInit() is undefined. + ReceivedMessage() + : addressPattern_(nullptr) + , typeTagsBegin_(nullptr) + , typeTagsEnd_(nullptr) + , arguments_(nullptr) + , size_(0) {} + + explicit ReceivedMessage(const ReceivedPacket& packet) + : addressPattern_(packet.Contents()) + , size_{packet.Size()} { + Init(packet.Contents(), packet.Size()); + } + explicit ReceivedMessage(const ReceivedBundleElement& bundleElement) + : addressPattern_(bundleElement.Contents()) + , size_{bundleElement.Size()} { + Init(bundleElement.Contents(), bundleElement.Size()); + } - uint32_t ArgumentCount() const OSCTAP_REALTIME { return static_cast(typeTagsEnd_ - typeTagsBegin_); } + // Non-throwing structural validation of a message body, without retaining the + // parsed object. nullptr == well-formed. + static const char* Validate(const char* message, osc_bundle_element_size_t size) { + ReceivedMessage m; + return m.TryInit(message, size); + } + const char* AddressPattern() const OSCTAP_REALTIME { return addressPattern_; } - const char *TypeTags() const OSCTAP_REALTIME { return typeTagsBegin_; } + // Support for non-standard SuperCollider integer address patterns: + bool AddressPatternIsUInt32() const { return (addressPattern_[0] == '\0'); } + uint32_t AddressPatternAsUInt32() const { return ToUInt32(addressPattern_); } + uint32_t ArgumentCount() const OSCTAP_REALTIME { return static_cast(typeTagsEnd_ - typeTagsBegin_); } - typedef ReceivedMessageArgumentIterator const_iterator; + const char* TypeTags() const OSCTAP_REALTIME { return typeTagsBegin_; } - ReceivedMessageArgumentIterator ArgumentsBegin() const OSCTAP_REALTIME - { - return ReceivedMessageArgumentIterator( typeTagsBegin_, arguments_ ); - } + typedef ReceivedMessageArgumentIterator const_iterator; - ReceivedMessageArgumentIterator ArgumentsEnd() const OSCTAP_REALTIME - { - return ReceivedMessageArgumentIterator( typeTagsEnd_, 0 ); - } + ReceivedMessageArgumentIterator ArgumentsBegin() const OSCTAP_REALTIME { + return ReceivedMessageArgumentIterator(typeTagsBegin_, arguments_); + } - ReceivedMessageArgumentStream ArgumentStream() const - { - return ReceivedMessageArgumentStream( ArgumentsBegin(), ArgumentsEnd() ); - } + ReceivedMessageArgumentIterator ArgumentsEnd() const OSCTAP_REALTIME { + return ReceivedMessageArgumentIterator(typeTagsEnd_, 0); + } - osc_bundle_element_size_t size() const { return size_; } - const char* data() const { return addressPattern_; } - - private: - friend class OwnedMessage; - - explicit ReceivedMessage( - const char *addressPattern, - const char *typeTagsBegin, - const char *typeTagsEnd, - const char *arguments, - const osc_bundle_element_size_t size): - addressPattern_{addressPattern}, - typeTagsBegin_{typeTagsBegin}, - typeTagsEnd_{typeTagsEnd}, - arguments_{arguments}, - size_{size} - { + ReceivedMessageArgumentStream ArgumentStream() const { + return ReceivedMessageArgumentStream(ArgumentsBegin(), ArgumentsEnd()); + } - } + osc_bundle_element_size_t size() const { return size_; } + const char* data() const { return addressPattern_; } - const char *addressPattern_; - const char *typeTagsBegin_; - const char *typeTagsEnd_; - const char *arguments_; - osc_bundle_element_size_t size_; // not const: TryInit() (re)assigns during parse -}; + private: + friend class OwnedMessage; -#ifndef OSCTAP_FREESTANDING -// OwnedMessage copies the message into a std::vector. It is hosted-only and -// excluded from the freestanding profile (no dynamic allocation / no ). -class OwnedMessage -{ - explicit OwnedMessage(const ReceivedMessage& other): - buffer_(other.AddressPattern(), other.AddressPattern() + other.size()), - message_(buffer_.data(), - (other.typeTagsBegin_ ? buffer_.data() + (other.typeTagsBegin_ - other.addressPattern_) : (const char*)nullptr), - (other.typeTagsEnd_ ? buffer_.data() + (other.typeTagsEnd_ - other.addressPattern_) : (const char*)nullptr), - (other.arguments_ ? buffer_.data() + (other.arguments_ - other.addressPattern_) : (const char*)nullptr), - other.size()) - { + explicit ReceivedMessage(const char* addressPattern, const char* typeTagsBegin, const char* typeTagsEnd, + const char* arguments, const osc_bundle_element_size_t size) + : addressPattern_{addressPattern} + , typeTagsBegin_{typeTagsBegin} + , typeTagsEnd_{typeTagsEnd} + , arguments_{arguments} + , size_{size} {} - } + const char* addressPattern_; + const char* typeTagsBegin_; + const char* typeTagsEnd_; + const char* arguments_; + osc_bundle_element_size_t size_; // not const: TryInit() (re)assigns during parse + }; - operator const ReceivedMessage&() { return message_; } - private: - std::vector buffer_; - ReceivedMessage message_; -}; +#ifndef OSCTAP_FREESTANDING + // OwnedMessage copies the message into a std::vector. It is hosted-only and + // excluded from the freestanding profile (no dynamic allocation / no ). + class OwnedMessage { + explicit OwnedMessage(const ReceivedMessage& other) + : buffer_(other.AddressPattern(), other.AddressPattern() + other.size()) + , message_(buffer_.data(), + (other.typeTagsBegin_ ? buffer_.data() + (other.typeTagsBegin_ - other.addressPattern_) + : (const char*)nullptr), + (other.typeTagsEnd_ ? buffer_.data() + (other.typeTagsEnd_ - other.addressPattern_) + : (const char*)nullptr), + (other.arguments_ ? buffer_.data() + (other.arguments_ - other.addressPattern_) + : (const char*)nullptr), + other.size()) {} + + operator const ReceivedMessage&() { return message_; } + + private: + std::vector buffer_; + ReceivedMessage message_; + }; #endif // OSCTAP_FREESTANDING -class ReceivedBundle{ - public: - // Non-throwing parse + structural validation of the bundle framing (size, - // "#bundle" tag, and element-size table). Returns nullptr on success (members - // set), or a static error string. Single source of truth; the throwing Init() - // delegates here. Note: this validates the bundle's own framing, not the - // contents of each element -- use TryValidatePacket() for a full recursive - // check before reading untrusted bundles on a no-exceptions build. - const char* TryInit( const char *bundle, osc_bundle_element_size_t size ) - { - elementCount_ = 0; - - if( !IsValidElementSizeValue(size) ) - return "invalid bundle size"; + class ReceivedBundle { + public: + // Non-throwing parse + structural validation of the bundle framing (size, + // "#bundle" tag, and element-size table). Returns nullptr on success (members + // set), or a static error string. Single source of truth; the throwing Init() + // delegates here. Note: this validates the bundle's own framing, not the + // contents of each element -- use TryValidatePacket() for a full recursive + // check before reading untrusted bundles on a no-exceptions build. + const char* TryInit(const char* bundle, osc_bundle_element_size_t size) { + elementCount_ = 0; - if( size < 16 ) - return "packet too short for bundle"; + if (!IsValidElementSizeValue(size)) + return "invalid bundle size"; - if( !IsMultipleOf4(size) ) - return "bundle size must be multiple of four"; + if (size < 16) + return "packet too short for bundle"; - if( bundle[0] != '#' - || bundle[1] != 'b' - || bundle[2] != 'u' - || bundle[3] != 'n' - || bundle[4] != 'd' - || bundle[5] != 'l' - || bundle[6] != 'e' - || bundle[7] != '\0' ) - return "bad bundle address pattern"; + if (!IsMultipleOf4(size)) + return "bundle size must be multiple of four"; - end_ = bundle + size; + if (bundle[0] != '#' || bundle[1] != 'b' || bundle[2] != 'u' || bundle[3] != 'n' || bundle[4] != 'd' + || bundle[5] != 'l' || bundle[6] != 'e' || bundle[7] != '\0') + return "bad bundle address pattern"; - timeTag_ = bundle + 8; + end_ = bundle + size; - const char *p = timeTag_ + 8; + timeTag_ = bundle + 8; - while( p < end_ ){ - if( p + osctap::OSC_SIZEOF_INT32 > end_ ) - return "packet too short for elementSize"; + const char* p = timeTag_ + 8; - // treat element size as an unsigned int for the purposes of this calculation - uint32_t elementSize = ToUInt32( p ); - if( (elementSize & ((uint32_t)0x03)) != 0 ) - return "bundle element size must be multiple of four"; + while (p < end_) { + if (p + osctap::OSC_SIZEOF_INT32 > end_) + return "packet too short for elementSize"; - // Compare sizes rather than advancing the pointer first, so that a huge - // elementSize can't overflow the pointer and slip past the bounds check. - const char *elementData = p + osctap::OSC_SIZEOF_INT32; - if( elementSize > (uint32_t)(end_ - elementData) ) - return "packet too short for bundle element"; + // treat element size as an unsigned int for the purposes of this calculation + uint32_t elementSize = ToUInt32(p); + if ((elementSize & ((uint32_t)0x03)) != 0) + return "bundle element size must be multiple of four"; - p = elementData + elementSize; + // Compare sizes rather than advancing the pointer first, so that a huge + // elementSize can't overflow the pointer and slip past the bounds check. + const char* elementData = p + osctap::OSC_SIZEOF_INT32; + if (elementSize > (uint32_t)(end_ - elementData)) + return "packet too short for bundle element"; - ++elementCount_; - } + p = elementData + elementSize; - if( p != end_ ) - return "bundle contents did not match bundle size"; - - return nullptr; - } - - // Throwing wrapper used by the constructors (preserves the original API). - void Init( const char *bundle, osc_bundle_element_size_t size ) - { - if( const char* err = TryInit( bundle, size ) ) - OSCTAP_THROW( MalformedBundleException( err ) ); - } + ++elementCount_; + } - // Default-constructs an empty (invalid) bundle for use with TryInit(). - ReceivedBundle() - : timeTag_( nullptr ), end_( nullptr ), elementCount_( 0 ) {} + if (p != end_) + return "bundle contents did not match bundle size"; - explicit ReceivedBundle( const ReceivedPacket& packet ) - : elementCount_( 0 ) - { - Init( packet.Contents(), packet.Size() ); - } - explicit ReceivedBundle( const ReceivedBundleElement& bundleElement ) - : elementCount_( 0 ) - { - Init( bundleElement.Contents(), bundleElement.Size() ); - } - - // Non-throwing structural validation of the bundle framing, without retaining - // the parsed object. nullptr == well-formed framing. - static const char* Validate( const char *bundle, osc_bundle_element_size_t size ) - { - ReceivedBundle b; - return b.TryInit( bundle, size ); - } + return nullptr; + } - uint64_t TimeTag() const - { - return ToUInt64( timeTag_ ); - } + // Throwing wrapper used by the constructors (preserves the original API). + void Init(const char* bundle, osc_bundle_element_size_t size) { + if (const char* err = TryInit(bundle, size)) + OSCTAP_THROW(MalformedBundleException(err)); + } - uint32_t ElementCount() const { return elementCount_; } + // Default-constructs an empty (invalid) bundle for use with TryInit(). + ReceivedBundle() + : timeTag_(nullptr) + , end_(nullptr) + , elementCount_(0) {} - typedef ReceivedBundleElementIterator const_iterator; + explicit ReceivedBundle(const ReceivedPacket& packet) + : elementCount_(0) { + Init(packet.Contents(), packet.Size()); + } + explicit ReceivedBundle(const ReceivedBundleElement& bundleElement) + : elementCount_(0) { + Init(bundleElement.Contents(), bundleElement.Size()); + } - ReceivedBundleElementIterator ElementsBegin() const - { - return ReceivedBundleElementIterator( timeTag_ + 8 ); - } + // Non-throwing structural validation of the bundle framing, without retaining + // the parsed object. nullptr == well-formed framing. + static const char* Validate(const char* bundle, osc_bundle_element_size_t size) { + ReceivedBundle b; + return b.TryInit(bundle, size); + } - ReceivedBundleElementIterator ElementsEnd() const - { - return ReceivedBundleElementIterator( end_ ); - } + uint64_t TimeTag() const { return ToUInt64(timeTag_); } + + uint32_t 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_; + uint32_t elementCount_; + }; + + inline auto begin(const osctap::ReceivedMessage& mes) { + return mes.ArgumentsBegin(); + } + + inline auto end(const osctap::ReceivedMessage& mes) { + return mes.ArgumentsEnd(); + } + + // Non-throwing, recursive validation of a complete OSC packet -- a message, or a + // bundle whose every element is itself well-formed, recursively. Returns nullptr + // if [data, data+size) is fully well-formed and therefore safe to construct *and + // read in full* without any OSCTAP_THROW firing; otherwise a static error string. + // + // This is the gate to use before handling untrusted input on a no-exceptions / + // freestanding build, where a malformed packet would otherwise hit the fatal + // handler (abort) during construction or iteration: + // + // if( osctap::TryValidatePacket(buf, n) == nullptr ) { + // osctap::ReceivedPacket p(buf, n); // won't abort + // ... read the message / iterate the bundle ... + // } else { + // ... drop the datagram ... + // } + // + // maxBundleNestingDepth bounds the recursion so a deeply-nested bundle from an + // attacker cannot exhaust the stack (mirrors OscPacketListener's dispatch bound). + inline const char* TryValidatePacket(const char* data, osc_bundle_element_size_t size, + unsigned int maxBundleNestingDepth = 64) { + if (const char* err = ReceivedPacket::ValidateSizeNoThrow(size)) + return err; + + if (size > 0 && data[0] == '#') { + // Bundle: validate the framing, then recurse into each element's contents. + if (const char* err = ReceivedBundle::Validate(data, size)) + return err; + if (maxBundleNestingDepth == 0) + return "bundle nested too deeply"; + + const char* end = data + size; + const char* p = data + 16; // skip "#bundle\0" (8) + time tag (8) + while (p < end) { + // Framing was validated above: elementSize is multiple-of-4 and in bounds. + uint32_t elementSize = ToUInt32(p); + const char* elementData = p + osctap::OSC_SIZEOF_INT32; + if (const char* err = TryValidatePacket(elementData, (osc_bundle_element_size_t)elementSize, + maxBundleNestingDepth - 1)) + return err; + p = elementData + elementSize; + } + return nullptr; + } - private: - const char *timeTag_; - const char *end_; - uint32_t elementCount_; -}; - - -inline auto begin(const osctap::ReceivedMessage& mes) -{ - return mes.ArgumentsBegin(); -} - -inline auto end(const osctap::ReceivedMessage& mes) -{ - return mes.ArgumentsEnd(); -} - - -// Non-throwing, recursive validation of a complete OSC packet -- a message, or a -// bundle whose every element is itself well-formed, recursively. Returns nullptr -// if [data, data+size) is fully well-formed and therefore safe to construct *and -// read in full* without any OSCTAP_THROW firing; otherwise a static error string. -// -// This is the gate to use before handling untrusted input on a no-exceptions / -// freestanding build, where a malformed packet would otherwise hit the fatal -// handler (abort) during construction or iteration: -// -// if( osctap::TryValidatePacket(buf, n) == nullptr ) { -// osctap::ReceivedPacket p(buf, n); // won't abort -// ... read the message / iterate the bundle ... -// } else { -// ... drop the datagram ... -// } -// -// maxBundleNestingDepth bounds the recursion so a deeply-nested bundle from an -// attacker cannot exhaust the stack (mirrors OscPacketListener's dispatch bound). -inline const char* TryValidatePacket( const char *data, osc_bundle_element_size_t size, - unsigned int maxBundleNestingDepth = 64 ) -{ - if( const char* err = ReceivedPacket::ValidateSizeNoThrow( size ) ) - return err; - - if( size > 0 && data[0] == '#' ){ - // Bundle: validate the framing, then recurse into each element's contents. - if( const char* err = ReceivedBundle::Validate( data, size ) ) - return err; - if( maxBundleNestingDepth == 0 ) - return "bundle nested too deeply"; - - const char *end = data + size; - const char *p = data + 16; // skip "#bundle\0" (8) + time tag (8) - while( p < end ){ - // Framing was validated above: elementSize is multiple-of-4 and in bounds. - uint32_t elementSize = ToUInt32( p ); - const char *elementData = p + osctap::OSC_SIZEOF_INT32; - if( const char* err = TryValidatePacket( elementData, - (osc_bundle_element_size_t)elementSize, maxBundleNestingDepth - 1 ) ) - return err; - p = elementData + elementSize; + // Message. + return ReceivedMessage::Validate(data, size); } - return nullptr; - } - - // Message. - return ReceivedMessage::Validate( data, size ); -} } // namespace osctap - - // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; diff --git a/osctap/osc/OscStreamFraming.h b/osctap/osc/OscStreamFraming.h index 39dfcb5..7783bf2 100644 --- a/osctap/osc/OscStreamFraming.h +++ b/osctap/osc/OscStreamFraming.h @@ -51,136 +51,132 @@ SLIP framing (the OSC 1.1 nominated alternative) is intentionally deferred. */ -namespace osctap{ - -enum { OSC_STREAM_FRAME_HEADER_SIZE = 4 }; - -// Default cap on a single framed packet (and therefore on the per-connection -// reassembly buffer). 64 KiB comfortably exceeds any normal OSC packet while -// bounding the memory a peer can make you hold. Override per-deframer. -enum { OSC_DEFAULT_MAX_FRAME_SIZE = 64 * 1024 }; - -// Encoder: write the 4-byte big-endian length prefix for a `packetSize`-byte -// packet into `header`. The caller then writes the payload. Pure, non-allocating, -// freestanding-safe. (The socket transmit path writes the header then the payload -// directly, avoiding a copy; FrameOscPacket() below is the one-buffer convenience.) -inline void WriteOscStreamFrameHeader( char header[OSC_STREAM_FRAME_HEADER_SIZE], uint32_t packetSize ) -{ - FromUInt32( header, packetSize ); -} - -// Convenience: write [4-byte length][payload] contiguously into `out` (capacity -// `outCapacity`). Returns the framed size (4 + packetSize), or 0 if it does not -// fit. Non-allocating. -inline std::size_t FrameOscPacket( const char* packet, uint32_t packetSize, - char* out, std::size_t outCapacity ) -{ - if( (std::size_t)packetSize + OSC_STREAM_FRAME_HEADER_SIZE > outCapacity ) - return 0; - WriteOscStreamFrameHeader( out, packetSize ); - if( packetSize ) - std::memcpy( out + OSC_STREAM_FRAME_HEADER_SIZE, packet, packetSize ); - return (std::size_t)packetSize + OSC_STREAM_FRAME_HEADER_SIZE; -} - -// Streaming decoder: feed it received bytes in whatever chunks the transport -// delivers; it emits each complete OSC packet exactly once. One instance per -// connection (it holds that connection's reassembly state). -// -// Non-throwing. Allocates at most one bounded reassembly buffer (<= maxFrameSize), -// and only when a packet straddles a read boundary -- packets contained whole in a -// single chunk are dispatched in place with no copy. -class OscStreamDeframer{ -public: - explicit OscStreamDeframer( uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE ) - : maxFrameSize_( maxFrameSize ) - , frameSize_( 0 ) - , headerFill_( 0 ) - , haveHeader_( false ) {} - - uint32_t MaxFrameSize() const { return maxFrameSize_; } - - // Feed `size` bytes received from the stream. For each complete packet, calls - // sink(const char* packet, uint32_t packetSize). Returns true normally; returns - // false as soon as a frame header announces a size greater than maxFrameSize() - // -- a protocol violation / DoS attempt, on which the caller should drop the - // connection (the deframer's state is then undefined until Reset()). - template - bool Consume( const char* data, std::size_t size, Sink&& sink ) - { - const char* p = data; - const char* const end = data + size; - - while( p < end ){ - if( !haveHeader_ ){ - if( headerFill_ == 0 && (std::size_t)(end - p) >= OSC_STREAM_FRAME_HEADER_SIZE ){ - // whole header present contiguously, nothing carried over - frameSize_ = ToUInt32( p ); - p += OSC_STREAM_FRAME_HEADER_SIZE; - }else{ - // accumulate the length prefix across reads - while( headerFill_ < OSC_STREAM_FRAME_HEADER_SIZE && p < end ) - header_[headerFill_++] = *p++; - if( headerFill_ < OSC_STREAM_FRAME_HEADER_SIZE ) - return true; // need more bytes to complete the header - frameSize_ = ToUInt32( header_ ); - headerFill_ = 0; - } +namespace osctap { - if( frameSize_ > maxFrameSize_ ) - return false; // oversized / hostile frame + enum { OSC_STREAM_FRAME_HEADER_SIZE = 4 }; - // A zero-length frame is structurally valid framing; it is - // forwarded as an empty packet, which the OSC layer - // (ReceivedPacket) then rejects -- framing doesn't judge OSC - // validity, it only delimits packets. + // Default cap on a single framed packet (and therefore on the per-connection + // reassembly buffer). 64 KiB comfortably exceeds any normal OSC packet while + // bounding the memory a peer can make you hold. Override per-deframer. + enum { OSC_DEFAULT_MAX_FRAME_SIZE = 64 * 1024 }; - haveHeader_ = true; - } + // Encoder: write the 4-byte big-endian length prefix for a `packetSize`-byte + // packet into `header`. The caller then writes the payload. Pure, non-allocating, + // freestanding-safe. (The socket transmit path writes the header then the payload + // directly, avoiding a copy; FrameOscPacket() below is the one-buffer convenience.) + inline void WriteOscStreamFrameHeader(char header[OSC_STREAM_FRAME_HEADER_SIZE], uint32_t packetSize) { + FromUInt32(header, packetSize); + } + + // Convenience: write [4-byte length][payload] contiguously into `out` (capacity + // `outCapacity`). Returns the framed size (4 + packetSize), or 0 if it does not + // fit. Non-allocating. + inline std::size_t FrameOscPacket(const char* packet, uint32_t packetSize, char* out, std::size_t outCapacity) { + if ((std::size_t)packetSize + OSC_STREAM_FRAME_HEADER_SIZE > outCapacity) + return 0; + WriteOscStreamFrameHeader(out, packetSize); + if (packetSize) + std::memcpy(out + OSC_STREAM_FRAME_HEADER_SIZE, packet, packetSize); + return (std::size_t)packetSize + OSC_STREAM_FRAME_HEADER_SIZE; + } + + // Streaming decoder: feed it received bytes in whatever chunks the transport + // delivers; it emits each complete OSC packet exactly once. One instance per + // connection (it holds that connection's reassembly state). + // + // Non-throwing. Allocates at most one bounded reassembly buffer (<= maxFrameSize), + // and only when a packet straddles a read boundary -- packets contained whole in a + // single chunk are dispatched in place with no copy. + class OscStreamDeframer { + public: + explicit OscStreamDeframer(uint32_t maxFrameSize = OSC_DEFAULT_MAX_FRAME_SIZE) + : maxFrameSize_(maxFrameSize) + , frameSize_(0) + , headerFill_(0) + , haveHeader_(false) {} + + uint32_t MaxFrameSize() const { return maxFrameSize_; } + + // Feed `size` bytes received from the stream. For each complete packet, calls + // sink(const char* packet, uint32_t packetSize). Returns true normally; returns + // false as soon as a frame header announces a size greater than maxFrameSize() + // -- a protocol violation / DoS attempt, on which the caller should drop the + // connection (the deframer's state is then undefined until Reset()). + template + bool Consume(const char* data, std::size_t size, Sink&& sink) { + const char* p = data; + const char* const end = data + size; + + while (p < end) { + if (!haveHeader_) { + if (headerFill_ == 0 && (std::size_t)(end - p) >= OSC_STREAM_FRAME_HEADER_SIZE) { + // whole header present contiguously, nothing carried over + frameSize_ = ToUInt32(p); + p += OSC_STREAM_FRAME_HEADER_SIZE; + } + else { + // accumulate the length prefix across reads + while (headerFill_ < OSC_STREAM_FRAME_HEADER_SIZE && p < end) + header_[headerFill_++] = *p++; + if (headerFill_ < OSC_STREAM_FRAME_HEADER_SIZE) + return true; // need more bytes to complete the header + frameSize_ = ToUInt32(header_); + headerFill_ = 0; + } + + if (frameSize_ > maxFrameSize_) + return false; // oversized / hostile frame + + // A zero-length frame is structurally valid framing; it is + // forwarded as an empty packet, which the OSC layer + // (ReceivedPacket) then rejects -- framing doesn't judge OSC + // validity, it only delimits packets. + + haveHeader_ = true; + } - // accumulate / dispatch the payload of frameSize_ bytes - if( buffer_.empty() && (std::size_t)(end - p) >= frameSize_ ){ - // whole payload present contiguously -> dispatch in place, no copy - sink( p, frameSize_ ); - p += frameSize_; - haveHeader_ = false; - }else{ - const std::size_t still = (std::size_t)frameSize_ - buffer_.size(); - const std::size_t avail = (std::size_t)(end - p); - const std::size_t take = avail < still ? avail : still; - buffer_.insert( buffer_.end(), p, p + take ); - p += take; - if( buffer_.size() == (std::size_t)frameSize_ ){ - sink( buffer_.data(), frameSize_ ); - buffer_.clear(); + // accumulate / dispatch the payload of frameSize_ bytes + if (buffer_.empty() && (std::size_t)(end - p) >= frameSize_) { + // whole payload present contiguously -> dispatch in place, no copy + sink(p, frameSize_); + p += frameSize_; haveHeader_ = false; } + else { + const std::size_t still = (std::size_t)frameSize_ - buffer_.size(); + const std::size_t avail = (std::size_t)(end - p); + const std::size_t take = avail < still ? avail : still; + buffer_.insert(buffer_.end(), p, p + take); + p += take; + if (buffer_.size() == (std::size_t)frameSize_) { + sink(buffer_.data(), frameSize_); + buffer_.clear(); + haveHeader_ = false; + } + } } + return true; } - return true; - } - // Discard any partial-frame state (e.g. after a connection reset). - void Reset() - { - buffer_.clear(); - frameSize_ = 0; - headerFill_ = 0; - haveHeader_ = false; - } + // Discard any partial-frame state (e.g. after a connection reset). + void Reset() { + buffer_.clear(); + frameSize_ = 0; + headerFill_ = 0; + haveHeader_ = false; + } -private: - std::vector buffer_; // accumulates a payload that spans reads - uint32_t maxFrameSize_; - uint32_t frameSize_; // payload size of the frame in progress - char header_[OSC_STREAM_FRAME_HEADER_SIZE]; - uint32_t headerFill_; // bytes of the header accumulated so far - bool haveHeader_; // false: reading header; true: reading payload -}; + private: + std::vector buffer_; // accumulates a payload that spans reads + uint32_t maxFrameSize_; + uint32_t frameSize_; // payload size of the frame in progress + char header_[OSC_STREAM_FRAME_HEADER_SIZE]; + uint32_t headerFill_; // bytes of the header accumulated so far + bool haveHeader_; // false: reading header; true: reading payload + }; } // namespace osctap - // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; diff --git a/osctap/osc/OscTypes.h b/osctap/osc/OscTypes.h index c767ec3..700f74e 100644 --- a/osctap/osc/OscTypes.h +++ b/osctap/osc/OscTypes.h @@ -52,162 +52,160 @@ // A realtime function must not throw, so the attribute also implies noexcept // (Clang enforces this via -Wperf-constraint-implies-noexcept). #if defined(__clang__) && (__clang_major__ >= 20) - #define OSCTAP_REALTIME noexcept [[clang::nonblocking]] +#define OSCTAP_REALTIME noexcept [[clang::nonblocking]] #else - #define OSCTAP_REALTIME +#define OSCTAP_REALTIME #endif - -namespace osctap{ - -enum ValueTypeSizes{ - OSC_SIZEOF_INT32 = 4, - OSC_SIZEOF_UINT32 = 4, - OSC_SIZEOF_INT64 = 8, - OSC_SIZEOF_UINT64 = 8 -}; - - -// osc_bundle_element_size_t is used for the size of bundle elements and blobs -// the OSC spec specifies these as int32_t (signed) but we ensure that they -// are always positive since negative field sizes make no sense. - -typedef int32_t osc_bundle_element_size_t; - -enum { - OSC_INT32_MAX = 0x7FFFFFFF, - - // Element sizes are specified to be int32_t, and are always rounded up to nearest - // multiple of 4. Therefore their values can't be greater than 0x7FFFFFFC. - OSC_BUNDLE_ELEMENT_SIZE_MAX = 0x7FFFFFFC -}; - - -constexpr inline bool IsValidElementSizeValue( osc_bundle_element_size_t x ) -{ - // sizes may not be negative or exceed OSC_BUNDLE_ELEMENT_SIZE_MAX - return x >= 0 && x <= OSC_BUNDLE_ELEMENT_SIZE_MAX; -} - - -constexpr inline bool IsMultipleOf4( osc_bundle_element_size_t x ) -{ - return (x & ((osc_bundle_element_size_t)0x03)) == 0; -} - - -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', - ARRAY_BEGIN_TYPE_TAG = '[', - ARRAY_END_TYPE_TAG = ']' -}; - - - -// i/o manipulators used for streaming interfaces - -struct BundleInitiator{ - constexpr explicit BundleInitiator( uint64_t timeTag_ ) : timeTag( timeTag_ ) {} - uint64_t timeTag{}; -}; - -constexpr BundleInitiator BeginBundle( uint64_t timeTag=1 ) -{ return BundleInitiator{timeTag}; } - -constexpr BundleInitiator BeginBundleImmediate() -{ return BundleInitiator{1}; } - - -struct BundleTerminator{ }; -constexpr BundleTerminator EndBundle() { return {}; } - -struct BeginMessage{ - constexpr explicit BeginMessage( const char *addressPattern_ ) : addressPattern( addressPattern_ ) {} - const char *addressPattern{}; -}; - -struct MessageTerminator{ }; -constexpr MessageTerminator EndMessage() -{ return {}; } - -// osc specific types. they are defined as structs so they can be used -// as separately identifiable types with the streaming operators. - -struct NilType{ }; -constexpr NilType OscNil() { return {}; } - - -struct InfinitumType{ }; -constexpr InfinitumType Infinitum() { return {}; } - -struct RgbaColor{ - constexpr RgbaColor() {} - constexpr explicit RgbaColor( uint32_t value_ ) : value( value_ ) {} - uint32_t value{}; - - constexpr operator uint32_t() const { return value; } -}; - - -struct MidiMessage{ - constexpr MidiMessage() {} - constexpr explicit MidiMessage( uint32_t value_ ) : value( value_ ) {} - uint32_t value{}; - - constexpr operator uint32_t() const { return value; } -}; - - -struct TimeTag{ - constexpr TimeTag() {} - constexpr explicit TimeTag( uint64_t value_ ) : value( value_ ) {} - uint64_t value{}; - - constexpr operator uint64_t() const { return value; } -}; - - -struct Symbol{ - constexpr Symbol() {} - constexpr explicit Symbol( const char* value_ ) : value( value_ ) {} - const char* value{}; - - constexpr operator const char *() const { return value; } -}; - - -struct Blob{ - constexpr Blob() {} - constexpr explicit Blob( const void* data_, osc_bundle_element_size_t size_ ) - : data( data_ ), size( size_ ) {} - const void* data{}; - osc_bundle_element_size_t size{}; -}; - -struct ArrayInitiator{ }; -constexpr ArrayInitiator BeginArray() { return {}; } - -struct ArrayTerminator{ }; -constexpr ArrayTerminator EndArray() { return {}; } +namespace osctap { + + enum ValueTypeSizes { OSC_SIZEOF_INT32 = 4, OSC_SIZEOF_UINT32 = 4, OSC_SIZEOF_INT64 = 8, OSC_SIZEOF_UINT64 = 8 }; + + // osc_bundle_element_size_t is used for the size of bundle elements and blobs + // the OSC spec specifies these as int32_t (signed) but we ensure that they + // are always positive since negative field sizes make no sense. + + typedef int32_t osc_bundle_element_size_t; + + enum { + OSC_INT32_MAX = 0x7FFFFFFF, + + // Element sizes are specified to be int32_t, and are always rounded up to nearest + // multiple of 4. Therefore their values can't be greater than 0x7FFFFFFC. + OSC_BUNDLE_ELEMENT_SIZE_MAX = 0x7FFFFFFC + }; + + constexpr inline bool IsValidElementSizeValue(osc_bundle_element_size_t x) { + // sizes may not be negative or exceed OSC_BUNDLE_ELEMENT_SIZE_MAX + return x >= 0 && x <= OSC_BUNDLE_ELEMENT_SIZE_MAX; + } + + constexpr inline bool IsMultipleOf4(osc_bundle_element_size_t x) { + return (x & ((osc_bundle_element_size_t)0x03)) == 0; + } + + 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', + ARRAY_BEGIN_TYPE_TAG = '[', + ARRAY_END_TYPE_TAG = ']' + }; + + // i/o manipulators used for streaming interfaces + + struct BundleInitiator { + constexpr explicit BundleInitiator(uint64_t timeTag_) + : timeTag(timeTag_) {} + uint64_t timeTag{}; + }; + + constexpr BundleInitiator BeginBundle(uint64_t timeTag = 1) { + return BundleInitiator{timeTag}; + } + + constexpr BundleInitiator BeginBundleImmediate() { + return BundleInitiator{1}; + } + + struct BundleTerminator {}; + constexpr BundleTerminator EndBundle() { + return {}; + } + + struct BeginMessage { + constexpr explicit BeginMessage(const char* addressPattern_) + : addressPattern(addressPattern_) {} + const char* addressPattern{}; + }; + + struct MessageTerminator {}; + constexpr MessageTerminator EndMessage() { + return {}; + } + + // osc specific types. they are defined as structs so they can be used + // as separately identifiable types with the streaming operators. + + struct NilType {}; + constexpr NilType OscNil() { + return {}; + } + + struct InfinitumType {}; + constexpr InfinitumType Infinitum() { + return {}; + } + + struct RgbaColor { + constexpr RgbaColor() {} + constexpr explicit RgbaColor(uint32_t value_) + : value(value_) {} + uint32_t value{}; + + constexpr operator uint32_t() const { return value; } + }; + + struct MidiMessage { + constexpr MidiMessage() {} + constexpr explicit MidiMessage(uint32_t value_) + : value(value_) {} + uint32_t value{}; + + constexpr operator uint32_t() const { return value; } + }; + + struct TimeTag { + constexpr TimeTag() {} + constexpr explicit TimeTag(uint64_t value_) + : value(value_) {} + uint64_t value{}; + + constexpr operator uint64_t() const { return value; } + }; + + struct Symbol { + constexpr Symbol() {} + constexpr explicit Symbol(const char* value_) + : value(value_) {} + const char* value{}; + + constexpr operator const char*() const { return value; } + }; + + struct Blob { + constexpr Blob() {} + constexpr explicit Blob(const void* data_, osc_bundle_element_size_t size_) + : data(data_) + , size(size_) {} + const void* data{}; + osc_bundle_element_size_t size{}; + }; + + struct ArrayInitiator {}; + constexpr ArrayInitiator BeginArray() { + return {}; + } + + struct ArrayTerminator {}; + constexpr ArrayTerminator EndArray() { + return {}; + } } // namespace osctap - - // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. namespace oscpack = osctap; diff --git a/osctap/osc/OscTypesTraits.h b/osctap/osc/OscTypesTraits.h index 0ce5505..ddb30ac 100644 --- a/osctap/osc/OscTypesTraits.h +++ b/osctap/osc/OscTypesTraits.h @@ -1,144 +1,123 @@ #pragma once -#include "OscReceivedElements.h" - #include -namespace osctap -{ -// Helpers to get the values. -template -struct OscpackFunction; - -// For the ones that requires access to more than the type. -struct object_required_trait {}; - -// For the ones where the value is included in the type. -struct object_useless_trait {}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsInt32; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsInt32Unchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsInt64; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsInt64Unchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsFloat; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsFloatUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsDouble; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsDoubleUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsChar; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsCharUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsString; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsStringUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsSymbol; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsSymbolUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_required_trait; - static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsBlob; - static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsBlobUnchecked; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_useless_trait; - static bool true_fun() { return true; } - static const constexpr auto convert = &true_fun; - static const constexpr auto convert_unchecked = &true_fun; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_useless_trait; - static bool false_fun() { return false; } - static const constexpr auto convert = &false_fun; - static const constexpr auto convert_unchecked = &false_fun; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_useless_trait; - static InfinitumType impulse_fun() { return {}; } - static const constexpr auto convert = &impulse_fun; - static const constexpr auto convert_unchecked = &impulse_fun; -}; - -template<> -struct OscpackFunction -{ - using conversion_mode = object_useless_trait; - static NilType nil_fun() { return {}; } - static const constexpr auto convert = &nil_fun; - static const constexpr auto convert_unchecked = &nil_fun; -}; - -template -auto convert(osctap::ReceivedMessageArgument arg, - std::enable_if_t< - std::is_same< - typename OscpackFunction::conversion_mode, - object_required_trait - >::value - >* = nullptr) -{ - return (arg.*OscpackFunction::convert)(); -} - -template -auto convert(osctap::ReceivedMessageArgument, - std::enable_if_t< - std::is_same< - typename OscpackFunction::conversion_mode, - object_useless_trait - >::value - >* = nullptr) -{ - return (*OscpackFunction::convert)(); -} - -} + +#include "OscReceivedElements.h" +namespace osctap { + // Helpers to get the values. + template + struct OscpackFunction; + + // For the ones that requires access to more than the type. + struct object_required_trait {}; + + // For the ones where the value is included in the type. + struct object_useless_trait {}; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsInt32; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsInt32Unchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsInt64; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsInt64Unchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsFloat; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsFloatUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsDouble; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsDoubleUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsChar; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsCharUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsString; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsStringUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsSymbol; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsSymbolUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_required_trait; + static const constexpr auto convert = &osctap::ReceivedMessageArgument::AsBlob; + static const constexpr auto convert_unchecked = &osctap::ReceivedMessageArgument::AsBlobUnchecked; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_useless_trait; + static bool true_fun() { return true; } + static const constexpr auto convert = &true_fun; + static const constexpr auto convert_unchecked = &true_fun; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_useless_trait; + static bool false_fun() { return false; } + static const constexpr auto convert = &false_fun; + static const constexpr auto convert_unchecked = &false_fun; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_useless_trait; + static InfinitumType impulse_fun() { return {}; } + static const constexpr auto convert = &impulse_fun; + static const constexpr auto convert_unchecked = &impulse_fun; + }; + + template <> + struct OscpackFunction { + using conversion_mode = object_useless_trait; + static NilType nil_fun() { return {}; } + static const constexpr auto convert = &nil_fun; + static const constexpr auto convert_unchecked = &nil_fun; + }; + + template + auto convert( + osctap::ReceivedMessageArgument arg, + std::enable_if_t::conversion_mode, object_required_trait>::value>* = + nullptr) { + return (arg.*OscpackFunction::convert)(); + } + + template + auto convert( + osctap::ReceivedMessageArgument, + std::enable_if_t::conversion_mode, object_useless_trait>::value>* = + nullptr) { + return (*OscpackFunction::convert)(); + } + +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/osctap/osc/OscUtilities.h b/osctap/osc/OscUtilities.h index db6c072..4a902b2 100644 --- a/osctap/osc/OscUtilities.h +++ b/osctap/osc/OscUtilities.h @@ -1,6 +1,6 @@ #pragma once #include -#include // std::memcpy (bit_cast fallback) +#include // std::memcpy (bit_cast fallback) #include // std::bit_cast (C++20) gives a well-defined, constexpr type pun; on C++17 we @@ -9,135 +9,133 @@ // merely inline. (constexpr implies inline, so the C++17 branch must spell out // inline to keep these header functions ODR-safe.) #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L - #include - #define OSCTAP_HAS_STD_BIT_CAST 1 - #define OSCTAP_BITCAST_CONSTEXPR constexpr +#include +#define OSCTAP_HAS_STD_BIT_CAST 1 +#define OSCTAP_BITCAST_CONSTEXPR constexpr #else - #define OSCTAP_HAS_STD_BIT_CAST 0 - #define OSCTAP_BITCAST_CONSTEXPR inline +#define OSCTAP_HAS_STD_BIT_CAST 0 +#define OSCTAP_BITCAST_CONSTEXPR inline #endif -namespace osctap -{ - -// Reinterpret the bits of one trivially-copyable type as another of the same -// size, without the undefined behaviour of union type-punning or pointer casts. -template -OSCTAP_BITCAST_CONSTEXPR To BitCast( const From& src ) noexcept -{ - static_assert( sizeof(To) == sizeof(From), "BitCast requires equal sizes" ); - static_assert( std::is_trivially_copyable::value - && std::is_trivially_copyable::value, - "BitCast requires trivially-copyable types" ); +namespace osctap { + + // Reinterpret the bits of one trivially-copyable type as another of the same + // size, without the undefined behaviour of union type-punning or pointer casts. + template + OSCTAP_BITCAST_CONSTEXPR To BitCast(const From& src) noexcept { + static_assert(sizeof(To) == sizeof(From), "BitCast requires equal sizes"); + static_assert(std::is_trivially_copyable::value && std::is_trivially_copyable::value, + "BitCast requires trivially-copyable types"); #if OSCTAP_HAS_STD_BIT_CAST - return std::bit_cast( src ); + return std::bit_cast(src); #else - To dst; - std::memcpy( &dst, &src, sizeof(To) ); - return dst; + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; #endif -} - -// OSC encodes integers and floats in big-endian (network) byte order. Assemble -// and disassemble them byte-by-byte: this is endian-agnostic and free of the -// strict-aliasing / misalignment UB that the old union + reinterpret_cast had. -// (uint8_t() of a possibly-signed char yields the raw byte, modulo 256.) - -constexpr uint32_t LoadBigEndian32( const char *p ) noexcept -{ - return (uint32_t(uint8_t(p[0])) << 24) - | (uint32_t(uint8_t(p[1])) << 16) - | (uint32_t(uint8_t(p[2])) << 8) - | uint32_t(uint8_t(p[3])); -} - -constexpr uint64_t LoadBigEndian64( const char *p ) noexcept -{ - return (uint64_t(uint8_t(p[0])) << 56) - | (uint64_t(uint8_t(p[1])) << 48) - | (uint64_t(uint8_t(p[2])) << 40) - | (uint64_t(uint8_t(p[3])) << 32) - | (uint64_t(uint8_t(p[4])) << 24) - | (uint64_t(uint8_t(p[5])) << 16) - | (uint64_t(uint8_t(p[6])) << 8) - | uint64_t(uint8_t(p[7])); -} - -constexpr void StoreBigEndian32( char *p, uint32_t x ) noexcept -{ - p[0] = char( uint8_t(x >> 24) ); - p[1] = char( uint8_t(x >> 16) ); - p[2] = char( uint8_t(x >> 8) ); - p[3] = char( uint8_t(x) ); -} - -constexpr void StoreBigEndian64( char *p, uint64_t x ) noexcept -{ - p[0] = char( uint8_t(x >> 56) ); - p[1] = char( uint8_t(x >> 48) ); - p[2] = char( uint8_t(x >> 40) ); - p[3] = char( uint8_t(x >> 32) ); - p[4] = char( uint8_t(x >> 24) ); - p[5] = char( uint8_t(x >> 16) ); - p[6] = char( uint8_t(x >> 8) ); - p[7] = char( uint8_t(x) ); -} - -// round up to the next highest multiple of 4. unless x is already a multiple of 4 -constexpr uint32_t RoundUp4( uint32_t x ) noexcept -{ - return (x + 3) & ~((uint32_t)0x03); -} - -OSCTAP_BITCAST_CONSTEXPR void FromInt32( char *p, int32_t x ) { StoreBigEndian32( p, BitCast(x) ); } -constexpr void FromUInt32( char *p, uint32_t x ) { StoreBigEndian32( p, x ); } -OSCTAP_BITCAST_CONSTEXPR void FromInt64( char *p, int64_t x ) { StoreBigEndian64( p, BitCast(x) ); } -constexpr void FromUInt64( char *p, uint64_t x ) { StoreBigEndian64( p, x ); } - -// 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. -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 -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; -} - -OSCTAP_BITCAST_CONSTEXPR int32_t ToInt32( const char *p ) { return BitCast( LoadBigEndian32( p ) ); } -constexpr uint32_t ToUInt32( const char *p ) noexcept { return LoadBigEndian32( p ); } -OSCTAP_BITCAST_CONSTEXPR int64_t ToInt64( const char *p ) { return BitCast( LoadBigEndian64( p ) ); } -constexpr uint64_t ToUInt64( const char *p ) noexcept { return LoadBigEndian64( p ); } -} + } + + // OSC encodes integers and floats in big-endian (network) byte order. Assemble + // and disassemble them byte-by-byte: this is endian-agnostic and free of the + // strict-aliasing / misalignment UB that the old union + reinterpret_cast had. + // (uint8_t() of a possibly-signed char yields the raw byte, modulo 256.) + + constexpr uint32_t LoadBigEndian32(const char* p) noexcept { + return (uint32_t(uint8_t(p[0])) << 24) | (uint32_t(uint8_t(p[1])) << 16) | (uint32_t(uint8_t(p[2])) << 8) + | uint32_t(uint8_t(p[3])); + } + + constexpr uint64_t LoadBigEndian64(const char* p) noexcept { + return (uint64_t(uint8_t(p[0])) << 56) | (uint64_t(uint8_t(p[1])) << 48) | (uint64_t(uint8_t(p[2])) << 40) + | (uint64_t(uint8_t(p[3])) << 32) | (uint64_t(uint8_t(p[4])) << 24) | (uint64_t(uint8_t(p[5])) << 16) + | (uint64_t(uint8_t(p[6])) << 8) | uint64_t(uint8_t(p[7])); + } + + constexpr void StoreBigEndian32(char* p, uint32_t x) noexcept { + p[0] = char(uint8_t(x >> 24)); + p[1] = char(uint8_t(x >> 16)); + p[2] = char(uint8_t(x >> 8)); + p[3] = char(uint8_t(x)); + } + + constexpr void StoreBigEndian64(char* p, uint64_t x) noexcept { + p[0] = char(uint8_t(x >> 56)); + p[1] = char(uint8_t(x >> 48)); + p[2] = char(uint8_t(x >> 40)); + p[3] = char(uint8_t(x >> 32)); + p[4] = char(uint8_t(x >> 24)); + p[5] = char(uint8_t(x >> 16)); + p[6] = char(uint8_t(x >> 8)); + p[7] = char(uint8_t(x)); + } + + // round up to the next highest multiple of 4. unless x is already a multiple of 4 + constexpr uint32_t RoundUp4(uint32_t x) noexcept { + return (x + 3) & ~((uint32_t)0x03); + } + + OSCTAP_BITCAST_CONSTEXPR void FromInt32(char* p, int32_t x) { + StoreBigEndian32(p, BitCast(x)); + } + constexpr void FromUInt32(char* p, uint32_t x) { + StoreBigEndian32(p, x); + } + OSCTAP_BITCAST_CONSTEXPR void FromInt64(char* p, int64_t x) { + StoreBigEndian64(p, BitCast(x)); + } + constexpr void FromUInt64(char* p, uint64_t x) { + StoreBigEndian64(p, x); + } + + // 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. + 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 + 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; + } + + OSCTAP_BITCAST_CONSTEXPR int32_t ToInt32(const char* p) { + return BitCast(LoadBigEndian32(p)); + } + constexpr uint32_t ToUInt32(const char* p) noexcept { + return LoadBigEndian32(p); + } + OSCTAP_BITCAST_CONSTEXPR int64_t ToInt64(const char* p) { + return BitCast(LoadBigEndian64(p)); + } + constexpr uint64_t ToUInt64(const char* p) noexcept { + return LoadBigEndian64(p); + } +} // namespace osctap // Backwards-compatibility alias: this library was formerly named oscpack. // Existing code that uses the oscpack:: namespace continues to compile. diff --git a/tests/CompatIncludeShim.cpp b/tests/CompatIncludeShim.cpp index b419a81..81efac5 100644 --- a/tests/CompatIncludeShim.cpp +++ b/tests/CompatIncludeShim.cpp @@ -13,23 +13,22 @@ */ // Deprecated include paths on purpose -- exercises the redirect shim under oscpack/. -#include -#include -#include -#include - #include #include -int main() -{ +#include +#include +#include +#include + +int main() { // Pack a message through the old `oscpack::` namespace + old include paths... - char buffer[256]; + char buffer[256]; oscpack::OutboundPacketStream p(buffer, sizeof(buffer)); p << oscpack::BeginMessage("/test") << (int32_t)42 << "hello" << oscpack::EndMessage(); // ...then parse it back and confirm the shimmed types interoperate. - oscpack::ReceivedPacket packet(p.Data(), p.Size()); + oscpack::ReceivedPacket packet(p.Data(), p.Size()); oscpack::ReceivedMessage msg(packet); if (std::strcmp(msg.AddressPattern(), "/test") != 0) { @@ -38,8 +37,8 @@ int main() } oscpack::ReceivedMessage::const_iterator arg = msg.ArgumentsBegin(); - int32_t i = (arg++)->AsInt32(); - const char *s = (arg++)->AsString(); + int32_t i = (arg++)->AsInt32(); + const char* s = (arg++)->AsString(); if (i != 42 || std::strcmp(s, "hello") != 0) { std::cerr << "compat-shim: unexpected argument values\n"; return 1; diff --git a/tests/OscConcurrencyTest.cpp b/tests/OscConcurrencyTest.cpp index c23d72e..42283c1 100644 --- a/tests/OscConcurrencyTest.cpp +++ b/tests/OscConcurrencyTest.cpp @@ -16,46 +16,42 @@ is needed. (POSIX only -- the dedicated TSan job runs on Linux.) */ -#include "ip/UdpSocket.h" -#include "ip/IpEndpointName.h" -#include "ip/PacketListener.h" - #include #include #include #include #include +#include "ip/IpEndpointName.h" +#include "ip/PacketListener.h" +#include "ip/UdpSocket.h" + using namespace osctap; namespace { -class CountingListener : public PacketListener { - public: - std::atomic packets{ 0 }; - void ProcessPacket( const char * /*data*/, int /*size*/, - const IpEndpointName & /*remoteEndpoint*/ ) override - { - packets.fetch_add( 1, std::memory_order_relaxed ); - } -}; + class CountingListener : public PacketListener { + public: + std::atomic packets{0}; + void ProcessPacket(const char* /*data*/, int /*size*/, const IpEndpointName& /*remoteEndpoint*/) override { + packets.fetch_add(1, std::memory_order_relaxed); + } + }; } // namespace -int main() -{ +int main() { CountingListener listener; // Bind a receive socket to loopback on an OS-assigned port (4-octet ctor // avoids a DNS lookup); LocalPort() reports the chosen port. - UdpListeningReceiveSocket receiver( - IpEndpointName( 127, 0, 0, 1, 0 ), &listener ); - const int port = receiver.LocalPort(); + UdpListeningReceiveSocket receiver(IpEndpointName(127, 0, 0, 1, 0), &listener); + const int port = receiver.LocalPort(); // Run the receive loop on its own thread; it blocks in select(). - std::atomic finished{ false }; - std::thread runner( [&] { + std::atomic finished{false}; + std::thread runner([&] { receiver.Run(); - finished.store( true, std::memory_order_release ); - } ); + finished.store(true, std::memory_order_release); + }); // Best-effort: send one packet so ProcessPacket() runs on the receive thread // concurrently with this one (exercises the receive path, not just the @@ -66,11 +62,12 @@ int main() // asserted. bool sent = false; try { - UdpTransmitSocket sender( IpEndpointName( 127, 0, 0, 1, port ) ); - const char ping[] = { '/','p',0,0, ',',0,0,0 }; // minimal valid OSC message - sender.Send( ping, sizeof( ping ) ); + UdpTransmitSocket sender(IpEndpointName(127, 0, 0, 1, port)); + const char ping[] = {'/', 'p', 0, 0, ',', 0, 0, 0}; // minimal valid OSC message + sender.Send(ping, sizeof(ping)); sent = true; - } catch( const std::exception & ) { + } + catch (const std::exception&) { // networking restricted in this environment; skip the receive coverage. } @@ -78,23 +75,23 @@ int main() // ProcessPacket() really does run concurrently before we stop the loop. The // wait is capped so a dropped datagram can't hang the test -- termination is // driven by the break loop below regardless. - if( sent ){ - for( int i = 0; i < 200 && listener.packets.load( std::memory_order_relaxed ) == 0; ++i ) - std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) ); + if (sent) { + for (int i = 0; i < 200 && listener.packets.load(std::memory_order_relaxed) == 0; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } // Stop Run() from this thread. Run() resets its break flag when it starts, so // a single AsynchronousBreak() could race ahead of that reset and be missed; // signalling in a loop until the run thread returns is race-free and is // guaranteed to terminate. Repeated breaks are harmless. - while( !finished.load( std::memory_order_acquire ) ){ + while (!finished.load(std::memory_order_acquire)) { receiver.AsynchronousBreak(); - std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) ); + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } runner.join(); - std::cout << "concurrency test: Run() vs AsynchronousBreak() OK (" - << listener.packets.load() << " packet(s) received)\n"; + std::cout << "concurrency test: Run() vs AsynchronousBreak() OK (" << listener.packets.load() + << " packet(s) received)\n"; return 0; } diff --git a/tests/OscFreestandingTest.cpp b/tests/OscFreestandingTest.cpp index ce592b5..196313f 100644 --- a/tests/OscFreestandingTest.cpp +++ b/tests/OscFreestandingTest.cpp @@ -13,83 +13,93 @@ path; malformed-input behaviour is covered by the hosted OscUnitTests suite. */ +#include #include #include -#include #include "osc/OscOutboundPacketStream.h" #include "osc/OscReceivedElements.h" // Guard rails: this TU must be compiled as the freestanding profile. #if OSCTAP_HAS_EXCEPTIONS -# error "OscFreestandingTest must be built with exceptions disabled (-fno-exceptions)" +#error "OscFreestandingTest must be built with exceptions disabled (-fno-exceptions)" #endif #ifndef OSCTAP_FREESTANDING -# error "OscFreestandingTest must be built with -DOSCTAP_FREESTANDING" +#error "OscFreestandingTest must be built with -DOSCTAP_FREESTANDING" #endif static int failures = 0; -#define CHECK(cond) \ - do { if(!(cond)){ std::printf("FAIL: %s (line %d)\n", #cond, __LINE__); ++failures; } } while(0) +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL: %s (line %d)\n", #cond, __LINE__); \ + ++failures; \ + } \ + } while (0) -int main() -{ +int main() { // --- serialize a message on the stack (no heap) ------------------------ - char buffer[256]; - const char* runtimeStr = "pico"; // a runtime const char* (not a literal): - // must serialize as a string, not a bool. - osctap::OutboundPacketStream p( buffer, sizeof(buffer) ); - p << osctap::BeginMessage( "/freestanding" ) - << true << (int32_t)2350 << (float)3.14159f << runtimeStr + char buffer[256]; + const char* runtimeStr = "pico"; // a runtime const char* (not a literal): + // must serialize as a string, not a bool. + osctap::OutboundPacketStream p(buffer, sizeof(buffer)); + p << osctap::BeginMessage("/freestanding") << true << (int32_t)2350 << (float)3.14159f << runtimeStr << osctap::EndMessage(); - CHECK( p.IsReady() ); - CHECK( p.Size() > 0 ); + CHECK(p.IsReady()); + CHECK(p.Size() > 0); // --- parse it back ----------------------------------------------------- - osctap::ReceivedMessage msg( osctap::ReceivedPacket( p.Data(), p.Size() ) ); + osctap::ReceivedMessage msg(osctap::ReceivedPacket(p.Data(), p.Size())); - CHECK( std::strcmp( msg.AddressPattern(), "/freestanding" ) == 0 ); + CHECK(std::strcmp(msg.AddressPattern(), "/freestanding") == 0); // Checked accessors: these route validation through OSCTAP_THROW, so their // mere compilation here proves the no-exceptions seam builds. Input is // valid, so no fatal handler fires. osctap::ReceivedMessage::const_iterator arg = msg.ArgumentsBegin(); - CHECK( arg->AsBool() == true ); ++arg; - CHECK( arg->AsInt32() == 2350 ); ++arg; - CHECK( arg->AsFloat() > 3.14f && arg->AsFloat() < 3.15f ); ++arg; - CHECK( std::strcmp( arg->AsString(), "pico" ) == 0 ); ++arg; - CHECK( arg == msg.ArgumentsEnd() ); + CHECK(arg->AsBool() == true); + ++arg; + CHECK(arg->AsInt32() == 2350); + ++arg; + CHECK(arg->AsFloat() > 3.14f && arg->AsFloat() < 3.15f); + ++arg; + CHECK(std::strcmp(arg->AsString(), "pico") == 0); + ++arg; + CHECK(arg == msg.ArgumentsEnd()); // Realtime read path: the throw-free *Unchecked accessors over a known-valid // message -- the hot loop an audio/embedded integrator runs every packet. arg = msg.ArgumentsBegin(); - CHECK( arg->AsBoolUnchecked() == true ); ++arg; - CHECK( arg->AsInt32Unchecked() == 2350 ); ++arg; - CHECK( arg->AsFloatUnchecked() > 3.14f ); ++arg; - CHECK( std::strcmp( arg->AsStringUnchecked(), "pico" ) == 0 ); + CHECK(arg->AsBoolUnchecked() == true); + ++arg; + CHECK(arg->AsInt32Unchecked() == 2350); + ++arg; + CHECK(arg->AsFloatUnchecked() > 3.14f); + ++arg; + CHECK(std::strcmp(arg->AsStringUnchecked(), "pico") == 0); // --- non-throwing validation gate (the point of TryInit/TryValidatePacket) --- // On this build OSCTAP_THROW would abort via the fatal handler, so the only // safe way to handle untrusted input is to gate it first. Reaching these lines // at all proves the gate returns instead of throwing/aborting. typedef osctap::osc_bundle_element_size_t sz_t; - CHECK( osctap::TryValidatePacket( p.Data(), (sz_t)p.Size() ) == nullptr ); // valid -> accepted + CHECK(osctap::TryValidatePacket(p.Data(), (sz_t)p.Size()) == nullptr); // valid -> accepted // truncate the valid message by one 4-byte word: arguments now exceed size. - CHECK( osctap::TryValidatePacket( p.Data(), (sz_t)(p.Size() - 4) ) != nullptr ); + CHECK(osctap::TryValidatePacket(p.Data(), (sz_t)(p.Size() - 4)) != nullptr); // structurally bogus little buffer (not a valid message): rejected, not fatal. - const char bad[6] = { '/', 'x', '\0', '\0', ',', 'i' }; - CHECK( osctap::TryValidatePacket( bad, (sz_t)sizeof(bad) ) != nullptr ); + const char bad[6] = {'/', 'x', '\0', '\0', ',', 'i'}; + CHECK(osctap::TryValidatePacket(bad, (sz_t)sizeof(bad)) != nullptr); // The same gate also drives a no-abort ReceivedMessage parse: osctap::ReceivedMessage probe; - CHECK( probe.TryInit( p.Data(), (sz_t)p.Size() ) == nullptr ); - CHECK( std::strcmp( probe.AddressPattern(), "/freestanding" ) == 0 ); + CHECK(probe.TryInit(p.Data(), (sz_t)p.Size()) == nullptr); + CHECK(std::strcmp(probe.AddressPattern(), "/freestanding") == 0); - if( failures == 0 ) - std::printf( "OscFreestandingTest: OK (exceptions disabled, freestanding)\n" ); + if (failures == 0) + std::printf("OscFreestandingTest: OK (exceptions disabled, freestanding)\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/OscLatencyBench.cpp b/tests/OscLatencyBench.cpp index 7d63569..139980a 100644 --- a/tests/OscLatencyBench.cpp +++ b/tests/OscLatencyBench.cpp @@ -14,9 +14,6 @@ ./build/OscLatencyBench [iterations] */ -#include "osc/OscReceivedElements.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include @@ -24,88 +21,100 @@ #include #include +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscReceivedElements.h" + using namespace oscpack; // volatile sink: keeps the optimiser from deleting the work we are timing. static volatile int64_t g_sink = 0; -static std::size_t BuildMessage( char* buf, std::size_t cap ) -{ - OutboundPacketStream p( buf, cap ); - const unsigned char blob[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; - p << BeginMessage( "/bench/path" ) - << (int32_t)42 << 3.14159f << (int64_t)123456789 - << "a-string-argument" << true - << Blob( blob, (osc_bundle_element_size_t)sizeof(blob) ) - << EndMessage(); +static std::size_t BuildMessage(char* buf, std::size_t cap) { + OutboundPacketStream p(buf, cap); + const unsigned char blob[] = {1, 2, 3, 4, 5, 6, 7, 8}; + p << BeginMessage("/bench/path") << (int32_t)42 << 3.14159f << (int64_t)123456789 << "a-string-argument" << true + << Blob(blob, (osc_bundle_element_size_t)sizeof(blob)) << EndMessage(); return p.Size(); } // The realtime read/dispatch hot path over a known-valid message. -static int64_t ReadHotPath( const ReceivedMessage& m ) -{ +static int64_t ReadHotPath(const ReceivedMessage& m) { int64_t acc = m.AddressPattern()[0]; - for( ReceivedMessage::const_iterator i = m.ArgumentsBegin(); i != m.ArgumentsEnd(); ++i ){ - switch( i->TypeTag() ){ - case INT32_TYPE_TAG: acc += i->AsInt32Unchecked(); break; - case FLOAT_TYPE_TAG: acc += (int64_t)i->AsFloatUnchecked(); break; - case INT64_TYPE_TAG: acc += i->AsInt64Unchecked(); break; - case STRING_TYPE_TAG: acc += i->AsStringUnchecked()[0]; break; - case TRUE_TYPE_TAG: acc += 1; break; - case BLOB_TYPE_TAG: { - const void* d; osc_bundle_element_size_t s; - i->AsBlobUnchecked( d, s ); - acc += s; - } break; - default: break; + for (ReceivedMessage::const_iterator i = m.ArgumentsBegin(); i != m.ArgumentsEnd(); ++i) { + switch (i->TypeTag()) { + case INT32_TYPE_TAG: + acc += i->AsInt32Unchecked(); + break; + case FLOAT_TYPE_TAG: + acc += (int64_t)i->AsFloatUnchecked(); + break; + case INT64_TYPE_TAG: + acc += i->AsInt64Unchecked(); + break; + case STRING_TYPE_TAG: + acc += i->AsStringUnchecked()[0]; + break; + case TRUE_TYPE_TAG: + acc += 1; + break; + case BLOB_TYPE_TAG: { + const void* d; + osc_bundle_element_size_t s; + i->AsBlobUnchecked(d, s); + acc += s; + } break; + default: + break; } } return acc; } -static void Report( const char* label, std::vector& ns ) -{ - std::sort( ns.begin(), ns.end() ); - auto pct = [&]( double p ){ return ns[(std::size_t)(p * (ns.size() - 1))]; }; - std::printf( " %-16s min=%6.0f median=%6.0f p99=%7.0f max=%8.0f ns/op\n", - label, ns.front(), pct(0.5), pct(0.99), ns.back() ); +static void Report(const char* label, std::vector& ns) { + std::sort(ns.begin(), ns.end()); + auto pct = [&](double p) { return ns[(std::size_t)(p * (ns.size() - 1))]; }; + std::printf(" %-16s min=%6.0f median=%6.0f p99=%7.0f max=%8.0f ns/op\n", label, ns.front(), pct(0.5), + pct(0.99), ns.back()); } -int main( int argc, char** argv ) -{ - const int N = (argc > 1) ? std::atoi( argv[1] ) : 200000; - using clk = std::chrono::high_resolution_clock; +int main(int argc, char** argv) { + const int N = (argc > 1) ? std::atoi(argv[1]) : 200000; + using clk = std::chrono::high_resolution_clock; - char buffer[256]; - const std::size_t size = BuildMessage( buffer, sizeof(buffer) ); + char buffer[256]; + const std::size_t size = BuildMessage(buffer, sizeof(buffer)); // --- read/dispatch hot path: read an already-validated message --- - ReceivedMessage m( ReceivedPacket( buffer, size ) ); - for( int i = 0; i < 1000; ++i ) g_sink += ReadHotPath( m ); // warm up + ReceivedMessage m(ReceivedPacket(buffer, size)); + for (int i = 0; i < 1000; ++i) + g_sink += ReadHotPath(m); // warm up - std::vector readNs; readNs.reserve( N ); - for( int i = 0; i < N; ++i ){ + std::vector readNs; + readNs.reserve(N); + for (int i = 0; i < N; ++i) { const auto t0 = clk::now(); - g_sink += ReadHotPath( m ); + g_sink += ReadHotPath(m); const auto t1 = clk::now(); - readNs.push_back( std::chrono::duration( t1 - t0 ).count() ); + readNs.push_back(std::chrono::duration(t1 - t0).count()); } // --- serialize: build the message into a buffer --- char obuf[256]; - for( int i = 0; i < 1000; ++i ) g_sink += (int)BuildMessage( obuf, sizeof(obuf) ); // warm up - - std::vector sendNs; sendNs.reserve( N ); - for( int i = 0; i < N; ++i ){ - const auto t0 = clk::now(); - const std::size_t s = BuildMessage( obuf, sizeof(obuf) ); - const auto t1 = clk::now(); + for (int i = 0; i < 1000; ++i) + g_sink += (int)BuildMessage(obuf, sizeof(obuf)); // warm up + + std::vector sendNs; + sendNs.reserve(N); + for (int i = 0; i < N; ++i) { + const auto t0 = clk::now(); + const std::size_t s = BuildMessage(obuf, sizeof(obuf)); + const auto t1 = clk::now(); g_sink += (int64_t)s + obuf[0]; - sendNs.push_back( std::chrono::duration( t1 - t0 ).count() ); + sendNs.push_back(std::chrono::duration(t1 - t0).count()); } - std::printf( "OscLatencyBench (%d iterations; timer overhead included):\n", N ); - Report( "read hot path", readNs ); - Report( "serialize", sendNs ); + std::printf("OscLatencyBench (%d iterations; timer overhead included):\n", N); + Report("read hot path", readNs); + Report("serialize", sendNs); return 0; } diff --git a/tests/OscMulticastTest.cpp b/tests/OscMulticastTest.cpp index 78d61b8..c340990 100644 --- a/tests/OscMulticastTest.cpp +++ b/tests/OscMulticastTest.cpp @@ -8,11 +8,6 @@ so it never false-fails on a restricted runner. */ -#include "ip/UdpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscPacketListener.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include @@ -22,38 +17,48 @@ #include #include +#include "ip/IpEndpointName.h" +#include "ip/UdpSocket.h" +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscPacketListener.h" + namespace { -class RecordingListener : public osctap::OscPacketListener { -public: - std::atomic count{ 0 }; - std::vector addresses; // written by receive thread, read after join - std::vector values; - -protected: - void ProcessMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& ) override - { - addresses.emplace_back( m.AddressPattern() ); - int v = 0; - auto a = m.ArgumentsBegin(); - if( a != m.ArgumentsEnd() && a->IsInt32() ) v = a->AsInt32Unchecked(); - values.push_back( v ); - count.fetch_add( 1, std::memory_order_relaxed ); - } -}; + class RecordingListener : public osctap::OscPacketListener { + public: + std::atomic count{0}; + std::vector addresses; // written by receive thread, read after join + std::vector values; + + protected: + void ProcessMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName&) override { + addresses.emplace_back(m.AddressPattern()); + int v = 0; + auto a = m.ArgumentsBegin(); + if (a != m.ArgumentsEnd() && a->IsInt32()) + v = a->AsInt32Unchecked(); + values.push_back(v); + count.fetch_add(1, std::memory_order_relaxed); + } + }; -int failures = 0; -#define CHECK(c) do{ if(!(c)){ std::printf("FAIL line %d: %s\n", __LINE__, #c); ++failures; } }while(0) + int failures = 0; +#define CHECK(c) \ + do { \ + if (!(c)) { \ + std::printf("FAIL line %d: %s\n", __LINE__, #c); \ + ++failures; \ + } \ + } while (0) } // namespace -int main() -{ +int main() { #ifndef _WIN32 // A multicast send to an unrouted group can raise SIGPIPE on macOS/BSD; ignore // it so the send merely fails and the test SKIPs instead of being killed. (The // UDP backend also sets SO_NOSIGPIPE; this is belt-and-suspenders.) - std::signal( SIGPIPE, SIG_IGN ); + std::signal(SIGPIPE, SIG_IGN); #endif // Administratively-scoped multicast group (239.0.0.0/8). @@ -63,37 +68,39 @@ int main() // Bind to an OS-assigned port on all interfaces, then join the group on it. osctap::UdpListeningReceiveSocket* receiver = nullptr; - int port = 0; + int port = 0; try { - receiver = new osctap::UdpListeningReceiveSocket( - osctap::IpEndpointName( osctap::IpEndpointName::ANY_ADDRESS, 0 ), &listener ); - port = receiver->LocalPort(); - receiver->JoinMulticastGroup( osctap::IpEndpointName( A, B, C, D, port ) ); - } catch( const std::exception& e ) { - std::printf( "OscMulticastTest: SKIP (multicast unavailable: %s)\n", e.what() ); + receiver = new osctap::UdpListeningReceiveSocket(osctap::IpEndpointName(osctap::IpEndpointName::ANY_ADDRESS, 0), + &listener); + port = receiver->LocalPort(); + receiver->JoinMulticastGroup(osctap::IpEndpointName(A, B, C, D, port)); + } + catch (const std::exception& e) { + std::printf("OscMulticastTest: SKIP (multicast unavailable: %s)\n", e.what()); delete receiver; return 0; } - std::thread runner( [&]{ receiver->Run(); } ); + std::thread runner([&] { receiver->Run(); }); bool sent = false; try { - osctap::UdpTransmitSocket sender( osctap::IpEndpointName( A, B, C, D, port ) ); - char buf[128]; - for( int i = 0; i < 3; ++i ){ - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( "/mc" ) << (int32_t)(100 + i) << osctap::EndMessage(); - sender.Send( p.Data(), p.Size() ); + osctap::UdpTransmitSocket sender(osctap::IpEndpointName(A, B, C, D, port)); + char buf[128]; + for (int i = 0; i < 3; ++i) { + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage("/mc") << (int32_t)(100 + i) << osctap::EndMessage(); + sender.Send(p.Data(), p.Size()); } sent = true; - } catch( const std::exception& e ) { - std::printf( "OscMulticastTest: SKIP (multicast send unavailable: %s)\n", e.what() ); + } + catch (const std::exception& e) { + std::printf("OscMulticastTest: SKIP (multicast send unavailable: %s)\n", e.what()); } - if( sent ){ - for( int i = 0; i < 500 && listener.count.load() < 3; ++i ) - std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + if (sent) { + for (int i = 0; i < 500 && listener.count.load() < 3; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } receiver->AsynchronousBreak(); @@ -101,26 +108,31 @@ int main() // No delivery is ambiguous (often a sandbox without multicast routing on the // default interface), so SKIP rather than fail. - if( !sent || listener.count.load() == 0 ){ - std::printf( "OscMulticastTest: SKIP (no multicast delivery in this environment)\n" ); + if (!sent || listener.count.load() == 0) { + std::printf("OscMulticastTest: SKIP (no multicast delivery in this environment)\n"); delete receiver; return 0; } - CHECK( listener.count.load() == 3 ); - if( listener.addresses.size() == 3 ){ - CHECK( listener.addresses[0] == "/mc" && listener.values[0] == 100 ); - CHECK( listener.addresses[1] == "/mc" && listener.values[1] == 101 ); - CHECK( listener.addresses[2] == "/mc" && listener.values[2] == 102 ); + CHECK(listener.count.load() == 3); + if (listener.addresses.size() == 3) { + CHECK(listener.addresses[0] == "/mc" && listener.values[0] == 100); + CHECK(listener.addresses[1] == "/mc" && listener.values[1] == 101); + CHECK(listener.addresses[2] == "/mc" && listener.values[2] == 102); } // Leaving the group while the socket stays open should also succeed. - try { receiver->LeaveMulticastGroup( osctap::IpEndpointName( A, B, C, D, port ) ); } - catch( const std::exception& e ) { std::printf( "FAIL: leave: %s\n", e.what() ); ++failures; } + try { + receiver->LeaveMulticastGroup(osctap::IpEndpointName(A, B, C, D, port)); + } + catch (const std::exception& e) { + std::printf("FAIL: leave: %s\n", e.what()); + ++failures; + } delete receiver; - if( failures == 0 ) - std::printf( "OscMulticastTest: OK (3 OSC messages over multicast 239.7.7.7)\n" ); + if (failures == 0) + std::printf("OscMulticastTest: OK (3 OSC messages over multicast 239.7.7.7)\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/OscRealtimeTest.cpp b/tests/OscRealtimeTest.cpp index dda43c0..c180599 100644 --- a/tests/OscRealtimeTest.cpp +++ b/tests/OscRealtimeTest.cpp @@ -28,127 +28,141 @@ read on the hot path through them. */ -#include "osc/OscReceivedElements.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscReceivedElements.h" + using namespace osctap; static int g_failures = 0; -#define CHECK(cond) do { if(!(cond)) { \ - std::cerr << "realtime test FAILED: " #cond " (line " << __LINE__ << ")\n"; \ - ++g_failures; } } while(0) +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::cerr << "realtime test FAILED: " #cond " (line " << __LINE__ << ")\n"; \ + ++g_failures; \ + } \ + } while (0) // A small struct so the realtime function returns data without allocating. struct ReadResult { - int64_t i32 = 0, i64 = 0; - double f = 0.0, d = 0.0; - uint32_t rgba = 0, midi = 0; - uint64_t timetag = 0; - char ch = 0; - bool boolTrue = false, boolFalse = true; - const char *str = nullptr, *sym = nullptr; - const void *blob = nullptr; - osc_bundle_element_size_t blobSize = 0; - uint32_t argCount = 0; - char firstAddrChar = 0; + int64_t i32 = 0, i64 = 0; + double f = 0.0, d = 0.0; + uint32_t rgba = 0, midi = 0; + uint64_t timetag = 0; + char ch = 0; + bool boolTrue = false, boolFalse = true; + const char * str = nullptr, *sym = nullptr; + const void* blob = nullptr; + osc_bundle_element_size_t blobSize = 0; + uint32_t argCount = 0; + char firstAddrChar = 0; }; // THE REALTIME HOT PATH: iterate an already-validated message and read every // argument through the throw-free OSCTAP_REALTIME accessors. No allocation, no // exceptions -- RTSan and -Wfunction-effects enforce this. -static ReadResult ReadHotPath( const ReceivedMessage& m ) OSCTAP_REALTIME -{ +static ReadResult ReadHotPath(const ReceivedMessage& m) OSCTAP_REALTIME { ReadResult r; r.firstAddrChar = m.AddressPattern()[0]; - r.argCount = m.ArgumentCount(); - - for( ReceivedMessage::const_iterator i = m.ArgumentsBegin(); - i != m.ArgumentsEnd(); ++i ){ - switch( i->TypeTag() ){ - case INT32_TYPE_TAG: r.i32 = i->AsInt32Unchecked(); break; - case FLOAT_TYPE_TAG: r.f = i->AsFloatUnchecked(); break; - case CHAR_TYPE_TAG: r.ch = i->AsCharUnchecked(); break; - case RGBA_COLOR_TYPE_TAG: r.rgba = i->AsRgbaColorUnchecked(); break; - case MIDI_MESSAGE_TYPE_TAG: r.midi = i->AsMidiMessageUnchecked();break; - case INT64_TYPE_TAG: r.i64 = i->AsInt64Unchecked(); break; - case TIME_TAG_TYPE_TAG: r.timetag = i->AsTimeTagUnchecked(); break; - case DOUBLE_TYPE_TAG: r.d = i->AsDoubleUnchecked(); break; - case STRING_TYPE_TAG: r.str = i->AsStringUnchecked(); break; - case SYMBOL_TYPE_TAG: r.sym = i->AsSymbolUnchecked(); break; - // AsBoolUnchecked() is throw-free / realtime-safe too, so read bool - // through it (its value lives in the type tag). - case TRUE_TYPE_TAG: r.boolTrue = i->AsBoolUnchecked(); break; - case FALSE_TYPE_TAG: r.boolFalse = i->AsBoolUnchecked(); break; - // Blob: AsBlobUnchecked() is now throw-free / realtime-safe (the size - // was validated at construction), so the blob payload is read on the - // hot path too. - case BLOB_TYPE_TAG: i->AsBlobUnchecked( r.blob, r.blobSize ); break; - // nil / infinitum / array markers: iterating past them is realtime-safe - // (Advance() does no allocation or throwing). - default: break; + r.argCount = m.ArgumentCount(); + + for (ReceivedMessage::const_iterator i = m.ArgumentsBegin(); i != m.ArgumentsEnd(); ++i) { + switch (i->TypeTag()) { + case INT32_TYPE_TAG: + r.i32 = i->AsInt32Unchecked(); + break; + case FLOAT_TYPE_TAG: + r.f = i->AsFloatUnchecked(); + break; + case CHAR_TYPE_TAG: + r.ch = i->AsCharUnchecked(); + break; + case RGBA_COLOR_TYPE_TAG: + r.rgba = i->AsRgbaColorUnchecked(); + break; + case MIDI_MESSAGE_TYPE_TAG: + r.midi = i->AsMidiMessageUnchecked(); + break; + case INT64_TYPE_TAG: + r.i64 = i->AsInt64Unchecked(); + break; + case TIME_TAG_TYPE_TAG: + r.timetag = i->AsTimeTagUnchecked(); + break; + case DOUBLE_TYPE_TAG: + r.d = i->AsDoubleUnchecked(); + break; + case STRING_TYPE_TAG: + r.str = i->AsStringUnchecked(); + break; + case SYMBOL_TYPE_TAG: + r.sym = i->AsSymbolUnchecked(); + break; + // AsBoolUnchecked() is throw-free / realtime-safe too, so read bool + // through it (its value lives in the type tag). + case TRUE_TYPE_TAG: + r.boolTrue = i->AsBoolUnchecked(); + break; + case FALSE_TYPE_TAG: + r.boolFalse = i->AsBoolUnchecked(); + break; + // Blob: AsBlobUnchecked() is now throw-free / realtime-safe (the size + // was validated at construction), so the blob payload is read on the + // hot path too. + case BLOB_TYPE_TAG: + i->AsBlobUnchecked(r.blob, r.blobSize); + break; + // nil / infinitum / array markers: iterating past them is realtime-safe + // (Advance() does no allocation or throwing). + default: + break; } } return r; } -int main() -{ +int main() { // --- off the realtime thread: build + validate a known-good message --- - char buffer[512]; - OutboundPacketStream p( buffer, sizeof(buffer) ); - const unsigned char blobBytes[] = { 1, 2, 3, 4, 5 }; - p << BeginMessage( "/rt/test" ) - << (int32_t)42 - << 3.5f - << 'z' - << RgbaColor( 0x11223344u ) - << MidiMessage( 0x55667788u ) - << (int64_t)0x0123456789ABCDEFLL - << TimeTag( 0xFEDCBA9876543210ULL ) - << 2.5 - << "hello" - << Symbol( "sym" ) - << true - << false - << OscNil() - << Infinitum() - << Blob( blobBytes, (osc_bundle_element_size_t)sizeof(blobBytes) ) + char buffer[512]; + OutboundPacketStream p(buffer, sizeof(buffer)); + const unsigned char blobBytes[] = {1, 2, 3, 4, 5}; + p << BeginMessage("/rt/test") << (int32_t)42 << 3.5f << 'z' << RgbaColor(0x11223344u) << MidiMessage(0x55667788u) + << (int64_t)0x0123456789ABCDEFLL << TimeTag(0xFEDCBA9876543210ULL) << 2.5 << "hello" << Symbol("sym") << true + << false << OscNil() << Infinitum() + << Blob(blobBytes, (osc_bundle_element_size_t)sizeof(blobBytes)) // Empty array: exercises iterating past the '[' and ']' markers without a // nested element colliding with the top-level scalars asserted below. - << BeginArray() << EndArray() - << EndMessage(); - CHECK( p.IsReady() ); + << BeginArray() << EndArray() << EndMessage(); + CHECK(p.IsReady()); // Construction validates the packet (may throw) -- explicitly off-RT. - ReceivedMessage m( ReceivedPacket( p.Data(), p.Size() ) ); + ReceivedMessage m(ReceivedPacket(p.Data(), p.Size())); // --- realtime region --- - ReadResult r = ReadHotPath( m ); + ReadResult r = ReadHotPath(m); // --- verify the hot path read everything correctly --- - CHECK( r.firstAddrChar == '/' ); - CHECK( r.i32 == 42 ); - CHECK( r.f == 3.5 ); - CHECK( r.ch == 'z' ); - CHECK( r.rgba == 0x11223344u ); - CHECK( r.midi == 0x55667788u ); - CHECK( r.i64 == 0x0123456789ABCDEFLL ); - CHECK( r.timetag == 0xFEDCBA9876543210ULL ); - CHECK( r.d == 2.5 ); - CHECK( r.str != nullptr && std::strcmp( r.str, "hello" ) == 0 ); - CHECK( r.sym != nullptr && std::strcmp( r.sym, "sym" ) == 0 ); - CHECK( r.boolTrue == true ); - CHECK( r.boolFalse == false ); - CHECK( r.argCount > 0 ); - CHECK( r.blob != nullptr && r.blobSize == 5 - && static_cast(r.blob)[0] == 1 - && static_cast(r.blob)[4] == 5 ); - - if( g_failures == 0 ) + CHECK(r.firstAddrChar == '/'); + CHECK(r.i32 == 42); + CHECK(r.f == 3.5); + CHECK(r.ch == 'z'); + CHECK(r.rgba == 0x11223344u); + CHECK(r.midi == 0x55667788u); + CHECK(r.i64 == 0x0123456789ABCDEFLL); + CHECK(r.timetag == 0xFEDCBA9876543210ULL); + CHECK(r.d == 2.5); + CHECK(r.str != nullptr && std::strcmp(r.str, "hello") == 0); + CHECK(r.sym != nullptr && std::strcmp(r.sym, "sym") == 0); + CHECK(r.boolTrue == true); + CHECK(r.boolFalse == false); + CHECK(r.argCount > 0); + CHECK(r.blob != nullptr && r.blobSize == 5 && static_cast(r.blob)[0] == 1 + && static_cast(r.blob)[4] == 5); + + if (g_failures == 0) std::cout << "realtime test: read hot path OK (RT-safe)\n"; return g_failures == 0 ? 0 : 1; } diff --git a/tests/OscStreamFramingTest.cpp b/tests/OscStreamFramingTest.cpp index 0314566..e910e3c 100644 --- a/tests/OscStreamFramingTest.cpp +++ b/tests/OscStreamFramingTest.cpp @@ -16,109 +16,113 @@ #include static int failures = 0; -#define CHECK(cond) do{ if(!(cond)){ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++failures; } }while(0) +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL line %d: %s\n", __LINE__, #cond); \ + ++failures; \ + } \ + } while (0) // Collects emitted packets so we can compare against what was framed. struct Collector { std::vector packets; - void operator()( const char* p, uint32_t n ) { packets.emplace_back( p, p + n ); } + void operator()(const char* p, uint32_t n) { packets.emplace_back(p, p + n); } }; // Build a wire buffer: each input packet length-prefixed and concatenated. -static std::vector Wire( const std::vector& packets ) -{ +static std::vector Wire(const std::vector& packets) { std::vector w; - for( const auto& pkt : packets ){ + for (const auto& pkt : packets) { char hdr[4]; - osctap::WriteOscStreamFrameHeader( hdr, (uint32_t)pkt.size() ); - w.insert( w.end(), hdr, hdr + 4 ); - w.insert( w.end(), pkt.begin(), pkt.end() ); + osctap::WriteOscStreamFrameHeader(hdr, (uint32_t)pkt.size()); + w.insert(w.end(), hdr, hdr + 4); + w.insert(w.end(), pkt.begin(), pkt.end()); } return w; } // Feed `wire` to a fresh deframer in fixed-size chunks; return the emitted packets. -static std::vector DeframeInChunks( const std::vector& wire, std::size_t chunk ) -{ +static std::vector DeframeInChunks(const std::vector& wire, std::size_t chunk) { osctap::OscStreamDeframer d; - Collector c; - bool ok = true; - for( std::size_t i = 0; i < wire.size(); i += chunk ){ + Collector c; + bool ok = true; + for (std::size_t i = 0; i < wire.size(); i += chunk) { std::size_t n = (i + chunk <= wire.size()) ? chunk : (wire.size() - i); - ok = d.Consume( wire.data() + i, n, c ) && ok; + ok = d.Consume(wire.data() + i, n, c) && ok; } - if( !ok ) c.packets.clear(); // signal protocol error to the caller's check + if (!ok) + c.packets.clear(); // signal protocol error to the caller's check return c.packets; } -int main() -{ +int main() { const std::vector packets = { - std::string( "/a\0\0,i\0\0\0\0\0\1", 12 ), // a small "message-ish" blob - std::string( "hello" ), - std::string( 1000, 'x' ), // spans typical read sizes - std::string( "" ), // empty payload (valid frame) - std::string( "/z" ), + std::string("/a\0\0,i\0\0\0\0\0\1", 12), // a small "message-ish" blob + std::string("hello"), + std::string(1000, 'x'), // spans typical read sizes + std::string(""), // empty payload (valid frame) + std::string("/z"), }; - const std::vector wire = Wire( packets ); + const std::vector wire = Wire(packets); // Feed the same stream at every chunk size from 1 byte up to the whole buffer: // reassembly must be invariant to how the bytes are split. - for( std::size_t chunk = 1; chunk <= wire.size(); ++chunk ){ - const std::vector got = DeframeInChunks( wire, chunk ); - bool same = ( got.size() == packets.size() ); - for( std::size_t i = 0; same && i < got.size(); ++i ) - same = ( got[i] == packets[i] ); - if( !same ){ - std::printf( "FAIL: mismatch at chunk size %zu (got %zu packets)\n", chunk, got.size() ); + for (std::size_t chunk = 1; chunk <= wire.size(); ++chunk) { + const std::vector got = DeframeInChunks(wire, chunk); + bool same = (got.size() == packets.size()); + for (std::size_t i = 0; same && i < got.size(); ++i) + same = (got[i] == packets[i]); + if (!same) { + std::printf("FAIL: mismatch at chunk size %zu (got %zu packets)\n", chunk, got.size()); ++failures; } } // Coalesced: the whole stream in one Consume() yields every packet in order. { - osctap::OscStreamDeframer d; Collector c; - CHECK( d.Consume( wire.data(), wire.size(), c ) ); - CHECK( c.packets.size() == packets.size() ); + osctap::OscStreamDeframer d; + Collector c; + CHECK(d.Consume(wire.data(), wire.size(), c)); + CHECK(c.packets.size() == packets.size()); } // Two deframers fed the same split stream agree (no shared/static state). - { - CHECK( DeframeInChunks( wire, 3 ) == DeframeInChunks( wire, 5 ) ); - } + { CHECK(DeframeInChunks(wire, 3) == DeframeInChunks(wire, 5)); } // Oversized-frame DoS guard: a header announcing > maxFrameSize -> Consume // returns false, and does so even when only the header has arrived. { - osctap::OscStreamDeframer d( 16 ); // tiny cap - char hdr[4]; - osctap::WriteOscStreamFrameHeader( hdr, 1u << 20 ); // 1 MiB announced + osctap::OscStreamDeframer d(16); // tiny cap + char hdr[4]; + osctap::WriteOscStreamFrameHeader(hdr, 1u << 20); // 1 MiB announced Collector c; - CHECK( d.Consume( hdr, 4, c ) == false ); - CHECK( c.packets.empty() ); + CHECK(d.Consume(hdr, 4, c) == false); + CHECK(c.packets.empty()); } // A frame exactly at the cap is accepted; one byte over is rejected. { - osctap::OscStreamDeframer ok( 8 ), over( 8 ); - Collector c1, c2; - const std::vector w8 = Wire( { std::string( 8, 'a' ) } ); - const std::vector w9 = Wire( { std::string( 9, 'b' ) } ); - CHECK( ok.Consume( w8.data(), w8.size(), c1 ) == true ); - CHECK( c1.packets.size() == 1 && c1.packets[0].size() == 8 ); - CHECK( over.Consume( w9.data(), w9.size(), c2 ) == false ); + osctap::OscStreamDeframer ok(8), over(8); + Collector c1, c2; + const std::vector w8 = Wire({std::string(8, 'a')}); + const std::vector w9 = Wire({std::string(9, 'b')}); + CHECK(ok.Consume(w8.data(), w8.size(), c1) == true); + CHECK(c1.packets.size() == 1 && c1.packets[0].size() == 8); + CHECK(over.Consume(w9.data(), w9.size(), c2) == false); } // FrameOscPacket convenience: correct framing + capacity check. { - char out[16]; - std::size_t n = osctap::FrameOscPacket( "hey", 3, out, sizeof(out) ); - CHECK( n == 7 ); - CHECK( osctap::ToUInt32( out ) == 3 ); - CHECK( std::memcmp( out + 4, "hey", 3 ) == 0 ); - CHECK( osctap::FrameOscPacket( "hey", 3, out, 6 ) == 0 ); // 4+3 > 6 -> no fit + char out[16]; + std::size_t n = osctap::FrameOscPacket("hey", 3, out, sizeof(out)); + CHECK(n == 7); + CHECK(osctap::ToUInt32(out) == 3); + CHECK(std::memcmp(out + 4, "hey", 3) == 0); + CHECK(osctap::FrameOscPacket("hey", 3, out, 6) == 0); // 4+3 > 6 -> no fit } - if( failures == 0 ) std::printf( "OscStreamFramingTest: OK\n" ); + if (failures == 0) + std::printf("OscStreamFramingTest: OK\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/OscTcpTest.cpp b/tests/OscTcpTest.cpp index 915b5a4..95a6742 100644 --- a/tests/OscTcpTest.cpp +++ b/tests/OscTcpTest.cpp @@ -9,11 +9,6 @@ server with AsynchronousBreak() from the main thread. */ -#include "ip/TcpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscPacketListener.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include @@ -22,36 +17,47 @@ #include #include +#include "ip/IpEndpointName.h" +#include "ip/TcpSocket.h" +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscPacketListener.h" + namespace { -class RecordingListener : public osctap::OscPacketListener { -public: - std::atomic count{ 0 }; - std::vector addresses; // written by server thread, read after join - std::vector sizes; // payload size hint per message - -protected: - void ProcessMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& ) override - { - addresses.emplace_back( m.AddressPattern() ); - int sz = 0; - auto a = m.ArgumentsBegin(); - if( a != m.ArgumentsEnd() ){ - if( a->IsInt32() ) sz = a->AsInt32Unchecked(); - else if( a->IsString() ) sz = (int)std::strlen( a->AsStringUnchecked() ); + class RecordingListener : public osctap::OscPacketListener { + public: + std::atomic count{0}; + std::vector addresses; // written by server thread, read after join + std::vector sizes; // payload size hint per message + + protected: + void ProcessMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName&) override { + addresses.emplace_back(m.AddressPattern()); + int sz = 0; + auto a = m.ArgumentsBegin(); + if (a != m.ArgumentsEnd()) { + if (a->IsInt32()) + sz = a->AsInt32Unchecked(); + else if (a->IsString()) + sz = (int)std::strlen(a->AsStringUnchecked()); + } + sizes.push_back(sz); + count.fetch_add(1, std::memory_order_relaxed); } - sizes.push_back( sz ); - count.fetch_add( 1, std::memory_order_relaxed ); - } -}; + }; -int failures = 0; -#define CHECK(c) do{ if(!(c)){ std::printf("FAIL line %d: %s\n", __LINE__, #c); ++failures; } }while(0) + int failures = 0; +#define CHECK(c) \ + do { \ + if (!(c)) { \ + std::printf("FAIL line %d: %s\n", __LINE__, #c); \ + ++failures; \ + } \ + } while (0) } // namespace -int main() -{ +int main() { RecordingListener listener; // Bind the server to an OS-assigned loopback port, then discover it. A bind @@ -59,73 +65,74 @@ int main() // CI) -> skip rather than fail, matching OscUdpTest / OscConcurrencyTest. osctap::TcpListeningReceiveSocket* serverPtr = nullptr; try { - serverPtr = new osctap::TcpListeningReceiveSocket( - osctap::IpEndpointName( 127, 0, 0, 1, 0 ), &listener ); - } catch( const std::exception& e ) { - std::printf( "OscTcpTest: SKIP (cannot bind loopback TCP: %s)\n", e.what() ); + serverPtr = new osctap::TcpListeningReceiveSocket(osctap::IpEndpointName(127, 0, 0, 1, 0), &listener); + } + catch (const std::exception& e) { + std::printf("OscTcpTest: SKIP (cannot bind loopback TCP: %s)\n", e.what()); return 0; } osctap::TcpListeningReceiveSocket& server = *serverPtr; - const int port = server.LocalEndpointFor( osctap::IpEndpointName( 127, 0, 0, 1, 0 ) ).port; - CHECK( port > 0 ); + const int port = server.LocalEndpointFor(osctap::IpEndpointName(127, 0, 0, 1, 0)).port; + CHECK(port > 0); - std::thread serverThread( [&]{ server.Run(); } ); + std::thread serverThread([&] { server.Run(); }); // Client: connect and send four messages. The last is large enough to be // split across TCP segments, forcing the server-side deframer to reassemble. - const std::string big( 4000, 'x' ); - bool sent = false; + const std::string big(4000, 'x'); + bool sent = false; try { - osctap::TcpTransmitSocket client( osctap::IpEndpointName( 127, 0, 0, 1, port ) ); - char buf[8192]; + osctap::TcpTransmitSocket client(osctap::IpEndpointName(127, 0, 0, 1, port)); + char buf[8192]; { - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( "/m1" ) << (int32_t)11 << osctap::EndMessage(); - client.Send( p.Data(), p.Size() ); + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage("/m1") << (int32_t)11 << osctap::EndMessage(); + client.Send(p.Data(), p.Size()); } { - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( "/m2" ) << (int32_t)22 << osctap::EndMessage(); - client.Send( p.Data(), p.Size() ); + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage("/m2") << (int32_t)22 << osctap::EndMessage(); + client.Send(p.Data(), p.Size()); } { - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( "/m3" ) << "hello" << osctap::EndMessage(); - client.Send( p.Data(), p.Size() ); + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage("/m3") << "hello" << osctap::EndMessage(); + client.Send(p.Data(), p.Size()); } { - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( "/big" ) << big.c_str() << osctap::EndMessage(); - client.Send( p.Data(), p.Size() ); + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage("/big") << big.c_str() << osctap::EndMessage(); + client.Send(p.Data(), p.Size()); } sent = true; - } catch( const std::exception& e ) { + } + catch (const std::exception& e) { // Connect/send denied by the environment -> skip (not a library failure). - std::printf( "OscTcpTest: SKIP (loopback TCP send unavailable: %s)\n", e.what() ); + std::printf("OscTcpTest: SKIP (loopback TCP send unavailable: %s)\n", e.what()); } // Wait (bounded) for all four to arrive, then stop the server. - if( sent ){ - for( int i = 0; i < 500 && listener.count.load() < 4; ++i ) - std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + if (sent) { + for (int i = 0; i < 500 && listener.count.load() < 4; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } server.AsynchronousBreak(); serverThread.join(); delete serverPtr; - if( !sent ) + if (!sent) return 0; // skipped: send path unavailable in this environment - CHECK( listener.count.load() == 4 ); - if( listener.addresses.size() == 4 ){ - CHECK( listener.addresses[0] == "/m1" && listener.sizes[0] == 11 ); - CHECK( listener.addresses[1] == "/m2" && listener.sizes[1] == 22 ); - CHECK( listener.addresses[2] == "/m3" && listener.sizes[2] == 5 ); // strlen("hello") - CHECK( listener.addresses[3] == "/big" && listener.sizes[3] == 4000 ); // reassembled + CHECK(listener.count.load() == 4); + if (listener.addresses.size() == 4) { + CHECK(listener.addresses[0] == "/m1" && listener.sizes[0] == 11); + CHECK(listener.addresses[1] == "/m2" && listener.sizes[1] == 22); + CHECK(listener.addresses[2] == "/m3" && listener.sizes[2] == 5); // strlen("hello") + CHECK(listener.addresses[3] == "/big" && listener.sizes[3] == 4000); // reassembled } - if( failures == 0 ) - std::printf( "OscTcpTest: OK (4 packets over TCP, incl. a reassembled 4000-byte message)\n" ); + if (failures == 0) + std::printf("OscTcpTest: OK (4 packets over TCP, incl. a reassembled 4000-byte message)\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/OscUdpTest.cpp b/tests/OscUdpTest.cpp index 12224de..748ba30 100644 --- a/tests/OscUdpTest.cpp +++ b/tests/OscUdpTest.cpp @@ -12,11 +12,6 @@ the test SKIPs (prints a notice, returns success) rather than failing. */ -#include "ip/UdpSocket.h" -#include "ip/IpEndpointName.h" -#include "osc/OscPacketListener.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include @@ -25,88 +20,100 @@ #include #include +#include "ip/IpEndpointName.h" +#include "ip/UdpSocket.h" +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscPacketListener.h" + namespace { -class RecordingListener : public osctap::OscPacketListener { -public: - std::atomic count{ 0 }; - std::vector addresses; // written by receive thread, read after join - std::vector values; - -protected: - void ProcessMessage( const osctap::ReceivedMessage& m, const osctap::IpEndpointName& ) override - { - addresses.emplace_back( m.AddressPattern() ); - int v = 0; - auto a = m.ArgumentsBegin(); - if( a != m.ArgumentsEnd() ){ - if( a->IsInt32() ) v = a->AsInt32Unchecked(); - else if( a->IsString() ) v = (int)std::strlen( a->AsStringUnchecked() ); + class RecordingListener : public osctap::OscPacketListener { + public: + std::atomic count{0}; + std::vector addresses; // written by receive thread, read after join + std::vector values; + + protected: + void ProcessMessage(const osctap::ReceivedMessage& m, const osctap::IpEndpointName&) override { + addresses.emplace_back(m.AddressPattern()); + int v = 0; + auto a = m.ArgumentsBegin(); + if (a != m.ArgumentsEnd()) { + if (a->IsInt32()) + v = a->AsInt32Unchecked(); + else if (a->IsString()) + v = (int)std::strlen(a->AsStringUnchecked()); + } + values.push_back(v); + count.fetch_add(1, std::memory_order_relaxed); } - values.push_back( v ); - count.fetch_add( 1, std::memory_order_relaxed ); - } -}; + }; -int failures = 0; -#define CHECK(c) do{ if(!(c)){ std::printf("FAIL line %d: %s\n", __LINE__, #c); ++failures; } }while(0) + int failures = 0; +#define CHECK(c) \ + do { \ + if (!(c)) { \ + std::printf("FAIL line %d: %s\n", __LINE__, #c); \ + ++failures; \ + } \ + } while (0) } // namespace -int main() -{ +int main() { RecordingListener listener; // Bind the receiver to an OS-assigned loopback port. A bind failure here means // the environment forbids loopback networking -> skip. osctap::UdpListeningReceiveSocket* receiver = nullptr; try { - receiver = new osctap::UdpListeningReceiveSocket( - osctap::IpEndpointName( 127, 0, 0, 1, 0 ), &listener ); - } catch( const std::exception& e ) { - std::printf( "OscUdpTest: SKIP (cannot bind loopback UDP: %s)\n", e.what() ); + receiver = new osctap::UdpListeningReceiveSocket(osctap::IpEndpointName(127, 0, 0, 1, 0), &listener); + } + catch (const std::exception& e) { + std::printf("OscUdpTest: SKIP (cannot bind loopback UDP: %s)\n", e.what()); return 0; } const int port = receiver->LocalPort(); - std::thread runner( [&]{ receiver->Run(); } ); + std::thread runner([&] { receiver->Run(); }); bool sent = false; try { - osctap::UdpTransmitSocket sender( osctap::IpEndpointName( 127, 0, 0, 1, port ) ); - char buf[256]; - const char* addrs[] = { "/u1", "/u2", "/u3" }; - const int ints[] = { 11, 22, 33 }; - for( int i = 0; i < 3; ++i ){ - osctap::OutboundPacketStream p( buf, sizeof(buf) ); - p << osctap::BeginMessage( addrs[i] ) << (int32_t)ints[i] << osctap::EndMessage(); - sender.Send( p.Data(), p.Size() ); + osctap::UdpTransmitSocket sender(osctap::IpEndpointName(127, 0, 0, 1, port)); + char buf[256]; + const char* addrs[] = {"/u1", "/u2", "/u3"}; + const int ints[] = {11, 22, 33}; + for (int i = 0; i < 3; ++i) { + osctap::OutboundPacketStream p(buf, sizeof(buf)); + p << osctap::BeginMessage(addrs[i]) << (int32_t)ints[i] << osctap::EndMessage(); + sender.Send(p.Data(), p.Size()); } sent = true; - } catch( const std::exception& e ) { - std::printf( "OscUdpTest: SKIP (loopback UDP send unavailable: %s)\n", e.what() ); + } + catch (const std::exception& e) { + std::printf("OscUdpTest: SKIP (loopback UDP send unavailable: %s)\n", e.what()); } - if( sent ){ - for( int i = 0; i < 500 && listener.count.load() < 3; ++i ) - std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + if (sent) { + for (int i = 0; i < 500 && listener.count.load() < 3; ++i) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } receiver->AsynchronousBreak(); runner.join(); delete receiver; - if( !sent ) + if (!sent) return 0; // skipped: send path unavailable in this environment - CHECK( listener.count.load() == 3 ); - if( listener.addresses.size() == 3 ){ - CHECK( listener.addresses[0] == "/u1" && listener.values[0] == 11 ); - CHECK( listener.addresses[1] == "/u2" && listener.values[1] == 22 ); - CHECK( listener.addresses[2] == "/u3" && listener.values[2] == 33 ); + CHECK(listener.count.load() == 3); + if (listener.addresses.size() == 3) { + CHECK(listener.addresses[0] == "/u1" && listener.values[0] == 11); + CHECK(listener.addresses[1] == "/u2" && listener.values[1] == 22); + CHECK(listener.addresses[2] == "/u3" && listener.values[2] == 33); } - if( failures == 0 ) - std::printf( "OscUdpTest: OK (3 packets over UDP loopback)\n" ); + if (failures == 0) + std::printf("OscUdpTest: OK (3 packets over UDP loopback)\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/OscUnitTests.cpp b/tests/OscUnitTests.cpp index 3b064b3..699b8e2 100644 --- a/tests/OscUnitTests.cpp +++ b/tests/OscUnitTests.cpp @@ -1,654 +1,614 @@ /* - oscpack -- Open Sound Control (OSC) packet manipulation library + oscpack -- Open Sound Control (OSC) packet manipulation library http://www.rossbencina.com/code/oscpack Copyright (c) 2004-2013 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. - - 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. + 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. + + 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. */ /* - The text above constitutes the entire oscpack license; however, - the oscpack developer(s) also make the following non-binding requests: - - 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. It is also - requested that these non-binding requests be included whenever the - above license is reproduced. + The text above constitutes the entire oscpack license; however, + the oscpack developer(s) also make the following non-binding requests: + + 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. It is also + requested that these non-binding requests be included whenever the + above license is reproduced. */ #include "OscUnitTests.h" -#include #include +#include #include #include +#include -#include "osc/OscReceivedElements.h" -#include "osc/OscPrintReceivedElements.h" +#include "ip/IpEndpointName.h" #include "osc/OscOutboundPacketStream.h" #include "osc/OscPacketListener.h" -#include "ip/IpEndpointName.h" - -#include +#include "osc/OscPrintReceivedElements.h" +#include "osc/OscReceivedElements.h" // Compile-time proof that the big-endian integer load path is constexpr // ("constexpr parsing"). Uses the oscpack:: alias deliberately (it must keep // resolving to osctap::). The signed/float paths are constexpr too, but only // when std::bit_cast is available (C++20). namespace { - constexpr char kBE42[4] = { 0, 0, 0, 42 }; - static_assert( oscpack::LoadBigEndian32( kBE42 ) == 42u, "constexpr LoadBigEndian32" ); - static_assert( oscpack::ToUInt32( kBE42 ) == 42u, "constexpr ToUInt32" ); - static_assert( oscpack::RoundUp4( 5 ) == 8u, "constexpr RoundUp4" ); + constexpr char kBE42[4] = {0, 0, 0, 42}; + static_assert(oscpack::LoadBigEndian32(kBE42) == 42u, "constexpr LoadBigEndian32"); + static_assert(oscpack::ToUInt32(kBE42) == 42u, "constexpr ToUInt32"); + static_assert(oscpack::RoundUp4(5) == 8u, "constexpr RoundUp4"); #if defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L - static_assert( oscpack::ToInt32( kBE42 ) == 42, "constexpr ToInt32 (bit_cast)" ); + static_assert(oscpack::ToInt32(kBE42) == 42, "constexpr ToInt32 (bit_cast)"); #endif -} - +} // namespace #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug namespace std { -using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'. -using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'. -} + using ::__strcmp__; // avoid error: E2316 '__strcmp__' is not a member of 'std'. + using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'. +} // namespace std #endif -namespace osc{ +namespace osc { - // NOTE: this deliberately uses the legacy `oscpack` namespace (now a - // compatibility alias for `osctap`). Leaving the tests on the alias is the - // live verification that the shim works -- do not rename to `osctap`. - using namespace oscpack; + // NOTE: this deliberately uses the legacy `oscpack` namespace (now a + // compatibility alias for `osctap`). Leaving the tests on the alias is the + // live verification that the shim works -- do not rename to `osctap`. + using namespace oscpack; -static int passCount_=0, failCount_=0; + static int passCount_ = 0, failCount_ = 0; -void PrintTestSummary() -{ - std::cout << (passCount_+failCount_) << " tests run, " << passCount_ << " passed, " << failCount_ << " failed.\n"; -} + void PrintTestSummary() { + std::cout << (passCount_ + failCount_) << " tests run, " << passCount_ << " passed, " << failCount_ + << " failed.\n"; + } -int FailureCount() -{ - return failCount_; -} + int FailureCount() { + return failCount_; + } -void pass_equality( const char *slhs, const char *srhs, const char *file, int line ) -{ - ++passCount_; - std::cout << file << "(" << line << "): PASSED : " << slhs << " == " << srhs << "\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"; -} + 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 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( std::strcmp( lhs, rhs ) == 0 ) - 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 (std::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__) -#define assertEqual( a, b ) assertEqual_( (a), (b), #a, #b, __FILE__, __LINE__ ) + //--------------------------------------------------------------------------- + char* AllocateAligned4(std::size_t size) { + char* s = new char[size + 4]; // over-allocate so we can round up to a 4-byte boundary + // Use uintptr_t, not long: long is 32-bit on Win64 (LLP64), which would + // truncate the pointer. + return (char*)(((uintptr_t)(s - 1) & ~(uintptr_t)0x03) + 4); + } -//--------------------------------------------------------------------------- -char * AllocateAligned4( std::size_t size ) -{ - char *s = new char[ size + 4 ]; // over-allocate so we can round up to a 4-byte boundary - // Use uintptr_t, not long: long is 32-bit on Win64 (LLP64), which would - // truncate the pointer. - return (char*)( ((uintptr_t)(s - 1) & ~(uintptr_t)0x03) + 4 ); -} + // allocate a 4 byte aligned copy of s + char* NewMessageBuffer(const char* s, std::size_t length) { + char* p = AllocateAligned4(length); + std::memcpy(p, s, length); + return p; + } -// allocate a 4 byte aligned copy of s -char * NewMessageBuffer( const char *s, std::size_t length ) -{ - char *p = AllocateAligned4( length ); - std::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); -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( std::strcmp( m.AddressPattern(), "/test" ), 0 ); - assertEqual( std::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 n2 = (i++)->AsInt32(); - (void)n2; - }catch( Exception& ){ - exceptionThrown = true; - } - assertEqual( exceptionThrown, true ); + // test argument iterator interface + bool unexpectedExceptionCaught = false; + try { + ReceivedMessage m(ReceivedPacket(buffer, sizeof(s) - 1)); - }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; - int32_t 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 ); -} + assertEqual(std::strcmp(m.AddressPattern(), "/test"), 0); + assertEqual(std::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 n2 = (i++)->AsInt32(); + (void)n2; + } + 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); -#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";\ - } + float f; + int32_t n; + bool b; + args >> f >> n >> b; -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" ); - // 0123012 3 0123012 3 0 1 2 3 0 1 2 3 0 1 2 3 - TEST2_PRINT( "/Array\0\0,[iii]\0\0\0\0\0\x1\0\0\0\x2\0\0\0\x3" ); - - 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; + (void)f; + (void)n; + (void)b; + + assertEqual(args.Eos(), true); + } + catch (Exception& e) { + std::cout << "unexpected exception: " << e.what() << "\n"; + unexpectedExceptionCaught = true; } - assertEqual( exceptionThrown, true ); - - }catch( Exception& e ){ - std::cout << "unexpected exception: " << e.what() << "\n"; - unexpectedExceptionCaught = true; + assertEqual(unexpectedExceptionCaught, false); } - 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 ) \ - { \ - std::memset( buffer, 0x74, bufferSize ); \ - OutboundPacketStream ps( buffer, bufferSize ); \ - ps << BeginMessage( addressPattern ) \ - << argument \ - << oscpack::EndMessage();\ - assertEqual( ps.IsReady(), true );\ - ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) );\ - std::cout << m << "\n";\ - assertEqual( m.ArgumentsBegin()-> recieveGetter () , value );\ - } \ - { \ - std::memset( buffer, 0x74, bufferSize ); \ - OutboundPacketStream ps( buffer, bufferSize ); \ - ps << BeginBundle( 1234 ) \ - << BeginMessage( addressPattern ) \ - << argument \ - << oscpack::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 TEST2_PRINT(ss) \ + { \ + const char s[] = ss; \ + ReceivedPacket p(NewMessageBuffer(s, sizeof(s) - 1), sizeof(s) - 1); \ + ReceivedMessage m(p); \ + std::cout << m << "\n"; \ } - -#define TEST_PACK_UNPACK( addressPattern, argument, type, recieveGetter ) \ - { \ - std::memset( buffer, 0x74, bufferSize ); \ - OutboundPacketStream ps( buffer, bufferSize ); \ - ps << BeginMessage( addressPattern ) \ - << argument \ - << oscpack::EndMessage();\ - assertEqual( ps.IsReady(), true );\ - ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) );\ - std::cout << m << "\n";\ - assertEqual( m.ArgumentsBegin()-> recieveGetter () , ( type ) argument );\ - } \ - { \ - std::memset( buffer, 0x74, bufferSize ); \ - OutboundPacketStream ps( buffer, bufferSize ); \ - ps << BeginBundle( 1234 ) \ - << BeginMessage( addressPattern ) \ - << argument \ - << oscpack::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 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"); + // 0123012 3 0123012 3 0 1 2 3 0 1 2 3 0 1 2 3 + TEST2_PRINT("/Array\0\0,[iii]\0\0\0\0\0\x1\0\0\0\x2\0\0\0\x3"); + + 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); } -void test3() -{ - int bufferSize = 1000; - char *buffer = AllocateAligned4( bufferSize ); - -// single message tests - // empty message - { - std::memset( buffer, 0x74, bufferSize ); - OutboundPacketStream ps( buffer, bufferSize ); - ps << BeginMessage( "/no_arguments" ) - << oscpack::EndMessage(); - assertEqual( ps.IsReady(), true ); - ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) ); - std::cout << m << "\n";\ + //----------------------------------------------------------------------- + + // 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) \ + { \ + std::memset(buffer, 0x74, bufferSize); \ + OutboundPacketStream ps(buffer, bufferSize); \ + ps << BeginMessage(addressPattern) << argument << oscpack::EndMessage(); \ + assertEqual(ps.IsReady(), true); \ + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); \ + std::cout << m << "\n"; \ + assertEqual(m.ArgumentsBegin()->recieveGetter(), value); \ + } \ + { \ + std::memset(buffer, 0x74, bufferSize); \ + OutboundPacketStream ps(buffer, bufferSize); \ + ps << BeginBundle(1234) << BeginMessage(addressPattern) << argument << oscpack::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); \ } - 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 ); +#define TEST_PACK_UNPACK(addressPattern, argument, type, recieveGetter) \ + { \ + std::memset(buffer, 0x74, bufferSize); \ + OutboundPacketStream ps(buffer, bufferSize); \ + ps << BeginMessage(addressPattern) << argument << oscpack::EndMessage(); \ + assertEqual(ps.IsReady(), true); \ + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); \ + std::cout << m << "\n"; \ + assertEqual(m.ArgumentsBegin()->recieveGetter(), (type)argument); \ + } \ + { \ + std::memset(buffer, 0x74, bufferSize); \ + OutboundPacketStream ps(buffer, bufferSize); \ + ps << BeginBundle(1234) << BeginMessage(addressPattern) << argument << oscpack::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 + { + std::memset(buffer, 0x74, bufferSize); + OutboundPacketStream ps(buffer, bufferSize); + ps << BeginMessage("/no_arguments") << oscpack::EndMessage(); + assertEqual(ps.IsReady(), true); + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); + std::cout << m << "\n"; + } - TEST_PACK_UNPACK0( "/nil", OscNil(), true, IsNil ); - TEST_PACK_UNPACK0( "/inf", Infinitum(), true, IsInfinitum ); + 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_UNPACK( "/an_int", (int32_t)1234, int32_t, AsInt32 ); + TEST_PACK_UNPACK0("/nil", OscNil(), true, IsNil); + TEST_PACK_UNPACK0("/inf", Infinitum(), true, IsInfinitum); - TEST_PACK_UNPACK( "/a_float", 3.1415926f, float, AsFloat ); + TEST_PACK_UNPACK("/an_int", (int32_t)1234, int32_t, AsInt32); - TEST_PACK_UNPACK( "/a_char", 'c', char, AsChar ); + TEST_PACK_UNPACK("/a_float", 3.1415926f, float, AsFloat); - TEST_PACK_UNPACK( "/an_rgba_color", RgbaColor(0x22334455), uint32_t, AsRgbaColor ); + TEST_PACK_UNPACK("/a_char", 'c', char, AsChar); - TEST_PACK_UNPACK( "/a_midi_message", MidiMessage(0x7F), uint32_t, AsMidiMessage ); + TEST_PACK_UNPACK("/an_rgba_color", RgbaColor(0x22334455), uint32_t, AsRgbaColor); - TEST_PACK_UNPACK( "/an_int64_t", (int64_t)(0xFFFFFFFF), int64_t, AsInt64 ); + TEST_PACK_UNPACK("/a_midi_message", MidiMessage(0x7F), uint32_t, AsMidiMessage); - TEST_PACK_UNPACK( "/a_time_tag", TimeTag(0xFFFFFFFF), uint64_t, AsTimeTag ); + TEST_PACK_UNPACK("/an_int64_t", (int64_t)(0xFFFFFFFF), int64_t, AsInt64); - TEST_PACK_UNPACK( "/a_double", (double)3.1415926, double, AsDouble ); + TEST_PACK_UNPACK("/a_time_tag", TimeTag(0xFFFFFFFF), uint64_t, AsTimeTag); - // blob - { - char blobData[] = "abcd"; - std::memset( buffer, 0x74, bufferSize ); - OutboundPacketStream ps( buffer, bufferSize ); - ps << BeginMessage( "/a_blob" ) - << Blob( blobData, 4 ) - << oscpack::EndMessage(); - assertEqual( ps.IsReady(), true ); - ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) ); - std::cout << m << "\n"; + TEST_PACK_UNPACK("/a_double", (double)3.1415926, double, AsDouble); - const void *value; - osc_bundle_element_size_t size; - m.ArgumentsBegin()->AsBlob( value, size ); - assertEqual( size, (osc_bundle_element_size_t)4 ); - assertEqual( (memcmp( value, blobData, 4 ) == 0), true ); - } + // blob + { + char blobData[] = "abcd"; + std::memset(buffer, 0x74, bufferSize); + OutboundPacketStream ps(buffer, bufferSize); + ps << BeginMessage("/a_blob") << Blob(blobData, 4) << oscpack::EndMessage(); + assertEqual(ps.IsReady(), true); + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); + std::cout << m << "\n"; - // array - { - int32_t arrayData[] = {1,2,3,4}; - const std::size_t sourceArrayItemCount = 4; - std::memset( buffer, 0x74, bufferSize ); - OutboundPacketStream ps( buffer, bufferSize ); - ps << BeginMessage( "/an_array" ) - << BeginArray(); - for( std::size_t j=0; j < sourceArrayItemCount; ++j ) - ps << arrayData[j]; - ps << EndArray() << oscpack::EndMessage(); - assertEqual( ps.IsReady(), true ); - ReceivedMessage m( ReceivedPacket(ps.Data(), ps.Size()) ); - std::cout << m << "\n"; - - ReceivedMessageArgumentIterator i = m.ArgumentsBegin(); - assertEqual( i->IsArrayBegin(), true ); - assertEqual( i->ComputeArrayItemCount(), sourceArrayItemCount ); - std::size_t arrayItemCount = i->ComputeArrayItemCount(); - ++i; // move past array begin marker - for( std::size_t j=0; j < arrayItemCount; ++j ){ - assertEqual( true, i->IsInt32() ); - int32_t k = i->AsInt32(); - assertEqual( k, arrayData[j] ); - ++i; + const void* value; + osc_bundle_element_size_t size; + m.ArgumentsBegin()->AsBlob(value, size); + assertEqual(size, (osc_bundle_element_size_t)4); + assertEqual((memcmp(value, blobData, 4) == 0), true); } - assertEqual( i->IsArrayEnd(), true ); - } - - - - TEST_PACK_UNPACK( "/a_string", "hello world", const char*, AsString ); + // array + { + int32_t arrayData[] = {1, 2, 3, 4}; + const std::size_t sourceArrayItemCount = 4; + std::memset(buffer, 0x74, bufferSize); + OutboundPacketStream ps(buffer, bufferSize); + ps << BeginMessage("/an_array") << BeginArray(); + for (std::size_t j = 0; j < sourceArrayItemCount; ++j) + ps << arrayData[j]; + ps << EndArray() << oscpack::EndMessage(); + assertEqual(ps.IsReady(), true); + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); + std::cout << m << "\n"; + + ReceivedMessageArgumentIterator i = m.ArgumentsBegin(); + assertEqual(i->IsArrayBegin(), true); + assertEqual(i->ComputeArrayItemCount(), sourceArrayItemCount); + std::size_t arrayItemCount = i->ComputeArrayItemCount(); + ++i; // move past array begin marker + for (std::size_t j = 0; j < arrayItemCount; ++j) { + assertEqual(true, i->IsInt32()); + int32_t k = i->AsInt32(); + assertEqual(k, arrayData[j]); + ++i; + } + + assertEqual(i->IsArrayEnd(), true); + } - TEST_PACK_UNPACK( "/a_symbol", Symbol("foobar"), const char*, AsSymbol ); + 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... + // nested bundles, and multiple messages in bundles... - { - std::memset( buffer, 0x74, bufferSize ); - OutboundPacketStream ps( buffer, bufferSize ); - ps << BeginBundle() - << BeginMessage( "/message_one" ) << 1 << 2 << 3 << 4 << oscpack::EndMessage() - << BeginMessage( "/message_two" ) << 1 << 2 << 3 << 4 << oscpack::EndMessage() - << BeginMessage( "/message_three" ) << 1 << 2 << 3 << 4 << oscpack::EndMessage() - << BeginMessage( "/message_four" ) << 1 << 2 << 3 << 4 << oscpack::EndMessage() - << EndBundle(); - assertEqual( ps.IsReady(), true ); - ReceivedBundle b( ReceivedPacket(ps.Data(), ps.Size()) ); - std::cout << b << "\n"; + { + std::memset(buffer, 0x74, bufferSize); + OutboundPacketStream ps(buffer, bufferSize); + ps << BeginBundle() << BeginMessage("/message_one") << 1 << 2 << 3 << 4 << oscpack::EndMessage() + << BeginMessage("/message_two") << 1 << 2 << 3 << 4 << oscpack::EndMessage() + << BeginMessage("/message_three") << 1 << 2 << 3 << 4 << oscpack::EndMessage() + << BeginMessage("/message_four") << 1 << 2 << 3 << 4 << oscpack::EndMessage() << EndBundle(); + assertEqual(ps.IsReady(), true); + ReceivedBundle b(ReceivedPacket(ps.Data(), ps.Size())); + std::cout << b << "\n"; + } } -} - -//--------------------------------------------------------------------------- -// Regression tests for malformed-packet handling. Several of these crafted -// packets previously slipped past validation -- most importantly the blob-size -// bounds check in ReceivedMessage::Init(), which constructed but never threw -// its MalformedMessageException, allowing an out-of-bounds read. Each packet -// below must now be rejected with an osc::Exception. - -static bool ParsingMessageThrows( const char *data, std::size_t size ) -{ - char *buffer = NewMessageBuffer( data, size ); - try{ - ReceivedMessage m( ReceivedPacket( buffer, size ) ); - // Walking the arguments should never be reached for these inputs -- - // validation in Init() should reject them up front -- but iterate - // anyway so the test fails loudly rather than reading out of bounds. - for( ReceivedMessage::const_iterator i = m.ArgumentsBegin(); - i != m.ArgumentsEnd(); ++i ) - (void)i->TypeTag(); - }catch( const Exception& ){ - return true; + //--------------------------------------------------------------------------- + // Regression tests for malformed-packet handling. Several of these crafted + // packets previously slipped past validation -- most importantly the blob-size + // bounds check in ReceivedMessage::Init(), which constructed but never threw + // its MalformedMessageException, allowing an out-of-bounds read. Each packet + // below must now be rejected with an osc::Exception. + + static bool ParsingMessageThrows(const char* data, std::size_t size) { + char* buffer = NewMessageBuffer(data, size); + try { + ReceivedMessage m(ReceivedPacket(buffer, size)); + // Walking the arguments should never be reached for these inputs -- + // validation in Init() should reject them up front -- but iterate + // anyway so the test fails loudly rather than reading out of bounds. + for (ReceivedMessage::const_iterator i = m.ArgumentsBegin(); i != m.ArgumentsEnd(); ++i) + (void)i->TypeTag(); + } + catch (const Exception&) { + return true; + } + return false; } - return false; -} -static bool ParsingBundleThrows( const char *data, std::size_t size ) -{ - char *buffer = NewMessageBuffer( data, size ); - try{ - ReceivedBundle b( ReceivedPacket( buffer, size ) ); - (void)b.ElementCount(); - }catch( const Exception& ){ - return true; + static bool ParsingBundleThrows(const char* data, std::size_t size) { + char* buffer = NewMessageBuffer(data, size); + try { + ReceivedBundle b(ReceivedPacket(buffer, size)); + (void)b.ElementCount(); + } + catch (const Exception&) { + return true; + } + return false; } - return false; -} -void test4() -{ - // CRITICAL: a blob whose declared size extends far past the packet. - // address "/b", type tags ",b", then a 4-byte blob size of 0x10000000 - // (256 MB, in-range for IsValidElementSizeValue) with no payload present. - { - char m[] = { '/','b',0,0, ',','b',0,0, 0,0,0,0 }; - m[8] = 0x10; // big-endian 0x10000000 blob size, no data follows - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + void test4() { + // CRITICAL: a blob whose declared size extends far past the packet. + // address "/b", type tags ",b", then a 4-byte blob size of 0x10000000 + // (256 MB, in-range for IsValidElementSizeValue) with no payload present. + { + char m[] = {'/', 'b', 0, 0, ',', 'b', 0, 0, 0, 0, 0, 0}; + m[8] = 0x10; // big-endian 0x10000000 blob size, no data follows + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // a blob size that is out of range (0xFFFFFFFF == negative int32) is rejected - { - char m[] = { '/','b',0,0, ',','b',0,0, 0,0,0,0 }; - m[8] = '\xFF'; m[9] = '\xFF'; m[10] = '\xFF'; m[11] = '\xFF'; - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + // a blob size that is out of range (0xFFFFFFFF == negative int32) is rejected + { + char m[] = {'/', 'b', 0, 0, ',', 'b', 0, 0, 0, 0, 0, 0}; + m[8] = '\xFF'; + m[9] = '\xFF'; + m[10] = '\xFF'; + m[11] = '\xFF'; + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // unterminated type tag string (no null terminator before end of packet) - { - const char m[] = { '/','x',0,0, ',','i','i','i' }; - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + // unterminated type tag string (no null terminator before end of packet) + { + const char m[] = {'/', 'x', 0, 0, ',', 'i', 'i', 'i'}; + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // fixed-size argument truncated: type tag 'i' but no 4 bytes of data follow - { - const char m[] = { '/','x',0,0, ',','i',0,0 }; - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + // fixed-size argument truncated: type tag 'i' but no 4 bytes of data follow + { + const char m[] = {'/', 'x', 0, 0, ',', 'i', 0, 0}; + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // array close ']' with no matching open '[' (array-level underflow) - { - const char m[] = { '/','x',0,0, ',',']',0,0 }; - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + // array close ']' with no matching open '[' (array-level underflow) + { + const char m[] = {'/', 'x', 0, 0, ',', ']', 0, 0}; + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // unterminated array: '[' with no closing ']' - { - const char m[] = { '/','x',0,0, ',','[',0,0 }; - assertEqual( ParsingMessageThrows( m, sizeof(m) ), true ); - } + // unterminated array: '[' with no closing ']' + { + const char m[] = {'/', 'x', 0, 0, ',', '[', 0, 0}; + assertEqual(ParsingMessageThrows(m, sizeof(m)), true); + } - // a bundle element whose declared size extends past the packet - { - char b[] = { '#','b','u','n','d','l','e',0, - 0,0,0,0,0,0,0,0, // time tag - 0,0,0,0 }; // element size - b[16] = 0x10; // 0x10000000-byte element, no data follows - assertEqual( ParsingBundleThrows( b, sizeof(b) ), true ); - } + // a bundle element whose declared size extends past the packet + { + char b[] = {'#', 'b', 'u', 'n', 'd', 'l', 'e', 0, 0, 0, 0, 0, 0, 0, 0, 0, // time tag + 0, 0, 0, 0}; // element size + b[16] = 0x10; // 0x10000000-byte element, no data follows + assertEqual(ParsingBundleThrows(b, sizeof(b)), true); + } - // positive control: a well-formed blob must still parse and round-trip. - { - char buffer[64]; - std::memset( buffer, 0, sizeof(buffer) ); - OutboundPacketStream ps( buffer, sizeof(buffer) ); - const char payload[] = { 1, 2, 3, 4, 5 }; - ps << BeginMessage( "/b" ) << Blob( payload, sizeof(payload) ) << oscpack::EndMessage(); - - bool ok = true; - try{ - ReceivedMessage m( ReceivedPacket( ps.Data(), ps.Size() ) ); - ReceivedMessage::const_iterator i = m.ArgumentsBegin(); - const void *data; - osc_bundle_element_size_t size; - i->AsBlob( data, size ); - assertEqual( size, (osc_bundle_element_size_t)sizeof(payload) ); - }catch( const Exception& ){ - ok = false; + // positive control: a well-formed blob must still parse and round-trip. + { + char buffer[64]; + std::memset(buffer, 0, sizeof(buffer)); + OutboundPacketStream ps(buffer, sizeof(buffer)); + const char payload[] = {1, 2, 3, 4, 5}; + ps << BeginMessage("/b") << Blob(payload, sizeof(payload)) << oscpack::EndMessage(); + + bool ok = true; + try { + ReceivedMessage m(ReceivedPacket(ps.Data(), ps.Size())); + ReceivedMessage::const_iterator i = m.ArgumentsBegin(); + const void* data; + osc_bundle_element_size_t size; + i->AsBlob(data, size); + assertEqual(size, (osc_bundle_element_size_t)sizeof(payload)); + } + catch (const Exception&) { + ok = false; + } + assertEqual(ok, true); } - assertEqual( ok, true ); } -} - -//--------------------------------------------------------------------------- -// Regression test for bounded bundle-nesting recursion. A deeply-nested bundle -// is valid OSC but would otherwise recurse once per level in ProcessBundle(), -// allowing a single untrusted packet to exhaust the stack. -namespace { + //--------------------------------------------------------------------------- + // Regression test for bounded bundle-nesting recursion. A deeply-nested bundle + // is valid OSC but would otherwise recurse once per level in ProcessBundle(), + // allowing a single untrusted packet to exhaust the stack. + + namespace { + + struct CountingListener : public oscpack::OscPacketListener { + int messageCount = 0; + void ProcessMessage(const oscpack::ReceivedMessage&, const oscpack::IpEndpointName&) override { + ++messageCount; + } + }; + + // Emit `depth` nested bundles wrapping a single message. + void BuildNestedBundle(OutboundPacketStream& ps, int depth) { + for (int i = 0; i < depth; ++i) + ps << BeginBundle(); + ps << BeginMessage("/deep") << 1 << oscpack::EndMessage(); + for (int i = 0; i < depth; ++i) + ps << EndBundle(); + } -struct CountingListener : public oscpack::OscPacketListener{ - int messageCount = 0; - void ProcessMessage( const oscpack::ReceivedMessage&, - const oscpack::IpEndpointName& ) override - { ++messageCount; } -}; - -// Emit `depth` nested bundles wrapping a single message. -void BuildNestedBundle( OutboundPacketStream& ps, int depth ) -{ - for( int i = 0; i < depth; ++i ) ps << BeginBundle(); - ps << BeginMessage( "/deep" ) << 1 << oscpack::EndMessage(); - for( int i = 0; i < depth; ++i ) ps << EndBundle(); -} + } // namespace -} // namespace + void test5() { + oscpack::IpEndpointName dummy; -void test5() -{ - oscpack::IpEndpointName dummy; - - // Shallow nesting: the inner message is delivered as normal. - { - char buf[1024]; - std::memset( buf, 0, sizeof(buf) ); - OutboundPacketStream ps( buf, sizeof(buf) ); - BuildNestedBundle( ps, 3 ); - CountingListener listener; - listener.ProcessPacket( ps.Data(), (int)ps.Size(), dummy ); - assertEqual( listener.messageCount, 1 ); - } + // Shallow nesting: the inner message is delivered as normal. + { + char buf[1024]; + std::memset(buf, 0, sizeof(buf)); + OutboundPacketStream ps(buf, sizeof(buf)); + BuildNestedBundle(ps, 3); + CountingListener listener; + listener.ProcessPacket(ps.Data(), (int)ps.Size(), dummy); + assertEqual(listener.messageCount, 1); + } - // Pathologically deep nesting (beyond the default limit): processing must - // terminate without exhausting the stack, and the over-deep inner message - // is not delivered. - { - const int depth = - (int)oscpack::OscPacketListener::DEFAULT_MAX_BUNDLE_NESTING_DEPTH + 50; - std::vector buf( 64 + depth * 24, 0 ); - OutboundPacketStream ps( buf.data(), buf.size() ); - BuildNestedBundle( ps, depth ); - CountingListener listener; - listener.ProcessPacket( ps.Data(), (int)ps.Size(), dummy ); - assertEqual( listener.messageCount, 0 ); + // Pathologically deep nesting (beyond the default limit): processing must + // terminate without exhausting the stack, and the over-deep inner message + // is not delivered. + { + const int depth = (int)oscpack::OscPacketListener::DEFAULT_MAX_BUNDLE_NESTING_DEPTH + 50; + std::vector buf(64 + depth * 24, 0); + OutboundPacketStream ps(buf.data(), buf.size()); + BuildNestedBundle(ps, depth); + CountingListener listener; + listener.ProcessPacket(ps.Data(), (int)ps.Size(), dummy); + assertEqual(listener.messageCount, 0); + } } -} - -//--------------------------------------------------------------------------- + //--------------------------------------------------------------------------- -void RunUnitTests() -{ - test1(); - test2(); - test3(); - test4(); - test5(); - PrintTestSummary(); -} + void RunUnitTests() { + test1(); + test2(); + test3(); + test4(); + test5(); + PrintTestSummary(); + } } // namespace osc - #ifndef NO_OSC_TEST_MAIN -int main(int argc, char* argv[]) -{ +int main(int argc, char* argv[]) { (void)argc; (void)argv; diff --git a/tests/OscUnitTests.h b/tests/OscUnitTests.h index 1b62c54..9b780f5 100644 --- a/tests/OscUnitTests.h +++ b/tests/OscUnitTests.h @@ -1,38 +1,38 @@ /* - 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. + 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{ +namespace osc { -void RunUnitTests(); + void RunUnitTests(); } // namespace osc diff --git a/tests/OscValidateTest.cpp b/tests/OscValidateTest.cpp index d52806c..656bb15 100644 --- a/tests/OscValidateTest.cpp +++ b/tests/OscValidateTest.cpp @@ -13,117 +13,141 @@ malformed input by *returning* instead of aborting. */ -#include "osc/OscReceivedElements.h" -#include "osc/OscOutboundPacketStream.h" - #include #include #include +#include "osc/OscOutboundPacketStream.h" +#include "osc/OscReceivedElements.h" + using osctap::osc_bundle_element_size_t; static int failures = 0; -#define CHECK(cond) do{ if(!(cond)){ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++failures; } }while(0) +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + std::printf("FAIL line %d: %s\n", __LINE__, #cond); \ + ++failures; \ + } \ + } while (0) // Oracle: does a *full recursive read* of this packet throw? Mirrors exactly what // TryValidatePacket promises is safe -- construct, and recurse through bundles and // messages, touching everything the validator walks. -static void FullRead( const char* data, osc_bundle_element_size_t size ) -{ - osctap::ReceivedPacket p( data, size ); - if( p.IsBundle() ){ - osctap::ReceivedBundle b( p ); - for( auto i = b.ElementsBegin(); i != b.ElementsEnd(); ++i ){ - if( i->IsBundle() ) { - osctap::ReceivedBundle nested( *i ); +static void FullRead(const char* data, osc_bundle_element_size_t size) { + osctap::ReceivedPacket p(data, size); + if (p.IsBundle()) { + osctap::ReceivedBundle b(p); + for (auto i = b.ElementsBegin(); i != b.ElementsEnd(); ++i) { + if (i->IsBundle()) { + osctap::ReceivedBundle nested(*i); (void)nested.ElementCount(); - for( auto j = nested.ElementsBegin(); j != nested.ElementsEnd(); ++j ){ - if( !j->IsBundle() ){ osctap::ReceivedMessage m(*j); (void)m.ArgumentCount(); } + for (auto j = nested.ElementsBegin(); j != nested.ElementsEnd(); ++j) { + if (!j->IsBundle()) { + osctap::ReceivedMessage m(*j); + (void)m.ArgumentCount(); + } } - } else { - osctap::ReceivedMessage m( *i ); + } + else { + osctap::ReceivedMessage m(*i); (void)m.ArgumentCount(); } } - } else { - osctap::ReceivedMessage m( p ); + } + else { + osctap::ReceivedMessage m(p); (void)m.ArgumentCount(); } } -static bool ThrowingPathRejects( const char* data, osc_bundle_element_size_t size ) -{ - try { FullRead( data, size ); return false; } - catch( const osctap::Exception& ) { return true; } +static bool ThrowingPathRejects(const char* data, osc_bundle_element_size_t size) { + try { + FullRead(data, size); + return false; + } + catch (const osctap::Exception&) { + return true; + } } // Assert the non-throwing gate and the throwing path agree on this input. -static void Differential( const char* label, const std::vector& buf ) -{ - const osc_bundle_element_size_t n = (osc_bundle_element_size_t)buf.size(); - const bool rejects = ThrowingPathRejects( buf.data(), n ); - const bool gateRejects = ( osctap::TryValidatePacket( buf.data(), n ) != nullptr ); - if( rejects != gateRejects ) - std::printf( "FAIL [%s]: throwing-path rejects=%d but gate rejects=%d\n", - label, (int)rejects, (int)gateRejects ), ++failures; +static void Differential(const char* label, const std::vector& buf) { + const osc_bundle_element_size_t n = (osc_bundle_element_size_t)buf.size(); + const bool rejects = ThrowingPathRejects(buf.data(), n); + const bool gateRejects = (osctap::TryValidatePacket(buf.data(), n) != nullptr); + if (rejects != gateRejects) + std::printf("FAIL [%s]: throwing-path rejects=%d but gate rejects=%d\n", label, (int)rejects, (int)gateRejects), + ++failures; } -static std::vector BuildMessage() -{ - char tmp[256]; - osctap::OutboundPacketStream p( tmp, sizeof(tmp) ); - p << osctap::BeginMessage( "/x" ) << (int32_t)1 << (float)2.5f << "hi" << true - << osctap::EndMessage(); - return std::vector( p.Data(), p.Data() + p.Size() ); +static std::vector BuildMessage() { + char tmp[256]; + osctap::OutboundPacketStream p(tmp, sizeof(tmp)); + p << osctap::BeginMessage("/x") << (int32_t)1 << (float)2.5f << "hi" << true << osctap::EndMessage(); + return std::vector(p.Data(), p.Data() + p.Size()); } -static std::vector BuildBundle() -{ - char tmp[256]; - osctap::OutboundPacketStream p( tmp, sizeof(tmp) ); - p << osctap::BeginBundleImmediate() - << osctap::BeginMessage( "/a" ) << (int32_t)7 << osctap::EndMessage() - << osctap::BeginMessage( "/b" ) << "yo" << osctap::EndMessage() - << osctap::EndBundle(); - return std::vector( p.Data(), p.Data() + p.Size() ); +static std::vector BuildBundle() { + char tmp[256]; + osctap::OutboundPacketStream p(tmp, sizeof(tmp)); + p << osctap::BeginBundleImmediate() << osctap::BeginMessage("/a") << (int32_t)7 << osctap::EndMessage() + << osctap::BeginMessage("/b") << "yo" << osctap::EndMessage() << osctap::EndBundle(); + return std::vector(p.Data(), p.Data() + p.Size()); } -int main() -{ +int main() { // --- valid inputs: both paths accept --- const std::vector msg = BuildMessage(); const std::vector bun = BuildBundle(); - CHECK( osctap::TryValidatePacket( msg.data(), (osc_bundle_element_size_t)msg.size() ) == nullptr ); - CHECK( osctap::TryValidatePacket( bun.data(), (osc_bundle_element_size_t)bun.size() ) == nullptr ); - Differential( "valid message", msg ); - Differential( "valid bundle", bun ); + CHECK(osctap::TryValidatePacket(msg.data(), (osc_bundle_element_size_t)msg.size()) == nullptr); + CHECK(osctap::TryValidatePacket(bun.data(), (osc_bundle_element_size_t)bun.size()) == nullptr); + Differential("valid message", msg); + Differential("valid bundle", bun); // --- malformed inputs: both paths must reject, identically --- // truncations (every prefix that isn't the whole thing) - for( std::size_t k = 1; k < msg.size(); ++k ) - Differential( "msg prefix", std::vector( msg.begin(), msg.begin() + k ) ); - for( std::size_t k = 1; k < bun.size(); ++k ) - Differential( "bun prefix", std::vector( bun.begin(), bun.begin() + k ) ); + for (std::size_t k = 1; k < msg.size(); ++k) + Differential("msg prefix", std::vector(msg.begin(), msg.begin() + k)); + for (std::size_t k = 1; k < bun.size(); ++k) + Differential("bun prefix", std::vector(bun.begin(), bun.begin() + k)); // corrupt the type tag to an unknown tag - { auto b = msg; // find the ',' type-tag start and poke a bogus tag after it - for( std::size_t i = 0; i + 1 < b.size(); ++i ) if( b[i] == ',' ){ b[i+1] = 'Q'; break; } - Differential( "unknown type tag", b ); } + { + auto b = msg; // find the ',' type-tag start and poke a bogus tag after it + for (std::size_t i = 0; i + 1 < b.size(); ++i) + if (b[i] == ',') { + b[i + 1] = 'Q'; + break; + } + Differential("unknown type tag", b); + } // claim a huge blob/forge: flip a size-ish word in the bundle's element size - { auto b = bun; if( b.size() > 19 ){ b[16] = '\x7F'; b[17] = '\xFF'; } // element size huge - Differential( "bundle element size overflow", b ); } + { + auto b = bun; + if (b.size() > 19) { + b[16] = '\x7F'; + b[17] = '\xFF'; + } // element size huge + Differential("bundle element size overflow", b); + } // non-multiple-of-4 total size - { auto b = msg; b.push_back( 'x' ); Differential( "size not mult of 4", b ); } + { + auto b = msg; + b.push_back('x'); + Differential("size not mult of 4", b); + } // --- explicit checks: trivial bad size + the nesting bound --- - CHECK( osctap::TryValidatePacket( "\0\0\0", 3 ) != nullptr ); // size 3: rejected + CHECK(osctap::TryValidatePacket("\0\0\0", 3) != nullptr); // size 3: rejected // The top-level bundle counts as the first nesting level, so maxDepth 0 rejects // it outright; the default depth accepts the same (shallow) bundle. - CHECK( osctap::TryValidatePacket( bun.data(), (osc_bundle_element_size_t)bun.size(), 0 ) != nullptr ); - CHECK( osctap::TryValidatePacket( bun.data(), (osc_bundle_element_size_t)bun.size() ) == nullptr ); + CHECK(osctap::TryValidatePacket(bun.data(), (osc_bundle_element_size_t)bun.size(), 0) != nullptr); + CHECK(osctap::TryValidatePacket(bun.data(), (osc_bundle_element_size_t)bun.size()) == nullptr); - if( failures == 0 ) std::printf( "OscValidateTest: OK (gate agrees with throwing path)\n" ); + if (failures == 0) + std::printf("OscValidateTest: OK (gate agrees with throwing path)\n"); return failures == 0 ? 0 : 1; } diff --git a/tests/Win32SocketSmoke.cpp b/tests/Win32SocketSmoke.cpp index 764571b..df2fe32 100644 --- a/tests/Win32SocketSmoke.cpp +++ b/tests/Win32SocketSmoke.cpp @@ -14,60 +14,58 @@ Only IpEndpointName string formatting actually runs. */ -#include "ip/UdpSocket.h" -#include "ip/TcpSocket.h" +#include + #include "ip/IpEndpointName.h" #include "ip/PacketListener.h" +#include "ip/TcpSocket.h" +#include "ip/UdpSocket.h" #include "osc/OscOutboundPacketStream.h" -#include - namespace { -class NullListener : public osctap::PacketListener { -public: - void ProcessPacket( const char *, int, const osctap::IpEndpointName & ) override {} -}; + class NullListener : public osctap::PacketListener { + public: + void ProcessPacket(const char*, int, const osctap::IpEndpointName&) override {} + }; -// Defined and ODR-used (its address is taken below) but never executed on CI. -// Forces the win32 UdpSocket / SocketReceiveMultiplexer template members -- the -// ctor, Bind/SendTo/Send, Run/AsynchronousBreak, and the getaddrinfo-based -// GetHostByName -- to compile and link. -void exercise_win32_backend() -{ - osctap::IpEndpointName ep( "127.0.0.1", 9000 ); // -> GetHostByName -> getaddrinfo + // Defined and ODR-used (its address is taken below) but never executed on CI. + // Forces the win32 UdpSocket / SocketReceiveMultiplexer template members -- the + // ctor, Bind/SendTo/Send, Run/AsynchronousBreak, and the getaddrinfo-based + // GetHostByName -- to compile and link. + void exercise_win32_backend() { + osctap::IpEndpointName ep("127.0.0.1", 9000); // -> GetHostByName -> getaddrinfo - osctap::UdpTransmitSocket tx( ep ); - char buf[8] = { 0 }; - tx.Send( buf, sizeof(buf) ); + osctap::UdpTransmitSocket tx(ep); + char buf[8] = {0}; + tx.Send(buf, sizeof(buf)); - NullListener listener; - osctap::UdpListeningReceiveSocket rx( - osctap::IpEndpointName( osctap::IpEndpointName::ANY_ADDRESS, 9000 ), &listener ); - rx.AsynchronousBreak(); + NullListener listener; + osctap::UdpListeningReceiveSocket rx(osctap::IpEndpointName(osctap::IpEndpointName::ANY_ADDRESS, 9000), + &listener); + rx.AsynchronousBreak(); - // TCP backend (ip/win32/TcpSocket.h): client Send + connection-aware server. - osctap::TcpTransmitSocket tcpTx( ep ); - tcpTx.Send( buf, sizeof(buf) ); + // TCP backend (ip/win32/TcpSocket.h): client Send + connection-aware server. + osctap::TcpTransmitSocket tcpTx(ep); + tcpTx.Send(buf, sizeof(buf)); - osctap::TcpListeningReceiveSocket tcpRx( - osctap::IpEndpointName( osctap::IpEndpointName::ANY_ADDRESS, 9001 ), &listener ); - tcpRx.Run(); - tcpRx.AsynchronousBreak(); -} + osctap::TcpListeningReceiveSocket tcpRx(osctap::IpEndpointName(osctap::IpEndpointName::ANY_ADDRESS, 9001), + &listener); + tcpRx.Run(); + tcpRx.AsynchronousBreak(); + } } // namespace -int main( int argc, char ** /*argv*/ ) -{ +int main(int argc, char** /*argv*/) { // Actually runs (no network): exercise IpEndpointName formatting. - char s[ osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ]; - osctap::IpEndpointName( 127, 0, 0, 1, 9000 ).AddressAndPortAsString( s ); - std::printf( "win32 socket smoke: %s\n", s ); + char s[osctap::IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH]; + osctap::IpEndpointName(127, 0, 0, 1, 9000).AddressAndPortAsString(s); + std::printf("win32 socket smoke: %s\n", s); // ODR-use the socket exercise so it links, but never call it on CI. void (*fn)() = &exercise_win32_backend; - if( argc == 0x7fffffff ) // never true; opaque to the optimiser + if (argc == 0x7fffffff) // never true; opaque to the optimiser fn(); return 0;