use std::io::{Read, Write};
use std::net::TcpStream;
use std::time::Duration;
use rustls::pki_types::ServerName;
use rustls::{ClientConnection, StreamOwned};
use crate::config::ResolvedTarget;
const CAMERA_PORT: u16 = 6000;
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
#[derive(Debug, thiserror::Error)]
pub enum CameraError {
#[error("TLS setup failed: {0}")]
Tls(String),
#[error("camera connection failed: {0}")]
Connect(String),
#[error("camera i/o error (no frame? the A1 camera is often off): {0}")]
Io(#[from] std::io::Error),
#[error("unexpected frame header (jpeg length {0}); framing differs or camera off")]
BadFrame(u32),
}
fn auth_packet(access_code: &str) -> Vec<u8> {
let mut p = Vec::with_capacity(80);
p.extend_from_slice(&0x40u32.to_le_bytes());
p.extend_from_slice(&0x3000u32.to_le_bytes());
p.extend_from_slice(&0u32.to_le_bytes());
p.extend_from_slice(&0u32.to_le_bytes());
let mut field = [0u8; 32];
let user = b"bblp";
field[..user.len()].copy_from_slice(user);
p.extend_from_slice(&field);
let mut field = [0u8; 32];
let code = access_code.as_bytes();
let n = code.len().min(32);
field[..n].copy_from_slice(&code[..n]);
p.extend_from_slice(&field);
p
}
pub struct CameraClient {
target: ResolvedTarget,
timeout: Duration,
}
impl CameraClient {
pub fn new(target: ResolvedTarget) -> Self {
Self {
target,
timeout: DEFAULT_TIMEOUT,
}
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn snapshot(&self) -> Result<Vec<u8>, CameraError> {
let config =
crate::tls::lan_client_config().map_err(|e| CameraError::Tls(e.to_string()))?;
let tcp = TcpStream::connect((self.target.ip.as_str(), CAMERA_PORT))?;
tcp.set_read_timeout(Some(self.timeout))?;
tcp.set_write_timeout(Some(self.timeout))?;
let server_name = ServerName::try_from(self.target.ip.clone())
.map_err(|e| CameraError::Tls(e.to_string()))?;
let conn = ClientConnection::new(config, server_name)
.map_err(|e| CameraError::Tls(e.to_string()))?;
let mut tls = StreamOwned::new(conn, tcp);
tls.write_all(&auth_packet(&self.target.access_code))?;
let mut header = [0u8; 16];
tls.read_exact(&mut header)?;
let jpeg_len = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
if !(1000..=8_000_000).contains(&jpeg_len) {
return Err(CameraError::BadFrame(jpeg_len));
}
let mut jpeg = vec![0u8; jpeg_len as usize];
tls.read_exact(&mut jpeg)?;
Ok(jpeg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn auth_packet_layout() {
let p = auth_packet("12345678");
assert_eq!(p.len(), 80);
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);
assert_eq!(&p[48..56], b"12345678"); }
}