Il modulo p2p

La classe PartecipantNode rappresenta un potenziale nodo partecipante. Ha l'attributo partecipant che dice se effettivamente lo è (in un dato servizio P2P, cioè in una data MapP2P)

La classe MapP2P eredita dalla classe Map e ha come dataclass la PartecipantNode.
Il suo attributo pid viene specificato al momento della creazione, nel costruttore. E' l'ID del servizio P2P che questa mappa rappresenta.
Il metodo partecipate si segna che il nodo partecipa. Memorizza se stesso come partecipante all'interno della mappa.
Il metodo node_del viene overridato dalla classe base Map solo per rendere tale metodo una microfunc. Sarà dichiarato gestore dell'evento NODE_DELETED della Maproute del nodo.
Il metodo me_changed sostituisce il me_change della classe base Map. Rende tale metodo una microfunc con dispatcher. Ha un nome diverso solo per accettare anche un altro parametro, il old_me, che non userà; questo per poterlo dichiarare come gestore dell'evento ME_CHANGED della Maproute del nodo.

La classe P2P

La classe P2P è la base dalla quale erediteranno tutte le classi che implementano un servizio p2p.

Eredita a sua volta dalla classe RPCDispatcher (vedi il modulo RPC). Viene istanziata passando le istanze di Radar, Maproute, e il PID (P2p ID) del servizio.
Nel suo costruttore si istanzia la MapP2P con le stesse dimensioni della Maproute e con il mio stesso NIP. Inoltre registra dei gestori (i metodi node_del e me_changed) per gli eventi ME_CHANGED e NODE_DELETED della Maproute. In questo modo si incarica di tenere sincronizzate queste mappe.
Ha un attributo partecipant che valorizza inizialmente a False.
Dichiara metodi remotable il partecipant_add e il msg_send.
Essendo una derivata della classe RPCDispatcher richiama il suo costruttore indicando che la sua root_instance è se stessa (self).

Il metodo h serve per ricavare da una generica chiave un indirizzo NIP.
Nella classe base P2P la chiave è lo stesso NIP che viene restituito. Si tratta di una implementazione ovviamente poco utile.
Il metodo deve essere quindi ridefinito nel modulo che implementerà un certo servizio, con un algoritmo che partendo da una chiave significativa per quel servizio, restituisca un NIP.
Per esempio nel modulo ANDNA la chiave sarà la stringa hostname, per poter poi trovare il nip del register node di quell'hostname; nel modulo Coord la chiave sarà una tupla (lvl, ip), per poter trovare il nip del coordinator node per il gnodo di livello lvl di quel ip; e così via.

Il NIP ottenuto con il metodo h potrebbe indicare un nodo non presente o che non partecipa a questo servizio P2P.
A questo punto va quindi utilizzato il metodo H per ottenere un indirizzo NIP valido.
Vediamo come funziona il metodo H.
Supponiamo che il nostro nodo abbia IP 1.2.3.4 (self.mapp2p.me = [4,3,2,1]) e che vogliamo trovare un IP valido per il IP 1.2.12.34 (IP = [34,12,2,1])
Partendo dal livello l più alto cerco nella mia mappa (self.mapp2p) un gnodo di livello l che sia un partecipante attivo. Il più vicino possibile all'identificativo richiesto, cioè IP[l].
Partendo dal livello 3, in questo caso trovo di sicuro che il ID = 1 si presta bene, perché è lo stesso mio gnodo di livello 3 ed io sono un partecipante. Quindi passo al livello 2. Anche qui trovo che ID = 2 si presta bene, per lo stesso motivo.
Arrivato al livello 1 cerco un ID vicino a IP[1], che è 12. Posso vedere nella mia mappa se il gnodo 12, all'interno del ggnodo 2, all'interno del gggnodo 1, è partecipante al servizio. Se non lo è provo con 11, poi con 13, poi con 10, poi con 14, e così via.
Se arrivo ad un gnodo partecipante, mettiamo ad esempio 5, che è diverso da quello a cui appartengo io (self.mapp2p.me[1] = 3) allora non posso scendere ad un dettaglio più basso, perché nella mia mappa non ho informazioni sui nodi di quel gnodo. Quindi interrompo qui.
In questo caso, quindi, il valore ritornato da H([34,12,2,1]) sarà la lista [None,5,2,1]. Il valore None all'indice 0 mi dice che non ho dettagli a quel livello. Il gnodo a cui chiedere qualcosa per il servizio in questione è un gnodo di livello 1, esattamente 1.2.5.

Il metodo partecipate (oltre a memorizzarlo sulla p2pmap) richiama sui vari vicini il metodo p2p.partecipant_add (vedi il modulo RPC).

Il metodo p2p.partecipant_add (richiamato da un nodo remoto) si segna sulla mappa (se già non lo sapeva) che quel nodo partecipa, e propaga l'informazione ai vicini.

Il metodo peer(hip,key) ritorna una istanza di FakeRmt (vedi il modulo RPC) quindi si può usare la sintassi astratta per eseguire il metodo rmt; questo viene overridato per chiamare il metodo msg_send;
il metodo msg_send invia un messaggio ad una destinazione (tramite ntkd... duplicazione?) e ne riceve risposta. La destinazione lo processa con il metodo msg_exec, che lo dispaccia ad una funzione remotable.

Il metodo peer si può richiamare con uno o l'altro degli identificatori del peer. Ad esempio il servizio Coordinator (vedi il modulo coord) si usa così:
   co2 = self.coordnode.peer(key = (lvl+1, newnip))
   newnip = co2.going_in(lvl)
   co2.close()

Il metodo neigh_get prende in input un ip per restituire un'istanza di Neigh relativa ad un nodo (vedi il modulo Radar); in questa istanza è presente l'attributo ntkd che può essere utilizzato per chiamare i metodi remoti (ntkd in realtà è un'istanza di TCPClient, vedere nel modulo RPC).

La classe P2PAll gestisce tutti i servizi p2p registrati.
Quando riceve dalla classe Hook il segnale HOOKED richiama il metodo p2p_hook. Questo riechiede al vicino "più vicino" (come gnodo) la mappa dei servizi noti e ne fa il merge con la propria. Poi esegue il P2P.participate su quelli che gli interessano. Infine emette il segnale P2P_HOOKED.