1use openmina_core::ActionEvent;
2use redux::Callback;
3use serde::{Deserialize, Serialize};
4
5use openmina_core::requests::RpcId;
6
7use crate::{connection::P2pConnectionErrorResponse, webrtc, P2pState, PeerId};
8
9use super::{P2pConnectionOutgoingError, P2pConnectionOutgoingInitOpts};
10
11#[derive(Serialize, Deserialize, Debug, Clone, ActionEvent)]
12#[action_event(fields(display(opts), display(peer_id), display(error)))]
13pub enum P2pConnectionOutgoingAction {
14 #[action_event(level = trace)]
16 RandomInit,
17 Init {
19 opts: P2pConnectionOutgoingInitOpts,
20 rpc_id: Option<RpcId>,
21 on_success: Option<Callback<(PeerId, Option<RpcId>)>>,
22 },
23 Reconnect {
26 opts: P2pConnectionOutgoingInitOpts,
27 rpc_id: Option<RpcId>,
28 },
29 #[action_event(level = trace)]
30 OfferSdpCreatePending {
31 peer_id: PeerId,
32 },
33 OfferSdpCreateError {
34 peer_id: PeerId,
35 error: String,
36 },
37 OfferSdpCreateSuccess {
38 peer_id: PeerId,
39 sdp: String,
40 },
41 OfferReady {
42 peer_id: PeerId,
43 offer: Box<webrtc::Offer>,
44 },
45 OfferSendSuccess {
46 peer_id: PeerId,
47 },
48 #[action_event(level = trace)]
49 AnswerRecvPending {
50 peer_id: PeerId,
51 },
52 AnswerRecvError {
53 peer_id: PeerId,
54 error: P2pConnectionErrorResponse,
55 },
56 AnswerRecvSuccess {
57 peer_id: PeerId,
58 answer: Box<webrtc::Answer>,
59 },
60 #[action_event(level = trace)]
61 FinalizePending {
62 peer_id: PeerId,
63 },
64 #[action_event(level = debug)]
66 FinalizeError {
67 peer_id: PeerId,
68 error: String,
69 },
70 FinalizeSuccess {
72 peer_id: PeerId,
73 remote_auth: Option<webrtc::ConnectionAuthEncrypted>,
74 },
75 Timeout {
77 peer_id: PeerId,
78 },
79 #[action_event(level = debug)]
81 Error {
82 peer_id: PeerId,
83 error: P2pConnectionOutgoingError,
84 },
85 Success {
87 peer_id: PeerId,
88 },
89}
90
91impl redux::EnablingCondition<P2pState> for P2pConnectionOutgoingAction {
92 fn is_enabled(&self, state: &P2pState, time: redux::Timestamp) -> bool {
93 match self {
94 P2pConnectionOutgoingAction::RandomInit => !state.already_has_min_peers() && state.disconnected_peers().next().is_some(),
95 P2pConnectionOutgoingAction::Init { opts, .. } => {
96 !state.already_has_min_peers() &&
97 &state.my_id() != opts.peer_id() &&
98 state
99 .peers
100 .get(opts.peer_id())
101 .is_none_or(|peer| !peer.status.is_connected_or_connecting())
102 }
103 P2pConnectionOutgoingAction::Reconnect { opts, .. } => {
104 !state.already_has_min_peers()
105 && state.peers.get(opts.peer_id()).is_some_and( |peer| {
106 peer.can_reconnect(time, &state.config.timeouts)
107 })
108 }
109 P2pConnectionOutgoingAction::OfferSdpCreatePending { peer_id } => state
110 .peers
111 .get(peer_id)
112 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
113 P2pConnectionOutgoingState::Init { .. },
114 )))),
115 P2pConnectionOutgoingAction::OfferSdpCreateError { peer_id, .. } => state
116 .peers
117 .get(peer_id)
118 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
119 P2pConnectionOutgoingState::OfferSdpCreatePending { .. },
120 )))),
121 P2pConnectionOutgoingAction::OfferSdpCreateSuccess { peer_id, .. } => state
122 .peers
123 .get(peer_id)
124 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
125 P2pConnectionOutgoingState::OfferSdpCreatePending { .. },
126 )))),
127 P2pConnectionOutgoingAction::OfferReady { peer_id, .. } => state
128 .peers
129 .get(peer_id)
130 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
131 P2pConnectionOutgoingState::OfferSdpCreateSuccess { .. },
132 )))),
133 P2pConnectionOutgoingAction::OfferSendSuccess { peer_id } => state
134 .peers
135 .get(peer_id)
136 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
137 P2pConnectionOutgoingState::OfferReady { .. },
138 )))),
139 P2pConnectionOutgoingAction::AnswerRecvPending { peer_id } => state
140 .peers
141 .get(peer_id)
142 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
143 P2pConnectionOutgoingState::OfferSendSuccess { .. },
144 )))),
145 P2pConnectionOutgoingAction::AnswerRecvError { peer_id, .. } => state
146 .peers
147 .get(peer_id)
148 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
149 P2pConnectionOutgoingState::AnswerRecvPending { .. },
150 )))),
151 P2pConnectionOutgoingAction::AnswerRecvSuccess { peer_id, .. } => state
152 .peers
153 .get(peer_id)
154 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
155 P2pConnectionOutgoingState::AnswerRecvPending { .. },
156 )))),
157 P2pConnectionOutgoingAction::FinalizePending { peer_id } => state
158 .peers
159 .get(peer_id)
160 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(v)) if match v {
161 P2pConnectionOutgoingState::Init { opts, .. } => opts.is_libp2p(),
162 P2pConnectionOutgoingState::AnswerRecvSuccess { .. } => true,
163 _ => false,
164 })),
165 P2pConnectionOutgoingAction::FinalizeError { peer_id, .. } => state
166 .peers
167 .get(peer_id)
168 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
169 P2pConnectionOutgoingState::FinalizePending { .. },
170 )))),
171 P2pConnectionOutgoingAction::FinalizeSuccess { peer_id, remote_auth: auth } => state
172 .peers
173 .get(peer_id)
174 .filter(|p| auth.is_some() || p.is_libp2p())
175 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
176 P2pConnectionOutgoingState::FinalizePending { .. },
177 )))),
178 P2pConnectionOutgoingAction::Timeout { peer_id } => state
179 .peers
180 .get(peer_id)
181 .and_then(|peer| peer.status.as_connecting()?.as_outgoing())
182 .is_some_and( |s| s.is_timed_out(time, &state.config.timeouts)),
183 P2pConnectionOutgoingAction::Error { peer_id, error } => state
184 .peers
185 .get(peer_id)
186 .is_some_and( |peer| match &peer.status {
187 P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(s)) => match error {
188 P2pConnectionOutgoingError::SdpCreateError(_) => {
189 matches!(s, P2pConnectionOutgoingState::OfferSdpCreatePending { .. })
190 }
191 P2pConnectionOutgoingError::Rejected(_)
192 | P2pConnectionOutgoingError::RemoteSignalDecryptionFailed
193 | P2pConnectionOutgoingError::RemoteInternalError => {
194 matches!(s, P2pConnectionOutgoingState::AnswerRecvPending { .. })
195 }
196 P2pConnectionOutgoingError::FinalizeError(_) => {
197 matches!(s, P2pConnectionOutgoingState::FinalizePending { .. })
198 }
199 P2pConnectionOutgoingError::ConnectionAuthError => {
200 matches!(s, P2pConnectionOutgoingState::FinalizeSuccess { .. })
201 }
202 P2pConnectionOutgoingError::Timeout => true,
203 },
204 _ => false,
205 }),
206 P2pConnectionOutgoingAction::Success { peer_id } => {
207 state
208 .peers
209 .get(peer_id)
210 .is_some_and( |peer| matches!(&peer.status, P2pPeerStatus::Connecting(P2pConnectionState::Outgoing(
211 P2pConnectionOutgoingState::FinalizeSuccess { .. },
212 ))))
213 }
214 }
215 }
216}
217
218use crate::{
220 connection::{P2pConnectionAction, P2pConnectionState},
221 P2pPeerStatus,
222};
223
224use super::P2pConnectionOutgoingState;
225
226impl From<P2pConnectionOutgoingAction> for crate::P2pAction {
227 fn from(a: P2pConnectionOutgoingAction) -> Self {
228 Self::Connection(P2pConnectionAction::Outgoing(a))
229 }
230}