defmodule SApp.Identity do use GenServer require Logger defmodule Manifest do defstruct [:pk] end defmodule State do defstruct [:info, :rev, :signed] end defimpl Shard.Manifest, for: Manifest do def start(m) do DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Identity, m.pk}) end end def start_link(pk) do GenServer.start_link(__MODULE__, pk) end def init(pk) do manifest = %Manifest{pk: pk} id = SData.term_hash manifest case Shard.Manager.register(id, manifest, self()) do :ok -> Shard.Manager.dispatch_to(id, nil, self()) state = case Shard.Manager.load_state(id) do nil -> info = %{nick: default_nick(pk)} SData.SignRev.new info st -> st end netgroup = %SNet.PubShardGroup{id: id} SNet.Group.init_lookup(netgroup, self()) if Shard.Keys.have_sk? pk do GenServer.cast(self(), :update_peer_info) end {:ok, %{pk: pk, id: id, state: state, netgroup: netgroup}} :redundant -> exit(:redundant) end end def handle_call(:manifest, _from, state) do {:reply, state.manifest, state} end def handle_call(:get_info, _from, state) do {:reply, SData.SignRev.get(state.state), state} end def handle_call({:set_info, new_info}, _from, state) do if Shard.Keys.have_sk?(state.pk) do {:ok, st2} = SData.SignRev.set(state.state, new_info, state.pk) Shard.Manager.save_state(state.id, st2) state = put_in(state.state, st2) bcast_state(state) {:reply, :ok, state} else {:reply, :impossible, state} end end def handle_cast({:peer_connected, peer_pid}, state) do GenServer.cast(peer_pid, {:send_msg, {:interested, [state.id]}}) {:noreply, state} end def handle_cast({:interested, peer_pid, _auth}, state) do SNet.Manager.send_pid(peer_pid, {state.id, nil, {:update, SData.SignRev.signed(state.state), true}}) {:noreply, state} end def handle_cast({:msg, conn_pid, _auth, _shard_id, nil, msg}, state) do state = case msg do {:update, signed, ask_reply} when signed != nil -> state = case SData.SignRev.merge(state.state, signed, state.pk) do {true, st2} -> Shard.Manager.save_state(state.id, st2) state = put_in(state.state, st2) bcast_state(state, [conn_pid]) state {false, _} -> state end if ask_reply do SNet.Manager.send_pid(conn_pid, {state.id, nil, {:update, SData.SignRev.signed(state.state), false}}) end state _ -> state end {:noreply, state} end def handle_cast(:update_peer_info, state) do peer_info = SNet.Addr.get_all_inet4() |> Enum.map(&({:inet, &1, Application.get_env(:shard, :port)})) prev_info = SData.SignRev.get(state.state) # TODO multi peer info new_info = Map.put(prev_info, :peer_info, peer_info) {:ok, st2} = SData.SignRev.set(state.state, new_info, state.pk) Shard.Manager.save_state(state.id, st2) state = put_in(state.state, st2) bcast_state(state) {:noreply, state} end defp bcast_state(state, _exclude \\ []) do # TODO: effectively apply exclude list SNet.Group.broadcast(state.netgroup, {state.id, nil, {:update, SData.SignRev.signed(state.state), false}}) end # ================ # PUBLIC INTERFACE # ================ @doc""" Return the default nickname associated to a pk, in the form "Anonxxxxxxxx" with some bytes of the pk in hex. """ def default_nick(pk) do nick_suffix = Shard.Keys.pk_display pk "Anon" <> nick_suffix end @doc""" Find the shard process for an identity. Launches such a process if necessary. """ def find_proc(pk) do Shard.Manager.find_or_start %Manifest{pk: pk} end @doc""" Get a user's nickname from his pk """ def get_nick(pk) do pid = find_proc pk info = GenServer.call(pid, :get_info) info.nick end end