From e3ef1784138d9afa63e16f0ace0adb5fb994d12b Mon Sep 17 00:00:00 2001 From: monoxane Date: Sat, 4 May 2024 23:08:19 +1000 Subject: [PATCH] feat: complete refactor to split router matrix and protocol --- model.go | 71 --------- nk.go | 240 ------------------------------ pkg/levels/levels.go | 7 + matrix.go => pkg/matrix/matrix.go | 48 +++++- pkg/models/models.go | 13 ++ pkg/router/labels.go | 59 ++++++++ pkg/router/router.go | 114 ++++++++++++++ utils.go => pkg/tbus/crc/crc.go | 4 +- pkg/tbus/model.go | 25 ++++ routing.go => pkg/tbus/routing.go | 39 +---- pkg/tbus/tbus.go | 177 ++++++++++++++++++++++ 11 files changed, 452 insertions(+), 345 deletions(-) delete mode 100644 model.go delete mode 100644 nk.go create mode 100644 pkg/levels/levels.go rename matrix.go => pkg/matrix/matrix.go (63%) create mode 100644 pkg/models/models.go create mode 100644 pkg/router/labels.go create mode 100644 pkg/router/router.go rename utils.go => pkg/tbus/crc/crc.go (87%) create mode 100644 pkg/tbus/model.go rename routing.go => pkg/tbus/routing.go (53%) create mode 100644 pkg/tbus/tbus.go diff --git a/model.go b/model.go deleted file mode 100644 index ff11713..0000000 --- a/model.go +++ /dev/null @@ -1,71 +0,0 @@ -package nk - -import ( - "net" - "sync" -) - -// Aliases - -type Model = string -type TBusAddress = uint8 -type Level = uint32 - -// Structs - -type Router struct { - IP string - Address TBusAddress - Destinations uint16 - Sources uint16 - Level Level - Matrix Matrix - Conn net.Conn - onUpdate func(*Update) -} - -type Destination struct { - Label string `json:"label"` - Id uint16 `json:"id"` - Source *Source `json:"source"` -} - -type Source struct { - Label string `json:"label"` - Id uint16 `json:"id"` -} - -type nkRoutePacketPayload struct { - NK2Header uint32 - RTRAddress TBusAddress - UNKNB uint16 - Destination uint16 - Source uint16 - LevelMask Level - UNKNC uint8 -} - -type nkRoutePacket struct { - HeaderA uint32 - HeaderB uint16 - Payload nkRoutePacketPayload - CRC uint16 -} - -type CrosspointRequest struct { - Source uint16 - Destination uint16 - Level Level - Address TBusAddress -} - -type Matrix struct { - destinations map[uint16]*Destination - sources map[uint16]*Source - mux sync.Mutex -} - -type Update struct { - Type string - Data interface{} -} diff --git a/nk.go b/nk.go deleted file mode 100644 index 71bfa73..0000000 --- a/nk.go +++ /dev/null @@ -1,240 +0,0 @@ -package nk - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - "log" - "net" - "strings" - "time" - - "github.com/pkg/errors" -) - -const ( - LVL_MDVID uint32 = 1 - - MODEL_NK_3G72 Model = "NK-3G72" - MODEL_NK_3G64 Model = "NK-3G64" - MODEL_NK_3G34 Model = "NK-3G34" - MODEL_NK_3G16 Model = "NK-3G16" - MODEL_NK_3G16_RCP Model = "NK-3G16-RCP" - MODEL_NK_3G164 Model = "NK-3G164" - MODEL_NK_3G164_RCP Model = "NK-3G164-RCP" -) - -var ( - NK2_KEEPALIVE = []byte("HI") - NK2_CONNECT_REQ = []byte{0x50, 0x48, 0x4f, 0x45, 0x4e, 0x49, 0x58, 0x2d, 0x44, 0x42, 0x20, 0x4e, 0x0a} - NK2_CONNECT_RESP = []byte{0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x0a} - NK2_HEADER = []byte{0x4e, 0x4b, 0x32} - NK_STATUS_RESP = []byte{0x05, 0x0B} - NK_MULTI_STATUS_REQ = []byte{0x50, 0x41, 0x53, 0x32, 0x00, 0x11, 0x4e, 0x4b, 0x32, 0x00, 0xfe, 0x02, 0x08, 0x00, 0x00, 0x00, 0x47, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x08} - NK_MULTI_STATUS_RESP = []byte{0x03, 0xe1} -) - -func New(IP string, RTRAddress uint8, model Model) *Router { - rtr := &Router{ - IP: IP, - Address: RTRAddress, - } - - switch model { - case MODEL_NK_3G72: - rtr.Destinations = 72 - rtr.Sources = 72 - rtr.Level = LVL_MDVID - case MODEL_NK_3G64: - rtr.Destinations = 64 - rtr.Sources = 64 - rtr.Level = LVL_MDVID - case MODEL_NK_3G34: - rtr.Destinations = 34 - rtr.Sources = 34 - rtr.Level = LVL_MDVID - case MODEL_NK_3G16, MODEL_NK_3G16_RCP: - rtr.Destinations = 16 - rtr.Sources = 16 - rtr.Level = LVL_MDVID - case MODEL_NK_3G164, MODEL_NK_3G164_RCP: - rtr.Destinations = 4 - rtr.Sources = 16 - rtr.Level = LVL_MDVID - } - - rtr.Matrix.destinations = make(map[uint16]*Destination) - rtr.Matrix.sources = make(map[uint16]*Source) - - for i := 0; i < int(rtr.Sources)+1; i++ { - rtr.Matrix.sources[uint16(i)] = &Source{ - Id: uint16(i), - Label: fmt.Sprintf("IN %d", i), - } - } - - rtr.Matrix.sources[0].SetLabel("DISCONNECTED") - - for i := 0; i < int(rtr.Destinations)+1; i++ { - rtr.Matrix.destinations[uint16(i)] = &Destination{ - Id: uint16(i), - Label: fmt.Sprintf("OUT %d", i), - } - } - - return rtr -} - -func (rtr *Router) LoadLabels(labels string) { - lines := strings.Split(labels, "\n") - for i, line := range lines { - columns := strings.Split(line, ",") - if len(columns) < 4 { - continue - } - - log.Printf("%+v", columns) - if _, ok := rtr.Matrix.destinations[uint16(i+1)]; ok { - rtr.Matrix.destinations[uint16(i+1)].SetLabel(columns[1]) - } - if _, ok := rtr.Matrix.sources[uint16(i+1)]; ok { - rtr.Matrix.sources[uint16(i+1)].SetLabel(columns[3]) - } - } -} - -func (rtr *Router) Connect() error { - conn, err := net.Dial("tcp", rtr.IP+":5000") - if err != nil { - log.Fatalln(err) - } - rtr.Conn = conn - defer rtr.Conn.Close() - - if _, err = rtr.Conn.Write(NK2_CONNECT_REQ); err != nil { - log.Printf("failed to send the client request: %v\n", err) - } - - go func() { - for range time.Tick(10 * time.Second) { - rtr.Conn.Write([]byte("HI")) - } - }() - - for { - buf := make([]byte, 2048) - len, err := rtr.Conn.Read(buf) - switch err { - case nil: - rtr.processNKMessage(buf, len) - case io.EOF: - return errors.New("remote connection closed") - default: - return errors.Wrap(err, "unhandled server error") - } - } -} - -func (rtr *Router) processNKMessage(buffer []byte, length int) { - msg := buffer[:length] - log.Printf("Processing message of len %d: %x", length, msg) - - if length == len(NK2_CONNECT_RESP) && bytes.Equal(msg, NK2_CONNECT_RESP) { - log.Printf("Sucessfully Connected") - rtr.Conn.Write(NK_MULTI_STATUS_REQ) - } - - if length > 3 && bytes.Equal(msg[:3], NK2_HEADER) { - log.Printf("NK Command or Response, CMD: %x", msg[5:7]) - if bytes.Equal(msg[5:7], NK_STATUS_RESP) { - rtr.parseSingleUpdateMessage(msg) - } - - if bytes.Equal(msg[5:7], NK_MULTI_STATUS_RESP) { - rtr.parseMultiUpdateMessage(msg) - } - } -} - -func (rtr *Router) parseSingleUpdateMessage(msg []byte) { - dst := binary.BigEndian.Uint16(msg[8:10]) + 1 - src := binary.BigEndian.Uint16(msg[10:12]) + 1 - lvl := binary.BigEndian.Uint32(msg[12:16]) - - rtr.updateMatrix(lvl, dst, src) -} - -func (rtr *Router) parseMultiUpdateMessage(msg []byte) { - table := msg[15 : len(msg)-2] - - currentCrosspointByte := 1 - for { - if currentCrosspointByte >= len(table) { - break - } - - dst := uint16(currentCrosspointByte/3) + 1 - src := binary.BigEndian.Uint16(table[currentCrosspointByte:currentCrosspointByte+2]) + 1 - lvl := uint32(1) - - rtr.updateMatrix(lvl, dst, src) - - currentCrosspointByte++ - currentCrosspointByte++ - currentCrosspointByte++ - } - -} - -func (rtr *Router) updateMatrix(lvl Level, dst uint16, src uint16) { - if lvl == rtr.Level { - // log.Printf("Updating Crosspoint State: DST %2d SRC %2d", dst, src) - rtr.Matrix.SetCrosspoint(dst, src) - - if rtr.onUpdate != nil { - go rtr.onUpdate(&Update{ - Type: "destination", - Data: rtr.Matrix.GetDestination(dst), - }) - } - } -} - -func (rtr *Router) UpdateSourceLabel(src int, label string) { - if src <= int(rtr.Sources) { - rtr.Matrix.GetSource(uint16(src)).SetLabel(label) - go rtr.onUpdate(&Update{ - Type: "source", - Data: rtr.Matrix.GetSource(uint16(src)), - }) - - for _, dst := range rtr.Matrix.destinations { - if dst.Source != nil && dst.Source.GetID() == uint16(src) { - if rtr.onUpdate != nil { - go rtr.onUpdate(&Update{ - Type: "destination", - Data: dst, - }) - } - } - } - } -} - -func (rtr *Router) UpdateDestinationLabel(dst int, label string) { - if dst <= int(rtr.Destinations) { - rtr.Matrix.destinations[uint16(dst)].SetLabel(label) - - if rtr.onUpdate != nil { - go rtr.onUpdate(&Update{ - Type: "destination", - Data: rtr.Matrix.GetDestination(uint16(dst)), - }) - } - } -} - -func (rtr *Router) SetOnUpdate(notify func(*Update)) { - rtr.onUpdate = notify -} diff --git a/pkg/levels/levels.go b/pkg/levels/levels.go new file mode 100644 index 0000000..90fb9a2 --- /dev/null +++ b/pkg/levels/levels.go @@ -0,0 +1,7 @@ +package levels + +type Level = int + +const ( + MD_Vid uint32 = 1 +) diff --git a/matrix.go b/pkg/matrix/matrix.go similarity index 63% rename from matrix.go rename to pkg/matrix/matrix.go index c4f4915..011205b 100644 --- a/matrix.go +++ b/pkg/matrix/matrix.go @@ -1,10 +1,50 @@ -package nk +package matrix import ( "encoding/json" + "fmt" "sort" + "sync" ) +type Destination struct { + Label string `json:"label"` + Id uint16 `json:"id"` + Source *Source `json:"source"` +} + +type Source struct { + Label string `json:"label"` + Id uint16 `json:"id"` +} + +type Matrix struct { + destinations map[uint16]*Destination + sources map[uint16]*Source + mux sync.Mutex +} + +func (matrix *Matrix) Init(numDestinations, numSources uint16) { + matrix.destinations = make(map[uint16]*Destination) + matrix.sources = make(map[uint16]*Source) + + for i := 0; i < int(numSources)+1; i++ { + matrix.sources[uint16(i)] = &Source{ + Id: uint16(i), + Label: fmt.Sprintf("IN %d", i), + } + } + + matrix.sources[0].SetLabel("DISCONNECTED") + + for i := 0; i < int(numDestinations)+1; i++ { + matrix.destinations[uint16(i)] = &Destination{ + Id: uint16(i), + Label: fmt.Sprintf("OUT %d", i), + } + } +} + func (matrix *Matrix) MarshalJSON() ([]byte, error) { type res struct { Destinations []*Destination `json:"destinations,omitempty"` @@ -89,3 +129,9 @@ func (src *Source) GetLabel() string { func (src *Source) SetLabel(lbl string) { src.Label = lbl } + +func (matrix *Matrix) ForEachDestination(callback func(uint16, *Destination)) { + for i, d := range matrix.destinations { + callback(i, d) + } +} diff --git a/pkg/models/models.go b/pkg/models/models.go new file mode 100644 index 0000000..3608576 --- /dev/null +++ b/pkg/models/models.go @@ -0,0 +1,13 @@ +package models + +type Model = string + +const ( + NK_3G72 Model = "NK-3G72" + NK_3G64 Model = "NK-3G64" + NK_3G34 Model = "NK-3G34" + NK_3G16 Model = "NK-3G16" + NK_3G16_RCP Model = "NK-3G16-RCP" + NK_3G164 Model = "NK-3G164" + NK_3G164_RCP Model = "NK-3G164-RCP" +) diff --git a/pkg/router/labels.go b/pkg/router/labels.go new file mode 100644 index 0000000..cc7eea6 --- /dev/null +++ b/pkg/router/labels.go @@ -0,0 +1,59 @@ +package router + +import ( + "strings" + + "github.com/monoxane/nk/pkg/matrix" +) + +func (rtr *Router) LoadLabels(labels string) { + lines := strings.Split(labels, "\n") + for i, line := range lines { + columns := strings.Split(line, ",") + if len(columns) < 4 { + continue + } + + if i < int(rtr.Destinations) { + rtr.Matrix.GetDestination(uint16(i + 1)).SetLabel(columns[1]) + } + + if i < int(rtr.Sources) { + rtr.Matrix.GetSource(uint16(i + 1)).SetLabel(columns[3]) + } + } +} + +func (rtr *Router) UpdateSourceLabel(src int, label string) { + if src <= int(rtr.Sources) { + rtr.Matrix.GetSource(uint16(src)).SetLabel(label) + go rtr.onRouteUpdate(&RouteUpdate{ + Type: "source", + Source: rtr.Matrix.GetSource(uint16(src)), + }) + + rtr.Matrix.ForEachDestination(func(i uint16, dst *matrix.Destination) { + if dst.Source != nil && dst.Source.GetID() == uint16(src) { + if rtr.onRouteUpdate != nil { + go rtr.onRouteUpdate(&RouteUpdate{ + Type: "destination", + Destination: dst, + }) + } + } + }) + } +} + +func (rtr *Router) UpdateDestinationLabel(dst int, label string) { + if dst <= int(rtr.Destinations) { + rtr.Matrix.GetDestination(uint16(dst)).SetLabel(label) + + if rtr.onRouteUpdate != nil { + go rtr.onRouteUpdate(&RouteUpdate{ + Type: "destination", + Destination: rtr.Matrix.GetDestination(uint16(dst)), + }) + } + } +} diff --git a/pkg/router/router.go b/pkg/router/router.go new file mode 100644 index 0000000..94c3d81 --- /dev/null +++ b/pkg/router/router.go @@ -0,0 +1,114 @@ +package router + +import ( + "fmt" + "net" + + "github.com/monoxane/nk/pkg/levels" + "github.com/monoxane/nk/pkg/matrix" + "github.com/monoxane/nk/pkg/models" + "github.com/monoxane/nk/pkg/tbus" +) + +type RouteUpdate struct { + Type string + Destination *matrix.Destination + Source *matrix.Source +} + +// A Router is a fully featured NK Routing matrix with state and label management +type Router struct { + // The NK-IPS or NK-NET interface used to access this Router + gateway *tbus.TBusGateway + + // The metadata of this router + Address tbus.TBusAddress + Destinations uint16 + Sources uint16 + Level tbus.Level + Matrix matrix.Matrix + + // client facing update messages + onRouteUpdate func(*RouteUpdate) + onStatusUpdate func(tbus.StatusUpdate) +} + +func New(ip net.IP, routerAddress tbus.TBusAddress, model models.Model) *Router { + rtr := &Router{} + + switch model { + case models.NK_3G72: + rtr.Destinations = 72 + rtr.Sources = 72 + rtr.Level = levels.MD_Vid + case models.NK_3G64: + rtr.Destinations = 64 + rtr.Sources = 64 + rtr.Level = levels.MD_Vid + case models.NK_3G34: + rtr.Destinations = 34 + rtr.Sources = 34 + rtr.Level = levels.MD_Vid + case models.NK_3G16, models.NK_3G16_RCP: + rtr.Destinations = 16 + rtr.Sources = 16 + rtr.Level = levels.MD_Vid + case models.NK_3G164, models.NK_3G164_RCP: + rtr.Destinations = 4 + rtr.Sources = 16 + rtr.Level = levels.MD_Vid + } + + rtr.Matrix.Init(rtr.Destinations, rtr.Sources) + + gw := tbus.NewGateway(ip, rtr.handleRouteUpdate, rtr.handleStatusUpdate) + + rtr.gateway = gw + + return rtr +} + +func (rtr *Router) Connect() error { + return rtr.gateway.Connect() +} + +func (rtr *Router) Disconnect() { + rtr.gateway.Disconnect() +} + +func (rtr *Router) SetOnUpdate(notify func(*RouteUpdate)) { + rtr.onRouteUpdate = notify +} + +func (rtr *Router) Route(dst uint16, src uint16) error { + if dst <= 0 || dst > rtr.Destinations { + return fmt.Errorf("requested destination is outside the range available on this router model") + } + + if src <= 0 || src > rtr.Sources { + return fmt.Errorf("requested source is outside the range available on this router model") + } + + return rtr.gateway.Route(rtr.Address, rtr.Level, dst, src) +} + +func (rtr *Router) handleRouteUpdate(update tbus.RouteUpdate) { + rtr.updateMatrix(uint16(update.Destination), uint16(update.Source)) +} + +func (rtr *Router) handleStatusUpdate(update tbus.StatusUpdate) { + if rtr.onStatusUpdate != nil { + rtr.onStatusUpdate(update) + } +} + +func (rtr *Router) updateMatrix(dst uint16, src uint16) { + rtr.Matrix.SetCrosspoint(dst, src) + + if rtr.onRouteUpdate != nil { + go rtr.onRouteUpdate(&RouteUpdate{ + Type: "destination", + Destination: rtr.Matrix.GetDestination(dst), + }) + } +} diff --git a/utils.go b/pkg/tbus/crc/crc.go similarity index 87% rename from utils.go rename to pkg/tbus/crc/crc.go index 9ff1854..d2fdea0 100644 --- a/utils.go +++ b/pkg/tbus/crc/crc.go @@ -1,7 +1,7 @@ -package nk +package crc // Simple 2 byte CRC for XPT payload -func crc16(buffer []byte) uint16 { +func CRC16(buffer []byte) uint16 { var crc = 0xFFFF var odd = 0x0000 diff --git a/pkg/tbus/model.go b/pkg/tbus/model.go new file mode 100644 index 0000000..6fa2d89 --- /dev/null +++ b/pkg/tbus/model.go @@ -0,0 +1,25 @@ +package tbus + +type TBusAddress = uint8 +type Level = uint32 +type Source = uint16 +type Destination = uint16 +type TBusCRC = uint16 + +// TBUS Protocol Packets +type nkRoutePacketPayload struct { + NK2Header uint32 + RTRAddress TBusAddress + UNKNB uint16 + Destination Destination + Source Source + LevelMask Level + UNKNC uint8 +} + +type nkRoutePacket struct { + HeaderA uint32 + HeaderB uint16 + Payload nkRoutePacketPayload + CRC TBusCRC +} diff --git a/routing.go b/pkg/tbus/routing.go similarity index 53% rename from routing.go rename to pkg/tbus/routing.go index 0718e35..2ab7407 100644 --- a/routing.go +++ b/pkg/tbus/routing.go @@ -1,41 +1,18 @@ -package nk +package tbus import ( "bytes" "encoding/binary" - "fmt" "log" - "github.com/pkg/errors" + "github.com/monoxane/nk/pkg/tbus/crc" ) -func (rtr *Router) Route(dst uint16, src uint16) error { - if dst <= 0 || dst > rtr.Destinations { - return fmt.Errorf("requested destination is outside the range available on this router model") - } - - if src <= 0 || src > rtr.Sources { - return fmt.Errorf("requested source is outside the range available on this router model") - } - - xptreq := CrosspointRequest{ - Source: src, - Destination: dst, - Level: rtr.Level, - Address: rtr.Address, - } - - packet, err := xptreq.Packet() - if err != nil { - return errors.Wrap(err, "unable to generate crosspoint route request") - } - - _, err = rtr.Conn.Write(packet) - if err != nil { - return errors.Wrap(err, "unable to send route request to router") - } - - return nil +type CrosspointRequest struct { + Source uint16 + Destination uint16 + Level Level + Address TBusAddress } // GenerateXPTRequest Just returns payload to send to router to close xpt @@ -63,7 +40,7 @@ func (xpt *CrosspointRequest) Packet() ([]byte, error) { HeaderA: 0x50415332, HeaderB: 0x0012, Payload: payload, - CRC: crc16(payloadBuffer.Bytes()), + CRC: crc.CRC16(payloadBuffer.Bytes()), } packetBuffer := new(bytes.Buffer) diff --git a/pkg/tbus/tbus.go b/pkg/tbus/tbus.go new file mode 100644 index 0000000..b207202 --- /dev/null +++ b/pkg/tbus/tbus.go @@ -0,0 +1,177 @@ +package tbus + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "log" + "net" + "time" + + "github.com/pkg/errors" +) + +var ( + NK2_KEEPALIVE = []byte("HI") + NK2_CONNECT_REQ = []byte{0x50, 0x48, 0x4f, 0x45, 0x4e, 0x49, 0x58, 0x2d, 0x44, 0x42, 0x20, 0x4e, 0x0a} + NK2_CONNECT_RESP = []byte{0x57, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x0a} + NK2_HEADER = []byte{0x4e, 0x4b, 0x32} + NK_STATUS_RESP = []byte{0x05, 0x0B} + NK_MULTI_STATUS_REQ = []byte{0x50, 0x41, 0x53, 0x32, 0x00, 0x11, 0x4e, 0x4b, 0x32, 0x00, 0xfe, 0x02, 0x08, 0x00, 0x00, 0x00, 0x47, 0xff, 0xff, 0xff, 0xff, 0xc7, 0x08} + NK_MULTI_STATUS_RESP = []byte{0x03, 0xe1} +) + +type RouteUpdate struct { + Destination int `json:"destination"` + Source int `json:"source"` + Level int `json:"level"` +} + +type StatusUpdate struct { + Connected bool `json:"connected"` + Reason string `json:"reason"` +} + +// A IPS is an implementation of the protocol interface with the NK Routing system via an NK-NET or NK-IPS +type TBusGateway struct { + IP net.IP + conn net.Conn + onRouteUpdate func(RouteUpdate) + onConnect func(StatusUpdate) + onDisconnect func(StatusUpdate) +} + +func NewGateway(ip net.IP, onRouteUpdate func(RouteUpdate), onStatusUpdate func(StatusUpdate)) *TBusGateway { + return &TBusGateway{ + IP: ip, + onRouteUpdate: onRouteUpdate, + onConnect: onStatusUpdate, + onDisconnect: onStatusUpdate, + } +} + +func (tbus *TBusGateway) Connect() error { + conn, err := net.Dial("tcp", tbus.IP.String()+":5000") + if err != nil { + log.Fatalln(err) + } + tbus.conn = conn + defer tbus.conn.Close() + + if _, err = tbus.conn.Write(NK2_CONNECT_REQ); err != nil { + log.Printf("failed to send the client request: %v\n", err) + } + + tbus.onConnect(StatusUpdate{ + Connected: true, + Reason: "Connected to TBus Gateway", + }) + + go func() { + for range time.Tick(10 * time.Second) { + if _, err := tbus.conn.Write([]byte("HI")); err != nil { + tbus.onDisconnect(StatusUpdate{ + Connected: false, + Reason: fmt.Sprintf("Keepalive failed: %s", err), + }) + } + } + }() + + for { + buf := make([]byte, 2048) + len, err := tbus.conn.Read(buf) + switch err { + case nil: + tbus.processNKMessage(buf, len) + case io.EOF: + return errors.New("remote connection closed") + default: + return errors.Wrap(err, "unhandled server error") + } + } +} + +func (tbus *TBusGateway) Disconnect() { + tbus.conn.Close() +} + +func (tbus *TBusGateway) processNKMessage(buffer []byte, length int) { + msg := buffer[:length] + log.Printf("Processing message of len %d: %x", length, msg) + + if length == len(NK2_CONNECT_RESP) && bytes.Equal(msg, NK2_CONNECT_RESP) { + log.Printf("Successfully Connected") + tbus.conn.Write(NK_MULTI_STATUS_REQ) + } + + if length > 3 && bytes.Equal(msg[:3], NK2_HEADER) { + log.Printf("NK Command or Response, CMD: %x", msg[5:7]) + if bytes.Equal(msg[5:7], NK_STATUS_RESP) { + tbus.parseSingleUpdateMessage(msg) + } + + if bytes.Equal(msg[5:7], NK_MULTI_STATUS_RESP) { + tbus.parseMultiUpdateMessage(msg) + } + } +} + +func (tbus *TBusGateway) parseSingleUpdateMessage(msg []byte) { + dst := binary.BigEndian.Uint16(msg[8:10]) + 1 + src := binary.BigEndian.Uint16(msg[10:12]) + 1 + lvl := binary.BigEndian.Uint32(msg[12:16]) + + tbus.onRouteUpdate(RouteUpdate{ + Destination: int(dst), + Source: int(src), + Level: int(lvl), + }) +} + +func (tbus *TBusGateway) parseMultiUpdateMessage(msg []byte) { + table := msg[15 : len(msg)-2] + + currentCrosspointByte := 1 + for { + if currentCrosspointByte >= len(table) { + break + } + + dst := uint16(currentCrosspointByte/3) + 1 + src := binary.BigEndian.Uint16(table[currentCrosspointByte:currentCrosspointByte+2]) + 1 + lvl := uint32(1) + + tbus.onRouteUpdate(RouteUpdate{ + Destination: int(dst), + Source: int(src), + Level: int(lvl), + }) + + currentCrosspointByte++ + currentCrosspointByte++ + currentCrosspointByte++ + } +} + +func (tbus *TBusGateway) Route(address TBusAddress, level Level, destination uint16, source uint16) error { + xptreq := CrosspointRequest{ + Source: source, + Destination: destination, + Level: level, + Address: address, + } + + packet, err := xptreq.Packet() + if err != nil { + return errors.Wrap(err, "unable to generate crosspoint route request") + } + + _, err = tbus.conn.Write(packet) + if err != nil { + return errors.Wrap(err, "unable to send route request to router") + } + + return nil +}