///|
fn text_bytes(text : String, maximum : Int32) -> Bytes raise BacnetError {
let mut size = 0
let mut i = 0
while i < text.length() {
let c = text.unsafe_get(i).to_int()
if c >= 0xd800 && c <= 0xdbff {
if i + 1 >= text.length() {
raise Invalid("unpaired surrogate", i)
}
let next = text.unsafe_get(i + 1).to_int()
if next < 0xdc00 || next > 0xdfff {
raise Invalid("unpaired surrogate", i)
}
size += 4
i += 2
} else {
if c >= 0xdc00 && c <= 0xdfff {
raise Invalid("unpaired surrogate", i)
}
size += if c < 128 { 1 } else if c < 2048 { 2 } else { 3 }
i += 1
}
if size > maximum {
raise Limit("text bytes")
}
}
@utf8.encode(text)
}
///|
fn application_body(
value : Value,
limits : Limits,
) -> (Int32, Bytes) raise BacnetError {
let w = Writer::new(limits.max_bytes)
let kind = match value {
Null => 0
Boolean(_) => 1
Unsigned(v) => {
w.put(unsigned_bytes(v))
2
}
Signed(v) => {
w.put(signed_bytes(v))
3
}
Real(v) => {
w.uint(v.reinterpret_as_uint(), 4)
4
}
DoubleValue(v) => {
let bits = v.reinterpret_as_uint64()
w.uint((bits >> 32).to_uint(), 4)
w.uint(bits.to_uint(), 4)
5
}
Octets(b) => {
w.put(b)
6
}
Text(s) => {
w.u8(0)
w.put(text_bytes(s, limits.max_bytes - 1))
7
}
Bits(unused, b) => {
if !bits_valid(unused, b) {
raise Invalid("bit string padding", 0)
}
w.u8(unused)
w.put(b)
8
}
Enumerated(v) => {
w.put(unsigned_bytes(v))
9
}
DateValue(d) => {
if !date_valid(d) {
raise Invalid("date components", 0)
}
w.u8(d.year)
w.u8(d.month)
w.u8(d.day)
w.u8(d.weekday)
10
}
TimeValue(t) => {
if !time_valid(t) {
raise Invalid("time components", 0)
}
w.u8(t.hour)
w.u8(t.minute)
w.u8(t.second)
w.u8(t.hundredth)
11
}
Object(id) => {
w.uint(id.number(), 4)
12
}
}
(kind, w.finish())
}
///|
fn application_value(
kind : Int32,
body : Bytes,
limits : Limits,
) -> Value raise BacnetError {
let r = Reader::new(body, limits)
let value : Value = match kind {
0 => {
if body.length() != 0 {
raise Invalid("null length", 0)
}
Null
}
2 => Unsigned(read_unsigned(body))
3 => Signed(read_signed(body))
4 => {
if body.length() != 4 {
raise Invalid("real length", 0)
}
Real(Float::reinterpret_from_uint(r.uint(4)))
}
5 => {
if body.length() != 8 {
raise Invalid("double length", 0)
}
let bits = (r.uint(4).to_uint64() << 32) | r.uint(4).to_uint64()
DoubleValue(bits.reinterpret_as_double())
}
6 => Octets(body)
7 => {
if r.u8() != 0 {
raise Unsupported("character encoding other than UTF-8")
}
let bytes = r.take(r.remaining())
let text = @utf8.decode(bytes[:], ignore_bom=false) catch {
_ => raise Invalid("malformed UTF-8", 0)
}
Text(text)
}
8 => {
let unused = r.u8()
let bytes = r.take(r.remaining())
if !bits_valid(unused, bytes) {
raise Invalid("bit string padding", 0)
}
Bits(unused, bytes)
}
9 => Enumerated(read_unsigned(body))
10 => {
if body.length() != 4 {
raise Invalid("date length", 0)
}
let d : Date = {
year: r.u8(),
month: r.u8(),
day: r.u8(),
weekday: r.u8(),
}
if !date_valid(d) {
raise Invalid("date components", 0)
}
DateValue(d)
}
11 => {
if body.length() != 4 {
raise Invalid("time length", 0)
}
let t : Time = {
hour: r.u8(),
minute: r.u8(),
second: r.u8(),
hundredth: r.u8(),
}
if !time_valid(t) {
raise Invalid("time components", 0)
}
TimeValue(t)
}
12 => {
if body.length() != 4 {
raise Invalid("object identifier length", 0)
}
Object(ObjectId::from_number(r.uint(4)))
}
_ => raise Unsupported("application tag")
}
value
}