]> git.lizzy.rs Git - mt_client.git/blob - src/net.rs
Handle net thread panic
[mt_client.git] / src / net.rs
1 use crate::{GfxEvent, NetEvent};
2 use cgmath::{Deg, Point3, Vector3};
3 use futures::future::OptionFuture;
4 use mt_net::{CltSender, ReceiverExt, SenderExt, ToCltPkt, ToSrvPkt};
5 use rand::RngCore;
6 use sha2::Sha256;
7 use srp::{client::SrpClient, groups::G_2048};
8 use std::{future::Future, time::Duration};
9 use tokio::{
10     sync::mpsc,
11     time::{interval, Instant, Interval},
12 };
13 use winit::event_loop::EventLoopProxy;
14
15 enum AuthState {
16     Init(Interval),
17     Verify(Vec<u8>, SrpClient<'static, Sha256>),
18     Done,
19 }
20
21 struct Conn {
22     tx: CltSender,
23     auth: AuthState,
24     send_pos_iv: Option<Interval>,
25     username: String,
26     password: String,
27     pos: Point3<f32>,
28     pitch: Deg<f32>,
29     yaw: Deg<f32>,
30     events: EventLoopProxy<GfxEvent>,
31 }
32
33 fn maybe_tick(iv: Option<&mut Interval>) -> OptionFuture<impl Future<Output = Instant> + '_> {
34     OptionFuture::from(iv.map(Interval::tick))
35 }
36
37 pub(crate) async fn run(
38     evt_out: EventLoopProxy<GfxEvent>,
39     mut evt_in: mpsc::UnboundedReceiver<NetEvent>,
40 ) {
41     let (tx, mut rx, worker) = mt_net::connect("localhost:30000").await.unwrap();
42
43     let mut conn = Conn {
44         tx,
45         auth: AuthState::Init(interval(Duration::from_millis(100))),
46         send_pos_iv: None,
47         username: "shrek".into(), // shrek is love, shrek is life <3
48         password: "boobies".into(),
49         pos: Point3::new(0.0, 0.0, 0.0),
50         pitch: Deg(0.0),
51         yaw: Deg(0.0),
52         events: evt_out,
53     };
54
55     let init_pkt = ToSrvPkt::Init {
56         serialize_version: 29,
57         proto_version: 40..=40,
58         player_name: conn.username.clone(),
59         send_full_item_meta: false,
60     };
61
62     let worker_thread = tokio::spawn(worker.run());
63
64     loop {
65         tokio::select! {
66             pkt = rx.recv() => match pkt {
67                 None => break,
68                 Some(Err(e)) => eprintln!("{e}"),
69                 Some(Ok(v)) => conn.handle_pkt(v).await,
70             },
71             Some(_) = maybe_tick(match &mut conn.auth {
72                 AuthState::Init(iv) => Some(iv),
73                 _ => None,
74             }) => {
75                 conn.tx.send(&init_pkt).await.unwrap();
76             }
77             Some(_) = maybe_tick(conn.send_pos_iv.as_mut()) => {
78                 conn.tx
79                     .send(&ToSrvPkt::PlayerPos(mt_net::PlayerPos {
80                         pos: conn.pos,
81                         vel: Vector3::new(0.0, 0.0, 0.0),
82                         pitch: conn.pitch,
83                         yaw: conn.yaw,
84                         keys: mt_net::enumset::EnumSet::empty(),
85                         fov: Deg(90.0).into(),
86                         wanted_range: 12,
87                     }))
88                     .await
89                     .unwrap();
90             }
91             evt = evt_in.recv() => {
92                 match evt {
93                     Some(NetEvent::PlayerPos(pos, yaw, pitch)) => {
94                         conn.pos = pos;
95                         conn.yaw = yaw;
96                         conn.pitch = pitch;
97                     },
98                     Some(NetEvent::Ready) => {
99                         conn.tx
100                             .send(&ToSrvPkt::CltReady {
101                                 major: 0,
102                                 minor: 1,
103                                 patch: 0,
104                                 reserved: 0,
105                                 version: format!("Minetest Rust {}", env!("CARGO_PKG_VERSION")),
106                                 formspec: 4,
107                             })
108                             .await
109                             .unwrap();
110                     }
111                     None => conn.tx.close(),
112                 }
113             }
114             _ = tokio::signal::ctrl_c() => {
115                 conn.tx.close();
116             }
117         }
118     }
119
120     worker_thread.await.unwrap();
121 }
122
123 impl Conn {
124     async fn handle_pkt(&mut self, pkt: ToCltPkt) {
125         use ToCltPkt::*;
126
127         match pkt {
128             Hello {
129                 auth_methods,
130                 username: name,
131                 ..
132             } => {
133                 use mt_net::AuthMethod;
134
135                 if !matches!(self.auth, AuthState::Init(_)) {
136                     return;
137                 }
138
139                 let srp = SrpClient::<Sha256>::new(&G_2048);
140
141                 let mut rand_bytes = vec![0; 32];
142                 rand::thread_rng().fill_bytes(&mut rand_bytes);
143
144                 if self.username != name {
145                     panic!("username changed");
146                 }
147
148                 if auth_methods.contains(AuthMethod::FirstSrp) {
149                     let verifier = srp.compute_verifier(
150                         self.username.to_lowercase().as_bytes(),
151                         self.password.as_bytes(),
152                         &rand_bytes,
153                     );
154
155                     self.tx
156                         .send(&ToSrvPkt::FirstSrp {
157                             salt: rand_bytes,
158                             verifier,
159                             empty_passwd: self.password.is_empty(),
160                         })
161                         .await
162                         .unwrap();
163
164                     self.auth = AuthState::Done;
165                 } else if auth_methods.contains(AuthMethod::Srp) {
166                     let a = srp.compute_public_ephemeral(&rand_bytes);
167
168                     self.tx
169                         .send(&ToSrvPkt::SrpBytesA { a, no_sha1: true })
170                         .await
171                         .unwrap();
172
173                     self.auth = AuthState::Verify(rand_bytes, srp);
174                 } else {
175                     panic!("unsupported auth methods: {auth_methods:?}");
176                 }
177             }
178             SrpBytesSaltB { salt, b } => {
179                 if let AuthState::Verify(a, srp) = &self.auth {
180                     let m = srp
181                         .process_reply(
182                             a,
183                             self.username.to_lowercase().as_bytes(),
184                             self.password.as_bytes(),
185                             &salt,
186                             &b,
187                         )
188                         .unwrap()
189                         .proof()
190                         .into();
191
192                     self.tx.send(&ToSrvPkt::SrpBytesM { m }).await.unwrap();
193
194                     self.auth = AuthState::Done;
195                 }
196             }
197             NodeDefs(defs) => {
198                 self.events.send_event(GfxEvent::NodeDefs(defs.0)).ok();
199             }
200             Kick(reason) => {
201                 println!("kicked: {reason}");
202             }
203             AcceptAuth { player_pos, .. } => {
204                 self.tx
205                     .send(&ToSrvPkt::Init2 {
206                         lang: "en_US".into(), // localization is unironically overrated
207                     })
208                     .await
209                     .unwrap();
210
211                 self.pos = player_pos;
212                 self.send_pos_iv = Some(interval(Duration::from_millis(100)));
213             }
214             MovePlayer { pos, pitch, yaw } => {
215                 self.pos = pos;
216                 self.pitch = pitch;
217                 self.yaw = yaw;
218
219                 self.events
220                     .send_event(GfxEvent::PlayerPos(self.pos, self.pitch, self.yaw))
221                     .ok();
222             }
223             BlockData { pos, block } => {
224                 self.events.send_event(GfxEvent::MapBlock(pos, block)).ok();
225                 self.tx
226                     .send(&ToSrvPkt::GotBlocks {
227                         blocks: Vec::from([pos]),
228                     })
229                     .await
230                     .unwrap();
231             }
232             AnnounceMedia { files, .. } => {
233                 self.tx
234                     .send(&ToSrvPkt::RequestMedia {
235                         filenames: files.into_keys().collect(), // TODO: cache
236                     })
237                     .await
238                     .ok();
239             }
240             Media { files, n, i } => {
241                 self.events
242                     .send_event(GfxEvent::Media(files, i + 1 == n))
243                     .ok();
244             }
245             ChatMsg { text, .. } => {
246                 println!("{text}");
247             }
248             _ => {}
249         }
250     }
251 }