-
Notifications
You must be signed in to change notification settings - Fork 15
/
usbcommon.go
307 lines (258 loc) · 7.83 KB
/
usbcommon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/* ipp-usb - HTTP reverse proxy, backed by IPP-over-USB connection to device
*
* Copyright (C) 2020 and up by Alexander Pevzner ([email protected])
* See LICENSE for license terms and conditions
*
* Common types for USB
*/
package main
import (
"crypto/sha1"
"fmt"
"sort"
"strings"
)
// UsbAddr represents an USB device address
type UsbAddr struct {
Bus int // The bus on which the device was detected
Address int // The address of the device on the bus
}
// String returns a human-readable representation of UsbAddr
func (addr UsbAddr) String() string {
return fmt.Sprintf("Bus %.3d Device %.3d", addr.Bus, addr.Address)
}
// Less returns true, if addr is "less" that addr2, for sorting
func (addr UsbAddr) Less(addr2 UsbAddr) bool {
return addr.Bus < addr2.Bus ||
(addr.Bus == addr2.Bus && addr.Address < addr2.Address)
}
// UsbAddrList represents a list of USB addresses
//
// For faster lookup and comparable logging, address list
// is always sorted in acceding order. To maintain this
// invariant, never modify list directly, and use the provided
// (*UsbAddrList) Add() function
type UsbAddrList []UsbAddr
// Add UsbAddr to UsbAddrList
func (list *UsbAddrList) Add(addr UsbAddr) {
// Find the smallest index of address list
// item which is greater or equal to the
// newly inserted address
//
// Note, of "not found" case sort.Search()
// returns len(*list)
i := sort.Search(len(*list), func(n int) bool {
return !(*list)[n].Less(addr)
})
// Check for duplicate
if i < len(*list) && (*list)[i] == addr {
return
}
// The simple case: all items are less
// that newly added, so just append new
// address to the end
if i == len(*list) {
*list = append(*list, addr)
return
}
// Insert item in the middle
*list = append(*list, (*list)[i])
(*list)[i] = addr
}
// Find address in a list. Returns address index,
// if address is found, -1 otherwise
func (list UsbAddrList) Find(addr UsbAddr) int {
i := sort.Search(len(list), func(n int) bool {
return !list[n].Less(addr)
})
if i < len(list) && list[i] == addr {
return i
}
return -1
}
// Diff computes a difference between two address lists,
// returning lists of elements to be added and to be removed
// to/from the list to convert it to the list2
func (list UsbAddrList) Diff(list2 UsbAddrList) (added, removed UsbAddrList) {
// Note, there is no needs to sort added and removed
// lists, they are already created sorted
for _, a := range list2 {
if list.Find(a) < 0 {
added.Add(a)
}
}
for _, a := range list {
if list2.Find(a) < 0 {
removed.Add(a)
}
}
return
}
// UsbIfAddr represents a full "address" of the USB interface
type UsbIfAddr struct {
UsbAddr // Device address
Num int // Interface number within Config
Alt int // Number of alternate setting
In, Out int // Input/output endpoint numbers
}
// String returns a human readable short representation of UsbIfAddr
func (ifaddr UsbIfAddr) String() string {
return fmt.Sprintf("Bus %.3d Device %.3d Interface %d Alt %d",
ifaddr.Bus,
ifaddr.Address,
ifaddr.Num,
ifaddr.Alt,
)
}
// UsbIfAddrList represents a list of USB interface addresses
type UsbIfAddrList []UsbIfAddr
// Add UsbIfAddr to UsbIfAddrList
func (list *UsbIfAddrList) Add(addr UsbIfAddr) {
*list = append(*list, addr)
}
// UsbDeviceDesc represents an IPP-over-USB device descriptor
type UsbDeviceDesc struct {
UsbAddr // Device address
Config int // IPP-over-USB configuration
IfAddrs UsbIfAddrList // IPP-over-USB interfaces
IfDescs []UsbIfDesc // Descriptors of all interfaces
}
// GetUsbDeviceInfo obtains UsbDeviceInfo by UsbDeviceDesc
// It may fail, if device cannot be opened
func (desc UsbDeviceDesc) GetUsbDeviceInfo() (UsbDeviceInfo, error) {
dev, err := UsbOpenDevice(desc)
if err == nil {
defer dev.Close()
return dev.UsbDeviceInfo()
}
return UsbDeviceInfo{}, err
}
// UsbIfDesc represents an USB interface descriptor
type UsbIfDesc struct {
Vendor uint16 // USB Vendor ID
Product uint16 // USB Device ID
Config int // Configuration
IfNum int // Interface number
Alt int // Alternate setting
Class int // Class
SubClass int // Subclass
Proto int // Protocol
}
// IsIppOverUsb check if interface is IPP over USB
//
// FIXME. The matching rules must be configurable
func (ifdesc UsbIfDesc) IsIppOverUsb() bool {
switch {
// The classical combination, 7/1/4
case ifdesc.Class == 7 && ifdesc.SubClass == 1 && ifdesc.Proto == 4:
return true
// Some HP devices use non-standard combination, 255/9/1
//
// This is valid at least with the following devices:
// HP LaserJet MFP M426fdn
// HP ColorLaserJet MFP M278-M281
case ifdesc.Vendor == 0x03f0 &&
ifdesc.Class == 255 && ifdesc.SubClass == 9 && ifdesc.Proto == 1:
return true
}
return false
}
// UsbDeviceInfo represents USB device information
type UsbDeviceInfo struct {
// Fields, directly decoded from USB
Vendor uint16 // Vendor ID
Product uint16 // Device ID
SerialNumber string // Device serial number
Manufacturer string // Manufacturer name
ProductName string // Product name
PortNum int // USB port number
BasicCaps UsbIppBasicCaps // Device basic capabilities
// Precomputed fields
MfgAndProduct string // Product with Manufacturer prefix, if needed
}
// UsbIppBasicCaps represents device basic capabilities bits,
// according to the IPP-USB specification, section 4.3
type UsbIppBasicCaps int
// Basic capabilities bits, see IPP-USB specification, section 4.3
const (
UsbIppBasicCapsPrint UsbIppBasicCaps = 1 << iota
UsbIppBasicCapsScan
UsbIppBasicCapsFax
UsbIppBasicCapsOther
UsbIppBasicCapsAnyHTTP
)
// String returns a human-readable representation of UsbAddr
func (caps UsbIppBasicCaps) String() string {
s := []string{}
if caps&UsbIppBasicCapsPrint != 0 {
s = append(s, "print")
}
if caps&UsbIppBasicCapsScan != 0 {
s = append(s, "scan")
}
if caps&UsbIppBasicCapsFax != 0 {
s = append(s, "fax")
}
if caps&UsbIppBasicCapsAnyHTTP != 0 {
s = append(s, "http")
}
return strings.Join(s, ",")
}
// FixUp fixes up precomputed fields
func (info *UsbDeviceInfo) FixUp() {
mfg := strings.TrimSpace(info.Manufacturer)
prod := strings.TrimSpace(info.ProductName)
info.MfgAndProduct = prod
if !strings.HasPrefix(prod, mfg) {
info.MfgAndProduct = mfg + " " + prod
}
}
// Ident returns device identification string, suitable as
// persistent state identifier
func (info UsbDeviceInfo) Ident() string {
id := fmt.Sprintf("%4.4x-%4.4x-%s-%s",
info.Vendor, info.Product, info.SerialNumber, info.MfgAndProduct)
id = strings.Map(func(c rune) rune {
switch {
case '0' <= c && c <= '9':
case 'a' <= c && c <= 'z':
case 'A' <= c && c <= 'Z':
case c == '-' || c == '_':
default:
c = '-'
}
return c
}, id)
return id
}
// DNSSdName generates device DNS-SD name in a case it is not available
// from IPP or eSCL
func (info UsbDeviceInfo) DNSSdName() string {
return info.MfgAndProduct
}
// UUID generates device UUID in a case it is not available
// from IPP or eSCL
func (info UsbDeviceInfo) UUID() string {
hash := sha1.New()
// Arbitrary namespace UUID
const namespace = "fe678de6-f422-467e-9f83-2354e26c3b41"
hash.Write([]byte(namespace))
hash.Write([]byte(info.Ident()))
uuid := hash.Sum(nil)
// UUID.Version = 5: Name-based with SHA1; see RFC4122, 4.1.3.
uuid[6] &= 0x0f
uuid[6] |= 0x5f
// UUID.Variant = 0b10: see RFC4122, 4.1.1.
uuid[8] &= 0x3F
uuid[8] |= 0x80
return fmt.Sprintf(
"%.2x%.2x%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x-%.2x%.2x%.2x%.2x%.2x%.2x",
uuid[0], uuid[1], uuid[2], uuid[3],
uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11],
uuid[12], uuid[13], uuid[14], uuid[15])
}
// Comment returns a short comment, describing a device
func (info UsbDeviceInfo) Comment() string {
return info.MfgAndProduct + " serial=" + info.SerialNumber
}