1 module osc.packet; 2 3 import std.range; 4 import std.conv; 5 import std.algorithm; 6 import std.bitmanip; 7 import std.datetime; 8 import std.variant; 9 10 import osc.address; 11 import osc.timetag; 12 import osc.typetag; 13 import osc.oscstring; 14 import osc.message; 15 import osc.bundle; 16 17 18 19 /// An OSC packet is the base class for the OSC message and bundle. 20 /// 21 class OSCPacket 22 { 23 24 /// Parses the given binaray data and returns an OSCPacket (a message or 25 /// a bundle). 26 /// 27 static OSCPacket parse( ubyte[] data ) 28 { 29 if( data[0] == '#' ) 30 return _parseBundle( data ); 31 else 32 return _parseMessage( data ); 33 } 34 35 36 /// Converts the packet into a byte array. 37 /// 38 @property abstract ubyte[] data( ) const; 39 40 41 42 43 static protected OSCBundle _parseBundle( ubyte[] data ) 44 { 45 assert( data[0 .. 8] == cast(ubyte[])"#bundle\0" ); 46 47 size_t idx = 8; 48 OSCBundle res = new OSCBundle( _get!SysTime( data, idx ) ); 49 idx += 8; 50 51 while( idx < data.length ) 52 { 53 size_t size = _get!int( data, idx ); 54 idx += 4; 55 56 ubyte[] msgdata = data[ idx .. idx + size ]; 57 OSCMessage msg = _parseMessage( msgdata ); 58 59 res.messages ~= msg; 60 idx += size; 61 62 idx += 4 - idx % 4; 63 } 64 65 return res; 66 } 67 68 static protected OSCMessage _parseMessage( ubyte[] data ) 69 { 70 size_t idx; 71 72 OSCMessage res = new OSCMessage( _getAddress( data, 0 ) ); 73 idx += data.countUntil(','); 74 75 assert( idx % 4 == 0, "Misaligned OSC packet." ); 76 77 string types = _getTypes( data, idx ); 78 idx += 1 + types.length; 79 80 81 82 foreach( t; types ) 83 { 84 switch( t ) 85 { 86 case '\0': 87 break; 88 89 case TypeTag.Integer: 90 res.add!int( _get!int( data, idx ) ); 91 idx += 4; 92 93 break; 94 95 case TypeTag.Float: 96 res.add!float( _get!float( data, idx ) ); 97 idx += 4; 98 break; 99 100 case TypeTag.String: 101 string v = _get!string( data, idx ); 102 idx += v.length; 103 res.add!string( v.stripRight( '\0' ) ); 104 break; 105 106 case TypeTag.Blob: 107 ubyte[] blob = _get!(ubyte[])( data, idx ); 108 res.add!(ubyte[])( blob ); 109 110 idx += 4 + blob.length + (4 - blob.length % 4); 111 break; 112 113 case TypeTag.TimeTag: 114 res.add!SysTime( _get!SysTime( data, idx ) ); 115 idx += 8; 116 break; 117 118 case TypeTag.True: 119 res.add!bool( true ); 120 break; 121 122 case TypeTag.False: 123 res.add!bool( false ); 124 break; 125 126 case TypeTag.Null: 127 res.addNull; 128 break; 129 130 case TypeTag.Impulse: 131 res.addImpulse; 132 break; 133 134 default: 135 assert( 0 ); 136 } 137 138 while( idx % 4 != 0 ) 139 idx ++; 140 } 141 142 return res; 143 } 144 145 146 147 static protected Address _getAddress( ubyte[] data, size_t idx ) 148 { 149 size_t i = idx; 150 151 for( ; i < data.length; i += 4 ) 152 { 153 if( data[i] == ',' ) 154 { 155 if( i == 0 ) 156 return Address( "" ); 157 158 return Address( data[ idx .. i ].dup 159 .stripRight( 0 ) 160 .map!( c => cast(char) c ) 161 .array ); 162 } 163 } 164 165 throw new Exception( "Invalid packet : no comma found." ); 166 } 167 168 static protected TypeTagString _getTypes( ubyte[] data, size_t idx ) 169 { 170 size_t i = idx + 4; 171 172 for( ; i < data.length; i += 4 ) 173 { 174 if( data[ i - 1 ] == 0 ) 175 { 176 return cast(TypeTagString) data[ idx+1 .. i ].dup; 177 } 178 } 179 180 throw new Exception( "Invalid packet : type tag string not terminated." ); 181 } 182 183 static protected T _get( T )( ubyte[] data, size_t idx ) 184 { 185 return data.peek!T( idx ); 186 } 187 188 static protected T _get( T:string )( ubyte[] data, size_t idx ) 189 { 190 size_t i = idx + 4; 191 192 for( ; i - 1 < data.length; i += 4 ) 193 { 194 if( data[ i - 1 ] == 0 ) 195 { 196 return cast(string) data[ idx .. i ].dup; 197 } 198 } 199 200 throw new Exception( "Invalid packet : string not terminated." ); 201 } 202 203 static protected T _get( T:ubyte[] )( ubyte[] data, size_t idx ) 204 { 205 size_t size = data.peek!int( idx ); 206 return data[ idx + 4 .. idx + size + 4 ].dup; 207 } 208 209 static protected T _get( T:SysTime )( ubyte[] data, size_t idx ) 210 { 211 return data[ idx .. idx + 8 ].toSysTime; 212 } 213 214 215 static protected ubyte[] _set( T )( const(T) v ) 216 { 217 ubyte[] buf = [0, 0, 0, 0]; 218 buf.write!T( v, 0 ); 219 return buf; 220 } 221 222 static protected ubyte[] _set( T:string )( const(T) v ) 223 { 224 return v.toOSC; 225 } 226 227 static protected ubyte[] _set( T:ubyte[] )( const(T) v ) 228 { 229 ubyte[] buf = _set!int( cast(int) v.length ); 230 buf ~= v; 231 232 return buf ~ (0 233 .repeat( 4 - buf.length % 4 ) 234 .map!( x => x.to!ubyte ) 235 .array); 236 } 237 238 static protected ubyte[] _set( T:SysTime )( const(T) v ) 239 { 240 return v.toOSC; 241 } 242 243 } 244 245 unittest 246 { 247 ubyte[] raw = [ 248 0x2f, 0x6f, 0x73, 0x63, 249 0x69, 0x6c, 0x6c, 0x61, 250 0x74, 0x6f, 0x72, 0x2f, 251 0x34, 0x2f, 0x66, 0x72, 252 0x65, 0x71, 0x75, 0x65, 253 0x6e, 0x63, 0x79, 0x00, 254 0x2c, 0x66, 0x00, 0x00, 255 0x43, 0xdc, 0x00, 0x00 256 ]; 257 258 OSCMessage msg = cast(OSCMessage) OSCPacket.parse( raw ); 259 260 assert( msg.address.toString == "/oscillator/4/frequency" ); 261 assert( msg.length == 1 ); 262 assert( msg.isFloat( 0 ) ); 263 assert( msg.at!float( 0 ) == 440 ); 264 } 265 266 unittest 267 { 268 ubyte[] raw = [ 269 0x23, 0x62, 0x75, 0x6e, 270 0x64, 0x6c, 0x65, 0x00, 271 0x00, 0x00, 0x00, 0x00, 272 0x00, 0x00, 0x00, 0x01, 273 0x00, 0x00, 0x00, 0x20, 274 0x2f, 0x6f, 0x73, 0x63, 275 0x69, 0x6c, 0x6c, 0x61, 276 0x74, 0x6f, 0x72, 0x2f, 277 0x34, 0x2f, 0x66, 0x72, 278 0x65, 0x71, 0x75, 0x65, 279 0x6e, 0x63, 0x79, 0x00, 280 0x2c, 0x66, 0x00, 0x00, 281 0x43, 0xdc, 0x00, 0x00 282 ]; 283 284 OSCBundle bdl = cast(OSCBundle) OSCPacket.parse( raw ); 285 286 assert( bdl.timetag == immediateSysTime ); 287 assert( bdl.messages.length == 1 ); 288 289 OSCMessage msg = bdl.messages[ 0 ]; 290 291 assert( msg.address.toString == "/oscillator/4/frequency" ); 292 assert( msg.types == "f" ); 293 assert( msg.at!float( 0 ) == 440 ); 294 }