aboutsummaryrefslogblamecommitdiff
path: root/shard/lib/app/identity.ex
blob: 59a4b9015415ca7128e817889d4b7e36c9961412 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                          














                                                                                     




                       


                                                                              
 
                   
 

                                       


                             


       
                    
                                           

     

                                              

     

                                

                                 











                                                
       
                                                                   

     
                                             
                                   






                                                         







                                                                     
       

     
                                       







                                                                                    
 
                                                              


                     




                                                                    
                                                           
                                                                                                        
                     

     
                                                                         
                       

                                                                          


                                                   
                                          



                       



                                                                                                               
                



                     














                                                                               


                                                                              
     






















                                                                 













                                                                                            


                                   
                               
     
   
defmodule SApp.Identity do
  @moduledoc"""
  Shard application for keeping state associated with a user's identity.

  Current functionality:

  - nickname
  - peer info: ip, port to connect to if we want a secure connection with this person
    (used for private chat)

  Future functionnality:

  - friend list
  - notifications & invites
  """

  use GenServer

  require Logger

  defmodule Manifest do
    @moduledoc"""
    Manifest for a user identity shard, defined by the public key of the user.
    """

    defstruct [:pk]

    defimpl Shard.Manifest do
      def module(_m), do: SApp.Identity
      def is_valid?(m) do
        byte_size(m.pk) == 32
      end
    end
  end

  defmodule State do
    defstruct [:pk, :id, :state, :netgroup]
  end

  def start_link(manifest) do
    GenServer.start_link(__MODULE__, manifest)
  end

  def init(manifest) do
    %Manifest{pk: pk} = manifest
    id = SData.term_hash manifest

    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, %State{pk: pk, id: id, state: state, netgroup: netgroup}}
  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(:send_deps, state) do
    deps = if Shard.Keys.have_sk?(state.pk) do
      [
        %SApp.Directory.Manifest{owner: state.pk, public: true, name: "collection"},
        %SApp.Directory.Manifest{owner: state.pk, public: false, name: "collection"}
      ]
    else
      []
    end

    GenServer.cast(Shard.Manager, {:dep_list, state.id, deps})
    {:noreply, state}
  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
    msg = {state.id, nil, {:update, SData.SignRev.signed(state.state), false}}
    SNet.Group.broadcast(state.netgroup, msg, exclude_pid: exclude)
  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 the info dict of an identity shard. The pid of the shard must be given as an argument.
  """
  def get_info(pid) do
    GenServer.call(pid, :get_info)
  end

  @doc"""
  Set the info dict of an identity shard.
  """
  def set_info(pid, new_info) do
    GenServer.call(pid, {:set_info, new_info})
  end

  @doc"""
  Get a user's nickname from his pk
  """
  def get_nick(pk) do
    get_info(find_proc pk).nick
  end
end