aboutsummaryrefslogblamecommitdiff
path: root/shard/lib/app/identity.ex
blob: 255bebbfbbb9eb47917e5325a91fb228ec4a7f41 (plain) (tree)




































                                                                                   





                                                                  





                         
                                          





                                 

                                      

                                                  

                

     
                     


                                         

     
                                             
                                   






                                                         







                                                                     
       

     
                                                           
                                                                                                  
                     

     
                                                                         
                       
                                             



                                                                  
                                          



                       
                



                     

















                                                                                                       

     
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 default_nick(pk) do
    nick_suffix = Shard.Keys.pk_display pk
    "Anon" <> nick_suffix
  end

  def find_proc(pk) do
    manifest = %Manifest{pk: pk}
    id = SData.term_hash manifest
    case Shard.Manager.find_proc id do
      nil ->
        {:ok, pid} = Shard.Manifest.start manifest
        pid
      pid -> pid
    end
  end

  def get_nick(pk) do
    pid = find_proc pk
    info = GenServer.call(pid, :get_info)
    info.nick
  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({:interested, peer_pid, _auth}, state) do
    SNet.Manager.send_pid(peer_pid, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
    {:noreply, state}
  end

  def handle_cast({:msg, conn_pid, _auth, _shard_id, nil, msg}, state) do
    state = case msg do
      {:update, signed} when signed != nil ->
        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
      _ -> 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

  def bcast_state(state, _exclude \\ []) do
    # TODO: effectively apply exclude list
    SNet.Group.broadcast(state.netgroup, {state.id, nil, {:update, SData.SignRev.signed(state.state)}})
  end
end