///|
/// Configuration for an in-process Modbus server.
pub(all) struct ServerConfig {
mode : Mode
max_devices : Int
max_frame : Int
allow_broadcast : Bool
max_pending_bytes : Int
}
///|
pub fn default_server_config(mode : Mode) -> ServerConfig {
{
mode,
max_devices: 247,
max_frame: limits_for(mode).max_adu,
allow_broadcast: true,
max_pending_bytes: 4096,
}
}
///|
pub fn validate_server_config(
config : ServerConfig,
) -> Result[Unit, ModbusError] {
if config.max_devices < 1 ||
config.max_devices > 247 ||
config.max_frame < minimum_frame_length(config.mode) ||
config.max_pending_bytes < config.max_frame {
Err(InvalidData)
} else {
Ok(())
}
}
///|
/// A multi-unit server that owns device instances and performs transport framing.
pub struct Server {
config : ServerConfig
devices : Array[Device]
parser : IncrementalParser
metrics : ProtocolMetrics
audit : AuditLog
}
///|
pub fn Server::new(config : ServerConfig) -> Result[Server, ModbusError] {
match validate_server_config(config) {
Err(error) => return Err(error)
Ok(_) => ()
}
let metrics = match ProtocolMetrics::new() {
Ok(value) => value
Err(error) => return Err(error)
}
let audit = match AuditLog::new() {
Ok(value) => value
Err(error) => return Err(error)
}
Ok({
config,
devices: [],
parser: IncrementalParser::new(
config.mode,
max_frame=config.max_frame,
role=Request,
),
metrics,
audit,
})
}
///|
pub fn Server::config(self : Server) -> ServerConfig {
self.config
}
///|
pub fn Server::mode(self : Server) -> Mode {
self.config.mode
}
///|
pub fn Server::add_device(
self : Server,
device : Device,
) -> Result[Unit, ModbusError] {
if self.devices.length() >= self.config.max_devices {
return Err(CapacityExceeded)
}
if self.find_device(device.unit_id()) is Some(_) {
return Err(InvalidUnitId)
}
self.devices.push(device)
Ok(())
}
///|
pub fn Server::remove_device(
self : Server,
unit_id : Byte,
) -> Result[Unit, ModbusError] {
let index = match self.find_device_index(unit_id) {
Some(value) => value
None => return Err(InvalidUnitId)
}
let retained : Array[Device] = []
for current in 0.. Int {
self.devices.length()
}
///|
pub fn Server::device(self : Server, unit_id : Byte) -> Device? {
self.find_device(unit_id)
}
///|
/// Handle a decoded request and return a decoded reply.
pub fn Server::handle(
self : Server,
request : Frame,
) -> Result[Frame, ModbusError] {
let device = match self.find_device(request.unit_id) {
Some(value) => value
None => {
if request.unit_id == 0 && self.config.allow_broadcast {
if self.devices.length() == 0 {
return Err(NoResponse)
}
return self.handle_broadcast(request)
}
return Err(UnitMismatch)
}
}
match device.handle(request) {
Ok(response) => {
let _ = self.metrics.record_response(
encoded_length(self.config.mode, response),
1,
response.is_exception(),
)
self.audit.record("request", self.config.mode, 0, request, "handled")
self.audit.record(
"response",
self.config.mode,
0,
response,
response_quality(request, response),
)
Ok(response)
}
Err(error) => {
self.metrics.record_error()
self.audit.record(
"request",
self.config.mode,
0,
request,
error_name(error),
)
Err(error)
}
}
}
///|
/// Decode an ADU, dispatch it, and encode the reply.
pub fn Server::handle_adu(
self : Server,
bytes : Array[Byte],
) -> Result[Array[Byte], ModbusError] {
let (transaction_id, request) = match
decode_transaction(self.config.mode, bytes) {
Ok(value) => value
Err(error) => return Err(error)
}
let response = match self.handle(request) {
Ok(value) => value
Err(error) => return Err(error)
}
encode_checked_response(self.config.mode, transaction_id, response)
}
///|
fn encode_checked_response(
mode : Mode,
transaction_id : UInt16,
response : Frame,
) -> Result[Array[Byte], ModbusError] {
match mode {
Rtu => Ok(encode_rtu(response))
Ascii => Ok(encode_ascii(response))
Tcp => Ok(encode_tcp(transaction_id, response))
}
}
///|
fn Server::handle_broadcast(
self : Server,
request : Frame,
) -> Result[Frame, ModbusError] {
let mut last_error : ModbusError? = None
for device in self.devices {
match device.serve(request) {
Ok(_) => ()
Err(error) => last_error = Some(error)
}
}
match last_error {
Some(error) => Err(error)
None => Err(NoResponse)
}
}
///|
fn Server::find_device_index(self : Server, unit_id : Byte) -> Int? {
for index in 0.. Device? {
match self.find_device_index(unit_id) {
Some(index) => Some(self.devices[index])
None => None
}
}
///|
pub fn Server::parser(self : Server) -> IncrementalParser {
self.parser
}
///|
pub fn Server::metrics(self : Server) -> ProtocolMetrics {
self.metrics
}
///|
pub fn Server::audit(self : Server) -> AuditLog {
self.audit
}
///|
/// Run a request against every configured unit and return normal replies.
pub fn Server::broadcast(self : Server, request : Frame) -> Array[Frame] {
let replies : Array[Frame] = []
for device in self.devices {
match device.serve(request) {
Ok(Reply(response)) => replies.push(response)
_ => ()
}
}
replies
}