vial_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. package via
  2. import (
  3. "encoding/binary"
  4. "strings"
  5. "testing"
  6. )
  7. // emptyVialPage is what get_supported returns once the list runs out: the whole
  8. // report is 0xFF padding and no ID is greater than the cursor. A list ends
  9. // because the firmware says so, not because the host ran out of answers.
  10. func emptyVialPage() []byte {
  11. buf := make([]byte, 32)
  12. buf[0] = lightingGet
  13. buf[1] = vialrgbGetSupported
  14. for i := 2; i < len(buf); i++ {
  15. buf[i] = 0xFF
  16. }
  17. return buf
  18. }
  19. // vialVersionResponse is a Vial keyboard-ID answer: the prefix stays in byte 0
  20. // and the version fills bytes 1 to 4, because Vial's handlers take msg = &data[1].
  21. func vialVersionResponse(version uint32) []byte {
  22. buf := make([]byte, 32)
  23. buf[0] = vialPrefix
  24. binary.LittleEndian.PutUint32(buf[1:5], version)
  25. return buf
  26. }
  27. // unhandledResponse is stock QMK's answer to a command its switch does not know:
  28. // raw_hid_receive ends with `default: { *command_id = id_unhandled; }`.
  29. func unhandledResponse() []byte {
  30. buf := make([]byte, 32)
  31. buf[0] = byte(Unhandled)
  32. return buf
  33. }
  34. // echoResponse is a keyboard that hands the request straight back. Nothing in
  35. // Vial's or QMK's source rules it out, and it is the answer that would make a
  36. // prefix probe believe anything, so it has to read as "not Vial".
  37. func echoResponse() []byte {
  38. buf := make([]byte, 32)
  39. buf[0] = vialPrefix
  40. buf[1] = vialGetKeyboardID
  41. return buf
  42. }
  43. func TestVialVersionReadsTheVersionOffAVialKeyboard(t *testing.T) {
  44. transport := &fakeTransport{queue: [][]byte{vialVersionResponse(0x00000006)}}
  45. protocol := Protocol{handle: transport}
  46. version, isVial, err := protocol.VialVersion()
  47. if err != nil {
  48. t.Fatalf("VialVersion() error = %v", err)
  49. }
  50. if !isVial {
  51. t.Error("VialVersion() = not Vial, want a keyboard that answered with a version")
  52. }
  53. if version != 6 {
  54. t.Errorf("VialVersion() = %d, want 6", version)
  55. }
  56. // The probe has to be the prefixed command, and the rest of the report zeroed,
  57. // so that an echo cannot look like an answer.
  58. sent := transport.reports[0]
  59. if sent[0] != vialPrefix || sent[1] != vialGetKeyboardID {
  60. t.Errorf("request = %v, want the Vial prefix and the keyboard-ID command", sent[:2])
  61. }
  62. for i := 2; i < len(sent); i++ {
  63. if sent[i] != 0 {
  64. t.Errorf("request byte %d = %d, want 0 so that an echo cannot pass for a version", i, sent[i])
  65. }
  66. }
  67. }
  68. // Stock QMK marks a command it does not know, and that is a keyboard that is not
  69. // Vial rather than a failure to talk to it.
  70. func TestVialVersionTreatsAnUnhandledCommandAsNotVial(t *testing.T) {
  71. transport := &fakeTransport{queue: [][]byte{unhandledResponse()}}
  72. protocol := Protocol{handle: transport}
  73. version, isVial, err := protocol.VialVersion()
  74. if err != nil {
  75. t.Fatalf("VialVersion() error = %v, want the unhandled marker read as an answer", err)
  76. }
  77. if isVial || version != 0 {
  78. t.Errorf("VialVersion() = %d, %t, want 0, false", version, isVial)
  79. }
  80. }
  81. // A keyboard that echoes the request answers with a prefix and a zero. A version
  82. // is never zero, so this must not read as Vial.
  83. func TestVialVersionDoesNotTrustAnEcho(t *testing.T) {
  84. transport := &fakeTransport{queue: [][]byte{echoResponse()}}
  85. protocol := Protocol{handle: transport}
  86. _, isVial, err := protocol.VialVersion()
  87. if err != nil {
  88. t.Fatalf("VialVersion() error = %v", err)
  89. }
  90. if isVial {
  91. t.Error("VialVersion() = Vial on an echoed request, which carries no version")
  92. }
  93. }
  94. // The supported list is a run of u16 from byte 2, padded to the end of the
  95. // report with 0xFF. Reading the padding as an ID would put a name on a number
  96. // the firmware never said was there.
  97. func TestVialEffectIDsStopsAtTheFirmwarePadding(t *testing.T) {
  98. page := make([]byte, 32)
  99. page[0] = lightingGet
  100. page[1] = vialrgbGetSupported
  101. for i, id := range []uint16{1, 4, 5} {
  102. binary.LittleEndian.PutUint16(page[2+i*2:], id)
  103. }
  104. // get_supported memsets the rest of the report to 0xFF, which is the padding
  105. // the reader has to stop at.
  106. for i := 8; i < len(page); i++ {
  107. page[i] = 0xFF
  108. }
  109. transport := &fakeTransport{queue: [][]byte{page, emptyVialPage()}}
  110. protocol := Protocol{handle: transport}
  111. ids, err := protocol.VialEffectIDs()
  112. if err != nil {
  113. t.Fatalf("VialEffectIDs() error = %v", err)
  114. }
  115. want := []uint16{1, 4, 5}
  116. if len(ids) != len(want) {
  117. t.Fatalf("VialEffectIDs() = %v, want %v", ids, want)
  118. }
  119. for i := range want {
  120. if ids[i] != want[i] {
  121. t.Errorf("VialEffectIDs()[%d] = %d, want %d", i, ids[i], want[i])
  122. }
  123. }
  124. }
  125. // get_supported fills one report at a time from a cursor the request carries, so
  126. // a list longer than one page takes more than one round trip.
  127. func TestVialEffectIDsFollowsTheCursor(t *testing.T) {
  128. first := make([]byte, 32)
  129. first[0] = lightingGet
  130. first[1] = vialrgbGetSupported
  131. binary.LittleEndian.PutUint16(first[2:], 40)
  132. second := make([]byte, 32)
  133. second[0] = lightingGet
  134. second[1] = vialrgbGetSupported
  135. binary.LittleEndian.PutUint16(second[2:], 45)
  136. for i := 4; i < len(second); i++ {
  137. second[i] = 0xFF
  138. }
  139. transport := &fakeTransport{queue: [][]byte{first, second, emptyVialPage()}}
  140. protocol := Protocol{handle: transport}
  141. ids, err := protocol.VialEffectIDs()
  142. if err != nil {
  143. t.Fatalf("VialEffectIDs() error = %v", err)
  144. }
  145. if len(ids) != 2 || ids[0] != 40 || ids[1] != 45 {
  146. t.Errorf("VialEffectIDs() = %v, want [40 45]", ids)
  147. }
  148. // The second request has to carry what the first page ended on, or the
  149. // firmware would send the same page again.
  150. cursor := binary.LittleEndian.Uint16(transport.reports[1][2:4])
  151. if cursor != 40 {
  152. t.Errorf("second request cursor = %d, want 40", cursor)
  153. }
  154. }
  155. // Stock QMK's rgb_matrix get has no default case, so a value ID it does not know
  156. // leaves the report unmarked rather than answering 0xFF. That is why the effect
  157. // IDs must not be asked for before the firmware is known to be Vial: on a stock
  158. // board the answer is an echo, and an echo of this request is bytes that can be
  159. // read as a list.
  160. func TestVialEffectIDsRefusesAListThatDoesNotAscend(t *testing.T) {
  161. page := make([]byte, 32)
  162. page[0] = lightingGet
  163. page[1] = vialrgbGetSupported
  164. binary.LittleEndian.PutUint16(page[2:], 7)
  165. binary.LittleEndian.PutUint16(page[4:], 3)
  166. transport := &fakeTransport{queue: [][]byte{page, emptyVialPage()}}
  167. protocol := Protocol{handle: transport}
  168. ids, err := protocol.VialEffectIDs()
  169. if err != nil {
  170. t.Fatalf("VialEffectIDs() error = %v, want a list read and then rejected", err)
  171. }
  172. if ids != nil {
  173. t.Errorf("VialEffectIDs() = %v, want nothing from a list that does not ascend", ids)
  174. }
  175. }
  176. // A firmware that echoes the request answers the second page with the cursor it
  177. // was given, and a cursor that cannot advance would loop forever. That is the case
  178. // the guard is for: an echoing keyboard is not a Vial keyboard, and the loop has
  179. // to end rather than keep asking.
  180. func TestVialEffectIDsStopsWhenTheFirmwareEchoesTheCursor(t *testing.T) {
  181. page := make([]byte, 32)
  182. page[0] = lightingGet
  183. page[1] = vialrgbGetSupported
  184. binary.LittleEndian.PutUint16(page[2:], 1)
  185. binary.LittleEndian.PutUint16(page[4:], 4)
  186. for i := 6; i < len(page); i++ {
  187. page[i] = 0xFF
  188. }
  189. transport := &fakeTransport{queue: [][]byte{page}}
  190. protocol := Protocol{handle: transport}
  191. _, err := protocol.VialEffectIDs()
  192. if err == nil {
  193. t.Fatal("VialEffectIDs() = nil error, want a failure when the cursor cannot advance")
  194. }
  195. if !strings.Contains(err.Error(), "advance") {
  196. t.Errorf("error = %q, want it to say the list did not advance", err)
  197. }
  198. }