moonfire-nvr/server/src/json.rs

476 lines
13 KiB
Rust

// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use db::auth::SessionHash;
use failure::{format_err, Error};
use serde::ser::{Error as _, SerializeMap, SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::ops::Not;
use uuid::Uuid;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TopLevel<'a> {
pub time_zone_name: &'a str,
// Use a custom serializer which presents the map's values as a sequence and includes the
// "days" and "camera_configs" attributes or not, according to the respective bools.
#[serde(serialize_with = "TopLevel::serialize_cameras")]
pub cameras: (&'a db::LockedDatabase, bool, bool),
#[serde(skip_serializing_if = "Option::is_none")]
pub session: Option<Session>,
#[serde(serialize_with = "TopLevel::serialize_signals")]
pub signals: (&'a db::LockedDatabase, bool),
#[serde(serialize_with = "TopLevel::serialize_signal_types")]
pub signal_types: &'a db::LockedDatabase,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Session {
pub username: String,
#[serde(serialize_with = "Session::serialize_csrf")]
pub csrf: SessionHash,
}
impl Session {
fn serialize_csrf<S>(csrf: &SessionHash, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut tmp = [0u8; 32];
csrf.encode_base64(&mut tmp);
serializer.serialize_str(::std::str::from_utf8(&tmp[..]).expect("base64 is UTF-8"))
}
}
/// JSON serialization wrapper for a single camera when processing `/api/` and
/// `/api/cameras/<uuid>/`. See `design/api.md` for details.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Camera<'a> {
pub uuid: Uuid,
pub short_name: &'a str,
pub description: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<CameraConfig<'a>>,
#[serde(serialize_with = "Camera::serialize_streams")]
pub streams: [Option<Stream<'a>>; 2],
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CameraConfig<'a> {
pub onvif_host: &'a str,
pub username: &'a str,
pub password: &'a str,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Stream<'a> {
pub retain_bytes: i64,
pub min_start_time_90k: Option<i64>,
pub max_end_time_90k: Option<i64>,
pub total_duration_90k: i64,
pub total_sample_file_bytes: i64,
pub fs_bytes: i64,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(serialize_with = "Stream::serialize_days")]
pub days: Option<BTreeMap<db::StreamDayKey, db::StreamDayValue>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<StreamConfig<'a>>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamConfig<'a> {
pub rtsp_url: &'a str,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Signal<'a> {
pub id: u32,
#[serde(serialize_with = "Signal::serialize_cameras")]
pub cameras: (&'a db::Signal, &'a db::LockedDatabase),
pub source: Uuid,
pub type_: Uuid,
pub short_name: &'a str,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PostSignalsEndBase {
Epoch,
Now,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginRequest<'a> {
pub username: &'a str,
pub password: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LogoutRequest<'a> {
pub csrf: &'a str,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PostSignalsRequest {
pub signal_ids: Vec<u32>,
pub states: Vec<u16>,
pub start_time_90k: Option<i64>,
pub end_base: PostSignalsEndBase,
pub rel_end_time_90k: Option<i64>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PostSignalsResponse {
pub time_90k: i64,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Signals {
pub times_90k: Vec<i64>,
pub signal_ids: Vec<u32>,
pub states: Vec<u16>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignalType<'a> {
pub uuid: Uuid,
#[serde(serialize_with = "SignalType::serialize_states")]
pub states: &'a db::signal::Type,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SignalTypeState<'a> {
value: u16,
name: &'a str,
#[serde(skip_serializing_if = "Not::not")]
motion: bool,
color: &'a str,
}
impl<'a> Camera<'a> {
pub fn wrap(
c: &'a db::Camera,
db: &'a db::LockedDatabase,
include_days: bool,
include_config: bool,
) -> Result<Self, Error> {
Ok(Camera {
uuid: c.uuid,
short_name: &c.short_name,
description: &c.description,
config: match include_config {
false => None,
true => Some(CameraConfig {
onvif_host: &c.onvif_host,
username: &c.username,
password: &c.password,
}),
},
streams: [
Stream::wrap(db, c.streams[0], include_days, include_config)?,
Stream::wrap(db, c.streams[1], include_days, include_config)?,
],
})
}
fn serialize_streams<S>(streams: &[Option<Stream>; 2], serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(streams.len()))?;
for (i, s) in streams.iter().enumerate() {
if let &Some(ref s) = s {
map.serialize_key(
db::StreamType::from_index(i)
.expect("invalid stream type index")
.as_str(),
)?;
map.serialize_value(s)?;
}
}
map.end()
}
}
impl<'a> Stream<'a> {
fn wrap(
db: &'a db::LockedDatabase,
id: Option<i32>,
include_days: bool,
include_config: bool,
) -> Result<Option<Self>, Error> {
let id = match id {
Some(id) => id,
None => return Ok(None),
};
let s = db
.streams_by_id()
.get(&id)
.ok_or_else(|| format_err!("missing stream {}", id))?;
Ok(Some(Stream {
retain_bytes: s.retain_bytes,
min_start_time_90k: s.range.as_ref().map(|r| r.start.0),
max_end_time_90k: s.range.as_ref().map(|r| r.end.0),
total_duration_90k: s.duration.0,
total_sample_file_bytes: s.sample_file_bytes,
fs_bytes: s.fs_bytes,
days: if include_days { Some(s.days()) } else { None },
config: match include_config {
false => None,
true => Some(StreamConfig {
rtsp_url: &s.rtsp_url,
}),
},
}))
}
fn serialize_days<S>(
days: &Option<BTreeMap<db::StreamDayKey, db::StreamDayValue>>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let days = match days.as_ref() {
Some(d) => d,
None => return serializer.serialize_none(),
};
let mut map = serializer.serialize_map(Some(days.len()))?;
for (k, v) in days {
map.serialize_key(k.as_ref())?;
let bounds = k.bounds();
map.serialize_value(&StreamDayValue {
start_time_90k: bounds.start.0,
end_time_90k: bounds.end.0,
total_duration_90k: v.duration.0,
})?;
}
map.end()
}
}
impl<'a> Signal<'a> {
pub fn wrap(s: &'a db::Signal, db: &'a db::LockedDatabase, _include_days: bool) -> Self {
Signal {
id: s.id,
cameras: (s, db),
source: s.source,
type_: s.type_,
short_name: &s.short_name,
}
}
fn serialize_cameras<S>(
cameras: &(&db::Signal, &db::LockedDatabase),
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (s, db) = cameras;
let mut map = serializer.serialize_map(Some(s.cameras.len()))?;
for sc in &s.cameras {
let c = db.cameras_by_id().get(&sc.camera_id).ok_or_else(|| {
S::Error::custom(format!("signal has missing camera id {}", sc.camera_id))
})?;
map.serialize_key(&c.uuid)?;
map.serialize_value(match sc.type_ {
db::signal::SignalCameraType::Direct => "direct",
db::signal::SignalCameraType::Indirect => "indirect",
})?;
}
map.end()
}
}
impl<'a> SignalType<'a> {
pub fn wrap(uuid: Uuid, type_: &'a db::signal::Type) -> Self {
SignalType {
uuid,
states: type_,
}
}
fn serialize_states<S>(type_: &db::signal::Type, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(type_.states.len()))?;
for s in &type_.states {
seq.serialize_element(&SignalTypeState::wrap(s))?;
}
seq.end()
}
}
impl<'a> SignalTypeState<'a> {
pub fn wrap(s: &'a db::signal::TypeState) -> Self {
SignalTypeState {
value: s.value,
name: &s.name,
motion: s.motion,
color: &s.color,
}
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct StreamDayValue {
pub start_time_90k: i64,
pub end_time_90k: i64,
pub total_duration_90k: i64,
}
impl<'a> TopLevel<'a> {
/// Serializes cameras as a list (rather than a map), optionally including the `days` and
/// `cameras` fields.
fn serialize_cameras<S>(
cameras: &(&db::LockedDatabase, bool, bool),
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (db, include_days, include_config) = *cameras;
let cs = db.cameras_by_id();
let mut seq = serializer.serialize_seq(Some(cs.len()))?;
for (_, c) in cs {
seq.serialize_element(
&Camera::wrap(c, db, include_days, include_config)
.map_err(|e| S::Error::custom(e))?,
)?;
}
seq.end()
}
/// Serializes signals as a list (rather than a map), optionally including the `days` field.
fn serialize_signals<S>(
signals: &(&db::LockedDatabase, bool),
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (db, include_days) = *signals;
let ss = db.signals_by_id();
let mut seq = serializer.serialize_seq(Some(ss.len()))?;
for (_, s) in ss {
seq.serialize_element(&Signal::wrap(s, db, include_days))?;
}
seq.end()
}
/// Serializes signals as a list (rather than a map), optionally including the `days` field.
fn serialize_signal_types<S>(db: &db::LockedDatabase, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let ss = db.signal_types_by_uuid();
let mut seq = serializer.serialize_seq(Some(ss.len()))?;
for (u, t) in ss {
seq.serialize_element(&SignalType::wrap(*u, t))?;
}
seq.end()
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListRecordings<'a> {
pub recordings: Vec<Recording>,
// There are likely very few video sample entries for a given stream in a given day, so
// representing with an unordered Vec (and having O(n) insert-if-absent) is probably better
// than dealing with a HashSet's code bloat.
#[serde(serialize_with = "ListRecordings::serialize_video_sample_entries")]
pub video_sample_entries: (&'a db::LockedDatabase, Vec<i32>),
}
impl<'a> ListRecordings<'a> {
fn serialize_video_sample_entries<S>(
video_sample_entries: &(&db::LockedDatabase, Vec<i32>),
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let (db, ref v) = *video_sample_entries;
let mut map = serializer.serialize_map(Some(v.len()))?;
for id in v {
map.serialize_entry(
id,
&VideoSampleEntry::from(&db.video_sample_entries_by_id().get(id).unwrap()),
)?;
}
map.end()
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Recording {
pub start_time_90k: i64,
pub end_time_90k: i64,
pub sample_file_bytes: i64,
pub video_samples: i64,
pub video_sample_entry_id: String,
pub start_id: i32,
pub open_id: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_uncommitted: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub end_id: Option<i32>,
#[serde(skip_serializing_if = "Not::not")]
pub growing: bool,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoSampleEntry {
pub width: u16,
pub height: u16,
pub pasp_h_spacing: u16,
pub pasp_v_spacing: u16,
}
impl VideoSampleEntry {
fn from(e: &db::VideoSampleEntry) -> Self {
Self {
width: e.width,
height: e.height,
pasp_h_spacing: e.pasp_h_spacing,
pasp_v_spacing: e.pasp_v_spacing,
}
}
}