//go:build linux package transport import ( "encoding/binary" "net" "reflect" "syscall" "testing" "omnisocketgo/cmd/internal/latencylog" "omnisocketgo/cmd/internal/protocol" ) func TestLinuxTimestampingRecordsKernelEvents(t *testing.T) { tests := []struct { name string msg protocol.Message }{ { name: "text", msg: protocol.Message{ Type: protocol.MessageTypeText, ID: 41, From: "peer-a", To: "peer-b", Body: []byte("hello over tcp"), }, }, { name: "file", msg: protocol.Message{ Type: protocol.MessageTypeFile, ID: 42, From: "peer-a", To: "peer-b", FileName: "payload.bin", Body: []byte{0x00, 0x01, 0x02, 0xff}, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { clientConn, serverConn := newTCPPair(t) senderLogger := &recordingLogger{} receiverLogger := &recordingLogger{} sender, err := NewTCPConn( clientConn, WithLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"), ) if err != nil { t.Fatalf("NewTCPConn(sender) error = %v", err) } receiver, err := NewTCPConn( serverConn, WithLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"), ) if err != nil { t.Fatalf("NewTCPConn(receiver) error = %v", err) } t.Cleanup(func() { _ = sender.Close() _ = receiver.Close() }) sendErr := make(chan error, 1) go func() { sendErr <- sender.Send(tt.msg) }() got, err := receiver.Receive() if err != nil { t.Fatalf("Receive() error = %v", err) } if err := <-sendErr; err != nil { t.Fatalf("Send() error = %v", err) } if !reflect.DeepEqual(got, tt.msg) { t.Fatalf("message mismatch: got %+v want %+v", got, tt.msg) } assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, tt.msg.ID) assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, tt.msg.ID) assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, tt.msg.ID) }) } } func TestParseTXTimestampSocketControlMessagesExtractsEventKindAndID(t *testing.T) { tests := []struct { name string kind uint32 wantEvent string }{ { name: "sched", kind: linuxSCMTstampSched, wantEvent: latencylog.EventATXSched, }, { name: "software", kind: linuxSCMTstampSnd, wantEvent: latencylog.EventATXSoftware, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { controlMessages := []syscall.SocketControlMessage{ makeTimestampSocketControlMessage(1_700_000_000_123_456_789), makeSocketExtendedErrControlMessage(tt.kind, 42), } event, ok := parseTXTimestampSocketControlMessages(controlMessages) if !ok { t.Fatal("parseTXTimestampSocketControlMessages() ok = false, want true") } if event.EventName != tt.wantEvent { t.Fatalf("event name = %q, want %q", event.EventName, tt.wantEvent) } if event.TSUnixNano != 1_700_000_000_123_456_789 { t.Fatalf("timestamp = %d, want %d", event.TSUnixNano, int64(1_700_000_000_123_456_789)) } if event.EEInfo != tt.kind { t.Fatalf("ee_info = %d, want %d", event.EEInfo, tt.kind) } if event.EEData != 42 { t.Fatalf("ee_data = %d, want 42", event.EEData) } }) } } func TestSelectTXTimestampEventsPrefersExactFinalChunkID(t *testing.T) { events := []observedTXTimestampEvent{ {Event: txTimestampEvent{EventName: latencylog.EventATXSched, TSUnixNano: 100, EEData: 7}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSoftware, TSUnixNano: 110, EEData: 7}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSched, TSUnixNano: 200, EEData: 9}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSoftware, TSUnixNano: 210, EEData: 9}}, } selection := selectTXTimestampEvents(events, 9, true) if !selection.HasEvent { t.Fatal("selection.HasEvent = false, want true") } if selection.SelectedID != 9 { t.Fatalf("SelectedID = %d, want 9", selection.SelectedID) } if got := selection.Timestamps[latencylog.EventATXSched]; got != 200 { t.Fatalf("selected sched = %d, want 200", got) } if got := selection.Timestamps[latencylog.EventATXSoftware]; got != 210 { t.Fatalf("selected software = %d, want 210", got) } } func TestSelectTXTimestampEventsFallsBackToHighestObservedID(t *testing.T) { events := []observedTXTimestampEvent{ {Event: txTimestampEvent{EventName: latencylog.EventATXSched, TSUnixNano: 100, EEData: 11}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSoftware, TSUnixNano: 120, EEData: 11}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSched, TSUnixNano: 200, EEData: 15}}, {Event: txTimestampEvent{EventName: latencylog.EventATXSoftware, TSUnixNano: 220, EEData: 15}}, } selection := selectTXTimestampEvents(events, 20, true) if !selection.HasEvent { t.Fatal("selection.HasEvent = false, want true") } if selection.SelectedID != 15 { t.Fatalf("SelectedID = %d, want 15", selection.SelectedID) } if got := selection.Timestamps[latencylog.EventATXSched]; got != 200 { t.Fatalf("selected sched = %d, want 200", got) } if got := selection.Timestamps[latencylog.EventATXSoftware]; got != 220 { t.Fatalf("selected software = %d, want 220", got) } } func TestLinuxTimestampingDebugLoggerCapturesChunkAndErrqueueEvents(t *testing.T) { clientConn, serverConn := newTCPPair(t) setTCPWriteBuffer(t, clientConn, 10*1024*1024) debugLogger := &recordingTXTimestampDebugLogger{} senderLogger := &recordingLogger{} receiverLogger := &recordingLogger{} sender, err := NewTCPConn( clientConn, WithLogger(senderLogger, latencylog.NodeRolePeer, "peer-a"), WithTXTimestampDebugLogger(debugLogger), ) if err != nil { t.Fatalf("NewTCPConn(sender) error = %v", err) } receiver, err := NewTCPConn( serverConn, WithLogger(receiverLogger, latencylog.NodeRolePeer, "peer-b"), ) if err != nil { t.Fatalf("NewTCPConn(receiver) error = %v", err) } t.Cleanup(func() { _ = sender.Close() _ = receiver.Close() }) body := make([]byte, 1<<20) for i := range body { body[i] = byte(i % 251) } msg := protocol.Message{ Type: protocol.MessageTypeFile, ID: 99, From: "peer-a", To: "peer-b", FileName: "payload.bin", Body: body, } sendErr := make(chan error, 1) go func() { sendErr <- sender.Send(msg) }() got, err := receiver.Receive() if err != nil { t.Fatalf("Receive() error = %v", err) } if err := <-sendErr; err != nil { t.Fatalf("Send() error = %v", err) } if !reflect.DeepEqual(got, msg) { t.Fatalf("message mismatch: got %+v want %+v", got, msg) } assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSched, msg.ID) assertHasEvent(t, senderLogger.Events(), latencylog.EventATXSoftware, msg.ID) assertHasEvent(t, receiverLogger.Events(), latencylog.EventBRXSoftware, msg.ID) sendChunkRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeSendChunk) errqueueRecords := debugRecordsByType(debugLogger.Records(), txTimestampDebugRecordTypeErrqueueEvent) if len(sendChunkRecords) == 0 { t.Fatal("send_chunk debug records = 0, want at least 1") } if len(errqueueRecords) == 0 { t.Fatal("errqueue_event debug records = 0, want at least 1") } finalChunkRecord := sendChunkRecords[len(sendChunkRecords)-1] if finalChunkRecord.ExpectedTXID == nil { t.Fatal("final send_chunk expected_tx_id = nil, want non-nil") } finalExpectedTXID := *finalChunkRecord.ExpectedTXID selectedRecords := selectedErrqueueRecords(errqueueRecords) if len(selectedRecords) == 0 { t.Fatal("selected errqueue debug records = 0, want at least 1") } highestObservedID := uint32(0) haveHighestObservedID := false haveExactFinalID := false for _, record := range errqueueRecords { if record.EEData == nil { continue } if !haveHighestObservedID || *record.EEData > highestObservedID { highestObservedID = *record.EEData haveHighestObservedID = true } if *record.EEData == finalExpectedTXID && isBusinessTXTimestampRecord(record) { haveExactFinalID = true } } if !haveHighestObservedID { t.Fatal("highestObservedID missing, want at least one ee_data") } wantSelectedID := highestObservedID if haveExactFinalID { wantSelectedID = finalExpectedTXID } for _, record := range selectedRecords { if record.EEData == nil { t.Fatalf("selected record missing ee_data: %+v", record) } if *record.EEData != wantSelectedID { t.Fatalf("selected ee_data = %d, want %d", *record.EEData, wantSelectedID) } } selectedByEventName := make(map[string]int64, len(selectedRecords)) for _, record := range selectedRecords { if record.TSUnixNano == nil { t.Fatalf("selected record missing timestamp: %+v", record) } selectedByEventName[record.EventName] = *record.TSUnixNano } senderEventsByName := make(map[string]int64) for _, event := range senderLogger.Events() { if event.MessageID != msg.ID { continue } if !isBusinessTXTimestampEventName(event.Event) { continue } senderEventsByName[event.Event] = event.TsUnixNano } for eventName, selectedTS := range selectedByEventName { if senderEventsByName[eventName] != selectedTS { t.Fatalf("sender latency event %s = %d, want %d from selected debug record", eventName, senderEventsByName[eventName], selectedTS) } } } func newTCPPair(t *testing.T) (net.Conn, net.Conn) { t.Helper() listener, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("net.Listen() error = %v", err) } type acceptResult struct { conn net.Conn err error } accepted := make(chan acceptResult, 1) go func() { conn, acceptErr := listener.Accept() accepted <- acceptResult{conn: conn, err: acceptErr} }() clientConn, err := net.Dial("tcp", listener.Addr().String()) if err != nil { _ = listener.Close() t.Fatalf("net.Dial() error = %v", err) } result := <-accepted if err := listener.Close(); err != nil { t.Fatalf("listener.Close() error = %v", err) } if result.err != nil { _ = clientConn.Close() t.Fatalf("listener.Accept() error = %v", result.err) } return clientConn, result.conn } func assertHasEvent(t *testing.T, events []latencylog.Event, wantEvent string, wantMessageID uint64) { t.Helper() for _, event := range events { if event.Event == wantEvent && event.MessageID == wantMessageID { if event.TsUnixNano <= 0 { t.Fatalf("event %s timestamp must be positive: %+v", wantEvent, event) } return } } t.Fatalf("missing event %s for message %d in %+v", wantEvent, wantMessageID, events) } func makeTimestampSocketControlMessage(tsUnixNano int64) syscall.SocketControlMessage { const timespec64Size = 16 data := make([]byte, timespec64Size*3) sec := uint64(tsUnixNano / int64(1e9)) nsec := uint64(tsUnixNano % int64(1e9)) binary.NativeEndian.PutUint64(data[:8], sec) binary.NativeEndian.PutUint64(data[8:16], nsec) return syscall.SocketControlMessage{ Header: syscall.Cmsghdr{ Level: syscall.SOL_SOCKET, Type: linuxSCMTimestampingNew, }, Data: data, } } func makeSocketExtendedErrControlMessage(kind, id uint32) syscall.SocketControlMessage { data := make([]byte, 16) data[4] = linuxSOEEOriginTimestamping binary.NativeEndian.PutUint32(data[8:12], kind) binary.NativeEndian.PutUint32(data[12:16], id) return syscall.SocketControlMessage{ Header: syscall.Cmsghdr{ Level: syscall.SOL_IP, Type: syscall.IP_RECVERR, }, Data: data, } } func setTCPWriteBuffer(t *testing.T, conn net.Conn, size int) { t.Helper() tcpConn, ok := conn.(*net.TCPConn) if !ok { t.Fatalf("conn type %T does not support SetWriteBuffer", conn) } if err := tcpConn.SetWriteBuffer(size); err != nil { t.Fatalf("SetWriteBuffer(%d) error = %v", size, err) } } func debugRecordsByType(records []TXTimestampDebugRecord, recordType string) []TXTimestampDebugRecord { filtered := make([]TXTimestampDebugRecord, 0, len(records)) for _, record := range records { if record.RecordType != recordType { continue } filtered = append(filtered, record) } return filtered } func selectedErrqueueRecords(records []TXTimestampDebugRecord) []TXTimestampDebugRecord { selected := make([]TXTimestampDebugRecord, 0, len(records)) for _, record := range records { if record.SelectedForLatency == nil || !*record.SelectedForLatency { continue } selected = append(selected, record) } return selected } func isBusinessTXTimestampRecord(record TXTimestampDebugRecord) bool { return record.EventName == latencylog.EventATXSched || record.EventName == latencylog.EventATXSoftware }