package usbip import ( "bytes" "encoding/binary" "testing" ) // The USB/IP wire format is fixed: a 20-byte basic header followed by a // 28-byte body. Any drift here desynchronises the stream permanently, so the // sizes are pinned. func TestWireFormatSizes(t *testing.T) { var buf bytes.Buffer if err := WriteURBHeader(&buf, &URBHeader{}); err != nil { t.Fatalf("WriteURBHeader: %v", err) } if buf.Len() != 20 { t.Errorf("URB header is %d bytes, want 20", buf.Len()) } buf.Reset() if err := WriteCmdSubmit(&buf, &CmdSubmitBody{}); err != nil { t.Fatalf("WriteCmdSubmit: %v", err) } if buf.Len() != 28 { t.Errorf("CMD_SUBMIT body is %d bytes, want 28", buf.Len()) } buf.Reset() if err := WriteRetSubmit(&buf, &RetSubmitBody{}); err != nil { t.Fatalf("WriteRetSubmit: %v", err) } if buf.Len() != 28 { t.Errorf("RET_SUBMIT body is %d bytes, want 28", buf.Len()) } buf.Reset() if err := WriteRetUnlink(&buf, &RetUnlinkBody{}); err != nil { t.Fatalf("WriteRetUnlink: %v", err) } if buf.Len() != 28 { t.Errorf("RET_UNLINK body is %d bytes, want 28", buf.Len()) } } func TestBuildRetSubmitInDirection(t *testing.T) { payload := []byte{0x01, 0x02, 0x03, 0x04} msg, err := BuildRetSubmit(42, 0x00030002, DirIn, 1, 0, uint32(len(payload)), payload) if err != nil { t.Fatalf("BuildRetSubmit: %v", err) } if len(msg) != 48+len(payload) { t.Fatalf("message is %d bytes, want %d", len(msg), 48+len(payload)) } hdr, err := ReadURBHeader(bytes.NewReader(msg)) if err != nil { t.Fatalf("ReadURBHeader: %v", err) } if hdr.Command != RetSubmit { t.Errorf("command = 0x%08x, want RET_SUBMIT", hdr.Command) } if hdr.SeqNum != 42 { t.Errorf("seqnum = %d, want 42", hdr.SeqNum) } if hdr.Direction != DirIn { t.Errorf("direction = %d, want IN", hdr.Direction) } body, err := ReadRetSubmit(bytes.NewReader(msg[20:])) if err != nil { t.Fatalf("ReadRetSubmit: %v", err) } if body.ActualLength != uint32(len(payload)) { t.Errorf("actual_length = %d, want %d", body.ActualLength, len(payload)) } if body.NumberOfPackets != 0xFFFFFFFF { t.Errorf("number_of_packets = %d, want 0xFFFFFFFF for non-ISO", body.NumberOfPackets) } if !bytes.Equal(msg[48:], payload) { t.Errorf("payload = %x, want %x", msg[48:], payload) } } // actual_length must be reported for OUT transfers too. The kernel UVC driver // checks it: a VS_PROBE SET_CUR that reports 0 instead of 26 fails the probe. func TestBuildRetSubmitOutReportsActualLength(t *testing.T) { msg, err := BuildRetSubmit(7, 1, DirOut, 0, 0, 26, nil) if err != nil { t.Fatalf("BuildRetSubmit: %v", err) } if len(msg) != 48 { t.Fatalf("OUT reply is %d bytes, want 48 (no payload)", len(msg)) } body, _ := ReadRetSubmit(bytes.NewReader(msg[20:])) if body.ActualLength != 26 { t.Errorf("actual_length = %d, want 26", body.ActualLength) } } // An IN reply carries no payload when the transfer failed, and the negative // status has to survive the unsigned round trip on the wire. func TestBuildRetSubmitErrorStatus(t *testing.T) { msg, err := BuildRetSubmit(9, 1, DirIn, 2, -32, 0, nil) if err != nil { t.Fatalf("BuildRetSubmit: %v", err) } if len(msg) != 48 { t.Fatalf("error reply is %d bytes, want 48", len(msg)) } status := int32(binary.BigEndian.Uint32(msg[20:24])) if status != -32 { t.Errorf("status = %d, want -32 (-EPIPE)", status) } } func TestBuildRetSubmitISO(t *testing.T) { descs := []ISOPacketDescriptor{ {Offset: 0, Length: 192, ActualLength: 192, Status: 0}, {Offset: 192, Length: 192, ActualLength: 100, Status: 0}, } packed := make([]byte, 292) // 192 + 100 actual bytes, packed without gaps msg, err := BuildRetSubmitISO(5, 1, DirIn, 1, 0, 292, packed, 1000, 2, 0, descs) if err != nil { t.Fatalf("BuildRetSubmitISO: %v", err) } wantLen := 48 + len(packed) + 2*16 if len(msg) != wantLen { t.Fatalf("ISO reply is %d bytes, want %d", len(msg), wantLen) } body, _ := ReadRetSubmit(bytes.NewReader(msg[20:])) if body.NumberOfPackets != 2 { t.Errorf("number_of_packets = %d, want 2", body.NumberOfPackets) } if body.StartFrame != 1000 { t.Errorf("start_frame = %d, want 1000", body.StartFrame) } // Descriptors follow the packed payload, big-endian. descOff := 48 + len(packed) var got ISOPacketDescriptor if err := binary.Read(bytes.NewReader(msg[descOff:]), binary.BigEndian, &got); err != nil { t.Fatalf("reading ISO descriptor: %v", err) } if got.Length != 192 || got.ActualLength != 192 { t.Errorf("first descriptor = %+v, want length 192 actual 192", got) } } func TestBusIDRoundTrip(t *testing.T) { tests := []string{"1-1", "1-4.3.2", "", "12345678901234567890123456789012"} for _, want := range tests { var arr [32]byte SetBusID(&arr, want) if got := GetBusID(arr); got != want { t.Errorf("GetBusID(SetBusID(%q)) = %q", want, got) } } } func TestSetBusIDTruncatesOverlongInput(t *testing.T) { var arr [32]byte SetBusID(&arr, "this-bus-id-is-far-longer-than-thirty-two-bytes") if got := len(GetBusID(arr)); got != 32 { t.Errorf("overlong bus ID produced %d bytes, want 32", got) } } func TestReadCmdSubmitParsesSetupPacket(t *testing.T) { var buf bytes.Buffer body := &CmdSubmitBody{ TransferBufferLen: 18, NumberOfPackets: 0, Interval: 0, // GET_DESCRIPTOR(device): bmRequestType=0x80 bRequest=0x06 wValue=0x0100 Setup: [8]byte{0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x12, 0x00}, } if err := WriteCmdSubmit(&buf, body); err != nil { t.Fatalf("WriteCmdSubmit: %v", err) } got, err := ReadCmdSubmit(&buf) if err != nil { t.Fatalf("ReadCmdSubmit: %v", err) } // Setup fields are little-endian even though the surrounding header is not. if wValue := binary.LittleEndian.Uint16(got.Setup[2:4]); wValue != 0x0100 { t.Errorf("wValue = 0x%04x, want 0x0100", wValue) } if wLength := binary.LittleEndian.Uint16(got.Setup[6:8]); wLength != 18 { t.Errorf("wLength = %d, want 18", wLength) } if got.TransferBufferLen != 18 { t.Errorf("transfer_buffer_length = %d, want 18", got.TransferBufferLen) } } func TestBuildImportReply(t *testing.T) { desc := &DeviceDescriptor{BusNum: 1, DevNum: 4, Speed: SpeedHigh} SetBusID(&desc.BusID, "1-4") ok, err := BuildImportReply(0, desc) if err != nil { t.Fatalf("BuildImportReply: %v", err) } if len(ok) != 8+312 { t.Errorf("successful reply is %d bytes, want %d", len(ok), 8+312) } // A failure carries only the header — no descriptor follows. fail, err := BuildImportReply(1, nil) if err != nil { t.Fatalf("BuildImportReply(1): %v", err) } if len(fail) != 8 { t.Errorf("failure reply is %d bytes, want 8", len(fail)) } hdr, _ := ReadOpHeader(bytes.NewReader(fail)) if hdr.Status != 1 { t.Errorf("status = %d, want 1", hdr.Status) } if hdr.Version != ProtocolVersion { t.Errorf("version = 0x%04x, want 0x%04x", hdr.Version, ProtocolVersion) } }