File: usbcommon.go

package info (click to toggle)
ipp-usb 0.9.30-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 756 kB
  • sloc: sh: 80; makefile: 40
file content (307 lines) | stat: -rw-r--r-- 8,019 bytes parent folder | download
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 (pzz@apevzner.com)
 * 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
}