diff --git a/src/wsecli_framing.erl b/src/wsecli_framing.erl deleted file mode 100644 index 3039b94..0000000 --- a/src/wsecli_framing.erl +++ /dev/null @@ -1,241 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - -%% @hidden - --module(wsecli_framing). --include("wsecli.hrl"). - --export([to_binary/1, from_binary/1, frame/1, frame/2]). - --define(OP_CODE_CONT, 0). --define(OP_CODE_TEXT, 1). --define(OP_CODE_BIN, 2). --define(OP_CODE_CLOSE, 8). --define(OP_CODE_PING, 9). --define(OP_CODE_PONG, 10). - --spec to_binary(Frame::#frame{}) -> binary(). -to_binary(Frame) -> - Bin1 = << - (Frame#frame.fin):1, - (Frame#frame.rsv1):1, (Frame#frame.rsv2):1, (Frame#frame.rsv3):1, - (Frame#frame.opcode):4, - (Frame#frame.mask):1, - (Frame#frame.payload_len):7, - (Frame#frame.extended_payload_len):(extended_payload_len_bit_width(Frame#frame.extended_payload_len, 16)), - (Frame#frame.extended_payload_len_cont):(extended_payload_len_bit_width(Frame#frame.extended_payload_len_cont, 64)) - >>, - - Bin2 = case Frame#frame.masking_key of - undefined -> Bin1; - Key -> - <> - end, - - case Frame#frame.payload of - undefined -> Bin2; - Payload -> - <> - end. - - %(Frame#frame.masking_key):32, - %(Frame#frame.payload)/binary - --spec from_binary(Data::binary()) -> list(#frame{}). -from_binary(Data) -> - lists:reverse(from_binary(Data, [])). - -from_binary(<>, Acc)-> - from_binary(Rest, [decode_frame(<>) | Acc]); - -from_binary(<>, Acc)-> - from_binary(Rest, [decode_frame(<>) | Acc]); - -from_binary(<>, Acc) -> - from_binary(Rest, [decode_frame(<>) | Acc]); - -from_binary(<<>>, Acc) -> - Acc. - -decode_frame(Data = <> ) -> - % TODO: ensure that Mask is not set - - Frame = #frame{ - fin = Fin, - rsv1 = Rsv1, rsv2 = Rsv2, rsv3 = Rsv3, - opcode = Opcode, - mask = Mask - }, - - Frame2 = binary_payload_length(Data, Frame), - binary_payload(Data, Frame2). - --spec binary_payload_length(Data::binary(), Frame::#frame{}) -> #frame{}. -binary_payload_length(Data, Frame) -> - <<_:9, PayloadLen:7, _/binary>> = Data, - case PayloadLen of - 126 -> - <<_:16, ExtendedPayloadLen:16, _/binary>> = Data, - Frame#frame{payload_len = PayloadLen, extended_payload_len = ExtendedPayloadLen}; - 127 -> - <<_:16, ExtendedPayloadLenCont:64, _/binary>> = Data, - Frame#frame{payload_len = PayloadLen, extended_payload_len_cont = ExtendedPayloadLenCont}; - _ -> - Frame#frame{payload_len = PayloadLen} - end. - --spec binary_payload(Data::binary(), Frame::#frame{}) -> #frame{}. -binary_payload(Data, Frame) -> - case Frame#frame.payload_len of - 126 -> - <<_:32, Payload/binary>> = Data; - 127 -> - <<_:80, Payload/binary>> = Data; - _ -> - <<_:16, Payload/binary>> = Data - end, - - case Frame#frame.opcode of - _ -> - Frame#frame{ payload = Payload } - end. - - -extended_payload_len_bit_width(PayloadLen, Max) -> - case PayloadLen of - 0 -> 0; - _ -> Max - end. - --spec frame(Data::binary() | string()) -> #frame{}. -frame(Data) when is_binary(Data) -> - frame(Data, [{opcode, binary}]); - -frame(Data) when is_list(Data)-> - frame(list_to_binary(Data), [{opcode, text}]). - --spec frame(Data::string() | binary(), Options::list()) -> #frame{}. -frame(Data, Options) when is_list(Data) -> - frame(list_to_binary(Data), Options); - -%don't like having this function clause just for close frames -frame({CloseCode, Reason}, Options) -> - BinReason = list_to_binary(Reason), - Data = <>, - frame(Data, Options); - -frame(Data, Options) -> - Frame = #frame{}, - Frame2 = length(Frame, Data), - Frame3 = mask(Frame2, Data), - apply_options(Frame3, Options). - --spec apply_options(Frame::#frame{}, Options::list()) -> #frame{}. -apply_options(Frame, [fin | Tail]) -> - T = Frame#frame{fin = 1}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, continuation} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_CONT}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, text} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_TEXT}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, binary} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_BIN}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, close} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_CLOSE}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, ping} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_PING}, - apply_options(T, Tail); - -apply_options(Frame, [{opcode, pong} | Tail]) -> - T = Frame#frame{opcode = ?OP_CODE_PONG}, - apply_options(T, Tail); - -apply_options(Frame, []) -> - Frame. - --spec length(Frame::#frame{}, Data :: binary()) -> #frame{}. -length(Frame, Data) -> - %Len = string:len(Data), - Len = byte_size(Data), - if - Len =< 125 -> - Frame#frame{ - payload_len = Len, - extended_payload_len = 0, - extended_payload_len_cont = 0 - }; - (Len > 125) and (Len =< 65536) -> - Frame#frame{ - payload_len = 126, - extended_payload_len = Len, - extended_payload_len_cont = 0 - }; - Len > 65536 -> - Frame#frame{ - payload_len = 127, - extended_payload_len = 0, - extended_payload_len_cont = Len - } - end. - --spec mask(Frame::#frame{}, Data::binary()) -> #frame{}. -mask(Frame, <<>>) -> - Frame#frame{mask = 0}; - -mask(Frame, Data) -> - <> = crypto:rand_bytes(4), - %BinData = list_to_binary(Data), - - Frame#frame{ - mask = 1, - masking_key = MaskKey, - payload = mask(Data, MaskKey, <<>>) - }. - - -% -% Masking code got at Cowboy source code -% --spec mask(Data::binary(), MaskKey::integer(), Acc::binary()) -> binary(). -mask(<>, MaskKey, Acc) -> - T = Data bxor MaskKey, - mask(Rest, MaskKey, <>); - -mask(<>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<<>>, _, Acc) -> - Acc. diff --git a/src/wsecli_handshake.erl b/src/wsecli_handshake.erl deleted file mode 100644 index e80651a..0000000 --- a/src/wsecli_handshake.erl +++ /dev/null @@ -1,74 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - -%% @hidden - --module(wsecli_handshake). - --include("wsecli.hrl"). --export([build/3, validate/2]). - --define(VERSION, 13). --define(GUID, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"). - --spec build(Resource ::string(), Host ::string(), Port::integer()) -> #handshake{}. -build(Resource, Host, Port) -> - RequestLine = [ - {method, "GET"}, - {version, "1.1"}, - {resource, Resource} - ], - - Headers =[ - {"Host", Host ++ ":" ++ integer_to_list(Port)}, - {"Upgrade", "websocket"}, - {"Connection", "upgrade"}, - {"Sec-Websocket-Key", wsecli_key:generate()}, - {"Sec-Websocket-Version", integer_to_list(?VERSION)} - ], - - Message = wsecli_http:build(request, RequestLine, Headers), - #handshake{ version = ?VERSION, message = Message}. - --spec validate(Response::#http_message{}, Handshake::#handshake{}) -> boolean(). -validate(Response, Handshake) -> - validate_http_status(Response) - and - validate_upgrade_header(Response) - and - validate_connection_header(Response) - and - validate_sec_websocket_accept_header(Response, Handshake). - - --spec validate_http_status(Response::#http_message{}) -> boolean(). -validate_http_status(Response) -> - "101" == wsecli_http:get_start_line_value(status, Response). - --spec validate_upgrade_header(Response ::#http_message{}) -> boolean(). -validate_upgrade_header(Response) -> - "websocket" == string:to_lower(wsecli_http:get_header_value("upgrade", Response)). - --spec validate_connection_header(Response ::#http_message{}) -> boolean(). -validate_connection_header(Response) -> - "upgrade" == string:to_lower(wsecli_http:get_header_value("connection", Response)). - --spec validate_sec_websocket_accept_header(Response::#http_message{}, Handshake::#handshake{}) -> boolean(). -validate_sec_websocket_accept_header(Response, Handshake) -> - ClientKey = wsecli_http:get_header_value("sec-websocket-key", Handshake#handshake.message), - BinaryClientKey = list_to_binary(ClientKey), - ExpectedHashedKey = base64:encode_to_string(crypto:sha(<>)), - HashedKey = wsecli_http:get_header_value("sec-websocket-accept", Response), - - ExpectedHashedKey == HashedKey. diff --git a/src/wsecli_http.erl b/src/wsecli_http.erl deleted file mode 100644 index 8bf29ac..0000000 --- a/src/wsecli_http.erl +++ /dev/null @@ -1,84 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - -%% @hidden - --module(wsecli_http). --include("wsecli.hrl"). - --export([build/3, to_request/1, from_response/1, get_start_line_value/2, get_header_value/2]). - --define(CTRL, "\r\n"). - --spec build(Type::atom(), StartLine::list({atom(), string()}), Headers::list({string(), string()})) -> list(string()). -build(Type, StartLine, Headers) -> - #http_message{type = Type, start_line = StartLine, headers = Headers}. - --spec to_request(Message::#http_message{}) -> list(string()). -to_request(Message) -> - build_request_line( - Message#http_message.start_line, - build_headers(Message#http_message.headers, ["\r\n"]) - ). - --spec build_headers(list({HeaderName::string(), HeaderValue::string()}), list(string())) -> list(string()). -build_headers(Headers, Acc) -> - lists:foldr(fun({Key, Value}, AccIn) -> - [ Key ++ ": " ++ Value ++ "\r\n" | AccIn] - end, Acc, Headers). - --spec build_request_line(list({Name::atom(), Value::string()}), list(string())) -> list(string()). -build_request_line(RequestLine, Acc) -> - Method = proplists:get_value(method, RequestLine), - Version = proplists:get_value(version, RequestLine), - Resource = proplists:get_value(resource, RequestLine), - - [Method ++ " " ++ Resource ++ " " ++ "HTTP/" ++ Version ++ "\r\n" | Acc]. - --spec from_response(Data::binary()) -> #http_message{}. -from_response(Data) -> - [StatusLine | Headers] = binary:split(Data, <>, [trim, global]), - {match, [_, Version, Status, Reason]} = re:run(StatusLine, "HTTP/([0-9]\.[0-9])\s([0-9]{3,3})\s([a-zA-z0-9 ]+)", [{capture, all, list}]), - - StatusLineList = [{version, Version}, {status, Status}, {reason, Reason}], - - HeadersList = lists:foldr(fun(Element, Acc) -> - {match, [_Match, HeaderName, HeaderValue]} = re:run(Element, "(\.+):\s+(\.+)", [{capture, all, list}]), - [{string:strip(HeaderName), HeaderValue} | Acc] - end, [], Headers), - - wsecli_http:build(response, StatusLineList, HeadersList). - --spec get_start_line_value(Key::atom(), Message::#http_message{}) -> string(). -get_start_line_value(Key, Message) -> - proplists:get_value(Key, Message#http_message.start_line). - --spec get_header_value(Key::string(), Message::#http_message{}) -> string(). -get_header_value(Key, Message) -> - LowerCasedKey = string:to_lower(Key), - get_header_value_case_insensitive(LowerCasedKey, Message#http_message.headers). - --spec get_header_value_case_insensitive(Key::string(), list()) -> undefined; - (Key::string(), list()) -> string(). -get_header_value_case_insensitive(_, []) -> - undefined; - -get_header_value_case_insensitive(Key, [{Name, Value} | Tail]) -> - LowerCaseName = string:to_lower(Name), - case Key == LowerCaseName of - true -> - Value; - false -> - get_header_value_case_insensitive(Key, Tail) - end. diff --git a/src/wsecli_key.erl b/src/wsecli_key.erl deleted file mode 100644 index 44b84c2..0000000 --- a/src/wsecli_key.erl +++ /dev/null @@ -1,23 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - -%% @hidden - --module(wsecli_key). - --export([generate/0]). - --spec generate() -> string(). -generate() -> - binary_to_list(base64:encode(crypto:rand_bytes(16))). diff --git a/src/wsecli_message.erl b/src/wsecli_message.erl deleted file mode 100644 index 8410f00..0000000 --- a/src/wsecli_message.erl +++ /dev/null @@ -1,167 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - -%% @hidden - --module(wsecli_message). --include("wsecli.hrl"). - --export([encode/2, decode/1, decode/2]). - --define(FRAGMENT_SIZE, 4096). --type message_type() :: begin_message | continue_message. - --spec encode(Data::string() | binary(), Type::atom()) -> binary(). -encode(Data, Type) when is_list(Data)-> - encode(list_to_binary(Data), Type); - -encode(Data, Type)-> - lists:reverse(encode(Data, Type, [])). - --spec decode(Data::binary()) -> list(#message{}). -decode(Data) -> - decode(Data, begin_message, #message{}). - --spec decode(Data::binary(), Message::#message{}) -> list(#message{}). -decode(Data, Message) -> - decode(Data, continue_message, Message). - - -% -% Internal -% --spec encode(Data::binary(), Type :: atom(), Acc ::list()) -> list(). -encode(Data, Type, _Acc) when Type =:= ping ; Type =:= pong ; Type =:= close-> - [frame(Data, [fin, {opcode, Type}])]; - %Frame = wsecli_framing:frame(Data, [fin, {opcode, Type}]), - %wsecli_framing:to_binary(Frame); - -encode(<>, Type, Acc) -> - [frame(Data, [fin, {opcode, Type}]) | Acc]; - -encode(<>, Type, []) -> - encode(Rest, continuation, [frame(Data, [{opcode, Type}]) | []]); - -encode(<>, Type, Acc) -> - encode(Rest, Type, [frame(Data, [{opcode, Type}]) | Acc]); - -encode(<<>>, _Type, Acc) -> - Acc; - -encode(<>, Type, Acc) -> - [frame(Data, [fin, {opcode, Type}]) | Acc]. - --spec frame(Data::binary(), Options::list()) -> binary(). -frame(Data, Options) -> - Frame = wsecli_framing:frame(Data, Options), - wsecli_framing:to_binary(Frame). - --spec decode(Data::binary(), Type :: message_type(), Message::#message{}) -> list(#message{}). -decode(Data, begin_message, _Message) -> - Frames = wsecli_framing:from_binary(Data), - lists:reverse(process_frames(begin_message, Frames, [])); - -decode(Data, continue_message, Message) -> - Frames = wsecli_framing:from_binary(Data), - lists:reverse(process_frames(continue_message, Frames, [Message | []])). - --spec process_frames(Type:: message_type(), Frames :: list(#frame{}), Messages :: list(#message{})) -> list(#message{}). -process_frames(_, [], Acc) -> - Acc; -process_frames(begin_message, Frames, Acc) -> - wtf(Frames, begin_message, #message{}, Acc); - -process_frames(continue_message, Frames, [FramgmentedMessage | Acc]) -> - wtf(Frames, continue_message, FramgmentedMessage, Acc). - -wtf([Frame | Frames], Type, XMessage, Acc) -> - case process_frame(Frame, Type, XMessage) of - {fragmented, Message} -> - process_frames(continue_message, Frames, [Message#message{type = fragmented} | Acc]); - {completed, Message} -> - process_frames(begin_message, Frames, [Message | Acc]) - end. - --spec process_frame(Frame :: #frame{}, MessageType :: message_type(), Message :: #message{})-> {fragmented | completed, #message{}}. -process_frame(Frame, begin_message, Message) -> - case contextualize_frame(Frame) of - open_close -> - BuiltMessage = build_message(Message, [Frame]), - {completed, BuiltMessage}; - open_continue -> - Frames = Message#message.frames, - {fragmented, Message#message{frames = [Frame | Frames]}} - end; - -process_frame(Frame, continue_message, Message) -> - case contextualize_frame(Frame) of - continue -> - Frames = Message#message.frames, - {fragmented, Message#message{frames = [Frame | Frames]}}; - continue_close -> - BuiltMessage = build_message(Message, lists:reverse([Frame | Message#message.frames])), - {completed, BuiltMessage} - end. - --spec contextualize_frame(Frame :: #frame{}) -> continue_close | open_continue | continue | open_close. -contextualize_frame(Frame) -> - case {Frame#frame.fin, Frame#frame.opcode} of - {1, 0} -> continue_close; - {0, 0} -> continue; - {1, _} -> open_close; - {0, _} -> open_continue - end. - -build_message(Message, Frames) -> - [HeadFrame | _] = Frames, - - case HeadFrame#frame.opcode of - 1 -> - Payload = build_payload_from_frames(text, Frames), - Message#message{type = text, payload = Payload}; - 2 -> - Payload = build_payload_from_frames(binary, Frames), - Message#message{type = binary, payload = Payload}; - 8 -> - Payload = build_payload_from_frames(close, Frames), - Message#message{type = close, payload = Payload}; - 9 -> - Payload = build_payload_from_frames(text, Frames), - Message#message{type = ping, payload = Payload}; - 10 -> - Payload = build_payload_from_frames(text, Frames), - Message#message{type = pong, payload = Payload} - end. - -build_payload_from_frames(close, [Frame]) -> - case Frame#frame.payload of - <<>> -> {undefined, undefined}; - <> -> {Status, binary_to_list(Reason)} - end; - -build_payload_from_frames(binary, Frames) -> - concatenate_payload_from_frames(Frames); - -build_payload_from_frames(text, Frames) -> - Payload = concatenate_payload_from_frames(Frames), - binary_to_list(Payload). - -concatenate_payload_from_frames(Frames) -> - concatenate_payload_from_frames(Frames, <<>>). - -concatenate_payload_from_frames([], Acc) -> - Acc; -concatenate_payload_from_frames([Frame | Rest], Acc) -> - concatenate_payload_from_frames(Rest, <>). - diff --git a/test/spec/wsecli_framing_spec.erl b/test/spec/wsecli_framing_spec.erl deleted file mode 100644 index dc2f804..0000000 --- a/test/spec/wsecli_framing_spec.erl +++ /dev/null @@ -1,493 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_framing_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). --include("wsecli.hrl"). -%-compile([export_all]). - -spec() -> - describe("wsecli_framing", fun()-> - describe("to_binary", fun() -> - describe("payload length <= 125", fun()-> - it("should return a binary representation of a frame", fun()-> - Data = "Foo bar", - - - Frame = wsecli_framing:frame(Data), - BinFrame = wsecli_framing:to_binary(Frame), - << - Fin:1, - Rsv1:1, Rsv2:1, Rsv3:1, - Opcode:4, - Mask:1, - PayloadLen:7, - MaskingKey:32, - Payload/binary - >> = BinFrame, - - assert_that(Fin, is(Frame#frame.fin)), - assert_that(Rsv1, is(Frame#frame.rsv1)), - assert_that(Rsv2, is(Frame#frame.rsv2)), - assert_that(Rsv3, is(Frame#frame.rsv3)), - assert_that(Opcode, is(Frame#frame.opcode)), - assert_that(Mask, is(Frame#frame.mask)), - assert_that(PayloadLen, is(Frame#frame.payload_len)), - assert_that(MaskingKey, is(Frame#frame.masking_key)), - assert_that(Payload, is(Frame#frame.payload)) - end) - end), - describe("payload length > 125 and <= 65536", fun() -> - it("should return a binary representation of a frame", fun()-> - Data = get_random_string(165), - - - Frame = wsecli_framing:frame(Data), - BinFrame = wsecli_framing:to_binary(Frame), - << - Fin:1, - Rsv1:1, Rsv2:1, Rsv3:1, - Opcode:4, - Mask:1, - PayloadLen:7, - ExtendedPayloadLen:16, - MaskingKey:32, - Payload/binary - >> = BinFrame, - - assert_that(Fin, is(Frame#frame.fin)), - assert_that(Rsv1, is(Frame#frame.rsv1)), - assert_that(Rsv2, is(Frame#frame.rsv2)), - assert_that(Rsv3, is(Frame#frame.rsv3)), - assert_that(Opcode, is(Frame#frame.opcode)), - assert_that(Mask, is(Frame#frame.mask)), - assert_that(PayloadLen, is(Frame#frame.payload_len)), - assert_that(ExtendedPayloadLen, is(Frame#frame.extended_payload_len)), - assert_that(MaskingKey, is(Frame#frame.masking_key)), - assert_that(Payload, is(Frame#frame.payload)) - end) - end), - describe("payload length > 65536", fun() -> - it("should return a binary representation of a frame", fun()-> - Data = get_random_string(78000), - - - Frame = wsecli_framing:frame(Data), - BinFrame = wsecli_framing:to_binary(Frame), - << - Fin:1, - Rsv1:1, Rsv2:1, Rsv3:1, - Opcode:4, - Mask:1, - PayloadLen:7, - ExtendedPayloadLen:64, - MaskingKey:32, - Payload/binary - >> = BinFrame, - - assert_that(Fin, is(Frame#frame.fin)), - assert_that(Rsv1, is(Frame#frame.rsv1)), - assert_that(Rsv2, is(Frame#frame.rsv2)), - assert_that(Rsv3, is(Frame#frame.rsv3)), - assert_that(Opcode, is(Frame#frame.opcode)), - assert_that(Mask, is(Frame#frame.mask)), - assert_that(PayloadLen, is(Frame#frame.payload_len)), - assert_that(ExtendedPayloadLen, is(Frame#frame.extended_payload_len_cont)), - assert_that(MaskingKey, is(Frame#frame.masking_key)), - assert_that(Payload, is(Frame#frame.payload)) - end) - end) - end), - describe("from_binary", fun() -> - describe("when binary is composed from various frames", fun() -> - it("should return a list of frame records", fun() -> - Text1 = "Jankle jankle", - Payload1 = list_to_binary(Text1), - PayloadLen1 = byte_size(Payload1), - - Text2 = "Pasa pra casa", - Payload2 = list_to_binary(Text2), - PayloadLen2 = byte_size(Payload2), - - BinFrame1 = get_binary_frame(0, 0, 0, 0, 1, 0, PayloadLen1, 0, Payload1), - BinFrame2 = get_binary_frame(1, 0, 0, 0, 0, 0, PayloadLen2, 0, Payload2), - - BinFrames = <>, - - [Frame1, Frame2] = wsecli_framing:from_binary(BinFrames), - - assert_that(Frame1#frame.fin, is(0)), - assert_that(Frame1#frame.rsv1, is(0)), - assert_that(Frame1#frame.rsv2, is(0)), - assert_that(Frame1#frame.rsv3, is(0)), - assert_that(Frame1#frame.opcode, is(1)), - assert_that(Frame1#frame.mask, is(0)), - assert_that(Frame1#frame.payload_len, is(PayloadLen1)), - assert_that(Frame1#frame.payload, is(Payload1)), - - assert_that(Frame2#frame.fin, is(1)), - assert_that(Frame2#frame.rsv1, is(0)), - assert_that(Frame2#frame.rsv2, is(0)), - assert_that(Frame2#frame.rsv3, is(0)), - assert_that(Frame2#frame.opcode, is(0)), - assert_that(Frame2#frame.mask, is(0)), - assert_that(Frame2#frame.payload_len, is(PayloadLen2)), - assert_that(Frame2#frame.payload, is(Payload2)) - end) - end), - describe("when payload length <= 125", fun()-> - it("should return a frame record", fun() -> - Text = "Jankle jankle", - Payload = list_to_binary(Text), - PayloadLen = byte_size(Payload), - - BinFrame = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLen, 0, Payload), - - [Frame] = wsecli_framing:from_binary(BinFrame), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(1)), - assert_that(Frame#frame.mask, is(0)), - assert_that(Frame#frame.payload_len, is(PayloadLen)), - assert_that(Frame#frame.payload, is(Payload)) - end) - end), - describe("when payload length > 125 and <= 65536", fun()-> - it("should return a frame record", fun() -> - Data = get_random_string(4096), - Payload = list_to_binary(Data), - PayloadLen = 126, - ExtendedPayloadLen = byte_size(Payload), - - BinFrame = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLen, ExtendedPayloadLen, Payload), - - [Frame] = wsecli_framing:from_binary(BinFrame), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(1)), - assert_that(Frame#frame.mask, is(0)), - assert_that(Frame#frame.payload_len, is(126)), - assert_that(Frame#frame.extended_payload_len, is(ExtendedPayloadLen)), - assert_that(Frame#frame.payload, is(Payload)) - end) - end), - describe("when payload length > 65536", fun()-> - it("should return a frame record", fun() -> - Data = get_random_string(70000), - Payload = list_to_binary(Data), - PayloadLen = 127, - ExtendedPayloadLenCont = byte_size(Payload), - - BinFrame = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLen, ExtendedPayloadLenCont, Payload), - - [Frame] = wsecli_framing:from_binary(BinFrame), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(1)), - assert_that(Frame#frame.mask, is(0)), - assert_that(Frame#frame.payload_len, is(127)), - assert_that(Frame#frame.extended_payload_len_cont, is(ExtendedPayloadLenCont)), - assert_that(Frame#frame.payload, is(Payload)) - end) - end) - end), - describe("frame", fun() -> - describe("when no options are passed", fun() -> - it("should unset fin", fun() -> - Frame = wsecli_framing:frame("Foo bar"), - assert_that(Frame#frame.fin, is(0)) - end), - it("should set opcode to text on text data", fun()-> - Frame = wsecli_framing:frame("Foo bar"), - assert_that(Frame#frame.opcode, is(1)) - end), - it("should set opcode to binary on binary data", fun()-> - Frame = wsecli_framing:frame(<<"Foo bar">>), - assert_that(Frame#frame.opcode, is(2)) - end) - end), - describe("when options are passed", fun()-> - it("should set fin if fin option is present", fun()-> - Frame = wsecli_framing:frame("Foo bar", [fin]), - assert_that(Frame#frame.fin, is(1)) - end), - it("should set opcode to text if opcode option is text", fun()-> - Frame = wsecli_framing:frame("Foo bar", [{opcode, text}]), - assert_that(Frame#frame.opcode, is(1)) - end), - it("should set opcode to binary if opcode option is binary", fun()-> - Frame = wsecli_framing:frame("asdasdasd", [{opcode, binary}]), - assert_that(Frame#frame.opcode, is(2)) - end), - it("should set opcode to ping if opcode option is ping", fun()-> - Frame = wsecli_framing:frame("pinging", [{opcode, ping}]), - assert_that(Frame#frame.opcode, is(9)) - end), - it("should set opcode to pong if opcode option is pong", fun() -> - Frame = wsecli_framing:frame("pingin", [{opcode, pong}]), - assert_that(Frame#frame.opcode, is(10)) - end), - it("should set opcode to close if opcode option is close", fun() -> - % Notice that this is an invalid payload for a close frame - Frame = wsecli_framing:frame("closing", [{opcode, close}]), - assert_that(Frame#frame.opcode, is(8)) - end), - it("should set opcode to continuation if opcode option is continuation", fun()-> - Frame = wsecli_framing:frame("Foo bar", [{opcode, continuation}]), - assert_that(Frame#frame.opcode, is(0)) - end) - end), - describe("text data", fun()-> - %it("should set the FIN bit when the message is not fragmented", fun()-> - % Data = "Foo bar", - - % Frame = wsecli_framing:frame(Data), - % assert_that(Frame#frame.fin, is(1)) - % end), - it("should leave the RSV bits unset", fun()-> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)) - end), - it("should set opcode to TEXT", fun() -> - Data = "Foo bar", - - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.opcode, is(1)) - end), - describe("data length <= 125", fun() -> - it("should set data length in payload length", fun() -> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.payload_len, is(string:len(Data))) - end), - it("should set 0 in extended payload length", fun()-> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len, is(0)) - end), - it("should set 0 in extended payload length cont.", fun()-> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len_cont, is(0)) - end) - end), - describe("data length > 125", fun() -> - it("should set in payload_len the value 126", fun() -> - Data = get_random_string(320), - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.payload_len, is(126)) - end), - it("should set data length in extended payload length", fun()-> - Data = get_random_string(455), - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len, is(455)) - end), - it("should set 0 in extended payload length cont.", fun()-> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len_cont, is(0)) - end) - end), - describe("data length > 65536", fun() -> - it("should set in payload_len the value 127", fun() -> - Data = get_random_string(70000), - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.payload_len, is(127)) - end), - it("should set 0 in extended payload length", fun()-> - Data = get_random_string(68000), - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len, is(0)) - end), - it("should set data length in extended payload length cont.", fun()-> - Data = get_random_string(75000), - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.extended_payload_len_cont, is(75000)) - end) - end), - describe("masking", fun() -> - it("should set MASK", fun() -> - Data = "Foo bar", - Frame = wsecli_framing:frame(Data), - assert_that(Frame#frame.mask,is(1)) - end), - it("should mask the payload", fun() -> - Data = "Foo bar", - BinData = list_to_binary(Data), - Frame = wsecli_framing:frame(Data), - MaskKey = Frame#frame.masking_key, - assert_that(Frame#frame.payload, is(mask(BinData, MaskKey, <<>>))) - end), - it("shouldn't affect payload length", fun() -> - Data = "Foo Bar", - BinData = list_to_binary(Data), - Frame = wsecli_framing:frame(Data), - assert_that(byte_size(Frame#frame.payload), is(byte_size(BinData))) - end) - end) - end), - %it("should set opcode to BINARY when data is binary", fun() -> - % Data = crypto:rand_bytes(64), - - % Frame = wsecli_framing:frame(Data), - % assert_that(Frame#frame.opcode, is(2)) - % end), - describe("control frames", fun()-> - describe("close", fun() -> - it("should frame closes without payload", fun() -> - Frame = wsecli_framing:frame([], [fin, {opcode, close}]), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(8)), - assert_that(Frame#frame.mask, is(0)) - end), - it("should frames closes with payload", fun() -> - Frame = wsecli_framing:frame({1000, "Closing this shit"}, [fin, {opcode, close}]), - %mask function also unmask the data - <> = mask( - Frame#frame.payload, - Frame#frame.masking_key, - <<>>), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(8)), - assert_that(Code, is(1000)), - assert_that(Frame#frame.mask, is(1)), - assert_that(binary_to_list(Reason), is("Closing this shit")) - end) - end), - describe("ping", fun() -> - it("should frame pings without payload", fun() -> - Frame = wsecli_framing:frame([], [fin, {opcode, ping}]), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(9)), - assert_that(Frame#frame.mask, is(0)), - assert_that(Frame#frame.payload, is(undefined)) - end), - it("should frame pings with payload", fun() -> - Frame = wsecli_framing:frame("Andale", [fin, {opcode, ping}]), - - MaskedData = mask( - list_to_binary("Andale"), - Frame#frame.masking_key, - <<>>), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(9)), - assert_that(Frame#frame.mask, is(1)), - assert_that(Frame#frame.payload, is(MaskedData)) - end) - end), - describe("pong", fun() -> - it("should frame pong without payload", fun() -> - Frame = wsecli_framing:frame([], [fin, {opcode, pong}]), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(10)), - assert_that(Frame#frame.mask, is(0)), - assert_that(Frame#frame.payload, is(undefined)) - end), - it("should fram pongs with payload", fun() -> - Frame = wsecli_framing:frame("Andale", [fin, {opcode, pong}]), - - MaskedData = mask( - list_to_binary("Andale"), - Frame#frame.masking_key, - <<>>), - - assert_that(Frame#frame.fin, is(1)), - assert_that(Frame#frame.rsv1, is(0)), - assert_that(Frame#frame.rsv2, is(0)), - assert_that(Frame#frame.rsv3, is(0)), - assert_that(Frame#frame.opcode, is(10)), - assert_that(Frame#frame.mask, is(1)), - assert_that(Frame#frame.payload, is(MaskedData)) - end) - end), - it("should not allow payload size over 125 bytes") - end) - end) - end). - -get_binary_frame(Fin, Rsv1, Rsv2, Rsv3, Opcode, Mask, Length, ExtendedPayloadLength, Payload) -> - Head = <>, - - case Length of - 126 -> - <>; - 127 -> - <>; - _ -> - <> - end. - -get_random_string(Length) -> - AllowedChars = "qwertyQWERTY1234567890", - lists:foldl(fun(_, Acc) -> - [lists:nth(random:uniform(length(AllowedChars)), - AllowedChars)] - ++ Acc - end, [], lists:seq(1, Length)). - -%mask(Bin, MaskKey, Acc) -> -mask(<>, MaskKey, Acc) -> - T = Data bxor MaskKey, - mask(Rest, MaskKey, <>); - -mask(<< Data:24>>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<< Data:16>>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<< Data:8>>, MaskKey, Acc) -> - <> = <>, - T = Data bxor MaskKey2, - <>; - -mask(<<>>, _, Acc) -> - Acc. diff --git a/test/spec/wsecli_handshake_spec.erl b/test/spec/wsecli_handshake_spec.erl deleted file mode 100644 index 28b4432..0000000 --- a/test/spec/wsecli_handshake_spec.erl +++ /dev/null @@ -1,65 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_handshake_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). - --include("wsecli.hrl"). - -spec() -> - describe("wsecli_handshake", fun() -> - it("should return a valid handshake request", fun() -> - Resource = "/", - Host = "localhost", - Port = 8080, - - HandShake = wsecli_handshake:build(Resource, Host, Port), - assert_that(HandShake#handshake.version, is(13)), - - HttpMessage = HandShake#handshake.message, - assert_that(wsecli_http:get_start_line_value(method, HttpMessage), is("GET")), - assert_that(wsecli_http:get_start_line_value(version, HttpMessage), is("1.1")), - assert_that(wsecli_http:get_start_line_value(resource, HttpMessage), is("/")), - - assert_that(wsecli_http:get_header_value("Host", HttpMessage), is(Host ++ ":" ++ integer_to_list(Port))), - assert_that(wsecli_http:get_header_value("Upgrade", HttpMessage), is("websocket")), - assert_that(wsecli_http:get_header_value("Connection", HttpMessage), is("upgrade")), - assert_that(wsecli_http:get_header_value("Sec-Websocket-Key", HttpMessage), is_not(undefined)), - assert_that(wsecli_http:get_header_value("Sec-Websocket-Version", HttpMessage), is("13")) - end), - it("should validate a handshake response", fun() -> - Resource = "/", - Host = "localhost", - Port = 8080, - - HandShake = wsecli_handshake:build(Resource, Host, Port), - Key = wsecli_http:get_header_value("sec-websocket-key", HandShake#handshake.message), - - BinResponse = list_to_binary(["HTTP/1.1 101 Switch Protocols\r\n - Upgrade: websocket\r\n - Connection: upgrade\r\n - Sec-Websocket-Accept: ", fake_sec_websocket_accept(Key), "\r\n", - "Header-A: A\r\n - Header-C: 123123\r\n - Header-D: D\r\n\r\n"]), - Response = wsecli_http:from_response(BinResponse), - - assert_that(wsecli_handshake:validate(Response, HandShake),is(true)) - end) - end). - -fake_sec_websocket_accept(Key) -> - BinaryKey = list_to_binary(Key), - base64:encode_to_string(crypto:sha(<>)). diff --git a/test/spec/wsecli_http_spec.erl b/test/spec/wsecli_http_spec.erl deleted file mode 100644 index 595137a..0000000 --- a/test/spec/wsecli_http_spec.erl +++ /dev/null @@ -1,124 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_http_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). --include("wsecli.hrl"). - -spec() -> - describe("wsecli_http", fun() -> - it("should build proper HTTP messages", fun() -> - RequestLine = [ - {method, "GET"}, - {version, "1.1"}, - {resource, "/"} - ], - - Headers = [ - {"Header-A", "A"}, - {"Header-B", "B"} - ], - - Message = wsecli_http:build(request, RequestLine, Headers), - - assert_that(Message#http_message.type, is(request)), - - assert_that(proplists:get_value(method, Message#http_message.start_line), is("GET")), - assert_that(proplists:get_value(version, Message#http_message.start_line), is("1.1")), - assert_that(proplists:get_value(resource, Message#http_message.start_line), is("/")), - assert_that(proplists:get_value("Header-A", Message#http_message.headers), is("A")), - assert_that(proplists:get_value("Header-B", Message#http_message.headers), is("B")) - end), - it("should build proper HTTP request strings", fun() -> - RequestLine = [ - {method, "GET"}, - {version, "1.1"}, - {resource, "/"} - ], - - Headers = [ - {"Header-A", "A"}, - {"Header-B", "B"} - ], - - Message = wsecli_http:build(request, RequestLine, Headers), - Request = wsecli_http:to_request(Message), - - assert_that(Request, is([ - "GET / HTTP/1.1\r\n", - "Header-A: A\r\n", - "Header-B: B\r\n", - "\r\n" - ])) - end), - it("should build a proper HTTP response from binary message", fun() -> - Data = <<"HTTP/1.1 205 Reset Content\r\n - Header-A: A\r\n - Header-C: dGhlIHNhbXBsZSBub25jZQ==\r\n - Header-D: D\r\n\r\n">>, - - StatusLine = [ - {version, "1.1"}, - {status, "205"}, - {reason, "Reset Content"} - ], - - Headers = [ - {"Header-A", "A"}, - {"Header-C", "dGhlIHNhbXBsZSBub25jZQ=="}, - {"Header-D", "D"} - ], - - ExpectedResponse = #http_message{type = response, start_line = StatusLine, headers = Headers}, - Response = wsecli_http:from_response(Data), - - assert_that(Response, is(ExpectedResponse)) - end), - it("should return http_message start_line values if present", fun() -> - Message = #http_message{ - type = request, - start_line = [ - {method, "GET"}, - {version, "1.1"}, - {resource, "/"} - ], - headers = [ - {"header-a", "A"}, - {"header-b", "b"} - ] - }, - - assert_that(wsecli_http:get_start_line_value(version, Message), is("1.1")), - assert_that(wsecli_http:get_start_line_value(method, Message), is("GET")), - assert_that(wsecli_http:get_start_line_value(resource, Message), is("/")) - end), - it("should return http_message header values if present", fun() -> - Message = #http_message{ - type = request, - start_line = [ - {method, "GET"}, - {version, "1.1"}, - {resource, "/"} - ], - headers = [ - {"Header-a", "A"}, - {"header-B", "b"} - ] - }, - - assert_that(wsecli_http:get_header_value("header-a", Message), is("A")), - assert_that(wsecli_http:get_header_value("header-b", Message), is("b")) - end) - end). diff --git a/test/spec/wsecli_key_spec.erl b/test/spec/wsecli_key_spec.erl deleted file mode 100644 index 0f26512..0000000 --- a/test/spec/wsecli_key_spec.erl +++ /dev/null @@ -1,33 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_key_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). - -spec() -> - describe("wsecli_key", fun() -> - it("should return a valid Sec-WebSocket-Key", fun() -> - %Meck crashes if we try to mock crypto module - %meck:new(crypto, [passthrough]), - meck:new(base64, [unstick, passthrough]), - - Key = wsecli_key:generate(), - - assert_that(length(Key), is(24)), - %assert_that(meck:called(crypto, rand_bytes, 16), is(true)), - assert_that(meck:called(base64, encode, '_'), is(true)), - meck:unload(base64) - end) - end). diff --git a/test/spec/wsecli_message_spec.erl b/test/spec/wsecli_message_spec.erl deleted file mode 100644 index f2545af..0000000 --- a/test/spec/wsecli_message_spec.erl +++ /dev/null @@ -1,445 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_message_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). --include("wsecli.hrl"). - --define(FRAGMENT_SIZE, 4096). - -spec() -> - describe("encode", fun() -> - before_all(fun()-> - meck:new(wsecli_framing, [passthrough]) - end), - - after_all(fun()-> - meck:unload(wsecli_framing) - end), - - it("should set opcode to 'text' if type is text", fun() -> - wsecli_message:encode("asadsd", text), - assert_that(meck:called(wsecli_framing, frame, ['_', [fin, {opcode, text}]]), is(true)) - end), - it("should set opcode to 'binary' if type is binary", fun() -> - wsecli_message:encode(<<"asdasd">>, binary), - assert_that(meck:called(wsecli_framing, frame, ['_', [fin, {opcode, binary}]]), is(true)) - end), - describe("when payload size is <= fragment size", fun()-> - it("should return a list with only one binary fragment", fun()-> - Data = "Foo bar", - [BinFrame | []] = wsecli_message:encode(Data, text), - assert_that(byte_size(list_to_binary(Data)),is(less_than(?FRAGMENT_SIZE))), - assert_that(is_binary(BinFrame), is(true)), - assert_that(meck:called(wsecli_framing, to_binary, '_'), is(true)), - assert_that(meck:called(wsecli_framing, frame, '_'), is(true)) - end), - it("should set opcode to 'type'", fun() -> - Data = "Foo bar", - [Frame] = wsecli_message:encode(Data, text), - - <<_:4, Opcode:4, _/binary>> = Frame, - - assert_that(Opcode, is(1)) - end), - it("should set fin", fun()-> - Data = "Foo bar", - [Frame] = wsecli_message:encode(Data, text), - - <> = Frame, - - assert_that(Fin, is(1)) - end) - end), - describe("when payload size is > fragment size", fun() -> - it("should return a list of binary fragments", fun()-> - Data = crypto:rand_bytes(5000), - Frames = wsecli_message:encode(Data, binary), - assert_that(meck:called(wsecli_framing, to_binary, '_'), is(true)), - assert_that(meck:called(wsecli_framing, frame, '_'), is(true)), - assert_that(length(Frames), is(2)) - end), - it("should set a payload of 4096 bytes or less on each fragment", fun() -> - Data = crypto:rand_bytes(12288), - Frames = wsecli_message:encode(Data, binary), - - [Frame1, Frame2, Frame3] = Frames, - - <<_:64, Payload1/binary>> = Frame1, - <<_:64, Payload2/binary>> = Frame2, - <<_:64, Payload3/binary>> = Frame3, - - assert_that(byte_size(Payload1), is(4096)), - assert_that(byte_size(Payload2), is(4096)), - assert_that(byte_size(Payload3), is(4096)) - end), - it("should set opcode to 'type' on the first fragment", fun()-> - Data = crypto:rand_bytes(5000), - Frames = wsecli_message:encode(Data, binary), - - [FirstFragment | _ ] = Frames, - - <<_:4, Opcode:4, _/binary>> = FirstFragment, - - assert_that(Opcode, is(2)) - end), - it("should unset fin on all fragments but last", fun() -> - Data = crypto:rand_bytes(12288), %4096 * 3 - Frames = wsecli_message:encode(Data, binary), - - [Frame1, Frame2, Frame3] = Frames, - - <> = Frame1, - <> = Frame2, - <> = Frame3, - - assert_that(Fin1, is(0)), - assert_that(Fin2, is(0)), - assert_that(Fin3, is(1)) - end), - it("should set opcode to 'continuation' on all fragments but first", fun() -> - Data = crypto:rand_bytes(12288), %4096 * 3 - Frames = wsecli_message:encode(Data, binary), - - [Frame1, Frame2, Frame3] = Frames, - - <<_:4, Opcode1:4, _/binary>> = Frame1, - <<_:4, Opcode2:4, _/binary>> = Frame2, - <<_:4, Opcode3:4, _/binary>> = Frame3, - - assert_that(Opcode1, is(2)), - assert_that(Opcode2, is(0)), - assert_that(Opcode3, is(0)) - end) - end), - describe("control messages", fun() -> - describe("close", fun() -> - it("should return a list of one frame", fun() -> - [_Frame] = wsecli_message:encode([], close) - end), - it("should return a close frame", fun() -> - [Frame] = wsecli_message:encode([], close), - - <> = Frame, - - assert_that(Fin, is(1)), - assert_that(Rsv, is(0)), - assert_that(Opcode, is(8)) - end), - it("should attach application payload", fun() -> - [Frame] = wsecli_message:encode({1004, "Chapando el garito"}, close), - - <<_Fin:1, _Rsv:3, _Opcode:4, 1:1, _PayloadLen:7, _Mask:32, _Payload/binary>> = Frame - end) - end), - describe("ping", fun() -> - it("should return a list of one frame", fun() -> - [_Frame] = wsecli_message:encode([], ping) - end), - it("should return a ping frame", fun() -> - [Frame] = wsecli_message:encode([], ping), - - <> = Frame, - - assert_that(Fin, is(1)), - assert_that(Rsv, is(0)), - assert_that(Opcode, is(9)) - end), - it("should attach application payload", fun() -> - [Frame] = wsecli_message:encode("1234", ping), - - <<_Fin:1, _Rsv:3, _Opcode:4, 1:1, 4:7, _Mask:32, _Payload:4/binary>> = Frame - end) - end), - describe("pong", fun() -> - it("should return a list of one frame", fun() -> - [_Frame] = wsecli_message:encode([], pong) - end), - it("should return a ping frame", fun() -> - [Frame] = wsecli_message:encode([], pong), - - <> = Frame, - - assert_that(Fin, is(1)), - assert_that(Rsv, is(0)), - assert_that(Opcode, is(10)) - end), - it("should attach application payload", fun() -> - [Frame] = wsecli_message:encode("1234", pong), - - <<_Fin:1, _Rsv:3, _Opcode:4, 1:1, 4:7, _Mask:32, _Payload:4/binary>> = Frame - end) - end) - end) - end), - describe("decode", fun()-> - describe("fragmented messages", fun() -> - it("should complain when control messages are fragmented"), - it("should return a fragmented message with undefined payload when message is not complete", fun() -> - Payload = crypto:rand_bytes(20), - << - Payload1:10/binary, - Payload2:5/binary, - _Payload3/binary - >> = Payload, - - FakeFragment1 = get_binary_frame(0, 0, 0, 0, 2, 0, 10, 0, Payload1), - FakeFragment2 = get_binary_frame(0, 0, 0, 0, 0, 0, 5, 0, Payload2), - - Data = <>, - - [Message] = wsecli_message:decode(Data), - - assert_that(Message#message.type, is(fragmented)), - assert_that(length(Message#message.frames), is(2)) - end), - it("should decode data containing a complete fragmented binary message", fun() -> - Payload = crypto:rand_bytes(40), - << - Payload1:10/binary, - Payload2:10/binary, - Payload3:10/binary, - Payload4:10/binary - >> = Payload, - - FakeFragment1 = get_binary_frame(0, 0, 0, 0, 2, 0, 10, 0, Payload1), - FakeFragment2 = get_binary_frame(0, 0, 0, 0, 0, 0, 10, 0, Payload2), - FakeFragment3 = get_binary_frame(0, 0, 0, 0, 0, 0, 10, 0, Payload3), - FakeFragment4 = get_binary_frame(1, 0, 0, 0, 0, 0, 10, 0, Payload4), - - Data = << FakeFragment1/binary, FakeFragment2/binary, FakeFragment3/binary, FakeFragment4/binary>>, - - [Message] = wsecli_message:decode(Data), - - assert_that(Message#message.type, is(binary)), - assert_that(Message#message.payload, is(Payload)) - end), - it("should decode data containing a complete fragmented text message", fun() -> - Text = "asasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd", - Payload = list_to_binary(Text), - << - Payload1:5/binary, - Payload2:2/binary, - Payload3/binary - >> = Payload, - - FakeFragment1 = get_binary_frame(0, 0, 0, 0, 1, 0, byte_size(Payload1), 0, Payload1), - FakeFragment2 = get_binary_frame(0, 0, 0, 0, 0, 0, byte_size(Payload2), 0, Payload2), - FakeFragment3 = get_binary_frame(1, 0, 0, 0, 0, 0, byte_size(Payload3), 0, Payload3), - - Data = << FakeFragment1/binary, FakeFragment2/binary, FakeFragment3/binary>>, - - [Message] = wsecli_message:decode(Data), - - assert_that(Message#message.type, is(text)), - assert_that(Message#message.payload, is(Text)) - end), - it("should complete a fragmented message", fun() -> - Payload = crypto:rand_bytes(20), - << - Payload1:10/binary, - Payload2:5/binary, - Payload3/binary - >> = Payload, - - FakeFragment1 = get_binary_frame(0, 0, 0, 0, 2, 0, 10, 0, Payload1), - FakeFragment2 = get_binary_frame(0, 0, 0, 0, 0, 0, 5, 0, Payload2), - FakeFragment3 = get_binary_frame(1, 0, 0, 0, 0, 0, 5, 0, Payload3), - - - Data1 = <>, - Data2 = <>, - - [Message1] = wsecli_message:decode(Data1), - [Message2] = wsecli_message:decode(Data2, Message1), - - assert_that(Message1#message.type, is(fragmented)), - assert_that(Message2#message.type, is(binary)), - assert_that(Message2#message.payload, is(Payload)) - end), - it("should decode data with complete fragmented messages and part of fragmented one", fun() -> - BinPayload1 = crypto:rand_bytes(30), - << - Payload1:10/binary, - Payload2:10/binary, - Payload3/binary - >> = BinPayload1, - - FakeFragment1 = get_binary_frame(0, 0, 0, 0, 2, 0, 10, 0, Payload1), - FakeFragment2 = get_binary_frame(0, 0, 0, 0, 0, 0, 10, 0, Payload2), - FakeFragment3 = get_binary_frame(1, 0, 0, 0, 0, 0, 10, 0, Payload3), - - BinPayload2 = crypto:rand_bytes(10), - << - Payload4:10/binary, - _/binary - >> = BinPayload2, - FakeFragment4 = get_binary_frame(0, 0, 0, 0, 2, 0, 10, 0, Payload4), - - Data = << FakeFragment1/binary, FakeFragment2/binary, FakeFragment3/binary, FakeFragment4/binary>>, - - [Message1, Message2] = wsecli_message:decode(Data), - - assert_that(Message1#message.type, is(binary)), - assert_that(Message1#message.payload, is(BinPayload1)), - assert_that(Message2#message.type, is(fragmented)), - assert_that(length(Message2#message.frames), is(1)) - end) - - end), - describe("unfragmented messages", fun()-> - it("should decode data containing various text messages", fun()-> - Text1 = "Churras churras", - Payload1 = list_to_binary(Text1), - PayloadLength1 = byte_size(Payload1), - - Text2 = "Pitas pitas", - Payload2 = list_to_binary(Text2), - PayloadLength2 = byte_size(Payload2), - - Text3 = "Pero que jallo eh", - Payload3 = list_to_binary(Text3), - PayloadLength3 = byte_size(Payload3), - - FakeMessage1 = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength1, 0, Payload1), - FakeMessage2 = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength2, 0, Payload2), - FakeMessage3 = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength3, 0, Payload3), - - Data = << FakeMessage1/binary, FakeMessage2/binary, FakeMessage3/binary>>, - - [Message1, Message2, Message3] = wsecli_message:decode(Data), - - assert_that(Message1#message.type, is(text)), - assert_that(Message1#message.payload, is(Text1)), - assert_that(Message2#message.type, is(text)), - assert_that(Message2#message.payload, is(Text2)), - assert_that(Message3#message.type, is(text)), - assert_that(Message3#message.payload, is(Text3)) - end), - it("should decode data containing text and binary messages", fun()-> - Text1 = "Churras churras", - Payload1 = list_to_binary(Text1), - PayloadLength1 = byte_size(Payload1), - - Payload2 = crypto:rand_bytes(20), - PayloadLength2 = 20, - - Text3 = "Pero que jallo eh", - Payload3 = list_to_binary(Text3), - PayloadLength3 = byte_size(Payload3), - - FakeMessage1 = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength1, 0, Payload1), - FakeMessage2 = get_binary_frame(1, 0, 0, 0, 2, 0, PayloadLength2, 0, Payload2), - FakeMessage3 = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength3, 0, Payload3), - - Data = << FakeMessage1/binary, FakeMessage2/binary, FakeMessage3/binary>>, - - [Message1, Message2, Message3] = wsecli_message:decode(Data), - - assert_that(Message1#message.type, is(text)), - assert_that(Message1#message.payload, is(Text1)), - assert_that(Message2#message.type, is(binary)), - assert_that(Message2#message.payload, is(Payload2)), - assert_that(Message3#message.type, is(text)), - assert_that(Message3#message.payload, is(Text3)) - end), - it("should decode data containing all message types"), - it("should decode data containing a binary message", fun() -> - Payload = crypto:rand_bytes(45), - %") - FakeMessage = get_binary_frame(1, 0, 0, 0, 2, 0, 45, 0, Payload), - [Message] = wsecli_message:decode(FakeMessage), - - assert_that( Message#message.payload, is(Payload)) - end), - it("should decode data containing a text message", fun() -> - Payload = "Iepa yei!", - PayloadLength = length(Payload), - PayloadData = list_to_binary(Payload), - - FakeMessage = get_binary_frame(1, 0, 0, 0, 1, 0, PayloadLength, 0, PayloadData), - [Message] = wsecli_message:decode(FakeMessage), - - assert_that( Message#message.payload, is(Payload)) - end), - describe("control frames", fun() -> - describe("ping", fun() -> - it("should return a message with type ping", fun() -> - FakeMessage = get_binary_frame(1, 0, 0, 0, 9, 0, 0, 0, <<>>), - - [Message] = wsecli_message:decode(FakeMessage), - - assert_that(Message#message.type, is(ping)) - end) - end), - describe("pong", fun() -> - it("should return a message with type pong", fun() -> - FakeMessage = get_binary_frame(1, 0, 0, 0, 10, 0, 0, 0, <<>>), - - [Message] = wsecli_message:decode(FakeMessage), - - assert_that(Message#message.type, is(pong)) - end) - end), - describe("close", fun() -> - it("should return a message with type close", fun() -> - FakeMessage = get_binary_frame(1, 0, 0, 0, 8, 0, 0, 0, <<>>), - - [Message] = wsecli_message:decode(FakeMessage), - - assert_that(Message#message.type, is(close)) - end), - describe("with payload", fun() -> - it("should return the payload a a tuple {Status, Reason}", fun()-> - Status = 1004, - Reason = list_to_binary("A tomar por saco"), - Payload = <>, - PayloadLen = byte_size(Payload), - FakeMessage = get_binary_frame(1, 0, 0, 0, 8, 0, PayloadLen, 0, Payload), - - [Message] = wsecli_message:decode(FakeMessage), - {St, Re} = Message#message.payload, - - assert_that(St, is(Status)), - assert_that(Re, is("A tomar por saco")) - end) - end), - describe("without payload", fun() -> - it("should return the payload as a tuple {undefined, undefined}", fun() -> - FakeMessage = get_binary_frame(1, 0, 0, 0, 8, 0, 0, 0, <<>>), - - [Message] = wsecli_message:decode(FakeMessage), - {Status, Reason} = Message#message.payload, - - assert_that(Status, is(undefined)), - assert_that(Reason, is(undefined)) - end) - end) - end) - end) - end) - end). - -get_binary_frame(Fin, Rsv1, Rsv2, Rsv3, Opcode, Mask, Length, ExtendedPayloadLength, Payload) -> - Head = <>, - - case Length of - 126 -> - <>; - 127 -> - <>; - _ -> - <> - end. diff --git a/test/spec/wsecli_spec.erl b/test/spec/wsecli_spec.erl deleted file mode 100644 index 5ab0841..0000000 --- a/test/spec/wsecli_spec.erl +++ /dev/null @@ -1,221 +0,0 @@ -%Copyright [2012] [Farruco Sanjurjo Arcay] - -% Licensed under the Apache License, Version 2.0 (the "License"); -% you may not use this file except in compliance with the License. -% You may obtain a copy of the License at - -% http://www.apache.org/licenses/LICENSE-2.0 - -% Unless required by applicable law or agreed to in writing, software -% distributed under the License is distributed on an "AS IS" BASIS, -% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -% See the License for the specific language governing permissions and -% limitations under the License. - --module(wsecli_spec). --include_lib("espec/include/espec.hrl"). --include_lib("hamcrest/include/hamcrest.hrl"). -%-compile([export_all]). - -spec() -> - describe("wsecli", fun() -> - before_each(fun() -> - meck:new(gen_tcp, [unstick, passthrough]), - mock_http_server:start(self(), 8080) - end), - - after_each(fun() -> - mock_http_server:stop(), - meck:unload(gen_tcp) - end), - - describe("on_close", fun()-> - it("should be called after the websocket connection has been closed", fun()-> - Pid = self(), - wsecli:start("localhost", 8080, "/"), - wsecli:on_close(fun(_Reason) -> Pid ! {Pid, on_close} end), - wsecli:stop(), - - assert_that((fun() -> - receive - {Pid, on_close} -> - true - after 500 -> - false - end - end)(), is(true)) - end) - end), - describe("start", fun() -> - it("should open a tcp connection to the desired Host", fun() -> - Host = "localhost", - Port = 8080, - Resource = "/", - - wsecli:start(Host, Port, Resource), - - assert_that(meck:called(gen_tcp, connect, '_'), is(true)), - cleanly_stop_wsecli(true) - end), - it("should send an opening handshake when connected", fun() -> - Host = "localhost", - Port = 8080, - Resource = "/", - - wsecli:start(Host, Port, Resource), - - assert_that(meck:called(gen_tcp, send, '_'), is(true)), - cleanly_stop_wsecli(true) - end), - describe("on successful handshake", fun() -> - it("should invoke on_open callback", fun() -> - Host = "localhost", - Port = 8080, - Resource = "/", - - Pid = self(), - wsecli:start(Host, Port, Resource), - wsecli:on_open(fun()-> Pid ! {Pid, on_open} end), - - assert_that((fun() -> - receive - {Pid, on_open} -> - true - after 500 -> - false - end - end)(), is(true)), - cleanly_stop_wsecli(true) - end) - end) - end), - describe("send", fun() -> - it("should send data as messages", fun() -> - meck:new(wsecli_message, [passthrough]), - - wsecli:start("localhost", 8080, "/"), - wsecli:on_open(fun() -> - wsecli:send("la casa de la pradera") - end), - - receive {mock_http_server, received_data} -> ok end, - - assert_that(meck:called(gen_tcp, send, '_'), is(true)), - %assert_that(meck:called(wsecli_message, encode, '_'), is(true)), - cleanly_stop_wsecli(true), - meck:unload(wsecli_message) - end), - it("should invoke on_error callback if not on open state", fun() -> - Pid = self(), - wsecli:start("localhost", 8080, "/"), - wsecli:on_error(fun(_Reason) -> Pid ! {Pid, on_error} end), - wsecli:send("La casa de la pradera"), - - assert_that((fun() -> - receive - {Pid, on_error} -> - true - after 500 -> - false - end - end)(), is(true)), - cleanly_stop_wsecli(true) - end) - end), - describe("on_message", fun() -> - it("should be called when a data message is received", fun() -> - Pid = self(), - wsecli:start("localhost", 8080, "/"), - wsecli:on_message(fun(_Type,_Messae)-> Pid ! {Pid, on_message} end), - wsecli:on_open(fun() -> wsecli:send("Hello") end), - - assert_that((fun() -> - receive - {Pid, on_message} -> - true - after 500 -> - false - end - end)(), is(true)), - cleanly_stop_wsecli(true) - end), - it("should be called with the data in the message as parameter", fun() -> - Pid = self(), - wsecli:start("localhost", 8080, "/"), - wsecli:on_message(fun(text, Message)-> Pid ! {Pid, on_message, Message} end), - wsecli:on_open(fun() -> wsecli:send("Hello") end), - - Message = receive - {Pid, on_message, Data} -> - Data - after 500 -> - false - end, - assert_that(Message, is("Hello")), - cleanly_stop_wsecli(true) - end) - end), - describe("stop", fun() -> - it("should not send websocket close handshake if not connected", fun() -> - meck:new(wsecli_message, [passthrough, unstick]), - wsecli:start("localhost", 8080, "/"), - cleanly_stop_wsecli(true), - - assert_that(meck:called(wsecli_message, encode, ['_', close]), is(false)), - meck:unload(wsecli_message) - end), - it("should send websocket close handshake if connected", fun() -> - meck:new(wsecli_message, [passthrough, unstick]), - wsecli:start("localhost", 8080, "/"), - Pid = self(), - wsecli:on_open(fun() -> - cleanly_stop_wsecli(true), - Pid ! {Pid, closed} - end), - - receive {Pid, closed} -> ok end, - - assert_that(meck:called(wsecli_message, encode, ['_', close]), is(true)), - meck:unload(wsecli_message) - end), - it("should close the socket", fun() -> - wsecli:start("localhost", 8080, "/"), - cleanly_stop_wsecli(true), - - assert_that(meck:called(gen_tcp, close, '_') , is(true)) - end) - end), - it("should receive fragmented messages", fun() -> - Bytes = crypto:rand_bytes(8000), - << Payload1:4000/binary, Payload2:2000/binary, Payload3/binary>> = Bytes, - - FakeFrame1 = <<0:1, 0:3, 2:4, 0:1, 126:7, 4000:16, Payload1/binary>>, - FakeFrame2 = <<0:1, 0:3, 0:4, 0:1, 126:7, 2000:16, Payload2/binary>>, - FakeFrame3 = <<1:1, 0:3, 0:4, 0:1, 126:7, 2000:16, Payload3/binary>>, - - Pid = self(), - wsecli:start("localhost", 8080, "/"), - wsecli:on_message(fun(binary, Payload) -> Pid ! {Pid, message, Payload} end), - - wsecli:on_open(fun() -> - %simulate socket messages - timer:sleep(100), - wsecli ! {tcp, socket, FakeFrame1}, - wsecli ! {tcp, socket, FakeFrame2}, - wsecli ! {tcp, socket, FakeFrame3} - end), - - Payload = receive {Pid, message, Message} -> Message end, - - assert_that(Payload, is(Bytes)), - cleanly_stop_wsecli(true) - end), - it("should handle ping messages"), - it("should handle pong messages") - end). - -cleanly_stop_wsecli(_) -> - Pid = self(), - wsecli:on_close(fun(_) -> Pid ! {Pid, on_close} end), - wsecli:stop(), - receive {Pid, on_close} -> true end.