///|
pub struct Server {
priv device : Device
priv cov : CovManager
priv cache : Array[CachedReply]
priv mut now : UInt64
priv mut cache_bytes : Int32
}
///|
pub fn Server::new(device : Device) -> Server {
{ device, cov: CovManager::new(device), cache: [], now: 0UL, cache_bytes: 0, }
}
///|
pub fn Server::device(self : Server) -> Device {
self.device
}
///|
pub fn Server::notifications(
self : Server,
now : UInt64,
) -> Array[CovEnvelope] raise BacnetError {
self.clock(now)
self.cov.poll(now)
}
///|
fn Device::read_access(
self : Device,
access : Array[ReadAccess],
) -> Array[ReadAccessResult] raise BacnetError {
let output : Array[ReadAccessResult] = []
let mut total = 0
let mut retained = 0
for entry in access {
let results : Array[ReadResult] = []
for (property, index) in entry.properties {
let properties = if property == 8U && index == None {
self.all_property_ids(entry.object) catch {
Property(_, _) => [property]
error => raise error
}
} else {
[property]
}
for p in properties {
if total >= self.limits.max_nodes {
raise Limit("expanded read access results")
}
total += 1
let reference : PropertyRef = {
object: entry.object,
property: p,
index,
}
let result = PropertyData(reference, self.read(reference)) catch {
Property(class, code) => PropertyFailure(reference, class, code)
error => raise error
}
let cost = match result {
PropertyData(_, value) =>
encode_tags(value, limits=self.limits).length() + 20
PropertyFailure(_, _, _) => 24
}
if cost > self.limits.max_bytes - retained {
raise Limit("read access result bytes")
}
retained += cost
results.push(result)
}
}
output.push({ object: entry.object, results, })
}
output
}
///|
fn Server::confirmed(
self : Server,
request : Request,
peer : String,
now : UInt64,
) -> Apdu raise BacnetError {
match request.service {
12 => {
let reference = decode_read_property(
request.body,
limits=self.device.limits,
)
let value = self.device.read(reference)
ComplexAck(
request.invoke,
12,
encode_property_ack({ reference, value, }, limits=self.device.limits),
)
}
14 => {
let access = decode_read_multiple(request.body, limits=self.device.limits)
let results = self.device.read_access(access)
ComplexAck(
request.invoke,
14,
encode_multiple_ack(results, limits=self.device.limits),
)
}
15 => {
let write = decode_write_property(request.body, limits=self.device.limits)
self.device.write(write)
SimpleAck(request.invoke, 15)
}
5 => {
self.cov.subscribe(
peer,
decode_subscribe_cov(request.body, limits=self.device.limits),
now,
)
SimpleAck(request.invoke, 5)
}
_ => Reject(request.invoke, 9)
}
}
///|
pub fn Server::handle(
self : Server,
apdu : Apdu,
peer : String,
now : UInt64,
) -> Apdu? raise BacnetError {
self.clock(now)
let request_wire = encode_apdu(apdu, limits=self.device.limits)
if request_wire.length() > 1476 {
raise Limit("device receive APDU maximum")
}
match apdu {
Confirmed(request) => {
if peer.is_empty() {
raise Invalid("empty request peer", 0)
}
let peer_bytes = text_bytes(peer, 256).length()
for i = 0; i < self.cache.length(); i = i + 1 {
let cached = self.cache[i]
if cached.peer == peer && cached.invoke == request.invoke {
if cached.request == request_wire {
return Some(decode_apdu(cached.response, limits=self.device.limits))
}
self.cache_bytes -= cached.request.length() +
cached.response.length() +
text_bytes(cached.peer, 256).length()
self.cache.remove(i) |> ignore
break
}
}
let response = self.confirmed(request, peer, now) catch {
Property(class, code) =>
ErrorPdu(request.invoke, request.service, encode_error(class, code))
Invalid(_, _) => Reject(request.invoke, 4)
Unsupported(_) => Reject(request.invoke, 9)
Limit(_) => Abort(request.invoke, true, 1)
error => raise error
}
let response_wire = encode_apdu(response, limits=self.device.limits) catch {
Limit(_) =>
encode_apdu(Abort(request.invoke, true, 1), limits=self.device.limits)
error => raise error
}
let response_wire = if response_wire.length() >
max_apdu_size(request.max_apdu) {
encode_apdu(Abort(request.invoke, true, 11), limits=self.device.limits)
} else {
response_wire
}
let cost = request_wire.length() + response_wire.length() + peer_bytes
let budget = self.device.limits.max_bytes * 2 + 256
while !self.cache.is_empty() &&
(
self.cache.length() >= self.device.limits.max_pending ||
cost > budget - self.cache_bytes
) {
let old = self.cache[0]
self.cache_bytes -= old.request.length() +
old.response.length() +
text_bytes(old.peer, 256).length()
self.cache.remove(0) |> ignore
}
self.cache.push({
peer,
invoke: request.invoke,
request: request_wire,
response: response_wire,
at: now,
})
self.cache_bytes += cost
Some(decode_apdu(response_wire, limits=self.device.limits))
}
Unconfirmed(8, body) => {
let range = decode_who_is(body, limits=self.device.limits)
let instance = self.device.id.instance
if (range.low is Some(low) && instance < low) ||
(range.high is Some(high) && instance > high) {
return None
}
Some(
Unconfirmed(
0,
encode_i_am(self.device.announcement(), limits=self.device.limits),
),
)
}
_ => None
}
}
///|
pub fn Server::datagram(
self : Server,
bytes : Bytes,
peer : String,
now : UInt64,
) -> Bytes? raise BacnetError {
let (npdu_bytes, broadcast) = match
decode_bvlc(bytes, limits=self.device.limits) {
OriginalUnicast(payload) => (payload, false)
OriginalBroadcast(payload) => (payload, true)
_ =>
raise Unsupported(
"device host accepts only original local-network datagrams",
)
}
let npdu = decode_npdu(npdu_bytes, limits=self.device.limits)
if npdu.destination is Some(_) || npdu.source is Some(_) {
raise Unsupported("device host does not route")
}
if npdu.payload.length() > 1476 {
raise Limit("device receive APDU maximum")
}
let apdu = decode_apdu(npdu.payload, limits=self.device.limits)
if broadcast && apdu is Confirmed(_) {
return None
}
if npdu.expecting_reply != (apdu is Confirmed(_)) {
raise Invalid("NPDU expecting-reply mismatch", 1)
}
match self.handle(apdu, peer, now) {
None => None
Some(response) =>
Some(
encode_bvlc(
OriginalUnicast(
encode_npdu(
Npdu::for_local(encode_apdu(response, limits=self.device.limits)),
limits=self.device.limits,
),
),
limits=self.device.limits,
),
)
}
}
///|
pub fn encode_datagram(
apdu : Apdu,
broadcast~ : Bool = false,
limits~ : Limits = Limits::default(),
) -> Bytes raise BacnetError {
let expecting_reply = apdu is Confirmed(_)
let npdu = encode_npdu(
Npdu::for_local(encode_apdu(apdu, limits~), expecting_reply~),
limits~,
)
encode_bvlc(
if broadcast {
OriginalBroadcast(npdu)
} else {
OriginalUnicast(npdu)
},
limits~,
)
}
///|
pub fn decode_datagram(
bytes : Bytes,
limits~ : Limits = Limits::default(),
) -> Apdu raise BacnetError {
let payload = match decode_bvlc(bytes, limits~) {
OriginalUnicast(p)
| OriginalBroadcast(p)
| Forwarded(_, p)
| DistributeBroadcast(p) => p
_ => raise Invalid("BVLC has no application data", 0)
}
decode_apdu(decode_npdu(payload, limits~).payload, limits~)
}
///|
priv struct CachedReply {
peer : String
invoke : Int32
request : Bytes
response : Bytes
at : UInt64
}
///|
fn Server::clock(self : Server, now : UInt64) -> Unit raise BacnetError {
if now < self.now {
raise State("server host clock moved backwards")
}
self.now = now
for i = self.cache.length() - 1; i >= 0; i = i - 1 {
let entry = self.cache[i]
if now - entry.at >= 30UL {
self.cache_bytes -= entry.request.length() +
entry.response.length() +
text_bytes(entry.peer, 256).length()
self.cache.remove(i) |> ignore
}
}
}