1use std::{collections::HashMap, fmt::Display, io::Write};
2
3use nusb::transfer::RequestBuffer;
4pub use nusb::{transfer::TransferError, Device, DeviceInfo, Interface};
5use thiserror::Error;
6use tracing::{info, warn};
7use tracing::{instrument, trace};
8
9use crate::protocol::FastBootResponse;
10use crate::protocol::{FastBootCommand, FastBootResponseParseError};
11
12pub fn devices() -> Result<impl Iterator<Item = DeviceInfo>, nusb::Error> {
14 Ok(nusb::list_devices()?.filter(|d| NusbFastBoot::find_fastboot_interface(d).is_some()))
15}
16
17#[derive(Debug, Error)]
19pub enum NusbFastBootError {
20 #[error("Transfer error: {0}")]
21 Transfer(#[from] nusb::transfer::TransferError),
22 #[error("Fastboot client failure: {0}")]
23 FastbootFailed(String),
24 #[error("Unexpected fastboot response")]
25 FastbootUnexpectedReply,
26 #[error("Unknown fastboot response: {0}")]
27 FastbootParseError(#[from] FastBootResponseParseError),
28}
29
30#[derive(Debug, Error)]
32pub enum NusbFastBootOpenError {
33 #[error("Failed to open device: {0}")]
34 Device(std::io::Error),
35 #[error("Failed to claim interface: {0}")]
36 Interface(std::io::Error),
37 #[error("Failed to find interface for fastboot")]
38 MissingInterface,
39 #[error("Failed to find required endpoints for fastboot")]
40 MissingEndpoints,
41 #[error("Unknown fastboot response: {0}")]
42 FastbootParseError(#[from] FastBootResponseParseError),
43}
44
45pub struct NusbFastBoot {
47 interface: nusb::Interface,
48 ep_out: u8,
49 max_out: usize,
50 ep_in: u8,
51 max_in: usize,
52}
53
54impl NusbFastBoot {
55 pub fn find_fastboot_interface(info: &DeviceInfo) -> Option<u8> {
57 info.interfaces().find_map(|i| {
58 if i.class() == 0xff && i.subclass() == 0x42 && i.protocol() == 0x3 {
59 Some(i.interface_number())
60 } else {
61 None
62 }
63 })
64 }
65
66 #[tracing::instrument(skip_all, err)]
69 pub fn from_interface(interface: nusb::Interface) -> Result<Self, NusbFastBootOpenError> {
70 let (ep_out, max_out, ep_in, max_in) = interface
71 .descriptors()
72 .find_map(|alt| {
73 let (ep_out, max_out) = alt.endpoints().find_map(|end| {
75 if end.transfer_type() == nusb::transfer::EndpointType::Bulk
76 && end.direction() == nusb::transfer::Direction::Out
77 {
78 Some((end.address(), end.max_packet_size()))
79 } else {
80 None
81 }
82 })?;
83 let (ep_in, max_in) = alt.endpoints().find_map(|end| {
84 if end.transfer_type() == nusb::transfer::EndpointType::Bulk
85 && end.direction() == nusb::transfer::Direction::In
86 {
87 Some((end.address(), end.max_packet_size()))
88 } else {
89 None
90 }
91 })?;
92 Some((ep_out, max_out, ep_in, max_in))
93 })
94 .ok_or(NusbFastBootOpenError::MissingEndpoints)?;
95 trace!(
96 "Fastboot endpoints: OUT: {} (max: {}), IN: {} (max: {})",
97 ep_out,
98 max_out,
99 ep_in,
100 max_in
101 );
102 Ok(Self {
103 interface,
104 ep_out,
105 max_out,
106 ep_in,
107 max_in,
108 })
109 }
110
111 #[tracing::instrument(skip_all, err)]
114 pub fn from_device(device: Device, interface: u8) -> Result<Self, NusbFastBootOpenError> {
115 let interface = device
116 .claim_interface(interface)
117 .map_err(NusbFastBootOpenError::Interface)?;
118 Self::from_interface(interface)
119 }
120
121 #[tracing::instrument(skip_all, err)]
124 pub fn from_info(info: &DeviceInfo) -> Result<Self, NusbFastBootOpenError> {
125 let interface =
126 Self::find_fastboot_interface(info).ok_or(NusbFastBootOpenError::MissingInterface)?;
127 let device = info.open().map_err(NusbFastBootOpenError::Device)?;
128 Self::from_device(device, interface)
129 }
130
131 #[tracing::instrument(skip_all, err)]
132 async fn send_data(&mut self, data: Vec<u8>) -> Result<(), NusbFastBootError> {
133 self.interface.bulk_out(self.ep_out, data).await.status?;
134 Ok(())
135 }
136
137 async fn send_command<S: Display>(
138 &mut self,
139 cmd: FastBootCommand<S>,
140 ) -> Result<(), NusbFastBootError> {
141 let mut out = vec![];
142 out.write_fmt(format_args!("{}", cmd)).unwrap();
144 trace!(
145 "Sending command: {}",
146 std::str::from_utf8(&out).unwrap_or("Invalid utf-8")
147 );
148 self.send_data(out).await
149 }
150
151 #[tracing::instrument(skip_all, err)]
152 async fn read_response(&mut self) -> Result<FastBootResponse, FastBootResponseParseError> {
153 let req = RequestBuffer::new(self.max_in);
154 let resp = self.interface.bulk_in(self.ep_in, req).await;
155 FastBootResponse::from_bytes(&resp.data)
156 }
157
158 #[tracing::instrument(skip_all, err)]
159 async fn handle_responses(&mut self) -> Result<String, NusbFastBootError> {
160 loop {
161 let resp = self.read_response().await?;
162 trace!("Response: {:?}", resp);
163 match resp {
164 FastBootResponse::Info(_) => (),
165 FastBootResponse::Text(_) => (),
166 FastBootResponse::Data(_) => {
167 return Err(NusbFastBootError::FastbootUnexpectedReply)
168 }
169 FastBootResponse::Okay(value) => return Ok(value),
170 FastBootResponse::Fail(fail) => {
171 return Err(NusbFastBootError::FastbootFailed(fail))
172 }
173 }
174 }
175 }
176
177 #[tracing::instrument(skip_all, err)]
178 async fn execute<S: Display>(
179 &mut self,
180 cmd: FastBootCommand<S>,
181 ) -> Result<String, NusbFastBootError> {
182 self.send_command(cmd).await?;
183 self.handle_responses().await
184 }
185
186 pub async fn get_var(&mut self, var: &str) -> Result<String, NusbFastBootError> {
190 let cmd = FastBootCommand::GetVar(var);
191 self.execute(cmd).await
192 }
193
194 pub async fn download(&mut self, size: u32) -> Result<DataDownload, NusbFastBootError> {
198 let cmd = FastBootCommand::<&str>::Download(size);
199 self.send_command(cmd).await?;
200 loop {
201 let resp = self.read_response().await?;
202 match resp {
203 FastBootResponse::Info(i) => info!("info: {i}"),
204 FastBootResponse::Text(t) => info!("Text: {}", t),
205 FastBootResponse::Data(size) => {
206 return Ok(DataDownload::new(self, size));
207 }
208 FastBootResponse::Okay(_) => {
209 return Err(NusbFastBootError::FastbootUnexpectedReply)
210 }
211 FastBootResponse::Fail(fail) => {
212 return Err(NusbFastBootError::FastbootFailed(fail))
213 }
214 }
215 }
216 }
217
218 pub async fn flash(&mut self, target: &str) -> Result<(), NusbFastBootError> {
220 let cmd = FastBootCommand::Flash(target);
221 self.execute(cmd).await.map(|v| {
222 trace!("Flash ok: {v}");
223 })
224 }
225
226 pub async fn continue_boot(&mut self) -> Result<(), NusbFastBootError> {
228 let cmd = FastBootCommand::<&str>::Continue;
229 self.execute(cmd).await.map(|v| {
230 trace!("Continue ok: {v}");
231 })
232 }
233
234 pub async fn erase(&mut self, target: &str) -> Result<(), NusbFastBootError> {
236 let cmd = FastBootCommand::Erase(target);
237 self.execute(cmd).await.map(|v| {
238 trace!("Erase ok: {v}");
239 })
240 }
241
242 pub async fn reboot(&mut self) -> Result<(), NusbFastBootError> {
244 let cmd = FastBootCommand::<&str>::Reboot;
245 self.execute(cmd).await.map(|v| {
246 trace!("Reboot ok: {v}");
247 })
248 }
249
250 pub async fn reboot_bootloader(&mut self) -> Result<(), NusbFastBootError> {
252 let cmd = FastBootCommand::<&str>::RebootBootloader;
253 self.execute(cmd).await.map(|v| {
254 trace!("Reboot ok: {v}");
255 })
256 }
257
258 pub async fn get_all_vars(&mut self) -> Result<HashMap<String, String>, NusbFastBootError> {
260 let cmd = FastBootCommand::GetVar("all");
261 self.send_command(cmd).await?;
262 let mut vars = HashMap::new();
263 loop {
264 let resp = self.read_response().await?;
265 trace!("Response: {:?}", resp);
266 match resp {
267 FastBootResponse::Info(i) => {
268 let Some((key, value)) = i.rsplit_once(':') else {
269 warn!("Failed to parse variable: {i}");
270 continue;
271 };
272 vars.insert(key.trim().to_string(), value.trim().to_string());
273 }
274 FastBootResponse::Text(t) => info!("Text: {}", t),
275 FastBootResponse::Data(_) => {
276 return Err(NusbFastBootError::FastbootUnexpectedReply)
277 }
278 FastBootResponse::Okay(_) => {
279 return Ok(vars);
280 }
281 FastBootResponse::Fail(fail) => {
282 return Err(NusbFastBootError::FastbootFailed(fail))
283 }
284 }
285 }
286 }
287}
288
289#[derive(Debug, Error)]
291pub enum DownloadError {
292 #[error("Trying to complete while nothing was Queued")]
293 NothingQueued,
294 #[error("Incorrect data length: expected {expected}, got {actual}")]
295 IncorrectDataLength { actual: u32, expected: u32 },
296 #[error(transparent)]
297 Nusb(#[from] NusbFastBootError),
298}
299
300pub struct DataDownload<'s> {
310 fastboot: &'s mut NusbFastBoot,
311 queue: nusb::transfer::Queue<Vec<u8>>,
312 size: u32,
313 left: u32,
314 current: Vec<u8>,
315}
316
317impl<'s> DataDownload<'s> {
318 fn new(fastboot: &'s mut NusbFastBoot, size: u32) -> DataDownload<'s> {
319 let queue = fastboot.interface.bulk_out_queue(fastboot.ep_out);
320 let current = Self::allocate_buffer(fastboot.max_out);
321 Self {
322 fastboot,
323 queue,
324 size,
325 left: size,
326 current,
327 }
328 }
329}
330
331impl DataDownload<'_> {
332 pub fn size(&self) -> u32 {
334 self.size
335 }
336
337 pub fn left(&self) -> u32 {
339 self.left
340 }
341
342 pub async fn extend_from_slice(&mut self, mut data: &[u8]) -> Result<(), DownloadError> {
347 self.update_size(data.len() as u32)?;
348 loop {
349 let left = self.current.capacity() - self.current.len();
350 if left >= data.len() {
351 self.current.extend_from_slice(data);
352 break;
353 } else {
354 self.current.extend_from_slice(&data[0..left]);
355 self.next_buffer().await?;
356 data = &data[left..];
357 }
358 }
359 Ok(())
360 }
361
362 pub async fn get_mut_data(&mut self, max: usize) -> Result<&mut [u8], DownloadError> {
367 if self.current.capacity() == self.current.len() {
368 self.next_buffer().await?;
369 }
370
371 let left = self.current.capacity() - self.current.len();
372 let size = left.min(max);
373 self.update_size(size as u32)?;
374
375 let len = self.current.len();
376 self.current.resize(len + size, 0);
377 Ok(&mut self.current[len..])
378 }
379
380 fn update_size(&mut self, size: u32) -> Result<(), DownloadError> {
381 if size > self.left {
382 return Err(DownloadError::IncorrectDataLength {
383 expected: self.size,
384 actual: size - self.left + self.size,
385 });
386 }
387 self.left -= size;
388 Ok(())
389 }
390
391 fn allocate_buffer(max_out: usize) -> Vec<u8> {
392 let size = (1024usize * 1024).next_multiple_of(max_out);
395 Vec::with_capacity(size)
396 }
397
398 async fn next_buffer(&mut self) -> Result<(), DownloadError> {
399 let mut next = if self.queue.pending() < 3 {
400 Self::allocate_buffer(self.fastboot.max_out)
401 } else {
402 let r = self.queue.next_complete().await;
403 r.status.map_err(NusbFastBootError::from)?;
404 let mut data = r.data.reuse();
405 data.truncate(0);
406 data
407 };
408 std::mem::swap(&mut next, &mut self.current);
409 self.queue.submit(next);
410 Ok(())
411 }
412
413 #[instrument(skip_all, err)]
417 pub async fn finish(mut self) -> Result<(), DownloadError> {
418 if self.left != 0 {
419 return Err(DownloadError::IncorrectDataLength {
420 expected: self.size,
421 actual: self.size - self.left,
422 });
423 }
424
425 if !self.current.is_empty() {
426 let current = std::mem::take(&mut self.current);
427 self.queue.submit(current);
428 }
429
430 while self.queue.pending() > 0 {
431 self.queue
432 .next_complete()
433 .await
434 .status
435 .map_err(NusbFastBootError::from)?;
436 }
437
438 self.fastboot.handle_responses().await?;
439 Ok(())
440 }
441}