// This file is part of Moonfire NVR, a security camera network video recorder.
// Copyright (C) 2016-2020 The Moonfire NVR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// In addition, as a special exception, the copyright holders give
// permission to link the code of portions of this program with the
// OpenSSL library under certain conditions as described in each
// individual source file, and distribute linked combinations including
// the two.
//
// You must obey the GNU General Public License in all respects for all
// of the code used other than OpenSSL. If you modify file(s) with this
// exception, you may extend this exception to your version of the
// file(s), but you are not obligated to do so. If you do not wish to do
// so, delete this exception statement from your version. If you delete
// this exception statement from all source files in the program, then
// also delete it here.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
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,
#[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(csrf: &SessionHash, serializer: S) -> Result
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//`. 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>,
#[serde(serialize_with = "Camera::serialize_streams")]
pub streams: [Option>; 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,
pub max_end_time_90k: Option,
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>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option>,
}
#[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,
pub states: Vec,
pub start_time_90k: Option,
pub end_base: PostSignalsEndBase,
pub rel_end_time_90k: Option,
}
#[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,
pub signal_ids: Vec,
pub states: Vec,
}
#[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 {
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(streams: &[Option; 2], serializer: S) -> Result
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,
include_days: bool,
include_config: bool,
) -> Result