%% A remote spawns an Erlang port running the 'remote' program from %% p11-kit. %% Receive p11 requests from p11p_server, forward them to the remote, %% wait for a reply. If a reply is received within a timeout period, %% forward the reply to the requesting p11p_server. If the request %% times out, inform the remote manager (our parent). %% TODO: "remote" is not a great name and we shouldn't just inherit it %% from p11p-kit -module(p11p_remote). -behaviour(gen_server). %% API. -export([start_link/3]). -export([send/3]). %% Genserver callbacks. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). %% Records and types. -record(state, { port :: port(), replyto :: pid() | undefined, timer :: reference() | undefined, token :: string() }). %% FIXME: move to config -define(P11KITREMOTE_PATH, "/home/linus/usr/libexec/p11-kit/p11-kit-remote"). %% API. -spec start_link(atom(), string(), string()) -> {ok, pid()} | {error, term()}. start_link(ServName, TokName, ModPath) -> lager:info("~p: p11p_remote starting for ~s", [ServName, ModPath]), gen_server:start_link({local, ServName}, ?MODULE, [TokName, ModPath], []). -spec send(pid(), pid(), binary()) -> ok. send(From, Remote, Data) -> gen_server:cast(Remote, {send, From, Data}). %% Genserver callbacks. init([TokName, ModPath]) -> Port = open_port({spawn_executable, ?P11KITREMOTE_PATH}, [stream, exit_status, {args, [ModPath, "-v"]}]), lager:debug("~p: ~s: New port: ~p", [self(), ?P11KITREMOTE_PATH, Port]), {ok, #state{port = Port, token = TokName}}. handle_call(Request, _From, State) -> lager:debug("~p: Unhandled call: ~p~n", [self(), Request]), {reply, unhandled, State}. handle_cast({send, From, Data}, #state{port = Port} = State) -> lager:debug("~p: sending request to remote ~p", [self(), Port]), port_command(Port, Data), Timer = erlang:start_timer(3000, self(), Port), NewState = State#state{replyto = From, timer = Timer}, {noreply, NewState}; handle_cast(Request, State) -> lager:debug("~p: Unhandled cast: ~p~n", [self(), Request]), {noreply, State}. handle_info({Port, {data, Data}}, #state{replyto = Pid, timer = Timer} = State) -> if Port == State#state.port -> erlang:cancel_timer(Timer, [{async, true}, {info, false}]), p11p_server:reply(Pid, Data); true -> lager:debug("~p: data from unknown port ~p", [self(), Port]) end, {noreply, State}; handle_info({timeout, Timer, Port}, #state{token = TokName} = State) -> NewState = if Port == State#state.port andalso Timer == State#state.timer -> p11p_remote_manager:timeout(TokName), State#state{timer = undefined}; true -> lager:debug("~p: unknown timer ~p fired for port ~p", [self(), Timer, Port]), State end, {noreply, NewState}; handle_info(Info, State) -> lager:debug("~p: Unhandled info: ~p~n", [self(), Info]), {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVersion, State, _Extra) -> {ok, State}.