%%% Copyright (c) 2019, Sunet. %%% See LICENSE for licensing information. %% Create an AF_UNIX socket and accept connections. On connect, spawn %% another p11p_server process. -module(p11p_server). -behaviour(gen_server). -include("p11p_rpc.hrl"). %% API. -export([start_link/1]). -export([reply/2, token_gone/2]). %% Genserver callbacks. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% Records and types. -record(state, { tokname :: string(), client :: pid() | undefined, socket :: gen_tcp:socket(), req_in :: p11rpc_msg() | undefined, req_out :: p11rpc_msg() | undefined, recv_count = 0 :: non_neg_integer(), % received from app send_count = 0 :: non_neg_integer() % sent to token }). %% API. -spec start_link(gen_tcp:socket()) -> {ok, pid()} | {error, term()}. start_link(Args) -> gen_server:start_link(?MODULE, Args, []). -spec reply(pid(), p11rpc_msg()) -> {ok, non_neg_integer()}. reply(Pid, Response) -> gen_server:call(Pid, {respond, Response}). -spec token_gone(pid(), boolean()) -> ok. token_gone(Pid, Hangup) -> case process_info(Pid) of undefined -> error(bad_server_pid); _ -> nop end, gen_server:cast(Pid, {token_gone, Hangup}). %% Genserver callbacks. init([Token, Socket]) -> lager:debug("~p: p11p_server starting for ~s", [self(), Token]), process_flag(trap_exit, true), % Need terminate/2. gen_server:cast(self(), accept), % Invoke accept, returning a socket in state. {ok, #state{tokname = Token, socket = Socket}}. %% FIXME: make this a cast handle_call({respond, Resp}, _, State = #state{send_count = Sent}) -> N = send_response(State#state.socket, p11p_rpc:serialise(Resp), Sent), {reply, {ok, N}, State#state{req_out = undefined, send_count = Sent + 1}}; handle_call(Call, _, S) -> lager:debug("~p: Unhandled call: ~p~n", [self(), Call]), {reply, unhandled, S}. %% Wait for new connection. handle_cast(accept, State = #state{tokname = TokName, socket = ListenSocket}) -> %% Blocking until client connects or timeout fires. %% Without a timeout our supervisor cannot terminate us. %% On timeout, just invoke ourselves again. case gen_tcp:accept(ListenSocket, 900) of {ok, Sock} -> lager:debug("~p: ~p: new connection accepted", [self(), Sock]), %% Start a new acceptor and return with new socket in state. p11p_server_sup:start_server([TokName, ListenSocket]), {noreply, State#state{socket = Sock}}; {error, timeout} -> gen_server:cast(self(), accept), {noreply, State}; {error, closed} -> lager:debug("~p: listening socket closed", [self()]), {stop, normal, State} end; handle_cast({token_gone, Hangup}, State = #state{send_count = Sent}) -> Resp = p11p_rpc:error(State#state.req_out#p11rpc_msg.call_code, ?CKR_DEVICE_ERROR), {ok, _} = send_response(State#state.socket, p11p_rpc:serialise(Resp), Sent), NewState = State#state{client = undefined, req_out = undefined, send_count = Sent + 1}, case Hangup of true -> {close, NewState}; false -> {noreply, NewState} end; handle_cast(Cast, State) -> lager:debug("~p: Unhandled cast: ~p~n", [self(), Cast]), {noreply, State}. %% First packet from P11 client. handle_info({tcp, Port, DataIn}, #state{tokname = TokName} = S) when S#state.client == undefined -> %%lager:debug("~p: received ~B octets from client on socket ~p, from new client", [self(), size(Data), Port]), <> = DataIn, case RPCVersion of ?RPC_VERSION -> {noreply, p11_app_data( S#state{client = p11p_manager:client_for_token(TokName)}, p11p_rpc:new(), Data)}; BadVersion -> lager:info("~p: ~p: invalid RPC version: ~p", [self(), Port, BadVersion]), {stop, bad_proto, S} end; %% Subsequent packages from P11 client. handle_info({tcp, _Port, DataIn}, #state{req_in = Msg} = S) -> %%lager:debug("~p: received ~B octets from client on socket ~p, with ~B octets already in buffer", [self(), size(Data), Port, size(Msg#p11rpc_msg.buffer)]), {noreply, p11_app_data(S, Msg, DataIn)}; handle_info({tcp_closed, Port}, S) -> lager:debug("~p: socket ~p closed", [self(), Port]), {stop, normal, S}; handle_info(Info, S) -> lager:debug("~p: Unhandled info: ~p~n", [self(), Info]), {noreply, S}. terminate(Reason, #state{socket = Sock, tokname = TokName}) -> ok = gen_tcp:close(Sock), %% FIXME: tell manager, so that the client can be stopped. we %% don't want to risk that another app (socket client) uses it p11p_manager:server_event(server_gone, TokName), lager:debug("~p: terminated with reason ~p", [self(), Reason]), ignored. code_change(_OldVersion, State, _Extra) -> {ok, State}. %% Private functions. p11_app_data(#state{client = Client, recv_count = Recv} = S, MsgIn, DataIn) -> case p11p_rpc:parse(MsgIn, DataIn) of {needmore, Msg} -> S#state{req_in = Msg}; {done, Msg} -> lager:debug("~p: -> ~s", [self(), p11p_rpc:dump(Msg)]), {ok, _BytesSent} = p11p_client:request(Client, Msg), S#state{req_out = Msg, req_in = p11p_rpc:new(Msg#p11rpc_msg.buffer), recv_count = Recv + 1} end. send_response(Sock, Inbuf, Sent) -> Buf = case Sent of 0 -> <>; _ -> Inbuf end, %%lager:debug("~p: sending ~B octets as response", [self(), size(Inbuf)]), ok = gen_tcp:send(Sock, Buf), {ok, size(Inbuf)}.