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 erase(&mut self, target: &str) -> Result<(), NusbFastBootError> {
228 let cmd = FastBootCommand::Erase(target);
229 self.execute(cmd).await.map(|v| {
230 trace!("Erase ok: {v}");
231 })
232 }
233
234 pub async fn reboot(&mut self) -> Result<(), NusbFastBootError> {
236 let cmd = FastBootCommand::<&str>::Reboot;
237 self.execute(cmd).await.map(|v| {
238 trace!("Reboot ok: {v}");
239 })
240 }
241
242 pub async fn reboot_bootloader(&mut self) -> Result<(), NusbFastBootError> {
244 let cmd = FastBootCommand::<&str>::RebootBootloader;
245 self.execute(cmd).await.map(|v| {
246 trace!("Reboot ok: {v}");
247 })
248 }
249
250 pub async fn get_all_vars(&mut self) -> Result<HashMap<String, String>, NusbFastBootError> {
252 let cmd = FastBootCommand::GetVar("all");
253 self.send_command(cmd).await?;
254 let mut vars = HashMap::new();
255 loop {
256 let resp = self.read_response().await?;
257 trace!("Response: {:?}", resp);
258 match resp {
259 FastBootResponse::Info(i) => {
260 let Some((key, value)) = i.rsplit_once(':') else {
261 warn!("Failed to parse variable: {i}");
262 continue;
263 };
264 vars.insert(key.trim().to_string(), value.trim().to_string());
265 }
266 FastBootResponse::Text(t) => info!("Text: {}", t),
267 FastBootResponse::Data(_) => {
268 return Err(NusbFastBootError::FastbootUnexpectedReply)
269 }
270 FastBootResponse::Okay(_) => {
271 return Ok(vars);
272 }
273 FastBootResponse::Fail(fail) => {
274 return Err(NusbFastBootError::FastbootFailed(fail))
275 }
276 }
277 }
278 }
279}
280
281#[derive(Debug, Error)]
283pub enum DownloadError {
284 #[error("Trying to complete while nothing was Queued")]
285 NothingQueued,
286 #[error("Incorrect data length: expected {expected}, got {actual}")]
287 IncorrectDataLength { actual: u32, expected: u32 },
288 #[error(transparent)]
289 Nusb(#[from] NusbFastBootError),
290}
291
292pub struct DataDownload<'s> {
302 fastboot: &'s mut NusbFastBoot,
303 queue: nusb::transfer::Queue<Vec<u8>>,
304 size: u32,
305 left: u32,
306 current: Vec<u8>,
307}
308
309impl<'s> DataDownload<'s> {
310 fn new(fastboot: &'s mut NusbFastBoot, size: u32) -> DataDownload<'s> {
311 let queue = fastboot.interface.bulk_out_queue(fastboot.ep_out);
312 let current = Self::allocate_buffer(fastboot.max_out);
313 Self {
314 fastboot,
315 queue,
316 size,
317 left: size,
318 current,
319 }
320 }
321}
322
323impl DataDownload<'_> {
324 pub fn size(&self) -> u32 {
326 self.size
327 }
328
329 pub fn left(&self) -> u32 {
331 self.left
332 }
333
334 pub async fn extend_from_slice(&mut self, mut data: &[u8]) -> Result<(), DownloadError> {
339 self.update_size(data.len() as u32)?;
340 loop {
341 let left = self.current.capacity() - self.current.len();
342 if left >= data.len() {
343 self.current.extend_from_slice(data);
344 break;
345 } else {
346 self.current.extend_from_slice(&data[0..left]);
347 self.next_buffer().await?;
348 data = &data[left..];
349 }
350 }
351 Ok(())
352 }
353
354 pub async fn get_mut_data(&mut self, max: usize) -> Result<&mut [u8], DownloadError> {
359 if self.current.capacity() == self.current.len() {
360 self.next_buffer().await?;
361 }
362
363 let left = self.current.capacity() - self.current.len();
364 let size = left.min(max);
365 self.update_size(size as u32)?;
366
367 let len = self.current.len();
368 self.current.resize(len + size, 0);
369 Ok(&mut self.current[len..])
370 }
371
372 fn update_size(&mut self, size: u32) -> Result<(), DownloadError> {
373 if size > self.left {
374 return Err(DownloadError::IncorrectDataLength {
375 expected: self.size,
376 actual: size - self.left + self.size,
377 });
378 }
379 self.left -= size;
380 Ok(())
381 }
382
383 fn allocate_buffer(max_out: usize) -> Vec<u8> {
384 let size = (1024usize * 1024).next_multiple_of(max_out);
387 Vec::with_capacity(size)
388 }
389
390 async fn next_buffer(&mut self) -> Result<(), DownloadError> {
391 let mut next = if self.queue.pending() < 3 {
392 Self::allocate_buffer(self.fastboot.max_out)
393 } else {
394 let r = self.queue.next_complete().await;
395 r.status.map_err(NusbFastBootError::from)?;
396 let mut data = r.data.reuse();
397 data.truncate(0);
398 data
399 };
400 std::mem::swap(&mut next, &mut self.current);
401 self.queue.submit(next);
402 Ok(())
403 }
404
405 #[instrument(skip_all, err)]
409 pub async fn finish(mut self) -> Result<(), DownloadError> {
410 if self.left != 0 {
411 return Err(DownloadError::IncorrectDataLength {
412 expected: self.size,
413 actual: self.size - self.left,
414 });
415 }
416
417 if !self.current.is_empty() {
418 let current = std::mem::take(&mut self.current);
419 self.queue.submit(current);
420 }
421
422 while self.queue.pending() > 0 {
423 self.queue
424 .next_complete()
425 .await
426 .status
427 .map_err(NusbFastBootError::from)?;
428 }
429
430 self.fastboot.handle_responses().await?;
431 Ok(())
432 }
433}