1use std::io::{Read, Write};
13use std::net::TcpStream;
14use std::time::Duration;
15
16use rustls::pki_types::ServerName;
17use rustls::{ClientConnection, StreamOwned};
18
19use crate::config::ResolvedTarget;
20
21const CAMERA_PORT: u16 = 6000;
22const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
23
24#[derive(Debug, thiserror::Error)]
26pub enum CameraError {
27 #[error("TLS setup failed: {0}")]
28 Tls(String),
29 #[error("camera connection failed: {0}")]
30 Connect(String),
31 #[error("camera i/o error (no frame? the A1 camera is often off): {0}")]
32 Io(#[from] std::io::Error),
33 #[error("unexpected frame header (jpeg length {0}); framing differs or camera off")]
34 BadFrame(u32),
35}
36
37fn auth_packet(access_code: &str) -> Vec<u8> {
39 let mut p = Vec::with_capacity(80);
40 p.extend_from_slice(&0x40u32.to_le_bytes());
41 p.extend_from_slice(&0x3000u32.to_le_bytes());
42 p.extend_from_slice(&0u32.to_le_bytes());
43 p.extend_from_slice(&0u32.to_le_bytes());
44 let mut field = [0u8; 32];
45 let user = b"bblp";
46 field[..user.len()].copy_from_slice(user);
47 p.extend_from_slice(&field);
48 let mut field = [0u8; 32];
49 let code = access_code.as_bytes();
50 let n = code.len().min(32);
51 field[..n].copy_from_slice(&code[..n]);
52 p.extend_from_slice(&field);
53 p
54}
55
56pub struct CameraClient {
58 target: ResolvedTarget,
59 timeout: Duration,
60}
61
62impl CameraClient {
63 pub fn new(target: ResolvedTarget) -> Self {
64 Self {
65 target,
66 timeout: DEFAULT_TIMEOUT,
67 }
68 }
69
70 pub fn with_timeout(mut self, timeout: Duration) -> Self {
71 self.timeout = timeout;
72 self
73 }
74
75 pub fn snapshot(&self) -> Result<Vec<u8>, CameraError> {
77 let config =
78 crate::tls::lan_client_config().map_err(|e| CameraError::Tls(e.to_string()))?;
79 let tcp = TcpStream::connect((self.target.ip.as_str(), CAMERA_PORT))?;
80 tcp.set_read_timeout(Some(self.timeout))?;
81 tcp.set_write_timeout(Some(self.timeout))?;
82 let server_name = ServerName::try_from(self.target.ip.clone())
83 .map_err(|e| CameraError::Tls(e.to_string()))?;
84 let conn = ClientConnection::new(config, server_name)
85 .map_err(|e| CameraError::Tls(e.to_string()))?;
86 let mut tls = StreamOwned::new(conn, tcp);
87
88 tls.write_all(&auth_packet(&self.target.access_code))?;
89
90 let mut header = [0u8; 16];
91 tls.read_exact(&mut header)?;
92 let jpeg_len = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
93 if !(1000..=8_000_000).contains(&jpeg_len) {
94 return Err(CameraError::BadFrame(jpeg_len));
95 }
96 let mut jpeg = vec![0u8; jpeg_len as usize];
97 tls.read_exact(&mut jpeg)?;
98 Ok(jpeg)
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn auth_packet_layout() {
108 let p = auth_packet("12345678");
109 assert_eq!(p.len(), 80);
110 assert_eq!(&p[0..4], &0x40u32.to_le_bytes()); assert_eq!(&p[4..8], &0x3000u32.to_le_bytes()); assert_eq!(&p[16..20], b"bblp"); assert_eq!(p[20], 0);
114 assert_eq!(&p[48..56], b"12345678"); }
116}