mod objects;
mod server;
mod state;
mod wire;
use crate::{Error, Result};
use server::Shared;
use std::net::{Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::time::Duration;
pub const CIPLINK_OBJECT: u16 = 0x64;
pub const CIPLINK_SIGNATURE: &str = "ciplink";
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdapterIdentity {
pub vendor_id: u16,
pub device_type: u16,
pub product_code: u16,
pub revision: (u8, u8),
pub serial: u32,
pub product_name: String,
}
impl Default for AdapterIdentity {
fn default() -> Self {
AdapterIdentity {
vendor_id: 0,
device_type: 0x2B,
product_code: 1,
revision: (1, 1),
serial: 0,
product_name: "ciplink adapter".into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AssemblyKind {
Input,
Output,
Config,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Assembly {
pub instance: u32,
pub kind: AssemblyKind,
pub size: usize,
}
impl Assembly {
pub fn input(instance: u32, size: usize) -> Self {
Assembly {
instance,
kind: AssemblyKind::Input,
size,
}
}
pub fn output(instance: u32, size: usize) -> Self {
Assembly {
instance,
kind: AssemblyKind::Output,
size,
}
}
pub fn config(instance: u32, size: usize) -> Self {
Assembly {
instance,
kind: AssemblyKind::Config,
size,
}
}
}
#[derive(Clone, Debug)]
pub struct AdapterConfig {
pub identity: AdapterIdentity,
pub assemblies: Vec<Assembly>,
pub input_only_heartbeat: u32,
pub listen_only_heartbeat: u32,
pub eds: Option<Vec<u8>>,
pub eds_name: String,
pub app_name: String,
pub app_version: String,
pub bind: Ipv4Addr,
pub port: u16,
pub io_port: u16,
pub rpi_min: Duration,
pub rpi_max: Duration,
pub mac: [u8; 6],
}
impl Default for AdapterConfig {
fn default() -> Self {
AdapterConfig {
identity: AdapterIdentity::default(),
assemblies: Vec::new(),
input_only_heartbeat: 238,
listen_only_heartbeat: 237,
eds: None,
eds_name: "ciplink.eds".into(),
app_name: String::new(),
app_version: String::new(),
bind: Ipv4Addr::UNSPECIFIED,
port: 44818,
io_port: 2222,
rpi_min: Duration::from_millis(2),
rpi_max: Duration::from_millis(3200),
mac: [0; 6],
}
}
}
impl AdapterConfig {
fn validate(&self) -> Result<()> {
let id = &self.identity;
if id.product_name.len() > 32 || !id.product_name.is_ascii() {
return Err(Error::request(
"product_name must be ASCII, at most 32 characters",
));
}
for (i, a) in self.assemblies.iter().enumerate() {
if !(1..=0xFFFF).contains(&a.instance) {
return Err(Error::request(format!(
"assembly instance {} is not 1 to 65535",
a.instance
)));
}
if self.assemblies[..i]
.iter()
.any(|b| b.instance == a.instance)
|| [self.input_only_heartbeat, self.listen_only_heartbeat].contains(&a.instance)
{
return Err(Error::request(format!(
"assembly instance {} is used twice",
a.instance
)));
}
if a.kind != AssemblyKind::Config && a.size > 65_000 {
return Err(Error::request(format!(
"assembly {} of {} bytes is larger than a connection can carry",
a.instance, a.size
)));
}
}
if self.input_only_heartbeat == self.listen_only_heartbeat {
return Err(Error::request(
"the input-only and listen-only heartbeats must differ",
));
}
if self.rpi_min > self.rpi_max || self.rpi_min.is_zero() {
return Err(Error::request(
"rpi_min must be above 0 and at most rpi_max",
));
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConnectionKind {
ExclusiveOwner,
InputOnly,
ListenOnly,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConnectionInfo {
pub kind: ConnectionKind,
pub originator: Ipv4Addr,
pub o_t_rpi: Duration,
pub t_o_rpi: Duration,
pub input: u32,
pub input_size: usize,
pub output: Option<u32>,
pub output_size: usize,
pub multicast: bool,
pub o_t_id: u32,
pub t_o_id: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Event {
Opened(ConnectionInfo),
Closed(ConnectionInfo),
TimedOut(ConnectionInfo),
Config {
instance: u32,
data: Vec<u8>,
},
Output {
instance: u32,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Output {
pub data: Vec<u8>,
pub run: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Stats {
pub produced: u64,
pub late: u64,
pub max_late: Duration,
}
pub struct Adapter {
shared: Arc<Shared>,
server: Option<server::Threads>,
}
impl std::fmt::Debug for Adapter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Adapter")
.field("addr", &self.addr())
.field("io_addr", &self.io_addr())
.finish()
}
}
impl Adapter {
pub fn start(config: AdapterConfig) -> Result<Adapter> {
config.validate()?;
let (shared, threads) = server::start(config)?;
Ok(Adapter {
shared,
server: Some(threads),
})
}
pub fn addr(&self) -> SocketAddr {
self.shared.addr
}
pub fn io_addr(&self) -> SocketAddr {
self.shared.io_addr
}
pub fn set_input(&self, instance: u32, data: &[u8]) -> Result<()> {
self.shared
.lock()
.set_input(instance, data)
.map_err(Error::request)
}
pub fn input(&self, instance: u32) -> Result<Vec<u8>> {
self.assembly(instance, AssemblyKind::Input)
}
pub fn output(&self, instance: u32) -> Result<Output> {
self.shared
.lock()
.output(instance)
.ok_or_else(|| Error::request(format!("no output assembly {instance}")))
}
pub fn config(&self, instance: u32) -> Result<Vec<u8>> {
self.assembly(instance, AssemblyKind::Config)
}
fn assembly(&self, instance: u32, kind: AssemblyKind) -> Result<Vec<u8>> {
self.shared
.lock()
.assembly(instance)
.filter(|a| a.kind == kind)
.map(|a| a.data.clone())
.ok_or_else(|| Error::request(format!("no {kind:?} assembly {instance}")))
}
pub fn next_event(&self, timeout: Option<Duration>) -> Option<Event> {
self.shared.next_event(timeout)
}
pub fn connections(&self) -> Vec<ConnectionInfo> {
self.shared.lock().connections()
}
pub fn set_detail(&self, detail: &str) {
detail.clone_into(&mut self.shared.lock().detail);
}
pub fn stats(&self) -> Stats {
self.shared.lock().stats
}
pub fn stop(mut self) {
self.shutdown();
}
fn shutdown(&mut self) {
if let Some(threads) = self.server.take() {
threads.stop(&self.shared);
}
}
}
impl Drop for Adapter {
fn drop(&mut self) {
self.shutdown();
}
}