aboutsummaryrefslogblamecommitdiff
path: root/shard/lib/net/group.ex
blob: f09d1742ffa781abc3945b2b008cc90a51b05568 (plain) (tree)





































                                                                                          
                                                           


                                                               

                                                                                






                                                       



                                                         










                                                          
                                                                    

                                  
                                                  












                                                                    




















                                                                                                  

       



                                                                                              


                                      






















                                                                                                 

       





                                                                        


       
defprotocol SNet.Group do
  @moduledoc"""
  A group is a specification of a bunch of peers we want and accept to talk to
  about some things. It supports a number of abstract operations for finding peers,
  broadcasting/gossiping, authenticating, etc.
  """

  @doc"""
  Find new peers for this group, open connections and notify us when connections are open.

  Launches background processes if necessary, returns immediately.
  """
  def init_lookup(group, notify_to) 

  @doc"""
  Get all currently open connections to peers in this group.
  """
  def get_connections(group)

  @doc"""
  Broadcast a message to peers of the group.
  Will send to at most nmax peers, so this is a good primitive for gossip.
  """
  def broadcast(group, msg, nmax \\ 10)

  @doc"""
  Check if a peer is allowed to participate in this group.
  """
  def in_group?(group, conn_pid, auth)
end

defmodule SNet.PubShardGroup do
  defstruct [:id]

  defimpl SNet.Group do
    def init_lookup(%SNet.PubShardGroup{id: id}, _notify_to) do
      # For now: ask all currently connected peers and connect to new peers we know of
      spawn fn ->
        for {_, pid, _} <- SNet.Manager.list_connections do
          GenServer.cast(pid, {:send_msg, {:interested, [id]}})
        end
        for peer_info <- Shard.Manager.get_shard_peers id do
          if SNet.Manager.get_connections_to peer_info == [] do
            SNet.Manager.add_peer(peer_info)      # TODO callback when connected
          end
        end
      end
      # TODO: use a DHT to find peers
    end

    def get_connections(%SNet.PubShardGroup{id: id}) do
      Shard.Manager.get_shard_peers(id)
      |> Enum.map(&(SNet.Manager.get_connections_to(&1)))
      |> Enum.filter(&(&1 != []))
      |> Enum.map(fn [{pid, _auth}|_] -> pid end)
    end

    def broadcast(group, msg, nmax) do
      %SNet.PubShardGroup{id: id} = group
      nsent = get_connections(group)
      |> Enum.shuffle
      |> Enum.take(nmax)
      |> Enum.map(&(GenServer.cast(&1, {:send_msg, msg})))
      |> Enum.count
      if nmax - nsent > 0 do
        Shard.Manager.get_shard_peers(id)
        |> Enum.filter(&(SNet.Manager.get_connections_to(&1) == []))
        |> Enum.shuffle
        |> Enum.take(nmax - nsent)
        |> Enum.map(&(SNet.Manager.send(&1, msg)))
      end
    end

    def in_group?(%SNet.PubShardGroup{id: _id}, _peer_pid, _auth) do
      true     # No access control
    end
  end
end

defmodule SNet.PrivGroup do
  defstruct [:pk_list]

  defimpl SNet.Group do
    def init_lookup(%SNet.PrivGroup{pk_list: pk_list}, notify_to) do
      spawn fn ->
        # 1. We might already have some connections to these guys
        for {_, pid, %SNet.Auth{my_pk: my_pk, his_pk: his_pk}} <- SNet.Manager.list_connections do
          if (my_pk in pk_list) and (his_pk in pk_list) do
            GenServer.cast(notify_to, {:peer_connected, pid})
          end
        end
        # 2. We might also want to open some new connections to these guys
        [my_pk|_] = Enum.filter(pk_list, &Shard.Keys.have_sk?/1)
        for pk <- pk_list do
          pid = SApp.Identity.find_proc(pk)
          info = GenServer.call(pid, :get_info)
          if Map.has_key?(info, :peer_info) do
            for pi <- info.peer_info do
              SNet.Manager.add_peer(pi, %SNet.Auth{my_pk: my_pk, his_pk: pk})
              # no callback here, we don't know if connect was successful
            end
          end
        end
      end
    end

    def get_connections(%SNet.PrivGroup{pk_list: pk_list}) do
      for {_, pid, %SNet.Auth{my_pk: my_pk, his_pk: his_pk}} <- SNet.Manager.list_connections,
        (my_pk in pk_list) and (his_pk in pk_list),
      do: pid
    end

    def broadcast(group, msg, nmax) do
      %SNet.PrivGroup{pk_list: pk_list} = group
      nsent = get_connections(group)
      |> Enum.shuffle
      |> Enum.take(nmax)
      |> Enum.map(&(GenServer.cast(&1, {:send_msg, msg})))
      |> Enum.count
      if nmax - nsent > 0 do
        my_pks = Enum.filter(pk_list, &Shard.Keys.have_sk?/1)
        [my_pk|_] = my_pks
        candidates = for pk <- pk_list,
          pid = SApp.Identity.find_proc(pk),
          info = GenServer.call(pid, :get_info),
          Map.has_key?(info, :peer_info),
          xx <- info.peer_info,
          do: {xx, pk}
        candidates
        |> Enum.filter(fn {peer_info, his_pk} ->
            SNet.Manager.get_auth_connections_to(peer_info, my_pks, his_pk) == [] end)
        |> Enum.shuffle()
        |> Enum.take(nmax - nsent)
        |> Enum.map(fn {peer_info, his_pk} ->
            SNet.Manager.send_auth(peer_info, %SNet.Auth{my_pk: my_pk, his_pk: his_pk}, msg) end)
      end
    end

    def in_group?(%SNet.PrivGroup{pk_list: pk_list}, _peer_pid, auth) do
      case auth do
        nil -> false
        %SNet.Auth{my_pk: my_pk, his_pk: his_pk} ->
          (my_pk in pk_list) and (his_pk in pk_list)
      end
    end
  end
end