| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- package via
- import (
- "encoding/binary"
- "strings"
- "testing"
- )
- // emptyVialPage is what get_supported returns once the list runs out: the whole
- // report is 0xFF padding and no ID is greater than the cursor. A list ends
- // because the firmware says so, not because the host ran out of answers.
- func emptyVialPage() []byte {
- buf := make([]byte, 32)
- buf[0] = lightingGet
- buf[1] = vialrgbGetSupported
- for i := 2; i < len(buf); i++ {
- buf[i] = 0xFF
- }
- return buf
- }
- // vialVersionResponse is a Vial keyboard-ID answer: the prefix stays in byte 0
- // and the version fills bytes 1 to 4, because Vial's handlers take msg = &data[1].
- func vialVersionResponse(version uint32) []byte {
- buf := make([]byte, 32)
- buf[0] = vialPrefix
- binary.LittleEndian.PutUint32(buf[1:5], version)
- return buf
- }
- // unhandledResponse is stock QMK's answer to a command its switch does not know:
- // raw_hid_receive ends with `default: { *command_id = id_unhandled; }`.
- func unhandledResponse() []byte {
- buf := make([]byte, 32)
- buf[0] = byte(Unhandled)
- return buf
- }
- // echoResponse is a keyboard that hands the request straight back. Nothing in
- // Vial's or QMK's source rules it out, and it is the answer that would make a
- // prefix probe believe anything, so it has to read as "not Vial".
- func echoResponse() []byte {
- buf := make([]byte, 32)
- buf[0] = vialPrefix
- buf[1] = vialGetKeyboardID
- return buf
- }
- func TestVialVersionReadsTheVersionOffAVialKeyboard(t *testing.T) {
- transport := &fakeTransport{queue: [][]byte{vialVersionResponse(0x00000006)}}
- protocol := Protocol{handle: transport}
- version, isVial, err := protocol.VialVersion()
- if err != nil {
- t.Fatalf("VialVersion() error = %v", err)
- }
- if !isVial {
- t.Error("VialVersion() = not Vial, want a keyboard that answered with a version")
- }
- if version != 6 {
- t.Errorf("VialVersion() = %d, want 6", version)
- }
- // The probe has to be the prefixed command, and the rest of the report zeroed,
- // so that an echo cannot look like an answer.
- sent := transport.reports[0]
- if sent[0] != vialPrefix || sent[1] != vialGetKeyboardID {
- t.Errorf("request = %v, want the Vial prefix and the keyboard-ID command", sent[:2])
- }
- for i := 2; i < len(sent); i++ {
- if sent[i] != 0 {
- t.Errorf("request byte %d = %d, want 0 so that an echo cannot pass for a version", i, sent[i])
- }
- }
- }
- // Stock QMK marks a command it does not know, and that is a keyboard that is not
- // Vial rather than a failure to talk to it.
- func TestVialVersionTreatsAnUnhandledCommandAsNotVial(t *testing.T) {
- transport := &fakeTransport{queue: [][]byte{unhandledResponse()}}
- protocol := Protocol{handle: transport}
- version, isVial, err := protocol.VialVersion()
- if err != nil {
- t.Fatalf("VialVersion() error = %v, want the unhandled marker read as an answer", err)
- }
- if isVial || version != 0 {
- t.Errorf("VialVersion() = %d, %t, want 0, false", version, isVial)
- }
- }
- // A keyboard that echoes the request answers with a prefix and a zero. A version
- // is never zero, so this must not read as Vial.
- func TestVialVersionDoesNotTrustAnEcho(t *testing.T) {
- transport := &fakeTransport{queue: [][]byte{echoResponse()}}
- protocol := Protocol{handle: transport}
- _, isVial, err := protocol.VialVersion()
- if err != nil {
- t.Fatalf("VialVersion() error = %v", err)
- }
- if isVial {
- t.Error("VialVersion() = Vial on an echoed request, which carries no version")
- }
- }
- // The supported list is a run of u16 from byte 2, padded to the end of the
- // report with 0xFF. Reading the padding as an ID would put a name on a number
- // the firmware never said was there.
- func TestVialEffectIDsStopsAtTheFirmwarePadding(t *testing.T) {
- page := make([]byte, 32)
- page[0] = lightingGet
- page[1] = vialrgbGetSupported
- for i, id := range []uint16{1, 4, 5} {
- binary.LittleEndian.PutUint16(page[2+i*2:], id)
- }
- // get_supported memsets the rest of the report to 0xFF, which is the padding
- // the reader has to stop at.
- for i := 8; i < len(page); i++ {
- page[i] = 0xFF
- }
- transport := &fakeTransport{queue: [][]byte{page, emptyVialPage()}}
- protocol := Protocol{handle: transport}
- ids, err := protocol.VialEffectIDs()
- if err != nil {
- t.Fatalf("VialEffectIDs() error = %v", err)
- }
- want := []uint16{1, 4, 5}
- if len(ids) != len(want) {
- t.Fatalf("VialEffectIDs() = %v, want %v", ids, want)
- }
- for i := range want {
- if ids[i] != want[i] {
- t.Errorf("VialEffectIDs()[%d] = %d, want %d", i, ids[i], want[i])
- }
- }
- }
- // get_supported fills one report at a time from a cursor the request carries, so
- // a list longer than one page takes more than one round trip.
- func TestVialEffectIDsFollowsTheCursor(t *testing.T) {
- first := make([]byte, 32)
- first[0] = lightingGet
- first[1] = vialrgbGetSupported
- binary.LittleEndian.PutUint16(first[2:], 40)
- second := make([]byte, 32)
- second[0] = lightingGet
- second[1] = vialrgbGetSupported
- binary.LittleEndian.PutUint16(second[2:], 45)
- for i := 4; i < len(second); i++ {
- second[i] = 0xFF
- }
- transport := &fakeTransport{queue: [][]byte{first, second, emptyVialPage()}}
- protocol := Protocol{handle: transport}
- ids, err := protocol.VialEffectIDs()
- if err != nil {
- t.Fatalf("VialEffectIDs() error = %v", err)
- }
- if len(ids) != 2 || ids[0] != 40 || ids[1] != 45 {
- t.Errorf("VialEffectIDs() = %v, want [40 45]", ids)
- }
- // The second request has to carry what the first page ended on, or the
- // firmware would send the same page again.
- cursor := binary.LittleEndian.Uint16(transport.reports[1][2:4])
- if cursor != 40 {
- t.Errorf("second request cursor = %d, want 40", cursor)
- }
- }
- // Stock QMK's rgb_matrix get has no default case, so a value ID it does not know
- // leaves the report unmarked rather than answering 0xFF. That is why the effect
- // IDs must not be asked for before the firmware is known to be Vial: on a stock
- // board the answer is an echo, and an echo of this request is bytes that can be
- // read as a list.
- func TestVialEffectIDsRefusesAListThatDoesNotAscend(t *testing.T) {
- page := make([]byte, 32)
- page[0] = lightingGet
- page[1] = vialrgbGetSupported
- binary.LittleEndian.PutUint16(page[2:], 7)
- binary.LittleEndian.PutUint16(page[4:], 3)
- transport := &fakeTransport{queue: [][]byte{page, emptyVialPage()}}
- protocol := Protocol{handle: transport}
- ids, err := protocol.VialEffectIDs()
- if err != nil {
- t.Fatalf("VialEffectIDs() error = %v, want a list read and then rejected", err)
- }
- if ids != nil {
- t.Errorf("VialEffectIDs() = %v, want nothing from a list that does not ascend", ids)
- }
- }
- // A firmware that echoes the request answers the second page with the cursor it
- // was given, and a cursor that cannot advance would loop forever. That is the case
- // the guard is for: an echoing keyboard is not a Vial keyboard, and the loop has
- // to end rather than keep asking.
- func TestVialEffectIDsStopsWhenTheFirmwareEchoesTheCursor(t *testing.T) {
- page := make([]byte, 32)
- page[0] = lightingGet
- page[1] = vialrgbGetSupported
- binary.LittleEndian.PutUint16(page[2:], 1)
- binary.LittleEndian.PutUint16(page[4:], 4)
- for i := 6; i < len(page); i++ {
- page[i] = 0xFF
- }
- transport := &fakeTransport{queue: [][]byte{page}}
- protocol := Protocol{handle: transport}
- _, err := protocol.VialEffectIDs()
- if err == nil {
- t.Fatal("VialEffectIDs() = nil error, want a failure when the cursor cannot advance")
- }
- if !strings.Contains(err.Error(), "advance") {
- t.Errorf("error = %q, want it to say the list did not advance", err)
- }
- }
|