p2p/channels/signaling/discovery/
p2p_channels_signaling_discovery_actions.rs

1use openmina_core::ActionEvent;
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    channels::P2pChannelsAction,
6    connection::P2pConnectionResponse,
7    identity::PublicKey,
8    webrtc::{EncryptedAnswer, EncryptedOffer, Offer},
9    P2pState, PeerId,
10};
11
12use super::{P2pChannelsSignalingDiscoveryState, SignalingDiscoveryState};
13
14#[derive(Debug, Clone, Serialize, Deserialize, ActionEvent)]
15#[action_event(fields(display(peer_id)))]
16pub enum P2pChannelsSignalingDiscoveryAction {
17    /// Initialize channel.
18    Init {
19        peer_id: PeerId,
20    },
21    Pending {
22        peer_id: PeerId,
23    },
24    /// Channel is ready.
25    Ready {
26        peer_id: PeerId,
27    },
28    /// Send request to get next peer discovery request from peer.
29    RequestSend {
30        peer_id: PeerId,
31    },
32    DiscoveryRequestReceived {
33        peer_id: PeerId,
34    },
35    DiscoveredSend {
36        peer_id: PeerId,
37        target_public_key: PublicKey,
38    },
39    DiscoveredRejectReceived {
40        peer_id: PeerId,
41    },
42    DiscoveredAcceptReceived {
43        peer_id: PeerId,
44        offer: EncryptedOffer,
45    },
46    AnswerSend {
47        peer_id: PeerId,
48        answer: Option<EncryptedAnswer>,
49    },
50    /// Received request to get next peer discovery request from us.
51    RequestReceived {
52        peer_id: PeerId,
53    },
54    DiscoveryRequestSend {
55        peer_id: PeerId,
56    },
57    DiscoveredReceived {
58        peer_id: PeerId,
59        target_public_key: PublicKey,
60    },
61    DiscoveredReject {
62        peer_id: PeerId,
63    },
64    DiscoveredAccept {
65        peer_id: PeerId,
66        offer: Box<Offer>,
67    },
68    AnswerReceived {
69        peer_id: PeerId,
70        answer: Option<EncryptedAnswer>,
71    },
72    AnswerDecrypted {
73        peer_id: PeerId,
74        answer: P2pConnectionResponse,
75    },
76}
77
78impl P2pChannelsSignalingDiscoveryAction {
79    pub fn peer_id(&self) -> &PeerId {
80        match self {
81            Self::Init { peer_id }
82            | Self::Pending { peer_id }
83            | Self::Ready { peer_id }
84            | Self::RequestSend { peer_id }
85            | Self::DiscoveryRequestReceived { peer_id }
86            | Self::DiscoveredSend { peer_id, .. }
87            | Self::DiscoveredRejectReceived { peer_id }
88            | Self::DiscoveredAcceptReceived { peer_id, .. }
89            | Self::AnswerSend { peer_id, .. }
90            | Self::RequestReceived { peer_id }
91            | Self::DiscoveryRequestSend { peer_id, .. }
92            | Self::DiscoveredReceived { peer_id, .. }
93            | Self::DiscoveredReject { peer_id, .. }
94            | Self::DiscoveredAccept { peer_id, .. }
95            | Self::AnswerReceived { peer_id, .. }
96            | Self::AnswerDecrypted { peer_id, .. } => peer_id,
97        }
98    }
99}
100
101impl redux::EnablingCondition<P2pState> for P2pChannelsSignalingDiscoveryAction {
102    fn is_enabled(&self, state: &P2pState, now: redux::Timestamp) -> bool {
103        match self {
104            P2pChannelsSignalingDiscoveryAction::Init { peer_id } => {
105                state.get_ready_peer(peer_id).is_some_and(|p| {
106                    matches!(
107                        &p.channels.signaling.discovery,
108                        P2pChannelsSignalingDiscoveryState::Enabled
109                    )
110                })
111            }
112            P2pChannelsSignalingDiscoveryAction::Pending { peer_id } => {
113                state.get_ready_peer(peer_id).is_some_and(|p| {
114                    matches!(
115                        &p.channels.signaling.discovery,
116                        P2pChannelsSignalingDiscoveryState::Init { .. }
117                    )
118                })
119            }
120            P2pChannelsSignalingDiscoveryAction::Ready { peer_id } => {
121                state.get_ready_peer(peer_id).is_some_and(|p| {
122                    matches!(
123                        &p.channels.signaling.discovery,
124                        P2pChannelsSignalingDiscoveryState::Pending { .. }
125                    )
126                })
127            }
128            P2pChannelsSignalingDiscoveryAction::RequestSend { peer_id } => {
129                state.get_ready_peer(peer_id).is_some_and(|p| {
130                    match &p.channels.signaling.discovery {
131                        P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
132                            match local {
133                                SignalingDiscoveryState::WaitingForRequest { .. } => true,
134                                SignalingDiscoveryState::DiscoveredRejected { time, .. }
135                                | SignalingDiscoveryState::Answered { time, .. } => {
136                                    // Allow one discovery request per minute.
137                                    // TODO(binier): make configurable
138                                    now.checked_sub(*time)
139                                        .is_some_and(|dur| dur.as_secs() >= 60)
140                                }
141                                _ => false,
142                            }
143                        }
144                        _ => false,
145                    }
146                })
147            }
148            P2pChannelsSignalingDiscoveryAction::DiscoveryRequestReceived { peer_id, .. } => state
149                .get_ready_peer(peer_id)
150                .is_some_and(|p| match &p.channels.signaling.discovery {
151                    P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
152                        matches!(local, SignalingDiscoveryState::Requested { .. })
153                    }
154                    _ => false,
155                }),
156            P2pChannelsSignalingDiscoveryAction::DiscoveredSend {
157                peer_id,
158                target_public_key,
159                ..
160            } => {
161                let target_peer_id = target_public_key.peer_id();
162                let has_peer_requested_discovery = state.get_ready_peer(peer_id).is_some_and(|p| {
163                    match &p.channels.signaling.discovery {
164                        P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
165                            matches!(local, SignalingDiscoveryState::DiscoveryRequested { .. })
166                        }
167                        _ => false,
168                    }
169                });
170                let target_peer_already_discovering_them =
171                    state.get_ready_peer(&target_peer_id).is_some_and(|p| {
172                        p.channels.signaling.sent_discovered_peer_id() == Some(*peer_id)
173                    });
174                has_peer_requested_discovery
175                    && !target_peer_already_discovering_them
176                    && state.ready_peers_iter().all(|(_, p)| {
177                        p.channels.signaling.sent_discovered_peer_id() != Some(target_peer_id)
178                    })
179            }
180            P2pChannelsSignalingDiscoveryAction::DiscoveredRejectReceived { peer_id, .. } => state
181                .get_ready_peer(peer_id)
182                .is_some_and(|p| match &p.channels.signaling.discovery {
183                    P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
184                        matches!(local, SignalingDiscoveryState::Discovered { .. })
185                    }
186                    _ => false,
187                }),
188            P2pChannelsSignalingDiscoveryAction::DiscoveredAcceptReceived { peer_id, .. } => state
189                .get_ready_peer(peer_id)
190                .is_some_and(|p| match &p.channels.signaling.discovery {
191                    P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
192                        matches!(local, SignalingDiscoveryState::Discovered { .. })
193                    }
194                    _ => false,
195                }),
196            P2pChannelsSignalingDiscoveryAction::AnswerSend { peer_id, .. } => state
197                .get_ready_peer(peer_id)
198                .is_some_and(|p| match &p.channels.signaling.discovery {
199                    P2pChannelsSignalingDiscoveryState::Ready { local, .. } => {
200                        matches!(local, SignalingDiscoveryState::DiscoveredAccepted { .. })
201                    }
202                    _ => false,
203                }),
204            P2pChannelsSignalingDiscoveryAction::RequestReceived { peer_id } => state
205                .get_ready_peer(peer_id)
206                .is_some_and(|p| match &p.channels.signaling.discovery {
207                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => matches!(
208                        remote,
209                        SignalingDiscoveryState::WaitingForRequest { .. }
210                            | SignalingDiscoveryState::DiscoveredRejected { .. }
211                            | SignalingDiscoveryState::Answered { .. }
212                    ),
213                    _ => false,
214                }),
215            // TODO(binier): constrain interval between these requests
216            // to handle malicious peers.
217            P2pChannelsSignalingDiscoveryAction::DiscoveryRequestSend { peer_id, .. } => {
218                !state.already_has_min_peers()
219                    && state.get_ready_peer(peer_id).is_some_and(|p| {
220                        match &p.channels.signaling.discovery {
221                            P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
222                                matches!(remote, SignalingDiscoveryState::Requested { .. })
223                            }
224                            _ => false,
225                        }
226                    })
227            }
228            P2pChannelsSignalingDiscoveryAction::DiscoveredReceived { peer_id, .. } => state
229                .get_ready_peer(peer_id)
230                .is_some_and(|p| match &p.channels.signaling.discovery {
231                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
232                        matches!(remote, SignalingDiscoveryState::DiscoveryRequested { .. })
233                    }
234                    _ => false,
235                }),
236            P2pChannelsSignalingDiscoveryAction::DiscoveredReject { peer_id, .. } => state
237                .get_ready_peer(peer_id)
238                .is_some_and(|p| match &p.channels.signaling.discovery {
239                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
240                        matches!(remote, SignalingDiscoveryState::Discovered { .. })
241                    }
242                    _ => false,
243                }),
244            P2pChannelsSignalingDiscoveryAction::DiscoveredAccept { peer_id, .. } => state
245                .get_ready_peer(peer_id)
246                .is_some_and(|p| match &p.channels.signaling.discovery {
247                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
248                        matches!(remote, SignalingDiscoveryState::Discovered { .. })
249                    }
250                    _ => false,
251                }),
252            P2pChannelsSignalingDiscoveryAction::AnswerReceived { peer_id, .. } => state
253                .get_ready_peer(peer_id)
254                .is_some_and(|p| match &p.channels.signaling.discovery {
255                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
256                        matches!(remote, SignalingDiscoveryState::DiscoveredAccepted { .. })
257                    }
258                    _ => false,
259                }),
260            P2pChannelsSignalingDiscoveryAction::AnswerDecrypted { peer_id, .. } => state
261                .get_ready_peer(peer_id)
262                .is_some_and(|p| match &p.channels.signaling.discovery {
263                    P2pChannelsSignalingDiscoveryState::Ready { remote, .. } => {
264                        matches!(remote, SignalingDiscoveryState::DiscoveredAccepted { .. })
265                    }
266                    _ => false,
267                }),
268        }
269    }
270}
271
272impl From<P2pChannelsSignalingDiscoveryAction> for crate::P2pAction {
273    fn from(action: P2pChannelsSignalingDiscoveryAction) -> Self {
274        Self::Channels(P2pChannelsAction::SignalingDiscovery(action))
275    }
276}