diff --git a/doc/report b/doc/report index aad268d..0031285 160000 --- a/doc/report +++ b/doc/report @@ -1 +1 @@ -Subproject commit aad268d03a3c23de917cfecf767d583d3cb32633 +Subproject commit 00312859714bef6e9a4fdb9931a41fef56eeb89a diff --git a/games/GGSChat/chat.py b/games/GGSChat/chat.py index e587570..aadf5d9 100644 --- a/games/GGSChat/chat.py +++ b/games/GGSChat/chat.py @@ -140,7 +140,7 @@ class GGSChat: "Content-Type: text\n" + "Content-Length: 0\n"+ "\n") - time.sleep(2) + #time.sleep(2) def updateUsers(self, text): evalNicks = eval(text) diff --git a/src/ggs_player.erl b/src/ggs_player.erl index d6e939e..0323bcc 100644 --- a/src/ggs_player.erl +++ b/src/ggs_player.erl @@ -21,6 +21,7 @@ start_link(Socket) -> % that arrives on it, we do not need to recv() manually. Since the socket % was opened in our parent process, we need to change the owner of it to % us, otherwise these messages end up in our parent. + ggs_protocol:start_link(), erlang:port_connect(Socket, self()), {ok, Token} = ggs_coordinator:join_lobby(), TableStatus = ggs_coordinator:join_table("1337"), @@ -62,7 +63,6 @@ loop(#pl_state{token = _Token, socket = Socket, table = Table} = State) -> receive {tcp, Socket, Data} -> % Just echo for now.. Parsed = ggs_protocol:parse(Data), - self() ! Parsed, loop(State); {notify, _From, Message} -> gen_tcp:send(Socket, Message), diff --git a/src/ggs_protocol.erl b/src/ggs_protocol.erl index d61dfc0..36a96d6 100644 --- a/src/ggs_protocol.erl +++ b/src/ggs_protocol.erl @@ -1,12 +1,50 @@ +%% Parse a string formatted with the GGS protocol using +%% an FSM. Each char is put into the FSM, which incrementally +%% builds a list of strings which represent the complete +%% message. + -module(ggs_protocol). --export([parse/1, getToken/1, create_message/4]). +-export([parse/1, getToken/1, create_message/4, + expect_headers/2, expect_data_section/2, + expect_headers/3, expect_data_section/3]). + +%% tests +-export([test/0, to_dictionary/2]). + +% gen_fsm callbacks.. +-export([init/1, handle_info/2, terminate/2, code_change/3, start_link/0]). + + +-define(SERVER, ?MODULE). + +%% JONTES TESTS +test() -> + start_link(), + parse("Token: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\nContent-Type: text\nContent-Length: 9\n\nHELLOWORLDToken: %s\nServer-Command: define\n"), + to_dictionary(["Hello: world", "Hi: there!"], []). +%% END TESTS + %% API Functions parse(Data) -> - Parsed = do_parse(Data, []), - prettify(Parsed). + lists:foreach(fun(X) -> + gen_fsm:sync_send_event(?SERVER, {char, X}) + end, Data). + + +start_link() -> + gen_fsm:start_link({local, ?SERVER}, ?MODULE, [], []). + +% Start state: {[""],0}, meaning: +% - Start with no strings parsed +% - Start with a data-section-lengths of 0 +init([]) -> + {ok,expect_headers,{[""], 0}}. + + getToken(Parsed) -> + case lists:keyfind(token, 1, Parsed) of {_, Value} -> Value; @@ -14,6 +52,8 @@ getToken(Parsed) -> false end. +%% Assemble a message which can b +%e used as a reply to a client create_message(Cmd, Enc, Acc, Data) -> Length = integer_to_list(string:len(Data)), Msg = "Client-Command: " ++ Cmd ++ "\n" ++ @@ -25,45 +65,64 @@ create_message(Cmd, Enc, Acc, Data) -> Data, Msg. +%%% Transitions +expect_headers(_Event, State) -> + {next_state, expect_headers, State}. +expect_data_section(_Event, State) -> + {next_state, expect_data_section, State}. -%% Internal helpers -do_parse(Data, ParsedMessage) -> - NewLinePos = string:chr(Data, $\n), - Line = string:substr(Data, 1, NewLinePos-1), - Tokens = re:split(Line, ": ", [{return, list}]), - case handle(Tokens) of - {Command, more} -> - do_parse(string:substr(Data, NewLinePos+1), ParsedMessage ++ [Command]); - {separator, data_next} -> - {_, Value} = lists:keyfind(content_len, 1, ParsedMessage), - {ContentLength, []} = string:to_integer(Value), - {data, ArgumentData} = handle_data(string:substr(Data, NewLinePos+1), ContentLength), - {ParsedMessage, ArgumentData} + +%%% End transitions +expect_headers({char, $\n}, _From, {Strings, Remains}) -> + [LastMessage|_] = Strings, + case LastMessage of + "" -> % We have a data section.. Last line should thus be the content length. + [LastMessage, SecondLast | _] = Strings, + case re:split(SecondLast, ": ", [{return, list}]) of + ["Content-Length", X] -> + {Int,_} = string:to_integer(X), + {reply, ok, expect_data_section, {[""|Strings], Int}}; + Other -> ok + end; + Other -> + {reply,ok,expect_headers, {[""|Strings], Remains}} + end; + +expect_headers({char, Char}, _From, {[Current|Rest], Remains}) -> + NewCurrent = Current ++ [Char], + {reply,ok, expect_headers, {[NewCurrent|Rest], Remains}}. + + +expect_data_section({char, Char}, From, {Strings, Remains}) -> + case Remains of + 0 -> + [LastMsg,_|Rest] = Strings, + NewMsg = LastMsg ++ [Char], + Result = [NewMsg|Rest], + {Pid,_} = From, + erlang:display(From), + Pid ! (prettify(to_dictionary(Rest, []), NewMsg)), + {reply, ok, expect_headers, {[""], 0}}; + Other -> [LastMsg|Rest] = Strings, + NewMsg = LastMsg ++ [Char], + {reply, ok, expect_data_section, {[NewMsg|Rest], Remains-1}} end. - -handle([[]]) -> - {separator, data_next}; -handle(["Server-Command", Param]) -> - {{srv_cmd, Param}, more}; -handle(["Game-Command", Param]) -> - {{game_cmd, Param}, more}; -handle(["Content-Length", Param]) -> - {{content_len, Param}, more}; -handle(["Token", Param]) -> - {{token, Param}, more}; -handle(["Content-Type", Param]) -> - {{content_type, Param}, more}. - -handle_data(Data, Length) -> - {data, string:substr(Data,1,Length)}. +handle_call(_Msg, _From, State) -> + {noreply, State}. +handle_info(_Msg, State) -> + {noreply, State}. +terminate(_Reason, _State) -> + ok. +code_change(_OldVsn, State, _Extra) -> + {ok, State}. -prettify({Args, Data}) -> - case lists:keyfind(srv_cmd, 1, Args) of +prettify(Args, Data) -> + case lists:keyfind("Server-Command", 1, Args) of {_, Value} -> {srv_cmd, Value, Args, Data}; _Other -> - case lists:keyfind(game_cmd, 1, Args) of + case lists:keyfind("Game-Command", 1, Args) of {_, Value} -> {game_cmd, Value, Args, Data}; _ -> @@ -71,3 +130,9 @@ prettify({Args, Data}) -> end end. +to_dictionary([], Dict) -> + Dict; +to_dictionary([S|Strings], Dict) -> + [First, Snd] = re:split(S, ": ", [{return, list}]), + to_dictionary(Strings, [{First, Snd}|Dict]). + diff --git a/src/ggs_table.erl b/src/ggs_table.erl index 909f789..7cdef80 100644 --- a/src/ggs_table.erl +++ b/src/ggs_table.erl @@ -74,7 +74,7 @@ send_command(TableToken, PlayerToken, Message) -> %% @private init([TableToken]) -> process_flag(trap_exit, true), - GameVM = ggs_gamevm_p:start_link(TableToken), + GameVM = ggs_gamevm_e:start_link(TableToken), {ok, #state { game_vm = GameVM, players = [] }}. @@ -100,14 +100,14 @@ handle_cast({notify, Player, Message}, #state { game_vm = GameVM } = State) -> PlayerToken = ggs_coordinator:player_pid_to_token(Player), case Message of {server, define, Args} -> - ggs_gamevm_p:define(GameVM, Args); + ggs_gamevm_e:define(GameVM, Args); {game, Command, Args} -> - ggs_gamevm_p:player_command(GameVM, PlayerToken, Command, Args) + ggs_gamevm_e:player_command(GameVM, PlayerToken, Command, Args) end, {noreply, State}; handle_cast({notify_game, Message, From}, #state { game_vm = GameVM } = State) -> - ggs_gamevm_p:player_command(GameVM, From, Message, ""), + ggs_gamevm_e:player_command(GameVM, From, Message, ""), {noreply, State}; handle_cast({notify_all_players, Message}, #state{players = Players} = State) ->