Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 364 lines (305 sloc) 12.765 kb
41a2260 @trung test cases for read/write amf0
authored
1 %% @author Trung Nguyen [trung@mdkt.org]
2 %% @copyright Trung Nguyen 2009
3 %% @doc Read/write functions for AMF0 Specification
a3db284 @trung amf0 and amf3 implementation
authored
4 -module(amf0).
5 -author("trung@mdkt.org").
6
41a2260 @trung test cases for read/write amf0
authored
7 -compile(export_all).
a3db284 @trung amf0 and amf3 implementation
authored
8
5ae03f2 @trung restructure include files
authored
9 -include("../include/action_message.hrl").
180bb0d @trung refactor types to differentiate between amf data types
authored
10 -include("../include/types.hrl").
a3db284 @trung amf0 and amf3 implementation
authored
11
12 -define(number_marker, 16#00).
13 -define(boolean_marker, 16#01).
14 -define(string_marker, 16#02).
15 -define(object_marker, 16#03).
16 -define(movieclip_marker, 16#04). %% reserved, not supported
17 -define(null_marker, 16#05).
18 -define(undefined_marker, 16#06).
19 -define(reference_marker, 16#07).
20 -define(ecma_array_marker, 16#08).
21 -define(object_end_marker, 16#09).
22 -define(strict_array_marker, 16#0A).
23 -define(date_marker, 16#0B).
24 -define(long_string_marker, 16#0C).
25 -define(unsupported_marker, 16#0D).
26 -define(recordset_marker, 16#0E). %% reserved, not supported
27 -define(xml_document_marker, 16#0F).
28 -define(typed_object_marker, 16#10).
0cc401d @trung parsing nicely initial request from flex client upon very first invocati...
authored
29 -define(avm_plus_object_marker, 16#11). %% define AMF3 must be used
a3db284 @trung amf0 and amf3 implementation
authored
30
180bb0d @trung refactor types to differentiate between amf data types
authored
31 len(Str) when is_record(Str, string) -> length(Str#string.data);
32 len(Str) when is_record(Str, long_string) -> length(Str#long_string.data);
33 len(Str) -> length(Str).
34
7ef05a7 @trung Lots of parsing but no test cases and proper dir structure
authored
35 %% Clear ETS tables
36 reset() ->
0cc401d @trung parsing nicely initial request from flex client upon very first invocati...
authored
37 _ = ref_table:clear(?OBJECT_REF_TABLE_AMF0),
7ef05a7 @trung Lots of parsing but no test cases and proper dir structure
authored
38 _ = amf3:reset(),
39 {ok}.
40
41a2260 @trung test cases for read/write amf0
authored
41 %% ===========================================================
42 %% READ methods
43 %% ===========================================================
44
a3db284 @trung amf0 and amf3 implementation
authored
45 read_u8(<<Value:8, Rest/binary>>) ->
46 {ok, Value, Rest}.
47
48 read_u16(<<Value:16, Rest/binary>>) ->
49 {ok, Value, Rest}.
50
51 read_u32(<<Value:32, Rest/binary>>) ->
52 {ok, Value, Rest}.
53
41a2260 @trung test cases for read/write amf0
authored
54 %% Read object from ETS based on the key Ref
55 %% Return {ok, Obj} or {bad, Reason}
56 read_object_reference(Ref) ->
57 ref_table:read(?OBJECT_REF_TABLE_AMF0, Ref).
58
59 write_object_reference(Obj) ->
60 {ok, inserted, Ref} = ref_table:insert(?OBJECT_REF_TABLE_AMF0, Obj),
61 {ok, Ref, Obj}.
62
a3db284 @trung amf0 and amf3 implementation
authored
63 read_string(Bin) ->
64 {ok, StringLen, Rest} = read_u16(Bin),
65 {StringBin, Rest1} = split_binary(Rest, StringLen),
66 {ok, String} = utf8:from_binary(StringBin),
180bb0d @trung refactor types to differentiate between amf data types
authored
67 {ok, #string{data = String}, Rest1}.
a3db284 @trung amf0 and amf3 implementation
authored
68
69 read_number(<<Value/float, Rest/binary>>) ->
70 {ok, Value, Rest};
71 read_number(Something) ->
72 {bad, {"Can't read number", Something}}.
73
74 read_boolean(<<Value:8, Rest/binary>>) ->
75 case Value of
76 0 ->
77 {ok, false, Rest};
78 _ ->
79 {ok, true, Rest}
80 end;
81 read_boolean(Something) ->
82 {bad, {"Can't read boolean", Something}}.
83
0cc401d @trung parsing nicely initial request from flex client upon very first invocati...
authored
84 read_strict_array(Bin, Count, Total, Acc) when Count == Total ->
85 {ok, Acc, Bin};
86 read_strict_array(Bin, Count, Total, Acc) ->
87 {ok, Obj, NextBin} = read_object(Bin),
88 read_strict_array(NextBin, Count + 1, Total, Acc ++ [Obj]).
89
90 read_strict_array(Bin) ->
91 {ok, Size, BinAfterSize} = read_u32(Bin),
92 read_strict_array(BinAfterSize, 0, Size, []).
93
6ab3d15 @trung amf3 read complete
authored
94 read_date(Bin) ->
95 %% Just read, not use
96 {ok, _, BinAfterTimeZone} = read_u16(Bin),
97 {ok, TimeInMilli, NextBin} = read_number(BinAfterTimeZone),
98 %% convert to erlang date
99 Date = utils:milliseconds_to_date(TimeInMilli),
100 {ok, Date, NextBin}.
101
102 read_long_string(Bin) ->
b1da337 @trung amf0:read_long_string
authored
103 {ok, StringLen, BinAfterLen} = read_u32(Bin),
104 {StringBin, NextBin} = split_binary(BinAfterLen, StringLen),
105 {ok, String} = utf8:from_binary(StringBin),
180bb0d @trung refactor types to differentiate between amf data types
authored
106 {ok, #long_string{data = String}, NextBin}.
6ab3d15 @trung amf3 read complete
authored
107
b763f05 @trung amf0:read_xml
authored
108 %% xml doc is long string
6ab3d15 @trung amf3 read complete
authored
109 read_xml(Bin) ->
180bb0d @trung refactor types to differentiate between amf data types
authored
110 {ok, LongString, NextBin} = read_long_string(Bin),
111 {ok, #xml{data = LongString#long_string.data}, NextBin}.
6ab3d15 @trung amf3 read complete
authored
112
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
113 read_typed_object_property(Bin, Obj) ->
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
114 {ok, PropertyName, BinAfterName} = read_string(Bin),
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
115 case read_object(BinAfterName) of
116 {object_end_marker, NextBin, _} ->
117 {ok, Obj, NextBin};
118 {ok, ObjValue, NextBin} ->
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
119 case record_utils:type(Obj) of
120 asobject ->
121 {ok, NewObj, _} = record_utils:set(Obj, PropertyName, ObjValue),
122 read_typed_object_property(NextBin, NewObj);
123 _ ->
124 {ok, NewObj, _} = record_utils:set(Obj, list_to_atom(PropertyName#string.data), ObjValue),
125 read_typed_object_property(NextBin, NewObj)
126 end
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
127 end.
128
6ab3d15 @trung amf3 read complete
authored
129 read_typed_object(Bin) ->
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
130 {ok, ClassName, BinAfterClassName} = read_string(Bin),
131 case registry:fc_to_record(ClassName#string.data) of
132 {ok, undefined} ->
133 % create asobject
134 read_typed_object_property(BinAfterClassName, #asobject{});
135 {ok, NewObject} ->
136 read_typed_object_property(BinAfterClassName, NewObject)
137 end.
6ab3d15 @trung amf3 read complete
authored
138
41a2260 @trung test cases for read/write amf0
authored
139 read_null(Bin) ->
140 {ok, null, Bin}.
141
142 read_objects_until_end(Bin, Acc) ->
143 {ok, Name, BinAfterName} = read_string(Bin),
144 case read_object(BinAfterName) of
145 {object_end_marker, NextBin, _} ->
180bb0d @trung refactor types to differentiate between amf data types
authored
146 {ok, #ecma_array{data = Acc}, NextBin};
41a2260 @trung test cases for read/write amf0
authored
147 {ok, Obj, NextBin} ->
148 %% Always read value but be careful to ignore erroneous 'length' prop
149 %% that is sometimes sent by the player. (via BlazeDS)
150 if
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
151 Name#string.data == "length" ->
41a2260 @trung test cases for read/write amf0
authored
152 read_objects_until_end(NextBin, Acc);
153 true ->
154 read_objects_until_end(NextBin, Acc ++ [{Name, Obj}])
155 end;
156 Other ->
157 Other
158 end.
159
160 %% The return {ok, [{"name", ValueObject}, ...], <<...>>}
161 read_ecma_array(Bin) ->
162 {ok, _Len, BinAfterLen} = read_u32(Bin),
163 %% read until we meet ?object_end_marker
164 read_objects_until_end(BinAfterLen, []).
165
166 read_reference(Bin) ->
167 {ok, Ref, NextBin} = read_u16(Bin),
168 {ok, Obj} = read_object_reference(Ref),
169 {ok, Obj, NextBin}.
170
a3db284 @trung amf0 and amf3 implementation
authored
171 %% return {ok, value/Value, Rest} or {bad, Reason}
6ab3d15 @trung amf3 read complete
authored
172 read_object(<<?number_marker:8, Rest/binary>>) -> read_number(Rest);
173 read_object(<<?boolean_marker:8, Rest/binary>>) -> read_boolean(Rest);
174 read_object(<<?string_marker:8, Rest/binary>>) -> read_string(Rest);
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
175 read_object(<<?object_marker:8, Rest/binary>>) -> read_typed_object_property(Rest, #asobject{});
6ab3d15 @trung amf3 read complete
authored
176 read_object(<<?movieclip_marker:8, Rest/binary>>) -> {bad, {"Reserved, not supported", Rest}};
41a2260 @trung test cases for read/write amf0
authored
177 read_object(<<?null_marker:8, Rest/binary>>) -> read_null(Rest);
6ab3d15 @trung amf3 read complete
authored
178 read_object(<<?undefined_marker:8, Rest/binary>>) -> {bad, {"Undefined marker", ?MODULE, ?LINE, Rest}};
41a2260 @trung test cases for read/write amf0
authored
179 read_object(<<?reference_marker:8, Rest/binary>>) -> read_reference(Rest);
180 read_object(<<?ecma_array_marker:8, Rest/binary>>) -> read_ecma_array(Rest);
181 read_object(<<?object_end_marker:8, Rest/binary>>) -> {object_end_marker, Rest, {?MODULE, ?LINE}};
6ab3d15 @trung amf3 read complete
authored
182 read_object(<<?strict_array_marker:8, Rest/binary>>) -> read_strict_array(Rest);
183 read_object(<<?date_marker:8, Rest/binary>>) -> read_date(Rest);
184 read_object(<<?long_string_marker:8, Rest/binary>>) -> read_long_string(Rest);
41a2260 @trung test cases for read/write amf0
authored
185 read_object(<<?unsupported_marker:8, Rest/binary>>) -> {bad, {"Unsupported marker", ?MODULE, ?LINE, Rest}};
6ab3d15 @trung amf3 read complete
authored
186 read_object(<<?recordset_marker:8, Rest/binary>>) -> {bad, {"Reserved, not supported", ?MODULE, ?LINE, Rest}};
187 read_object(<<?xml_document_marker:8, Rest/binary>>) -> read_xml(Rest);
188 read_object(<<?typed_object_marker:8, Rest/binary>>) -> read_typed_object(Rest);
a3db284 @trung amf0 and amf3 implementation
authored
189 %% switch to AMF3
190 read_object(<<?avm_plus_object_marker:8, Rest/binary>>) -> amf3:read_object(Rest).
41a2260 @trung test cases for read/write amf0
authored
191
192 %% ===========================================================
193 %% WRITE methods
194 %% Use xxx(marker, Value) to build binary with marker
195 %% ===========================================================
196
180bb0d @trung refactor types to differentiate between amf data types
authored
197 write_object(null) -> write_null();
198 write_object(true) -> write_boolean(true);
199 write_object(false) -> write_boolean(false);
200 write_object(Number) when is_number(Number) -> write_number(Number);
201 write_object(String) when is_record(String, string) -> write_string(String);
202 write_object(LongString) when is_record(LongString, long_string) -> write_long_string(LongString);
203 write_object(EcmaArray) when is_record(EcmaArray, ecma_array) -> write_ecma_array(EcmaArray);
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
204 write_object({{Ye, Mo, Da}, {Ho, Mi, Se}}) -> write_date({{Ye, Mo, Da}, {Ho, Mi, Se}});
180bb0d @trung refactor types to differentiate between amf data types
authored
205 write_object(Xml) when is_record(Xml, xml) -> write_xml(Xml);
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
206 write_object(Array) when is_list(Array) -> write_strict_array(Array);
207 write_object(Obj) ->
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
208 ObjType = record_utils:type(Obj),
0b92c57 @trung flatten fields list when return
authored
209 case registry:record_to_fc(ObjType) of
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
210 {ok, undefined} ->
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
211 % write asobject
212 case ObjType of
213 asobject ->
214 PropertyArray = Obj#asobject.array,
215 {ok, ObjEnd} = write_object_end(),
216 write_object_now(?object_marker, [write_object_info(Field, Obj) || {Field, Obj} <- PropertyArray] ++ ObjEnd);
217 _ ->
218 {bad, {"Unknown object type", ObjType, ?MODULE, ?LINE}}
219 end;
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
220 {ok, ClassName} ->
9ad6cd8 @trung read/write typed object for amf0
authored
221 {ok, ClassNameBin} = string_to_binary(#string{data = ClassName}),
0b92c57 @trung flatten fields list when return
authored
222 case ObjType of
223 undefined ->
224 {bad, {"Object type not found even it was registered", ClassName, Obj}};
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
225 Type ->
226 Fields = record_utils:fields_atom(Type),
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
227 ObjBin = list_to_binary([write_object_info(X, Obj) || X <- Fields ]),
0c6e6cd @trung remote initialize values in records, write typed object
authored
228 {ok, ObjEnd} = write_object_end(),
0b92c57 @trung flatten fields list when return
authored
229 write_object_now(?typed_object_marker, [ClassNameBin, ObjBin, ObjEnd])
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
230 end
231 end.
180bb0d @trung refactor types to differentiate between amf data types
authored
232
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
233 write_object(ref, Ref) -> write_reference(Ref);
7c3aece @trung amf3 test cases
authored
234 write_object(amf3, Obj) ->
235 {ok, Amf3Bin} = amf3:write_object(Obj),
236 write_object_now(?avm_plus_object_marker, Amf3Bin).
180bb0d @trung refactor types to differentiate between amf data types
authored
237
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
238 write_object_info(Field, Obj) when is_record(Field, string) ->
239 {ok, FieldBin} = string_to_binary(Field),
240 {ok, ValueBin} = write_object(Obj),
241 [FieldBin, ValueBin];
242 write_object_info(Field, Obj) ->
9ad6cd8 @trung read/write typed object for amf0
authored
243 {ok, FieldBin} = string_to_binary(#string{data = atom_to_list(Field)}),
0c6e6cd @trung remote initialize values in records, write typed object
authored
244 case record_utils:get(Obj, Field) of
37ec5e4 @trung passed test case unregistered typed object for amf0
authored
245 {ok, undefined} ->
246 [];
247 {ok, Value} ->
248 {ok, ValueBin} = write_object(Value),
249 [FieldBin, ValueBin]
250 end.
180bb0d @trung refactor types to differentiate between amf data types
authored
251
41a2260 @trung test cases for read/write amf0
authored
252 %% return {ok, ReturnBin} or {bad, Reason}
253 write_u8(Value) -> {ok, <<Value:8>>}.
254
255 write_u16(Value) -> {ok, <<Value:16>>}.
256
257 write_u32(Value) -> {ok, <<Value:32>>}.
258
259 write_object_now(Marker, Bin) ->
260 {ok, list_to_binary([<<Marker:8>>, Bin])}.
261
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
262 write_number(Value) ->
263 {ok, Bin} = number_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
264 write_object_now(?number_marker, Bin).
265
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
266 number_to_binary(Value) ->
41a2260 @trung test cases for read/write amf0
authored
267 {ok, <<Value/float>>}.
268
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
269 write_string(Value) ->
270 {ok, Bin} = string_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
271 write_object_now(?string_marker, Bin).
272
273 %% write UTF-8-empty
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
274 string_to_binary(#string{data = Value}) when length(Value) == 0 ->
41a2260 @trung test cases for read/write amf0
authored
275 write_u16(0);
276
277 %% write utf8 string to binary
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
278 string_to_binary(#string{data = Value}) ->
41a2260 @trung test cases for read/write amf0
authored
279 Len = length(Value),
280 {ok, LenBin} = write_u16(Len),
281 {ok, StringBin} = utf8:to_binary(Value),
282 {ok, list_to_binary([LenBin, StringBin])}.
283
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
284 write_long_string(Value) ->
285 {ok, Bin} = long_string_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
286 write_object_now(?long_string_marker, Bin).
287
288 %% write utf8 long string to binary
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
289 long_string_to_binary(#long_string{data = Value}) ->
41a2260 @trung test cases for read/write amf0
authored
290 Len = length(Value),
291 {ok, LenBin} = write_u32(Len),
292 {ok, StringBin} = utf8:to_binary(Value),
293 {ok, list_to_binary([LenBin, StringBin])}.
294
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
295 write_xml(Value) ->
296 {ok, Bin} = xml_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
297 write_object_now(?xml_document_marker, Bin).
298
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
299 xml_to_binary(#xml{data = Value}) ->
300 long_string_to_binary(#long_string{data = Value}).
41a2260 @trung test cases for read/write amf0
authored
301
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
302 write_date(Value) ->
303 {ok, Bin} = date_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
304 write_object_now(?date_marker, Bin).
305
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
306 date_to_binary({Date, Time}) -> date_to_binary(utils:date_to_milliseconds({Date, Time}));
307 date_to_binary(Milliseconds) when is_number(Milliseconds) ->
41a2260 @trung test cases for read/write amf0
authored
308 {ok, TimezoneBin} = write_u16(0), %% just for reserved, not used
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
309 {ok, DateBin} = number_to_binary(Milliseconds),
41a2260 @trung test cases for read/write amf0
authored
310 {ok, list_to_binary([TimezoneBin, DateBin])}.
311
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
312 write_boolean(Value) ->
313 {ok, Bin} = boolean_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
314 write_object_now(?boolean_marker, Bin).
315
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
316 boolean_to_binary(true) -> write_u8(1);
317 boolean_to_binary(false) -> write_u8(0).
41a2260 @trung test cases for read/write amf0
authored
318
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
319 %% input Value is the list of objects
41a2260 @trung test cases for read/write amf0
authored
320 write_strict_array(Value) ->
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
321 {ok, Bin} = strict_array_to_binary(Value),
322 write_object_now(?strict_array_marker, Bin).
323
324 item_to_binary(Obj) ->
325 {ok, Bin} = write_object(Obj),
326 Bin.
41a2260 @trung test cases for read/write amf0
authored
327
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
328 strict_array_to_binary(Value) ->
329 Len = length(Value),
330 {ok, LenBin} = write_u32(Len),
331 ObjBinArray = [ item_to_binary(X) || X <- Value ],
332 {ok, list_to_binary([LenBin, ObjBinArray])}.
41a2260 @trung test cases for read/write amf0
authored
333
334 write_typed_object(Value) ->
0c6e6cd @trung remote initialize values in records, write typed object
authored
335 {bad, {"Not yet implemented", Value, ?MODULE, ?LINE}}.
41a2260 @trung test cases for read/write amf0
authored
336
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
337 write_reference(Value) ->
338 {ok, Bin} = reference_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
339 write_object_now(?reference_marker, Bin).
340
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
341 reference_to_binary(Value) ->
41a2260 @trung test cases for read/write amf0
authored
342 write_u16(Value).
343
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
344 write_ecma_array(Value) ->
345 {ok, Bin} = ecma_array_to_binary(Value),
41a2260 @trung test cases for read/write amf0
authored
346 write_object_now(?ecma_array_marker, Bin).
347
348 write_ecma_array_item({Name, Value}) ->
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
349 {ok, NameBin} = string_to_binary(Name),
350 {ok, ValueBin} = write_object(Value),
41a2260 @trung test cases for read/write amf0
authored
351 [NameBin, ValueBin].
352
0b13b0c @trung created helper to generate record_utils dynamically for records
authored
353 ecma_array_to_binary(#ecma_array{data = Value}) ->
41a2260 @trung test cases for read/write amf0
authored
354 {ok, LenBin} = write_u32(0),
355 ArrayBin = [write_ecma_array_item(X) || X <- Value],
356 {ok, ObjectEndBin} = write_object_end(),
0c6e6cd @trung remote initialize values in records, write typed object
authored
357 {ok, list_to_binary([LenBin, ArrayBin, ObjectEndBin])}.
41a2260 @trung test cases for read/write amf0
authored
358
359 write_null() ->
360 {ok, <<?null_marker:8>>}.
361
362 write_object_end() ->
363 {ok, <<0, 0, ?object_end_marker>>}.
Something went wrong with that request. Please try again.