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 }