| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package bacnetip |
| |
| import ( |
| "encoding/binary" |
| "fmt" |
| readWriteModel "github.com/apache/plc4x/plc4go/protocols/bacnetip/readwrite/model" |
| "github.com/apache/plc4x/plc4go/spi" |
| "github.com/pkg/errors" |
| "github.com/rs/zerolog/log" |
| "net" |
| "reflect" |
| "regexp" |
| "strings" |
| ) |
| |
| type AddressType int |
| |
| const ( |
| NULL_ADDRESS AddressType = iota |
| LOCAL_BROADCAST_ADDRESS |
| LOCAL_STATION_ADDRESS |
| REMOTE_BROADCAST_ADDRESS |
| REMOTE_STATION_ADDRESS |
| GLOBAL_BROADCAST_ADDRESS |
| ) |
| |
| func (a AddressType) String() string { |
| switch a { |
| case NULL_ADDRESS: |
| return "NULL_ADDRESS" |
| case LOCAL_BROADCAST_ADDRESS: |
| return "LOCAL_BROADCAST_ADDRESS" |
| case LOCAL_STATION_ADDRESS: |
| return "LOCAL_STATION_ADDRESS" |
| case REMOTE_BROADCAST_ADDRESS: |
| return "REMOTE_BROADCAST_ADDRESS" |
| case REMOTE_STATION_ADDRESS: |
| return "REMOTE_STATION_ADDRESS" |
| case GLOBAL_BROADCAST_ADDRESS: |
| return "GLOBAL_BROADCAST_ADDRESS" |
| default: |
| return "Unknown" |
| } |
| } |
| |
| type AddressTuple[L any, R any] struct { |
| Left L |
| Right R |
| } |
| |
| func (a *AddressTuple[L, R]) String() string { |
| return fmt.Sprintf("(%v, %v)", a.Left, a.Right) |
| } |
| |
| var _field_address = regexp.MustCompile(`((?:\d+)|(?:0x(?:[0-9A-Fa-f][0-9A-Fa-f])+))`) |
| var _ip_address_port = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)(?::(\d+))?`) |
| var _ip_address_mask_port = regexp.MustCompile(`(\d+\.\d+\.\d+\.\d+)(?:/(\d+))?(?::(\d+))?`) |
| var _net_ip_address_port = regexp.MustCompile(`(\d+):` + _ip_address_port.String()) |
| var _at_route = regexp.MustCompile(`(?:[@](?:` + _field_address.String() + `|` + _ip_address_port.String() + `))?`) |
| |
| var field_address_re = regexp.MustCompile(`^` + _field_address.String() + `$`) |
| var ip_address_port_re = regexp.MustCompile(`^` + _ip_address_port.String() + `$`) |
| var ip_address_mask_port_re = regexp.MustCompile(`^` + _ip_address_mask_port.String() + `$`) |
| var net_ip_address_port_re = regexp.MustCompile(`^` + _net_ip_address_port.String() + `$`) |
| var net_ip_address_mask_port_re = regexp.MustCompile(`^` + _net_ip_address_port.String() + `$`) |
| |
| var ethernet_re = regexp.MustCompile(`^([0-9A-Fa-f][0-9A-Fa-f][:]){5}([0-9A-Fa-f][0-9A-Fa-f])$`) |
| var interface_re = regexp.MustCompile(`^(?:([\w]+))(?::(\d+))?$`) |
| |
| var net_broadcast_route_re = regexp.MustCompile(`^([0-9])+:[*]` + _at_route.String() + `$`) |
| var net_station_route_re = regexp.MustCompile(`^([0-9])+:` + _field_address.String() + _at_route.String() + `$`) |
| var net_ip_address_route_re = regexp.MustCompile(`^([0-9])+:` + _ip_address_port.String() + _at_route.String() + `$`) |
| |
| var combined_pattern = regexp.MustCompile(`^(?:(?:([0-9]+)|([*])):)?(?:([*])|` + _field_address.String() + `|` + _ip_address_mask_port.String() + `)` + _at_route.String() + `$`) |
| |
| type Address struct { |
| AddrType AddressType |
| AddrNet *uint16 |
| AddrAddress []byte |
| AddrLen *uint32 |
| AddrRoute *Address |
| |
| AddrIP *uint32 |
| AddrMask *uint32 |
| AddrHost *uint32 |
| AddrSubnet *uint32 |
| AddrPort *uint16 |
| AddrTuple *AddressTuple[string, uint16] |
| AddrBroadcastTuple *AddressTuple[string, uint16] |
| } |
| |
| func NewAddress(args ...any) (*Address, error) { |
| log.Debug().Interface("args", args).Msg("NewAddress") |
| a := &Address{} |
| a.AddrNet = nil |
| a.AddrAddress = nil |
| a.AddrLen = nil |
| a.AddrRoute = nil |
| |
| switch len(args) { |
| case 1: |
| if err := a.decodeAddress(args[0]); err != nil { |
| return nil, errors.Wrap(err, "decodeAddress") |
| } |
| case 2: |
| if err := a.decodeAddress(args[1]); err != nil { |
| return nil, errors.Wrap(err, "decodeAddress") |
| } |
| switch a.AddrType { |
| case LOCAL_STATION_ADDRESS: |
| a.AddrType = REMOTE_STATION_ADDRESS |
| var addrNet = (args[0]).(uint16) |
| a.AddrNet = &addrNet |
| case LOCAL_BROADCAST_ADDRESS: |
| a.AddrType = REMOTE_BROADCAST_ADDRESS |
| var addrNet = (args[0]).(uint16) |
| a.AddrNet = &addrNet |
| default: |
| return nil, errors.New("unrecognized address ctor form") |
| } |
| } |
| return a, nil |
| } |
| |
| // decodeAddress Initialize the address from a string. Lots of different forms are supported |
| func (a *Address) decodeAddress(addr any) error { |
| log.Debug().Msgf("decodeAddress %v (%T)", addr, addr) |
| |
| // start out assuming this is a local station and didn't get routed |
| a.AddrType = LOCAL_STATION_ADDRESS |
| a.AddrNet = nil |
| a.AddrAddress = nil |
| a.AddrLen = nil |
| a.AddrRoute = nil |
| |
| switch { |
| case addr == "*": |
| log.Debug().Msg("localBroadcast") |
| a.AddrType = LOCAL_BROADCAST_ADDRESS |
| case addr == "*:*": |
| log.Debug().Msg("globalBroadcast") |
| a.AddrType = GLOBAL_BROADCAST_ADDRESS |
| default: |
| switch addr := addr.(type) { |
| case net.Addr: |
| // TODO: hacked in udp support |
| udpAddr := addr.(*net.UDPAddr) |
| a.AddrAddress = udpAddr.IP.To4() |
| if a.AddrAddress == nil { |
| a.AddrAddress = udpAddr.IP.To16() |
| } |
| length := uint32(len(a.AddrAddress)) |
| a.AddrLen = &length |
| port := uint16(udpAddr.Port) |
| a.AddrPort = &port |
| addr.String() |
| case int: |
| log.Debug().Msg("int") |
| if addr < 0 || addr > 255 { |
| return errors.New("address out of range") |
| } |
| a.AddrAddress = []byte{byte(addr)} |
| length := uint32(1) |
| a.AddrLen = &length |
| case []byte: |
| log.Debug().Msg("byte array") |
| a.AddrAddress = addr |
| length := uint32(len(addr)) |
| a.AddrLen = &length |
| |
| if *a.AddrLen == 6 { |
| ip := ipv4ToUint32(addr[:4]) |
| a.AddrIP = &ip |
| mask := uint32((1 << 32) - 1) |
| a.AddrMask = &mask |
| host := *a.AddrIP & ^(*a.AddrMask) |
| a.AddrHost = &host |
| subnet := *a.AddrIP & *a.AddrMask |
| a.AddrSubnet = &subnet |
| port := portToUint16(addr[4:]) |
| a.AddrPort = &port |
| |
| a.AddrTuple = &AddressTuple[string, uint16]{uint32ToIpv4(*a.AddrIP).String(), *a.AddrPort} |
| a.AddrBroadcastTuple = &AddressTuple[string, uint16]{"255.255.255.255", *a.AddrPort} |
| } |
| case string: |
| log.Debug().Msg("str") |
| |
| m := combined_pattern.MatchString(addr) |
| if m { |
| log.Debug().Msg("combined pattern") |
| groups := combined_pattern.FindStringSubmatch(addr) |
| _net := groups[0] |
| global_broadcast := groups[1] |
| local_broadcast := groups[2] |
| local_addr := groups[3] |
| local_ip_addr := groups[4] |
| local_ip_net := groups[5] |
| local_ip_port := groups[6] |
| route_addr := groups[7] |
| route_ip_addr := groups[8] |
| route_ip_port := groups[9] |
| |
| a := func(...any) { |
| |
| } |
| a(_net, global_broadcast, local_broadcast, local_addr, local_ip_addr, local_ip_net, local_ip_port, route_addr, route_ip_addr, route_ip_port) |
| } |
| panic("parsing not yet ported") |
| case AddressTuple[string, uint16]: |
| uaddr, port := addr.Left, addr.Right |
| a.AddrPort = &port |
| |
| var addrstr []byte |
| if uaddr == "" { |
| // when ('', n) is passed it is the local host address, but that could be more than one on a multi homed machine, |
| // the empty string # means "any". |
| addrstr = make([]byte, 4) |
| } else { |
| addrstr = net.ParseIP(uaddr) |
| } |
| a.AddrTuple = &AddressTuple[string, uint16]{uaddr, *a.AddrPort} |
| log.Debug().Msgf("addrstr: %s", addrstr) |
| |
| ip := ipv4ToUint32(addrstr) |
| a.AddrIP = &ip |
| mask := uint32(0xFFFFFFFF) |
| a.AddrMask = &mask |
| host := uint32(0) |
| a.AddrHost = &host |
| subnet := uint32(0) |
| a.AddrSubnet = &subnet |
| a.AddrBroadcastTuple = a.AddrTuple |
| |
| a.AddrAddress = append(addrstr, uint16ToPort(*a.AddrPort)...) |
| length := uint32(6) |
| a.AddrLen = &length |
| case AddressTuple[int, uint16]: |
| uaddr, port := addr.Left, addr.Right |
| a.AddrPort = &port |
| |
| addrstr := uint32ToIpv4(uint32(uaddr)) |
| a.AddrTuple = &AddressTuple[string, uint16]{addrstr.String(), *a.AddrPort} |
| log.Debug().Msgf("addrstr: %s", addrstr) |
| |
| ip := ipv4ToUint32(addrstr) |
| a.AddrIP = &ip |
| mask := uint32(0xFFFFFFFF) |
| a.AddrMask = &mask |
| host := uint32(0) |
| a.AddrHost = &host |
| subnet := uint32(0) |
| a.AddrSubnet = &subnet |
| a.AddrBroadcastTuple = a.AddrTuple |
| |
| a.AddrAddress = append(addrstr, uint16ToPort(*a.AddrPort)...) |
| length := uint32(6) |
| a.AddrLen = &length |
| default: |
| return errors.Errorf("integer, string or tuple required (Actual %T)", addr) |
| } |
| } |
| return nil |
| } |
| |
| func (a *Address) Equals(other any) bool { |
| if a == nil && other == nil { |
| return true |
| } else if a == nil && other != nil { |
| return false |
| } |
| switch other := other.(type) { |
| case *Address: |
| if a == other { |
| return true |
| } |
| // TODO: don't use reflect here |
| return reflect.DeepEqual(a, other) |
| case Address: |
| // TODO: don't use reflect here |
| return reflect.DeepEqual(*a, other) |
| default: |
| return false |
| } |
| } |
| |
| func (a *Address) String() string { |
| if a == nil { |
| return "<nil>" |
| } |
| var sb strings.Builder |
| sb.WriteString(a.AddrType.String()) |
| if a.AddrNet != nil { |
| _, _ = fmt.Fprintf(&sb, ", net: %d", *a.AddrNet) |
| } |
| if len(a.AddrAddress) > 0 { |
| _, _ = fmt.Fprintf(&sb, ", address: %d", a.AddrAddress) |
| } |
| if a.AddrLen != nil { |
| _, _ = fmt.Fprintf(&sb, " with len %d", *a.AddrLen) |
| } |
| if a.AddrRoute != nil { |
| _, _ = fmt.Fprintf(&sb, ", route: %s", a.AddrRoute) |
| } |
| if a.AddrIP != nil { |
| _, _ = fmt.Fprintf(&sb, ", ip: %d", *a.AddrIP) |
| } |
| if a.AddrMask != nil { |
| _, _ = fmt.Fprintf(&sb, ", mask: %d", *a.AddrMask) |
| } |
| if a.AddrHost != nil { |
| _, _ = fmt.Fprintf(&sb, ", host: %d", *a.AddrHost) |
| } |
| if a.AddrSubnet != nil { |
| _, _ = fmt.Fprintf(&sb, ", subnet: %d", *a.AddrSubnet) |
| } |
| if a.AddrPort != nil { |
| _, _ = fmt.Fprintf(&sb, ", port: %d", *a.AddrPort) |
| } |
| if a.AddrTuple != nil { |
| _, _ = fmt.Fprintf(&sb, ", tuple: %s", a.AddrTuple) |
| } |
| if a.AddrBroadcastTuple != nil { |
| _, _ = fmt.Fprintf(&sb, ", broadcast tuple: %s", a.AddrBroadcastTuple) |
| } |
| return sb.String() |
| } |
| |
| func portToUint16(port []byte) uint16 { |
| switch len(port) { |
| case 2: |
| default: |
| panic("port must be 2 bytes") |
| } |
| return binary.BigEndian.Uint16(port) |
| } |
| |
| func uint16ToPort(number uint16) []byte { |
| port := make([]byte, 2) |
| binary.BigEndian.PutUint16(port, number) |
| return port |
| } |
| |
| func ipv4ToUint32(ip net.IP) uint32 { |
| switch len(ip) { |
| case 4: |
| default: |
| panic("ip must be either 4 bytes") |
| } |
| return binary.BigEndian.Uint32(ip) |
| } |
| |
| func uint32ToIpv4(number uint32) net.IP { |
| ipv4 := make(net.IP, 4) |
| binary.BigEndian.PutUint32(ipv4, number) |
| return ipv4 |
| } |
| |
| func NewLocalStation(addr any, route *Address) (*Address, error) { |
| l := &Address{} |
| l.AddrType = LOCAL_STATION_ADDRESS |
| l.AddrRoute = route |
| |
| switch addr := addr.(type) { |
| case int: |
| if addr < 0 || addr > 255 { |
| return nil, errors.New("address out of range") |
| } |
| l.AddrAddress = []byte{byte(addr)} |
| length := uint32(1) |
| l.AddrLen = &length |
| case []byte: |
| log.Debug().Msg("bytearray") |
| l.AddrAddress = addr |
| length := uint32(len(addr)) |
| l.AddrLen = &length |
| default: |
| return nil, errors.New("integer or byte array required") |
| } |
| return l, nil |
| } |
| |
| func NewRemoteStation(net *uint16, addr any, route *Address) (*Address, error) { |
| l := &Address{} |
| l.AddrType = REMOTE_STATION_ADDRESS |
| l.AddrNet = net |
| l.AddrRoute = route |
| |
| switch addr := addr.(type) { |
| case int: |
| if addr < 0 || addr > 255 { |
| return nil, errors.New("address out of range") |
| } |
| l.AddrAddress = []byte{byte(addr)} |
| length := uint32(1) |
| l.AddrLen = &length |
| case []byte: |
| log.Debug().Msg("bytearray") |
| l.AddrAddress = addr |
| length := uint32(len(addr)) |
| l.AddrLen = &length |
| default: |
| return nil, errors.New("integer or byte array required") |
| } |
| return l, nil |
| } |
| |
| func NewLocalBroadcast(route *Address) *Address { |
| l := &Address{} |
| l.AddrType = LOCAL_BROADCAST_ADDRESS |
| l.AddrRoute = route |
| return l |
| } |
| |
| func NewRemoteBroadcast(net *uint16, route *Address) *Address { |
| r := &Address{} |
| r.AddrType = REMOTE_BROADCAST_ADDRESS |
| r.AddrNet = net |
| r.AddrRoute = route |
| return r |
| } |
| |
| func NewGlobalBroadcast(route *Address) *Address { |
| g := &Address{} |
| g.AddrType = GLOBAL_BROADCAST_ADDRESS |
| g.AddrRoute = route |
| return g |
| } |
| |
| type PCI struct { |
| *_PCI |
| expectingReply bool |
| networkPriority readWriteModel.NPDUNetworkPriority |
| } |
| |
| func (p *PCI) String() string { |
| return fmt.Sprintf("PCI{%s, expectingReply: %t, networkPriority: %s}", p._PCI, p.expectingReply, p.networkPriority) |
| } |
| |
| func NewPCI(msg spi.Message, pduSource *Address, pduDestination *Address, expectingReply bool, networkPriority readWriteModel.NPDUNetworkPriority) *PCI { |
| return &PCI{ |
| _New_PCI(msg, pduSource, pduDestination), |
| expectingReply, |
| networkPriority, |
| } |
| } |
| |
| type _PDU interface { |
| GetMessage() spi.Message |
| GetPDUSource() *Address |
| GetPDUDestination() *Address |
| SetPDUDestination(*Address) |
| GetExpectingReply() bool |
| GetNetworkPriority() readWriteModel.NPDUNetworkPriority |
| } |
| |
| type PDU struct { |
| *PCI |
| } |
| |
| func NewPDU(msg spi.Message, pduOptions ...PDUOption) *PDU { |
| p := &PDU{ |
| NewPCI(msg, nil, nil, false, readWriteModel.NPDUNetworkPriority_NORMAL_MESSAGE), |
| } |
| for _, option := range pduOptions { |
| option(p) |
| } |
| return p |
| } |
| |
| func NewPDUFromPDU(pdu _PDU, pduOptions ...PDUOption) *PDU { |
| msg := pdu.(*PDU).pduUserData |
| p := &PDU{ |
| NewPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()), |
| } |
| for _, option := range pduOptions { |
| option(p) |
| } |
| return p |
| } |
| |
| func NewPDUFromPDUWithNewMessage(pdu _PDU, msg spi.Message, pduOptions ...PDUOption) *PDU { |
| p := &PDU{ |
| NewPCI(msg, pdu.GetPDUSource(), pdu.GetPDUDestination(), pdu.GetExpectingReply(), pdu.GetNetworkPriority()), |
| } |
| for _, option := range pduOptions { |
| option(p) |
| } |
| return p |
| } |
| |
| func NewPDUWithAllOptions(msg spi.Message, pduSource *Address, pduDestination *Address, expectingReply bool, networkPriority readWriteModel.NPDUNetworkPriority) *PDU { |
| return &PDU{ |
| NewPCI(msg, pduSource, pduDestination, expectingReply, networkPriority), |
| } |
| } |
| |
| type PDUOption func(pdu *PDU) |
| |
| func WithPDUSource(pduSource *Address) PDUOption { |
| return func(pdu *PDU) { |
| pdu.pduSource = pduSource |
| } |
| } |
| |
| func WithPDUDestination(pduDestination *Address) PDUOption { |
| return func(pdu *PDU) { |
| pdu.pduDestination = pduDestination |
| } |
| } |
| |
| func WithPDUExpectingReply(expectingReply bool) PDUOption { |
| return func(pdu *PDU) { |
| pdu.expectingReply = expectingReply |
| } |
| } |
| |
| func WithPDUNetworkPriority(networkPriority readWriteModel.NPDUNetworkPriority) PDUOption { |
| return func(pdu *PDU) { |
| pdu.networkPriority = networkPriority |
| } |
| } |
| |
| func (p *PDU) GetMessage() spi.Message { |
| return p.pduUserData |
| } |
| |
| func (p *PDU) GetPDUSource() *Address { |
| return p.pduSource |
| } |
| |
| func (p *PDU) GetPDUDestination() *Address { |
| return p.pduDestination |
| } |
| |
| func (p *PDU) SetPDUDestination(destination *Address) { |
| p.pduDestination = destination |
| } |
| |
| func (p *PDU) GetExpectingReply() bool { |
| return p.expectingReply |
| } |
| |
| func (p *PDU) GetNetworkPriority() readWriteModel.NPDUNetworkPriority { |
| return p.networkPriority |
| } |
| |
| func (p *PDU) String() string { |
| return fmt.Sprintf("PDU{\n%s}", p._PCI) |
| } |