diff --git a/example/readProperty/readProperty.go b/example/readProperty/readProperty.go new file mode 100644 index 0000000..c68734c --- /dev/null +++ b/example/readProperty/readProperty.go @@ -0,0 +1,120 @@ +package main + +import ( + "fmt" + "log" + "net" + "time" + + "github.com/absmach/bacnet/pkg/bacnet" + "github.com/absmach/bacnet/pkg/encoding" + "github.com/absmach/bacnet/pkg/transport" +) + +func main() { + serverIp := "127.0.0.6:47809" + serverLocalAddr := "127.0.0.1:0" + netType := encoding.IPV4 + destination := bacnet.NewAddress(0, nil, serverIp, &netType) + npdu := bacnet.NewNPDU(&destination, nil, nil, nil) + npdu.Control.SetDataExpectingReply(true) + npdu.Control.SetNetworkPriority(bacnet.NormalMessage) + + npduBytes, err := npdu.Encode() + if err != nil { + log.Fatal(err) + } + + apdu := bacnet.APDU{ + PduType: bacnet.PDUTypeConfirmedServiceRequest, + ServiceChoice: byte(bacnet.ReadProperty), + SegmentedResponseAccepted: false, + MaxSegmentsAccepted: bacnet.MaxSegments(encoding.NoSegmentation), + InvokeID: 0, + } + + apduBytes, err := apdu.Encode() + if err != nil { + log.Fatal(err) + } + + req := bacnet.ReadPropertyRequest{ + PropertyIdentifier: encoding.PresentValue, + ObjectIdentifier: &bacnet.ObjectIdentifier{Type: encoding.AnalogInput, Instance: 10}, + } + + mes := append(npduBytes, apduBytes...) + mes = append(mes, req.Encode()...) + + blvc, err := bacnet.NewBVLC(transport.IP) + if err != nil { + log.Fatal(err) + } + blvcBytes := blvc.Encode(bacnet.BVLCOriginalBroadcastNPDU, uint16(len(mes)+4)) + message := append(blvcBytes, mes...) + + remoteAddr, err := net.ResolveUDPAddr("udp", serverIp) + if err != nil { + fmt.Println("Error resolving remote address:", err) + return + } + + localAddr, err := net.ResolveUDPAddr("udp", serverLocalAddr) + if err != nil { + fmt.Println("Error: ", err) + return + } + + // Create a UDP connectionBACnetAddress + conn, err := net.DialUDP("udp", localAddr, remoteAddr) + if err != nil { + fmt.Println("Error creating UDP connection:", err) + return + } + defer conn.Close() + + _, err = conn.Write(message) + if err != nil { + log.Fatal("Error sending WhoIsRequest:", err) + } + + // Wait for responses + buffer := make([]byte, 1500) + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // Set a timeout for responses + + for { + n, _, err := conn.ReadFromUDP(buffer) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + // Timeout reached, no more responses + log.Println("No more responses received.") + break + } + log.Println("Error reading response:", err) + break + } + + response := buffer[:n] + blvc := bacnet.BVLC{BVLLTypeBACnetIP: blvc.BVLLTypeBACnetIP} + headerLength, function, msgLength, err := blvc.Decode(response, 0) + if err != nil { + log.Fatal(err) + } + fmt.Printf("headerLength %v BVLCfunction %v msgLen %v\n", headerLength, function, msgLength) + fmt.Println("blvc", blvc) + npdu := bacnet.NPDU{Version: 1} + npduLen := npdu.Decode(response, headerLength) + fmt.Println("npdu", npdu) + apdu := bacnet.APDU{} + apduLen, err := apdu.Decode(response, headerLength+npduLen) + if err != nil { + log.Fatal(err) + } + fmt.Println("apdu", apdu) + readPropACK := bacnet.ReadPropertyACK{} + if _, err = readPropACK.Decode(response, headerLength+npduLen+apduLen-2, len(response)); err != nil { + log.Fatal(err) + } + fmt.Println("readprop", readPropACK) + } +} diff --git a/example/whoIs/whois.go b/example/whoIs/whois.go new file mode 100644 index 0000000..bc77856 --- /dev/null +++ b/example/whoIs/whois.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "log" + "net" + "time" + + "github.com/absmach/bacnet/pkg/bacnet" + "github.com/absmach/bacnet/pkg/encoding" + "github.com/absmach/bacnet/pkg/transport" +) + +func main() { + serverIp := "127.0.0.6:47809" + serverLocalAddr := "127.0.0.1:0" + + var highLimit, lowLimit uint32 = 4000000, 0 + req := bacnet.WhoIs{ + HighLimit: &highLimit, + LowLimit: &lowLimit, + } + whoisBytes := req.Encode() + + netType := encoding.IPV4 + broads := bacnet.NewAddress(encoding.MaxUint16-1, nil, serverIp, &netType) + + npdu := bacnet.NewNPDU(&broads, nil, nil, nil) + npdu.Control.SetNetworkPriority(bacnet.NormalMessage) + npduBytes, err := npdu.Encode() + if err != nil { + log.Fatalf("failed to encode npdu with error %v", err) + } + + apdu := bacnet.APDU{ + PduType: bacnet.PDUTypeUnconfirmedServiceRequest, + ServiceChoice: byte(bacnet.ServiceChoiceWhoIs), + } + + apduBytes, err := apdu.Encode() + if err != nil { + log.Fatal(err) + } + + mes := append(npduBytes, apduBytes...) + mes = append(mes, whoisBytes...) + + blvc, err := bacnet.NewBVLC(transport.IP) + if err != nil { + log.Fatal(err) + } + + blvcBytes := blvc.Encode(bacnet.BVLCOriginalBroadcastNPDU, uint16(len(mes)+int(blvc.BVLCHeaderLength))) + message := append(blvcBytes, mes...) + + remoteAddr, err := net.ResolveUDPAddr("udp", serverIp) + if err != nil { + log.Fatal("Error resolving remote address:", err) + } + + localAddr, err := net.ResolveUDPAddr("udp", serverLocalAddr) + if err != nil { + log.Fatal("Error: ", err) + return + } + + conn, err := net.DialUDP("udp", localAddr, remoteAddr) + if err != nil { + log.Fatal("Error creating UDP connection:", err) + } + defer conn.Close() + + _, err = conn.Write(message) + if err != nil { + log.Fatal("Error sending WhoIsRequest:", err) + } + + // Wait for responses + buffer := make([]byte, 1500) + conn.SetReadDeadline(time.Now().Add(5 * time.Second)) // Set a timeout for responses + + for { + n, _, err := conn.ReadFromUDP(buffer) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + // Timeout reached, no more responses + log.Println("No more responses received.") + break + } + log.Println("Error reading response:", err) + break + } + + response := buffer[:n] + log.Printf("Received response: %X\n", response) + blvc := bacnet.BVLC{BVLLTypeBACnetIP: blvc.BVLLTypeBACnetIP} + headerLength, function, msgLength, err := blvc.Decode(response, 0) + if err != nil { + log.Fatal(err) + } + fmt.Println(response) + fmt.Printf("headerLength %v BVLCfunction %v msgLen %v\n", headerLength, function, msgLength) + fmt.Println("blvc", blvc) + fmt.Println(response[headerLength:]) + npdu := bacnet.NPDU{Version: 1} + npduLen := npdu.Decode(response, headerLength) + fmt.Println("npdu", npdu) + fmt.Println(response[headerLength+npduLen:]) + apdu := bacnet.APDU{} + apduLen, err := apdu.Decode(response, headerLength+npduLen) + if err != nil { + log.Fatal(err) + } + fmt.Println("apdu", apdu) + fmt.Println(response[headerLength+npduLen+apduLen:]) + iam := bacnet.IAmRequest{} + iamLen, err := iam.Decode(response, headerLength+npduLen+apduLen) + if err != nil { + log.Fatal(err) + } + fmt.Println("iam", iam) + fmt.Println(response[headerLength+npduLen+apduLen+iamLen:]) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c8d470d --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/absmach/bacnet + +go 1.21.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/internal/bitarray.go b/internal/bitarray.go new file mode 100644 index 0000000..0054b2b --- /dev/null +++ b/internal/bitarray.go @@ -0,0 +1,65 @@ +package internal + +import "errors" + +var ( + errBitArrayLen = errors.New("bit array length must be 8 to convert to byte") + errOutOfBounds = errors.New("index is out of the range for the bitarray") +) + +// BitArray defines an array of bits. +type BitArray struct { + bits []bool +} + +// Creates a new bit array for the provided length. +func NewBitArray(length int) *BitArray { + return &BitArray{ + bits: make([]bool, length), + } +} + +// Set sets the value of the bit array at the given index and value. +func (ba *BitArray) Set(index int, value bool) error { + if index >= 0 && index < len(ba.bits) { + ba.bits[index] = value + return nil + } + return errOutOfBounds +} + +// Get returns the the value of the bit array at the given index. +func (ba *BitArray) Get(index int) (bool, error) { + if index >= 0 && index < len(ba.bits) { + return ba.bits[index], nil + } + return false, errOutOfBounds +} + +// ToByte converts bitarray to byte value. +// TODO return byte array. +func (ba *BitArray) ToByte() (byte, error) { + // Ensure the length of the bit array is 8 to convert to a byte. + if len(ba.bits) != 8 { + return 0, errBitArrayLen + } + + var byteValue byte + for i := 0; i < 8; i++ { + if ba.bits[i] { + byteValue |= 1 << uint(7-i) + } + } + + return byteValue, nil +} + +// NewBitArrayFromByte creates a new bit array from the given byte. +// TODO check length of incoming byte. +func NewBitArrayFromByte(byteValue byte) *BitArray { + bitArray := NewBitArray(8) + for j := 0; j < 8; j++ { + bitArray.Set(j, byteValue&(1< 0xFFFF { + return -1, errors.New("Value exceeds 0xFFFF") + } + iam.VendorID = decodedValue + + return leng, nil +} + +func (iam IAmRequest) Encode() []byte { + tmp := iam.IamDeviceIdentifier.Encode() + propID := iam.SegmentationSupported.(encoding.PropertyIdentifier) + return append(append(append(append(encoding.EncodeTag(encoding.BACnetApplicationTag(BACnetObjectIdentifier), false, len(tmp)), tmp...), encoding.EncodeApplicationUnsigned(iam.MaxAPDULengthAccepted)...), encoding.EncodeApplicationEnumerated(uint32(propID))...), encoding.EncodeApplicationUnsigned(iam.VendorID)...) +} + +type YouAreRequest struct { + VendorID uint32 + ModelName string + SerialNumber string + DeviceIdentifier ObjectIdentifier + DeviceMACAddress []byte +} + +func (youAre *YouAreRequest) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == byte(UnsignedInt) { + leng1, decodedValue, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + youAre.VendorID = decodedValue + } else { + return -1, errors.New("Invalid tag number") + } + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == byte(CharacterString) { + leng1, decodedValue := encoding.DecodeCharacterString(buffer, offset+leng, apduLen-leng, int(lenValue)) + + leng += leng1 + youAre.ModelName = decodedValue + } else { + return -1, errors.New("Invalid tag number") + } + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == byte(CharacterString) { + leng1, decodedValue := encoding.DecodeCharacterString(buffer, offset+leng, apduLen-leng, int(lenValue)) + leng += leng1 + youAre.SerialNumber = decodedValue + } else { + return -1, errors.New("Invalid tag number") + } + + if leng < apduLen { + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(BACnetObjectIdentifier) { + leng += leng1 + youAre.DeviceIdentifier = ObjectIdentifier{} + leng1, err = youAre.DeviceIdentifier.Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + if leng < apduLen { + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(OctetString) { + leng += leng1 + leng1, decodedValue := encoding.DecodeOctetString(buffer, offset+leng, int(lenValue)) + leng += leng1 + youAre.DeviceMACAddress = decodedValue + } + } + + return leng, nil +} + +func (youAre YouAreRequest) Encode() []byte { + buffer := append(append(append([]byte{}, encoding.EncodeApplicationUnsigned(youAre.VendorID)...), + encoding.EncodeApplicationCharacterString(youAre.ModelName)...), + encoding.EncodeApplicationCharacterString(youAre.SerialNumber)...) + + if youAre.DeviceIdentifier != (ObjectIdentifier{}) { + buffer = append(buffer, youAre.DeviceIdentifier.EncodeApp()...) + } + + if len(youAre.DeviceMACAddress) > 0 { + buffer = append(buffer, encoding.EncodeApplicationOctetString(youAre.DeviceMACAddress, 0, len(youAre.DeviceMACAddress))...) + } + + return buffer +} diff --git a/pkg/bacnet/network.go b/pkg/bacnet/network.go new file mode 100644 index 0000000..b5a98a2 --- /dev/null +++ b/pkg/bacnet/network.go @@ -0,0 +1,127 @@ +package bacnet + +import ( + "encoding/binary" + "errors" + "fmt" + "strings" + + "github.com/absmach/bacnet/pkg/encoding" +) + +var ( + errMacAddressLen = errors.New("invalid mac address length") + errInvalidTagNumber = errors.New("invalid tag number") +) + +type ApplicationTags int + +const ( + Null ApplicationTags = iota + Boolean + UnsignedInt + SignedInt + Real + Double + OctetString + CharacterString + ApplicationTagsBitString + Enumerated + Date + Time + BACnetObjectIdentifier + Reserve1 + Reserve2 + Reserve3 +) + +type Address struct { + // BACnet Network Number. + // NetworkNumber = 0, for local. + NetworkNumber uint32 + // MacAddress represnets the ip address with 4 bytes and 2 bytes for port. + // If len == 0, then this a broadcast address. + MacAddress []byte +} + +func NewAddress(networkNumber uint32, macAddress []byte, address interface{}, netType *encoding.NetworkType) Address { + addr := Address{ + NetworkNumber: networkNumber, + MacAddress: macAddress, + } + + switch addr1 := address.(type) { + case string: + if address != "" { + switch *netType { + case encoding.IPV4: + tmp1 := strings.Split(addr1, ":") + parts := strings.Split(tmp1[0], ".") + var ipAddr [4]byte + for i, part := range parts { + val := byte(0) + fmt.Sscanf(part, "%d", &val) + ipAddr[i] = val + } + var port uint16 + fmt.Sscanf(tmp1[1], "%d", &port) + addr.MacAddress = append(ipAddr[:], byte(port>>8), byte(port)) + case encoding.Ethernet: + parts := strings.Split(addr1, "-") + for _, part := range parts { + val := byte(0) + fmt.Sscanf(part, "%d", &val) + addr.MacAddress = append(addr.MacAddress, val) + } + } + } + case ObjectIdentifier: + if *netType == encoding.IPV4 { + addr.MacAddress = make([]byte, 8) + binary.LittleEndian.PutUint64(addr.MacAddress, uint64(addr1.Instance)) + } + } + + return addr +} + +func (ba *Address) IPAndPort() (string, int, error) { + if len(ba.MacAddress) < 6 { + return "", 0, errMacAddressLen + } + ip := fmt.Sprintf("%d.%d.%d.%d", ba.MacAddress[0], ba.MacAddress[1], ba.MacAddress[2], ba.MacAddress[3]) + port := int(ba.MacAddress[4])<<8 + int(ba.MacAddress[5]) + return ip, port, nil +} + +func (ba *Address) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(UnsignedInt) { + leng += leng1 + leng1, ba.NetworkNumber, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(OctetString) { + leng += leng1 + leng1, ba.MacAddress = encoding.DecodeOctetString(buffer, offset+leng, int(lenValue)) + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} diff --git a/pkg/bacnet/npdu.go b/pkg/bacnet/npdu.go new file mode 100644 index 0000000..f1277fc --- /dev/null +++ b/pkg/bacnet/npdu.go @@ -0,0 +1,275 @@ +package bacnet + +import ( + "encoding/binary" + "errors" + + "github.com/absmach/bacnet/internal" +) + +// NPDU Network Protocol Data Unit netwrok layer data packet. +type NPDU struct { + Version uint8 // Always one. + Control NPDUControlInformation + DNET uint16 + DLEN uint8 + DADR []byte + Destination Address + SNET uint16 + SLEN uint8 + SADR []byte + Source Address + MessageType byte + HopCount byte + VendorID uint16 +} + +// NPDUControlInformation a bit array to define network control information. +type NPDUControlInformation struct { + control internal.BitArray +} + +// NewNPDUControlInformation creates a new bit array for network control info. +func NewNPDUControlInformation() *NPDUControlInformation { + return &NPDUControlInformation{ + control: *internal.NewBitArray(8), + } +} + +// IsNetworkLayerMessage returns wether a message is a network layer message. +func (nci *NPDUControlInformation) IsNetworkLayerMessage() bool { + val, err := nci.control.Get(0) + if err != nil { + return false + } + return val +} + +// SetNetworkLayerMessage sets the value for the netwrok layer message bit. +func (nci *NPDUControlInformation) SetNetworkLayerMessage(a bool) { + nci.control.Set(0, a) +} + +// IsDestinationSpecifier returns based on the npdu control bit bit if it is a message specifier. +func (nci *NPDUControlInformation) IsDestinationSpecifier() bool { + val, err := nci.control.Get(2) + if err != nil { + return false + } + return val +} + +func (nci *NPDUControlInformation) SetDestinationSpecifier(a bool) { + nci.control.Set(2, a) +} + +func (nci *NPDUControlInformation) IsSourceSpecifier() bool { + val, err := nci.control.Get(4) + if err != nil { + return false + } + return val +} + +func (nci *NPDUControlInformation) SetSourceSpecifier(a bool) { + nci.control.Set(4, a) +} + +func (nci *NPDUControlInformation) IsDataExpectingReply() bool { + val, err := nci.control.Get(5) + if err != nil { + return false + } + return val +} + +func (nci *NPDUControlInformation) SetDataExpectingReply(a bool) { + nci.control.Set(5, a) +} + +// NetworkPriority returns the network priority based on the network control information bits. +func (nci *NPDUControlInformation) NetworkPriority() (NetworkPriority, error) { + control7, err := nci.control.Get(7) + if err != nil { + return 0, err + } + control6, err := nci.control.Get(6) + if err != nil { + return 0, err + } + switch { + case !control6 && !control7: + return NormalMessage, nil + case !control6 && control7: + return UrgentMessage, nil + case control6 && !control7: + return CriticalEquipmentMessage, nil + case control6 && control7: + return LifeSafetyMessage, nil + default: + return 0, errors.New("invalid network priority") + } +} + +// SetNetworkPriority sets the network control information based on network priority set. +func (nci *NPDUControlInformation) SetNetworkPriority(a NetworkPriority) error { + switch a { + case NormalMessage: + nci.control.Set(6, false) + nci.control.Set(7, false) + case UrgentMessage: + nci.control.Set(6, false) + nci.control.Set(7, true) + case CriticalEquipmentMessage: + nci.control.Set(6, true) + nci.control.Set(7, false) + case LifeSafetyMessage: + nci.control.Set(6, true) + nci.control.Set(7, true) + default: + return errors.New("invalid network priority") + } + return nil +} + +// Encode encodes network control information. +func (nci *NPDUControlInformation) Encode() ([]byte, error) { + b, err := nci.control.ToByte() + if err != nil { + return []byte{}, err + } + return []byte{b}, nil +} + +// Decode decodes network control information from a byte buffer. +func (nci *NPDUControlInformation) Decode(buffer []byte, offset int) int { + if offset < len(buffer) { + nci.control = *internal.NewBitArrayFromByte(buffer[offset]) + return 1 + } + return 0 +} + +// NewNPDU creates a new NPDU. +func NewNPDU(destination *Address, source *Address, hopCount *uint8, vendorID *uint16) *NPDU { + npdu := &NPDU{ + Version: 1, + Control: *NewNPDUControlInformation(), + Destination: *destination, + Source: *source, + } + switch hopCount { + case nil: + npdu.HopCount = 255 + default: + npdu.HopCount = *hopCount + } + if vendorID != nil { + npdu.VendorID = *vendorID + } + + if destination != nil && destination.NetworkNumber > 0 { + npdu.Control.SetDestinationSpecifier(true) + npdu.DNET = uint16(destination.NetworkNumber) + npdu.DLEN = uint8(len(destination.MacAddress)) + npdu.DADR = destination.MacAddress + } + + if source != nil && source.NetworkNumber > 0 && source.NetworkNumber < 0xFFFF { + npdu.Control.SetSourceSpecifier(true) + npdu.SNET = uint16(source.NetworkNumber) + npdu.SLEN = uint8(len(source.MacAddress)) + npdu.SADR = source.MacAddress + } + + return npdu +} + +// Encode encodes the NPDU data to []byte. +func (npdu *NPDU) Encode() ([]byte, error) { + buffer := make([]byte, 0) + buffer = append(buffer, npdu.Version) + ctrlBuf, err := npdu.Control.Encode() + if err != nil { + return buffer, err + } + buffer = append(buffer, ctrlBuf...) + + if npdu.Control.IsDestinationSpecifier() { + buffer = append(buffer, uint8(npdu.DNET>>8), uint8(npdu.DNET&0xFF)) + if npdu.DNET == 0xFFFF { + buffer = append(buffer, 0x00) + } else { + buffer = append(buffer, npdu.DLEN) + buffer = append(buffer, npdu.DADR...) + } + } + + if npdu.Control.IsSourceSpecifier() { + buffer = append(buffer, uint8(npdu.SNET>>8), uint8(npdu.SNET&0xFF)) + buffer = append(buffer, npdu.SLEN) + buffer = append(buffer, npdu.SADR...) + } + + if npdu.Control.IsDestinationSpecifier() { + buffer = append(buffer, npdu.HopCount) + } + + if npdu.Control.IsNetworkLayerMessage() { + buffer = append(buffer, npdu.MessageType) + if npdu.MessageType >= 0x80 && npdu.MessageType <= 0xFF { + buffer = append(buffer, uint8(npdu.VendorID>>8), uint8(npdu.VendorID&0xFF)) + } + } + + return buffer, nil +} + +// Decode decodes []byte to NPDU. +func (npdu *NPDU) Decode(buffer []byte, offset int) int { + length := 0 + version := buffer[offset] // always 1!!!! + length++ + if version != npdu.Version { + return -1 + } + + npdu.Control = *NewNPDUControlInformation() + length += npdu.Control.Decode(buffer, offset+length) + + if npdu.Control.IsDestinationSpecifier() { + npdu.DNET = binary.BigEndian.Uint16(buffer[offset+length : offset+length+2]) + length += 2 + npdu.DLEN = buffer[offset+length] + length++ + npdu.DADR = buffer[offset+length : offset+length+int(npdu.DLEN)] + length += int(npdu.DLEN) + npdu.Destination = NewAddress(uint32(npdu.DNET), npdu.DADR, "", nil) + } + + if npdu.Control.IsSourceSpecifier() { + npdu.SNET = binary.BigEndian.Uint16(buffer[offset+length : offset+length+2]) + length += 2 + npdu.SLEN = buffer[offset+length] + length++ + npdu.SADR = buffer[offset+length : offset+length+int(npdu.SLEN)] + length += int(npdu.SLEN) + npdu.Source = NewAddress(uint32(npdu.SNET), npdu.SADR, "", nil) + } + + if npdu.Control.IsDestinationSpecifier() { + npdu.HopCount = buffer[offset+length] + length++ + } + + if npdu.Control.IsNetworkLayerMessage() { + npdu.MessageType = buffer[offset+length] + length++ + if npdu.MessageType >= 0x80 { + npdu.VendorID = binary.BigEndian.Uint16(buffer[offset+length : offset+length+2]) + length += 2 + } + } + + return length +} diff --git a/pkg/bacnet/object.go b/pkg/bacnet/object.go new file mode 100644 index 0000000..5477449 --- /dev/null +++ b/pkg/bacnet/object.go @@ -0,0 +1,59 @@ +package bacnet + +import ( + "encoding/binary" + + "github.com/absmach/bacnet/pkg/encoding" +) + +type ObjectInstance uint32 + +type ObjectIdentifier struct { + Type encoding.ObjectType + Instance ObjectInstance +} + +func (oi *ObjectIdentifier) Decode(buf []byte, offset, apdulen int) (int, error) { + len, val, err := encoding.DecodeUnsigned(buf, offset, 4) + if err != nil { + return -1, err + } + oi.Instance = ObjectInstance(val) & ObjectInstance(encoding.MaxInstance) + oi.Type = encoding.ObjectType(val >> encoding.InstanceBits & encoding.MaxObject) + return len, nil +} + +func (oi *ObjectIdentifier) DecodeContext(buf []byte, offset, apdulen int, tagNumber byte) (int, error) { + len := 0 + if encoding.IsContextTag(buf, offset+len, tagNumber) { + len1, _, lenVal, err := encoding.DecodeTagNumberAndValue(buf, offset+len) + if err != nil { + return -1, err + } + len += len1 + leng1, err := oi.Decode(buf, offset+len1, int(lenVal)) + if err != nil { + return -1, err + } + len += leng1 + return len, nil + } + return -1, errInvalidTagNumber +} + +func (oi ObjectIdentifier) Encode() []byte { + value := uint32(oi.Type)&encoding.MaxObject< 0 { + ttag := ApplicationTags(tagNumber) + bv.Tag = &ttag + length += tagLen + + decodeLen := 0 + + switch *bv.Tag { + case Null: + bv.Value = nil + decodeLen = 0 + case Boolean: + if lenValueType > 0 { + bv.Value = true + } else { + bv.Value = false + } + case UnsignedInt: + if *propID == encoding.RoutingTable { + bv.Tag = nil + bv.Value = &RouterEntry{} + length-- + decodeLen, err = bv.Value.(*RouterEntry).Decode(buffer, offset+length, apduLen) + if err != nil { + return -1, err + } + } else if *propID == encoding.ActiveVtSessions { + bv.Tag = nil + bv.Value = &VTSession{} + length-- + decodeLen = bv.Value.(*VTSession).Decode(buffer, offset+length, apduLen) + } else if *propID == encoding.ThreatLevel || *propID == encoding.ThreatAuthority { + bv.Tag = nil + bv.Value = &AccessThreatLevel{} + length-- + decodeLen = bv.Value.(*AccessThreatLevel).Decode(buffer, offset+length, apduLen) + } else { + var uintVal uint32 + decodeLen, uintVal, err = encoding.DecodeUnsigned(buffer, offset+length, int(lenValueType)) + if err != nil { + return -1, err + } + bv.Value = uintVal + } + case SignedInt: + var intValue int + decodeLen, intValue = encoding.DecodeSigned(buffer, offset+length, int(lenValueType)) + bv.Value = intValue + case Real: + var floatValue float32 + decodeLen, floatValue = encoding.DecodeRealSafe(buffer, offset+length, int(lenValueType)) + bv.Value = floatValue + case Double: + var doubleValue float64 + decodeLen, doubleValue = encoding.DecodeDoubleSafe(buffer, offset+length, int(lenValueType)) + bv.Value = doubleValue + case OctetString: + var octetValue []byte + decodeLen, octetValue = encoding.DecodeOctetString(buffer, offset+length, int(lenValueType)) + bv.Value = octetValue + case CharacterString: + var stringValue string + decodeLen, stringValue = encoding.DecodeCharacterString(buffer, offset+length, apduLen, int(lenValueType)) + bv.Value = stringValue + case ApplicationTagsBitString: + switch *propID { + case encoding.RecipientList: + bv.Tag = nil + bv.Value = &Destination{} + length-- + decodeLen, err = bv.Value.(*Destination).Decode(buffer, offset+length, apduLen) + if err != nil { + return -1, err + } + case encoding.StatusFlags: + bv.Tag = nil + bitValue := &StatusFlags{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + case encoding.EventEnable, encoding.AckedTransitions: + bv.Tag = nil + bitValue := &EventTransitionBits{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + case encoding.LimitEnable: + bv.Tag = nil + bitValue := &LimitEnable{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + case encoding.ProtocolObjectTypesSupported: + bv.Tag = nil + bitValue := &ObjectTypesSupported{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + case encoding.ProtocolServicesSupported: + bv.Tag = nil + bitValue := &ServicesSupported{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + default: + bitValue := &BitString{} + decodeLen = bitValue.Decode(buffer, offset, int(lenValueType)) + bv.Value = bitValue + } + case Enumerated: + decodeLen, bv.Value, err = encoding.DecodeEnumerated(buffer, offset+length, lenValueType, objType, propID) + if err != nil { + return -1, err + } + case Date: + switch *propID { + case encoding.EffectivePeriod: + bv.Tag = nil + bv.Value = &DateRange{} + length-- + decodeLen, err = bv.Value.(*DateRange).Decode(buffer, offset+length, apduLen) + if err != nil { + return -1, err + } + case encoding.MinimumValueTimestamp, + encoding.MaximumValueTimestamp, + encoding.ChangeOfStateTime, + encoding.TimeOfStateCountReset, + encoding.TimeOfActiveTimeReset, + encoding.ModificationDate, + encoding.UpdateTime, + encoding.CountChangeTime, + encoding.StartTime, + encoding.StopTime, + encoding.LastCredentialAddedTime, + encoding.LastCredentialRemovedTime, + encoding.ActivationTime, + encoding.ExpiryTime, + encoding.LastUseTime, + encoding.TimeOfStrikeCountReset, + encoding.ValueChangeTime: + bv.Tag = nil + bv.Value = &DateTime{} + length-- + decodeLen = bv.Value.(*DateTime).Decode(buffer, offset+length) + default: + decodeLen, bv.Value = encoding.DecodeDateSafe(buffer, offset+length, int(lenValueType)) + } + if (*objType == encoding.DateTimeValue || *objType == encoding.TimePatternValue) && (*propID == encoding.PresentValue || *propID == encoding.RelinquishDefault) { + decodeLen, bv.Value = encoding.DecodeDateSafe(buffer, offset+length, int(lenValueType)) + } + case Time: + decodeLen, bv.Value = encoding.DecodeBACnetTimeSafe(buffer, offset+length, int(lenValueType)) + case BACnetObjectIdentifier: + if *propID == encoding.LastKeyServer || + *propID == encoding.ManualSlaveAddressBinding || + *propID == encoding.SlaveAddressBinding || + *propID == encoding.DeviceAddressBinding { + bv.Tag = nil + bv.Value = &AddressBinding{} + length-- + decodeLen, err = bv.Value.(*AddressBinding).Decode(buffer, offset+length, apduLen) + if err != nil { + return -1, err + } + } else { + var objectType encoding.ObjectType + var instance uint32 + decodeLen, objectType, instance, err = encoding.DecodeObjectIDSafe(buffer, offset+length, lenValueType) + if err != nil { + return -1, err + } + bv.Value = ObjectIdentifier{Type: objectType, Instance: ObjectInstance(instance)} + } + default: + log.Println("Unhandled tag:", bv.Tag) + length = apduLen + } + + if decodeLen < 0 { + return -1, fmt.Errorf("no tags decoded") + } + length += decodeLen + } + } else { + switch *propID { + case encoding.BacnetIpGlobalAddress, encoding.FdBbmdAddress: + bv.Value = &HostNPort{} + length1, err := bv.Value.(*HostNPort).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.UtcTimeSynchronizationRecipients, + encoding.RestartNotificationRecipients, + encoding.TimeSynchronizationRecipients, + encoding.CovuRecipients: + bv.Value = &Recipient{} + length1, err := bv.Value.(*Recipient).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.KeySets: + bv.Value = &SecurityKeySet{} + length1, err := bv.Value.(*SecurityKeySet).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.EventTimeStamps, + encoding.LastCommandTime, + encoding.CommandTimeArray, + encoding.LastRestoreTime, + encoding.TimeOfDeviceRestart, + encoding.AccessEventTime, + encoding.UpdateTime: + bv.Value = &TimeStamp{} + length1, err := bv.Value.(*TimeStamp).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.ListOfGroupMembers: + bv.Value = &ReadAccessSpecification{} + length, err = bv.Value.(*ReadAccessSpecification).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + case encoding.ListOfObjectPropertyReferences: + bv.Value = &DeviceObjectPropertyReference{} + length, err = bv.Value.(*DeviceObjectPropertyReference).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + case encoding.MemberOf, + encoding.ZoneMembers, + encoding.DoorMembers, + encoding.SubordinateList, + encoding.Represents, + encoding.AccessEventCredential, + encoding.AccessDoors, + encoding.ZoneTo, + encoding.ZoneFrom, + encoding.CredentialsInZone, + encoding.LastCredentialAdded, + encoding.LastCredentialRemoved, + encoding.EntryPoints, + encoding.ExitPoints, + encoding.Members, + encoding.Credentials, + encoding.Accompaniment, + encoding.BelongsTo, + encoding.LastAccessPoint, + encoding.EnergyMeterRef: + bv.Value = &DeviceObjectReference{} + length1, err := bv.Value.(*DeviceObjectReference).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.EventAlgorithmInhibitRef, + encoding.InputReference, + encoding.ManipulatedVariableReference, + encoding.ControlledVariableReference: + bv.Value = &ObjectPropertyReference{} + length1, err := bv.Value.(*ObjectPropertyReference).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.LoggingRecord: + bv.Value = &AccumulatorRecord{} + length, err = bv.Value.(*AccumulatorRecord).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + case encoding.PropertyIdentifierAction: + bv.Value = &ActionList{} + length1, err := bv.Value.(*ActionList).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.Scale: + bv.Value = &Scale{} + length1, err := bv.Value.(*Scale).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.LightingCommand: + bv.Value = &LightingCommand{} + length1, err := bv.Value.(*LightingCommand).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.Prescale: + bv.Value = &Prescale{} + length1, err := bv.Value.(*Prescale).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.RequestedShedLevel, + encoding.ExpectedShedLevel, + encoding.ActualShedLevel: + bv.Value = &ShedLevel{} + length1, err := bv.Value.(*ShedLevel).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.LogBuffer: + switch *objType { + case encoding.TrendLog: + bv.Value = &LogRecord{} + length1, err := bv.Value.(*LogRecord).Decode(buffer, offset+length, apduLen-length, nil, nil) + if err != nil { + return -1, err + } + length += length1 + case encoding.EventLog: + bv.Value = &EventLogRecord{} + length1, err := bv.Value.(*EventLogRecord).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + } + case encoding.DateList: + bv.Value = &CalendarEntry{} + length1, err := bv.Value.(*CalendarEntry).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.PresentValue: + switch *objType { + case encoding.Group: + bv.Value = &ReadAccessResult{} + length1, err := bv.Value.(*ReadAccessResult).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.Channel: + bv.Value = &ChannelValue{} + length += bv.Value.(*ChannelValue).Decode(buffer, offset+length, apduLen-length) + case encoding.GlobalGroup: + bv.Value = &PropertyAccessResult{} + length += bv.Value.(*PropertyAccessResult).Decode(buffer, offset+length, apduLen-length) + case encoding.CredentialDataInput: + bv.Value = &AuthenticationFactor{} + length += bv.Value.(*AuthenticationFactor).Decode(buffer, offset+length, apduLen-length) + } + case encoding.NegativeAccessRules, + encoding.PositiveAccessRules: + bv.Value = &AccessRule{} + length += bv.Value.(*AccessRule).Decode(buffer, offset+length, apduLen-length) + case encoding.Tags: + bv.Value = &NameValue{} + length1, err := bv.Value.(*NameValue).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.SubordinateTags: + bv.Value = &NameValueCollection{} + length1, err := bv.Value.(*NameValueCollection).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.NetworkAccessSecurityPolicies: + bv.Value = &NetworkSecurityPolicy{} + length1, err := bv.Value.(*NetworkSecurityPolicy).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.PortFilter: + bv.Value = &PortPermission{} + length1, err := bv.Value.(*PortPermission).Decode(buffer, offset+length, apduLen-length) + if err != nil { + return -1, err + } + length += length1 + case encoding.PriorityArray: + bv.Value = &PriorityArray{} + length += bv.Value.(*PriorityArray).Decode(buffer, offset+length, apduLen-length) + case encoding.ProcessIdentifierFilter: + bv.Value = &ProcessIdSelection{} + length += bv.Value.(*ProcessIdSelection).Decode(buffer, offset+length, apduLen-length) + case encoding.SetpointReference: + bv.Value = &SetpointReference{} + length += bv.Value.(*SetpointReference).Decode(buffer, offset+length, apduLen-length) + case encoding.ExceptionSchedule: + bv.Value = &SpecialEvent{} + length += bv.Value.(*SpecialEvent).Decode(buffer, offset+length, apduLen-length) + case encoding.StateChangeValues: + bv.Value = &TimerStateChangeValue{} + length += bv.Value.(*TimerStateChangeValue).Decode(buffer, offset+length, apduLen-length) + case encoding.ValueSource, encoding.ValueSourceArray: + bv.Value = &ValueSource{} + length += bv.Value.(*ValueSource).Decode(buffer, offset+length, apduLen-length) + case encoding.VirtualMacAddressTable: + bv.Value = &VMACEntry{} + length += bv.Value.(*VMACEntry).Decode(buffer, offset+length, apduLen-length) + case encoding.AssignedAccessRights: + bv.Value = &AssignedAccessRights{} + length += bv.Value.(*AssignedAccessRights).Decode(buffer, offset+length, apduLen-length) + case encoding.AssignedLandingCalls: + bv.Value = &AssignedLandingCalls{} + length += bv.Value.(*AssignedLandingCalls).Decode(buffer, offset+length, apduLen-length) + case encoding.AccessEventAuthenticationFactor: + bv.Value = &AuthenticationFactor{} + length += bv.Value.(*AuthenticationFactor).Decode(buffer, offset+length, apduLen-length) + case encoding.SupportedFormats: + bv.Value = &AuthenticationFactorFormat{} + length += bv.Value.(*AuthenticationFactorFormat).Decode(buffer, offset+length, apduLen-length) + case encoding.AuthenticationPolicyList: + bv.Value = &AuthenticationPolicy{} + length += bv.Value.(*AuthenticationPolicy).Decode(buffer, offset+length, apduLen-length) + case encoding.ActiveCovSubscriptions: + bv.Value = &COVSubscription{} + length += bv.Value.(*COVSubscription).Decode(buffer, offset+length, apduLen-length) + case encoding.AuthenticationFactors: + bv.Value = &CredentialAuthenticationFactor{} + length += bv.Value.(*CredentialAuthenticationFactor).Decode(buffer, offset+length, apduLen-length) + case encoding.WeeklySchedule: + bv.Value = &DailySchedule{} + length += bv.Value.(*DailySchedule).Decode(buffer, offset+length, apduLen-length) + case encoding.SubscribedRecipients: + bv.Value = &EventNotificationSubscription{} + length += bv.Value.(*EventNotificationSubscription).Decode(buffer, offset+length, apduLen-length) + case encoding.EventParameters: + bv.Value = &EventParameter{} + length += bv.Value.(*EventParameter).Decode(buffer, offset+length, apduLen-length) + case encoding.FaultParameters: + bv.Value = &FaultParameter{} + length += bv.Value.(*FaultParameter).Decode(buffer, offset+length, apduLen-length) + default: + bv.Value = nil + } + } + return length, nil +} + +func (bv *Value) Encode() []byte { + if bv.Tag == nil { + return nil + } else { + switch *bv.Tag { + case Boolean: + return encoding.EncodeApplicationBoolean(bv.Value.(bool)) + case UnsignedInt: + return encoding.EncodeApplicationUnsigned(bv.Value.(uint32)) + case SignedInt: + return encoding.EncodeApplicationSigned(bv.Value.(int32)) + case Real: + return encoding.EncodeApplicationReal(bv.Value.(float32)) + case Double: + return encoding.EncodeApplicationDouble(bv.Value.(float64)) + case OctetString: + return encoding.EncodeApplicationOctetString(bv.Value.([]byte), 0, len(bv.Value.([]byte))) + case CharacterString: + return encoding.EncodeApplicationCharacterString(bv.Value.(string)) + case ApplicationTagsBitString: + return encoding.EncodeApplicationBitString(bv.Value) + case Enumerated: + return encoding.EncodeApplicationEnumerated(bv.Value.(uint32)) + case Date: + return encoding.EncodeApplicationDate(bv.Value.(time.Time)) + case Time: + return encoding.EncodeApplicationTime(bv.Value.(time.Time)) + case BACnetObjectIdentifier: + return bv.Value.(*ObjectIdentifier).EncodeApp() + default: + switch bv.Value.(type) { + case int: + return encoding.EncodeApplicationEnumerated(uint32(bv.Value.(int))) + } + log.Printf("Unsupported BACnetApplicationTag: %v", bv.Tag) + return nil + } + } +} + +// BACnetRouterEntryStatus is an enumeration for the status of BACnetRouterEntry. +type RouterEntryStatus int + +const ( + Available RouterEntryStatus = iota + BACnetRouterEntryStatusBusy + Disconnected +) + +// BACnetRouterEntry represents a BACnet router entry. +type RouterEntry struct { + NetworkNumber uint32 + MACAddress []byte + Status RouterEntryStatus + PerformanceIndex uint32 +} + +// Decode decodes a RouterEntry from an encoded byte buffer. +func (entry *RouterEntry) Decode(buffer []byte, offset, apduLen int) (int, error) { + var length int + + // network_number + length1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.UnsignedInt) { + return -1, errors.New("Error decoding network_number") + } + length += length1 + length1, entry.NetworkNumber, err = encoding.DecodeUnsigned(buffer, offset+length, int(lenValue)) + if err != nil { + return -1, err + } + length += length1 + + // mac_address + length1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+length) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.OctetString) { + return -1, errors.New("Error decoding mac_address") + } + length += length1 + length1, entry.MACAddress = encoding.DecodeOctetString(buffer, offset+length, int(lenValue)) + length += length1 + + // status + length1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+length) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.Enumerated) { + return -1, errors.New("Error decoding status") + } + length += length1 + length1, Val, err := encoding.DecodeUnsigned(buffer, offset+length, int(lenValue)) + if err != nil { + return -1, err + } + length += length1 + entry.Status = RouterEntryStatus(Val) + + // performance_index (optional) + if offset < apduLen { + length1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+length) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.UnsignedInt) { + length += length1 + length1, entry.PerformanceIndex, err = encoding.DecodeUnsigned(buffer, offset+length, int(lenValue)) + if err != nil { + return -1, err + } + length += length1 + } + } + + return length, nil +} + +type VTSession struct { + LocalVTSessionID int + RemoteVTSessionID int + RemoteVTAddress Address +} + +// Decode method for BACnetVTSession +func (b *VTSession) Decode(buffer []byte, offset, apduLen int) int { + // Implement decode logic here + return -1 +} + +// BACnetAccessThreatLevel struct definition +type AccessThreatLevel struct { + Value int +} + +// decode method for BACnetAccessThreatLevel +func (b *AccessThreatLevel) Decode(buffer []byte, offset, apduLen int) int { + // Implement decode logic here + return -1 +} + +type Destination struct { + ValidDays *DaysOfWeek + FromTime time.Time + ToTime time.Time + Recipient *Recipient + ProcessIdentifier uint32 + IssueConfirmedNotifications bool + Transitions *EventTransitionBits +} + +func (b *Destination) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.BitString) { + return -1, errInvalidTagNumber + } + leng += leng1 + b.ValidDays = &DaysOfWeek{} + + leng1 = b.ValidDays.Decode(buffer, offset+leng, int(lenValue)) + + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.Time) { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, b.FromTime = encoding.DecodeBACnetTimeSafe(buffer, offset+leng, int(lenValue)) + + leng += leng1 + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.Time) { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, b.ToTime = encoding.DecodeBACnetTimeSafe(buffer, offset+leng, int(lenValue)) + + leng += leng1 + + b.Recipient = &Recipient{} + leng1, err = b.Recipient.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.UnsignedInt) { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, b.ProcessIdentifier, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + + leng += leng1 + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.Boolean) { + return -1, errInvalidTagNumber + } + leng += leng1 + if lenValue > 0 { + b.IssueConfirmedNotifications = true + } else { + b.IssueConfirmedNotifications = false + } + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != byte(encoding.BitString) { + return -1, errInvalidTagNumber + } + leng += leng1 + + b.Transitions = &EventTransitionBits{} + leng1 = b.Transitions.Decode(buffer, offset+leng, int(lenValue)) + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + + return leng, nil +} + +type DaysOfWeek struct { + unusedBits byte + bitString BitString + monday bool + tuesday bool + wednesday bool + thursday bool + friday bool + saturday bool + sunday bool +} + +func NewBACnetDaysOfWeek() *DaysOfWeek { + return &DaysOfWeek{ + unusedBits: 1, + bitString: *NewBACnetBitString(1, *internal.NewBitArray(8)), + } +} + +func (d *DaysOfWeek) Decode(buffer []byte, offset int, apduLen int) int { + d.bitString = BitString{} + return d.bitString.Decode(buffer, offset, apduLen) +} + +func (d *DaysOfWeek) SetDay(day int, value bool) error { + if day < 0 || day > 6 { + return fmt.Errorf("Day index out of range") + } + d.bitString.Value.Set(day, value) + return nil +} + +func (d *DaysOfWeek) GetDay(day int) (bool, error) { + if day < 0 || day > 6 { + return false, fmt.Errorf("Day index out of range") + } + return d.bitString.Value.Get(day) +} + +type BitString struct { + UnusedBits byte + Value internal.BitArray +} + +func NewBACnetBitString(unusedBits byte, value internal.BitArray) *BitString { + return &BitString{ + UnusedBits: unusedBits, + Value: value, + } +} + +func (b *BitString) Decode(buffer []byte, offset, apduLen int) int { + leng := 0 + if apduLen > 0 { + b.UnusedBits = buffer[offset] + leng += 1 + b.Value = *internal.NewBitArray((apduLen - 1) * 8) + bit := 0 + for i := 1; i < apduLen; i++ { + for i2 := 0; i2 < 8; i2++ { + b.Value.Set(bit, buffer[offset+i]&(1<<(7-i2)) != 0) + bit++ + } + } + } + return apduLen +} + +type Recipient struct { + Value interface{} +} + +func (br *Recipient) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + + if tagNumber == 0 { + // device_identifier + leng += leng1 + br.Value = &ObjectIdentifier{} + leng1, err = br.Value.(*ObjectIdentifier).Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else if tagNumber == 1 { + // address + br.Value = &Address{} + leng1, err = br.Value.(*Address).Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +// BACnetEventTransitionBits represents a BACnet event transition bits structure. +type EventTransitionBits struct { + UnusedBits uint8 + BitString *BitString +} + +// NewBACnetEventTransitionBits creates a new BACnet event transition bits instance. +func NewBACnetEventTransitionBits() *EventTransitionBits { + return &EventTransitionBits{ + UnusedBits: 5, + BitString: NewBACnetBitString(5, *internal.NewBitArray(5)), + } +} + +// Decode decodes the bit string from a buffer. +func (e *EventTransitionBits) Decode(buffer []byte, offset, apduLen int) int { + bitString := NewBACnetBitString(0, internal.BitArray{}) + decodedLen := bitString.Decode(buffer, offset, apduLen) + + e.BitString = bitString + return decodedLen +} + +// ToOffNormal returns the value of ToOffNormal property. +func (e *EventTransitionBits) ToOffNormal() (bool, error) { + return e.BitString.Value.Get(0) +} + +// SetToOffNormal sets the value of ToOffNormal property. +func (e *EventTransitionBits) SetToOffNormal(a bool) { + e.BitString.Value.Set(0, a) +} + +// ToFault returns the value of ToFault property. +func (e *EventTransitionBits) ToFault() (bool, error) { + return e.BitString.Value.Get(1) +} + +// SetToFault sets the value of ToFault property. +func (e *EventTransitionBits) SetToFault(a bool) { + e.BitString.Value.Set(1, a) +} + +// ToNormal returns the value of ToNormal property. +func (e *EventTransitionBits) ToNormal() (bool, error) { + return e.BitString.Value.Get(2) +} + +// SetToNormal sets the value of ToNormal property. +func (e *EventTransitionBits) SetToNormal(a bool) { + e.BitString.Value.Set(2, a) +} + +// BACnetStatusFlags represents a BACnet status flags. +type StatusFlags struct { + unusedbits int + bitstring BitString + inalarm bool + fault bool + overridden bool + outofservice bool +} + +// NewBACnetStatusFlags creates a new BACnetStatusFlags instance. +func NewBACnetStatusFlags() *StatusFlags { + return &StatusFlags{ + unusedbits: 4, + bitstring: *NewBACnetBitString(4, *internal.NewBitArrayFromByte(0x00)), + inalarm: false, + fault: false, + overridden: false, + outofservice: false, + } +} + +// decode decodes BACnetStatusFlags from a buffer. +func (s *StatusFlags) Decode(buffer []byte, offset, apduLen int) int { + s.bitstring = *NewBACnetBitString(byte(s.unusedbits), *internal.NewBitArrayFromByte(0x00)) + return s.bitstring.Decode(buffer, offset, apduLen) +} + +// InAlarm returns the inalarm property. +func (s *StatusFlags) InAlarm() (bool, error) { + return s.bitstring.Value.Get(0) +} + +// SetInAlarm sets the inalarm property. +func (s *StatusFlags) SetInAlarm(a bool) { + s.bitstring.Value.Set(0, a) +} + +// Fault returns the fault property. +func (s *StatusFlags) Fault() (bool, error) { + return s.bitstring.Value.Get(1) +} + +// SetFault sets the fault property. +func (s *StatusFlags) SetFault(a bool) { + s.bitstring.Value.Set(1, a) +} + +// Overridden returns the overridden property. +func (s *StatusFlags) Overridden() (bool, error) { + return s.bitstring.Value.Get(2) +} + +// SetOverridden sets the overridden property. +func (s *StatusFlags) SetOverridden(a bool) { + s.bitstring.Value.Set(2, a) +} + +// OutOfService returns the outofservice property. +func (s *StatusFlags) OutOfService() (bool, error) { + return s.bitstring.Value.Get(3) +} + +// SetOutOfService sets the outofservice property. +func (s *StatusFlags) SetOutOfService(a bool) { + s.bitstring.Value.Set(3, a) +} + +type LimitEnable struct { + unusedBits uint8 + bitString BitString + lowLimitEnable bool + highLimitEnable bool +} + +func NewBACnetLimitEnable() *LimitEnable { + return &LimitEnable{ + unusedBits: 6, + bitString: *NewBACnetBitString(6, *internal.NewBitArrayFromByte(0x00)), + lowLimitEnable: false, + highLimitEnable: false, + } +} + +func (b *LimitEnable) Decode(buffer []byte, offset, apduLen int) int { + b.bitString = *NewBACnetBitString(0, *internal.NewBitArrayFromByte(0x00)) + return b.bitString.Decode(buffer, offset, apduLen) +} + +func (b *LimitEnable) LowLimitEnable() (bool, error) { + return b.bitString.Value.Get(0) +} + +func (b *LimitEnable) SetLowLimitEnable(a bool) { + b.bitString.Value.Set(0, a) +} + +func (b *LimitEnable) HighLimitEnable() (bool, error) { + return b.bitString.Value.Get(1) +} + +func (b *LimitEnable) SetHighLimitEnable(a bool) { + b.bitString.Value.Set(1, a) +} + +type ObjectTypesSupported struct { + unusedbits uint8 + bitstring BitString +} + +type ObjectTypesSupportedProperty int + +const ( + AnalogInput ObjectTypesSupportedProperty = iota + AanalofOutput + AnalogValue + BinaryInput + BinaryOutput + // TODO Add other properties here +) + +func NewBACnetObjectTypesSupported() *ObjectTypesSupported { + return &ObjectTypesSupported{ + unusedbits: 3, + bitstring: *NewBACnetBitString(3, *internal.NewBitArray(64)), + } +} + +func (b *ObjectTypesSupported) Set(property ObjectTypesSupportedProperty, value bool) { + b.bitstring.Value.Set(int(property), value) +} + +func (b *ObjectTypesSupported) Get(property ObjectTypesSupportedProperty) (bool, error) { + return b.bitstring.Value.Get(int(property)) +} + +func (b *ObjectTypesSupported) Decode(buf []byte, offset, apduLen int) int { + return b.bitstring.Decode(buf, offset, apduLen) +} + +type ServicesSupported struct { + unusedbits uint8 + bitstring BitString +} + +type ServicesSupportedProperty int + +// TODO check index sequence +const ( + acknowledgeAlarm ServicesSupportedProperty = iota + confirmedCOVNotification + confirmedCOVNotificationMultiple + confirmedEventNotification + getAlarmSummary + getEnrollmentSummary + getEventInformation + lifeSafetyOperation + subscribeCOV + subscribeCOVProperty + subscribeCOVPropertyMultiple + atomicReadFile + atomicWriteFile + addListElement + removeListElement + createObject + deleteObject + readProperty + readPropertyMultiple + readRange + writeGroup + writeProperty + writePropertyMultiple + deviceCommunicationControl + confirmedPrivateTransfer + confirmedTextMessage + reinitializeDevice + vtOpen + vtClose + vtData + whoAmI + youAre + iAm + iHave + unconfirmedCOVNotification + unconfirmedCOVNotificationMultiple + unconfirmedEventNotification + unconfirmedPrivateTransfer + unconfirmedTextMessage + timeSynchronization + utcTimeSynchronization + whoHas + whoIs +) + +func NewBACnetServicesSupported() *ServicesSupported { + return &ServicesSupported{ + unusedbits: 7, + bitstring: *NewBACnetBitString(7, *internal.NewBitArrayFromByte(0x00000000000000)), + } +} + +func (b *ServicesSupported) Set(property ServicesSupportedProperty, value bool) { + b.bitstring.Value.Set(int(property), value) +} + +func (b *ServicesSupported) Get(property ServicesSupportedProperty) (bool, error) { + return b.bitstring.Value.Get(int(property)) +} + +func (b *ServicesSupported) Decode(buf []byte, offset, apduLen int) int { + return b.bitstring.Decode(buf, offset, apduLen) +} + +// BACnetDateRange is a struct representing a date range in BACnet. +type DateRange struct { + StartDate time.Time + EndDate time.Time +} + +// Decode decodes a BACnetDateRange from the given buffer, offset, and length. +func (dr *DateRange) Decode(buffer []byte, offset, apduLen int) (int, error) { + var leng int + + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(Date) { + leng += leng1 + leng1, startDate := encoding.DecodeDateSafe(buffer, offset+leng, int(lenValue)) + dr.StartDate = startDate + leng += leng1 + } else { + return -1, fmt.Errorf("Unexpected tag number: %v", tagNumber) + } + + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber == byte(Date) { + leng += leng1 + leng1, endDate := encoding.DecodeDateSafe(buffer, offset+leng, int(lenValue)) + + dr.EndDate = endDate + leng += leng1 + } else { + return -1, fmt.Errorf("Unexpected tag number: %v", tagNumber) + } + + return leng, nil +} + +type AddressBinding struct { + DeviceIdentifier ObjectIdentifier + DeviceAddress Address +} + +func (binding *AddressBinding) Decode(buffer []byte, offset int, apduLen int) (int, error) { + length := 0 + length1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+length) + if err != nil { + return -1, err + } + + // device_identifier + if tagNumber == byte(BACnetObjectIdentifier) { + length += length1 + binding.DeviceIdentifier = ObjectIdentifier{} + leng1, err := binding.DeviceIdentifier.Decode(buffer, offset+length, int(lenValue)) + if err != nil { + return -1, err + } + length += leng1 + } else { + return -1, errInvalidTagNumber + } + + length1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+length) + if err != nil { + return -1, err + } + + if tagNumber == byte(UnsignedInt) { + binding.DeviceAddress = Address{} + leng1, err := binding.DeviceAddress.Decode(buffer, offset+length, int(lenValue)) + if err != nil { + return -1, err + } + length += leng1 + } else { + return -1, errInvalidTagNumber + } + + return length, nil +} + +type HostNPort struct { + Host *HostAddress + Port uint32 +} + +func (b *HostNPort) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + if !encoding.IsOpeningTagNumber(buffer, offset+leng, 0) { + return -1, errors.New("Invalid opening tag") + } + leng++ + b.Host = &HostAddress{} + hostLen, err := b.Host.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + leng += hostLen + leng++ + + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 1 { + leng1, b.Port, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Invalid tag number") + } + } else { + return -1, errors.New("Invalid context tag") + } + + return leng, nil +} + +type HostAddress struct { + Value interface{} +} + +func (b *HostAddress) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + + switch tagNumber { + case byte(Null): + leng += leng1 + b.Value = nil + case byte(OctetString): + leng += leng1 + leng1, octetString := encoding.DecodeOctetString(buffer, offset+leng, int(lenValue)) + b.Value = octetString + leng += leng1 + case byte(CharacterString): + leng += leng1 + leng1, characterString := encoding.DecodeCharacterString(buffer, offset+leng, apduLen-leng, int(lenValue)) + b.Value = characterString + leng += leng1 + default: + return -1, errors.New("Invalid tag number") + } + + return leng, nil +} + +type SecurityKeySet struct { + KeyRevision uint32 + ActivationTime *DateTime + ExpirationTime *DateTime + KeyIDs []*KeyIdentifier +} + +func (b *SecurityKeySet) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // key_revision + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 0 { + leng1, b.KeyRevision, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Invalid tag number") + } + } else { + return -1, errors.New("Invalid context tag") + } + + // activation_time + if encoding.IsOpeningTagNumber(buffer, offset+leng, 1) { + leng++ + b.ActivationTime = &DateTime{} + leng1 := b.ActivationTime.Decode(buffer, offset+leng) + leng += leng1 + } else { + return -1, errors.New("Invalid opening tag for activation_time") + } + + // expiration_time + if encoding.IsOpeningTagNumber(buffer, offset+leng, 2) { + leng++ + b.ExpirationTime = &DateTime{} + leng1 := b.ExpirationTime.Decode(buffer, offset+leng) + leng += leng1 + } else { + return -1, errors.New("Invalid opening tag for expiration_time") + } + + b.KeyIDs = make([]*KeyIdentifier, 0) + if encoding.IsOpeningTagNumber(buffer, offset+leng, 3) && leng < apduLen { + leng++ + for !encoding.IsClosingTagNumber(buffer, offset+leng, 3) { + bValue := &KeyIdentifier{} + leng1, err := bValue.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + b.KeyIDs = append(b.KeyIDs, bValue) + leng += leng1 + } + leng++ + } else { + return -1, errors.New("Invalid opening tag for key_ids or unexpected end of data") + } + + return leng, nil +} + +type KeyIdentifier struct { + Algorithm uint32 + KeyID uint32 +} + +func (b *KeyIdentifier) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // algorithm + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 0 { + leng1, b.Algorithm, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Invalid tag number for algorithm") + } + } else { + return -1, errors.New("Invalid context tag for algorithm") + } + + // key_id + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 1 { + leng1, b.KeyID, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Invalid tag number for key_id") + } + } else { + return -1, errors.New("Invalid context tag for key_id") + } + + return leng, nil +} + +type TimeStamp struct { + Value interface{} +} + +func (b *TimeStamp) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + if encoding.IsContextTag(buffer, offset+leng, 2) { + // BACnetDateTime + leng1, tagNumber, _, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 2 { + b.Value = &DateTime{} + leng1 := b.Value.(*DateTime).Decode(buffer, offset+leng) + leng += leng1 + } else { + return -1, errors.New("Invalid tag number for BACnetDateTime") + } + } else if encoding.IsContextTag(buffer, offset+leng, 1) { + // sequence number + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 1 { + leng1, seqNum, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + b.Value = seqNum + leng += leng1 + } else { + return -1, errors.New("Invalid tag number for sequence number") + } + } else if encoding.IsContextTag(buffer, offset+leng, 0) { + // time + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 0 { + leng1, bacnetTime := encoding.DecodeBACnetTimeSafe(buffer, offset+leng, int(lenValue)) + b.Value = bacnetTime + leng += leng1 + } else { + return -1, errors.New("Invalid tag number for time") + } + } else { + return -1, errors.New("Invalid context tag") + } + + return leng, nil +} + +func (b *TimeStamp) Encode() []byte { + // Implement the encoding logic as needed for your specific application. + return nil +} + +func (b *TimeStamp) EncodeContext(tagNumber encoding.BACnetApplicationTag) []byte { + tmp := b.Encode() + return append(encoding.EncodeTag(tagNumber, true, len(tmp)), tmp...) +} + +// ReadAccessSpecification represents a BACnet Read Access Specification. +type ReadAccessSpecification struct { + ObjectIdentifier ObjectIdentifier + ListOfPropertyReferences []PropertyReference +} + +// Decode decodes the ReadAccessSpecification from the buffer. +func (ras *ReadAccessSpecification) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // ObjectIdentifier + ras.ObjectIdentifier = ObjectIdentifier{} + leng1, err := ras.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + leng += leng1 + + // ListOfPropertyReferences + if buffer[offset+leng] == 0x30 { // Check for opening tag (0x30) + leng++ + + ras.ListOfPropertyReferences = make([]PropertyReference, 0) + + for apduLen-leng > 1 && buffer[offset+leng] != 0x00 { // Check for closing tag (0x00) + bValue := PropertyReference{} + leng1, err := bValue.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + leng += leng1 + + ras.ListOfPropertyReferences = append(ras.ListOfPropertyReferences, bValue) + } + } else { + return -1, errors.New("Invalid opening tag for ListOfPropertyReferences") + } + + leng++ + + return leng, nil +} + +// PropertyReference represents a BACnet property reference. +type PropertyReference struct { + PropertyIdentifier interface{} + PropertyArrayIndex uint32 +} + +// Decode decodes the PropertyReference from the buffer. +func (ref *PropertyReference) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // propertyIdentifier + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + propID := encoding.PropertyList + leng1, ref.PropertyIdentifier, err = encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, &propID) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Missing context tag for PropertyIdentifier") + } + + if leng < apduLen { + if encoding.IsContextTag(buffer, offset+leng, 1) && !encoding.IsClosingTagNumber(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, ref.PropertyArrayIndex, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + return leng, nil +} + +type DeviceObjectPropertyReference struct { + ObjectIdentifier ObjectIdentifier + PropertyIdentifier interface{} + PropertyArrayIndex uint32 + DeviceIdentifier ObjectIdentifier +} + +func (bdopr *DeviceObjectPropertyReference) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // tag 0 objectidentifier + bdopr.ObjectIdentifier = ObjectIdentifier{} + leng1, err := bdopr.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errors.New("failed to decode object identifier") + } + leng += leng1 + + // tag 1 propertyidentifier + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + propID := encoding.PropertyList + leng1, bdopr.PropertyIdentifier, err = encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, &propID) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Missing tag property Identifier") + } + + if leng < apduLen { + // tag 2 property-array-index optional + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bdopr.PropertyArrayIndex, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + if leng < apduLen { + // tag 3 device-identifier optional + bdopr.DeviceIdentifier = ObjectIdentifier{} + leng1, err := bdopr.DeviceIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 3) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errors.New("failed to decode device identifier") + } + leng += leng1 + } + + return leng, nil +} + +type DeviceObjectReference struct { + DeviceIdentifier ObjectIdentifier + ObjectIdentifier ObjectIdentifier +} + +func (bdor *DeviceObjectReference) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // tag 0 device-identifier optional + bdor.DeviceIdentifier = ObjectIdentifier{} + leng1, err := bdor.DeviceIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + if leng1 > 0 { + leng += leng1 + } + + // tag 1 objectidentifier + bdor.ObjectIdentifier = ObjectIdentifier{} + leng1, err = bdor.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 1) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + + return leng, nil +} + +type ObjectPropertyReference struct { + ObjectIdentifier ObjectIdentifier + PropertyIdentifier interface{} + PropertyArrayIndex uint32 +} + +func (bopr *ObjectPropertyReference) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // tag 0 objectidentifier + bopr.ObjectIdentifier = ObjectIdentifier{} + leng1, err := bopr.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + + // tag 1 propertyidentifier + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + propID := encoding.PropertyList + leng1, bopr.PropertyIdentifier, err = encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, &propID) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + if leng < apduLen { + // tag 2 property-array-index optional + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bopr.PropertyArrayIndex, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + return leng, nil +} + +type AccumulatorRecord struct { + Timestamp TimeStamp + PresentValue uint32 + AccumulatedValue uint32 + AccumulatorStatus AccumulatorStatus +} + +type AccumulatorStatus int + +const ( + AccumulatorStatusNormal AccumulatorStatus = iota + AccumulatorStatusStarting + AccumulatorStatusRecovered + AccumulatorStatusAbnormal + AccumulatorStatusFailed +) + +func (bar *AccumulatorRecord) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // 0 timestamp + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + bar.Timestamp = TimeStamp{} + leng1, err = bar.Timestamp.Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + } else { + return -1, errors.New("Missing tag 0") + } + + // 1 present-value + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bar.PresentValue, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Missing tag 1") + } + + // 2 accumulated-value + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bar.AccumulatedValue, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errors.New("Missing tag 2") + } + + // 3 accumulator-status + if encoding.IsContextTag(buffer, offset+leng, 3) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, statusValue, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + bar.AccumulatorStatus = AccumulatorStatus(statusValue) + leng += leng1 + } else { + return -1, errors.New("Missing tag 3") + } + + return leng, nil +} + +type ActionList struct { + Action []ActionCommand +} + +func (bal *ActionList) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // SEQUENCE OF BACnetActionCommand + if encoding.IsOpeningTagNumber(buffer, offset+leng, 0) { + leng += 1 + bal.Action = make([]ActionCommand, 0) + for !encoding.IsClosingTagNumber(buffer, offset+leng, 0) { + bac := ActionCommand{} + leng1, err := bac.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + bal.Action = append(bal.Action, bac) + } + leng += 1 + } + + return leng, nil +} + +type ActionCommand struct { + DeviceIdentifier ObjectIdentifier + ObjectIdentifier ObjectIdentifier + PropertyIdentifier interface{} + PropertyArrayIndex int + PropertyValue []Value + Priority int + PostDelay int + QuitOnFailure bool + WriteSuccessful bool +} + +func (bac *ActionCommand) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // 0 device_identifier optional + if encoding.IsContextTag(buffer, offset+leng, 0) { + bac.DeviceIdentifier = ObjectIdentifier{} + leng1, err := bac.DeviceIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + leng += leng1 + } + + // 1 object_identifier + if encoding.IsContextTag(buffer, offset+leng, 1) { + bac.ObjectIdentifier = ObjectIdentifier{} + leng1, err := bac.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 1) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + // 2 property_identifier + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + propID := encoding.PropertyList + leng1, bac.PropertyIdentifier, err = encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, &propID) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + // 3 property_array_index + if encoding.IsContextTag(buffer, offset+leng, 3) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + bac.PropertyArrayIndex, _, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + + // tag 4 property-value + if encoding.IsOpeningTagNumber(buffer, offset+leng, 4) { + leng += 1 + bac.PropertyValue = []Value{} + for !encoding.IsClosingTagNumber(buffer, offset+leng, 4) && leng < apduLen { + bv := Value{} + propID := bac.PropertyIdentifier.(encoding.PropertyIdentifier) + leng1, _ := bv.Decode(buffer, offset+leng, apduLen-leng, &bac.ObjectIdentifier.Type, &propID) + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + bac.PropertyValue = append(bac.PropertyValue, bv) + } + if encoding.IsClosingTagNumber(buffer, offset+leng, 4) { + leng += 1 + } else { + return -1, errInvalidTagNumber + } + } else { + return -1, errInvalidTagNumber + } + + if leng < apduLen { + // tag 5 priority optional + if encoding.IsContextTag(buffer, offset+leng, 5) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + bac.Priority, _, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + if leng < apduLen { + // tag 6 post-delay optional + if encoding.IsContextTag(buffer, offset+leng, 6) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + bac.PostDelay, _, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + if leng < apduLen { + // tag 7 quit-on-failure optional + if encoding.IsContextTag(buffer, offset+leng, 7) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + uVal, _, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bac.QuitOnFailure = uVal > 0 + } + } + + if leng < apduLen { + // tag 8 write-successful optional + if encoding.IsContextTag(buffer, offset+leng, 8) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + uVal, _, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bac.WriteSuccessful = uVal > 0 + } + } + + return leng, nil +} + +type Scale struct { + Value interface{} +} + +func (bs *Scale) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + if encoding.IsContextTag(buffer, offset+leng, 0) { + // float-scale + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bs.Value = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + } else if encoding.IsContextTag(buffer, offset+leng, 1) { + // integer-scale + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bs.Value, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type LightingCommand struct { + Operation LightingOperation + TargetLevel float32 + RampRate float32 + StepIncrement float32 + FadeTime uint32 + Priority uint32 +} + +type LightingOperation uint32 + +const ( + LightingOperationUnknown LightingOperation = iota + LightingOperationOff + LightingOperationOn + LightingOperationToggle + LightingOperationDecrement + LightingOperationIncrement +) + +func (blc *LightingCommand) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // operation + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, uVal, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + blc.Operation = LightingOperation(uVal) + } else { + return -1, errInvalidTagNumber + } + + if leng < apduLen { + // target-level + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, blc.TargetLevel = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + } + } + + if leng < apduLen { + // ramp-rate + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, blc.RampRate = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + } + } + + if leng < apduLen { + // step-increment + if encoding.IsContextTag(buffer, offset+leng, 3) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, blc.StepIncrement = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + } + } + + if leng < apduLen { + // fade-time + if encoding.IsContextTag(buffer, offset+leng, 4) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, blc.FadeTime, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + if leng < apduLen { + // priority + if encoding.IsContextTag(buffer, offset+leng, 5) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, blc.Priority, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } + } + + return leng, nil +} + +type Prescale struct { + Multiplier uint32 + ModuloDivide uint32 +} + +func (bp *Prescale) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + // multiplier + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bp.Multiplier, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + // modulo_divide + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bp.ModuloDivide, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type ShedLevelChoice int + +const ( + BACnetShedLevelChoicePercent ShedLevelChoice = iota + BACnetShedLevelChoiceLevel + BACnetShedLevelChoiceAmount +) + +type ShedLevel struct { + Choice ShedLevelChoice + Value interface{} +} + +func (bsl *ShedLevel) Decode(buffer []byte, offset int, apduLen int) (int, error) { + leng := 0 + + if encoding.IsContextTag(buffer, offset+leng, 0) { + // percent + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bsl.Value, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bsl.Choice = BACnetShedLevelChoicePercent + } else if encoding.IsContextTag(buffer, offset+leng, 1) { + // level + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bsl.Value, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bsl.Choice = BACnetShedLevelChoiceLevel + } else if encoding.IsContextTag(buffer, offset+leng, 2) { + // amount + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, bsl.Value = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + bsl.Choice = BACnetShedLevelChoiceAmount + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type LogRecordChoice int + +const ( + BACnetLogRecordChoiceLogStatus LogRecordChoice = iota + BACnetLogRecordChoiceBooleanValue + BACnetLogRecordChoiceRealValue + BACnetLogRecordChoiceEnumeratedValue + BACnetLogRecordChoiceUnsignedValue + BACnetLogRecordChoiceIntegerValue + BACnetLogRecordChoiceBitstringValue + BACnetLogRecordChoiceNullValue + BACnetLogRecordChoiceFailure + BACnetLogRecordChoiceTimeChange + BACnetLogRecordChoiceAnyValue +) + +type LogRecord struct { + Timestamp TimeStamp + LogDatum interface{} + StatusFlags StatusFlags +} + +func (blr *LogRecord) Decode(buffer []byte, offset, apduLen int, objType *encoding.ObjectType, propID *encoding.PropertyIdentifier) (int, error) { + leng := 0 + + // timestamp + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + blr.Timestamp = TimeStamp{} + leng1, err = blr.Timestamp.Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + if encoding.IsContextTag(buffer, offset+leng, 1) { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + + switch LogRecordChoice(tagNumber) { + case BACnetLogRecordChoiceLogStatus: + blr.LogDatum = &LogStatus{} + leng += blr.LogDatum.(*LogStatus).Decode(buffer, offset+leng, int(lenValue)) + case BACnetLogRecordChoiceBooleanValue: + blr.LogDatum = buffer[offset+leng] > 0 + leng++ + case BACnetLogRecordChoiceRealValue: + leng1, logValue := encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + blr.LogDatum = logValue + case BACnetLogRecordChoiceEnumeratedValue: + leng1, logValue, err := encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, nil) + if err != nil { + return -1, err + } + leng += leng1 + blr.LogDatum = logValue + case BACnetLogRecordChoiceUnsignedValue: + leng1, logValue, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + blr.LogDatum = logValue + case BACnetLogRecordChoiceIntegerValue: + leng1, logValue := encoding.DecodeSigned(buffer, offset+leng, int(lenValue)) + leng += leng1 + blr.LogDatum = logValue + case BACnetLogRecordChoiceBitstringValue: + blr.LogDatum = &BitString{} + leng += blr.LogDatum.(*BitString).Decode(buffer, offset+leng, int(lenValue)) + case BACnetLogRecordChoiceNullValue: + blr.LogDatum = nil + leng++ + case BACnetLogRecordChoiceFailure: + blr.LogDatum = &Error{} + leng1, err = blr.LogDatum.(*Error).Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + leng += leng1 + if encoding.IsClosingTagNumber(buffer, offset+leng, byte(BACnetLogRecordChoiceFailure)) { + leng++ + } else { + return -1, errInvalidTagNumber + } + case BACnetLogRecordChoiceTimeChange: + leng1, logValue := encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + blr.LogDatum = logValue + case BACnetLogRecordChoiceAnyValue: + blr.LogDatum = []Value{} + for !encoding.IsClosingTagNumber(buffer, offset+leng, byte(BACnetLogRecordChoiceAnyValue)) && leng < apduLen { + bValue := Value{} + leng1, _ := bValue.Decode(buffer, offset+leng, apduLen-leng, objType, propID) + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + blr.LogDatum = append(blr.LogDatum.([]Value), bValue) + } + if encoding.IsClosingTagNumber(buffer, offset+leng, byte(BACnetLogRecordChoiceAnyValue)) { + leng++ + } else { + return -1, errInvalidTagNumber + } + default: + return -1, errInvalidTagNumber + } + } else { + return -1, errInvalidTagNumber + } + + if leng < apduLen { + // status-flags optional + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + blr.StatusFlags = StatusFlags{} + leng += blr.StatusFlags.Decode(buffer, offset+leng, int(lenValue)) + } + } + + return leng, nil +} + +type LogStatus struct { + UnusedBits uint8 + BitString *BitString +} + +func NewBACnetLogStatus() LogStatus { + return LogStatus{ + UnusedBits: 5, + BitString: NewBACnetBitString(5, *internal.NewBitArrayFromByte(0x00)), + } +} + +func (bls *LogStatus) Decode(buffer []byte, offset, apduLen int) int { + bls.BitString = NewBACnetBitString(5, *internal.NewBitArrayFromByte(0x00)) + return bls.BitString.Decode(buffer, offset, apduLen) +} + +func (bls *LogStatus) SetLogDisabled(a bool) { + bls.BitString.Value.Set(0, a) +} + +func (bls *LogStatus) SetBufferPurged(a bool) { + bls.BitString.Value.Set(1, a) +} + +func (bls *LogStatus) SetLogInterrupted(a bool) { + bls.BitString.Value.Set(2, a) +} + +func (bls *LogStatus) LogDisabled() (bool, error) { + return bls.BitString.Value.Get(0) +} + +func (bls *LogStatus) BufferPurged() (bool, error) { + return bls.BitString.Value.Get(1) +} + +func (bls *LogStatus) LogInterrupted() (bool, error) { + return bls.BitString.Value.Get(2) +} + +type Error struct { + ErrorClass ErrorClassEnum + ErrorCode ErrorCodeEnum +} + +func (be *Error) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // Decode error_class + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == byte(Enumerated) { + leng1, eVal, err := encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, nil) + if err != nil { + return -1, err + } + leng += leng1 + be.ErrorClass = ErrorClassEnum(eVal.(uint32)) + } else { + return -1, errInvalidTagNumber + } + + // Decode error_code + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == byte(Enumerated) { + leng1, eVal, err := encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, nil) + if err != nil { + return -1, err + } + leng += leng1 + be.ErrorCode = ErrorCodeEnum(eVal.(uint32)) + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type CalendarEntry struct { + Value interface{} +} + +func (ce *CalendarEntry) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + if tagNumber == 0 { + leng1, ce.Value = encoding.DecodeDateSafe(buffer, offset+leng, int(lenValue)) + leng += leng1 + } else if tagNumber == 1 { + ce.Value = &DateRange{} + leng1, err := ce.Value.(*DateRange).Decode(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + } else if tagNumber == 2 { + ce.Value = &WeekNDay{} + leng += ce.Value.(*WeekNDay).Decode(buffer, offset+leng, int(lenValue)) + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type EventLogRecord struct { + Timestamp DateTime + LogDatum interface{} +} + +func (elr *EventLogRecord) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + if encoding.IsContextTag(buffer, offset+leng, 0) { + leng1, _, _, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + elr.Timestamp = DateTime{} + leng += elr.Timestamp.Decode(buffer, offset+leng) + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type WeekNDay struct { + Month int + WeekOfMonth int + DayOfWeek int +} + +func (wnd *WeekNDay) Decode(buffer []byte, offset, apduLen int) int { + if apduLen >= 3 { + wnd.Month = int(buffer[offset]) + wnd.WeekOfMonth = int(buffer[offset+1]) + wnd.DayOfWeek = int(buffer[offset+2]) + } else { + return -1 + } + return 3 +} + +type ReadAccessResultReadResult struct { + PropertyIdentifier encoding.PropertyIdentifier + PropertyArrayIndex uint32 + ReadResult interface{} // Either BACnetValue or BACnetError +} + +func (rarr *ReadAccessResultReadResult) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + // 2 propertyidentifier + if encoding.IsContextTag(buffer, offset+leng, 2) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + propID := encoding.PropertyList + leng1, propertyIdentifier, err := encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, &propID) + if err != nil { + return -1, err + } + leng += leng1 + rarr.PropertyIdentifier = encoding.PropertyIdentifier(propertyIdentifier.(uint32)) + } else { + return -1, errInvalidTagNumber + } + + // 3 property_array_index + if encoding.IsContextTag(buffer, offset+leng, 3) { + leng1, _, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + leng1, propertyArrayIndex, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + rarr.PropertyArrayIndex = propertyArrayIndex + } + + if leng < apduLen { + if encoding.IsOpeningTagNumber(buffer, offset+leng, 4) { + rarr.ReadResult = &Value{} + leng1, err := rarr.ReadResult.(*Value).Decode(buffer, offset+leng, apduLen-leng, nil, nil) + if err != nil { + return -1, err + } + leng += leng1 + if encoding.IsClosingTagNumber(buffer, offset+leng, 4) { + leng += 1 + } else { + return -1, errInvalidTagNumber + } + } else if encoding.IsOpeningTagNumber(buffer, offset+leng, 5) { + rarr.ReadResult = &Error{} + leng1, err := rarr.ReadResult.(*Error).Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + leng += leng1 + if encoding.IsClosingTagNumber(buffer, offset+leng, 5) { + leng += 1 + } else { + return -1, errInvalidTagNumber + } + } + } + return leng, nil +} + +type ReadAccessResult struct { + ObjectIdentifier ObjectIdentifier + ListOfResults []ReadAccessResultReadResult +} + +func (rar *ReadAccessResult) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + // tag 0 objectidentifier + rar.ObjectIdentifier = ObjectIdentifier{} + if encoding.IsClosingTagNumber(buffer, offset+leng, 0) { + leng1, err := rar.ObjectIdentifier.DecodeContext(buffer, offset+leng, apduLen-leng, 0) + if err != nil { + return -1, err + } + leng += leng1 + } else { + return -1, errInvalidTagNumber + } + + if encoding.IsOpeningTagNumber(buffer, offset+leng, 1) { + leng += 1 + rar.ListOfResults = make([]ReadAccessResultReadResult, 0) + + for (apduLen-leng) > 1 && !encoding.IsClosingTagNumber(buffer, offset+leng, 1) { + bValue := ReadAccessResultReadResult{} + leng1, err := bValue.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + leng += leng1 + + rar.ListOfResults = append(rar.ListOfResults, bValue) + } + + if encoding.IsClosingTagNumber(buffer, offset+leng, 1) { + leng += 1 + } else { + return -1, errInvalidTagNumber + } + } else { + return -1, errInvalidTagNumber + } + + return leng, nil +} + +type AccessRule struct { + TimeRangeSpecifier TimeRangeSpecifierChoice + TimeRange DeviceObjectPropertyReference + LocationSpecifier LocationSpecifierChoice + Location DeviceObjectReference + Enable bool +} + +type TimeRangeSpecifierChoice int + +const ( + Specified TimeRangeSpecifierChoice = iota + Always +) + +type LocationSpecifierChoice int + +const ( + SpecifiedLocation LocationSpecifierChoice = iota + All +) + +func (bar *AccessRule) Decode(buffer []byte, offset, apduLen int) int { + return -1 +} + +type NameValue struct { + Name string + Value Value +} + +func (bnv *NameValue) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // Name + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != 0 { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, bnv.Name = encoding.DecodeCharacterString(buffer, offset+leng, apduLen-leng, int(lenValue)) + leng += leng1 + + // Decode value + decodeLen := 0 + if leng < apduLen { + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + leng += leng1 + + switch ApplicationTags(tagNumber) { + case Null: + bnv.Value = Value{Value: nil} + decodeLen = 0 + // Fixme: fix null type nothing else to do, some Error occurs!!!! + case Boolean: + if lenValue > 0 { + bnv.Value = Value{Value: true} + } else { + bnv.Value = Value{Value: false} + } + case UnsignedInt: + decodeLen, bnv.Value.Value, err = encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + case SignedInt: + decodeLen, bnv.Value.Value = encoding.DecodeSigned(buffer, offset+leng, int(lenValue)) + case Real: + decodeLen, bnv.Value.Value = encoding.DecodeRealSafe(buffer, offset+leng, int(lenValue)) + case Double: + decodeLen, bnv.Value.Value = encoding.DecodeDoubleSafe(buffer, offset+leng, int(lenValue)) + case OctetString: + decodeLen, bnv.Value.Value = encoding.DecodeOctetString(buffer, offset+leng, int(lenValue)) + case CharacterString: + decodeLen, bnv.Value.Value = encoding.DecodeCharacterString(buffer, offset+leng, apduLen-leng, int(lenValue)) + case ApplicationTagsBitString: + bitValue := BitString{} + decodeLen = bitValue.Decode(buffer, offset+leng, int(lenValue)) + bnv.Value.Value = bitValue + case Enumerated: + decodeLen, bnv.Value.Value, err = encoding.DecodeEnumerated(buffer, offset+leng, lenValue, nil, nil) + if err != nil { + return -1, err + } + case Date: + decodeLen, dateValue := encoding.DecodeDateSafe(buffer, offset+leng, int(lenValue)) + + if leng < apduLen { + leng1, tagNumber, _, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng+decodeLen) + if err != nil { + return -1, err + } + if tagNumber == byte(Time) { + leng += leng1 + leng-- + bnv.Value.Value = &DateTime{} + decodeLen = bnv.Value.Value.(*DateTime).Decode(buffer, offset+leng) + } + } else { + bnv.Value.Value = dateValue + } + case Time: + decodeLen, bnv.Value.Value = encoding.DecodeBACnetTimeSafe(buffer, offset+leng, int(lenValue)) + } + + if decodeLen < 0 { + return -1, errInvalidMessageLength + } + leng += decodeLen + } + + return leng, nil +} + +type NameValueCollection struct { + Members []NameValue +} + +func (bnc *NameValueCollection) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // Check if it's an opening tag number + if !encoding.IsOpeningTagNumber(buffer, offset+leng, 0) { + return -1, errInvalidTagNumber + } + + leng += 1 + bnc.Members = make([]NameValue, 0) + + for !encoding.IsClosingTagNumber(buffer, offset+leng, 0) { + bValue := NameValue{} + leng1, err := bValue.Decode(buffer, offset+leng, apduLen-leng) + if err != nil { + return -1, err + } + if leng1 < 0 { + return -1, errInvalidMessageLength + } + leng += leng1 + bnc.Members = append(bnc.Members, bValue) + } + + leng += 1 + return leng, nil +} + +type SecurityPolicy int + +type NetworkSecurityPolicy struct { + PortID int + SecurityLevel SecurityPolicy +} + +func (bns *NetworkSecurityPolicy) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // port_id + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != 0 { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, portID, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + leng += leng1 + bns.PortID = int(portID) + + leng = 0 + // security_level + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != 1 { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, uVal, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bns.SecurityLevel = SecurityPolicy(uVal) + + return leng, nil +} + +type PortPermission struct { + PortID int + Enabled bool +} + +func (bpp *PortPermission) Decode(buffer []byte, offset, apduLen int) (int, error) { + leng := 0 + + // port_id + leng1, tagNumber, lenValue, err := encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != 0 { + return -1, errInvalidTagNumber + } + leng += leng1 + leng1, portID, err := encoding.DecodeUnsigned(buffer, offset+leng, int(lenValue)) + if err != nil { + return -1, err + } + leng += leng1 + bpp.PortID = int(portID) + + leng = 0 + // enabled + leng1, tagNumber, lenValue, err = encoding.DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, err + } + if tagNumber != 1 { + return -1, errInvalidTagNumber + } + leng += leng1 + if lenValue > 0 { + bpp.Enabled = true + } else { + bpp.Enabled = false + } + + return leng, nil +} + +type PriorityValue struct { + Value interface{} +} + +func (bpv *PriorityValue) Decode(buffer []byte, offset, apduLen int) int { + // TODO Implement decoding logic for BACnetPriorityValue + return -1 +} + +type PriorityArray struct { + Value [16]PriorityValue +} + +func (bpa *PriorityArray) Decode(buffer []byte, offset, apduLen int) int { + leng := 0 + i := 0 + + for leng < apduLen && i < 16 { + bpa.Value[i] = PriorityValue{} + leng += bpa.Value[i].Decode(buffer, offset+leng, apduLen-leng) + i++ + } + + return leng +} + +type ProcessIdSelection struct { + Value interface{} // You can specify the type you expect here +} + +func (bps *ProcessIdSelection) Decode(buffer []byte, offset, apduLen int) int { + // TODO Implement decoding logic for BACnetProcessIdSelection + return -1 +} + +type PropertyAccessResult struct { + ObjectIdentifier ObjectIdentifier + PropertyIdentifier encoding.PropertyIdentifier + PropertyArrayIndex int + DeviceIdentifier ObjectIdentifier + AccessResult interface{} +} + +func (bpar *PropertyAccessResult) Decode(buffer []byte, offset, apduLen int) int { + // TODO Implement decoding logic for BACnetPropertyAccessResult here + return -1 +} + +type SetpointReference struct { + Value interface{} +} + +func (bsr *SetpointReference) Decode(buffer []byte, offset, apduLen int) int { + // TODO Implement decoding logic for BACnetSetpointReference here + return 0 +} + +type SpecialEvent struct { + Period interface{} + ListOfTimeValues []interface{} + EventPriority int +} + +func (bse *SpecialEvent) Decode(buffer []byte, offset, apduLen int) int { + // TODO Implement decoding logic for BACnetSpecialEvent here + return 0 +} + +type TimerStateChangeValue struct { + Value interface{} +} + +func (scv *TimerStateChangeValue) Decode([]byte, int, int) int { + // TODO implement decoder + return -1 +} + +type ValueSource struct { + Value interface{} +} + +func (scv *ValueSource) Decode([]byte, int, int) int { + // TODO implement decoder + return -1 +} + +type VMACEntry struct { + VirtualMacAddress interface{} + NativeMacAddress interface{} +} + +func (scv *VMACEntry) Decode([]byte, int, int) int { + // TODO implement decoder + return -1 +} + +type AssignedAccessRights struct { + AssignedAccessRights DeviceObjectReference + Enable bool +} + +func (scv *AssignedAccessRights) Decode([]byte, int, int) int { + // TODO implement decoder + return -1 +} + +type AssignedLandingCalls struct { + LandingCalls []landingCall +} + +type landingCall struct { + FloorNumber int + Direction LiftCarDirection +} + +func (balc *AssignedLandingCalls) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetAssignedLandingCalls + return 0 +} + +type LiftCarDirection int + +type AuthenticationFactor struct { + FormatType AuthenticationFactorType + FormatClass int + Value []byte +} + +func (baf *AuthenticationFactor) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetAuthenticationFactor + return 0 +} + +type AuthenticationFactorType int + +type AuthenticationFactorFormat struct { + FormatType AuthenticationFactorType + VendorID int + VendorFormat int +} + +func (baff *AuthenticationFactorFormat) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for AuthenticationFactorFormat + return 0 +} + +type AuthenticationPolicy struct { + Policies []policy + OrderEnforced bool + Timeout int +} + +type policy struct { + CredentialDataInput DeviceObjectReference + Index int +} + +func (bap *AuthenticationPolicy) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for AuthenticationPolicy + return 0 +} + +type BDTEntry struct { + // Define BDTEntry structure here +} + +type ChannelValue struct { + Value interface{} +} + +func (bcv *ChannelValue) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetChannelValue + return 0 +} + +type COVSubscription struct { + Recipient RecipientProcess + MonitoredPropertyReference ObjectPropertyReference + IssueConfirmedNotifications bool + TimeRemaining int + COVIncrement float64 +} + +func (bcs *COVSubscription) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetCOVSubscription + return 0 +} + +type AccessAuthenticationFactorDisable int + +type CredentialAuthenticationFactor struct { + Disable AccessAuthenticationFactorDisable + AuthenticationFactor AuthenticationFactor +} + +func (bcaf *CredentialAuthenticationFactor) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetCredentialAuthenticationFactor + return 0 +} + +type DailySchedule struct { + DaySchedule []interface{} +} + +func (bds *DailySchedule) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetDailySchedule + return 0 +} + +type RecipientProcess struct { + // Define BACnetRecipientProcess structure here +} + +type EventNotificationSubscription struct { + Recipient Recipient + ProcessIdentifier int + IssueConfirmedNotifications bool + TimeRemaining int +} + +func (bens *EventNotificationSubscription) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetEventNotificationSubscription + return 0 +} + +type EventParameter struct { + Value interface{} +} + +func (bep *EventParameter) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetEventParameter + return 0 +} + +type FaultParameter struct { + Value interface{} +} + +func (bfp *FaultParameter) Decode(buffer []byte, offset, apduLen int) int { + // Implement decoding logic for BACnetFaultParameter + return 0 +} diff --git a/pkg/bacnet/whois.go b/pkg/bacnet/whois.go new file mode 100644 index 0000000..56ef9b2 --- /dev/null +++ b/pkg/bacnet/whois.go @@ -0,0 +1,74 @@ +package bacnet + +import ( + "fmt" + + "github.com/absmach/bacnet/pkg/encoding" +) + +type WhoIs struct { + HighLimit, LowLimit *uint32 +} + +func (w *WhoIs) Decode(buf []byte, offset, apduLen int) (int, error) { + if apduLen <= 0 { + return 0, fmt.Errorf("invalid apdu length") + } + length, tagNum, lenVal, err := encoding.DecodeTagNumberAndValue(buf, offset) + if err != nil { + return -1, err + } + if tagNum != 0 { + return -1, fmt.Errorf("invalid tag number") + } + if apduLen > length { + len1, decVal, err := encoding.DecodeUnsigned(buf, offset+length, int(lenVal)) + if err != nil { + return -1, err + } + length += len1 + + if decVal <= encoding.MaxInstance { + w.LowLimit = &decVal + } + + if apduLen > length { + len1, tagNum, lenVal, err := encoding.DecodeTagNumberAndValue(buf, offset+length) + if err != nil { + return -1, err + } + length += len1 + if tagNum != 1 { + return -1, fmt.Errorf("invalid tag number") + } + if apduLen > length { + len1, decVal, err := encoding.DecodeUnsigned(buf, offset+length, int(lenVal)) + if err != nil { + return -1, err + } + length += len1 + if decVal <= encoding.MaxInstance { + w.HighLimit = &decVal + } + } else { + return -1, fmt.Errorf("apdu lenth greater than message lenth") + } + } else { + return -1, fmt.Errorf("apdu lenth greater than message lenth") + } + } else { + return -1, fmt.Errorf("apdu lenth greater than message lenth") + } + return length, nil +} + +func (w WhoIs) Encode() []byte { + var res []byte + if w.LowLimit != nil && *w.LowLimit <= encoding.MaxInstance { + res = append(res, encoding.EncodeContextUnsigned(0, *w.LowLimit)...) + } + if w.HighLimit != nil && *w.HighLimit <= encoding.MaxInstance { + res = append(res, encoding.EncodeContextUnsigned(1, *w.HighLimit)...) + } + return res +} diff --git a/pkg/encoding/date_time.go b/pkg/encoding/date_time.go new file mode 100644 index 0000000..9502011 --- /dev/null +++ b/pkg/encoding/date_time.go @@ -0,0 +1,86 @@ +package encoding + +import "time" + +func DecodeApplicationDate(buf []byte, offset int) (int, time.Time) { + len, tagNum := decodeTagNumber(buf, offset) + if tagNum == byte(Date) { + len1, date := decodeDate(buf, offset+len) + return len + len1, date + } + return -1, time.Time{} +} + +func DecodeApplicationTime(buf []byte, offset int) (int, time.Time) { + len, tagNum := decodeTagNumber(buf, offset) + if tagNum == byte(Time) { + len1, btime := decodeBACnetTime(buf, offset+len) + return len + len1, btime + } + return -1, time.Time{} +} + +func decodeDate(buf []byte, offset int) (int, time.Time) { + year := buf[offset] + month := buf[offset+1] + day := buf[offset+2] + wday := buf[offset+3] + if month == 0xFF && day == 0xFF && wday == 0xFF && year == 0xFF { + return 4, time.Time{} + } + //BACnet year starts in 1900 + return 4, time.Date(int(year)+1900, time.Month(month), int(day), 0, 0, 0, 0, nil) +} + +func DecodeDateSafe(buf []byte, offset, lenVal int) (int, time.Time) { + if lenVal != 4 { + return lenVal, time.Time{} + } + return decodeDate(buf, offset) +} + +func decodeBACnetTime(buf []byte, offset int) (int, time.Time) { + hour := buf[offset] + min := buf[offset+1] + sec := buf[offset+2] + hundredths := buf[offset+3] + if hour == 0xFF && min == 0xFF && sec == 0xFF && hundredths == 0xFF { + return 4, time.Time{} + } + if hundredths > 100 { + hundredths = 0 + } + return 4, time.Date(0, 0, 0, int(hour), int(min), int(sec), int(hundredths*10), nil) +} + +func decodeBACnetTimeSafe(buf []byte, offset int, lenVal int) (int, time.Time) { + if lenVal != 4 { + return lenVal, time.Time{} + } + return decodeBACnetTime(buf, offset) +} + +func EncodeApplicationDate(date time.Time) []byte { + return append(EncodeTag(Date, false, 4), encodeBacnetDate(date)...) +} +func EncodeApplicationTime(date time.Time) []byte { + return append(EncodeTag(Time, false, 4), encodeBacnetTime(date)...) +} + +func encodeBacnetDate(date time.Time) []byte { + data := make([]byte, 4) + data[0] = byte(date.Year() - 1900) + data[1] = byte(date.Month()) + data[2] = byte(date.Day()) + data[3] = byte(date.Weekday()) + return data +} + +func encodeBacnetTime(date time.Time) []byte { + data := make([]byte, 4) + data[0] = byte(date.Hour()) + data[1] = byte(date.Minute()) + data[2] = byte(date.Second()) + data[3] = byte(date.Nanosecond() / 10) + return data +} diff --git a/pkg/encoding/decoding.go b/pkg/encoding/decoding.go new file mode 100644 index 0000000..6035dc4 --- /dev/null +++ b/pkg/encoding/decoding.go @@ -0,0 +1,174 @@ +package encoding + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "time" +) + +var errOutOfBounds = errors.New("lenth is out of range of array") + +type BacnetCharacterStringEncodings int + +const ( + CharacterANSIX34 BacnetCharacterStringEncodings = 0 + CharacterUTF8 BacnetCharacterStringEncodings = 0 + CharacterMSDBCS BacnetCharacterStringEncodings = 1 + CharacterJISC6226 BacnetCharacterStringEncodings = 2 + CharacterJISX0208 BacnetCharacterStringEncodings = 2 + CharacterUCS4 BacnetCharacterStringEncodings = 3 + CharacterUCS2 BacnetCharacterStringEncodings = 4 + CharacterISO8859 BacnetCharacterStringEncodings = 5 +) + +func DecodeUnsigned(buffer []byte, offset, length int) (int, uint32, error) { + if length > len(buffer) { + return -1, 0, errOutOfBounds + } + value := uint32(0) + for i := 0; i < length; i++ { + value += uint32(buffer[offset+i]) << uint(8*(length-i-1)) + } + return length, value, nil +} + +func DecodeOctetString(buf []byte, offset, lenVal int) (int, []byte) { + tmp := make([]byte, lenVal) + copy(tmp, buf[offset:offset+lenVal]) + return len(tmp), tmp +} + +// multiCharsetCharacterstringDecode decodes a multi-character set character string. +func multiCharsetCharacterStringDecode(buffer []byte, offset, maxLength int, encoding BacnetCharacterStringEncodings, length int) (bool, string) { + var charString, enc string + + switch encoding { + case CharacterUCS2: + enc = "utf-16" + case CharacterUCS4: + enc = "utf-32" + case CharacterISO8859: + enc = "latin-1" + case CharacterJISX0208: + enc = "shift_jisx0213" + case CharacterMSDBCS: + enc = "dbcs" + default: + enc = "utf-8" + } + + c := make([]byte, 0) + for i := 0; i < length; i++ { + c = append(c, buffer[offset+i]) + } + + if enc == "utf-8" { + charString = string(c) + } else { + charString = string(c) + charString = string([]rune(charString)) // Convert to Unicode + } + + return true, charString +} + +func DecodeCharacterString(buffer []byte, offset, maxLength, lenValue int) (int, string) { + leng := 0 + status := false + var charString string + + status, charString = multiCharsetCharacterStringDecode(buffer, offset+1, maxLength, BacnetCharacterStringEncodings(buffer[offset]), lenValue-1) + if status { + leng = lenValue + } + + return leng, charString +} + +func decodeContextCharacterString(buffer []byte, offset, maxLength int, tagNumber byte) (int, string, error) { + leng := 0 + status := false + charString := "" + + if IsContextTag(buffer, offset+leng, tagNumber) { + leng1, _, lenValue, err := DecodeTagNumberAndValue(buffer, offset+leng) + if err != nil { + return -1, "", err + } + leng += leng1 + + status, charString = multiCharsetCharacterStringDecode(buffer, offset+1+leng, maxLength, BacnetCharacterStringEncodings(buffer[offset+leng]), int(lenValue)-1) + if status { + leng += int(lenValue) + } else { + leng = -1 + } + } + + return leng, charString, nil +} + +func DecodeSigned(buffer []byte, offset int, lenValue int) (int, int) { + value := 0 + for i := 0; i < lenValue; i++ { + value += int(buffer[offset+i]) << uint(8*(lenValue-i-1)) + } + // Check if it is negative + if value > int(math.Pow(256, float64(lenValue))/2)-1 { + value = -(int(math.Pow(256, float64(lenValue))) - value) + } + return lenValue, value +} + +func decodeReal(buffer []byte, offset int) (int, float32) { + value := binary.BigEndian.Uint32(buffer[offset : offset+4]) + return 4, math.Float32frombits(value) +} + +func DecodeRealSafe(buffer []byte, offset int, lenValue int) (int, float32) { + if lenValue != 4 { + return lenValue, float32(0.0) + } + return decodeReal(buffer, offset) +} + +func decodeDouble(buffer []byte, offset int) (int, float64) { + value := binary.BigEndian.Uint64(buffer[offset : offset+8]) + return 8, math.Float64frombits(value) +} + +func DecodeDoubleSafe(buffer []byte, offset int, lenValue int) (int, float64) { + if lenValue != 8 { + return lenValue, float64(0.0) + } + return decodeDouble(buffer, offset) +} + +func DecodeBACnetTimeSafe(buf []byte, offset, lenVal int) (int, time.Time) { + if lenVal != 4 { + return lenVal, time.Time{} + } + return decodeBACnetTime(buf, offset) +} + +func decodeObjectID(buffer []byte, offset int) (int, ObjectType, uint32, error) { + leng, value, err := DecodeUnsigned(buffer, offset, 4) + if err != nil { + return -1, 0, 0, err + } + + objectInstance := value & MaxInstance + + objectType := ObjectType((int(value) >> InstanceBits) & MaxObject) + + return leng, objectType, objectInstance, nil +} + +func DecodeObjectIDSafe(buffer []byte, offset int, lenValue uint32) (int, ObjectType, uint32, error) { + if lenValue != 4 { + return 0, 0, 0, fmt.Errorf("lenValue not equal to 4") + } + return decodeObjectID(buffer, offset) +} diff --git a/pkg/encoding/encoding.go b/pkg/encoding/encoding.go new file mode 100644 index 0000000..6c19d8c --- /dev/null +++ b/pkg/encoding/encoding.go @@ -0,0 +1,149 @@ +package encoding + +import ( + "bytes" + "encoding/binary" + "math" +) + +const ( + MaxObject = 0x3FF + InstanceBits = 22 + MaxInstance = 0x3FFFFF + MaxBitstringBytes = 15 + ArrayAll = 0xFFFFFFFF + NoPriority = 0 + MinPriority = 1 + MaxPriority = 16 + MaxUint8 = 0x100 + MaxUint16 = 0x10000 + MaxUint24 = 0x1000000 +) + +func EncodeUnsigned(value uint32) []byte { + switch { + case value < MaxUint8: + buf := make([]byte, 1) + buf[0] = uint8(value) + return buf + case value < MaxUint16: + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf, uint16(value)) + return buf + case value < MaxUint24: + buf := make([]byte, 3) + buf[0] = byte((value & 0xff0000) >> 16) + buf[1] = byte((value & 0x00ff00) >> 8) + buf[2] = byte(value & 0x0000ff) + return buf + default: + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, value) + return buf + } +} + +func EncodeSigned(value int32) []byte { + switch { + case value < MaxUint8: + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, uint8(value)) + return buf.Bytes() + case value < MaxUint16: + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, uint16(value)) + return buf.Bytes() + case value < MaxUint24: + buf := make([]byte, 3) + buf[0] = byte((value & 0xff0000) >> 16) + buf[1] = byte((value & 0x00ff00) >> 8) + buf[2] = byte(value & 0x0000ff) + return buf + default: + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, value) + return buf.Bytes() + } +} + +func EncodeContextUnsigned(tagNum BACnetApplicationTag, val uint32) []byte { + len := 0 + switch { + case val < MaxUint8: + len = 1 + case val < MaxUint16: + len = 2 + case val < MaxUint24: + len = 3 + default: + len = 4 + } + return append(EncodeTag(tagNum, true, len), EncodeUnsigned(val)...) +} + +// EncodeApplicationUnsigned encodes an unsigned integer value as a BACnet application tag. +func EncodeApplicationUnsigned(value uint32) []byte { + tmp := EncodeUnsigned(value) + tag := EncodeTag(UnsignedInt, false, len(tmp)) + return append(tag, tmp...) +} + +func EncodeApplicationEnumerated(value uint32) []byte { + tmp := EncodeUnsigned(value) + return append(EncodeTag(Enumerated, false, len(tmp)), tmp...) +} + +func EncodeApplicationOctetString(octetString []byte, octetOffset, octetCount int) []byte { + tag := EncodeTag(OctetString, false, octetCount) + octetStringSegment := octetString[octetOffset : octetOffset+octetCount] + return append(tag, octetStringSegment...) +} + +func EncodeApplicationCharacterString(value string) []byte { + tmp := encodeBACnetCharacterString(value) + tag := EncodeTag(CharacterString, false, len(tmp)) + return append(tag, tmp...) +} + +func encodeBACnetCharacterString(value string) []byte { + encoding := []byte{byte(CharacterUTF8)} + encodedValue := []byte(value) + return append(encoding, encodedValue...) +} + +func EncodeApplicationBoolean(val bool) []byte { + if val { + return EncodeTag(Boolean, false, 1) + } + return EncodeTag(Boolean, false, 0) +} + +func EncodeApplicationSigned(val int32) []byte { + tmp := EncodeSigned(val) + return append(EncodeTag(SignedInt, false, len(tmp)), tmp...) +} + +func EncodeApplicationReal(val float32) []byte { + return append(EncodeTag(Real, false, 4), encodeBACnetReal(val)...) +} + +func EncodeApplicationDouble(val float64) []byte { + return append(EncodeTag(Double, false, 8), encodeBACnetDouble(val)...) +} + +func encodeBACnetReal(value float32) []byte { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, math.Float32bits(value)) + return buf +} + +func encodeBACnetDouble(value float64) []byte { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, math.Float64bits(value)) + return buf +} + +func EncodeApplicationBitString(val interface{}) []byte { + // TODO + return nil +} diff --git a/pkg/encoding/property.go b/pkg/encoding/property.go new file mode 100644 index 0000000..8213937 --- /dev/null +++ b/pkg/encoding/property.go @@ -0,0 +1,1856 @@ +package encoding + +type NetworkType int + +const ( + Ethernet NetworkType = iota + ARCnet + MSTP + PTP + LonTalk + IPV4 + Zigbee + Virtual + IPV6 + Serial +) + +type ObjectType uint16 + +const ( + AnalogInput ObjectType = iota + AnalogOutput + AnalogValue + BinaryInput + BinaryOutput + BinaryValue + Calendar + ObjectTypeCommand + ObjectTypeDevice + EventEnrollment + File + Group + Loop + MultiStateInput + MultiStateOutput + ObjectTypeNotificationClass + Program + Schedule + Averaging + MultiStateValue + TrendLog + LifeSafetyPoint + LifeSafetyZone + Accumulator + PulseConverter + EventLog + GlobalGroup + TrendLogMultiple + LoadControl + StructuredView + AccessDoor + Timer + AccessCredential + AccessPoint + AccessRights + AccessUser + AccessZone + CredentialDataInput + NetworkSecurity + BitStringValue + CharacterStringValue + DatePatternValue + DateValue + DateTimePatternValue + DateTimeValue + IntegerValue + LargeAnalogValue + OctetStringValue + PositiveIntegerValue + TimePatternValue + TimeValue + NotificationForwarder + AlertEnrollment + Channel + LightingOutput + BinaryLightingOutput + NetworkPort + ObjectTypeElevatorGroup + Escalator + Lift + Staging +) + +// PropertyIdentifier represents property identifiers. +type PropertyIdentifier int + +const ( + AckedTransitions PropertyIdentifier = 0 + AckRequired PropertyIdentifier = 1 + PropertyIdentifierAction PropertyIdentifier = 2 + ActionText PropertyIdentifier = 3 + ActiveText PropertyIdentifier = 4 + ActiveVtSessions PropertyIdentifier = 5 + AlarmValue PropertyIdentifier = 6 + AlarmValues PropertyIdentifier = 7 + All PropertyIdentifier = 8 + AllWritesSuccessful PropertyIdentifier = 9 + ApduSegmentTimeout PropertyIdentifier = 10 + ApduTimeout PropertyIdentifier = 11 + ApplicationSoftwareVersion PropertyIdentifier = 12 + Archive PropertyIdentifier = 13 + Bias PropertyIdentifier = 14 + ChangeOfStateCount PropertyIdentifier = 15 + ChangeOfStateTime PropertyIdentifier = 16 + NotificationClass PropertyIdentifier = 17 + Blank1 PropertyIdentifier = 18 + ControlledVariableReference PropertyIdentifier = 19 + ControlledVariableUnits PropertyIdentifier = 20 + ControlledVariableValue PropertyIdentifier = 21 + CovIncrement PropertyIdentifier = 22 + DateList PropertyIdentifier = 23 + DaylightSavingsStatus PropertyIdentifier = 24 + Deadband PropertyIdentifier = 25 + DerivativeConstant PropertyIdentifier = 26 + DerivativeConstantUnits PropertyIdentifier = 27 + Description PropertyIdentifier = 28 + DescriptionOfHalt PropertyIdentifier = 29 + DeviceAddressBinding PropertyIdentifier = 30 + DeviceType PropertyIdentifier = 31 + EffectivePeriod PropertyIdentifier = 32 + ElapsedActiveTime PropertyIdentifier = 33 + ErrorLimit PropertyIdentifier = 34 + EventEnable PropertyIdentifier = 35 + PropertyIdentifierEventState PropertyIdentifier = 36 + PropertyIdentifierEventType PropertyIdentifier = 37 + ExceptionSchedule PropertyIdentifier = 38 + FaultValues PropertyIdentifier = 39 + FeedbackValue PropertyIdentifier = 40 + PropertyIdentifierFileAccessMethod PropertyIdentifier = 41 + FileSize PropertyIdentifier = 42 + FileType PropertyIdentifier = 43 + FirmwareRevision PropertyIdentifier = 44 + HighLimit PropertyIdentifier = 45 + InactiveText PropertyIdentifier = 46 + InProcess PropertyIdentifier = 47 + InstanceOf PropertyIdentifier = 48 + IntegralConstant PropertyIdentifier = 49 + IntegralConstantUnits PropertyIdentifier = 50 + IssueConfirmedNotifications PropertyIdentifier = 51 + LimitEnable PropertyIdentifier = 52 + ListOfGroupMembers PropertyIdentifier = 53 + ListOfObjectPropertyReferences PropertyIdentifier = 54 + ListOfSessionKeys PropertyIdentifier = 55 + LocalDate PropertyIdentifier = 56 + LocalTime PropertyIdentifier = 57 + Location PropertyIdentifier = 58 + LowLimit PropertyIdentifier = 59 + ManipulatedVariableReference PropertyIdentifier = 60 + MaximumOutput PropertyIdentifier = 61 + MaxApduLengthAccepted PropertyIdentifier = 62 + MaxInfoFrames PropertyIdentifier = 63 + MaxMaster PropertyIdentifier = 64 + MaxPresValue PropertyIdentifier = 65 + MinimumOffTime PropertyIdentifier = 66 + MinimumOnTime PropertyIdentifier = 67 + MinimumOutput PropertyIdentifier = 68 + MinPresValue PropertyIdentifier = 69 + ModelName PropertyIdentifier = 70 + ModificationDate PropertyIdentifier = 71 + PropertyIdentifierNotifyType PropertyIdentifier = 72 + NumberOfApduRetries PropertyIdentifier = 73 + NumberOfStates PropertyIdentifier = 74 + ObjectIdentifierPI PropertyIdentifier = 75 + ObjectList PropertyIdentifier = 76 + ObjectName PropertyIdentifier = 77 + ObjectPropertyReference PropertyIdentifier = 78 + ObjectTypePI PropertyIdentifier = 79 + Optional PropertyIdentifier = 80 + OutOfService PropertyIdentifier = 81 + OutputUnits PropertyIdentifier = 82 + EventParameters PropertyIdentifier = 83 + PropertyIdentifierPolarity PropertyIdentifier = 84 + PresentValue PropertyIdentifier = 85 + Priority PropertyIdentifier = 86 + PriorityArray PropertyIdentifier = 87 + PriorityForWriting PropertyIdentifier = 88 + ProcessIdentifier PropertyIdentifier = 89 + ProgramChange PropertyIdentifier = 90 + ProgramLocation PropertyIdentifier = 91 + PropertyIdentifierProgramState PropertyIdentifier = 92 + ProportionalConstant PropertyIdentifier = 93 + ProportionalConstantUnits PropertyIdentifier = 94 + ProtocolConformanceClass PropertyIdentifier = 95 + ProtocolObjectTypesSupported PropertyIdentifier = 96 + ProtocolServicesSupported PropertyIdentifier = 97 + ProtocolVersion PropertyIdentifier = 98 + ReadOnly PropertyIdentifier = 99 + ReasonForHalt PropertyIdentifier = 100 + Recipient PropertyIdentifier = 101 + RecipientList PropertyIdentifier = 102 + PropertyIdentifierReliability PropertyIdentifier = 103 + RelinquishDefault PropertyIdentifier = 104 + Required PropertyIdentifier = 105 + Resolution PropertyIdentifier = 106 + SegmentationSupported PropertyIdentifier = 107 + Setpoint PropertyIdentifier = 108 + SetpointReference PropertyIdentifier = 109 + StateText PropertyIdentifier = 110 + StatusFlags PropertyIdentifier = 111 + SystemStatus PropertyIdentifier = 112 + TimeDelay PropertyIdentifier = 113 + TimeOfActiveTimeReset PropertyIdentifier = 114 + TimeOfStateCountReset PropertyIdentifier = 115 + TimeSynchronizationRecipients PropertyIdentifier = 116 + Units PropertyIdentifier = 117 + UpdateInterval PropertyIdentifier = 118 + UtcOffset PropertyIdentifier = 119 + VendorIdentifier PropertyIdentifier = 120 + VendorName PropertyIdentifier = 121 + VtClassesSupported PropertyIdentifier = 122 + WeeklySchedule PropertyIdentifier = 123 + AttemptedSamples PropertyIdentifier = 124 + AverageValue PropertyIdentifier = 125 + BufferSize PropertyIdentifier = 126 + ClientCovIncrement PropertyIdentifier = 127 + CovResubscriptionInterval PropertyIdentifier = 128 + CurrentNotifyTime PropertyIdentifier = 129 + EventTimeStamps PropertyIdentifier = 130 + LogBuffer PropertyIdentifier = 131 + LogDeviceObjectProperty PropertyIdentifier = 132 + Enable PropertyIdentifier = 133 + LogInterval PropertyIdentifier = 134 + MaximumValue PropertyIdentifier = 135 + MinimumValue PropertyIdentifier = 136 + NotificationThreshold PropertyIdentifier = 137 + PreviousNotifyTime PropertyIdentifier = 138 + ProtocolRevision PropertyIdentifier = 139 + RecordsSinceNotification PropertyIdentifier = 140 + RecordCount PropertyIdentifier = 141 + StartTime PropertyIdentifier = 142 + StopTime PropertyIdentifier = 143 + StopWhenFull PropertyIdentifier = 144 + TotalRecordCount PropertyIdentifier = 145 + ValidSamples PropertyIdentifier = 146 + WindowInterval PropertyIdentifier = 147 + WindowSamples PropertyIdentifier = 148 + MaximumValueTimestamp PropertyIdentifier = 149 + MinimumValueTimestamp PropertyIdentifier = 150 + VarianceValue PropertyIdentifier = 151 + ActiveCovSubscriptions PropertyIdentifier = 152 + BackupFailureTimeout PropertyIdentifier = 153 + ConfigurationFiles PropertyIdentifier = 154 + DatabaseRevision PropertyIdentifier = 155 + DirectReading PropertyIdentifier = 156 + LastRestoreTime PropertyIdentifier = 157 + MaintenanceRequired PropertyIdentifier = 158 + MemberOf PropertyIdentifier = 159 + Mode PropertyIdentifier = 160 + OperationExpected PropertyIdentifier = 161 + Setting PropertyIdentifier = 162 + Silenced PropertyIdentifier = 163 + TrackingValue PropertyIdentifier = 164 + ZoneMembers PropertyIdentifier = 165 + LifeSafetyAlarmValues PropertyIdentifier = 166 + MaxSegmentsAccepted PropertyIdentifier = 167 + ProfileName PropertyIdentifier = 168 + AutoSlaveDiscovery PropertyIdentifier = 169 + ManualSlaveAddressBinding PropertyIdentifier = 170 + SlaveAddressBinding PropertyIdentifier = 171 + SlaveProxyEnable PropertyIdentifier = 172 + LastNotifyRecord PropertyIdentifier = 173 + ScheduleDefault PropertyIdentifier = 174 + AcceptedModes PropertyIdentifier = 175 + AdjustValue PropertyIdentifier = 176 + Count PropertyIdentifier = 177 + CountBeforeChange PropertyIdentifier = 178 + CountChangeTime PropertyIdentifier = 179 + CovPeriod PropertyIdentifier = 180 + InputReference PropertyIdentifier = 181 + LimitMonitoringInterval PropertyIdentifier = 182 + LoggingObject PropertyIdentifier = 183 + LoggingRecord PropertyIdentifier = 184 + Prescale PropertyIdentifier = 185 + PulseRate PropertyIdentifier = 186 + Scale PropertyIdentifier = 187 + ScaleFactor PropertyIdentifier = 188 + UpdateTime PropertyIdentifier = 189 + ValueBeforeChange PropertyIdentifier = 190 + ValueSet PropertyIdentifier = 191 + ValueChangeTime PropertyIdentifier = 192 + AlignIntervals PropertyIdentifier = 193 + IntervalOffset PropertyIdentifier = 195 + LastRestartReason PropertyIdentifier = 196 + PropertyIdentifierLoggingType PropertyIdentifier = 197 + RestartNotificationRecipients PropertyIdentifier = 202 + TimeOfDeviceRestart PropertyIdentifier = 203 + TimeSynchronizationInterval PropertyIdentifier = 204 + Trigger PropertyIdentifier = 205 + UtcTimeSynchronizationRecipients PropertyIdentifier = 206 + NodeSubtype PropertyIdentifier = 207 + PropertyIdentifierNodeType PropertyIdentifier = 208 + StructuredObjectList PropertyIdentifier = 209 + SubordinateAnnotations PropertyIdentifier = 210 + SubordinateList PropertyIdentifier = 211 + ActualShedLevel PropertyIdentifier = 212 + DutyWindow PropertyIdentifier = 213 + ExpectedShedLevel PropertyIdentifier = 214 + FullDutyBaseline PropertyIdentifier = 215 + RequestedShedLevel PropertyIdentifier = 218 + ShedDuration PropertyIdentifier = 219 + ShedLevelDescriptions PropertyIdentifier = 220 + ShedLevels PropertyIdentifier = 221 + StateDescription PropertyIdentifier = 222 + PropertyIdentifierDoorAlarmState PropertyIdentifier = 226 + DoorExtendedPulseTime PropertyIdentifier = 227 + DoorMembers PropertyIdentifier = 228 + DoorOpenTooLongTime PropertyIdentifier = 229 + DoorPulseTime PropertyIdentifier = 230 + PropertyIdentifierDoorStatus PropertyIdentifier = 231 + DoorUnlockDelayTime PropertyIdentifier = 232 + PropertyIdentifierLockStatus PropertyIdentifier = 233 + MaskedAlarmValues PropertyIdentifier = 234 + SecuredStatus PropertyIdentifier = 235 + AbsenteeLimit PropertyIdentifier = 244 + AccessAlarmEvents PropertyIdentifier = 245 + AccessDoors PropertyIdentifier = 246 + PropertyIdentifierAccessEvent PropertyIdentifier = 247 + AccessEventAuthenticationFactor PropertyIdentifier = 248 + AccessEventCredential PropertyIdentifier = 249 + AccessEventTime PropertyIdentifier = 250 + AccessTransactionEvents PropertyIdentifier = 251 + Accompaniment PropertyIdentifier = 252 + AccompanimentTime PropertyIdentifier = 253 + ActivationTime PropertyIdentifier = 254 + ActiveAuthenticationPolicy PropertyIdentifier = 255 + AssignedAccessRights PropertyIdentifier = 256 + AuthenticationFactors PropertyIdentifier = 257 + AuthenticationPolicyList PropertyIdentifier = 258 + AuthenticationPolicyNames PropertyIdentifier = 259 + PropertyIdentifierAuthenticationStatus PropertyIdentifier = 260 + PropertyIdentifierAuthorizationMode PropertyIdentifier = 261 + BelongsTo PropertyIdentifier = 262 + CredentialDisable PropertyIdentifier = 263 + CredentialStatus PropertyIdentifier = 264 + Credentials PropertyIdentifier = 265 + CredentialsInZone PropertyIdentifier = 266 + DaysRemaining PropertyIdentifier = 267 + EntryPoints PropertyIdentifier = 268 + ExitPoints PropertyIdentifier = 269 + ExpiryTime PropertyIdentifier = 270 + ExtendedTimeEnable PropertyIdentifier = 271 + FailedAttemptEvents PropertyIdentifier = 272 + FailedAttempts PropertyIdentifier = 273 + FailedAttemptsTime PropertyIdentifier = 274 + LastAccessEvent PropertyIdentifier = 275 + LastAccessPoint PropertyIdentifier = 276 + LastCredentialAdded PropertyIdentifier = 277 + LastCredentialAddedTime PropertyIdentifier = 278 + LastCredentialRemoved PropertyIdentifier = 279 + LastCredentialRemovedTime PropertyIdentifier = 280 + LastUseTime PropertyIdentifier = 281 + Lockout PropertyIdentifier = 282 + LockoutRelinquishTime PropertyIdentifier = 283 + MasterExemption PropertyIdentifier = 284 + MaxFailedAttempts PropertyIdentifier = 285 + Members PropertyIdentifier = 286 + MusterPoint PropertyIdentifier = 287 + NegativeAccessRules PropertyIdentifier = 288 + NumberOfAuthenticationPolicies PropertyIdentifier = 289 + OccupancyCount PropertyIdentifier = 290 + OccupancyCountAdjust PropertyIdentifier = 291 + OccupancyCountEnable PropertyIdentifier = 292 + OccupancyExemption PropertyIdentifier = 293 + OccupancyLowerLimit PropertyIdentifier = 294 + OccupancyLowerLimitEnforced PropertyIdentifier = 295 + OccupancyState PropertyIdentifier = 296 + OccupancyUpperLimit PropertyIdentifier = 297 + OccupancyUpperLimitEnforced PropertyIdentifier = 298 + PassbackExemption PropertyIdentifier = 299 + PassbackMode PropertyIdentifier = 300 + PassbackTimeout PropertyIdentifier = 301 + PositiveAccessRules PropertyIdentifier = 302 + ReasonForDisable PropertyIdentifier = 303 + SupportedFormats PropertyIdentifier = 304 + SupportedFormatClasses PropertyIdentifier = 305 + ThreatAuthority PropertyIdentifier = 306 + ThreatLevel PropertyIdentifier = 307 + TraceFlag PropertyIdentifier = 308 + TransactionNotificationClass PropertyIdentifier = 309 + UserExternalIdentifier PropertyIdentifier = 310 + UserInformationReference PropertyIdentifier = 311 + UserName PropertyIdentifier = 317 + UserType PropertyIdentifier = 318 + UsesRemaining PropertyIdentifier = 319 + ZoneFrom PropertyIdentifier = 320 + ZoneTo PropertyIdentifier = 321 + AccessEventTag PropertyIdentifier = 322 + GlobalIdentifier PropertyIdentifier = 323 + VerificationTime PropertyIdentifier = 326 + BaseDeviceSecurityPolicy PropertyIdentifier = 327 + DistributionKeyRevision PropertyIdentifier = 328 + DoNotHide PropertyIdentifier = 329 + KeySets PropertyIdentifier = 330 + LastKeyServer PropertyIdentifier = 331 + NetworkAccessSecurityPolicies PropertyIdentifier = 332 + PacketReorderTime PropertyIdentifier = 333 + SecurityPduTimeout PropertyIdentifier = 334 + SecurityTimeWindow PropertyIdentifier = 335 + SupportedSecurityAlgorithm PropertyIdentifier = 336 + UpdateKeySetTimeout PropertyIdentifier = 337 + BackupAndRestoreState PropertyIdentifier = 338 + BackupPreparationTime PropertyIdentifier = 339 + RestoreCompletionTime PropertyIdentifier = 340 + RestorePreparationTime PropertyIdentifier = 341 + BitMask PropertyIdentifier = 342 + BitText PropertyIdentifier = 343 + IsUtc PropertyIdentifier = 344 + GroupMembers PropertyIdentifier = 345 + GroupMemberNames PropertyIdentifier = 346 + MemberStatusFlags PropertyIdentifier = 347 + RequestedUpdateInterval PropertyIdentifier = 348 + CovuPeriod PropertyIdentifier = 349 + CovuRecipients PropertyIdentifier = 350 + EventMessageTexts PropertyIdentifier = 351 + EventMessageTextsConfig PropertyIdentifier = 352 + EventDetectionEnable PropertyIdentifier = 353 + EventAlgorithmInhibit PropertyIdentifier = 354 + EventAlgorithmInhibitRef PropertyIdentifier = 355 + TimeDelayNormal PropertyIdentifier = 356 + ReliabilityEvaluationInhibit PropertyIdentifier = 357 + FaultParameters PropertyIdentifier = 358 + PropertyIdentifierFaultType PropertyIdentifier = 359 + LocalForwardingOnly PropertyIdentifier = 360 + ProcessIdentifierFilter PropertyIdentifier = 361 + SubscribedRecipients PropertyIdentifier = 362 + PortFilter PropertyIdentifier = 363 + AuthorizationExemptions PropertyIdentifier = 364 + AllowGroupDelayInhibit PropertyIdentifier = 365 + ChannelNumber PropertyIdentifier = 366 + ControlGroups PropertyIdentifier = 367 + ExecutionDelay PropertyIdentifier = 368 + LastPriority PropertyIdentifier = 369 + PropertyIdentifierWriteStatus PropertyIdentifier = 370 + PropertyList PropertyIdentifier = 371 + SerialNumber PropertyIdentifier = 372 + BlinkWarnEnable PropertyIdentifier = 373 + DefaultFadeTime PropertyIdentifier = 374 + DefaultRampRate PropertyIdentifier = 375 + DefaultStepIncrement PropertyIdentifier = 376 + EgressTime PropertyIdentifier = 377 + InProgress PropertyIdentifier = 378 + InstantaneousPower PropertyIdentifier = 379 + LightingCommand PropertyIdentifier = 380 + LightingCommandDefaultPriority PropertyIdentifier = 381 + MaxActualValue PropertyIdentifier = 382 + MinActualValue PropertyIdentifier = 383 + Power PropertyIdentifier = 384 + Transition PropertyIdentifier = 385 + EgressActive PropertyIdentifier = 386 + InterfaceValue PropertyIdentifier = 387 + FaultHighLimit PropertyIdentifier = 388 + FaultLowLimit PropertyIdentifier = 389 + LowDiffLimit PropertyIdentifier = 390 + StrikeCount PropertyIdentifier = 391 + TimeOfStrikeCountReset PropertyIdentifier = 392 + DefaultTimeout PropertyIdentifier = 393 + InitialTimeout PropertyIdentifier = 394 + LastStateChange PropertyIdentifier = 395 + StateChangeValues PropertyIdentifier = 396 + TimerRunning PropertyIdentifier = 397 + PropertyIdentifierTimerState PropertyIdentifier = 398 + ApduLength PropertyIdentifier = 399 + IpAddress PropertyIdentifier = 400 + IpDefaultGateway PropertyIdentifier = 401 + IpDhcpEnable PropertyIdentifier = 402 + IpDhcpLeaseTime PropertyIdentifier = 403 + IpDhcpLeaseTimeRemaining PropertyIdentifier = 404 + IpDhcpServer PropertyIdentifier = 405 + IpDnsServer PropertyIdentifier = 406 + BacnetIpGlobalAddress PropertyIdentifier = 407 + BacnetIpMode PropertyIdentifier = 408 + BacnetIpMulticastAddress PropertyIdentifier = 409 + BacnetIpNatTraversal PropertyIdentifier = 410 + IpSubnetMask PropertyIdentifier = 411 + BacnetIpUdpPort PropertyIdentifier = 412 + BbmdAcceptFdRegistrations PropertyIdentifier = 413 + BbmdBroadcastDistributionTable PropertyIdentifier = 414 + BbmdForeignDeviceTable PropertyIdentifier = 415 + ChangesPending PropertyIdentifier = 416 + Command PropertyIdentifier = 417 + FdBbmdAddress PropertyIdentifier = 418 + FdSubscriptionLifetime PropertyIdentifier = 419 + LinkSpeed PropertyIdentifier = 420 + LinkSpeeds PropertyIdentifier = 421 + LinkSpeedAutonegotiate PropertyIdentifier = 422 + MacAddress PropertyIdentifier = 423 + NetworkInterfaceName PropertyIdentifier = 424 + NetworkNumber PropertyIdentifier = 425 + PropertyIdentifierNetworkNumberQuality PropertyIdentifier = 426 + PropertyIdentifierNetworkType PropertyIdentifier = 427 + RoutingTable PropertyIdentifier = 428 + VirtualMacAddressTable PropertyIdentifier = 429 + CommandTimeArray PropertyIdentifier = 430 + CurrentCommandPriority PropertyIdentifier = 431 + LastCommandTime PropertyIdentifier = 432 + ValueSource PropertyIdentifier = 433 + ValueSourceArray PropertyIdentifier = 434 + BacnetIpv6Mode PropertyIdentifier = 435 + Ipv6Address PropertyIdentifier = 436 + Ipv6PrefixLength PropertyIdentifier = 437 + BacnetIpv6UdpPort PropertyIdentifier = 438 + Ipv6DefaultGateway PropertyIdentifier = 439 + BacnetIpv6MulticastAddress PropertyIdentifier = 440 + Ipv6DnsServer PropertyIdentifier = 441 + Ipv6AutoAddressingEnable PropertyIdentifier = 442 + Ipv6DhcpLeaseTime PropertyIdentifier = 443 + Ipv6DhcpLeaseTimeRemaining PropertyIdentifier = 444 + Ipv6DhcpServer PropertyIdentifier = 445 + Ipv6ZoneIndex PropertyIdentifier = 446 + AssignedLandingCalls PropertyIdentifier = 447 + CarAssignedDirection PropertyIdentifier = 448 + CarDoorCommand PropertyIdentifier = 449 + CarDoorStatus PropertyIdentifier = 450 + CarDoorText PropertyIdentifier = 451 + CarDoorZone PropertyIdentifier = 452 + CarDriveStatus PropertyIdentifier = 453 + CarLoad PropertyIdentifier = 454 + CarLoadUnits PropertyIdentifier = 455 + CarMode PropertyIdentifier = 456 + CarMovingDirection PropertyIdentifier = 457 + CarPosition PropertyIdentifier = 458 + ElevatorGroup PropertyIdentifier = 459 + EnergyMeter PropertyIdentifier = 460 + EnergyMeterRef PropertyIdentifier = 461 + PropertyIdentifierEscalatorMode PropertyIdentifier = 462 + FloorText PropertyIdentifier = 464 + GroupId PropertyIdentifier = 465 + GroupMode PropertyIdentifier = 467 + HigherDeck PropertyIdentifier = 468 + InstallationId PropertyIdentifier = 469 + LandingCalls PropertyIdentifier = 470 + LandingCallControl PropertyIdentifier = 471 + LandingDoorStatus PropertyIdentifier = 472 + LowerDeck PropertyIdentifier = 473 + MachineRoomId PropertyIdentifier = 474 + MakingCarCall PropertyIdentifier = 475 + NextStoppingFloor PropertyIdentifier = 476 + OperationDirection PropertyIdentifier = 477 + PassengerAlarm PropertyIdentifier = 478 + PowerMode PropertyIdentifier = 479 + RegisteredCarCall PropertyIdentifier = 480 + ActiveCovMultipleSubscriptions PropertyIdentifier = 481 + PropertyIdentifierProtocolLevel PropertyIdentifier = 482 + ReferencePort PropertyIdentifier = 483 + DeployedProfileLocation PropertyIdentifier = 484 + ProfileLocation PropertyIdentifier = 485 + Tags PropertyIdentifier = 486 + SubordinateNodeTypes PropertyIdentifier = 487 + SubordinateRelationships PropertyIdentifier = 489 + SubordinateTags PropertyIdentifier = 488 + DefaultSubordinateRelationship PropertyIdentifier = 490 + Represents PropertyIdentifier = 491 + DefaultPresentValue PropertyIdentifier = 492 + PresentStage PropertyIdentifier = 493 + Stages PropertyIdentifier = 494 + StageNames PropertyIdentifier = 495 + TargetReferences PropertyIdentifier = 496 + FaultSignals PropertyIdentifier = 463 +) + +type Segmentation int + +const ( + SegmentedBoth Segmentation = iota + SegmentedTransmit + SegmentedReceive + NoSegmentation +) + +type EventType int + +const ( + ChangeOfBitstring EventType = 0 + ChangeOfState EventType = 1 + ChangeOfValue EventType = 2 + CommandFailure EventType = 3 + FloatingLimit EventType = 4 + OutOfRange EventType = 5 + Complex EventType = 6 + ChangeOfLifeSafety EventType = 8 + Extended EventType = 9 + BufferReady EventType = 10 + UnsignedRange EventType = 11 + BACnetEventTypeAccessEvent EventType = 13 + DoubleOutOfRange EventType = 14 + SignedOutOfRange EventType = 15 + UnsignedOutOfRange EventType = 16 + ChangeOfCharacterstring EventType = 17 + ChangeOfStatusFlag EventType = 18 + ChangeOfReliability EventType = 19 + None EventType = 20 + ChangeOfDiscreteValue EventType = 21 + ChangeOfTimer EventType = 22 +) + +type FaultType int + +const ( + BacnetFaultTypeNone FaultType = iota + FaultCHARACTERSTRING + FaultEXTENDED + FaultLIFE_SAFETY + FaultSTATE + FaultStatusFlags + FaultOutOfRange + FaultListed +) + +type NotifyType int + +const ( + ALARM NotifyType = iota + EVENT + ACK_NOTIFICATION +) + +type EventState int + +const ( + Normal EventState = iota + Fault + OffNormal + BACnetEventStateHighLimit + BACnetEventStateLowLimit + LifeSafetyAlarm +) + +type AccessCredentialDisableReason int + +const ( + Disabled AccessCredentialDisableReason = iota + DisabledNeedsProvisioning + DisabledUnassigned + DisabledNotYetActive + DisabledExpired + DisabledLockout + DisabledMaxDays + DisabledMaxUses + DisabledInactivity + DisabledManual +) + +type AccessCredentialDisable int + +const ( + BACnetAccessCredentialDisableNone AccessCredentialDisable = iota + Disable + DisableManual + DisableLockout +) + +type AccessPassbackMode int + +const ( + PassbackOff AccessPassbackMode = iota + HardPassback + SoftPassback +) + +type AccessUserType int + +const ( + Asset AccessUserType = iota + BACnetAccessUserTypeGroup + Person +) + +type AccessZoneOccupancyState int + +const ( + BACnetAccessZoneOccupancyStateNormal AccessZoneOccupancyState = iota + BelowLowerLimit + AtLowerLimit + AtUpperLimit + AboveUpperLimit + BACnetAccessZoneOccupancyStateDisabled + NotSupported +) + +type Action int + +const ( + Direct Action = iota + Reverse +) + +type NetworkNumberQuality int + +const ( + Unknown NetworkNumberQuality = iota + Learned + LearnedConfigured + Configured +) + +type BinaryPV int + +const ( + Inactive BinaryPV = iota + Active +) + +type DoorValue int + +const ( + Lock DoorValue = iota + Unlock + PulseUnlock + ExtendedPulseUnlock +) + +type AuthenticationStatus int + +const ( + NotReady AuthenticationStatus = iota + Ready + BACnetAuthenticationStatusDisabled + WaitingForAuthenticationFactor + WaitingForAccompaniment + WaitingForVerification + BACnetAuthenticationStatusInProgress +) + +type AuthorizationExemption int + +const ( + Passback AuthorizationExemption = iota + OccupancyCheck + BACnetAuthorizationExemptionAccessRights + BACnetAuthorizationExemptionLockout + Deny + Verification + AuthorizationDelay +) + +type AuthorizationMode int + +const ( + Authorize AuthorizationMode = iota + GrantActive + DenyAll + VerificationRequired + AuthorizationDelayed + BACnetAuthorizationModeNone +) + +type BackupState int + +const ( + Idle BackupState = iota + PreparingForBackup + PreparingForRestor + PerformingABACKUP + PerformingARestor +) + +type BinaryLightingPV int + +const ( + Off BinaryLightingPV = iota + On + Warn + WarnOff + WarnRelinquish + Stop +) + +type DeviceStatus int + +const ( + Operational DeviceStatus = iota + OperationalReadOnly + DownloadRequired + DownloadInProgress + NonOperational + BackupInProgress +) + +type DoorAlarmState int + +const ( + BACnetDoorAlarmStateNormal DoorAlarmState = iota + Alarm + DoorOpenTooLong + ForcedOpen + Tamper + DoorFault + LockDown + FreeAccess + EgressOpen +) + +type DoorSecuredStatus int + +const ( + Secured DoorSecuredStatus = iota + UNSecured + BACnetDoorSecuredStatusUnknown +) + +type DoorStatus int + +const ( + CLOSED DoorStatus = iota + OPENED + UNKNOWN + DOOR_FAULT + UNUSED + NONE + CLOSING + OPENING + SAFETY_LOCKED + LIMITED_OPENED +) + +type EngineeringUnits int + +const ( + metersPerSecondPerSecond EngineeringUnits = 166 + SquareMeters EngineeringUnits = 0 + SquareCentimeters EngineeringUnits = 116 + SquareFeet EngineeringUnits = 1 + SquareInches EngineeringUnits = 115 + Currency1 EngineeringUnits = 105 + Currency2 EngineeringUnits = 106 + Currency3 EngineeringUnits = 107 + Currency4 EngineeringUnits = 108 + Currency5 EngineeringUnits = 109 + Currency6 EngineeringUnits = 110 + Currency7 EngineeringUnits = 111 + Currency8 EngineeringUnits = 112 + Currency9 EngineeringUnits = 113 + Currency10 EngineeringUnits = 114 + Milliamperes EngineeringUnits = 2 + Amperes EngineeringUnits = 3 + AmperesPerMeter EngineeringUnits = 167 + AmperesPerSquareMeter EngineeringUnits = 168 + AmpereSquareMeters EngineeringUnits = 169 + Decibels EngineeringUnits = 199 + DecibelsMillivolt EngineeringUnits = 200 + DecibelsVolt EngineeringUnits = 201 + Farads EngineeringUnits = 170 + Henrys EngineeringUnits = 171 + Ohms EngineeringUnits = 4 + OhmMeters EngineeringUnits = 172 + Milliohms EngineeringUnits = 145 + Kilohms EngineeringUnits = 122 + Megohms EngineeringUnits = 123 + Microsiemens EngineeringUnits = 190 + Millisiemens EngineeringUnits = 202 + Siemens EngineeringUnits = 173 + SiemensPerMeter EngineeringUnits = 174 + Teslas EngineeringUnits = 175 + Volts EngineeringUnits = 5 + Millivolts EngineeringUnits = 124 + Kilovolts EngineeringUnits = 6 + Megavolts EngineeringUnits = 7 + VoltAmperes EngineeringUnits = 8 + KilovoltAmperes EngineeringUnits = 9 + MegavoltAmperes EngineeringUnits = 10 + VoltAmperesReactive EngineeringUnits = 11 + KilovoltAmperesReactive EngineeringUnits = 12 + MegavoltAmperesReactive EngineeringUnits = 13 + VoltsPerDegreeKelvin EngineeringUnits = 176 + VoltsPerMeter EngineeringUnits = 177 + DegreesPhase EngineeringUnits = 14 + PowerFactor EngineeringUnits = 15 + Webers EngineeringUnits = 178 + Joules EngineeringUnits = 16 + Kilojoules EngineeringUnits = 17 + KilojoulesPerKilogram EngineeringUnits = 125 + Megajoules EngineeringUnits = 126 + WattHours EngineeringUnits = 18 + KilowattHours EngineeringUnits = 19 + MegawattHours EngineeringUnits = 146 + WattHoursReactive EngineeringUnits = 203 + KilowattHoursReactive EngineeringUnits = 204 + MegawattHoursReactive EngineeringUnits = 205 + Btus EngineeringUnits = 20 + KiloBtus EngineeringUnits = 147 + MegaBtus EngineeringUnits = 148 + Therms EngineeringUnits = 21 + TonHours EngineeringUnits = 22 + JoulesPerKilogramDryAir EngineeringUnits = 23 + KilojoulesPerKilogramDryAir EngineeringUnits = 149 + MegajoulesPerKilogramDryAir EngineeringUnits = 150 + BtusPerPoundDryAir EngineeringUnits = 24 + BtusPerPound EngineeringUnits = 117 + JoulesPerDegreeKelvin EngineeringUnits = 127 + KilojoulesPerDegreeKelvin EngineeringUnits = 151 + MegajoulesPerDegreeKelvin EngineeringUnits = 152 + JoulesPerKilogramDegreeKelvin EngineeringUnits = 128 + Newton EngineeringUnits = 153 + CyclesPerHour EngineeringUnits = 25 + CyclesPerMinute EngineeringUnits = 26 + Hertz EngineeringUnits = 27 + Kilohertz EngineeringUnits = 129 + Megahertz EngineeringUnits = 130 + PerHour EngineeringUnits = 131 + GramsOfWaterPerKilogramDryAir EngineeringUnits = 28 + PercentRelativeHumidity EngineeringUnits = 29 + Micrometers EngineeringUnits = 194 + Millimeters EngineeringUnits = 30 + Centimeters EngineeringUnits = 118 + Kilometers EngineeringUnits = 193 + Meters EngineeringUnits = 31 + Inches EngineeringUnits = 32 + Feet EngineeringUnits = 33 + Candelas EngineeringUnits = 179 + CandelasPerSquareMeter EngineeringUnits = 180 + WattsPerSquareFoot EngineeringUnits = 34 + WattsPerSquareMeter EngineeringUnits = 35 + Lumens EngineeringUnits = 36 + Luxes EngineeringUnits = 37 + FootCandles EngineeringUnits = 38 + Milligrams EngineeringUnits = 196 + Grams EngineeringUnits = 195 + Kilograms EngineeringUnits = 39 + PoundsMass EngineeringUnits = 40 + Tons EngineeringUnits = 41 + GramsPerSecond EngineeringUnits = 154 + GramsPerMinute EngineeringUnits = 155 + KilogramsPerSecond EngineeringUnits = 42 + KilogramsPerMinute EngineeringUnits = 43 + KilogramsPerHour EngineeringUnits = 44 + PoundsMassPerSecond EngineeringUnits = 119 + PoundsMassPerMinute EngineeringUnits = 45 + PoundsMassPerHour EngineeringUnits = 46 + TonsPerHour EngineeringUnits = 156 + Milliwatts EngineeringUnits = 132 + Watts EngineeringUnits = 47 + Kilowatts EngineeringUnits = 48 + Megawatts EngineeringUnits = 49 + BtusPerHour EngineeringUnits = 50 + KiloBtusPerHour EngineeringUnits = 157 + Horsepower EngineeringUnits = 51 + TonsRefrigeration EngineeringUnits = 52 + Pascals EngineeringUnits = 53 + Hectopascals EngineeringUnits = 133 + Kilopascals EngineeringUnits = 54 + Millibars EngineeringUnits = 134 + Bars EngineeringUnits = 55 + PoundsForcePerSquareInch EngineeringUnits = 56 + MillimetersOfWater EngineeringUnits = 206 + CentimetersOfWater EngineeringUnits = 57 + InchesOfWater EngineeringUnits = 58 + MillimetersOfMercury EngineeringUnits = 59 + CentimetersOfMercury EngineeringUnits = 60 + InchesOfMercury EngineeringUnits = 61 + DegreesCelsius EngineeringUnits = 62 + DegreesKelvin EngineeringUnits = 63 + DegreesKelvinPerHour EngineeringUnits = 181 + DegreesKelvinPerMinute EngineeringUnits = 182 + DegreesFahrenheit EngineeringUnits = 64 + DegreeDaysCelsius EngineeringUnits = 65 + DegreeDaysFahrenheit EngineeringUnits = 66 + DeltaDegreesFahrenheit EngineeringUnits = 120 + DeltaDegreesKelvin EngineeringUnits = 121 + Years EngineeringUnits = 67 + Months EngineeringUnits = 68 + Weeks EngineeringUnits = 69 + Days EngineeringUnits = 70 + Hours EngineeringUnits = 71 + Minutes EngineeringUnits = 72 + Seconds EngineeringUnits = 73 + HundredthsSeconds EngineeringUnits = 158 + Milliseconds EngineeringUnits = 159 + NewtonMeters EngineeringUnits = 160 + MillimetersPerSecond EngineeringUnits = 161 + MillimetersPerMinute EngineeringUnits = 162 + MetersPerSecond EngineeringUnits = 74 + MetersPerMinute EngineeringUnits = 163 + MetersPerHour EngineeringUnits = 164 + KilometersPerHour EngineeringUnits = 75 + FeetPerSecond EngineeringUnits = 76 + FeetPerMinute EngineeringUnits = 77 + MilesPerHour EngineeringUnits = 78 + CubicFeet EngineeringUnits = 79 + CubicMeters EngineeringUnits = 80 + ImperialGallons EngineeringUnits = 81 + Milliliters EngineeringUnits = 197 + Liters EngineeringUnits = 82 + UsGallons EngineeringUnits = 83 + CubicFeetPerSecond EngineeringUnits = 142 + CubicFeetPerMinute EngineeringUnits = 84 + MillionCubicFeetPerMinute EngineeringUnits = 254 + CubicFeetPerHour EngineeringUnits = 191 + StandardCubicFeetPerDay EngineeringUnits = 47808 + MillionStandardCubicFeetPerDay EngineeringUnits = 47809 + ThousandCubicFeetPerDay EngineeringUnits = 47810 + ThousandStandardCubicFeetPerDay EngineeringUnits = 47811 + PoundsMassPerDay EngineeringUnits = 47812 + CubicMetersPerSecond EngineeringUnits = 85 + CubicMetersPerMinute EngineeringUnits = 165 + CubicMetersPerHour EngineeringUnits = 135 + ImperialGallonsPerMinute EngineeringUnits = 86 + MillilitersPerSecond EngineeringUnits = 198 + LitersPerSecond EngineeringUnits = 87 + LitersPerMinute EngineeringUnits = 88 + LitersPerHour EngineeringUnits = 136 + UsGallonsPerMinute EngineeringUnits = 89 + UsGallonsPerHour EngineeringUnits = 192 + DegreesAngular EngineeringUnits = 90 + DegreesCelsiusPerHour EngineeringUnits = 91 + DegreesCelsiusPerMinute EngineeringUnits = 92 + DegreesFahrenheitPerHour EngineeringUnits = 93 + DegreesFahrenheitPerMinute EngineeringUnits = 94 + JouleSeconds EngineeringUnits = 183 + KilogramsPerCubicMeter EngineeringUnits = 186 + KwHoursPerSquareMeter EngineeringUnits = 137 + KwHoursPerSquareFoot EngineeringUnits = 138 + MegajoulesPerSquareMeter EngineeringUnits = 139 + MegajoulesPerSquareFoot EngineeringUnits = 140 + NoUnits EngineeringUnits = 95 + NewtonSeconds EngineeringUnits = 187 + NewtonsPerMeter EngineeringUnits = 188 + PartsPerMillion EngineeringUnits = 96 + PartsPerBillion EngineeringUnits = 97 + Percent EngineeringUnits = 98 + PercentObscurationPerFoot EngineeringUnits = 143 + PercentObscurationPerMeter EngineeringUnits = 144 + PercentPerSecond EngineeringUnits = 99 + PerMinute EngineeringUnits = 100 + PerSecond EngineeringUnits = 101 + PsiPerDegreeFahrenheit EngineeringUnits = 102 + Radians EngineeringUnits = 103 + RadiansPerSecond EngineeringUnits = 184 + RevolutionsPerMinute EngineeringUnits = 104 + SquareMetersPerNewton EngineeringUnits = 185 + WattsPerMeterPerDegreeKelvin EngineeringUnits = 189 + WattsPerSquareMeterDegreeKelvin EngineeringUnits = 141 + PerMille EngineeringUnits = 207 + GramsPerGram EngineeringUnits = 208 + KilogramsPerKilogram EngineeringUnits = 209 + GramsPerKilogram EngineeringUnits = 210 + MilligramsPerGram EngineeringUnits = 211 + MilligramsPerKilogram EngineeringUnits = 212 + GramsPerMilliliter EngineeringUnits = 213 + GramsPerLiter EngineeringUnits = 214 + MilligramsPerLiter EngineeringUnits = 215 + MicrogramsPerLiter EngineeringUnits = 216 + GramsPerCubicMeter EngineeringUnits = 217 + MilligramsPerCubicMeter EngineeringUnits = 218 + MicrogramsPerCubicMeter EngineeringUnits = 219 + NanogramsPerCubicMeter EngineeringUnits = 220 + GramsPerCubicCentimeter EngineeringUnits = 221 + Becquerels EngineeringUnits = 222 + Kilobecquerels EngineeringUnits = 223 + Megabecquerels EngineeringUnits = 224 + Gray EngineeringUnits = 225 + Milligray EngineeringUnits = 226 + Microgray EngineeringUnits = 227 + Sieverts EngineeringUnits = 228 + Millisieverts EngineeringUnits = 229 + Microsieverts EngineeringUnits = 230 + MicrosievertsPerHour EngineeringUnits = 231 + Millirems EngineeringUnits = 47814 + MilliremsPerHour EngineeringUnits = 47815 + DecibelsA EngineeringUnits = 232 + NephelometricTurbidityUnit EngineeringUnits = 233 + Ph EngineeringUnits = 234 + GramsPerSquareMeter EngineeringUnits = 235 + MinutesPerDegreeKelvin EngineeringUnits = 236 + MeterSquaredPerMeter EngineeringUnits = 237 + AmpereSeconds EngineeringUnits = 238 + VoltAmpereHours EngineeringUnits = 239 + KilovoltAmpereHours EngineeringUnits = 240 + MegavoltAmpereHours EngineeringUnits = 241 + VoltAmpereHoursReactive EngineeringUnits = 242 + KilovoltAmpereHoursReactive EngineeringUnits = 243 + MegavoltAmpereHoursReactive EngineeringUnits = 244 + VoltSquareHours EngineeringUnits = 245 + AmpereSquareHours EngineeringUnits = 246 + JoulePerHours EngineeringUnits = 247 + CubicFeetPerDay EngineeringUnits = 248 + CubicMetersPerDay EngineeringUnits = 249 + WattHoursPerCubicMeter EngineeringUnits = 250 + JoulesPerCubicMeter EngineeringUnits = 251 + MolePercent EngineeringUnits = 252 + PascalSeconds EngineeringUnits = 253 + MillionStandardCubicFeetPerMinute EngineeringUnits = 254 +) + +type EscalatorMode int + +const ( + BacnetescalatorModeUnknown EscalatorMode = iota + BacnetescalatorModeStop + BACnetEscalatorModeUp + BACnetEscalatorModeDown + BACnetEscalatorModeInspection + BacnetescalatorModeOutOfService +) + +type EscalatorOperationDirection int + +const ( + BacnetEscalatorOperationDirectionUnknown EscalatorOperationDirection = iota + BACnetEscalatorOperationDirectionStopped + UpRatedSpeed + UpReducedSpeed + DownRatedSpeed + DownReducedSpeed +) + +type FileAccessMethod int + +const ( + RecordAccess FileAccessMethod = iota + StreamAccess +) + +type IPMode int + +const ( + BacnetIPModeNormal IPMode = iota + Foreign + Bbmd +) + +type LifeSafetyMode int + +const ( + BacnetLifeSafetyModeOff LifeSafetyMode = iota + Lon + Test + Manned + Unmanned + Armed + Disarmed + Prearmed + Slow + Fast + Disconnected + Enabled + BacnetLifeSafetyModeDisabled + AutomaticReleaseDisabled + Default +) + +type LifeSafetyOperation int + +const ( + BacnetLifeSafetyOperationNone LifeSafetyOperation = iota + Silence + SilenceAudible + SilenceVisual + Reset + ResetAlarm + ResetFault + Unsilence + UnsilenceAudible + UnsilenceVisual +) + +type LifeSafetyState int + +const ( + Quiet LifeSafetyState = iota + PreAlarm + BacnetlifesafetystateAlarm + BacnetlifesafetystateFault + FaultPreAlarm + FaultAlarm + BacnetlifesafetystateNotReady + BacnetlifesafetystateActive + BacnetlifesafetystateTamper + TestAlarm + TestActive + TestFault + TestFaultAlarm + Holdup + Duress + TamperAlarm + Abnormal + BacnetlifesafetystateEmergencyPower + Delayed + Blocked + LocalAlarm + GeneralAlarm + Supervisory + TestSupervisory +) + +type LiftCarDirection int + +const ( + BacnetliftcardirectionUnknown LiftCarDirection = iota + BacnetliftcardirectionNone + Stopped + Up + Down + UpAndDown +) + +type LiftCarDoorCommand int + +const ( + BACnetLiftCarDoorCommandNone LiftCarDoorCommand = iota + Open + Close +) + +type LiftCarDriveStatus int + +const ( + BACnetLiftCarDriveStatusUnknown LiftCarDriveStatus = iota + Stationary + Braking + Accelerate + Decelerate + RatedSpeed + SingleFloorJump + TwoFloorJump + ThreeFloorJump + MultiFloorJump +) + +type LiftCarMode int + +const ( + BACnetLiftCarModeUnknown LiftCarMode = iota + BACnetLiftCarModeNormal + Vip + Homing + Parking + AttendantControl + FirefighterControl + BACnetLiftCarModeEmergencyPower + Inspection + CabinetRecall + EarthquakeOperation + FireOperation + BACnetLiftCarModeOutOfService + OccupantEvacuation +) + +type LiftFault int + +const ( + ControllerFault LiftFault = iota + DriveAndMotorFault + GovernorAndSafetyGearFault + LiftShaftDeviceFault + PowerSupplyFault + SafetyInterlockFault + DoorClosingFault + DoorOpeningFault + CarStoppedOutsideLandingZone + CallButtonStuck + StartFailure + ControllerSupplyFault + SelfTestFailure + RuntimeLimitExceeded + PositionLost + DriveTemperatureExceeded + LoadMeasurementFault +) + +type LiftGroupMode int + +const ( + BACnetLiftGroupModeUnknown LiftGroupMode = iota + BACnetLiftGroupModeNormal + DownPeak + TwoWay + FourWay + EmergencyPower + UpPeak +) + +type LoggingType int + +const ( + Polled LoggingType = iota + Cov + Triggered +) + +type Maintenance int + +const ( + BACnetMaintenanceNone Maintenance = iota + PeriodicTest + NeedServiceOperational + NeedServiceInoperative +) + +type NetworkPortCommand int + +const ( + BACnetNetworkPortCommandIdle NetworkPortCommand = iota + DiscardChanges + RenewFdRegistration + RestartSlaveDiscovery + RenewDhcp + RestartAutonegotiation + Disconnect + RestartPort +) + +type NodeType int + +const ( + BACnetNodeTypeUnknown NodeType = iota + System + Network + BACnetNodeTypeDevice + Organizational + Area + Equipment + Point + Collection + BACnetNodeTypeProperty + Functional + BACnetNodeTypeOther + Subsystem + Building + Floor + Section + Module + Tree + Member + Protocol + Room + Zone +) + +type Relationship int + +const ( + BACnetRelationshipUnknown Relationship = iota + BACnetRelationshipDefault + Contains + ContainedBy + Uses + UsedBy + Commands + CommandedBy + Adjusts + AdjustedBy + Ingress + Egress + SuppliesAir + ReceivesAir + SuppliesHotAir + ReceivesHotAir + SuppliesCoolAir + ReceivesCoolAir + SuppliesPower + ReceivesPower + SuppliesGas + ReceivesGas + SuppliesWater + ReceivesWater + SuppliesHotWater + ReceivesHotWater + SuppliesCoolWater + ReceivesCoolWater + SuppliesSteam + ReceivesSteam +) + +type Reliability int + +const ( + NoFaultDetected Reliability = iota + NoSensor + OverRange + UnderRange + OpenLoop + ShortedLoop + NoOutput + UnreliableOther + ProcessError + MultiStateFault + ConfigurationError + CommunicationFailure Reliability = iota + 1 + MemberFault + MonitoredObjectFault + Tripped + LampFailure + ActivationFailure + RenewDhcpFailure + RenewFdRegistrationFailure + RestartAutoNegotiationFailure + RestartFailure + ProprietaryCommandFailure + FaultsListed + ReferencedObjectFault +) + +type RestartReason int + +const ( + BACnetRestartReasonUnknown RestartReason = iota + ColdStart + WarmStart + DetectedPowerLost + DetectedPowerOff + HardwareWatchdog + SoftwareWatchdog + Suspended +) + +type SecurityLevel int + +const ( + Incapable SecurityLevel = iota + Plain + Signed + Encrypted + SignedEndToEnd + EncryptedEndToEnd +) + +type Polarity int + +const ( + BACnetPolarityNormal Polarity = iota + BACnetPolarityReverse +) + +type ProtocolLevel int + +const ( + Physical ProtocolLevel = iota + BACnetProtocolLevelProtocol + BACnetApplication + NonBACnetApplication +) + +type SilencedState int + +const ( + Unsilenced SilencedState = iota + AudibleSilenced + VisibleSilenced + AllSilenced +) + +type TimerState int + +const ( + BACnetTimerStateIdle TimerState = iota + Running + Expired +) + +type TimerTransition int + +const ( + BACnetTimerTransitionNone TimerTransition = iota + IdleToRunning + RunningToIdle + RunningToRunning + RunningToExpired + ForcedToExpired + ExpiredToIdle + ExpiredToRunning +) + +type VTClass int + +const ( + DefaultTerminal VTClass = iota + ANSI_X3_64 + DEC_VT52 + DEC_VT100 + DEC_VT220 + HP_700_94 + IBM_3130 +) + +type AccessEvent int + +const ( + BACnetAccessEventNone AccessEvent = iota + Granted + Muster + PassbackDetected + BACnetAccessEventDuress + Trace + LockoutMaxAttempts + LockoutOther + LockoutRelinquished + LockedByHigherPriority + BACnetAccessEventOutOfService + OutOfServiceRelinquished + AccompanimentBy + AuthenticationFactorRead + BACnetAccessEventAuthorizationDelayed + BACnetAccessEventVerificationRequired + NoEntryAfterGrant + DeniedDenyAll AccessEvent = iota + 111 + DeniedUnknownCredential + DeniedAuthenticationUnavailable + DeniedAuthenticationFactorTimeout + DeniedIncorrectAuthenticationFactor + DeniedZoneNoAccessRights + DeniedPointNoAccessRights + DeniedNoAccessRights + DeniedOutOfTimeRange + DeniedThreatLevel + DeniedPassback + DeniedUnexpectedLocationUsage + DeniedMaxAttempts + DeniedLowerOccupancyLimit + DeniedUpperOccupancyLimit + DeniedAuthenticationFactorLost + DeniedAuthenticationFactorStolen + DeniedAuthenticationFactorDamaged + DeniedAuthenticationFactorDestroyed + DeniedAuthenticationFactorDisabled + DeniedAuthenticationFactorError + DeniedCredentialUnassigned + DeniedCredentialNotProvisioned + DeniedCredentialNotYetActive + DeniedCredentialExpired + DeniedCredentialManualDisable + DeniedCredentialLockout + DeniedCredentialMaxDays + DeniedCredentialMaxUses + DeniedCredentialInactivity + DeniedCredentialDisabled + DeniedNoAccompaniment + DeniedIncorrectAccompaniment + DeniedLockout + DeniedVerificationFailed + DeniedVerificationTimeout + DeniedOther +) + +type LightingInProgress int + +const ( + BACnetLightingInProgressIdle LightingInProgress = iota + FadeActive + RampActive + NotControlled + BACnetLightingInProgressOther +) + +type LightingOperation int + +const ( + BACnetLightingOperationNone LightingOperation = iota + FadeTo + RampTo + StepUp + StepDown + StepOn + StepOff + BACnetLightingOperationWarn + BACnetLightingOperationWarnOff + BACnetLightingOperationWarnRelinquish + BACnetLightingOperationStop +) + +type LightingTransition int + +const ( + BACnetLightingTransitionNone LightingTransition = iota + Fade + Ramp +) + +type LockStatus int + +const ( + Locked LockStatus = iota + Unlocked + LockFault + Unused + BACnetLockStatusUnknown +) + +type EscalatorFault int + +const ( + BACnetescalatorfaultControllerFault EscalatorFault = iota + BACnetescalatorfaultDriveAndMotorFault + MechanicalComponentFault + OverspeedFault + BACnetescalatorfaultPowerSupplyFault + SafetyDeviceFault + BACnetescalatorfaultControllerSupplyFault + BACnetescalatorfaultDriveTemperatureExceeded + CombPlateFault +) + +type ProgramError int + +const ( + BACnetProgramErrorNormal = iota + LoadFailed + Internal + BACnetProgramErrorProgram + BACnetProgramErrorOther +) + +type ProgramRequest int + +const ( + BACnetProgramRequestReady = iota + Load + Run + Halt + Restart + Unload +) + +type ProgramState int + +const ( + BACnetProgramStateIdle ProgramState = iota + Loading + BACnetProgramStateRunning + Waiting + Halted + Unloading +) + +type ShedState int + +const ( + BACnetShedStateInactive ShedState = iota + RequestPending + Compliant + NonCompliant +) + +type WriteStatus int + +const ( + BACnetWriteStatusIdle WriteStatus = iota + BACnetWriteStatusInProgress + Successful + Failed +) + +type VendorSpecificValue int + +func DecodeEnumerated(buffer []byte, offset int, lenValue uint32, objType *ObjectType, propID *PropertyIdentifier) (length int, val interface{}, err error) { + leng, value, err := DecodeUnsigned(buffer, offset, int(lenValue)) + if err != nil { + return length, val, err + } + if propID != nil { + switch *propID { + case SegmentationSupported: + val = Segmentation(value) + case PropertyList: + val = PropertyIdentifier(value) + case PropertyIdentifierEventType: + val = EventType(value) + case PropertyIdentifierNotifyType: + val = NotifyType(value) + case PropertyIdentifierFaultType: + val = FaultType(value) + case PropertyIdentifierEventState: + val = EventState(value) + case ObjectTypePI: + val = ObjectType(value) + case ReasonForDisable: + val = AccessCredentialDisableReason(value) + case CredentialDisable: + val = AccessCredentialDisable(value) + case PassbackMode: + val = AccessPassbackMode(value) + case UserType: + val = AccessUserType(value) + case PropertyIdentifierNetworkNumberQuality: + val = NetworkNumberQuality(value) + case OccupancyState: + val = AccessZoneOccupancyState(value) + case PropertyIdentifierAction: + if *objType == Loop { + val = Action(value) + } + case PresentValue, AlarmValue, FeedbackValue, RelinquishDefault: + switch *objType { + case BinaryInput, BinaryOutput, BinaryValue: + val = BinaryPV(value) + case AccessDoor: + val = DoorValue(value) + case LifeSafetyPoint, LifeSafetyZone: + val = LifeSafetyState(value) + case LightingOutput: + val = BinaryLightingPV(value) + case LoadControl: + val = ShedState(value) + } + case PropertyIdentifierAuthenticationStatus: + val = AuthenticationStatus(value) + case AuthorizationExemptions: + val = AuthorizationExemption(value) + case PropertyIdentifierAuthorizationMode: + val = AuthorizationMode(value) + case BackupAndRestoreState: + val = BackupState(value) + case SystemStatus: + val = DeviceStatus(value) + case SecuredStatus: + val = DoorSecuredStatus(value) + case PropertyIdentifierDoorStatus, CarDoorStatus: + val = DoorStatus(value) + case Units, CarLoadUnits: + val = EngineeringUnits(value) + case PropertyIdentifierEscalatorMode: + val = EscalatorMode(value) + case OperationDirection: + val = EscalatorOperationDirection(value) + case PropertyIdentifierFileAccessMethod: + val = FileAccessMethod(value) + case OperationExpected: + val = LifeSafetyOperation(value) + case CarDoorCommand: + val = LiftCarDoorCommand(value) + case CarDriveStatus: + val = LiftCarDriveStatus(value) + case CarMode: + val = LiftCarMode(value) + case GroupMode: + val = LiftGroupMode(value) + case PropertyIdentifierLoggingType: + val = LoggingType(value) + case PropertyIdentifierReliability: + val = Reliability(value) + case LastRestartReason: + val = RestartReason(value) + case PropertyIdentifierNetworkType: + val = NetworkType(value) + case BaseDeviceSecurityPolicy: + val = SecurityLevel(value) + case CarMovingDirection, CarAssignedDirection: + val = LiftCarDirection(value) + case BacnetIpMode, BacnetIpv6Mode: + val = IPMode(value) + case MaintenanceRequired: + val = Maintenance(value) + case PropertyIdentifierPolarity: + val = Polarity(value) + case PropertyIdentifierProtocolLevel: + val = ProtocolLevel(value) + case Silenced: + val = SilencedState(value) + case PropertyIdentifierAccessEvent, AccessAlarmEvents, AccessTransactionEvents, FailedAttemptEvents: + if *objType == AccessPoint { + val = AccessEvent(value) + } + case LastAccessEvent: + if *objType == AccessCredential { + val = AccessEvent(value) + } + case CredentialStatus: + if *objType == AccessCredential { + val = BinaryPV(value) + } + case PropertyIdentifierLockStatus: + if *objType == AccessDoor { + val = LockStatus(value) + } + case PropertyIdentifierDoorAlarmState, MaskedAlarmValues, AlarmValues, FaultValues: + switch *objType { + case AccessDoor: + val = DoorAlarmState(value) + case LifeSafetyPoint, LifeSafetyZone: + val = LifeSafetyState(value) + case Timer: + val = TimerState(value) + } + case Mode, AcceptedModes: + if *objType == LifeSafetyPoint || *objType == LifeSafetyZone { + val = LifeSafetyMode(value) + } + case TrackingValue, LifeSafetyAlarmValues: + if *objType == LifeSafetyPoint || *objType == LifeSafetyZone { + val = LifeSafetyState(value) + } + case FaultSignals: + switch *objType { + case Escalator: + val = EscalatorFault(value) + case Lift: + val = LiftFault(value) + } + case InProgress: + if *objType == LightingOutput { + val = LightingInProgress(value) + } + case Transition: + if *objType == LightingOutput { + val = LightingTransition(value) + } + case Command: + if *objType == NetworkPort { + val = NetworkPortCommand(value) + } + case PropertyIdentifierNodeType, SubordinateNodeTypes: + if *objType == StructuredView { + val = NodeType(value) + } + case SubordinateRelationships, DefaultSubordinateRelationship: + if *objType == StructuredView { + val = Relationship(value) + } + case ReasonForHalt: + if *objType == Program { + val = ProgramError(value) + } + case ProgramChange: + if *objType == Program { + val = ProgramRequest(value) + } + case PropertyIdentifierProgramState: + if *objType == Program { + val = ProgramState(value) + } + case PropertyIdentifierTimerState: + if *objType == Timer { + val = TimerState(value) + } + case LastStateChange: + if *objType == Timer { + val = TimerTransition(value) + } + case VtClassesSupported: + val = VTClass(value) + case PropertyIdentifierWriteStatus: + if *objType == Channel { + val = WriteStatus(value) + } + default: + val = VendorSpecificValue(value) + } + + return leng, val, nil + } + return leng, value, nil +} + +func EncodeContextEnumerated(tagNumber BACnetApplicationTag, value uint32) []byte { + length := 0 + if value < 0x100 { + length = 1 + } else if value < 0x10000 { + length = 2 + } else if value < 0x1000000 { + length = 3 + } else { + length = 4 + } + + return append(EncodeTag(tagNumber, true, length), EncodeUnsigned(value)...) +} diff --git a/pkg/encoding/tags.go b/pkg/encoding/tags.go new file mode 100644 index 0000000..a6f5036 --- /dev/null +++ b/pkg/encoding/tags.go @@ -0,0 +1,156 @@ +package encoding + +type BACnetApplicationTag int + +const ( + Null BACnetApplicationTag = iota + Boolean + UnsignedInt + SignedInt + Real + Double + OctetString + CharacterString + BitString + Enumerated + Date + Time + BACnetObjectIdentifier + Reserve1 + Reserve2 + Reserve3 +) + +const ( + extendedTagMask = 0xF0 + extendedValueMask = 0x07 + extendedTagValue = 5 + openingTagValue = 6 + closingTagValue = 7 + contextSpecificBit = 0x08 +) + +// isExtendedTagNumber checks if a byte represents an extended tag. +func isExtendedTagNumber(b byte) bool { + return (b & extendedTagMask) == extendedTagMask +} + +// isExtendedValue checks if a byte represents an extended value. +func isExtendedValue(b byte) bool { + return (b & extendedValueMask) == extendedTagValue +} + +// isOpeningTag checks if a byte represents an opening tag. +func isOpeningTag(b byte) bool { + return (b & extendedValueMask) == openingTagValue +} + +// isClosingTag checks if a byte represents a closing tag. +func isClosingTag(b byte) bool { + return (b & extendedValueMask) == closingTagValue +} + +// IsContextSpecific checks if the context-specific bit is set in a byte. +func IsContextSpecific(b byte) bool { + return (b & contextSpecificBit) == contextSpecificBit +} + +func IsContextTag(buf []byte, offset int, tagNum byte) bool { + _, myTagNum := decodeTagNumber(buf, offset) + return IsContextSpecific(buf[offset]) && myTagNum == tagNum + +} + +func IsContextTagWithLength(buf []byte, offset int, tagNum byte) (int, bool) { + tagLen, myTagNum := decodeTagNumber(buf, offset) + return tagLen, IsContextSpecific(buf[offset]) && myTagNum == tagNum +} + +func DecodeTagNumberAndValue(buf []byte, offset int) (len int, tagNum byte, val uint32, err error) { + len, tagNum = decodeTagNumber(buf, offset) + + switch { + case isExtendedValue(buf[offset]): + switch buf[offset+len] { + case 255: + len += 1 + len1, val1, err := DecodeUnsigned(buf, offset+len, 4) + if err != nil { + return len, tagNum, val, err + } + len += len1 + val = val1 + case 254: + len += 1 + len1, val1, err := DecodeUnsigned(buf, offset+len, 2) + if err != nil { + return len, tagNum, val, err + } + len += len1 + val = val1 + default: + val = uint32(buf[offset+len]) + len += 1 + } + case isOpeningTag(buf[offset]), isClosingTag(buf[offset]): + val = 0 + default: + val = uint32(buf[offset] & 0x07) + } + return len, tagNum, val, nil + +} + +func decodeTagNumber(buf []byte, offset int) (len int, tagNum byte) { + len = 1 + + if isExtendedTagNumber(buf[offset]) { + return len + 1, buf[offset+len] + } + return len, buf[offset] >> 4 +} + +func EncodeTag(tagNum BACnetApplicationTag, ctxSpecific bool, lenVal int) []byte { + tag := []byte{} + value := byte(0) + + if ctxSpecific { + value = 0x8 + } + + if tagNum <= 14 { + value += byte(tagNum) << 4 + tag = append(tag, value) + } else { + value += 0xF0 + tag = append(tag, value) + tag = append(tag, byte(tagNum)) + } + + if lenVal <= 4 { + tag[0] += byte(lenVal) + return tag + } + tag[0] += 5 + switch { + case lenVal <= 253: + tag = append(tag, byte(lenVal)) + return tag + case lenVal <= 65535: + tag = append(tag, 254) + return append(tag, EncodeUnsigned(uint32(lenVal))...) + default: + tag = append(tag, 255) + return append(tag, EncodeUnsigned(uint32(lenVal))...) + } +} + +func IsOpeningTagNumber(buf []byte, offset int, tagNum byte) bool { + _, myTagNum := decodeTagNumber(buf, offset) + return isOpeningTag(buf[offset]) && myTagNum == tagNum +} + +func IsClosingTagNumber(buf []byte, offset int, tagNum byte) bool { + _, myTagNum := decodeTagNumber(buf, offset) + return isClosingTag(buf[offset]) && myTagNum == tagNum +} diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go new file mode 100644 index 0000000..57ba5ac --- /dev/null +++ b/pkg/transport/transport.go @@ -0,0 +1,7 @@ +package transport + +type Transport int + +const ( + IP = iota +) diff --git a/pkg/transport/udp/broadcast.go b/pkg/transport/udp/broadcast.go new file mode 100644 index 0000000..90c1941 --- /dev/null +++ b/pkg/transport/udp/broadcast.go @@ -0,0 +1,41 @@ +package udp + +import ( + "net" + "strconv" + + "github.com/absmach/bacnet/pkg/bacnet" + "github.com/absmach/bacnet/pkg/encoding" +) + +const globalBroadcast = "255.255.255.255" + +// GetBroadcastAddress returns the broadcast address given the local address and port. +func GetBroadcastAddress(localEndpoint string, port int) (bacnet.Address, error) { + broadcast := globalBroadcast + interfaces, err := net.Interfaces() + if err != nil { + return bacnet.Address{}, err + } + + for _, iface := range interfaces { + addrs, err := iface.Addrs() + if err != nil { + return bacnet.Address{}, err + } + + for _, addr := range addrs { + if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil { + ipAddress := ipnet.IP.String() + if ipAddress == localEndpoint { + if iface.Flags&net.FlagBroadcast != 0 { + broadcast = ipnet.IP.Mask(ipnet.IP.DefaultMask()).String() + } + } + } + } + } + netType := encoding.IPV4 + return bacnet.NewAddress(0xFFFF, nil, broadcast+":"+strconv.Itoa(port), &netType), nil + +}