1. 引言
本节为非规范性内容。
通用串行总线(USB)是有线外设的事实标准。大多数 USB 设备实现了大约十几种标准“设备类”之一,这些设备类规定了设备用于声明其支持的功能、使用这些功能的命令和数据格式的方式。标准设备类包括键盘、鼠标、音频、视频和存储设备。操作系统通过操作系统厂商提供的“类驱动”支持这些设备。然而,仍有大量设备不属于这些标准化设备类。这些设备需要硬件厂商编写本地驱动和 SDK,开发者才能利用这些设备,而这些本地代码阻止了这些设备被 Web 所用。
WebUSB API 提供了一种安全地向 Web 暴露 USB 设备服务的方法。它为使用过现有本地 USB 库的开发者提供了熟悉的 API,并暴露了现有规范中定义的设备接口。有了该 API,硬件制造商可以为其设备构建跨平台的 JavaScript SDK。这对于 Web 是有益的,因为不必等到某种新设备足够流行、浏览器才会提供专门的 API,创新硬件可以从一开始就在 Web 上得到支持。
关于 USB 的更多信息,请参见§ 10 附录:USB 简要介绍。
2. 应用动机
本节为非规范性内容。
2.1. 教育设备
Web 的软件交付模式是教育应用的关键推动因素,因为它们可以在任何计算机上快速加载,无需考虑平台兼容性或管理员权限。科学课程正将计算化测量和数据记录引入教学。这些工具通常需要捆绑的软件,而这些软件在受管计算机上很难安装,因为每增加一个本地应用都会给本已繁忙的 IT 部门增加负担。基于 Web 的硬件 API 允许直接在现有在线课程材料中为这些设备提供支持,实现完全无缝体验。
学习编程的学生使用各种微控制器开发套件,可以借助在线开发工具编写和上传代码。这些工具已经存在,但它们需要一个本地组件让浏览器与硬件通信。这些本地扩展提升了入门门槛,并且可能给用户带来安全隐患,而在 Web 沙箱环境中运行的代码不存在这些问题。
2.2. Web 驱动
Web 的可组合性使得完全由 Web 技术构建新的硬件支持生态成为可能。以 3D 打印机为例,想象一个托管 3D 对象模型的网站希望把打印功能直接集成到页面中。Web 支持二维打印,但并没有三维打印的 API。如果制造商托管可嵌入页面,利用 WebUSB API 向其打印机发送数据,网站就可以像集成地图一样把硬件支持添加进来,就像许多网站集成了嵌入式地图那样。
2.3. 设备升级与诊断
虽然蓝牙等无线协议通常是消费类设备更便捷的选择,但 USB 端口仍在不断普及,因为它们能提供便捷的供电方式,也可作为设备出现故障时的“最后一根救命稻草”。通过在支持网站中集成升级和诊断工具,硬件厂商可以为任何平台的用户提供工具,并在用户来求助时收集更好的诊断数据。落地页让设备厂商可以将用户引导至其网站上与设备相关的帮助页面。
3. 安全与隐私注意事项
本节为非规范性内容。
WebUSB API 是一项强大的特性,可能会给用户带来新的隐私和安全风险。这些风险大致可分为三类,后续小节会分别描述。
3.1. 滥用设备访问
外设的用途多种多样。例如,U 盘可以存储数据,相机或麦克风可以采集外部信息,打印机可以控制物理世界的对象。上述每种设备在 Web 平台上都有专门的高级 API,并具备安全机制防止恶意网页滥用:向外部存储读写数据需用户手动选择文件,启用麦克风或摄像头需用户授权并可能有指示灯提示,打印文档也需用户明确操作。本 API 提供了连接这类高级 API 未覆盖设备的一般机制,因此同样需要一套通用机制来防止恶意页面滥用设备。
首项保护措施是 requestDevice()
函数。当调用该函数时,UA(用户代理)可以显示权限弹窗。即使对于非恶意页面,此举也通过在连接设备前提示用户,有助于保护用户隐私。UA 还可在设备连接开启时显示事件指示。
第二项,本规范要求只有 [powerful-features] 所述的安全上下文才能访问 USB 设备。这既可保证被执行代码的来源可信,也可防止设备数据在传输过程中被窃取。
最后,因为 USB 设备无法区分来自多个来源的请求,操作系统只允许一个 USB 接口被单一用户态或内核态驱动占用。UA 作为用户态驱动,因此每次只允许有一个执行上下文独占 USB 接口。如果多个执行上下文试图
claim 接口,claimInterface()
函数会失败。
3.2. 攻击设备
历史上,除高安全性应用外,USB 设备往往被设计为信任所连接主机,因此主机是访问设备功能的传统门槛。在制定本规范时,曾考虑两种可能性:第一,UA 可通知设备请求的来源(origin),类似于 HTTP 请求包含的
Referrer 头。问题在于,这种方式将访问控制压力转嫁给设备,而设备通常处理与存储能力有限,因此规范努力简化设备所需工作量。
起初本规范采用的办法是要求 UA 通过类似 [CORS] 的机制控制访问。设备可为 UA 提供一组静态数据结构,用于定义允许访问的源(origin)。为兼容传统设备,还可通过公共注册表(out of band)方式提供允许的 origin,方便过渡。
这种方案的缺点有二:一是厂商必须设计支持 WebUSB 的新设备,或依赖难以规范化的公共注册表。而产品开发周期很长,仅一个编辑草案影响有限。二是没有为第三方开发者留出利用该 API 的途径。这无助于创新,也让新能力的实际受益开发者有限。
权衡后,作者认为 requestDevice()
方法所促进的权限提示与§ 8.1 权限策略集成已经能对设备访问提供足够保护。
3.3. 攻击主机
如果设备被攻破,攻击者除了滥用设备本身能力,还可用其进一步攻击连接主机,甚至若攻击具有持久性,今后无论连接哪台主机都可复现前述攻击。上文措施旨在减少该类攻击路径,因为一旦设备被攻击者控制(例如刷入恶意固件),UA 已无法再阻止进一步破坏。
本规范建议设备厂商采用纵深防御,设计设备时只接受数字签名固件升级,和/或要求物理接入设备才能更改某些配置信息。
4. WebUSB 描述符与请求
本规范定义了 UA 可用于收集与实现本 API 相关的设备信息的描述符与命令。
4.1. WebUSB 平台能力描述符
设备通过在其二进制对象存储区(Binary Object Store)中包含如下平台描述符来声明对 WebUSB 指令集的支持:
| 偏移量 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 0 | bLength | 1 | 数字 | 描述符大小。必须为 24。 |
| 1 | bDescriptorType | 1 | 常量 | DEVICE CAPABILITY 描述符类型([USB31] 表 9-6)。 |
| 2 | bDevCapabilityType | 1 | 常量 | PLATFORM 能力类型([USB31] 表 9-14)。 |
| 3 | bReserved | 1 | 数字 | 保留字段,应置为 0。 |
| 4 | PlatformCapabilityUUID | 16 | UUID | 必须为 {3408b638-09a9-47a0-8bfd-a0768815b665}。 |
| 20 | bcdVersion | 2 | BCD | 支持的协议版本。必须设置为 0x0100。 |
| 22 | bVendorCode | 1 | 数字 | 发起 WebUSB 请求使用的 bRequest 值。 |
| 23 | iLandingPage | 1 | 数字 | 设备落地页的 URL 描述符索引。 |
当 iLandingPage 字段非 0 时,表示厂商希望用户访问的落地页的URL,用户可以通过此页面操作设备。UA 可在设备接入时建议用户访问该 URL。
注: USB 是小端序总线,因此按 [RFC4122],上述
UUID 必须按字节序 {0x38, 0xB6, 0x08,
0x34, 0xA9, 0x09, 0xA0, 0x47, 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6,
0x65} 传输。
4.2. WebUSB 设备请求
本规范定义的所有控制传输都属于厂商自定义请求。WebUSB 平台能力描述符中的 bVendorCode
表明设备希望主机发起
控制传输请求时应使用的 bRequest,类型由
wIndex 字段指定。
| 常量 | 值 |
|---|---|
| (保留) | 1 |
| GET_URL | 2 |
4.2.1. 获取 URL(Get URL)
该请求用于获取指定索引的 URL 描述符。
设备必须返回对应索引的URL 描述符,索引无效则中止传输。
| bmRequestType | bRequest | wValue | wIndex | wLength | 数据 |
|---|---|---|---|---|---|
| 11000000B | bVendorCode
| 描述符索引 | GET_URL | 描述符长度 | 描述符 |
4.3. WebUSB 描述符
这些描述符类型是由本规范定义的请求返回的。
| 常量 | 值 |
|---|---|
| (保留) | 0-2 |
| WEBUSB_URL | 3 |
4.3.1. URL 描述符
该描述符包含一个 URL,由获取 URL(Get URL)请求返回。
| 偏移量 | 字段 | 大小 | 值 | 说明 |
|---|---|---|---|---|
| 0 | bLength | 1 | 数字 | 描述符大小。 |
| 1 | bDescriptorType | 1 | 常量 | WEBUSB_URL。 |
| 2 | bScheme | 1 | 数字 | URL 协议前缀。 |
| 3 | URL | 可变 | 字符串 | UTF-8 编码(不含协议前缀)的 URL。 |
bScheme 字段必须取值如下之一:
| 值 | 前缀 |
|---|---|
| 0 | "http://" |
| 1 | "https://" |
| 255 | "" |
特殊值 255 表示 URL 字段中已包含整个 URL(含协议)。
5. 设备枚举
dictionary {USBDeviceFilter unsigned short ;vendorId unsigned short ;productId octet ;classCode octet ;subclassCode octet ;protocolCode DOMString ; };serialNumber dictionary {USBDeviceRequestOptions required sequence <USBDeviceFilter >;filters sequence <USBDeviceFilter >= []; }; [exclusionFilters Exposed =(Worker ,Window ),SecureContext ]interface :USB EventTarget {attribute EventHandler ;onconnect attribute EventHandler ;ondisconnect Promise <sequence <USBDevice >>getDevices (); [Exposed =Window ]Promise <USBDevice >requestDevice (USBDeviceRequestOptions ); }; [options Exposed =Window ,SecureContext ]partial interface Navigator { [SameObject ]readonly attribute USB ; }; [usb Exposed =Worker ,SecureContext ]partial interface WorkerNavigator { [SameObject ]readonly attribute USB ; };usb
getDevices()
检查是否已经拥有对已连接设备的访问权限,
document. addEventListener( 'DOMContentLoaded' , async () => { let devices= await navigator. usb. getDevices(); devices. forEach( device=> { // 将 |device| 添加到 UI。 }); });
页面加载后,用户可能会连接或断开设备,因此脚本还需要注册相关事件,以保证界面实时更新:
navigator. usb. addEventListener( 'connect' , event=> { // 将 |event.device| 添加到 UI。 }); navigator. usb. addEventListener( 'disconnect' , event=> { // 从 UI 中移除 |event.device|。 });
如果是用户首次访问本页面,则不会拥有任何设备访问权限,因此页面需要在相关全局对象具备临时激活时,调用 requestDevice()。下面是页面仅支持厂商编号为
0xABCD、含厂商自定义子类 0x01 的设备时的示例:
let button= document. getElementById( 'request-device' ); button. addEventListener( 'click' , async () => { let device; try { device= await navigator. usb. requestDevice({ filters: [{ vendorId: 0xABCD , classCode: 0xFF , // 厂商自定义 protocolCode: 0x01 }]}); } catch ( err) { // 用户未选择设备。 } if ( device!== undefined ) { // 将 |device| 添加到 UI。 } });
本规范中的方法通常为异步完成,其工作会排入 USB 任务源(task source)。
若 USB 设备 device 满足以下步骤则视为匹配过滤器
filter,返回match:
-
令 deviceDesc 为 device 的 设备描述符。
-
如果
filter.存在且vendorIddeviceDesc.idVendor不等于filter.则返回vendorIdmismatch。 -
如果
filter.存在且productIddeviceDesc.idProduct不等于filter.则返回productIdmismatch。 -
如果
filter.存在,则令serialNumber为 字符串描述符,索引为serialNumberdeviceDesc.iSerialNumber。如 device 返回错误或 serialNumber 不等于filter.,则返回serialNumbermismatch。 -
如果
filter.存在且对于 device 的任一接口 interface,若 interface 匹配接口过滤器 filter,则返回classCodematch。 -
如果
filter.存在且classCodedeviceDesc.bDeviceClass不等于filter., 则返回classCodemismatch。 -
如果
filter.存在且subclassCodedeviceDesc.bDeviceSubClass不等于filter., 则返回subclassCodemismatch。 -
如果
filter.存在且protocolCodedeviceDesc.bDeviceProtocol不等于filter., 则返回protocolCodemismatch。 -
返回
match。
注: 上述步骤将设备描述符的
bDeviceClass, bDeviceSubClass, bDeviceProtocol 字段视作
接口描述符的一部分进行比较。
若 USB 接口 interface 满足下列步骤则视为匹配接口过滤器
filter,返回 match:
-
令 desc 为 interface 的接口描述符。
-
如果
filter.存在且classCodedesc.bInterfaceClass不等于filter., 则返回classCodemismatch。 -
如果
filter.存在且subclassCodedesc.bInterfaceSubClass不等于filter., 则返回subclassCodemismatch。 -
如果
filter.存在且protocolCodedesc.bInterfaceProtocol不等于filter., 则返回protocolCodemismatch。 -
返回
match。
若 USBDeviceFilter
filter 满足下述步骤则为有效过滤器(返回valid):
-
如果
filter.存在,但subclassCodefilter.不存在, 返回classCodeinvalid。 -
如果
filter.存在,但protocolCodefilter.不存在, 返回subclassCodeinvalid。 -
返回
valid。
UA 必须能够枚举系统所有已连接设备。不过,不要求每次算法请求枚举时都执行实际操作。UA
可以在首次枚举后缓存结果,并开始监听插拔事件,将新连接设备加入缓存,移除被拔出的设备。这种方式更优,因为 getDevices()
和 requestDevice()
方法可减少操作系统调用和总线流量。
onconnect
属性是 connect 事件类型的事件处理程序 IDL 属性。
ondisconnect
属性是 disconnect 事件类型的事件处理程序 IDL 属性。
getDevices() 方法执行时,必须运行以下步骤:
-
令 document 为 global 的 关联文档,如无就为
null。 -
令 storage 为:
-
如 global 是
ServiceWorkerGlobalScope,则为其脚本执行环境中的USBPermissionStorage对象; -
否则为当前脚本环境的
USBPermissionStorage对象。
-
-
令 promise 为新 promise。
-
并行执行以下步骤:
-
枚举所有系统已连接设备。记为 enumerationResult。
-
令 devices 为一个新空
Array。 -
遍历 enumerationResult,对每个 device:
-
如果本方法为首次被调用,检查 device 的权限,用 storage。
-
在 storage.
allowedDevices找到元素 allowedDevice,device 位于 allowedDevice.[[devices]]。 如未找到,继续下一个 device。
-
将代表 device 的
USBDevice对象加入 devices。 -
グローバルタスクをキューに追加 する(global で USB タスクソースを指定)、promise を resolve(解決)し、devices で完了させる。
-
-
返回 promise。
requestDevice(options) 方法执行时,必须运行以下步骤:
-
请求使用如下描述符的权限,
{ name: "usb" filters: options. filters exclusionFilters: options. exclusionFilters} 令 permissionResult 为生成的
Promise。 -
当 permissionResult 被满足时,带有 result ,执行以下步骤:
-
如果 result.
devices为空,抛出 "NotFoundError"DOMException并中止后续步骤。 -
返回 result.
devices[0]。
-
Document
document,
USBPermissionStorage
storage,
USBPermissionDescriptor
options 和
USBPermissionResult
status,UA 必须执行:
-
令 global 为 storage 的 相关全局对象。
-
遍历 options.
filters, 如果其中某个 filter 不是有效过滤器,返回 一个被拒 promise,原因是TypeError。 -
遍历options.
exclusionFilters, 如果其中某个 exclusionFilter 不是有效过滤器,返回被拒 promise,原因是TypeError。 -
检查算法是否在 global 具备临时激活时触发。否则返回 被拒的 promise,报
SecurityErrorDOMException。 -
令 promise 为新 promise。
-
并行执行以下步骤。
-
枚举系统所有已连接设备。令该结果为enumerationResult。
-
如果 enumerationResult 中的设备对 document 在阻止列表中,则移除该设备。
-
如果 enumerationResult 中的设备不 匹配过滤器(options.
filters)中的过滤条件,则移除该设备。 -
如果 enumerationResult 中的设备 匹配过滤器(options.
exclusionFilters)中的过滤条件,则移除该设备。 -
向用户显示设备选择弹窗,请用户从 enumerationResult 中选择设备。UA 应为每个设备显示易于理解的名称。
-
等待用户选择了某个 device 或取消了弹窗。
-
在 USB 任务源 USB task source 上,为 global 排队全局任务,执行下列步骤:
-
将 status.
state赋值为"ask"。 -
如果用户取消弹窗,则将 status.
devices赋值为空的FrozenArray, resolve promise,返回undefined,并终止后续步骤。 -
令 deviceObj 为表示 device 的
USBDevice对象。 -
将 status.
devices赋值为包含 deviceObj 的新FrozenArray。 -
resolve promise,返回
undefined。
-
-
-
返回 promise。
要将允许的 USB
设备 device 添加到 USBPermissionStorage
storage 中,UA 必须执行以下步骤:
-
在
storage.查找元素 allowedDevice,其allowedDevices[[devices]]内包含 device。 若找到,终止后续步骤。 -
令 serialNumber 为 device 的 序列号,如无则为
undefined。 -
向
storage.追加allowedDevices{ vendorId: vendorId, productId: productId, serialNumber: serialNumber }, 其[[devices]]内部插槽包含唯一的 device。
要检查新的 USB 设备 权限 device,给定 USBPermissionStorage
storage,UA 必须执行以下步骤:
-
令 serialNumber 为 device 的序列号,如无则为
undefined。 -
在
storage.查找元素 allowedDevice,满足:allowedDevices-
allowedDevice.等于 vendorId。vendorId -
allowedDevice.等于 productId。productId -
allowedDevice.等于 serialNumber。serialNumber
-
-
如未找到该元素,则返回
null。 -
将 device 添加到 allowedDevice@
[[devices]]。 -
返回 allowedDevice。
要移除已允许的 USB 设备 device,给定 USBPermissionStorage
storage,UA 必须执行以下步骤:
-
在
storage.查找元素 allowedDevice,其allowedDevices[[devices]]包含 device,如未找到则中止后续步骤。 -
将 allowedDevice 从
storage.中移除。allowedDevices
5.1. 事件
dictionary :USBConnectionEventInit EventInit {required USBDevice ; }; [device Exposed =(Worker ,Window ),SecureContext ]interface :USBConnectionEvent Event {(constructor DOMString ,type USBConnectionEventInit ); [eventInitDict SameObject ]readonly attribute USBDevice ; };device
注: Worker 可以为 connect 和 disconnect 事件注册事件监听器,但只有在该 worker 处于活动状态时,事件监听器才会被调用。
当 UA 检测到一个新的 USB 设备 device 连接到主机时,它必须对每个脚本执行环境执行以下步骤:
-
令 storage 为当前脚本执行环境中的
USBPermissionStorage对象。 -
使用 storage 检查 device 的权限,并令 allowedDevice 为结果。
-
如果 allowedDevice 为
null,则中止这些步骤。 -
令 deviceObj 为表示 device 的
USBDevice对象。 -
在 device 的 相关全局对象 的
Navigator对象的usb上 触发 名为 connect 的事件,使用USBConnectionEvent, 并将device属性设为 deviceObj。
当 UA 检测到某个 USB 设备 device 已从主机断开时,它必须对每个脚本执行环境执行以下步骤:
-
令 storage 为当前脚本执行环境中的
USBPermissionStorage对象。 -
在
storage.中搜索元素 allowedDevice,使得 device 位于 allowedDevice@allowedDevices[[devices]], 如果不存在这样的元素, 则中止这些步骤。 -
从 allowedDevice@
[[devices]]中移除 device。 -
如果
allowedDevice.为serialNumberundefined且 allowedDevice@[[devices]]为空,则从storage.中移除 allowedDevice。allowedDevices -
令 device 为表示 device 的
USBDevice对象。 -
在 device 的 相关全局对象 的
Navigator对象的usb上 触发 名为 disconnect 的事件,使用USBConnectionEvent, 并将device属性设为 device。
6. 设备使用
bConfigurationValue 1),该配置包含一个接口(bInterfaceNumber 1)和一个批量端点(bEndpointAddress 0x81,意味着端点
1,且为 IN 端点)。当采样数据时,数据可在此端点上获取。该端点的最大包长度为 16 字节,以支持 8 个通道同时激活。但为节省总线带宽,可任意组合地激活或停用通道。数据包仅为传输所需的长度。
开始时我们打开设备,选择第一个配置(设备只有一个,但操作系统在枚举时可能尚未选择),并声明数据记录接口,
await device. open(); if ( device. configuration=== null ) await device. selectConfiguration( 1 ); await device. claimInterface( 1 );
对于该应用,我们关心读取通道 1、2 和 5,因此发送一个 control transfer 来激活这些通道,
await device. controlTransferOut({ requestType: ' vendor ' , recipient: ' interface ' , request: 0x01 , // 厂商特定请求:启用通道 value: 0x0013 , // 0b00010011(通道 1、2 和 5) index: 0x0001 // 接收者为接口 1 });
应用现在可以开始轮询设备以获取数据。因为我们只期望来自 3 个通道的数据,所以请求 6 字节缓冲区。只要收到完整缓冲区(以大端传输),就把捕获到的值打印到控制台。如果设备遇到错误并通过端点 STALL 来表示,则在继续前先清除该错误,
while ( true ) { let result= await device. transferIn( 1 , 6 ); if ( result. data&& result. data. byteLength=== 6 ) { console. log( 'Channel 1: ' + result. data. getUint16( 0 )); console. log( 'Channel 2: ' + result. data. getUint16( 2 )); console. log( 'Channel 5: ' + result. data. getUint16( 4 )); } if ( result. status=== 'stall' ) { console. warn( 'Endpoint stalled. Clearing.' ); await device. clearHalt( 1 ); } }
6.1. USBDevice 接口
enum {USBTransferStatus ,"ok" ,"stall" }; ["babble" Exposed =(Worker ,Window ),SecureContext ]interface {USBInTransferResult (constructor USBTransferStatus ,status optional DataView ?);data readonly attribute DataView ?;data readonly attribute USBTransferStatus ; }; [status Exposed =(Worker ,Window ),SecureContext ]interface {USBOutTransferResult (constructor USBTransferStatus ,status optional unsigned long = 0);bytesWritten readonly attribute unsigned long ;bytesWritten readonly attribute USBTransferStatus ; }; [status Exposed =(Worker ,Window ),SecureContext ]interface {USBIsochronousInTransferPacket (constructor USBTransferStatus ,status optional DataView ?);data readonly attribute DataView ?;data readonly attribute USBTransferStatus ; }; [status Exposed =(Worker ,Window ),SecureContext ]interface {USBIsochronousInTransferResult (constructor sequence <USBIsochronousInTransferPacket >,packets optional DataView ?);data readonly attribute DataView ?;data readonly attribute FrozenArray <USBIsochronousInTransferPacket >; }; [packets Exposed =(Worker ,Window ),SecureContext ]interface {USBIsochronousOutTransferPacket (constructor USBTransferStatus ,status optional unsigned long = 0);bytesWritten readonly attribute unsigned long ;bytesWritten readonly attribute USBTransferStatus ; }; [status Exposed =(Worker ,Window ),SecureContext ]interface {USBIsochronousOutTransferResult (constructor sequence <USBIsochronousOutTransferPacket >);packets readonly attribute FrozenArray <USBIsochronousOutTransferPacket >; }; [packets Exposed =(Worker ,Window ),SecureContext ]interface {USBDevice readonly attribute octet usbVersionMajor ;readonly attribute octet usbVersionMinor ;readonly attribute octet usbVersionSubminor ;readonly attribute octet deviceClass ;readonly attribute octet deviceSubclass ;readonly attribute octet deviceProtocol ;readonly attribute unsigned short vendorId ;readonly attribute unsigned short productId ;readonly attribute octet deviceVersionMajor ;readonly attribute octet deviceVersionMinor ;readonly attribute octet deviceVersionSubminor ;readonly attribute DOMString ?manufacturerName ;readonly attribute DOMString ?productName ;readonly attribute DOMString ?serialNumber ;readonly attribute USBConfiguration ?configuration ;readonly attribute FrozenArray <USBConfiguration >configurations ;readonly attribute boolean opened ;Promise <undefined >open ();Promise <undefined >close ();Promise <undefined >forget ();Promise <undefined >selectConfiguration (octet );configurationValue Promise <undefined >claimInterface (octet );interfaceNumber Promise <undefined >releaseInterface (octet );interfaceNumber Promise <undefined >selectAlternateInterface (octet ,interfaceNumber octet );alternateSetting Promise <USBInTransferResult >controlTransferIn (USBControlTransferParameters ,setup unsigned short );length Promise <USBOutTransferResult >controlTransferOut (USBControlTransferParameters ,setup optional BufferSource );data Promise <undefined >clearHalt (USBDirection ,direction octet );endpointNumber Promise <USBInTransferResult >transferIn (octet ,endpointNumber unsigned long );length Promise <USBOutTransferResult >transferOut (octet ,endpointNumber BufferSource );data Promise <USBIsochronousInTransferResult >isochronousTransferIn (octet ,endpointNumber sequence <unsigned long >);packetLengths Promise <USBIsochronousOutTransferResult >isochronousTransferOut (octet ,endpointNumber BufferSource ,data sequence <unsigned long >);packetLengths Promise <undefined >reset (); };
通过下表描述的 USBDevice
实例在创建时包含如下内部插槽(internal slots):
| Internal Slot | 初始值 | 说明(非规范性) |
|---|---|---|
[[configurations]]
| 一个空的 sequence,元素类型为 USBConfiguration
| 设备支持的所有配置。 |
[[configurationValue]]
| <在说明中总是设置> | 设备当前的配置值。 |
[[selectedAlternateSetting]]
| 一个 空 列表,元素为 integer | 当前配置下每个接口的当前 alternate setting。 |
[[claimedInterface]]
| 一个 空 列表,元素为 boolean | 当前配置下每个接口的声明(claimed)状态。 |
-
令 deviceDescriptor 为通过执行 Get Descriptor(将
DescriptorType设置为DEVICE)而得到的已连接设备的 device descriptor。 -
返回 deviceDescriptor。
-
令 deviceDescriptor 为执行 获取已连接 USB 设备的设备描述符 的结果。
-
令 numConfigurations 为 deviceDescriptor 的
bNumConfigurations值。 -
令 configurationIndex 为 0。
-
当 configurationIndex 小于 numConfigurations 时:
-
令 descriptors 为通过执行 Get Descriptor(将
DescriptorType设置为CONFIGURATION,并将DescriptorIndex设为 configurationIndex)得到的描述符列表。 -
如果 descriptors[0] 的
bDescriptorType等于CONFIGURATION,则将 descriptors[0] 追加 到 configurationDescriptors。 -
将 configurationIndex 加 1。
-
-
返回 configurationDescriptors。
-
令 global 为 interface 的 相关全局对象。
-
令 configuration 为 interface.
[[configuration]]。 -
令 device 为 configuration.
[[device]]。 -
如果 configuration 与 使用 device 执行 查找当前配置 得到的结果不同,则返回。
-
如果 使用 interface 执行 查找接口是否被声明(claimed) 的结果不为
true,则返回。 -
令 currAlternateInterface 为 使用 interface 执行 查找当前 alternate setting 的 alternate interface 的结果。
-
对每个 endpoint 属于 currAlternateInterface.
[[endpoints]]执行:-
中止在 endpoint 上当前排定的所有传输。
-
在给定 global 的 USB task source 上 排队一个全局任务,以便 拒绝 相关的 promise,并以 "
AbortError"DOMException作为原因。
-
-
令 interfaceIndex 为 0。
-
当 interfaceIndex 小于 configuration.
[[interfaces]]的 大小 时:-
如果 configuration.
[[interfaces]][interfaceIndex].[[interfaceNumber]]等于 interfaceNumber,则返回 interfaceIndex。 -
将 interfaceIndex 加 1。
-
-
返回
-1。
-
令 alternateIndex 为 0。
-
当 alternateIndex 小于 interface.
[[alternates]]的 大小 时:-
如果 interface.
[[alternates]][alternateIndex].[[alternateSetting]]等于 alternateSetting,则返回 alternateIndex。 -
将 alternateIndex 加 1。
-
-
返回
-1。
-
对每个 configuration 属于 device.
[[configurations]]执行:-
如果 configuration.
[[configurationValue]]等于 device.[[configurationValue]], 则返回 configuration。
-
-
返回
null。
-
令 configuration 为 使用 device 执行 查找当前配置 的结果。
-
如果 configuration 为
null,则返回null。 -
对每个 interface 属于 configuration.
[[interfaces]]执行:-
如果 使用 interface 执行 查找接口是否被声明(claimed) 的结果不为
true,则继续下一个 interface。 -
令 alternate 为 使用 interface 执行 查找当前 alternate setting 的 alternate interface 的结果。
-
对每个 endpoint 属于 alternate.
[[endpoints]]执行:-
如果 endpoint.
[[endpointAddress]]等于 endpointAddress,则返回 endpoint。
-
-
-
返回
null。
-
如果 device 已不再连接到系统,返回 一个被拒的 promise,原因是 "
NotFoundError"DOMException。 -
如果 device.
opened不为true,返回 一个被拒的 promise,原因是 "InvalidStateError"DOMException。 -
如果 device.
[[configurationValue]]等于0,返回 一个被拒的 promise,原因是 "InvalidStateError"DOMException。 -
返回
undefined。
USBDevice
对象,执行以下步骤:
-
将 this.
[[configurationValue]]设置为执行 Get Configuration 返回的值。 -
令 configurationDescriptors 为 执行 查找已连接 USB 设备的配置描述符列表 的结果。
-
对每个 configurationDescriptor 属于 configurationDescriptors 执行:
-
令 configuration 为 使用 new 创建的
USBConfiguration对象,调用形式为 USBConfiguration(device, configurationValue),其中 device 设置为 this,且 configurationValue 为 configurationDescriptor 的bConfigurationValue。 -
将 configuration 追加 到 this.
[[configurations]]。 -
如果
bConfigurationValue等于 this.[[configurationValue]]:-
令 numInterfaces 为 configuration.
[[interfaces]]的 大小。 -
将 this.
[[selectedAlternateSetting]]调整大小为 numInterfaces。 -
将 this.
[[selectedAlternateSetting]]填充为 0。 -
将 this.
[[claimedInterface]]调整大小为 numInterfaces。 -
将 this.
[[claimedInterface]]填充为false。
-
-
所有 USB 设备必须有一个 默认控制通道,其
endpointNumber 0。
6.1.1. 属性
usbVersionMajor, 类型为 octet, 只读usbVersionMinor, 类型为 octet, 只读usbVersionSubminor, 类型为 octet, 只读-
usbVersionMajor、usbVersionMinor与usbVersionSubminor属性声明了设备所支持的 USB 协议版本。它们必须对应于bcdUSB字段的值(见 设备描述符),使得值0xJJMN表示主版本为JJ,次版本为M,子次版本为N。 deviceClass, 类型为 octet, 只读deviceSubclass, 类型为 octet, 只读deviceProtocol, 类型为 octet, 只读-
deviceClass、deviceSubclass与deviceProtocol属性声明了设备支持的通信接口。它们必须分别对应于 设备描述符 中的bDeviceClass、bDeviceSubClass与bDeviceProtocol字段的值。 vendorId, 类型为 unsigned short, 只读productId, 类型为 unsigned short, 只读deviceVersionMajor, 类型为 octet, 只读deviceVersionMinor, 类型为 octet, 只读deviceVersionSubminor, 类型为 octet, 只读-
deviceVersionMajor、deviceVersionMinor与deviceVersionSubminor属性声明了设备制造商定义的设备发布号。它们必须对应于 设备描述符 中的bcdDevice字段,使得值0xJJMN表示主版本为JJ,次版本为M,子次版本为N。 manufacturerName, 类型为 DOMString, 只读,可为空productName, 类型为 DOMString, 只读,可为空serialNumber, 类型为 DOMString, 只读,可为空-
manufacturerName、productName与serialNumber属性应当包含在 字符串描述符(由设备描述符中的iManufacturer、iProduct与iSerialNumber索引)中定义的值(如果这些值存在)。 configuration, 类型为 USBConfiguration, 只读,可为空-
configuration属性包含了设备当前选择的配置,且必须是USBConfiguration集合configurations中的一个。configurationgetter 的步骤如下: configurations, 类型为 FrozenArray<USBConfiguration>, 只读-
configurations属性包含一个 sequence,其元素为表示设备支持的USBConfiguration对象。configurationsgetter 的步骤如下: opened, 类型为 boolean, 只读-
opened属性当且仅当当前执行上下文已打开设备时应为true;否则应为false。
6.1.2. 方法
open() 方法在被调用时,必须执行以下步骤:
-
如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "
NotFoundError"DOMException。 -
如果 this.
opened为true,则返回 一个已解决的 promise,值为undefined。 -
并行 执行以下步骤。
-
执行必要的平台特定步骤以开始与设备的会话。
-
在给定 global 的 USB 任务源 上排队一个全局任务以运行以下步骤:
-
如果上述平台特定步骤因任何原因失败,则 拒绝 promise,原因是 "
NetworkError"DOMException并中止这些步骤。 -
解析 promise,值为
undefined。
-
-
-
返回 promise。
close() 方法在被调用时,必须执行以下步骤:
-
如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "
NotFoundError"DOMException。 -
如果 this.
opened为false,则返回 一个已解决的 promise,值为undefined。 -
中止当前针对该设备正在运行的所有其他算法,并拒绝它们关联的 promise,并抛出一个 "
AbortError"DOMException。 -
并行 执行以下步骤。
-
执行必要的平台特定步骤以释放所有被声明的接口,就像对每个被声明的接口调用了
releaseInterface(interfaceNumber)一样。 -
执行必要的平台特定步骤以结束与设备的会话。
-
在给定 global 的 USB 任务源 上排队一个全局任务以运行以下步骤:
-
-
返回 promise。
注: 当没有任何 [ECMAScript] 代码可以再观察到某个
USBDevice
实例 device 时,UA 应当调用 device.close()(建议)。
forget() 方法在被调用时,必须执行以下步骤:
-
令 device 为 this。
-
令 storage 为当前脚本执行环境中的
USBPermissionStorage对象。 -
使用 storage 执行 从存储中移除 device。
-
返回 一个已解决的 promise,值为
undefined。
注: 用户代理可以决定跨 API 合并权限,例如将 WebHID 与 WebUSB 的设备访问跟踪为统一的低级设备访问权限。因此,未来该方法也可能撤销额外的(目前未指定的)权限。
selectConfiguration(configurationValue)
方法在被调用时,必须执行以下步骤:
-
如果 this 已不再连接到系统,则返回 一个被拒绝的 promise,原因是 "
NotFoundError"DOMException。 -
令 selectedConfiguration 为
null。 -
遍历 this.
[[configurations]]的每个 configuration:-
如果 configuration.
[[configurationValue]]等于 configurationValue,则将 selectedConfiguration 设为该 configuration 并跳出循环。
-
-
如果 selectedConfiguration 仍为
null,则返回 一个被拒绝的 promise,原因是 "NotFoundError"DOMException。 -
如果 this.
opened不等于true,则返回 一个被拒绝的 promise,原因是 "InvalidStateError"DOMException。 -
并行 执行以下步骤。
-
如果 activeConfiguration 不为
null:-
遍历 activeConfiguration.
[[interfaces]]的每个 interface,对其执行 中止该接口上当前排定的传输。
-
-
发出一个
SET_CONFIGURATION的 控制传输,其configurationValue设置为 configurationValue。 -
在给定 global 的 USB 任务源 上排队一个全局任务以运行以下步骤:
-
如果上述 控制传输 失败,则 拒绝 promise,原因是 "
NetworkError"DOMException并中止这些步骤。 -
令 numInterfaces 为 selectedConfiguration.
[[interfaces]]的 大小。 -
将 this.
[[selectedAlternateSetting]]调整大小为 numInterfaces。 -
将 this.
[[selectedAlternateSetting]]填充为 0。 -
将 this.
[[claimedInterface]]调整大小为 numInterfaces。 -
将 this.
[[claimedInterface]]填充为false。 -
将 this.
[[configurationValue]]设为 configurationValue。 -
解析 promise,值为
undefined。
-
-
-
返回 promise。
claimInterface(interfaceNumber)
方法在被调用时,必须执行以下步骤:
-
令 interfaces 为 activeConfiguration.
[[interfaces]]。 -
令 interfaceIndex 为 使用 查找接口索引(以 interfaceNumber 与 activeConfiguration 为参数)的结果。
-
如果 interfaceIndex 等于
-1,则返回 一个被拒绝的 promise,原因是 "NotFoundError"DOMException。 -
如果 this.
[[claimedInterface]][interfaceIndex] 为true,则返回 一个已解决的 promise,值为undefined。 -
令 unrestricted 为
false。 -
令 document 为 global 的 关联 Document,如果不存在则为
null。 -
如果 document 不为
null且 document 被允许使用 名为"usb-unrestricted"的 策略控制特性,则将 unrestricted 设为true。 -
如果 interfaces[interfaceIndex].
[[isProtectedClass]]为true且 unrestricted 为false,则返回 一个被拒绝的 promise,原因是 "SecurityError"DOMException。 -
令 promise 为 一个新 promise。
-
并行 执行以下步骤。
-
执行必要的平台特定步骤以请求将 interfaces[interfaceIndex] 的独占控制权赋予当前执行上下文。
-
在给定 global 的 USB 任务源 上排队一个全局任务以运行以下步骤:
-
如果上述平台特定步骤失败,则 拒绝 promise,原因是 "
NetworkError"DOMException并中止这些步骤。 -
将 this.
[[claimedInterface]][interfaceIndex] 设为true。 -
解析 promise,值为
undefined。
-
-
-
返回 promise。
releaseInterface(interfaceNumber)
方法在被调用时,必须执行以下步骤:
-
令 interfaces 为 activeConfiguration.
[[interfaces]]。 -
令 interfaceIndex 为用 查找接口索引,输入为 interfaceNumber 和 activeConfiguration 的结果。
-
如果 interfaceIndex 等于
-1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 this.
[[claimedInterface]][interfaceIndex] 为false, 则返回 一个以解决状态完成的 promise,其值为undefined。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
执行必要的平台特定步骤,以放弃对 interfaces[interfaceIndex] 的独占控制。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
将 this.
[[selectedAlternateSetting]][interfaceIndex] 设为0。 -
将 this.
[[claimedInterface]][interfaceIndex] 设为false。 -
将 promise 解析为
undefined。
-
-
-
返回 promise。
selectAlternateInterface(interfaceNumber, alternateSetting)
方法在被调用时,必须执行以下步骤:
-
令 interfaces 为 activeConfiguration.
[[interfaces]]。 -
令 interfaceIndex 为用 查找接口索引,输入为 interfaceNumber 和 activeConfiguration 的结果。
-
如果 interfaceIndex 等于
-1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 this.
[[claimedInterface]][interfaceIndex] 为false, 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidStateError"DOMException。 -
令 interface 为 interfaces[interfaceIndex]。
-
令 alternateIndex 为用 查找备选设置索引,输入为 alternateSetting 和 interface 的结果。
-
如果 alternateIndex 等于
-1,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
用 interface 中止当前已调度在某接口上的传输。
-
发出一个
SET_INTERFACE控制传输,其中interfaceNumber设为 interfaceNumber,alternateSetting设为 alternateSetting。 -
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
如果上述 控制传输失败,则拒绝 promise,拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 this.
[[selectedAlternateSetting]][interfaceIndex] 设为 alternateSetting。 -
将 promise 解析为
undefined。
-
-
-
返回 promise。
controlTransferIn(setup, length)
方法在被调用时,必须执行以下步骤:
-
如果用 检查控制传输参数的有效性,输入为 this 和 setup,返回一个
Promise, 则返回该值。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
如果 length 大于 0,令 buffer 为一个具有 length 字节空间的主机缓冲区。
-
向 this 发起一次 控制传输,其 设置包 参数来自 setup,并将
bmRequestType中的数据传输方向设为 “device to host”,将wLength设为 length。 如已定义,还应提供 buffer 作为接收此传输响应数据的写入目标。 -
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 bytesTransferred 为写入 buffer 的字节数。
-
令 result 为 新的
USBInTransferResult。 -
如果从设备接收到数据,则创建一个 新的
ArrayBuffer, 其包含 buffer 的前 bytesTransferred 个字节,并将 result.data设为在其之上构建的一个 新的DataView。 -
将 result.
status设为: -
如果传输因其他任何原因失败,则拒绝 promise,拒因为 "
NetworkError", 并中止这些步骤。 -
将 promise 解析为 result。
-
-
-
返回 promise。
controlTransferOut(setup, data)
方法在被调用时,必须执行以下步骤:
-
如果用 检查控制传输参数的有效性,输入为 this 和 setup,返回一个
Promise, 则返回该值。 -
获取缓冲区源 data 的一份拷贝,并令结果为 bytes。
-
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
向 控制传输 的目标 this 发起传输,其 设置包 参数来自 setup,将
bmRequestType中的数据传输方向设为 “host to device”,并将wLength设为 bytes 的长度。 -
在传输的 数据阶段 发送 bytes。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 result 为 新的
USBOutTransferResult。 -
将 result.
status设为:-
"ok", 否则,并将 result.bytesWritten设为 bytes 的长度。
-
如果传输因其他任何原因失败,则拒绝 promise, 拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 promise 解析为 result。
-
-
-
返回 promise。
clearHalt(direction, endpointNumber)
方法在被调用时,必须执行以下步骤:
-
令 endpointAddress 为
endpointNumber | 0x80,如果 direction 等于"in", 否则为 endpointNumber。 -
如果 endpoint 为
null,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
向设备发出
ClearFeature(ENDPOINT_HALT)控制传输 以清除 endpoint 上的停滞状态。 -
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
如果上述 控制传输 失败,则拒绝 promise, 拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 promise 解析为
undefined。
-
-
-
返回 promise。
transferIn(endpointNumber, length)
方法在被调用时,必须执行以下步骤:
-
令 endpointAddress 为
endpointNumber | 0x80(即"in"方向)。 -
如果 endpoint 为
null,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 endpoint.
type不等于"bulk"或"interrupt", 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidAccessError"DOMException。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
令 buffer 为一个具有 length 字节空间的主机缓冲区。
-
视 endpoint 的类型,执行 批量 或 中断 IN 传输 于 endpoint 上,从设备接收 length 字节数据到 buffer。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 bytesTransferred 为写入 buffer 的字节数。
-
令 result 为 新的
USBInTransferResult。 -
如果从设备接收到数据,则创建一个 新的
ArrayBuffer, 其包含 buffer 的前 bytesTransferred 个字节,并将 result.data设为在其之上构建的一个 新的DataView。 -
将 result.
status设为: -
如果传输因其他任何原因失败,则拒绝 promise,拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 promise 解析为 result。
-
-
-
返回 promise。
transferOut(endpointNumber, data)
方法在被调用时,必须执行以下步骤:
-
令 endpointAddress 为 endpointNumber(即
"out"方向)。 -
如果 endpoint 为
null,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 endpoint.
type不等于"bulk"或"interrupt", 则返回 一个以拒绝状态完成的 promise,其拒因为 "InvalidAccessError"DOMException。 -
获取缓冲区源 data 的一份拷贝,并令结果为 bytes。
-
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
视 endpoint 的类型,执行 批量 或 中断 OUT 传输 于 endpoint 上,将 bytes 发送到设备。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 result 为 新的
USBOutTransferResult。 -
将 result.
bytesWritten设为成功发送到设备的数据量。 -
将 result.
status设为: -
如果传输因其他任何原因失败,则拒绝 promise,拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 promise 解析为 result。
-
-
-
返回 promise。
isochronousTransferIn(endpointNumber, packetLengths)
方法在被调用时,必须执行以下步骤:
-
令 endpointAddress 为
endpointNumber | 0x80(即"in"方向)。 -
如果 endpoint 为
null,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 endpoint.
type不等于"isochronous", 则返回 一个以拒绝状态完成的 promise, 其拒因为 "InvalidAccessError"DOMException。 -
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
令 totalLength 为 packetLengths 各元素之和。
-
令 buffer 为一个具有 totalLength 字节空间的主机缓冲区。
-
在 endpoint 上执行一次 等时 IN 传输,按照 packetLengths 从设备读取分组并写入 buffer。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 result 为 新的
USBIsochronousInTransferResult。 -
令 data 为一个 新的
ArrayBuffer,以 buffer 初始化。 -
对于每个分组 i 从
0到packetLengths.length - 1:-
令 packet 为 新的
USBIsochronousInTransferPacket。 -
令 view 为一个 新的
DataView, 其覆盖 data 中包含该分组从设备接收数据的部分,并将 packet.data设为 view。 -
将 packet.
status设为: -
如果传输因其他任何原因失败,则拒绝 promise,拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 result.
packets[i] 设为 packet。
-
-
将 promise 解析为 result。
-
-
-
返回 promise。
isochronousTransferOut(endpointNumber, data, packetLengths)
方法在被调用时,必须执行以下步骤:
-
令 endpointAddress 为 endpointNumber(即
"out"方向)。 -
如果 endpoint 为
null,返回 一个以拒绝状态完成的 promise,其拒因为 "NotFoundError"DOMException。 -
如果 endpoint.
type不等于"isochronous", 则返回 一个以拒绝状态完成的 promise, 其拒因为 "InvalidAccessError"DOMException。 -
获取缓冲区源 data 的一份拷贝,并令结果为 bytes。
-
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
在 endpoint 上执行一次 等时 OUT 传输,将 bytes 写入设备,分成
packetLengths.length个分组, 每个分组为 packetLengths[i] 字节(对于分组 i 从0到packetLengths.length - 1)。 -
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
令 result 为 新的
USBIsochronousOutTransferResult。 -
对于每个分组 i 从
0到packetLengths.length - 1:-
令 packet 为 新的
USBIsochronousOutTransferPacket。 -
将 packet.
bytesWritten设为作为该分组的一部分成功发送到设备的数据量。 -
将 packet.
status设为: -
如果传输因其他任何原因失败,则拒绝 promise,拒因为 "
NetworkError"DOMException, 并中止这些步骤。 -
将 result.
packets[i] 设为 packet。
-
-
将 promise 解析为 result。
-
-
-
返回 promise。
reset() 方法在被调用时,必须执行以下步骤:
-
令 promise 为 一个新的 promise。
-
并行运行以下步骤。
-
中止设备上的所有操作,并在 全局任务队列中入队一个基于 USB 任务源 的任务, 以拒绝其相关的 promise,拒因为 "
AbortError"DOMException。 -
执行必要的平台特定操作以对设备进行软重置。
-
在全局任务队列中入队一个基于 USB 任务源、给定 global 的任务, 以运行以下步骤:
-
如果软重置失败,则拒绝 promise,拒因为 "
NetworkError", 并中止这些步骤。 -
将 promise 解析为
undefined。
-
-
-
返回 promise。
设备重置后处于什么配置? [Issue #36]
6.2. USBControlTransferParameters 字典
enum {USBRequestType ,"standard" ,"class" };"vendor" enum {USBRecipient ,"device" ,"interface" ,"endpoint" };"other" dictionary {USBControlTransferParameters required USBRequestType requestType ;required USBRecipient recipient ;required octet request ;required unsigned short value ;required unsigned short ; };index
USBDevice
device 和 USBControlTransferParameters
setup 来检查控制传输参数的有效性,
执行以下步骤:
-
令 configuration 为用 device 查找当前配置 的结果。
-
如果 configuration 为
null,返回undefined。 -
如果 setup.
recipient为"interface", 则执行以下步骤:-
令 interfaceNumber 为 setup.
index的低 8 位。 -
令 interfaceIndex 为用 interfaceNumber 和 configuration 查找接口索引 的结果。
-
如果 interfaceIndex 等于
-1,返回 一个被拒绝的 promise,其拒因为 "NotFoundError"DOMException。 -
令 interface 为 configuration.
[[interfaces]][interfaceIndex]。 -
如果用 interface 查找接口是否已被声明 的结果不为
true,返回 一个被拒绝的 promise,其拒因为 "InvalidStateError"DOMException。
-
-
如果 setup.
recipient为"endpoint", 则运行以下步骤:-
令 endpointAddress 为 setup.
index。 -
令 endpoint 为用 endpointAddress 和 device 查找端点 的结果。
-
如果 endpoint 为
null,返回 一个被拒绝的 promise,其拒因为 "NotFoundError"DOMException。 -
令 alternate 为 endpoint.
[[alternateInterface]]。 -
令 interface 为 alternate.
[[interface]]。 -
如果用 interface 查找接口是否已被声明 的结果为
false,返回 一个被拒绝的 promise,其拒因为 "InvalidStateError"DOMException。
-
-
返回
undefined。
6.2.1. 成员
requestType, 类型为 USBRequestType-
requestType属性填充 设置包 中的bmRequestType字段的一部分,以指示 此请求是 USB 标准的一部分、某个特定 USB 设备类规范,还是供应商特定协议的一部分。 recipient, 类型为 USBRecipient-
recipient属性填充 设置包 中的bmRequestType字段的一部分,以指示 控制传输 是面向整个设备,还是某个特定的接口或端点。 request, 类型为 octet-
request属性填充 设置包 的bRequest字段。有效请求由 USB 标准、USB 设备类规范或设备供应商定义。 value, 类型为 unsigned short-
value和index属性分别填充 设置包 的wValue与wIndex字段。 这些字段的含义取决于所发起的请求。
6.3. USBConfiguration 接口
[Exposed =(Worker ,Window ),SecureContext ]interface {USBConfiguration constructor (USBDevice ,device octet );configurationValue readonly attribute octet configurationValue ;readonly attribute DOMString ?configurationName ;readonly attribute FrozenArray <USBInterface >interfaces ; };
USBConfiguration
的实例具有以下表格中描述的 内部槽位:
| 内部槽位 | 初始值 | 描述(非规范性) |
|---|---|---|
[[device]]
| <在正文中设置> | USBDevice
对象 this 所属的对象。
|
[[interfaces]]
| 一个空的由 USBInterface
组成的 sequence
| 此配置支持的所有接口。 |
[[configurationValue]]
| <在正文中设置> | 此配置对应的配置值。 |
-
令 deviceDescriptor 为 查找已连接 USB 设备的设备描述符 的结果。
-
令 numConfigurations 为 deviceDescriptor 的
bNumConfigurations。 -
令 configurationIndex 为 0。
-
当 configurationIndex 小于 numConfigurations 时:
-
令 descriptors 为通过执行 Get Descriptor、将
DescriptorType设为CONFIGURATION且DescriptorIndex设为 configurationIndex 所得到的描述符列表。 -
如果 descriptors[0] 的
bDescriptorType等于CONFIGURATION且 descriptors 的bConfigurationValue等于 configurationValue,则返回 descriptors。 -
将 configurationIndex 加 1。
-
-
返回 空 结果。
6.3.1. 构造函数
USBConfiguration(device, configurationValue)
构造函数在被调用时必须执行以下步骤:
-
将 this.
[[device]]设为 device。 -
将 this.
[[configurationValue]]设为 configurationValue。 -
令 descriptors 为以
configurationValue设为 configurationValue 来 查找配置的描述符列表 的结果。 -
令 seen 为一个空的 有序集合。
-
对 descriptors 中的每个 descriptor:
-
如果 descriptor 的
bDescriptorType不等于INTERFACE,跳过。 -
如果 descriptor 的
bInterfaceNumber在 seen 中,跳过。 -
令 interface 为一个 新的
USBInterface对象,使用 USBInterface(configuration,interfaceNumber), 其中 configuration 设为 this, interfaceNumber 设为 descriptor 的bInterfaceNumber。 -
将 interface 追加到 this.
[[interfaces]]。 -
将 descriptor 的
bInterfaceNumber追加到 seen。
-
6.3.2. 属性
configurationValue, 类型为 octet,只读-
每个设备配置都应具有唯一的
configurationValue, 它与定义该配置的 配置描述符 的bConfigurationValue字段匹配。 configurationName, 类型为 DOMString,只读,可空-
configurationName属性应包含 配置描述符 的iConfiguration字段所引用的 字符串描述符 的值(如果已定义)。 interfaces, 类型为 FrozenArray<USBInterface>,只读-
interfaces属性应包含此设备配置公开的接口列表。interfaces的 getter 步骤为:-
返回 this.
[[interfaces]]。
-
需要包含一些关于设备配置的非规范性信息。[Issue #46]
6.4. USBInterface 接口
[Exposed =(Worker ,Window ),SecureContext ]interface {USBInterface constructor (USBConfiguration ,configuration octet );interfaceNumber readonly attribute octet interfaceNumber ;readonly attribute USBAlternateInterface alternate ;readonly attribute FrozenArray <USBAlternateInterface >alternates ;readonly attribute boolean claimed ; };
USBInterface
的实例具有以下表格中描述的 内部槽位:
| 内部槽位 | 初始值 | 描述(非规范性) |
|---|---|---|
[[configuration]]
| <在正文中设置> | USBConfiguration
对象 this 所属的对象。
|
[[interfaceNumber]]
| <在正文中设置> | 此接口的接口编号。 |
[[alternates]]
| 一个空的由 USBAlternateInterface
组成的 sequence
| 此接口支持的所有备选设置。 |
[[isProtectedClass]]
| false
| 此接口是否具有属于受保护类别的任何备选设置。 |
接口描述符
interface 当且仅当其
bInterfaceClass 等于以下值之一时,具有受保护的接口类别。
| 代码 | 描述 |
|---|---|
0x01
| 音频(Audio) |
0x03
| HID(人机接口设备) |
0x08
| 大容量存储(Mass Storage) |
0x0B
| 智能卡(Smart Card) |
0x0E
| 视频(Video) |
0x10
| 音频/视频设备(Audio/Video Devices) |
0xE0
| 无线控制器(Wireless Controller) |
注意: 本规范试图在保护用户免受恶意内容侵害(通过限制对敏感设备的访问)与尽可能支持更多设备之间取得平衡。 如介绍所述,此 API 的目标是支持其他更高级别 API 未覆盖的设备。上述列表包含了已有高级别 API 的接口类别, 这些高级别 API 相比通过本 API 的低级访问,能为用户隐私和安全提供更好的保护。
-
令 configuration 为 interface.
[[configuration]]。 -
令 device 为 configuration.
[[device]]。 -
如果 configuration 与用 device 查找当前配置 的结果不同,返回
false。 -
令 interfaceIndex 为用 interface.
[[interfaceNumber]]和 configuration 查找接口索引 的结果。 -
断言:interfaceIndex 不等于
-1。 -
返回 device.
[[claimedInterface]][interfaceIndex]。
-
令 configuration 为 interface.
[[configuration]]。 -
令 device 为 configuration.
[[device]]。 -
令 alternateIndex 为 0。
-
如果用 interface 查找接口是否已被声明 的结果为
true:-
令 interfaceIndex 为用 interface.
[[interfaceNumber]]和 configuration 查找接口索引 的结果。 -
断言:interfaceIndex 不等于
-1。 -
将 alternateIndex 设为用 device.
[[selectedAlternateSetting]][interfaceIndex] 和 interface 查找备选索引 的结果。
-
-
断言:alternateIndex 不等于
-1。 -
返回 interface.
alternates[alternateIndex]。
注意: 根据 接口描述符 [USB31],每个接口至少有一个备选设置, 因为在 接口描述符 中必须至少存在一个备选设置。
6.4.1. 构造函数
USBInterface(configuration, interfaceNumber)
构造函数在被调用时必须执行以下步骤:
-
将 this.
[[configuration]]设为 configuration。 -
将 this.
[[interfaceNumber]]设为 interfaceNumber。 -
令 descriptors 为以
configurationValue设为 configuration.[[configurationValue]]来 查找配置的描述符列表 的结果。 -
对 descriptors 中的每个 descriptor:
-
如果 descriptor 的
bDescriptorType不等于INTERFACE,则继续。 -
如果 descriptor 的
bInterfaceNumber不等于 interfaceNumber,则继续。 -
如果 具有受保护的接口类别,则将 this.
[[isProtectedClass]]设为true。 -
令 alternate 为一个 新的
USBAlternateInterface对象, 使用 USBAlternateInterface(deviceInterface,alternateSetting), 其中 deviceInterface 设为 this, alternateSetting 设为 descriptor 的bAlternateSetting。 -
将 alternate 追加到 this.
[[alternates]]。
-
6.4.2. 属性
interfaceNumber, 类型为 octet,只读-
每个接口提供一组由其 接口描述符 中的
bInterfaceNumber字段标识的alternates。interfaceNumber属性必须与该字段匹配。interfaceNumber的 getter 步骤为: alternate, 类型为 USBAlternateInterface,只读-
alternate属性应被设为当前为该接口选择的USBAlternateInterface, 默认应为其bAlternateSetting等于0的那一个。alternate的 getter 步骤为:-
返回以 查找当前备选设置对应的备选接口、参数为 this 的结果。
-
alternates, 类型为 FrozenArray<USBAlternateInterface>,只读-
alternates方法提供由其 接口描述符 中的单个bInterfaceNumber字段标识的USBAlternateInterface对象集合。alternates的 getter 步骤为:-
返回 this.
[[alternates]]。
-
claimed, 类型为 boolean,只读-
当该接口被当前执行上下文声明时,
claimed属性应设为true;否则应设为false。claimed的 getter 步骤为:-
返回以 查找接口是否已被声明、参数为 this 的结果。
-
6.5. USBAlternateInterface 接口
[Exposed =(Worker ,Window ),SecureContext ]interface {USBAlternateInterface constructor (USBInterface ,deviceInterface octet );alternateSetting readonly attribute octet alternateSetting ;readonly attribute octet interfaceClass ;readonly attribute octet interfaceSubclass ;readonly attribute octet interfaceProtocol ;readonly attribute DOMString ?interfaceName ;readonly attribute FrozenArray <USBEndpoint >endpoints ; };
USBAlternateInterface
的实例具有以下表格中描述的 内部槽位:
| 内部槽位 | 初始值 | 描述(非规范性) |
|---|---|---|
[[interface]]
| <在正文中设置> | USBInterface
对象 this 所属的对象。
|
[[endpoints]]
| 一个空的由 USBEndpoint
组成的 sequence
| 此接口公开的所有端点。 |
[[alternateSetting]]
| <在正文中设置> | 此接口的备选设置值。 |
-
令 descriptors 为以 configurationValue 来 查找配置的描述符列表 的结果。
-
令 endpointDescriptors 为一个空的 列表。
-
令 index 为 0。
-
当 index 小于 descriptors 的 大小 时:
-
令 descriptor 为 descriptors[index]。
-
如果 descriptor 的
bDescriptorType不等于INTERFACE, 将 index 加 1 并继续。 -
如果 descriptor 的
bInterfaceNumber不等于 interfaceNumber, 将 index 加 1 并继续。 -
如果 descriptor 的
bAlternateSetting不等于 alternateSetting, 将 index 加 1 并继续。 -
如果
bNumEndpoints等于 0,返回 endpointDescriptors。 -
令 numEndpoints 为 descriptor 的
bNumEndpoints。 -
令 indexEndpoints 为 0。
-
令 offset 为 index + 1。
-
当 indexEndpoints 小于 numEndpoints 时:
-
将 descriptors[indexEndpoints + offset] 追加到 endpointDescriptors。
-
将 indexEndpoints 加 1。
-
-
返回 endpointDescriptors。
-
6.5.1. 构造函数
USBAlternateInterface(deviceInterface, alternateSetting)
构造函数在被调用时必须执行以下步骤:
-
将 this.
[[interface]]设为 deviceInterface。 -
将 this.
[[alternateSetting]]设为 alternateSetting。 -
令 descriptors 为 查找端点描述符列表 的结果, 其中
interfaceNumber设为 deviceInterface.interfaceNumber,alternateSetting设为 alternateSetting,configurationValue设为 deviceInterface.[[configuration]].[[configurationValue]]。 -
对 descriptors 中的每个 descriptor:
-
如果 descriptor 的
bmAttributes表明它是控制传输类型(即根据 端点描述符,位0..1为00),则继续。 -
令 endpointAddress 为 descriptor 的
bEndpointAddress。 -
令 endpoint 为一个 新的
USBEndpoint对象, 使用 USBEndpoint(alternate,endpointNumber,direction), 其中 alternate 设为 this, endpointNumber 设为endpointAddress & 0xF, direction 设为 dir。 -
将 endpoint 追加到 this.
[[endpoints]]。
-
6.5.2. 属性
alternateSetting, 类型为 octet,只读-
在给定接口内,每个备选接口配置应具有唯一的
alternateSetting, 并与定义它的 接口描述符 的bAlternateSetting字段匹配。 interfaceClass, 类型为 octet,只读interfaceSubclass, 类型为 octet,只读interfaceProtocol, 类型为 octet,只读-
interfaceClass、interfaceSubclass和interfaceProtocol属性声明了该接口所支持的通信接口。它们必须分别对应于 接口描述符 的bInterfaceClass、bInterfaceSubClass和bInterfaceProtocol字段的取值。 interfaceName, 类型为 DOMString,只读,可空-
interfaceName属性应包含 接口描述符 的iInterface字段所索引的 字符串描述符 的值(如果已定义)。 endpoints, 类型为 FrozenArray<USBEndpoint>,只读-
endpoints属性应包含此接口公开的端点列表。这些端点应从该 接口描述符 所包含的 端点描述符 中填充, 且该序列的元素数量应与 接口描述符 的bNumEndpoints字段相匹配。endpoints的 getter 步骤为:-
返回 this.
[[endpoints]]。
-
6.6. USBEndpoint 接口
enum {USBDirection ,"in" };"out" enum {USBEndpointType ,"bulk" ,"interrupt" }; ["isochronous" Exposed =(Worker ,Window ),SecureContext ]interface {USBEndpoint (constructor USBAlternateInterface ,alternate octet ,endpointNumber USBDirection );direction readonly attribute octet endpointNumber ;readonly attribute USBDirection direction ;readonly attribute USBEndpointType type ;readonly attribute unsigned long packetSize ; };
USBEndpoint
的实例具有下表所述的 内部槽位:
| 内部槽位 | 初始值 | 描述(非规范性) |
|---|---|---|
[[alternateInterface]]
| <在正文中设置> | USBAlternateInterface
对象,即 this 所隶属的对象。
|
[[endpointAddress]]
| <在正文中设置> | 此端点的端点地址。 |
-
令 alternateInterface 为 endpoint.
[[alternateInterface]]。 -
令 interface 为 alternateInterface.
[[interface]]。 -
令 configuration 为 interface.
[[configuration]]。 -
令 endpointDescriptors 为 查找端点描述符列表 的结果,其中
alternateSetting设为 alternateInterface.alternateSetting,interfaceNumber设为 interface.interfaceNumber,configurationValue设为 configuration.configurationValue。 -
对于 endpointDescriptors 中的每个 endpointDescriptor:
-
如果 endpoint.
[[endpointAddress]]等于 endpointDescriptor 的bEndpointAddress,则返回 endpointDescriptor。
-
6.6.1. 构造函数
endpointNumber(alternate, endpointNumber, direction)
构造函数在被调用时必须执行以下步骤:
-
将 this.
[[alternateInterface]]设为 alternate。 -
若 direction 为
"in",则将 endpointAddress 设为endpointNumber | 0x80;否则设为 endpointNumber。 -
将 this.
[[endpointAddress]]设为 endpointAddress。
6.6.2. 属性
endpointNumber, 类型为 octet,只读direction, 类型为 USBDirection,只读-
在特定设备配置内,每个端点应具有唯一的
endpointNumber与direction的组合。endpointNumber必须等于定义该端点的 端点描述符 中bEndpointAddress字段的低 4 位。direction属性声明该端点支持的数据传输方向;若bEndpointAddress的最高有效位被置位,则其值为"in", 否则为"out"。一个端点要么承载从设备到主机的IN数据,要么承载从主机到设备的OUT数据。 type, 类型为 USBEndpointType,只读-
type属性声明该端点支持的数据传输类型。type的 getter 步骤为:-
令 attr 为 endpointDescriptor 的
bmAttributes。 -
令 typeBits 为
attr & 0x3。 -
若 typeBits 等于
b01,返回"isochronous"。 -
若 typeBits 等于
b10,返回"bulk"。 -
若 typeBits 等于
b11,返回"interrupt"。
注意: 按照 USBAlternateInterface(deviceInterface,alternateSetting) 的步骤,不应存在属于控制传输类型(Control Transfer Type)的端点对象。
packetSize, 类型为 unsigned long,只读-
packetSize属性声明此端点采用的数据包大小,并且必须等于定义该端点的 端点描述符 中wMaxPacketSize的值。在高速(High-Speed)、高带宽(High-Bandwidth)的端点中,该值包含通过每微帧多次事务所提供的乘数因子;在 SuperSpeed 设备中,该值包含 SuperSpeed 端点伴随描述符(Endpoint Companion descriptor)中bMaxBurst字段所提供的乘数因子。packetSize的 getter 步骤为:
7. USB 阻止列表
// USBBlocklistEntry is never exposed.dictionary {USBBlocklistEntry required unsigned short ;idVendor required unsigned short ;idProduct required unsigned short ; };bcdDevice
本规范依赖于此代码库中的 blocklist.txt 文件来限制网站可访问的设备集合。
在某个 URL
url 处解析阻止列表 的结果,是通过以下算法生成的
列表(其元素为 USBBlocklistEntry
对象):
-
获取 url,令 contents 为其正文,并以 UTF-8 解码。
-
令 lines 为从 contents 开头开始,在码位
'\n'上进行严格分割的结果。 -
令 blocklist 为一个空的 列表。
-
对于 lines 中的每一行 line:
-
将 line 设为自行开头起、从 line 中收集一段码位序列(直至遇到不等于
'#'的码位)所得的结果。 -
将 line 设为从 line 中去除前导与尾随 ASCII 空白 的结果。
-
令 components 为自 line 开头、在码位
':'上对 line 进行严格分割 的结果。 -
令 idVendor 为将 components[0] 按十六进制数解释的结果。
-
令 idProduct 为将 components[1] 按十六进制数解释的结果。
-
令 bcdDevice 为
0xFFFF。 -
若 components 的大小 为 3,则将 bcdDevice 设为将 components[2] 按十六进制数解释的结果。
-
追加 一个新的
USBBlocklistEntry, 其包含 idVendor、idProduct 与 bcdDevice,并将其加入 blocklist。
-
-
返回 blocklist。
USB 阻止列表是在 解析阻止列表 https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt 后得到的结果。 UA 应当定期重新获取该阻止列表,但获取频率未作规定。
若以下步骤返回 “blocked”,则 USBDevice
device 对于某个 Document
document 被视为在阻止列表中:
-
如果 document 非
null且 document 被允许使用 名为"usb-unrestricted"的策略控制特性,则返回 “not blocked”。 -
-
令 bcdDevice 为 device.
deviceVersionMajor<< 8 + device.deviceVersionMinor<< 4 + device.deviceVersionSubminor。 -
如果 bcdDevice 小于或等于 entry.
bcdDevice, 返回 “blocked”。
-
返回 “not blocked”。
8. 集成
8.1. 权限策略
本规范定义了一个由令牌 "usb" 标识的策略控制特性,用于控制 usb
属性是否暴露在 Navigator
对象上。
此特性的 默认允许列表为
["self"]。
本规范还定义了第二个由令牌 "usb-unrestricted" 标识的策略控制特性,用于控制是否可以访问在阻止列表中的 USB 设备以及具有受保护类别的设备接口。该特性必须仅为在 Web
应用清单中声明了该特性的
Isolated Web Apps 启用(参见 [APPMANIFEST])。
此特性的 默认允许列表为
["self"]。通常这意味着默认允许该特性用于处于顶层可遍历中的 Document。
然而,由于要求该特性仅对在清单中声明了该特性的 Isolated Web Apps 启用,其有效的默认允许列表为 ["none"]。
8.2. 权限 API
[permissions] API 为网站提供了一种统一的方式来向用户请求权限并查询已获得的权限。
"usb" 这一强大特性(powerful feature)定义如下:
- 权限描述符类型
-
dictionary :USBPermissionDescriptor PermissionDescriptor {sequence <USBDeviceFilter >;filters sequence <USBDeviceFilter >; };exclusionFilters - 额外权限数据类型
-
USBPermissionStorage, 定义如下:dictionary {AllowedUSBDevice required octet ;vendorId required octet ;productId DOMString ; };serialNumber dictionary {USBPermissionStorage sequence <AllowedUSBDevice >= []; };allowedDevices AllowedUSBDevice实例具有一个内部槽位[[devices]],其中保存一组USB 设备。 - 权限结果类型
-
[
Exposed =(Worker ,Window )]interface :USBPermissionResult PermissionStatus {attribute FrozenArray <USBDevice >; };devices - 权限查询算法
-
要使用
USBPermissionDescriptordesc、USBPermissionStoragestorage 与USBPermissionResultstatus 来查询 “usb” 权限,UA 必须:-
如果
desc.已设置,则对于filtersdesc.中的每个 filter,若 filter 不是有效的过滤器,则抛出filtersTypeError并中止这些步骤。 -
如果
desc.已设置,则对于exclusionFiltersdesc.中的每个 exclusionFilter,若 exclusionFilter 不是有效的过滤器,则抛出exclusionFiltersTypeError并中止这些步骤。 -
令 matchingDevices 为一个新的
Array。 -
对于
storage.中的每个 allowedDevice,以及allowedDevicesallowedDevice@中的每个 device,执行以下子步骤:[[devices]]-
如果
desc.已设置,且 device 未能匹配filtersdesc.中的某个设备过滤器,则继续下一个 device。filters -
如果
desc.已设置,且 device 匹配exclusionFiltersdesc.中的某个设备过滤器,则继续下一个 device。exclusionFilters -
获取表示 device 的
USBDevice并将其加入 matchingDevices。
-
-
将
status.设为一个新的devicesFrozenArray,其内容为 matchingDevices。
-
- 权限请求算法
- 请求 “usb” 权限。
9. 术语
本规范使用了一些来自 [USB31] 的术语。尽管引用的是 USB 3.1 版本,但其中许多概念在更早的版本中也已存在。与本规范相关的不同 USB 版本之间的显著差异将明确指出。
描述符(Descriptors) 是可以从设备读取的二进制数据结构,用于描述其属性与功能:
-
设备描述符(device descriptor) 包含适用于整个设备的信息,详见 [USB31] 第 9.6.1 节。
-
配置描述符(configuration descriptor) 描述一组可由主机选择的设备接口与端点,其字段详见 [USB31] 第 9.6.3 节。
-
接口描述符(interface descriptor) 描述设备某个功能组件的接口,包括其协议与通信端点,其字段详见 [USB31] 第 9.6.5 节。
-
接口关联描述符(interface association descriptor) 在属于同一功能单元的多个接口之间创建关联,其字段详见 [USB31] 第 9.6.4 节。
-
端点描述符(endpoint descriptor) 描述一个数据通道,数据通过该通道发送到设备或从设备接收,其字段详见 [USB31] 第 9.6.6 节。
-
字符串描述符(string descriptor) 包含一个 UTF16-LE 字符串,并被其他描述符通过索引引用,其字段详见 [USB31] 第 9.6.9 节。
二进制对象存储(Binary Object Store,BOS) 是一组较为自由形式的额外描述符。值得注意的是其中的 平台描述符(Platform Descriptor) 类型,允许第三方(例如本规范)声明自定义类型的描述符。每种类型由一个 UUID 标识。二进制对象存储详见 [USB31] 第 9.6.2 节。
USB 设备 具有一个 设备描述符,并链接到一个或多个
配置描述符。其
厂商 ID(vendor ID) 由 USB-IF
分配给设备制造商,并存储于
设备描述符 的
idVendor 字段;其
产品
ID(product
ID) 由制造商分配,并存储于 设备描述符 的 idProduct 字段;其
序列号(serial
number) 是可选属性:当 设备描述符 的
iSerialNumber 字段不等于 0 时定义,其值为该索引引用的 字符串描述符。
Get Configuration 是获取当前设备配置值的请求,详见 [USB31] 第 9.4.2 节。
Set Descriptor 是根据指定的描述符类型与描述符索引获取描述符的请求,详见 [USB31] 第 9.4.8 节。
Get Descriptor 是根据指定的描述符类型与描述符索引获取描述符的请求,详见 [USB31] 第 9.4.3 节。
注意: 由于设备的描述符通常在大多数时间保持不变(除非偶尔发生 Set Descriptor 操作修改描述符),为减少对设备冗余的 Get Descriptor 流量,实现可以采用读通 & 写通缓存层来存储设备的描述符。
控制传输(control transfer) 是 USB 传输中的一种特殊类别,最常用于配置设备。它由三个阶段组成:设置(setup)、数据(data) 与 状态(status)。在设置阶段(setup stage),会向设备发送一个包含请求参数(包括传输方向与后续数据大小)的 设置包(setup packet);在数据阶段(data stage),数据会被发送到设备或从设备接收;在状态阶段(status stage),将确认请求已成功处理,或指示失败。
10. 附录:USB 简介
本节为非规范性内容。
USB 是一种网络,但与传统的 TCP/IP 网络非常不同。它更像是一个 RPC 系统。所有流量都由主机(即你的计算机)主导。尽管某些设备(如智能手机)既可充当 USB 主机,又可充当 USB 客户端,但在同一时间内它们只能承担其中一种角色。
10.1. 描述符
USB 设备通过提供一组称为描述符的二进制结构向主机标识自身。主机首先读取的是设备描述符,其中包含基本信息,如由 USB-IF 和制造商分配的厂商与产品 ID。随后主机可以读取设备的 配置描述符,它描述了设备的能力,包括其公开的接口与端点。类(class)既可在设备级别声明,也可在单个接口级别声明。具有多个提供不同功能的接口的设备称为复合设备(composite device)。
10.2. 传输
无论数据是从主机到设备,还是相反,传输始终由主机发起。OUT 传输 将数据从主机传至设备,并可能等待设备确认已接收数据;IN 传输 将数据从设备传回主机,并可能需要等待设备准备好要发送的数据。传输针对设备的某个端点执行,并根据所发送的流量类型而分为不同种类。
-
批量传输(Bulk transfers) 适合在可用带宽内发送大量数据。这通常用于读取和写入 USB 大容量存储设备的数据。
-
中断传输(Interrupt transfers) 提供保证时延(通过预留带宽以避免被大型批量传输阻塞),但包大小有限。它们用于信号传递和小数据包,如鼠标移动与按键。
-
等时传输(Isochronous transfers) 也会预留带宽,但不保证交付,常用于音视频等流式数据。
-
每个设备还有一个特殊的 默认端点(default endpoint)。常规端点仅承载单向数据,而控制传输拥有名为 SETUP 包 的小型报头,它总是发送到设备并包含请求参数,此外还包含更大的数据负载(可以是 IN 或 OUT)。