4
%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
20
%% Test suite for erlang:decode_packet/3
22
-module(decode_packet_SUITE).
24
-include("test_server.hrl").
26
-export([all/1,init_per_testcase/2,fin_per_testcase/2,
27
basic/1, packet_size/1, neg/1, http/1, line/1, ssl/1]).
30
[basic, packet_size, neg, http, line, ssl].
32
init_per_testcase(Func, Config) when is_atom(Func), is_list(Config) ->
33
Seed = {S1,S2,S3} = now(),
34
random:seed(S1,S2,S3),
35
io:format("*** SEED: ~p ***\n", [Seed]),
36
Dog=?t:timetrap(?t:minutes(1)),
37
[{watchdog, Dog}|Config].
39
fin_per_testcase(_Func, Config) ->
40
Dog=?config(watchdog, Config),
41
?t:timetrap_cancel(Dog).
45
basic(Config) when is_list(Config) ->
46
?line Packet = <<101,22,203,54,175>>,
47
?line Rest = <<123,34,0,250>>,
48
?line Bin = <<Packet/binary,Rest/binary>>,
49
?line {ok, Bin, <<>>} = decode_pkt(raw,Bin),
51
?line {more, 5+1} = decode_pkt(1,<<5,1,2,3,4>>),
52
?line {more, 5+2} = decode_pkt(2,<<0,5,1,2,3,4>>),
53
?line {more, 5+4} = decode_pkt(4,<<0,0,0,5,1,2,3,4>>),
55
?line {more, undefined} = decode_pkt(1,<<>>),
56
?line {more, undefined} = decode_pkt(2,<<0>>),
57
?line {more, undefined} = decode_pkt(4,<<0,0,0>>),
59
Types = [1,2,4,asn1,sunrm,cdr,fcgi,tpkt,ssl_tls],
61
%% Run tests for different header types and bit offsets.
63
lists:foreach(fun({Type,Bits})->basic_pack(Type,Packet,Rest,Bits),
64
more_length(Type,Packet,Bits) end,
65
[{T,B} || T<-Types, B<-lists:seq(0,32)]),
68
basic_pack(Type,Body,Rest,BitOffs) ->
69
?line {Bin,Unpacked,_} = pack(Type,Body,Rest,BitOffs),
70
?line {ok, Unpacked, Rest} = decode_pkt(Type,Bin),
74
?line <<_:1,NRest/bits>> = Rest,
75
basic_pack(Type,Body,NRest,BitOffs)
78
more_length(Type,Body,BitOffs) ->
79
?line {Bin,_,_} = pack(Type,Body,<<>>,BitOffs),
80
HdrSize = byte_size(Bin) - byte_size(Body),
81
more_length_do(Type,HdrSize,Bin,byte_size(Bin)).
83
more_length_do(_,_,_,0) ->
85
more_length_do(Type,HdrSize,Bin,Size) ->
86
TrySize = (Size*3) div 4,
87
NSize = if TrySize < HdrSize -> Size - 1;
90
{B1,_} = split_binary(Bin,NSize),
91
?line {more, Length} = decode_pkt(Type,B1),
93
L when L=:=byte_size(Bin) -> ok;
94
undefined when NSize<HdrSize -> ok
96
more_length_do(Type,HdrSize,Bin,NSize).
100
pack(Type,Packet,Rest) ->
101
{Bin,Unpacked} = pack(Type,Packet),
102
{<<Bin/binary,Rest/bits>>,Unpacked}.
105
% pack(raw,B,R,Bits);
106
%pack(raw,Body,Rest,BitOffs) ->
107
% Orig = <<0:BitOffs,Body/binary,Rest/bits>>,
108
% <<_:BitOffs,Bin/bits>> = Orig,
109
% {Bin,<<Bin/binary,Rest/bits>>,Orig};
110
pack(Type,Body,Rest,BitOffs) ->
111
{Packet,Unpacked} = pack(Type,Body),
113
%% Make Bin a sub-bin with an arbitrary bitoffset within Orig
114
Prefix = random:uniform(1 bsl BitOffs) - 1,
115
Orig = <<Prefix:BitOffs,Packet/binary,Rest/bits>>,
116
<<_:BitOffs,Bin/bits>> = Orig,
120
Psz = byte_size(Bin),
121
{<<Psz:8,Bin/binary>>, Bin};
123
Psz = byte_size(Bin),
124
{<<Psz:16,Bin/binary>>, Bin};
126
Psz = byte_size(Bin),
127
{<<Psz:32,Bin/binary>>, Bin};
129
Ident = case random:uniform(3) of
131
2 -> <<16#1f,16#81,17>>;
132
3 -> <<16#1f,16#81,16#80,16#80,17>>
134
Psz = byte_size(Bin),
135
Length = case random:uniform(4) of
138
R when R=<2 andalso Psz < 16#10000 ->
140
R when R=<3 andalso Psz < 16#1000000 ->
142
_ when Psz < 16#100000000 ->
145
Res = <<Ident/binary,Length/binary,Bin/binary>>,
148
Psz = byte_size(Bin),
149
Res = if Psz < 16#80000000 ->
150
<<Psz:32,Bin/binary>>
155
Major = random:uniform(256) - 1,
156
Minor = random:uniform(256) - 1,
157
MType = random:uniform(256) - 1,
158
Psz = byte_size(Bin),
159
Res = case random:uniform(2) of
160
1 -> <<GIOP/binary,Major:8,Minor:8,0:8,MType:8,Psz:32/big,Bin/binary>>;
161
2 -> <<GIOP/binary,Major:8,Minor:8,1:8,MType:8,Psz:32/little,Bin/binary>>
166
Type = random:uniform(256) - 1,
167
Id = random:uniform(65536) - 1,
168
PaddSz = random:uniform(16) - 1,
169
Psz = byte_size(Bin),
170
Reserv = random:uniform(256) - 1,
171
Padd = case PaddSz of
173
_ -> list_to_binary([random:uniform(256)-1
174
|| _<- lists:seq(1,PaddSz)])
176
Res = <<Ver:8,Type:8,Id:16,Psz:16/big,PaddSz:8,Reserv:8,Bin/binary>>,
177
{<<Res/binary,Padd/binary>>, Res};
180
Reserv = random:uniform(256) - 1,
181
Size = byte_size(Bin) + 4,
182
Res = <<Ver:8,Reserv:8,Size:16,Bin/binary>>,
185
Content = case (random:uniform(256) - 1) of
189
Major = random:uniform(256) - 1,
190
Minor = random:uniform(256) - 1,
191
pack_ssl(Content,Major,Minor,Bin).
193
pack_ssl(Content, Major, Minor, Body) ->
196
Size = byte_size(Body),
197
Res = <<1:1,(Size+3):15, 1:8, Major:8, Minor:8, Body/binary>>,
199
Data = <<1:8, (Size+2):24, Major:8, Minor:8, Body/binary>>;
200
C when is_integer(C) ->
201
Size = byte_size(Body),
202
Res = <<Content:8, Major:8, Minor:8, Size:16, Body/binary>>,
205
{Res, {ssl_tls,[],C,{Major,Minor}, Data}}.
208
packet_size(doc) -> [];
209
packet_size(suite) -> [];
210
packet_size(Config) when is_list(Config) ->
211
?line Packet = <<101,22,203,54,175>>,
212
?line Rest = <<123,34,0,250>>,
214
F = fun({Type,Max})->
215
?line {Bin,Unpacked} = pack(Type,Packet,Rest),
216
?line case decode_pkt(Type,Bin,[{packet_size,Max}]) of
217
{ok,Unpacked,Rest} when Max=:=0; Max>=byte_size(Packet) ->
219
{error,_} when Max<byte_size(Packet), Max=/=0 ->
221
{error,_} when Type=:=fcgi, Max=/=0 ->
222
%% packet includes random amount of padding
226
?line lists:foreach(F, [{T,D} || T<-[1,2,4,asn1,sunrm,cdr,fcgi,tpkt,ssl_tls],
227
D<-lists:seq(0, byte_size(Packet)*2)]),
229
%% Test OTP-8102, "negative" 4-byte sizes.
230
lists:foreach(fun(Size) ->
231
?line {error,_} = decode_pkt(4,<<Size:32,Packet/binary>>)
239
neg(Config) when is_list(Config) ->
240
?line Bin = <<"dummy">>,
241
Fun = fun()->dummy end,
243
BadargF = fun(T,B,Opts)-> {'EXIT',{badarg,_}} = (catch decode_pkt(T,B,Opts)) end,
246
lists:foreach(fun(T)-> BadargF(T,Bin,[]) end,
247
[3,-1,5,2.0,{2},unknown,[],"line",Bin,Fun,self()]),
250
lists:foreach(fun(B)-> BadargF(0,B,[]) end,
251
[3,2.0,unknown,[],"Bin",[Bin],{Bin},Fun,self()]),
254
InvOpts = [2,false,self(),Bin,"Options",Fun,
255
packet_size,{packet_size},{packet_size,0,false},
256
{packet_size,-1},{packet_size,100.0},{packet_size,false},
257
{line_length,-1},{line_length,100.0},{line_length,false}],
259
lists:foreach(fun(Opt)-> BadargF(0,Bin,Opt),
260
BadargF(0,Bin,[Opt]),
261
BadargF(0,Bin,[Opt,{packet_size,1000}]),
262
BadargF(0,Bin,[{packet_size,1000},Opt]) end,
269
http(Config) when is_list(Config) ->
270
?line <<"foo">> = http_do(http_request("foo")),
271
?line <<" bar">> = http_do(http_request(" bar")),
272
?line <<"Hello!">> = http_do(http_response("Hello!")),
274
%% Test all known header atoms
276
ValB = list_to_binary(Val),
279
?line StrA = list_to_atom(Str),
280
?line StrB = list_to_binary(Str),
281
?line Bin = <<StrB/binary,": ",ValB/binary,"\r\n",Rest/binary>>,
282
?line {ok, {http_header,N,StrA,undefined,Val}, Rest} = decode_pkt(httph,Bin),
283
?line {ok, {http_header,N,StrA,undefined,ValB}, Rest} = decode_pkt(httph_bin,Bin),
286
?line lists:foldl(HdrF, 1, http_hdr_strings()),
288
%% Test all known method atoms
290
?line MethA = list_to_atom(Meth),
291
?line MethB = list_to_binary(Meth),
292
?line Bin = <<MethB/binary," /invalid/url HTTP/1.0\r\n",Rest/binary>>,
293
?line {ok, {http_request,MethA,{abs_path,"/invalid/url"},{1,0}},
294
Rest} = decode_pkt(http,Bin),
295
?line {ok, {http_request,MethA,{abs_path,<<"/invalid/url">>},{1,0}},
296
Rest} = decode_pkt(http_bin,Bin)
298
?line lists:foreach(MethF, http_meth_strings()),
300
%% Test all uri variants
301
UriF = fun({Str,ResL,ResB}) ->
302
Bin = <<"GET ",(list_to_binary(Str))/binary," HTTP/1.1\r\n",Rest/binary>>,
303
{ok, {http_request, 'GET', ResL, {1,1}}, Rest} = decode_pkt(http,Bin),
304
{ok, {http_request, 'GET', ResB, {1,1}}, Rest} = decode_pkt(http_bin,Bin)
306
lists:foreach(UriF, http_uri_variants()),
309
http_with_bin(http) ->
311
http_with_bin(httph) ->
316
http_do({Bin, []}, _) ->
318
http_do({Bin,[{_Line,PL,PB}|Tail]}, Type) ->
319
?line {ok, PL, Rest} = decode_pkt(Type,Bin),
320
?line {ok, PB, Rest} = decode_pkt(http_with_bin(Type),Bin),
322
%% Same tests again but as SubBin
323
PreLen = random:uniform(64),
324
Prefix = random:uniform(1 bsl PreLen) - 1,
325
SufLen = random:uniform(64),
326
Suffix = random:uniform(1 bsl SufLen) - 1,
327
Orig = <<Prefix:PreLen, Bin/bits, Suffix:SufLen>>,
328
BinLen = bit_size(Bin),
329
<<_:PreLen, SubBin:BinLen/bits, _/bits>> = Orig, % Make SubBin
330
?line SubBin = Bin, % just to make sure
332
?line {ok, PL, Rest} = decode_pkt(Type,SubBin),
333
?line {ok, PB, Rest} = decode_pkt(http_with_bin(Type),SubBin),
334
http_do({Rest, Tail}, httph).
337
QnA = [{"POST /invalid/url HTTP/1.1\r\n",
338
{http_request, 'POST', {abs_path, "/invalid/url" }, {1,1}},
339
{http_request, 'POST', {abs_path,<<"/invalid/url">>}, {1,1}}},
340
{"Connection: close\r\n",
341
{http_header,2,'Connection',undefined, "close"},
342
{http_header,2,'Connection',undefined,<<"close">>}},
343
{"Host\t : localhost:8000\r\n", % white space before :
344
{http_header,14,'Host',undefined, "localhost:8000"},
345
{http_header,14,'Host',undefined,<<"localhost:8000">>}},
346
{"User-Agent: perl post\r\n",
347
{http_header,24,'User-Agent',undefined, "perl post"},
348
{http_header,24,'User-Agent',undefined,<<"perl post">>}},
349
{"Content-Length: 4\r\n",
350
{http_header,38,'Content-Length',undefined, "4"},
351
{http_header,38,'Content-Length',undefined,<<"4">>}},
352
{"Content-Type: text/xml; charset=utf-8\r\n",
353
{http_header,42,'Content-Type',undefined, "text/xml; charset=utf-8"},
354
{http_header,42,'Content-Type',undefined,<<"text/xml; charset=utf-8">>}},
355
{"Other-Field: with some text\r\n",
356
{http_header,0, "Other-Field" ,undefined, "with some text"},
357
{http_header,0,<<"Other-Field">>,undefined,<<"with some text">>}},
358
{"Multi-Line: Once upon a time in a land far far away,\r\n"
359
" there lived a princess imprisoned in the highest tower\r\n"
360
" of the most haunted castle.\r\n",
361
{http_header,0, "Multi-Line" ,undefined, "Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle."},
362
{http_header,0,<<"Multi-Line">>,undefined,<<"Once upon a time in a land far far away,\r\n there lived a princess imprisoned in the highest tower\r\n of the most haunted castle.">>}},
366
Bin = lists:foldl(fun({Line,_,_},Acc) -> LineBin = list_to_binary(Line),
367
<<Acc/binary,LineBin/binary>> end,
369
MsgBin = list_to_binary(Msg),
370
{<<Bin/binary,MsgBin/binary>>, QnA}.
373
http_response(Msg) ->
374
QnA = [{"HTTP/1.0 404 Object Not Found\r\n",
375
{http_response, {1,0}, 404, "Object Not Found"},
376
{http_response, {1,0}, 404, <<"Object Not Found">>}},
377
{"Server: inets/4.7.16\r\n",
378
{http_header, 30, 'Server', undefined, "inets/4.7.16"},
379
{http_header, 30, 'Server', undefined, <<"inets/4.7.16">>}},
380
{"Date: Fri, 04 Jul 2008 17:16:22 GMT\r\n",
381
{http_header, 3, 'Date', undefined, "Fri, 04 Jul 2008 17:16:22 GMT"},
382
{http_header, 3, 'Date', undefined, <<"Fri, 04 Jul 2008 17:16:22 GMT">>}},
383
{"Content-Type: text/html\r\n",
384
{http_header, 42, 'Content-Type', undefined, "text/html"},
385
{http_header, 42, 'Content-Type', undefined, <<"text/html">>}},
386
{"Content-Length: 207\r\n",
387
{http_header, 38, 'Content-Length', undefined, "207"},
388
{http_header, 38, 'Content-Length', undefined, <<"207">>}},
395
Bin = lists:foldl(fun({Line,_,_},Acc) -> LineBin = list_to_binary(Line),
396
<<Acc/binary,LineBin/binary>> end,
398
MsgBin = list_to_binary(Msg),
399
{<<Bin/binary,MsgBin/binary>>, QnA}.
401
http_hdr_strings() ->
402
%% Must be correct order
403
["Cache-Control","Connection","Date","Pragma","Transfer-Encoding",
404
"Upgrade","Via","Accept", "Accept-Charset", "Accept-Encoding",
405
"Accept-Language", "Authorization","From","Host","If-Modified-Since",
406
"If-Match","If-None-Match","If-Range","If-Unmodified-Since","Max-Forwards",
407
"Proxy-Authorization","Range","Referer","User-Agent","Age","Location",
408
"Proxy-Authenticate","Public","Retry-After","Server","Vary","Warning",
409
"Www-Authenticate","Allow","Content-Base","Content-Encoding",
410
"Content-Language","Content-Length","Content-Location","Content-Md5",
411
"Content-Range","Content-Type","Etag","Expires","Last-Modified",
412
"Accept-Ranges","Set-Cookie","Set-Cookie2","X-Forwarded-For","Cookie",
413
"Keep-Alive","Proxy-Connection"].
415
http_meth_strings() ->
416
["OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE"].
418
http_uri_variants() ->
420
{"http://tools.ietf.org/html/rfc3986",
421
{absoluteURI,http, "tools.ietf.org", undefined, "/html/rfc3986"},
422
{absoluteURI,http,<<"tools.ietf.org">>,undefined,<<"/html/rfc3986">>}},
423
{"http://otp.ericsson.se:8000/product/internal/",
424
{absoluteURI,http, "otp.ericsson.se" ,8000, "/product/internal/"},
425
{absoluteURI,http,<<"otp.ericsson.se">>,8000,<<"/product/internal/">>}},
426
{"https://example.com:8042/over/there?name=ferret#nose",
427
{absoluteURI,https, "example.com", 8042, "/over/there?name=ferret#nose"},
428
{absoluteURI,https,<<"example.com">>,8042,<<"/over/there?name=ferret#nose">>}},
429
{"ftp://cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm",
430
{scheme, "ftp", "//cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm"},
431
{scheme,<<"ftp">>,<<"//cnn.example.com&story=breaking_news@10.0.0.1/top_story.htm">>}},
432
{"/some/absolute/path",
433
{abs_path, "/some/absolute/path"},
434
{abs_path,<<"/some/absolute/path">>}},
435
{"something_else", "something_else", <<"something_else">>}].
440
line(Config) when is_list(Config) ->
441
Text = <<"POST /invalid/url HTTP/1.1\r\n"
442
"Connection: close\r\n"
443
"Host\t : localhost:8000\r\n"
444
"User-Agent: perl post\r\n"
445
"Content-Length: 4\r\n"
446
"Content-Type: text/xml; charset=utf-8\r\n"
447
"Other-Field: with some text\r\n"
448
"Multi-Line: Once upon a time in a land far far away,\r\n"
449
" there lived a princess imprisoned in the highest tower\r\n"
450
" of the most haunted castle.\r\n"
453
lists:foreach(fun(MaxLen) -> line_do(Text,MaxLen) end,
457
line_do(Bin,MaxLen) ->
458
Res = decode_pkt(line,Bin,[{line_length,MaxLen}]),
459
MyRes = decode_line(Bin,MaxLen),
463
line_do(Rest,MaxLen);
468
% Emulates decode_packet(line,Bin,[{line_length,MaxLen}])
469
decode_line(Bin,MaxLen) ->
470
?line case find_in_binary($\n,Bin) of
471
notfound when MaxLen>0 andalso byte_size(Bin) >= MaxLen ->
472
{LineB,Rest} = split_binary(Bin,MaxLen),
476
Pos when MaxLen>0 andalso Pos > MaxLen ->
477
{LineB,Rest} = split_binary(Bin,MaxLen),
480
{LineB,Rest} = split_binary(Bin,Pos),
484
find_in_binary(Byte, Bin) ->
485
case string:chr(binary_to_list(Bin),Byte) of
492
ssl(Config) when is_list(Config) ->
495
Body = <<234,189,73,199,1,32,4,0,254>>,
496
Rest = <<23,123,203,12,234>>,
499
{Packet,Unpacked} = pack_ssl(Content, Major, Minor, Body),
500
Bin = <<Packet/binary,Rest/binary>>,
501
?line {ok, Unpacked, Rest} = decode_pkt(ssl_tls, Bin)
507
decode_pkt(Type,Bin) ->
508
decode_pkt(Type,Bin,[]).
509
decode_pkt(Type,Bin,Opts) ->
510
%%io:format("decode_packet(~p,~p,~p)\n",[Type,Bin,Opts]),
511
Res = erlang:decode_packet(Type,Bin,Opts),
512
%%io:format(" -> ~p\n",[Res]),