use crate::error::{CaError, CaResult};
pub const DBR_STRING: u16 = 0;
pub const DBR_SHORT: u16 = 1;
pub const DBR_FLOAT: u16 = 2;
pub const DBR_ENUM: u16 = 3;
pub const DBR_CHAR: u16 = 4;
pub const DBR_LONG: u16 = 5;
pub const DBR_DOUBLE: u16 = 6;
pub const DBR_INT: u16 = DBR_SHORT;
pub const DBR_STS_STRING: u16 = 7;
pub const DBR_STS_SHORT: u16 = 8;
pub const DBR_STS_FLOAT: u16 = 9;
pub const DBR_STS_ENUM: u16 = 10;
pub const DBR_STS_CHAR: u16 = 11;
pub const DBR_STS_LONG: u16 = 12;
pub const DBR_STS_DOUBLE: u16 = 13;
pub const DBR_STS_INT: u16 = DBR_STS_SHORT;
pub const DBR_TIME_STRING: u16 = 14;
pub const DBR_TIME_SHORT: u16 = 15;
pub const DBR_TIME_FLOAT: u16 = 16;
pub const DBR_TIME_ENUM: u16 = 17;
pub const DBR_TIME_CHAR: u16 = 18;
pub const DBR_TIME_LONG: u16 = 19;
pub const DBR_TIME_DOUBLE: u16 = 20;
pub const DBR_TIME_INT: u16 = DBR_TIME_SHORT;
pub const DBR_GR_STRING: u16 = 21;
pub const DBR_GR_SHORT: u16 = 22;
pub const DBR_GR_FLOAT: u16 = 23;
pub const DBR_GR_ENUM: u16 = 24;
pub const DBR_GR_CHAR: u16 = 25;
pub const DBR_GR_LONG: u16 = 26;
pub const DBR_GR_DOUBLE: u16 = 27;
pub const DBR_GR_INT: u16 = DBR_GR_SHORT;
pub const DBR_CTRL_STRING: u16 = 28;
pub const DBR_CTRL_SHORT: u16 = 29;
pub const DBR_CTRL_FLOAT: u16 = 30;
pub const DBR_CTRL_ENUM: u16 = 31;
pub const DBR_CTRL_CHAR: u16 = 32;
pub const DBR_CTRL_LONG: u16 = 33;
pub const DBR_CTRL_DOUBLE: u16 = 34;
pub const DBR_CTRL_INT: u16 = DBR_CTRL_SHORT;
pub const DBR_PUT_ACKT: u16 = 35;
pub const DBR_PUT_ACKS: u16 = 36;
pub const DBR_STSACK_STRING: u16 = 37;
pub const DBR_CLASS_NAME: u16 = 38;
pub const LAST_BUFFER_TYPE: u16 = DBR_CLASS_NAME;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u16)]
pub enum DbFieldType {
String = 0,
Short = 1, Float = 2,
Enum = 3,
Char = 4, Long = 5, Double = 6,
Int64 = 7,
UInt64 = 8,
UShort = 9,
ULong = 10,
}
impl DbFieldType {
pub fn from_u16(v: u16) -> CaResult<Self> {
match v {
0 => Ok(Self::String),
1 => Ok(Self::Short),
2 => Ok(Self::Float),
3 => Ok(Self::Enum),
4 => Ok(Self::Char),
5 => Ok(Self::Long),
6 => Ok(Self::Double),
_ => Err(CaError::UnsupportedType(v)),
}
}
pub fn element_size(&self) -> usize {
match self {
Self::String => 40, Self::Short | Self::Enum | Self::UShort => 2,
Self::Float | Self::Long | Self::ULong => 4,
Self::Char => 1,
Self::Double | Self::Int64 | Self::UInt64 => 8,
}
}
fn ca_wire_type(&self) -> u16 {
match self {
Self::Int64 | Self::UInt64 | Self::ULong => Self::Double as u16,
Self::UShort => Self::Long as u16,
other => *other as u16,
}
}
pub fn sts_dbr_type(&self) -> u16 {
self.ca_wire_type() + 7
}
pub fn time_dbr_type(&self) -> u16 {
self.ca_wire_type() + 14
}
pub fn gr_dbr_type(&self) -> u16 {
self.ca_wire_type() + 21
}
pub fn ctrl_dbr_type(&self) -> u16 {
self.ca_wire_type() + 28
}
pub fn buffer_size(&self, count: usize) -> usize {
self.element_size() * count
}
pub fn to_dbr_type(&self) -> DbFieldType {
*self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum DbfLinkClass {
InLink = DBF_INLINK,
OutLink = DBF_OUTLINK,
FwdLink = DBF_FWDLINK,
}
pub const DBF_INLINK: u8 = 14;
pub const DBF_OUTLINK: u8 = 15;
pub const DBF_FWDLINK: u8 = 16;
impl DbfLinkClass {
pub fn dbf_type(self) -> u8 {
self as u8
}
}
pub fn is_link_dbf_type(dbf_type: u8) -> bool {
(DBF_INLINK..=DBF_FWDLINK).contains(&dbf_type)
}
pub fn dbf_link_class(record_type: &str, field: &str) -> Option<DbfLinkClass> {
use DbfLinkClass::*;
let f = field.trim().to_ascii_uppercase();
match f.as_str() {
"FLNK" => return Some(FwdLink),
"SDIS" | "TSEL" | "INP" | "DOL" | "SIML" | "NVL" | "SVL" | "SUBL" | "SELL" => {
return Some(InLink);
}
"OUT" => return Some(OutLink),
"SIOL" => {
return Some(if is_output_record_type(record_type) {
OutLink
} else {
InLink
});
}
_ => {}
}
let one_alnum = |rest: &str| rest.len() == 1 && rest.as_bytes()[0].is_ascii_alphanumeric();
let lnk_class = if is_fanout_link_record(record_type) {
FwdLink
} else {
OutLink
};
for (prefix, class) in [
("INP", InLink),
("DOL", InLink),
("OUT", OutLink),
("LNK", lnk_class),
] {
if let Some(rest) = f.strip_prefix(prefix) {
if one_alnum(rest) {
return Some(class);
}
}
}
None
}
fn is_output_record_type(record_type: &str) -> bool {
matches!(
record_type,
"ao" | "bo" | "longout" | "int64out" | "mbbo" | "mbboDirect" | "stringout" | "lso" | "aao"
)
}
fn is_fanout_link_record(record_type: &str) -> bool {
record_type == "fanout"
}
pub fn dbr_buffer_size(dbr_type: u16, native_type: DbFieldType, count: usize) -> usize {
if dbr_type == DBR_CLASS_NAME {
return 40;
}
let value_size = native_type.element_size() * count;
crate::types::codec::dbr_meta_size(dbr_type, native_type) + value_size
}
fn dbr_native_index(dbr_type: u16) -> Option<u16> {
match dbr_type {
0..=6 => Some(dbr_type),
7..=13 => Some(dbr_type - 7),
14..=20 => Some(dbr_type - 14),
21..=27 => Some(dbr_type - 21),
28..=34 => Some(dbr_type - 28),
35 | 36 => Some(1), 37 => Some(0), 38 => Some(0),
_ => None,
}
}
pub fn native_type_for_dbr(dbr_type: u16) -> CaResult<DbFieldType> {
match dbr_native_index(dbr_type) {
Some(idx) => DbFieldType::from_u16(idx),
None => Err(CaError::UnsupportedType(dbr_type)),
}
}
const DBR_TEXT: [&str; (LAST_BUFFER_TYPE + 1) as usize] = [
"DBR_STRING",
"DBR_SHORT",
"DBR_FLOAT",
"DBR_ENUM",
"DBR_CHAR",
"DBR_LONG",
"DBR_DOUBLE",
"DBR_STS_STRING",
"DBR_STS_SHORT",
"DBR_STS_FLOAT",
"DBR_STS_ENUM",
"DBR_STS_CHAR",
"DBR_STS_LONG",
"DBR_STS_DOUBLE",
"DBR_TIME_STRING",
"DBR_TIME_SHORT",
"DBR_TIME_FLOAT",
"DBR_TIME_ENUM",
"DBR_TIME_CHAR",
"DBR_TIME_LONG",
"DBR_TIME_DOUBLE",
"DBR_GR_STRING",
"DBR_GR_SHORT",
"DBR_GR_FLOAT",
"DBR_GR_ENUM",
"DBR_GR_CHAR",
"DBR_GR_LONG",
"DBR_GR_DOUBLE",
"DBR_CTRL_STRING",
"DBR_CTRL_SHORT",
"DBR_CTRL_FLOAT",
"DBR_CTRL_ENUM",
"DBR_CTRL_CHAR",
"DBR_CTRL_LONG",
"DBR_CTRL_DOUBLE",
"DBR_PUT_ACKT",
"DBR_PUT_ACKS",
"DBR_STSACK_STRING",
"DBR_CLASS_NAME",
];
pub fn dbr_text_to_type(text: &str) -> Option<u16> {
DBR_TEXT.iter().position(|&n| n == text).map(|i| i as u16)
}
#[cfg(test)]
mod buffer_size_tests {
use super::*;
#[test]
fn sts_double_meta_is_8() {
assert_eq!(dbr_buffer_size(DBR_STS_DOUBLE, DbFieldType::Double, 1), 16);
assert_eq!(
dbr_buffer_size(DBR_STS_DOUBLE, DbFieldType::Double, 5),
8 + 8 * 5
);
}
#[test]
fn sts_char_meta_is_5() {
assert_eq!(dbr_buffer_size(DBR_STS_CHAR, DbFieldType::Char, 1), 6);
assert_eq!(dbr_buffer_size(DBR_STS_CHAR, DbFieldType::Char, 10), 5 + 10);
}
#[test]
fn sts_short_meta_is_4() {
assert_eq!(dbr_buffer_size(DBR_STS_SHORT, DbFieldType::Short, 1), 6);
assert_eq!(dbr_buffer_size(DBR_STS_LONG, DbFieldType::Long, 1), 8);
assert_eq!(dbr_buffer_size(DBR_STS_FLOAT, DbFieldType::Float, 1), 8);
}
#[test]
fn plain_value_size_only() {
assert_eq!(dbr_buffer_size(DBR_DOUBLE, DbFieldType::Double, 3), 24);
}
#[test]
fn time_meta_includes_risc_pad() {
assert_eq!(dbr_buffer_size(DBR_TIME_DOUBLE, DbFieldType::Double, 1), 24);
assert_eq!(dbr_buffer_size(DBR_TIME_SHORT, DbFieldType::Short, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_ENUM, DbFieldType::Enum, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_CHAR, DbFieldType::Char, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_FLOAT, DbFieldType::Float, 1), 16);
assert_eq!(dbr_buffer_size(DBR_TIME_LONG, DbFieldType::Long, 1), 16);
assert_eq!(
dbr_buffer_size(DBR_TIME_DOUBLE, DbFieldType::Double, 4),
16 + 8 * 4
);
}
#[test]
fn gr_ctrl_meta_is_per_type() {
assert_eq!(dbr_buffer_size(DBR_GR_SHORT, DbFieldType::Short, 1), 24 + 2);
assert_eq!(dbr_buffer_size(DBR_GR_FLOAT, DbFieldType::Float, 1), 40 + 4);
assert_eq!(
dbr_buffer_size(DBR_GR_DOUBLE, DbFieldType::Double, 1),
64 + 8
);
assert_eq!(dbr_buffer_size(DBR_GR_CHAR, DbFieldType::Char, 1), 19 + 1);
assert_eq!(dbr_buffer_size(DBR_GR_LONG, DbFieldType::Long, 1), 36 + 4);
assert_eq!(dbr_buffer_size(DBR_GR_ENUM, DbFieldType::Enum, 1), 422 + 2);
assert_eq!(
dbr_buffer_size(DBR_CTRL_DOUBLE, DbFieldType::Double, 1),
80 + 8
);
assert_eq!(
dbr_buffer_size(DBR_CTRL_SHORT, DbFieldType::Short, 1),
28 + 2
);
assert_eq!(dbr_buffer_size(DBR_CTRL_CHAR, DbFieldType::Char, 1), 21 + 1);
}
}
#[cfg(test)]
mod dbf_link_class_tests {
use super::*;
#[test]
fn dbcommon_links_classified_uniformly() {
assert_eq!(dbf_link_class("ai", "FLNK"), Some(DbfLinkClass::FwdLink));
assert_eq!(dbf_link_class("ao", "SDIS"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("calc", "TSEL"), Some(DbfLinkClass::InLink));
}
#[test]
fn record_specific_link_families_the_old_name_list_missed() {
assert_eq!(dbf_link_class("seq", "DOL0"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "LNK0"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("seq", "DOLA"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "DOLF"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("seq", "LNKF"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("sel", "NVL"), Some(DbfLinkClass::InLink));
assert_eq!(
dbf_link_class("histogram", "SVL"),
Some(DbfLinkClass::InLink)
);
assert_eq!(dbf_link_class("calc", "INPA"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("aSub", "INPU"), Some(DbfLinkClass::InLink));
assert_eq!(
dbf_link_class("fanout", "OUTA"),
Some(DbfLinkClass::OutLink)
);
assert_eq!(dbf_link_class("printf", "INP0"), Some(DbfLinkClass::InLink));
}
#[test]
fn siol_class_depends_on_record_direction() {
assert_eq!(dbf_link_class("bo", "SIOL"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("ao", "SIOL"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("ai", "SIOL"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("bi", "SIOL"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("bo", "SIML"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("ai", "SIML"), Some(DbfLinkClass::InLink));
}
#[test]
fn lnk_class_depends_on_record_type() {
assert_eq!(
dbf_link_class("fanout", "LNK0"),
Some(DbfLinkClass::FwdLink),
"fanout LNK0 is DBF_FWDLINK (16), not OUTLINK"
);
assert_eq!(
dbf_link_class("fanout", "LNKF"),
Some(DbfLinkClass::FwdLink),
"fanout LNKF is DBF_FWDLINK (16), not OUTLINK"
);
assert_eq!(dbf_link_class("seq", "LNK0"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("seq", "LNKF"), Some(DbfLinkClass::OutLink));
assert_eq!(dbf_link_class("sseq", "LNK1"), Some(DbfLinkClass::OutLink));
assert_eq!(
dbf_link_class("fanout", "FLNK"),
Some(DbfLinkClass::FwdLink)
);
}
#[test]
fn plain_value_fields_are_not_links() {
for f in [
"VAL", "EGU", "PREC", "HOPR", "RVAL", "DESC", "A", "B", "OVAL",
] {
assert_eq!(dbf_link_class("ai", f), None, "{f} must not be a link");
}
}
#[test]
fn case_insensitive_and_trims() {
assert_eq!(dbf_link_class("ai", "inp"), Some(DbfLinkClass::InLink));
assert_eq!(dbf_link_class("ao", " OUT "), Some(DbfLinkClass::OutLink));
}
#[test]
fn dbf_codes_and_range_match_pvxs() {
assert_eq!(DbfLinkClass::InLink.dbf_type(), 14);
assert_eq!(DbfLinkClass::OutLink.dbf_type(), 15);
assert_eq!(DbfLinkClass::FwdLink.dbf_type(), 16);
assert!(is_link_dbf_type(DBF_INLINK));
assert!(is_link_dbf_type(DBF_OUTLINK));
assert!(is_link_dbf_type(DBF_FWDLINK));
assert!(!is_link_dbf_type(13)); assert!(!is_link_dbf_type(17)); assert!(!is_link_dbf_type(0)); }
}
#[cfg(test)]
mod dbr_text_tests {
use super::*;
#[test]
fn resolves_every_type_name_to_its_code() {
for (code, name) in DBR_TEXT.iter().enumerate() {
assert_eq!(
dbr_text_to_type(name),
Some(code as u16),
"{name} should resolve to {code}"
);
}
assert_eq!(dbr_text_to_type("DBR_STRING"), Some(DBR_STRING));
assert_eq!(dbr_text_to_type("DBR_TIME_FLOAT"), Some(DBR_TIME_FLOAT));
assert_eq!(
dbr_text_to_type("DBR_STSACK_STRING"),
Some(DBR_STSACK_STRING)
);
assert_eq!(dbr_text_to_type("DBR_CLASS_NAME"), Some(DBR_CLASS_NAME));
}
#[test]
fn is_case_sensitive_like_c_strcmp() {
assert_eq!(dbr_text_to_type("dbr_time_float"), None);
assert_eq!(dbr_text_to_type("DBR_Time_Float"), None);
}
#[test]
fn unknown_and_bare_family_names_do_not_match() {
assert_eq!(dbr_text_to_type("TIME_FLOAT"), None);
assert_eq!(dbr_text_to_type("DOUBLE"), None);
assert_eq!(dbr_text_to_type("DBR_NONSENSE"), None);
assert_eq!(dbr_text_to_type(""), None);
}
}