use std::fmt;
use std::path::PathBuf;
use crate::security::{RtspSecurityPolicy, redact_url};
#[derive(Clone)]
pub enum SourceSpec {
Rtsp {
url: String,
transport: RtspTransport,
security: RtspSecurityPolicy,
},
File {
path: PathBuf,
loop_: bool,
},
V4l2 { device: String },
Custom { pipeline_fragment: String },
}
impl SourceSpec {
#[must_use]
pub fn rtsp(url: impl Into<String>) -> Self {
let url = url.into();
let security = if url.starts_with("rtsp://") {
RtspSecurityPolicy::AllowInsecure
} else {
RtspSecurityPolicy::PreferTls
};
Self::Rtsp {
url,
transport: RtspTransport::Tcp,
security,
}
}
#[must_use]
pub fn rtsp_tls(url: impl Into<String>) -> Self {
Self::Rtsp {
url: url.into(),
transport: RtspTransport::Tcp,
security: RtspSecurityPolicy::PreferTls,
}
}
#[must_use]
pub fn rtsp_insecure(url: impl Into<String>) -> Self {
Self::Rtsp {
url: url.into(),
transport: RtspTransport::Tcp,
security: RtspSecurityPolicy::AllowInsecure,
}
}
#[must_use]
pub fn file(path: impl Into<PathBuf>) -> Self {
Self::File {
path: path.into(),
loop_: false,
}
}
#[must_use]
pub fn file_looping(path: impl Into<PathBuf>) -> Self {
Self::File {
path: path.into(),
loop_: true,
}
}
#[must_use]
pub fn is_file_nonloop(&self) -> bool {
matches!(self, Self::File { loop_: false, .. })
}
}
impl fmt::Debug for SourceSpec {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rtsp {
url,
transport,
security,
} => f
.debug_struct("Rtsp")
.field("url", &redact_url(url))
.field("transport", transport)
.field("security", security)
.finish(),
Self::File { path, loop_ } => f
.debug_struct("File")
.field("path", path)
.field("loop_", loop_)
.finish(),
Self::V4l2 { device } => f.debug_struct("V4l2").field("device", device).finish(),
Self::Custom { .. } => f
.debug_struct("Custom")
.field("pipeline_fragment", &"<redacted>")
.finish(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RtspTransport {
Tcp,
UdpUnicast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CameraMode {
Fixed,
Observed,
}
#[derive(Debug, Clone)]
pub struct ReconnectPolicy {
pub max_attempts: u32,
pub base_delay: std::time::Duration,
pub max_delay: std::time::Duration,
pub backoff: BackoffKind,
}
impl Default for ReconnectPolicy {
fn default() -> Self {
Self {
max_attempts: 0,
base_delay: std::time::Duration::from_secs(1),
max_delay: std::time::Duration::from_secs(30),
backoff: BackoffKind::Exponential,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackoffKind {
Exponential,
Linear,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::RtspSecurityPolicy;
#[test]
fn file_looping_creates_looping_file_spec() {
let spec = SourceSpec::file_looping("/tmp/test.mp4");
match &spec {
SourceSpec::File { path, loop_ } => {
assert_eq!(path.to_str().unwrap(), "/tmp/test.mp4");
assert!(*loop_, "file_looping should create a looping spec");
}
_ => panic!("expected File variant"),
}
assert!(!spec.is_file_nonloop());
}
#[test]
fn file_creates_nonlooping_file_spec() {
let spec = SourceSpec::file("/tmp/test.mp4");
assert!(spec.is_file_nonloop());
}
#[test]
fn rtsp_plain_scheme_infers_allow_insecure() {
let spec = SourceSpec::rtsp("rtsp://example.com/stream");
match &spec {
SourceSpec::Rtsp {
url,
transport,
security,
} => {
assert_eq!(url, "rtsp://example.com/stream");
assert_eq!(*transport, RtspTransport::Tcp);
assert_eq!(*security, RtspSecurityPolicy::AllowInsecure);
}
_ => panic!("expected Rtsp variant"),
}
}
#[test]
fn rtsp_tls_scheme_infers_prefer_tls() {
let spec = SourceSpec::rtsp("rtsps://example.com/stream");
match &spec {
SourceSpec::Rtsp {
url,
transport,
security,
} => {
assert_eq!(url, "rtsps://example.com/stream");
assert_eq!(*transport, RtspTransport::Tcp);
assert_eq!(*security, RtspSecurityPolicy::PreferTls);
}
_ => panic!("expected Rtsp variant"),
}
}
#[test]
fn rtsp_no_scheme_infers_prefer_tls() {
let spec = SourceSpec::rtsp("example.com/stream");
match &spec {
SourceSpec::Rtsp { security, .. } => {
assert_eq!(*security, RtspSecurityPolicy::PreferTls);
}
_ => panic!("expected Rtsp variant"),
}
}
#[test]
fn rtsp_tls_forces_prefer_tls() {
let spec = SourceSpec::rtsp_tls("rtsp://example.com/stream");
match &spec {
SourceSpec::Rtsp { url, security, .. } => {
assert_eq!(url, "rtsp://example.com/stream");
assert_eq!(*security, RtspSecurityPolicy::PreferTls);
}
_ => panic!("expected Rtsp variant"),
}
}
#[test]
fn rtsp_insecure_creates_allow_insecure_spec() {
let spec = SourceSpec::rtsp_insecure("rtsp://example.com/stream");
match &spec {
SourceSpec::Rtsp { url, security, .. } => {
assert_eq!(url, "rtsp://example.com/stream");
assert_eq!(*security, RtspSecurityPolicy::AllowInsecure);
}
_ => panic!("expected Rtsp variant"),
}
}
}