From 470d54383ac30bad1a0023383e31d86d2506ec17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Mon, 16 Feb 2026 15:37:44 +0100 Subject: [PATCH 1/2] feat(backend): remove blcu --- backend/cmd/config.toml | 6 - backend/cmd/dev-config.toml | 6 +- backend/cmd/main.go | 3 +- backend/cmd/setup_transport.go | 40 --- backend/cmd/setup_vehicle.go | 42 --- backend/internal/config/config_types.go | 7 - backend/internal/vehicle/models/order_data.go | 2 +- backend/pkg/boards/blcu.go | 275 ----------------- backend/pkg/boards/blcu_integration_test.go | 284 ------------------ backend/pkg/boards/blcu_simple_test.go | 106 ------- backend/pkg/boards/events.go | 28 -- backend/pkg/broker/topics/blcu/blcu_test.go | 204 ------------- backend/pkg/broker/topics/blcu/download.go | 100 ------ backend/pkg/broker/topics/blcu/register.go | 22 -- backend/pkg/broker/topics/blcu/upload.go | 124 -------- backend/pkg/transport/packet/blcu/decoder.go | 20 -- backend/pkg/transport/packet/blcu/packet.go | 22 -- backend/pkg/transport/presentation/decoder.go | 2 - .../transport/presentation/decoder_test.go | 21 +- backend/pkg/transport/transport.go | 35 +-- backend/pkg/vehicle/constructor.go | 5 - backend/pkg/vehicle/notification.go | 10 +- backend/pkg/vehicle/vehicle.go | 43 --- 23 files changed, 14 insertions(+), 1393 deletions(-) delete mode 100644 backend/pkg/boards/blcu.go delete mode 100644 backend/pkg/boards/blcu_integration_test.go delete mode 100644 backend/pkg/boards/blcu_simple_test.go delete mode 100644 backend/pkg/broker/topics/blcu/blcu_test.go delete mode 100644 backend/pkg/broker/topics/blcu/download.go delete mode 100644 backend/pkg/broker/topics/blcu/register.go delete mode 100644 backend/pkg/broker/topics/blcu/upload.go delete mode 100644 backend/pkg/transport/packet/blcu/decoder.go delete mode 100644 backend/pkg/transport/packet/blcu/packet.go diff --git a/backend/cmd/config.toml b/backend/cmd/config.toml index f75149e79..90f742c3b 100644 --- a/backend/cmd/config.toml +++ b/backend/cmd/config.toml @@ -33,12 +33,6 @@ max_retries = 0 # Maximum retries before cycling (0 = infinite retr connection_timeout_ms = 1000 # Connection timeout in milliseconds keep_alive_ms = 1000 # Keep-alive interval in milliseconds -# BLCU (Boot Loader Control Unit) Configuration -[blcu] -ip = "127.0.0.1" # TFTP server IP address -download_order_id = 0 # Packet ID for download orders (0 = use default) -upload_order_id = 0 # Packet ID for upload orders (0 = use default) - # TFTP Configuration [tftp] block_size = 131072 # TFTP block size in bytes (128kB) diff --git a/backend/cmd/dev-config.toml b/backend/cmd/dev-config.toml index 876049230..0ea168ec3 100644 --- a/backend/cmd/dev-config.toml +++ b/backend/cmd/dev-config.toml @@ -66,11 +66,7 @@ timeout_ms = 5000 # Timeout for TFTP operations in milliseconds backoff_factor = 2 # Backoff factor for retries enable_progress = true # Enable progress updates during transfers -# BLCU Configuration -[blcu] -ip = "10.10.10.5" # BLCU IP address -download_order_id = 0 # Order ID for download operations (0 = use default) -upload_order_id = 0 # Order ID for upload operations (0 = use default) + # Logging Configuration [logging] diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 637bfd8e7..addc5c879 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -23,7 +23,6 @@ const ( TcpServer = "TCP_SERVER" UDP = "UDP" SNTP = "SNTP" - BlcuAck = "blcu_ack" AddStateOrder = "add_state_order" RemoveStateOrder = "remove_state_order" ) @@ -62,7 +61,7 @@ func main() { } // <--- vehicle orders ---> - vehicleOrders, err := vehicle_models.NewVehicleOrders(podData.Boards, adj.Info.Addresses[BLCU]) + vehicleOrders, err := vehicle_models.NewVehicleOrders(podData.Boards) if err != nil { trace.Fatal().Err(err).Msg("creating vehicleOrders") } diff --git a/backend/cmd/setup_transport.go b/backend/cmd/setup_transport.go index 796ba29af..c86f9270e 100644 --- a/backend/cmd/setup_transport.go +++ b/backend/cmd/setup_transport.go @@ -13,11 +13,9 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/utils" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/network/tcp" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/network/udp" - blcu_packet "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/blcu" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/order" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" @@ -45,11 +43,6 @@ func configureTransport( transp.SetTargetIp(adj.Info.Addresses[board.Name], abstraction.TransportTarget(board.Name)) } - // If BLCU is configured set BLCU packet ID mappings - if common.Contains(config.Vehicle.Boards, "BLCU") { - configureBLCUTransport(adj, transp, config) - } - // Start handling TCP CLIENT connections configureTCPClientTransport(adj, podData, transp, config) @@ -61,36 +54,6 @@ func configureTransport( } -// configureBLCUTransport sets the packet IDs and target IP for the BLCU board. -// It prefers values from config, falls back to ADJ and finally to a loopback default. -func configureBLCUTransport(adj adj_module.ADJ, - transp *transport.Transport, - config config.Config) { - // Use configurable packet IDs or defaults - downloadOrderID := config.Blcu.DownloadOrderId - uploadOrderID := config.Blcu.UploadOrderId - if downloadOrderID == 0 { - downloadOrderID = boards.DefaultBlcuDownloadOrderId - } - if uploadOrderID == 0 { - uploadOrderID = boards.DefaultBlcuUploadOrderId - } - - transp.SetIdTarget(abstraction.PacketId(downloadOrderID), abstraction.TransportTarget("BLCU")) - transp.SetIdTarget(abstraction.PacketId(uploadOrderID), abstraction.TransportTarget("BLCU")) - - // Use BLCU address from config, ADJ, or default - blcuIP := config.Blcu.IP - if blcuIP == "" { - if adjBlcuIP, exists := adj.Info.Addresses[BLCU]; exists { - blcuIP = adjBlcuIP - } else { - blcuIP = "127.0.0.1" - } - } - transp.SetTargetIp(blcuIP, abstraction.TransportTarget("BLCU")) -} - func configureTCPClientTransport( adj adj_module.ADJ, podData pod_data.PodData, @@ -247,9 +210,6 @@ func getTransportDecEnc(info adj_module.Info, podData pod_data.PodData) (*presen encoder.SetPacketEncoder(id, dataEncoder) } - // Register BLCU ack decoder - decoder.SetPacketDecoder(abstraction.PacketId(info.MessageIds[BlcuAck]), blcu_packet.NewDecoder()) - // TODO Solve this foking mess, I have tried... stateOrdersDecoder := order.NewDecoder(binary.LittleEndian) stateOrdersDecoder.SetActionId(abstraction.PacketId(info.MessageIds[AddStateOrder]), stateOrdersDecoder.DecodeAdd) diff --git a/backend/cmd/setup_vehicle.go b/backend/cmd/setup_vehicle.go index 21423de33..b8a7b62e0 100644 --- a/backend/cmd/setup_vehicle.go +++ b/backend/cmd/setup_vehicle.go @@ -18,9 +18,7 @@ import ( "github.com/HyperloopUPV-H8/h9-backend/internal/pod_data" "github.com/HyperloopUPV-H8/h9-backend/internal/update_factory" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" - blcu_topics "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" connection_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/connection" data_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/data" logger_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/logger" @@ -75,7 +73,6 @@ func configureBroker(subloggers abstraction.SubloggersMap, loggerHandler *logger }) broker.SetPool(pool) - blcu_topics.RegisterTopics(broker, pool) return broker, cleanup } @@ -101,45 +98,6 @@ func configureVehicle( vehicle.SetIdToBoardName(idToBoard) vehicle.SetTransport(transp) - // Register BLCU board for handling bootloader operations - if blcuIP, exists := adj.Info.Addresses[BLCU]; exists { - blcuId, idExists := adj.Info.BoardIds["BLCU"] - if !idExists { - return fmt.Errorf("BLCU IP found in ADJ but board ID missing") - } else { - // Get configurable order IDs or use defaults - downloadOrderId := config.Blcu.DownloadOrderId - uploadOrderId := config.Blcu.UploadOrderId - if downloadOrderId == 0 { - downloadOrderId = boards.DefaultBlcuDownloadOrderId - } - if uploadOrderId == 0 { - uploadOrderId = boards.DefaultBlcuUploadOrderId - } - - tftpConfig := boards.TFTPConfig{ - BlockSize: config.TFTP.BlockSize, - Retries: config.TFTP.Retries, - TimeoutMs: config.TFTP.TimeoutMs, - BackoffFactor: config.TFTP.BackoffFactor, - EnableProgress: config.TFTP.EnableProgress, - } - blcuBoard := boards.NewWithConfig(blcuIP, tftpConfig, abstraction.BoardId(blcuId), downloadOrderId, uploadOrderId) - vehicle.AddBoard(blcuBoard) - vehicle.SetBlcuId(abstraction.BoardId(blcuId)) - - trace. - Info(). - Str("ip", blcuIP). - Int("id", int(blcuId)). - Uint16("download_order_id", downloadOrderId). - Uint16("upload_order_id", uploadOrderId). - Msg("BLCU board registered") - } - } else { - trace.Warn().Msg("BLCU not found in ADJ configuration - bootloader operations unavailable") - } - return nil } diff --git a/backend/internal/config/config_types.go b/backend/internal/config/config_types.go index 256ece881..1c0a3a559 100644 --- a/backend/internal/config/config_types.go +++ b/backend/internal/config/config_types.go @@ -26,12 +26,6 @@ type TFTP struct { EnableProgress bool `toml:"enable_progress"` } -type Blcu struct { - IP string `toml:"ip"` - DownloadOrderId uint16 `toml:"download_order_id"` - UploadOrderId uint16 `toml:"upload_order_id"` -} - type TCP struct { BackoffMinMs int `toml:"backoff_min_ms"` BackoffMaxMs int `toml:"backoff_max_ms"` @@ -54,6 +48,5 @@ type Config struct { Transport Transport TFTP TFTP TCP TCP - Blcu Blcu Logging Logging } diff --git a/backend/internal/vehicle/models/order_data.go b/backend/internal/vehicle/models/order_data.go index ac4bc044b..e19ff59de 100644 --- a/backend/internal/vehicle/models/order_data.go +++ b/backend/internal/vehicle/models/order_data.go @@ -36,7 +36,7 @@ type StateOrderDescription struct { Enabled bool `json:"enabled"` } -func NewVehicleOrders(boards []pod_data.Board, blcuName string) (VehicleOrders, error) { +func NewVehicleOrders(boards []pod_data.Board) (VehicleOrders, error) { vehicleOrders := VehicleOrders{ Boards: make([]BoardOrders, 0), } diff --git a/backend/pkg/boards/blcu.go b/backend/pkg/boards/blcu.go deleted file mode 100644 index 9c4534404..000000000 --- a/backend/pkg/boards/blcu.go +++ /dev/null @@ -1,275 +0,0 @@ -package boards - -import ( - "bytes" - "fmt" - "time" - - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/transport" - "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/network/tftp" - dataPacket "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" -) - -const ( - BlcuName = "BLCU" - - AckId = abstraction.BoardEvent("ACK") - DownloadEventId = abstraction.BoardEvent("DOWNLOAD") - UploadEventId = abstraction.BoardEvent("UPLOAD") - - // Default order IDs - can be overridden via config.toml - DefaultBlcuDownloadOrderId = 701 - DefaultBlcuUploadOrderId = 700 -) - -type TFTPConfig struct { - BlockSize int - Retries int - TimeoutMs int - BackoffFactor int - EnableProgress bool -} - -type BLCU struct { - api abstraction.BoardAPI - ackChan chan struct{} - ip string - tftpConfig TFTPConfig - id abstraction.BoardId - downloadOrderId uint16 - uploadOrderId uint16 -} - -// Deprecated: Use NewWithConfig with proper board ID and order IDs from configuration -func New(ip string) *BLCU { - return NewWithTFTPConfig(ip, TFTPConfig{ - BlockSize: 131072, // 128kB - Retries: 3, - TimeoutMs: 5000, - BackoffFactor: 2, - EnableProgress: true, - }, 0) // Board ID 0 indicates missing configuration -} - -// Deprecated: Use NewWithConfig for proper order ID configuration -func NewWithTFTPConfig(ip string, tftpConfig TFTPConfig, id abstraction.BoardId) *BLCU { - return &BLCU{ - ackChan: make(chan struct{}), - ip: ip, - tftpConfig: tftpConfig, - id: id, - downloadOrderId: DefaultBlcuDownloadOrderId, - uploadOrderId: DefaultBlcuUploadOrderId, - } -} - -func NewWithConfig(ip string, tftpConfig TFTPConfig, id abstraction.BoardId, downloadOrderId, uploadOrderId uint16) *BLCU { - return &BLCU{ - ackChan: make(chan struct{}), - ip: ip, - tftpConfig: tftpConfig, - id: id, - downloadOrderId: downloadOrderId, - uploadOrderId: uploadOrderId, - } -} -func (board *BLCU) Id() abstraction.BoardId { - return board.id -} - -func (boards *BLCU) Notify(boardNotification abstraction.BoardNotification) { - switch notification := boardNotification.(type) { - case *AckNotification: - boards.ackChan <- struct{}{} - - case *DownloadEvent: - err := boards.download(*notification) - if err != nil { - fmt.Println(ErrDownloadFailure{ - Timestamp: time.Now(), - Inner: err, - }.Error()) - } - case *UploadEvent: - err := boards.upload(*notification) - if err != nil { - fmt.Println(ErrUploadFailure{ - Timestamp: time.Now(), - Inner: err, - }.Error()) - } - default: - fmt.Println(ErrInvalidBoardEvent{ - Event: notification.Event(), - Timestamp: time.Now(), - }.Error()) - } -} - -func (boards *BLCU) SetAPI(api abstraction.BoardAPI) { - boards.api = api -} - -func (boards *BLCU) download(notification DownloadEvent) error { - // Notify the BLCU - ping := dataPacket.NewPacketWithValues( - abstraction.PacketId(boards.downloadOrderId), - map[dataPacket.ValueName]dataPacket.Value{ - BlcuName: dataPacket.NewEnumValue(dataPacket.EnumVariant(notification.Board)), - }, - map[dataPacket.ValueName]bool{ - BlcuName: true, - }) - - err := boards.api.SendMessage(transport.NewPacketMessage(ping)) - if err != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: err, - } - } - - // Wait for the ACK - <-boards.ackChan - - // TODO! Notify on progress - - client, err := tftp.NewClient(boards.ip, - tftp.WithBlockSize(boards.tftpConfig.BlockSize), - tftp.WithRetries(boards.tftpConfig.Retries), - tftp.WithTimeout(time.Duration(boards.tftpConfig.TimeoutMs)*time.Millisecond), - ) - if err != nil { - return ErrNewClientFailed{ - Addr: boards.ip, - Timestamp: time.Now(), - Inner: err, - } - } - - buffer := &bytes.Buffer{} - - _, err = client.ReadFile(BlcuName, tftp.BinaryMode, buffer) - if err != nil { - pushErr := boards.api.SendPush(abstraction.BrokerPush( - &DownloadFailure{ - Error: err, - }, - )) - if pushErr != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: pushErr, - } - } - - return ErrReadingFileFailed{ - Filename: string(notification.Event()), - Timestamp: time.Now(), - Inner: err, - } - } - - pushErr := boards.api.SendPush(abstraction.BrokerPush( - &DownloadSuccess{ - Data: buffer.Bytes(), - }, - )) - if pushErr != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: err, - } - } - - return nil -} - -func (boards *BLCU) upload(notification UploadEvent) error { - ping := dataPacket.NewPacketWithValues(abstraction.PacketId(boards.uploadOrderId), - map[dataPacket.ValueName]dataPacket.Value{ - BlcuName: dataPacket.NewEnumValue(dataPacket.EnumVariant(notification.Board)), - }, - map[dataPacket.ValueName]bool{ - BlcuName: true, - }) - - err := boards.api.SendMessage(transport.NewPacketMessage(ping)) - if err != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: err, - } - } - - <-boards.ackChan - - // TODO! Notify on progress - - client, err := tftp.NewClient(boards.ip, - tftp.WithBlockSize(boards.tftpConfig.BlockSize), - tftp.WithRetries(boards.tftpConfig.Retries), - tftp.WithTimeout(time.Duration(boards.tftpConfig.TimeoutMs)*time.Millisecond), - ) - if err != nil { - return ErrNewClientFailed{ - Addr: boards.ip, - Timestamp: time.Now(), - Inner: err, - } - } - - data := notification.Data - buffer := bytes.NewBuffer(data) - - read, err := client.WriteFile(BlcuName, tftp.BinaryMode, buffer) - if err != nil { - pushErr := boards.api.SendPush(abstraction.BrokerPush( - &UploadFailure{ - Error: err, - })) - if pushErr != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: pushErr, - } - } - - return ErrReadingFileFailed{ - Filename: string(notification.Event()), - Timestamp: time.Now(), - Inner: err, - } - } - - // Check if all bytes written - if int(read) != len(data) { - err = ErrNotAllBytesWritten{ - Timestamp: time.Now(), - } - - pushErr := boards.api.SendPush(abstraction.BrokerPush( - &UploadFailure{ - Error: err, - })) - if pushErr != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: pushErr, - } - } - - return err - } - - pushErr := boards.api.SendPush(abstraction.BrokerPush( - &UploadSuccess{})) - if pushErr != nil { - return ErrSendMessageFailed{ - Timestamp: time.Now(), - Inner: pushErr, - } - } - return nil -} diff --git a/backend/pkg/boards/blcu_integration_test.go b/backend/pkg/boards/blcu_integration_test.go deleted file mode 100644 index 6586c88c2..000000000 --- a/backend/pkg/boards/blcu_integration_test.go +++ /dev/null @@ -1,284 +0,0 @@ -package boards_test - -import ( - "encoding/json" - "testing" - "time" - - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" - "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" - blcu_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" - "github.com/HyperloopUPV-H8/h9-backend/pkg/vehicle" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" - "github.com/rs/zerolog" -) - -// MockTransport implements abstraction.Transport for testing -type MockTransport struct { - sentMessages []abstraction.TransportMessage -} - -func (m *MockTransport) SendMessage(msg abstraction.TransportMessage) error { - m.sentMessages = append(m.sentMessages, msg) - return nil -} - -func (m *MockTransport) HandleClient(config interface{}, target string) error { - return nil -} - -func (m *MockTransport) HandleServer(config interface{}, addr string) error { - return nil -} - -func (m *MockTransport) HandleSniffer(sniffer interface{}) error { - return nil -} - -func (m *MockTransport) SetAPI(api abstraction.TransportAPI) {} - -func (m *MockTransport) SetIdTarget(id abstraction.PacketId, target abstraction.TransportTarget) {} - -func (m *MockTransport) SetTargetIp(ip string, target abstraction.TransportTarget) {} - -func (m *MockTransport) SetpropagateFault(propagate bool) {} - -func (m *MockTransport) WithDecoder(decoder interface{}) abstraction.Transport { - return m -} - -func (m *MockTransport) WithEncoder(encoder interface{}) abstraction.Transport { - return m -} - -// MockLogger implements abstraction.Logger for testing -type MockLogger struct{} - -func (m *MockLogger) Start() error { - return nil -} - -func (m *MockLogger) Stop() error { - return nil -} - -func (m *MockLogger) PushRecord(record abstraction.LoggerRecord) error { - return nil -} - -func (m *MockLogger) PullRecord(request abstraction.LoggerRequest) (abstraction.LoggerRecord, error) { - return nil, nil -} - -// TestBLCUDownloadOrder tests the BLCU download order flow -func TestBLCUDownloadOrder(t *testing.T) { - // Setup - logger := zerolog.New(nil).Level(zerolog.Disabled) - - // Create vehicle - v := vehicle.New(logger) - - // Create and setup broker - b := broker.New(logger) - connections := make(chan *websocket.Client) - pool := websocket.NewPool(connections, logger) - b.SetPool(pool) - - // Register BLCU topics - blcu_topic.RegisterTopics(b, pool) - - // Set broker and transport - v.SetBroker(b) - mockTransport := &MockTransport{} - v.SetTransport(mockTransport) - mockLogger := &MockLogger{} - v.SetLogger(mockLogger) - - // Create BLCU board - blcuBoard := boards.New("192.168.0.10") // Example IP - - // This is the missing step - register the BLCU board with the vehicle - v.AddBoard(blcuBoard) - - // Note: In a real scenario, we would capture responses through the broker - - // Test download request - t.Run("Download Request", func(t *testing.T) { - downloadRequest := &blcu_topic.DownloadRequest{ - Board: "VCU", - } - - // Send download request through UserPush - err := v.UserPush(downloadRequest) - if err != nil { - t.Fatalf("UserPush failed: %v", err) - } - - // Simulate ACK from board - blcuBoard.Notify(boards.AckNotification{ - ID: boards.AckId, - }) - - // Check if the download order was sent to the board - if len(mockTransport.sentMessages) == 0 { - t.Fatal("No message sent to transport") - } - - // Verify the packet sent contains the correct order ID - // In a real test, we would decode the packet and verify its contents - }) -} - -// TestBLCUUploadOrder tests the BLCU upload order flow -func TestBLCUUploadOrder(t *testing.T) { - // Setup - logger := zerolog.New(nil).Level(zerolog.Disabled) - - // Create vehicle - v := vehicle.New(logger) - - // Create and setup broker - b := broker.New(logger) - connections := make(chan *websocket.Client) - pool := websocket.NewPool(connections, logger) - b.SetPool(pool) - - // Register BLCU topics - blcu_topic.RegisterTopics(b, pool) - - // Set broker and transport - v.SetBroker(b) - mockTransport := &MockTransport{} - v.SetTransport(mockTransport) - mockLogger := &MockLogger{} - v.SetLogger(mockLogger) - - // Create BLCU board - blcuBoard := boards.New("192.168.0.10") // Example IP - - // Register the BLCU board with the vehicle - v.AddBoard(blcuBoard) - - // Test upload request - t.Run("Upload Request", func(t *testing.T) { - // Using the internal request type that has Data field - uploadRequest := &blcu_topic.UploadRequestInternal{ - Board: "VCU", - Data: []byte("test firmware data"), - } - - // Send upload request through UserPush - err := v.UserPush(uploadRequest) - if err != nil { - t.Fatalf("UserPush failed: %v", err) - } - - // Simulate ACK from board - blcuBoard.Notify(boards.AckNotification{ - ID: boards.AckId, - }) - - // Check if the upload order was sent to the board - if len(mockTransport.sentMessages) == 0 { - t.Fatal("No message sent to transport") - } - }) -} - -// TestBLCUWebSocketFlow tests the complete WebSocket flow for BLCU orders -func TestBLCUWebSocketFlow(t *testing.T) { - // Setup - logger := zerolog.New(nil).Level(zerolog.Disabled) - - // Create vehicle - v := vehicle.New(logger) - - // Create and setup broker - b := broker.New(logger) - connections := make(chan *websocket.Client) - pool := websocket.NewPool(connections, logger) - b.SetPool(pool) - - // Register BLCU topics - blcu_topic.RegisterTopics(b, pool) - - // Set broker - v.SetBroker(b) - mockTransport := &MockTransport{} - v.SetTransport(mockTransport) - mockLogger := &MockLogger{} - v.SetLogger(mockLogger) - - // Create BLCU board - blcuBoard := boards.New("192.168.0.10") - v.AddBoard(blcuBoard) - - // Simulate WebSocket client message - t.Run("WebSocket Download Message", func(t *testing.T) { - // Get download topic handler from registered topics - downloadHandler := &blcu_topic.Download{} - downloadHandler.SetAPI(b) - downloadHandler.SetPool(pool) - - // Create WebSocket message - downloadReq := blcu_topic.DownloadRequest{ - Board: "VCU", - } - payload, _ := json.Marshal(downloadReq) - - wsMessage := &websocket.Message{ - Topic: blcu_topic.DownloadName, - Payload: payload, - } - - // Simulate client message - // Create a valid UUID for ClientId - clientUUID := [16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} - clientId := websocket.ClientId(clientUUID) - downloadHandler.ClientMessage(clientId, wsMessage) - - // Give some time for async operations - time.Sleep(100 * time.Millisecond) - - // Verify order was sent - if len(mockTransport.sentMessages) == 0 { - t.Error("No message sent to transport after WebSocket message") - } - }) -} - -// TestBLCURegistrationIssue demonstrates the issue when BLCU is not registered -func TestBLCURegistrationIssue(t *testing.T) { - // Setup WITHOUT registering BLCU board - logger := zerolog.New(nil).Level(zerolog.Disabled) - - v := vehicle.New(logger) - b := broker.New(logger) - connections := make(chan *websocket.Client) - pool := websocket.NewPool(connections, logger) - b.SetPool(pool) - blcu_topic.RegisterTopics(b, pool) - v.SetBroker(b) - - // Try to send download request without BLCU board registered - t.Run("Download Without Registration", func(t *testing.T) { - defer func() { - if r := recover(); r == nil { - // If no panic, check if the request was handled - // In the current implementation, this will fail silently - t.Log("Request handled without BLCU registration - this is the bug!") - } - }() - - downloadRequest := &blcu_topic.DownloadRequest{ - Board: "VCU", - } - - // This will fail because boards[boards.BlcuId] is nil - err := v.UserPush(downloadRequest) - if err == nil { - t.Log("UserPush succeeded but BLCU board notification will fail") - } - }) -} \ No newline at end of file diff --git a/backend/pkg/boards/blcu_simple_test.go b/backend/pkg/boards/blcu_simple_test.go deleted file mode 100644 index 7ff5633e8..000000000 --- a/backend/pkg/boards/blcu_simple_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package boards_test - -import ( - "testing" - - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" - blcu_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" - "github.com/HyperloopUPV-H8/h9-backend/pkg/vehicle" - "github.com/rs/zerolog" -) - -// TestBLCUBoardRegistration tests that BLCU board can be registered with different configurations -func TestBLCUBoardRegistration(t *testing.T) { - logger := zerolog.New(nil).Level(zerolog.Disabled) - v := vehicle.New(logger) - - // Test deprecated constructor (should use board ID 0) - blcuBoard := boards.New("192.168.0.10") - v.AddBoard(blcuBoard) - - // Verify board is registered with ID 0 (missing configuration) - if blcuBoard.Id() != 0 { - t.Errorf("Expected board ID 0 for deprecated constructor, got %d", blcuBoard.Id()) - } -} - -// TestBLCUWithCustomConfiguration tests BLCU with custom board ID and order IDs -func TestBLCUWithCustomConfiguration(t *testing.T) { - logger := zerolog.New(nil).Level(zerolog.Disabled) - v := vehicle.New(logger) - - // Test new constructor with custom configuration - tftpConfig := boards.TFTPConfig{ - BlockSize: 131072, - Retries: 3, - TimeoutMs: 5000, - BackoffFactor: 2, - EnableProgress: true, - } - - customBoardId := abstraction.BoardId(7) - customDownloadOrderId := uint16(801) - customUploadOrderId := uint16(802) - - blcuBoard := boards.NewWithConfig("192.168.0.10", tftpConfig, customBoardId, customDownloadOrderId, customUploadOrderId) - v.AddBoard(blcuBoard) - - // Verify board is registered with custom ID - if blcuBoard.Id() != customBoardId { - t.Errorf("Expected board ID %d, got %d", customBoardId, blcuBoard.Id()) - } -} - -// TestBLCUWithDefaultConfiguration tests BLCU with default order IDs -func TestBLCUWithDefaultConfiguration(t *testing.T) { - logger := zerolog.New(nil).Level(zerolog.Disabled) - v := vehicle.New(logger) - - // Test deprecated constructor (should use default order IDs) - tftpConfig := boards.TFTPConfig{ - BlockSize: 131072, - Retries: 3, - TimeoutMs: 5000, - BackoffFactor: 2, - EnableProgress: true, - } - - boardId := abstraction.BoardId(7) - blcuBoard := boards.NewWithTFTPConfig("192.168.0.10", tftpConfig, boardId) - v.AddBoard(blcuBoard) - - // Verify board is registered - if blcuBoard.Id() != boardId { - t.Errorf("Expected board ID %d, got %d", boardId, blcuBoard.Id()) - } -} - -// TestBLCURequestStructures tests the request structures -func TestBLCURequestStructures(t *testing.T) { - // Test download request - downloadReq := &blcu_topic.DownloadRequest{ - Board: "VCU", - } - if downloadReq.Topic() != "blcu/downloadRequest" { - t.Errorf("Expected topic 'blcu/downloadRequest', got '%s'", downloadReq.Topic()) - } - - // Test upload request - uploadReq := &blcu_topic.UploadRequest{ - Board: "VCU", - File: "dGVzdCBkYXRh", // base64 for "test data" - } - if uploadReq.Topic() != "blcu/uploadRequest" { - t.Errorf("Expected topic 'blcu/uploadRequest', got '%s'", uploadReq.Topic()) - } - - // Test internal upload request - uploadReqInternal := &blcu_topic.UploadRequestInternal{ - Board: "VCU", - Data: []byte("test data"), - } - if uploadReqInternal.Topic() != "blcu/uploadRequest" { - t.Errorf("Expected topic 'blcu/uploadRequest', got '%s'", uploadReqInternal.Topic()) - } -} \ No newline at end of file diff --git a/backend/pkg/boards/events.go b/backend/pkg/boards/events.go index 045195a83..77a3cbef1 100644 --- a/backend/pkg/boards/events.go +++ b/backend/pkg/boards/events.go @@ -18,10 +18,6 @@ type DownloadEvent struct { Board string } -func (download DownloadEvent) Topic() abstraction.BrokerTopic { - return "blcu/download" -} - func (download DownloadEvent) Event() abstraction.BoardEvent { return download.BoardEvent } @@ -33,10 +29,6 @@ type UploadEvent struct { Length int } -func (upload UploadEvent) Topic() abstraction.BrokerTopic { - return "blcu/upload" -} - func (upload UploadEvent) Event() abstraction.BoardEvent { return upload.BoardEvent } @@ -45,40 +37,20 @@ type BoardPush struct { Data int64 } -func (boardPush BoardPush) Topic() abstraction.BrokerTopic { - return "blcu/boardPush" -} - type DownloadSuccess struct { Data []byte } -func (downloadSuccess DownloadSuccess) Topic() abstraction.BrokerTopic { - return "blcu/download/success" -} - type UploadSuccess struct{} -func (uploadSuccess UploadSuccess) Topic() abstraction.BrokerTopic { - return "blcu/upload/success" -} - type DownloadFailure struct { Error error } -func (downloadFailure DownloadFailure) Topic() abstraction.BrokerTopic { - return "blcu/download/failure" -} - type UploadFailure struct { Error error } -func (uploadFailure UploadFailure) Topic() abstraction.BrokerTopic { - return "blcu/upload/failure" -} - type BoardMessage struct { ID abstraction.TransportEvent // UploadName } diff --git a/backend/pkg/broker/topics/blcu/blcu_test.go b/backend/pkg/broker/topics/blcu/blcu_test.go deleted file mode 100644 index fd3136224..000000000 --- a/backend/pkg/broker/topics/blcu/blcu_test.go +++ /dev/null @@ -1,204 +0,0 @@ -package blcu_test - -import ( - "encoding/json" - "fmt" - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" - "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" - "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/tests_functions" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" - ws "github.com/gorilla/websocket" - "github.com/rs/zerolog" - "log" - "os" - "testing" - "time" -) - -var errorFlag bool - -type OutputNotMatchingError struct{} - -func (e *OutputNotMatchingError) Error() string { - return "Output does not match" -} - -type MockAPI struct{} - -func (api MockAPI) UserPush(push abstraction.BrokerPush) error { - switch push.(type) { - case blcu.DownloadRequest: - if push.(blcu.DownloadRequest).Board != "test" { - errorFlag = true - return &OutputNotMatchingError{} - } - errorFlag = false - log.Printf("Output matches") - return nil - case *blcu.UploadRequestInternal: - req := push.(*blcu.UploadRequestInternal) - if req.Board != "test" || string(req.Data) != "test" { - errorFlag = true - fmt.Printf("Expected board 'test' and data 'test', got board '%s' and data '%s'\n", req.Board, string(req.Data)) - return &OutputNotMatchingError{} - } - errorFlag = false - log.Printf("Output matches") - return nil - } - return nil -} - -func (api MockAPI) UserPull(request abstraction.BrokerRequest) (abstraction.BrokerResponse, error) { - return nil, nil -} - -func TestBLCUTopic_Download_Push(t *testing.T) { - logger := zerolog.New(os.Stdout).With().Timestamp().Logger() - clientChan := make(chan *websocket.Client) - u := tests_functions.StartServer(logger, "download") - - // Mock first client as it always fails - c, _, err := ws.DefaultDialer.Dial(u.String(), nil) - if err != nil { - log.Printf("Expected dial error") - } - c.Close() - - // Set up the client - c, _, err = ws.DefaultDialer.Dial(u.String(), nil) - if err != nil { - logger.Fatal().Err(err).Msg("Error dialing") - } - defer c.Close() - defer logger.Info().Str("id", "client").Msg("Client connection closed") - - api := broker.New(logger) - pool := websocket.NewPool(clientChan, logger) - client := websocket.NewClient(c) - clientChan <- client - - download := blcu.Download{} - download.SetAPI(api) - download.SetPool(pool) - - // Simulate sending a download request - request := blcu.DownloadRequest{Board: "test"} - err = download.Push(request) - if err != nil { - t.Fatal("Error pushing download request:", err) - } - - // Use a timeout for client read - done := make(chan struct{}) - go func() { - output, readErr := client.Read() - if readErr != nil { - logger.Error().Err(readErr).Msg("Client read failed") - done <- struct{}{} - return - } - if output.Topic != blcu.DownloadName { - t.Errorf("Expected topic %s, got %s", blcu.DownloadName, output.Topic) - } - if string(output.Payload) != "test" { - t.Error("Expected payload 'test', got", string(output.Payload)) - } - done <- struct{}{} - }() - - select { - case <-done: - logger.Info().Msg("Test completed successfully") - case <-time.After(3 * time.Second): - t.Error("Test timed out") - } -} - -func TestBLCUTopic_Download_ClientMessage(t *testing.T) { - download := blcu.Download{} - download.SetAPI(&MockAPI{}) - - download.ClientMessage(websocket.ClientId{0}, &websocket.Message{ - Topic: blcu.DownloadName, - Payload: []byte(`{"board":"test"}`), - }) - - if errorFlag { - t.Fatal("Output does not match") - } -} - -func TestBLCUTopic_Upload_Push(t *testing.T) { - logger := zerolog.New(os.Stdout).With().Timestamp().Logger() - clientChan := make(chan *websocket.Client) - u := tests_functions.StartServer(logger, "upload") - - // Set up the client - c, _, err := ws.DefaultDialer.Dial(u.String(), nil) - if err != nil { - logger.Fatal().Err(err).Msg("Error dialing") - } - defer c.Close() - defer logger.Info().Str("id", "client").Msg("Client connection closed") - - api := broker.New(logger) - pool := websocket.NewPool(clientChan, logger) - client := websocket.NewClient(c) - clientChan <- client - - upload := blcu.Upload{} - upload.SetAPI(api) - upload.SetPool(pool) - - // Simulate sending an upload request - request := blcu.UploadRequest{Board: "test", File: "dGVzdA=="} // "test" in base64 - err = upload.Push(request) - if err != nil { - t.Fatal("Error pushing upload request:", err) - } - - // Use a timeout for client read - done := make(chan struct{}) - go func() { - output, err := client.Read() - if err != nil { - logger.Error().Err(err).Msg("Client read failed") - done <- struct{}{} - return - } - if output.Topic != blcu.UploadName { - t.Errorf("Expected topic %s, got %s", blcu.UploadName, output.Topic) - } - if string(output.Payload) != "test" { - t.Error("Expected payload 'test', got", string(output.Payload)) - } - done <- struct{}{} - }() - - select { - case <-done: - logger.Info().Msg("Test completed successfully") - case <-time.After(3 * time.Second): - t.Error("Test timed out") - } -} - -func TestBLCUTopic_Upload_ClientMessage(t *testing.T) { - upload := blcu.Upload{} - upload.SetAPI(&MockAPI{}) - - // Use base64 encoded data as the frontend would send - payload := blcu.UploadRequest{Board: "test", File: "dGVzdA=="} // "test" in base64 - payloadBytes, _ := json.Marshal(payload) - - upload.ClientMessage(websocket.ClientId{0}, &websocket.Message{ - Topic: blcu.UploadName, - Payload: payloadBytes, - }) - - if errorFlag { - t.Fatal("Output does not match") - } -} diff --git a/backend/pkg/broker/topics/blcu/download.go b/backend/pkg/broker/topics/blcu/download.go deleted file mode 100644 index 8ee022956..000000000 --- a/backend/pkg/broker/topics/blcu/download.go +++ /dev/null @@ -1,100 +0,0 @@ -package blcu - -import ( - "encoding/json" - "fmt" - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" -) - -const DownloadName abstraction.BrokerTopic = "blcu/download" - -type Download struct { - pool *websocket.Pool - api abstraction.BrokerAPI - client websocket.ClientId -} - -func (download *Download) Topic() abstraction.BrokerTopic { - return DownloadName -} - -type DownloadRequest struct { - Board string `json:"board"` -} - -func (request DownloadRequest) Topic() abstraction.BrokerTopic { - return "blcu/downloadRequest" -} - -func (download *Download) Push(push abstraction.BrokerPush) error { - switch p := push.(type) { - case *boards.DownloadSuccess: - // Send success response with the downloaded data - response := map[string]interface{}{ - "percentage": 100, - "failure": false, - "file": p.Data, // The downloaded file data - } - payload, _ := json.Marshal(response) - err := download.pool.Write(download.client, websocket.Message{ - Topic: DownloadName, - Payload: payload, - }) - if err != nil { - return err - } - case *boards.DownloadFailure: - // Send failure response - response := map[string]interface{}{ - "percentage": 0, - "failure": true, - } - payload, _ := json.Marshal(response) - err := download.pool.Write(download.client, websocket.Message{ - Topic: DownloadName, - Payload: payload, - }) - if err != nil { - return err - } - } - - return nil -} - -func (download *Download) Pull(request abstraction.BrokerRequest) (abstraction.BrokerResponse, error) { - return nil, nil -} - -func (download *Download) ClientMessage(id websocket.ClientId, message *websocket.Message) { - download.client = id - - switch message.Topic { - case DownloadName: - err := download.handleDownload(message) - if err != nil { - fmt.Printf("error handling download: %v\n", err) - } - } -} - -func (download *Download) handleDownload(message *websocket.Message) error { - var downloadRequest DownloadRequest - err := json.Unmarshal(message.Payload, &downloadRequest) - if err != nil { - return err - } - - pushErr := download.api.UserPush(&downloadRequest) - return pushErr -} - -func (download *Download) SetPool(pool *websocket.Pool) { - download.pool = pool -} - -func (download *Download) SetAPI(api abstraction.BrokerAPI) { - download.api = api -} diff --git a/backend/pkg/broker/topics/blcu/register.go b/backend/pkg/broker/topics/blcu/register.go deleted file mode 100644 index ee735b716..000000000 --- a/backend/pkg/broker/topics/blcu/register.go +++ /dev/null @@ -1,22 +0,0 @@ -package blcu - -import ( - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" - "github.com/HyperloopUPV-H8/h9-backend/pkg/broker" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" -) - -func RegisterTopics(b *broker.Broker, pool *websocket.Pool) { - upload := &Upload{} - upload.SetAPI(b) - upload.SetPool(pool) - download := &Download{} - download.SetAPI(b) - download.SetPool(pool) - b.AddTopic(UploadName, upload) - b.AddTopic(DownloadName, download) - b.AddTopic(boards.UploadSuccess{}.Topic(), upload) - b.AddTopic(boards.UploadFailure{}.Topic(), upload) - b.AddTopic(boards.DownloadSuccess{}.Topic(), download) - b.AddTopic(boards.DownloadFailure{}.Topic(), download) -} diff --git a/backend/pkg/broker/topics/blcu/upload.go b/backend/pkg/broker/topics/blcu/upload.go deleted file mode 100644 index 82e2b1278..000000000 --- a/backend/pkg/broker/topics/blcu/upload.go +++ /dev/null @@ -1,124 +0,0 @@ -package blcu - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" - "github.com/HyperloopUPV-H8/h9-backend/pkg/websocket" -) - -const UploadName abstraction.BrokerTopic = "blcu/upload" - -type Upload struct { - pool *websocket.Pool - api abstraction.BrokerAPI - client websocket.ClientId -} - -func (upload *Upload) Topic() abstraction.BrokerTopic { - return UploadName -} - -type UploadRequest struct { - Board string `json:"board"` - File string `json:"file"` // Base64 encoded file data from frontend -} - -func (request UploadRequest) Topic() abstraction.BrokerTopic { - return "blcu/uploadRequest" -} - -// UploadRequestInternal is the internal representation with decoded data -type UploadRequestInternal struct { - Board string - Data []byte -} - -func (request UploadRequestInternal) Topic() abstraction.BrokerTopic { - return "blcu/uploadRequest" -} - -func (upload *Upload) Push(push abstraction.BrokerPush) error { - switch push.(type) { - case *boards.UploadSuccess: - // Send success response - response := map[string]interface{}{ - "percentage": 100, - "failure": false, - } - payload, _ := json.Marshal(response) - err := upload.pool.Write(upload.client, websocket.Message{ - Topic: UploadName, - Payload: payload, - }) - if err != nil { - return err - } - - case *boards.UploadFailure: - // Send failure response - response := map[string]interface{}{ - "percentage": 0, - "failure": true, - } - payload, _ := json.Marshal(response) - err := upload.pool.Write(upload.client, websocket.Message{ - Topic: UploadName, - Payload: payload, - }) - if err != nil { - return err - } - } - - return nil -} - -func (upload *Upload) Pull(request abstraction.BrokerRequest) (abstraction.BrokerResponse, error) { - return nil, nil -} - -func (upload *Upload) ClientMessage(id websocket.ClientId, message *websocket.Message) { - upload.client = id - - switch message.Topic { - case UploadName: - err := upload.handleUpload(message) - if err != nil { - fmt.Printf("error handling download: %v\n", err) - } - } -} - -func (upload *Upload) handleUpload(message *websocket.Message) error { - var uploadRequest UploadRequest - err := json.Unmarshal(message.Payload, &uploadRequest) - if err != nil { - return err - } - - // Decode base64 file data - fileData, err := base64.StdEncoding.DecodeString(uploadRequest.File) - if err != nil { - return fmt.Errorf("failed to decode base64 file data: %w", err) - } - - // Create the internal upload event with decoded data - internalRequest := &UploadRequestInternal{ - Board: uploadRequest.Board, - Data: fileData, - } - - pushErr := upload.api.UserPush(internalRequest) - return pushErr -} - -func (upload *Upload) SetPool(pool *websocket.Pool) { - upload.pool = pool -} - -func (upload *Upload) SetAPI(api abstraction.BrokerAPI) { - upload.api = api -} diff --git a/backend/pkg/transport/packet/blcu/decoder.go b/backend/pkg/transport/packet/blcu/decoder.go deleted file mode 100644 index 273bf48c8..000000000 --- a/backend/pkg/transport/packet/blcu/decoder.go +++ /dev/null @@ -1,20 +0,0 @@ -package blcu - -import ( - "io" - - "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" -) - -// Decoder is a decoder for the blcu Ack packet -type Decoder struct{} - -// NewDecoder creates a new Decoder -func NewDecoder() *Decoder { - return &Decoder{} -} - -// Decode decodes the next packet on reader and returns the corresponding blcuAck. -func (decoder *Decoder) Decode(id abstraction.PacketId, reader io.Reader) (abstraction.Packet, error) { - return NewAck(id), nil -} diff --git a/backend/pkg/transport/packet/blcu/packet.go b/backend/pkg/transport/packet/blcu/packet.go deleted file mode 100644 index ebe5c57c9..000000000 --- a/backend/pkg/transport/packet/blcu/packet.go +++ /dev/null @@ -1,22 +0,0 @@ -package blcu - -import "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - -// Ack is the blcu Ack message sent when the blcu is ready to create a tftp connection. -// -// the packet just has the ID. -type Ack struct { - id abstraction.PacketId -} - -// NewAck creates a new blcu ack packet with the given id -func NewAck(id abstraction.PacketId) *Ack { - return &Ack{ - id: id, - } -} - -// Id returns the ID of the packet -func (packet *Ack) Id() abstraction.PacketId { - return packet.id -} diff --git a/backend/pkg/transport/presentation/decoder.go b/backend/pkg/transport/presentation/decoder.go index 14e2924ec..bc30a1272 100644 --- a/backend/pkg/transport/presentation/decoder.go +++ b/backend/pkg/transport/presentation/decoder.go @@ -5,7 +5,6 @@ import ( "io" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/blcu" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/order" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" @@ -20,7 +19,6 @@ type PacketDecoder interface { // Type assertions to check packet decoders follows the Decoder interface var _ PacketDecoder = &data.Decoder{} -var _ PacketDecoder = &blcu.Decoder{} var _ PacketDecoder = &order.Decoder{} var _ PacketDecoder = &protection.Decoder{} var _ PacketDecoder = &state.Decoder{} diff --git a/backend/pkg/transport/presentation/decoder_test.go b/backend/pkg/transport/presentation/decoder_test.go index 8df16058d..829f85b89 100644 --- a/backend/pkg/transport/presentation/decoder_test.go +++ b/backend/pkg/transport/presentation/decoder_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/blcu" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/order" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" @@ -29,22 +28,7 @@ func TestDecoder(t *testing.T) { endianness := binary.LittleEndian testcases := []testcase{ - { - name: "blcu ack", - input: bytes.NewReader([]byte{0x01, 0x00}), - output: []abstraction.Packet{ - blcu.NewAck(1), - }, - }, - { - name: "multiple blcu ack", - input: bytes.NewReader([]byte{0x01, 0x00, 0x01, 0x00, 0x01, 0x00}), - output: []abstraction.Packet{ - blcu.NewAck(1), - blcu.NewAck(1), - blcu.NewAck(1), - }, - }, + { name: "state orders add", input: bytes.NewReader([]byte{0x03, 0x00, 0x01, 0x00, 0xFF, 0xFF}), @@ -648,7 +632,6 @@ func TestDecoder(t *testing.T) { } // getDecoder generates a mock Decoder with the following packet IDs: -// 1 - blcuAck // 3 - add state order // 4 - remove state order // 5 - protection warning @@ -659,8 +642,6 @@ func getDecoder(endianness binary.ByteOrder) *presentation.Decoder { nullLogger := zerolog.New(io.Discard) decoder := presentation.NewDecoder(endianness, nullLogger) - decoder.SetPacketDecoder(1, blcu.NewDecoder()) - ordersDecoder := order.NewDecoder(endianness) ordersDecoder.SetActionId(3, ordersDecoder.DecodeAdd) ordersDecoder.SetActionId(4, ordersDecoder.DecodeRemove) diff --git a/backend/pkg/transport/transport.go b/backend/pkg/transport/transport.go index 2fb9ea39f..cca4b0e7a 100644 --- a/backend/pkg/transport/transport.go +++ b/backend/pkg/transport/transport.go @@ -58,7 +58,7 @@ var zeroTime time.Time // applying exponential backoff between attempts. func (transport *Transport) HandleClient(config tcp.ClientConfig, remote string) error { client := tcp.NewClient(remote, config, transport.logger) - clientLogger := transport.logger.With().Str("remoteAddress", remote).Logger() + clientLogger := transport.logger.With().Str("remoteAddress", remote).Logger() defer clientLogger.Warn().Msg("abort connection") var hasConnected = false @@ -205,7 +205,7 @@ func (transport *Transport) targetFromTCPConn(conn net.Conn) (abstraction.Transp } // rejectIfConnectedTCPConn closes and rejects conn if target already has an active connection. -func (transport *Transport) rejectIfConnectedTCPConn(target abstraction.TransportTarget, conn net.Conn, logger zerolog.Logger,) error { +func (transport *Transport) rejectIfConnectedTCPConn(target abstraction.TransportTarget, conn net.Conn, logger zerolog.Logger) error { transport.connectionsMx.Lock() defer transport.connectionsMx.Unlock() @@ -238,7 +238,7 @@ func (transport *Transport) registerTCPConn(target abstraction.TransportTarget, func (transport *Transport) readLoopTCPConn(conn net.Conn, logger zerolog.Logger) { from := conn.RemoteAddr().String() to := conn.LocalAddr().String() - + go func() { for { packet, err := transport.decoder.DecodeNext(conn) @@ -267,7 +267,6 @@ func (transport *Transport) readLoopTCPConn(conn net.Conn, logger zerolog.Logger }() } - // SendMessage triggers an event to send something to the vehicle. Some messages // might additional means to pass information around (e.g. file read and write) func (transport *Transport) SendMessage(message abstraction.TransportMessage) error { @@ -342,7 +341,7 @@ func (transport *Transport) handlePacketEvent(message PacketMessage) error { defer transport.connectionsMx.RUnlock() conn, ok := transport.connections[target] if !ok { - eventLogger.Warn().Msg("target not connected") + eventLogger.Warn().Msg("target not connected") err := ErrConnClosed{Target: target} return nil, err @@ -379,24 +378,6 @@ func (transport *Transport) handlePacketEvent(message PacketMessage) error { return nil } -// handleFileWrite writes a file through tftp to the blcu -func (transport *Transport) handleFileWrite(message FileWriteMessage) error { - _, err := transport.tftp.WriteFile(message.Filename(), tftp.BinaryMode, message) - if err != nil { - transport.errChan <- err - } - return err -} - -// handleFileRead reads a file through tftp from the blcu -func (transport *Transport) handleFileRead(message FileReadMessage) error { - _, err := transport.tftp.ReadFile(message.Filename(), tftp.BinaryMode, message) - if err != nil { - transport.errChan <- err - } - return err -} - // HandleSniffer starts listening for packets on the provided sniffer and handles them. // // This function will block until the sniffer is closed @@ -414,7 +395,7 @@ func (transport *Transport) HandleSniffer(sniffer *sniffer.Sniffer) { func (transport *Transport) HandleUDPServer(server *udp.Server) { packetsCh := server.GetPackets() errorsCh := server.GetErrors() - + for { select { case packet := <-packetsCh: @@ -437,7 +418,7 @@ func (transport *Transport) replicateFault(packet abstraction.Packet, logger zer func (transport *Transport) handleUDPPacket(udpPacket udp.Packet) { srcAddr := fmt.Sprintf("%s:%d", udpPacket.SourceIP, udpPacket.SourcePort) dstAddr := fmt.Sprintf("%s:%d", udpPacket.DestIP, udpPacket.DestPort) - + // Create a reader from the payload readerAny := transport.byteReaderPool.Get() var reader *bytes.Reader @@ -460,12 +441,12 @@ func (transport *Transport) handleUDPPacket(udpPacket udp.Packet) { transport.errChan <- err return } - + // Intercept packets with id == 0 and replicate if transport.propagateFault && packet.Id() == 0 { transport.replicateFault(packet, transport.logger) } - + // Send notification transport.api.Notification(NewPacketNotification(packet, srcAddr, dstAddr, udpPacket.Timestamp)) diff --git a/backend/pkg/vehicle/constructor.go b/backend/pkg/vehicle/constructor.go index 6dab58618..8468195f9 100644 --- a/backend/pkg/vehicle/constructor.go +++ b/backend/pkg/vehicle/constructor.go @@ -75,8 +75,3 @@ func (vehicle *Vehicle) SetIpToBoardId(ipToBoardId map[string]abstraction.BoardI vehicle.ipToBoardId = ipToBoardId vehicle.trace.Info().Msg("set ip to board id") } - -func (vehicle *Vehicle) SetBlcuId(id abstraction.BoardId) { - vehicle.BlcuId = id - vehicle.trace.Info().Uint16("blcu_id", uint16(id)).Msg("set blcu id") -} diff --git a/backend/pkg/vehicle/notification.go b/backend/pkg/vehicle/notification.go index 8a049e606..4bf0d9d05 100644 --- a/backend/pkg/vehicle/notification.go +++ b/backend/pkg/vehicle/notification.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" data_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/data" message_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/message" order_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/order" @@ -15,7 +14,7 @@ import ( protection_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/protection" state_logger "github.com/HyperloopUPV-H8/h9-backend/pkg/logger/state" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport" - blcu_packet "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/blcu" + "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/data" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/order" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" @@ -130,12 +129,7 @@ func (vehicle *Vehicle) handlePacketNotification(notification transport.PacketNo vehicle.trace.Error().Stack().Err(err).Msg("remove state orders") return errors.Join(fmt.Errorf("remove state orders (state orders from %s to %s)", notification.From, notification.To), err) } - case *blcu_packet.Ack: - vehicle.boards[vehicle.BlcuId].Notify(abstraction.BoardNotification( - &boards.AckNotification{ - ID: boards.AckId, - }, - )) + } return nil } diff --git a/backend/pkg/vehicle/vehicle.go b/backend/pkg/vehicle/vehicle.go index e682f29f4..6e51cbb24 100644 --- a/backend/pkg/vehicle/vehicle.go +++ b/backend/pkg/vehicle/vehicle.go @@ -5,12 +5,10 @@ import ( "fmt" "os" - "github.com/HyperloopUPV-H8/h9-backend/pkg/boards" "github.com/HyperloopUPV-H8/h9-backend/pkg/transport/packet/protection" "github.com/HyperloopUPV-H8/h9-backend/internal/update_factory" "github.com/HyperloopUPV-H8/h9-backend/pkg/abstraction" - blcu_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/blcu" connection_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/connection" logger_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/logger" message_topic "github.com/HyperloopUPV-H8/h9-backend/pkg/broker/topics/message" @@ -95,47 +93,6 @@ func (vehicle *Vehicle) UserPush(push abstraction.BrokerPush) error { status.Fulfill(status.Enable()) } - case "blcu/downloadRequest": - download := push.(*blcu_topic.DownloadRequest) - - if board, exists := vehicle.boards[vehicle.BlcuId]; exists { - board.Notify(abstraction.BoardNotification( - &boards.DownloadEvent{ - BoardEvent: boards.DownloadEventId, - BoardID: vehicle.BlcuId, - Board: download.Board, - }, - )) - } else { - fmt.Fprintf(os.Stderr, "BLCU board not registered\n") - } - - case "blcu/uploadRequest": - // Handle both UploadRequest and UploadRequestInternal - var uploadEvent *boards.UploadEvent - switch u := push.(type) { - case *blcu_topic.UploadRequestInternal: - uploadEvent = &boards.UploadEvent{ - BoardEvent: boards.UploadEventId, - Board: u.Board, - Data: u.Data, - Length: len(u.Data), - } - case *blcu_topic.UploadRequest: - // This shouldn't happen as the handler should convert to Internal - fmt.Fprintf(os.Stderr, "received raw UploadRequest, expected UploadRequestInternal\n") - return nil - default: - fmt.Fprintf(os.Stderr, "unknown upload type: %T\n", push) - return nil - } - - if board, exists := vehicle.boards[vehicle.BlcuId]; exists { - board.Notify(abstraction.BoardNotification(uploadEvent)) - } else { - fmt.Fprintf(os.Stderr, "BLCU board not registered\n") - } - default: fmt.Printf("unknow topic %s\n", push.Topic()) } From 7e4adad60b1b560767b819d4ffbab23614e15cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Ribal=20del=20R=C3=ADo?= Date: Mon, 16 Feb 2026 19:22:46 +0100 Subject: [PATCH 2/2] feat(backend): remove blcu --- backend/pkg/transport/transport.go | 4 - backend/pkg/transport/transport_test.go | 234 ++++++++---------------- 2 files changed, 81 insertions(+), 157 deletions(-) diff --git a/backend/pkg/transport/transport.go b/backend/pkg/transport/transport.go index cca4b0e7a..d4020a5d6 100644 --- a/backend/pkg/transport/transport.go +++ b/backend/pkg/transport/transport.go @@ -275,10 +275,6 @@ func (transport *Transport) SendMessage(message abstraction.TransportMessage) er switch msg := message.(type) { case PacketMessage: err = transport.handlePacketEvent(msg) - case FileWriteMessage: - err = transport.handleFileWrite(msg) - case FileReadMessage: - err = transport.handleFileRead(msg) default: err = ErrUnrecognizedEvent{message.Event()} } diff --git a/backend/pkg/transport/transport_test.go b/backend/pkg/transport/transport_test.go index 428dc4f13..8cde13a44 100644 --- a/backend/pkg/transport/transport_test.go +++ b/backend/pkg/transport/transport_test.go @@ -24,7 +24,6 @@ import ( "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" "github.com/google/gopacket/pcapgo" - tftpv3 "github.com/pin/tftp/v3" "github.com/rs/zerolog" ) @@ -123,8 +122,8 @@ func NewMockBoardServer(address string) *MockBoardServer { logger := zerolog.Nop() enc := presentation.NewEncoder(binary.BigEndian, logger) - dec := presentation.NewDecoder(binary.BigEndian, logger) - wireTestPacketCodec(enc, dec, abstraction.PacketId(100)) + dec := presentation.NewDecoder(binary.BigEndian, logger) + wireTestPacketCodec(enc, dec, abstraction.PacketId(100)) return &MockBoardServer{ address: address, @@ -138,47 +137,47 @@ func NewMockBoardServer(address string) *MockBoardServer { func (s *MockBoardServer) Start() error { s.mu.Lock() defer s.mu.Unlock() - + if s.running { return fmt.Errorf("server already running") } - + listener, err := net.Listen("tcp", s.address) if err != nil { return fmt.Errorf("failed to listen on %s: %w", s.address, err) } - + s.listener = listener s.running = true - + go s.acceptLoop() - + return nil } func (s *MockBoardServer) Stop() error { s.mu.Lock() defer s.mu.Unlock() - + if !s.running { return nil } - + s.running = false - + // Close all connections for _, conn := range s.connections { conn.Close() } s.connections = s.connections[:0] - + // Close listener if s.listener != nil { err := s.listener.Close() s.listener = nil return err } - + return nil } @@ -194,11 +193,11 @@ func (s *MockBoardServer) acceptLoop() { } continue } - + s.mu.Lock() s.connections = append(s.connections, conn) s.mu.Unlock() - + go s.handleConnection(conn) } } @@ -216,19 +215,19 @@ func (s *MockBoardServer) handleConnection(conn net.Conn) { } s.mu.Unlock() }() - + for { s.mu.RLock() running := s.running s.mu.RUnlock() - + if !running { return } - + // Set read timeout to avoid blocking forever conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - + packet, err := s.decoder.DecodeNext(conn) if err != nil { if netErr, ok := err.(net.Error); ok && netErr.Timeout() { @@ -236,7 +235,7 @@ func (s *MockBoardServer) handleConnection(conn net.Conn) { } return } - + s.mu.Lock() s.packetsRecv = append(s.packetsRecv, packet) s.mu.Unlock() @@ -264,18 +263,17 @@ func createTestTransport(t *testing.T) (*Transport, *TestTransportAPI) { logger := zerolog.New(zerolog.Nop()).With().Timestamp().Logger() enc := presentation.NewEncoder(binary.BigEndian, logger) - dec := presentation.NewDecoder(binary.BigEndian, logger) - wireTestPacketCodec(enc, dec, abstraction.PacketId(100)) + dec := presentation.NewDecoder(binary.BigEndian, logger) + wireTestPacketCodec(enc, dec, abstraction.PacketId(100)) wireTestPacketCodec(enc, dec, abstraction.PacketId(0)) - transport := NewTransport(logger). WithEncoder(enc). WithDecoder(dec) - + api := NewTestTransportAPI() transport.SetAPI(api) - + return transport, api } @@ -315,23 +313,23 @@ func waitForCondition(condition func() bool, timeout time.Duration, message stri // test wiring: register a trivial codec for a data packet id. func wireTestPacketCodec(enc *presentation.Encoder, dec *presentation.Decoder, id abstraction.PacketId) { - dataEnc := data.NewEncoder(binary.BigEndian) - dataDec := data.NewDecoder(binary.BigEndian) + dataEnc := data.NewEncoder(binary.BigEndian) + dataDec := data.NewDecoder(binary.BigEndian) - // Empty descriptor = no payload values, just the id header. - var desc data.Descriptor - dataEnc.SetDescriptor(id, desc) - dataDec.SetDescriptor(id, desc) + // Empty descriptor = no payload values, just the id header. + var desc data.Descriptor + dataEnc.SetDescriptor(id, desc) + dataDec.SetDescriptor(id, desc) - enc.SetPacketEncoder(id, dataEnc) - dec.SetPacketDecoder(id, dataDec) + enc.SetPacketEncoder(id, dataEnc) + dec.SetPacketDecoder(id, dataDec) } // Unit Tests func TestTransport_Creation(t *testing.T) { logger := zerolog.Nop() transport := NewTransport(logger) - + if transport == nil { t.Fatal("Transport should not be nil") } @@ -351,10 +349,10 @@ func TestTransport_Creation(t *testing.T) { func TestTransport_SetIdTarget(t *testing.T) { transport, _ := createTestTransport(t) - + transport.SetIdTarget(100, "TEST_BOARD") transport.SetIdTarget(200, "ANOTHER_BOARD") - + // Access the internal map to verify if target := transport.idToTarget[100]; target != abstraction.TransportTarget("TEST_BOARD") { t.Errorf("Expected TEST_BOARD, got %s", target) @@ -366,10 +364,10 @@ func TestTransport_SetIdTarget(t *testing.T) { func TestTransport_SetTargetIp(t *testing.T) { transport, _ := createTestTransport(t) - + transport.SetTargetIp("192.168.1.100", "TEST_BOARD") transport.SetTargetIp("192.168.1.101", "ANOTHER_BOARD") - + // Access the internal map to verify if target := transport.ipToTarget["192.168.1.100"]; target != abstraction.TransportTarget("TEST_BOARD") { t.Errorf("Expected TEST_BOARD, got %s", target) @@ -585,15 +583,15 @@ func TestHandleUDPPacket_Success(t *testing.T) { // Integration Tests func TestTransport_ClientServerConnection(t *testing.T) { transport, api := createTestTransport(t) - + // Setup board configuration boardIP := "127.0.0.1" boardPort := getAvailablePort(t) target := abstraction.TransportTarget("TEST_BOARD") - + transport.SetTargetIp(boardIP, target) transport.SetIdTarget(100, target) - + // Create and start mock board server mockBoard := NewMockBoardServer(boardPort) err := mockBoard.Start() @@ -601,23 +599,23 @@ func TestTransport_ClientServerConnection(t *testing.T) { t.Fatalf("Failed to start mock board: %v", err) } defer mockBoard.Stop() - + // Configure client clientAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("Failed to resolve client address: %v", err) } - + clientConfig := tcp.NewClientConfig(clientAddr) clientConfig.TryReconnect = false // Don't retry for this test - + // Start client connection in goroutine clientDone := make(chan error, 1) go func() { err := transport.HandleClient(clientConfig, boardPort) clientDone <- err }() - + // Ensure cleanup defer func() { mockBoard.Stop() @@ -628,7 +626,7 @@ func TestTransport_ClientServerConnection(t *testing.T) { // Client should exit when board stops } }() - + // Wait for connection err = waitForCondition(func() bool { return mockBoard.GetConnectionCount() > 0 @@ -636,7 +634,7 @@ func TestTransport_ClientServerConnection(t *testing.T) { if err != nil { t.Fatal(err) } - + // Verify connection update was sent err = waitForCondition(func() bool { updates := api.GetConnectionUpdates() @@ -645,10 +643,10 @@ func TestTransport_ClientServerConnection(t *testing.T) { if err != nil { t.Fatal(err) } - + // Stop the board to trigger disconnection mockBoard.Stop() - + // Wait for client to detect disconnection select { case err := <-clientDone: @@ -659,7 +657,7 @@ func TestTransport_ClientServerConnection(t *testing.T) { case <-time.After(2 * time.Second): t.Fatal("Client should have detected disconnection") } - + // Verify disconnection update err = waitForCondition(func() bool { updates := api.GetConnectionUpdates() @@ -672,16 +670,16 @@ func TestTransport_ClientServerConnection(t *testing.T) { func TestTransport_PacketSending(t *testing.T) { transport, api := createTestTransport(t) - + // Setup boardIP := "127.0.0.1" boardPort := getAvailablePort(t) target := abstraction.TransportTarget("TEST_BOARD") packetID := abstraction.PacketId(100) - + transport.SetTargetIp(boardIP, target) transport.SetIdTarget(packetID, target) - + // Create mock board mockBoard := NewMockBoardServer(boardPort) err := mockBoard.Start() @@ -689,18 +687,18 @@ func TestTransport_PacketSending(t *testing.T) { t.Fatalf("Failed to start mock board: %v", err) } defer mockBoard.Stop() - + // Start client clientAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") clientConfig := tcp.NewClientConfig(clientAddr) clientConfig.TryReconnect = false - + clientDone := make(chan struct{}) go func() { defer close(clientDone) transport.HandleClient(clientConfig, boardPort) }() - + // Ensure cleanup defer func() { mockBoard.Stop() @@ -709,25 +707,25 @@ func TestTransport_PacketSending(t *testing.T) { case <-time.After(1 * time.Second): } }() - + // Wait for connection err = waitForCondition(func() bool { - updates := api.GetConnectionUpdates() - return len(updates) > 0 && updates[len(updates)-1].Target == target && updates[len(updates)-1].IsConnected + updates := api.GetConnectionUpdates() + return len(updates) > 0 && updates[len(updates)-1].Target == target && updates[len(updates)-1].IsConnected }, 2*time.Second, "Should establish connection") if err != nil { t.Fatal(err) } - + // Create and send packet testPacket := data.NewPacket(packetID) testPacket.SetTimestamp(time.Now()) - + err = transport.SendMessage(NewPacketMessage(testPacket)) if err != nil { t.Fatalf("Failed to send packet: %v", err) } - + // Verify packet was received by board err = waitForCondition(func() bool { packets := mockBoard.GetReceivedPackets() @@ -736,7 +734,7 @@ func TestTransport_PacketSending(t *testing.T) { if err != nil { t.Fatal(err) } - + // Verify no error notifications notifications := api.GetNotifications() for _, notification := range notifications { @@ -748,16 +746,16 @@ func TestTransport_PacketSending(t *testing.T) { func TestTransport_UnknownTarget(t *testing.T) { transport, api := createTestTransport(t) - + // Try to send packet to unknown target unknownPacket := data.NewPacket(999) // Unknown packet ID unknownPacket.SetTimestamp(time.Now()) - + err := transport.SendMessage(NewPacketMessage(unknownPacket)) if err == nil { t.Fatal("Expected error when sending to unknown target") } - + // Should be ErrUnrecognizedId var unrecognizedErr ErrUnrecognizedId if !ErrorAs(err, &unrecognizedErr) { @@ -765,7 +763,7 @@ func TestTransport_UnknownTarget(t *testing.T) { } else if unrecognizedErr.Id != abstraction.PacketId(999) { t.Errorf("Expected packet ID 999, got %d", unrecognizedErr.Id) } - + // Verify error notification err = waitForCondition(func() bool { notifications := api.GetNotifications() @@ -782,43 +780,43 @@ func TestTransport_UnknownTarget(t *testing.T) { func TestTransport_ReconnectionBehavior(t *testing.T) { transport, api := createTestTransport(t) - + // Setup boardIP := "127.0.0.1" boardPort := getAvailablePort(t) target := abstraction.TransportTarget("RECONNECT_BOARD") - + transport.SetTargetIp(boardIP, target) transport.SetIdTarget(100, target) - + // Create mock board mockBoard := NewMockBoardServer(boardPort) err := mockBoard.Start() if err != nil { t.Fatalf("Failed to start mock board: %v", err) } - + // Configure client with fast reconnection for testing clientAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:0") clientConfig := tcp.NewClientConfig(clientAddr) clientConfig.TryReconnect = true clientConfig.MaxConnectionRetries = 0 // Infinite retries clientConfig.ConnectionBackoffFunction = tcp.NewExponentialBackoff( - 10*time.Millisecond, // Fast for testing + 10*time.Millisecond, // Fast for testing 1.5, 100*time.Millisecond, ) - + // Start client with proper cleanup ctx, cancel := context.WithCancel(context.Background()) clientConfig.Context = ctx - + clientDone := make(chan struct{}) go func() { defer close(clientDone) transport.HandleClient(clientConfig, boardPort) }() - + // Ensure cleanup happens defer func() { cancel() @@ -830,7 +828,7 @@ func TestTransport_ReconnectionBehavior(t *testing.T) { t.Log("Warning: client goroutine did not finish within timeout") } }() - + // Wait for initial connection err = waitForCondition(func() bool { return mockBoard.GetConnectionCount() > 0 @@ -838,7 +836,7 @@ func TestTransport_ReconnectionBehavior(t *testing.T) { if err != nil { t.Fatal(err) } - + // Verify connection update err = waitForCondition(func() bool { updates := api.GetConnectionUpdates() @@ -847,10 +845,10 @@ func TestTransport_ReconnectionBehavior(t *testing.T) { if err != nil { t.Fatal(err) } - + // Simulate board restart mockBoard.Stop() - + // Wait for disconnection detection err = waitForCondition(func() bool { updates := api.GetConnectionUpdates() @@ -864,14 +862,14 @@ func TestTransport_ReconnectionBehavior(t *testing.T) { if err != nil { t.Fatal(err) } - + // Restart board mockBoard = NewMockBoardServer(boardPort) err = mockBoard.Start() if err != nil { t.Fatalf("Failed to restart mock board: %v", err) } - + // Wait for reconnection err = waitForCondition(func() bool { return mockBoard.GetConnectionCount() > 0 @@ -879,7 +877,7 @@ func TestTransport_ReconnectionBehavior(t *testing.T) { if err != nil { t.Fatal(err) } - + // Verify reconnection update err = waitForCondition(func() bool { updates := api.GetConnectionUpdates() @@ -1001,76 +999,6 @@ func TestHandleUDPServer_Dispatches(t *testing.T) { } } -func TestHandleFileWriteRead_WithRealTFTP(t *testing.T) { - readHandler := func(filename string, rf io.ReaderFrom) error { - _, err := rf.ReadFrom(bytes.NewBufferString("from-server")) - return err - } - writeBuf := &bytes.Buffer{} - writeHandler := func(filename string, wt io.WriterTo) error { - _, err := wt.WriteTo(writeBuf) - return err - } - server := tftpv3.NewServer(readHandler, writeHandler) - addr := fmt.Sprintf("127.0.0.1:%d", getAvailableUDPPort(t)) - go func() { - _ = server.ListenAndServe(addr) - }() - defer server.Shutdown() - time.Sleep(20 * time.Millisecond) - - client, err := tftp.NewClient(addr) - if err != nil { - t.Fatalf("failed to create tftp client: %v", err) - } - - tr := NewTransport(defaultLogger()).WithTFTP(client) - tr.SetAPI(NewTestTransportAPI()) - - if err := tr.handleFileWrite(NewFileWriteMessage("file.bin", bytes.NewBufferString("hello"))); err != nil { - t.Fatalf("handleFileWrite error: %v", err) - } - if writeBuf.String() != "hello" { - t.Fatalf("expected written data 'hello', got %q", writeBuf.String()) - } - - out := &bytes.Buffer{} - if err := tr.handleFileRead(NewFileReadMessage("file.bin", out)); err != nil { - t.Fatalf("handleFileRead error: %v", err) - } - if out.String() != "from-server" { - t.Fatalf("expected read data 'from-server', got %q", out.String()) - } -} - -func TestHandleFileWriteRead_ErrorPath(t *testing.T) { - // Point to an unused UDP port to force WriteFile/ReadFile errors. - addr := fmt.Sprintf("127.0.0.1:%d", getAvailableUDPPort(t)) - client, err := tftp.NewClient(addr, tftp.WithTimeout(50*time.Millisecond), tftp.WithRetries(1)) - if err != nil { - t.Fatalf("failed to create tftp client: %v", err) - } - - tr := NewTransport(defaultLogger()).WithTFTP(client) - api := NewTestTransportAPI() - tr.SetAPI(api) - - if err := tr.handleFileWrite(NewFileWriteMessage("file.bin", bytes.NewBufferString("hello"))); err == nil { - t.Fatalf("expected error writing to unreachable TFTP server") - } - if err := waitForCondition(func() bool { return len(api.GetNotifications()) > 0 }, time.Second, "error notification"); err != nil { - t.Fatalf("expected error notification") - } - - api.Reset() - if err := tr.handleFileRead(NewFileReadMessage("file.bin", &bytes.Buffer{})); err == nil { - t.Fatalf("expected error reading from unreachable TFTP server") - } - if err := waitForCondition(func() bool { return len(api.GetNotifications()) > 0 }, time.Second, "error notification"); err != nil { - t.Fatalf("expected error notification") - } -} - func TestHandleSniffer_Dispatches(t *testing.T) { tr, api := createTestTransport(t) @@ -1152,4 +1080,4 @@ func ErrorAs(err error, target interface{}) bool { } } return false -} \ No newline at end of file +}