Privet 是一种由云服务使用的云设备本地发现 API。本文档分为以下几个部分:
1. 简介
联网的云设备具有诸多优势。它们可以使用在线转换服务,在设备离线时托管作业队列,并且可以从世界上的任何地方访问。不过,由于给定用户可访问许多云设备,我们需要提供一种基于位置信息查找最近设备的方法。Privet 协议旨在将云设备的灵活性与合适的本地发现机制绑定在一起,以便在新的环境中轻松发现设备。
此协议的目标如下:- 使云设备可在本地被发现
- 向云服务注册云设备
- 将已注册的设备与其云端表示形式相关联
- 启用离线功能
- 简化实现,以便小型设备可以利用它
Privet 协议包含 2 个主要部分:发现和 API。发现功能用于在本地网络上查找设备,而 API 用于获取有关设备的信息并执行某些操作。在本文档中,设备是指实现 Privet 协议的云连接设备。
2. 发现
发现是一种基于 Zeroconf (mDNS + DNS-SD) 的协议。设备必须实现 IPv4 链路本地寻址。设备必须符合 mDNS 和 DNS-SD 规范。
- http://www.rfc-editor.org/rfc/rfc3927.txt (IPv4 链路本地)
- http://www.rfc-editor.org/rfc/rfc4862.txt (IPv6 链路本地)
- http://www.rfc-editor.org/rfc/rfc6762.txt (mDNS)
- http://www.rfc-editor.org/rfc/rfc6763.txt (DNS-SD)
设备必须根据上述规范执行名称冲突解决。
2.1. 服务类型
DNS 服务发现使用以下格式表示服务类型:_applicationprotocol._transportprotocol。对于 Privet 协议,DNS-SD 的服务类型应为:_privet._tcp
设备也可以实现其他服务类型。建议为设备实现的所有服务类型使用相同的服务实例名称。例如:打印机可以实现“Printer XYZ._privet._tcp”和“Printer XYZ._printer._tcp”服务。这样可以简化用户设置。不过,Privet 客户端只会查找“_privet._tcp”。
除了主要服务类型之外,设备还必须为其对应的子类型宣传 PTR 记录(请参阅 DNS-SD 规范:“7.1. 选择性实例枚举(子类型)”。 格式应如下所示: _<subtype>._sub._privet._tcp
目前,唯一支持的设备子类型是 printer。因此,所有打印机都必须宣传两个 PTR 记录:
- _privet._tcp.local.
- _printer._sub._privet._tcp.local.
2.2. TXT 记录
DNS 服务发现定义了用于在 TXT 记录中添加有关服务的可选信息的字段。TXT 记录由键值对组成。每个键值对都以长度字节开头,后跟最多 255 个字节的文本。键是第一个“=”字符之前的文本,值是第一个“=”字符之后的文本,直到末尾。规范允许记录中没有值,在这种情况下,将没有“=”字符或“=”字符后没有文本。(请参阅 DNS-SD 规范:有关 DNS TXT 记录格式,请参阅“2.1. DNS-SD TXT 记录大小”(建议的长度)。
Privet 要求设备在 TXT 记录中发送以下键值对。键/值字符串不区分大小写,例如“CS=online”和“cs=ONLINE”是相同的。TXT 记录中的信息必须与通过 /info API 可访问的信息相同(请参阅 4.1.API 部分)。
建议将 TXT 记录大小保持在 512 字节以下。
2.2.1. txtvers
TXT 结构的相应版本。txtvers 必须是 TXT 结构的第一个记录。目前,唯一支持的版本是 1。
txtvers=1
2.2.2. ty
提供设备的用户可读名称。例如:
ty=Google Cloud Ready Printer Model XYZ
2.2.3. 备注(可选)
提供设备的用户可读名称。例如:
note=1st floor lobby printer
注意:这是一个可选的键,可以跳过。不过,如果存在,用户应该能够修改此值。注册设备时必须使用相同的说明。
2.2.4. url
设备所连接的服务器网址(包括协议)。例如:
url=https://www.google.com/cloudprint
2.2.5. 类型
相应设备支持的设备子类型(以英文逗号分隔的列表)。格式为: “type=_subtype1,_subtype2”。目前,唯一受支持的设备子类型是 printer。
type=printer
列出的每个子类型都应使用相应的 PTR 记录进行宣传。对于每个受支持的服务子类型,都应有一个对应的项。服务子类型名称 (<subtype>._sub._privet._tcp) 应与此处的设备类型相同。
2.2.6. id
设备 ID。如果设备尚未注册,则应存在此键,但值应为空。例如:
id=11111111-2222-3333-4444-555555555555 id=
2.2.7. cs
指示设备的当前连接状态。此规范中定义了四种可能的值。
- “online”表示设备目前已连接到云端。
- “离线”表示设备在本地网络上可用,但无法与服务器通信。
- “正在连接”表示设备正在执行启动序列,但尚未完全联网。
- “not-configured”表示设备的互联网访问权限尚未配置。此值目前未使用,但在未来版本的规范中可能会有用。
- cs=online
- cs=offline
- cs=connecting
如果设备已向云注册,则在启动时,它应检查与服务器的连接,以检测其连接状态(例如,调用云 API 以获取设备设置)。设备可能会使用其通知渠道(例如 XMPP)连接状态来报告此值。 未注册的设备在启动时可能会 ping 某个网域,以检测其连接状态(例如,云打印设备会 ping www.google.com)。
3. 通告
在设备启动、关闭或状态更改时,设备必须按照 mDNS 规范中所述执行通告步骤。应至少发送两次相应的公告,两次公告之间至少间隔 1 秒。
3.1. 启动
在设备启动时,必须按照 mDNS 规范中所述执行探测和通告步骤。在这种情况下,应发送 SRV、PTR 和 TXT 记录。建议尽可能将所有记录分组到一个 DNS 响应中。如果不是,建议按以下顺序添加:SRV、PTR、TXT 记录。
3.2. 关停
在设备关机时,它应尝试通过发送 TTL=0 的“告别数据包”(如 mDNS 文档中所述)来通知所有相关方。
3.3. 更新
如果 TXT 中描述的任何信息发生变化,设备必须发送更新公告。在这种情况下,只需发送新的 TXT 记录即可。例如,设备注册后,必须发送包含新设备 ID 的更新公告。
4. API
发现云设备后,客户端即可通过本地网络直接与该设备通信。所有 API 都基于 HTTP 1.1。数据格式基于 JSON。API 请求可以是 GET 请求,也可以是 POST 请求。
每个请求都必须包含有效的“X-Privet-Token”标头。唯一允许具有空“X-Privet-Token”标头的请求是 /privet/info 请求(请注意,标头必须仍然存在)。如果缺少“X-Privet-Token”标头,设备必须返回以下 HTTP 400 错误:
HTTP/1.1 400 Missing X-Privet-Token header.
如果“X-Privet-Token”标头为空或无效,设备必须响应“invalid X-Privet-Token error”(invalid_x_privet_token,详情请参阅“错误”部分)。唯一的例外是 /info API。如需详细了解这样做的原因以及应如何生成令牌,请参阅附录 A:XSSI 和 XSRF 攻击及防范。
如果所请求的 API 不存在或不受支持,设备必须返回 HTTP 404 错误。
4.1. API 可用性
在公开任何 API(包括 /info API)之前,设备必须联系服务器以检查本地设置。本地设置必须在重新启动之间保持不变。如果服务器不可用,则应使用上次已知的本地设置。如果设备尚未注册,则应遵循默认设置。
Cloud Print 设备必须按照以下步骤注册、接收和更新本地设置。
4.1.1. 注册
设备注册时,必须指定“local_settings”参数,如下所示:
{ "current": { "local_discovery": true, "access_token_enabled": true, "printer/local_printing_enabled": true, "printer/conversion_printing_enabled": true, "xmpp_timeout_value": 300 } }
值名称 | 值类型 | 说明 |
---|---|---|
local_discovery | 布尔值 | 指示是否允许本地发现功能。如果为“false”,则必须停用所有本地 API(包括 /info)和 DNS-SD 发现。默认情况下,新注册的设备应传递“true”。 |
access_token_enabled | 布尔值(可选) | 指示是否应在本地网络上公开 /accesstoken API。默认值应为“true”。 |
printer/local_printing_enabled | 布尔值(可选) | 指示是否应在本地网络上公开本地打印功能(/printer/createjob、/printer/submitdoc、/printer/jobstate)。默认值应为“true”。 |
printer/conversion_printing_enabled | 布尔值(可选) | 指示本地打印是否可以将作业发送到服务器进行转换。仅在启用本地打印时才有意义。 |
xmpp_timeout_value | int(可选) | 表示 XMPP 渠道 ping 之间的时间间隔(以秒为单位)。默认情况下必须为 300(5 分钟)或更长时间。 |
重要提示:如果缺少任何可选值,则表示设备完全不支持相应的功能。
4.1.2. 启动
在设备启动时,它应与服务器联系,以检查哪些 API 可在本地网络中公开。对于连接到云打印的打印机,它们应调用:
/cloudprint/printer?printerid=<printer_id>
/cloudprint/list
/cloudprint/printer 优先于 /cloudprint/list,但两者均可正常运行。
此 API 会返回当前设备参数,包括本地 API 的设置。服务器的回复将采用以下格式:
"local_settings": { "current": { "local_discovery": true, "access_token_enabled": true, "printer/local_printing_enabled": true, "printer/conversion_printing_enabled": true, "xmpp_timeout_value": 300 }, "pending": { "local_discovery": true, "access_token_enabled": true, "printer/local_printing_enabled": false, "printer/conversion_printing_enabled": false, "xmpp_timeout_value": 500 } }
“current”对象表示当前生效的设置。
“pending”对象表示应应用于设备的设置(此对象可能缺失)。
设备看到“待处理”设置后,必须更新其状态(见下文)。
4.1.3. 更新
如果需要更新设置,系统会向设备发送 XMPP 通知。通知将采用以下格式:
<device_id>/update_settings
收到此类通知后,设备必须查询服务器以获取最新设置。 云打印设备必须使用:
/cloudprint/printer?printerid=<printer_id>
一旦设备因 /cloudprint/printer API(在启动时或由于通知)而看到“待处理”部分,就必须更新其内部状态以记住新设置。它必须调用服务器 API 来确认新设置。对于云打印机,设备必须调用 /cloudprint/update API 并使用“local_settings”参数(与注册期间一样)。
重新连接到 XMPP 渠道时,设备必须调用 /cloudprint/printer API 来检查自上次以来本地设置是否已更改。
4.1.3.1. 本地设置待处理
设备用于调用服务器 API 的“local_settings”参数绝不能包含“pending”部分。
4.1.3.2. 本地设置当前
只有设备可以更改“local_settings”的“current”部分。 其他所有人都会更改“待处理”部分,并等待设备将更改传播到“当前”部分。
4.1.4. 离线
如果在启动期间无法联系服务器,设备在收到通知后必须使用最近一次的已知本地设置。
4.1.5. 从服务中删除设备
如果设备已从服务(例如 GCP)中删除,系统会向设备发送 XMPP 通知。通知将采用以下格式:
<device_id>/delete
收到此类通知后,设备必须前往服务器检查其状态。云打印设备必须使用:
/cloudprint/printer?printerid=<printer_id>
设备必须收到成功的 HTTP 回答,其中 success=false 且没有设备/打印机说明。这意味着设备已从服务器中移除,并且设备必须擦除其凭据并进入默认出厂设置模式。
每当设备收到指示其已因 /cloudprint/printer API(启动、更新设置通知、每日 ping)而被删除的回复时,都必须删除其凭据并进入默认模式。
4.2. /privet/info API
信息 API 是强制性的,必须由每个设备实现。这是一个针对“/privet/info”网址的 HTTP GET 请求:GET /privet/info HTTP/1.1
信息 API 会返回有关设备及其支持的功能的基本信息。此 API 绝不能更改设备状态或执行任何操作,因为它容易受到 XSRF 攻击。这是唯一允许具有空“X-Privet-Token”标头的 API。客户端应调用 /privet/info API,并将“X-Privet-Token”标头设置为 X-Privet-Token:“”
信息 API 必须返回与发现期间 TXT 记录中提供的数据一致的数据。
4.2.1. 输入
/privet/info API 没有输入参数。
4.2.2. 返回
/privet/info API 会返回有关设备和受支持功能的基本信息。
TXT 列表示 DNS-SD TXT 记录中的相应字段。
值名称 | 值类型 | 说明 | TXT |
---|---|---|---|
版本 | 字符串 | 支持的最高 API 版本(主版本.次版本),目前为 1.0 | |
name | 字符串 | 设备的直观易懂的名称。 | ty |
说明 | 字符串 | (可选)设备说明。应可由用户修改。 | note |
网址 | 字符串 | 相应设备正在与之通信的服务器的网址。网址必须包含协议规范,例如:https://www.google.com/cloudprint。 | 网址 |
类型 | 字符串列表 | 支持的设备类型列表。 | 类型 |
id | 字符串 | 设备 ID,如果设备尚未注册,则为空。 | id |
device_state | 字符串 | 设备的状态。 空闲表示设备已准备就绪 正在处理表示设备正忙,功能可能会暂时受限 已停止表示设备无法正常运行,需要用户干预 | |
connection_state | 字符串 | 与服务器 (base_url) 的连接状态
online - 连接可用 offline - 无连接 connecting - 正在执行启动步骤 not-configured - 尚未配置连接 注册设备可能会根据通知渠道(例如 XMPP 连接状态)的状态报告其连接状态。 | cs |
制造商 | 字符串 | 设备制造商的名称 | |
模型 | 字符串 | 设备的型号 | |
serial_number | 字符串 | 唯一设备标识符。在此规范中,此字段必须是 UUID。(GCP 1.1 规范)
(可选)我们强烈建议您在所有位置使用相同的序列号 ID,以便不同的客户端可以识别同一设备。例如,实现 IPP 的打印机可能会在“printer-device-id”字段中使用此序列号 ID。 | |
固件 | 字符串 | 设备固件版本 | |
运行时间 | int | 自设备启动以来的秒数。 | |
setup_url | 字符串 | (可选)包含设置说明的网页的网址(包括协议) | |
support_url | 字符串 | (可选)包含支持信息、常见问题解答信息的网页的网址(包括协议) | |
update_url | 字符串 | (可选)包含更新固件说明的网页的网址(包括协议) | |
x-privet-token | 字符串 | 必须传递给所有 API 的 X-Privet-Token 标头的值,以防止 XSSI 和 XSRF 攻击。详情请参阅 6.1。 | |
API | API 说明 | 支持的 API 列表(如下所述) | |
semantic_state | JSON | (可选)采用 CloudDeviceState 格式的设备的语义状态。 |
api - 是一个 JSON 列表,其中包含可通过本地网络使用的 API 列表。请注意,并非所有 API 都可能同时通过本地网络提供。例如,新连接的设备应仅支持 /register API:
"api": [ "/privet/register", ]
"api": [ "/privet/accesstoken", "/privet/capabilities", "/privet/printer/submitdoc", ]
目前可用的 API 如下:
- /privet/register - 用于通过本地网络注册设备的 API。(有关详情,请参阅 /privet/register API)。一旦设备成功在云端注册,就必须隐藏此 API。
- /privet/accesstoken - 用于从设备请求访问令牌的 API(有关详情,请参阅 /privet/accesstoken API)。
- /privet/capabilities - 用于检索设备功能的 API(有关详情,请参阅 /privet/capabilities API)。
- /privet/printer/* - 特定于设备类型“打印机”的 API,如需了解详情,请参阅特定于打印机的 API。
{ "version": "1.0", "name": "Gene’s printer", "description": "Printer connected through Chrome connector", "url": "https://www.google.com/cloudprint", "type": [ "printer" ], "id": "11111111-2222-3333-4444-555555555555", "device_state": "idle", "connection_state": "online", "manufacturer": "Google", "model": "Google Chrome", "serial_number": "1111-22222-33333-4444", "firmware": "24.0.1312.52", "uptime": 600, "setup_url": "http://support.google.com/cloudprint/answer/1686197/?hl=en", "support_url": "http://support.google.com/cloudprint/?hl=en", "update_url": "http://support.google.com/cloudprint/?hl=en", "x-privet-token": "AIp06DjQd80yMoGYuGmT_VDAApuBZbInsQ:1358377509659", "api": [ "/privet/accesstoken", "/privet/capabilities", "/privet/printer/submitdoc", ] }
{ "version": "1.0", "name": "Gene’s printer", "description": "Printer connected through Chrome connector", "url": "https://www.google.com/cloudprint", "type": [ "printer" ], "id": "11111111-2222-3333-4444-555555555555", "device_state": "stopped", "connection_state": "online", "manufacturer": "Google", "model": "Google Chrome", "serial_number": "1111-22222-33333-4444", "firmware": "24.0.1312.52", "uptime": 600, "setup_url": "http://support.google.com/cloudprint/answer/1686197/?hl=en", "support_url": "http://support.google.com/cloudprint/?hl=en", "update_url": "http://support.google.com/cloudprint/?hl=en", "x-privet-token": "AIp06DjQd80yMoGYuGmT_VDAApuBZbInsQ:1358377509659", "api": [ "/privet/accesstoken", "/privet/capabilities", "/privet/printer/submitdoc", ], "semantic_state": { "version": "1.0", "printer": { "state": "STOPPED", "marker_state": { "item": [ { "vendor_id": "ink", "state": "EXHAUSTED", "level_percent": 0 } ] } } } }
4.2.3. 错误
仅当缺少 X-Privet-Token 标头时,/privet/info API 才应返回错误。必须是 HTTP 400 错误:
HTTP/1.1 400 Missing X-Privet-Token header.
4.3. /privet/register API
/privet/register API 是可选的。这是一个 HTTP POST 请求。/privet/register API 必须检查是否存在有效的 X-Privet-Token 标头。设备必须在“/privet/register”网址上实现此 API:
POST /privet/register?action=start&user=user@domain.com HTTP/1.1 POST /privet/register?action=complete&user=user@domain.com HTTP/1.1
设备应仅在允许匿名注册时公开 /privet/register API。例如:
- 当设备开启(或在点击设备上的特殊按钮后)且尚未注册时,应公开 /privet/register API,以允许本地网络中的用户声明打印机。
- 注册完成后,设备应停止公开 /privet/register API,以防止本地网络上的其他用户重新声明设备。
- 某些设备可能具有不同的设备注册方式,并且根本不应公开 /privet/register API(例如 Chrome 云打印连接器)。
注册流程包含 3 个步骤(请参阅 Cloud Print 的匿名注册)。
- 启动匿名注册流程。
- 客户端通过调用 /privet/register API 来启动此流程。设备可能会在此时等待用户确认。
- 获取声明令牌。
客户端轮询以确定设备何时准备好继续。设备准备就绪后,会向服务器发送请求,以检索注册令牌和注册网址。收到的令牌和网址应返回给客户端。在此步骤中,如果设备收到另一个用于初始化注册的调用,则应执行以下操作:
- 如果这是开始注册的同一用户,则舍弃所有之前的数据(如果有),然后开始新的注册流程。
- 如果这是不同的用户,则返回 device_busy 错误和 30 秒超时。
完成注册流程。
客户端声明设备后,应通知设备完成注册。注册流程完成后,设备应发送更新公告,其中包含新获取的设备 ID。
注意:当设备正在处理 /privet/register API 调用时,不得同时处理其他 /privet/register API 调用。设备必须返回 device_busy 错误和 30 秒超时。
强烈建议用户在设备上确认注册。如果已实现,设备必须在收到 /privet/register?action=start API 调用后等待用户确认。客户端将调用 /privet/register?action=getClaimToken API,以了解用户确认何时完成以及声明令牌何时可用。如果用户在设备上取消注册(例如,按“取消”按钮),则必须返回 user_cancel 错误。如果用户未在特定时间范围内确认注册,则必须返回 confirmation_timeout 错误。如需了解详情,请参阅“默认值”部分。
4.3.1. 输入
/privet/register API 具有以下输入参数:名称 | 值 |
---|---|
action | 可以是以下值之一:
start - 开始注册流程 getClaimToken - 检索设备的声明令牌 cancel - 取消注册流程 complete - 完成注册流程 |
用户 | 将声明此设备的所有权的用户的电子邮件地址。 |
设备必须检查所有操作(开始、getClaimToken、取消、完成)中的电子邮件地址是否一致。
4.3.2. 返回
/privet/register API 会返回以下数据:值名称 | 值类型 | 说明 |
---|---|---|
action | 字符串 | 与输入参数中的操作相同。 |
用户 | 字符串(可选) | 与输入参数中的用户相同(如果输入中省略了用户,则可能缺失)。 |
token | 字符串(可选) | 注册令牌(对于“getClaimToken”响应是必需的,对于“start”“complete”“cancel”则省略)。 |
claim_url | 字符串(可选) | 注册网址(对于“getClaimToken”响应是必需的,对于“start”“complete”“cancel”则省略)。对于云打印机,它必须是服务器发送的“complete_invite_url”。 |
automated_claim_url | 字符串(可选) | 注册网址(对于“getClaimToken”响应是必需的,对于“start”“complete”“cancel”则省略)。对于云打印机,它必须是从服务器收到的“automated_invite_url”。 |
device_id | 字符串(可选) | 新设备 ID(对于“start”响应,此字段可省略;对于“complete”响应,此字段为必需字段)。 |
设备必须仅在注册完成后,才会在 /privet/info API 响应中返回其设备 ID。
示例 1:
{ "action": "start", "user": "user@domain.com", }
示例 2:
{ "action": "getClaimToken", "user": "user@domain.com", "token": "AAA111222333444555666777", "claim_url": "https://domain.com/SoMeUrL", }
示例 3:
{ "action": "complete", "user": "user@domain.com", "device_id": "11111111-2222-3333-4444-555555555555", }
4.3.3. 错误
/privet/register API 可能会返回以下错误(详情请参阅“错误”部分):错误 | 说明 |
---|---|
device_busy | 设备正忙,无法执行所请求的操作。超时后重试。 |
pending_user_action | 在响应“getClaimToken”时,此错误表示设备仍在等待用户确认,应在超时后重试“getClaimToken”请求。 |
user_cancel | 用户明确从设备取消了注册流程。 |
confirmation_timeout | 用户确认超时。 |
invalid_action | 调用了无效操作。例如,如果客户端在调用 action=start 和 action=getClaimToken 之前调用了 action=complete。 |
invalid_params | 请求中指定的参数无效。(未知参数应被安全地忽略,以实现未来兼容性)。例如,如果客户端调用了 action=unknown 或 user=,则返回此值。 |
device_config_error | 设备端上的日期/时间(或其他某些设置)有误。用户需要前往(设备内部网站)并配置设备设置。 |
离线 | 设备目前处于离线状态,无法与服务器通信。 |
server_error | 注册过程中出现服务器错误。 |
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
成功完成注册后,设备必须停止公开 /privet/register API。如果设备未公开 /privet/register API,则必须返回 HTTP 404 错误。 因此,如果设备已注册,则调用此 API 必须返回 404。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
4.4. /privet/accesstoken API
/privet/accesstoken API 是可选的。这是一个 HTTP GET 请求。/privet/accesstoken API 必须检查是否存在有效的“X-Privet-Token”标头。设备必须在“/privet/accesstoken”网址上实现此 API:GET /privet/accesstoken HTTP/1.1
当设备收到 /accesstoken API 调用时,应调用服务器以检索指定用户的访问令牌,并将该令牌返回给客户端。然后,客户端将使用该访问令牌通过云访问此设备。
Cloud Print 设备必须调用以下 API:
/cloudprint/proximitytoken
"proximity_token": { "user": "user@domain.com", "token": "AAA111222333444555666777", "expires_in": 600 }
4.4.1. 输入
/privet/accesstoken API 具有以下输入参数:名称 | 值 |
---|---|
用户 | 打算使用此访问令牌的用户的电子邮件地址。在请求中可能为空。 |
4.4.2. 返回
/privet/accesstoken API 返回以下数据:值名称 | 值类型 | 说明 |
---|---|---|
token | 字符串 | 服务器返回的访问令牌 |
用户 | 字符串 | 与输入参数中的用户相同。 |
expires_in | int | 相应令牌到期前的秒数。从服务器接收并在此响应中传递。 |
示例:
{ "token": "AAA111222333444555666777", "user": "user@domain.com", "expires_in": 600 }
4.4.3. 错误
/privet/accesstoken API 可能会返回以下错误(详情请参阅“错误”部分):错误 | 说明 |
---|---|
离线 | 设备目前处于离线状态,无法与服务器通信。 |
access_denied | 权限不足。访问遭拒。当请求被服务器明确拒绝时,设备应返回此错误。 |
invalid_params | 请求中指定的参数无效。(未知参数应被安全地忽略,以实现未来兼容性)。例如,如果客户端调用了 /accesstoken?user= 或 /accesstoken。 |
server_error | 服务器错误。 |
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
如果设备未公开 /privet/accesstoken API,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
4.5. /privet/capabilities API
/privet/capabilities API 是可选的。这是一个 HTTP GET 请求。/privet/capabilities API 必须检查是否存在有效的“X-Privet-Token”标头。设备必须在“/privet/capabilities”网址上实现此 API:GET /privet/capabilities HTTP/1.1
4.5.1. 输入
/privet/capabilities API 具有以下输入参数:名称 | 值 |
---|---|
离线 | (可选)只能是“offline=1”。在这种情况下,设备应返回离线使用时的功能(如果这些功能与“在线”功能不同)。 |
4.5.2. 返回
/privet/capabilities API 以云设备说明 (CDD) JSON 格式返回设备功能(有关详情,请参阅 CDD 文档)。打印机必须至少在此处返回受支持类型的列表。例如,当前处于在线状态的云端就绪打印机可能会返回如下内容(至少):{ "version": "1.0", "printer": { "supported_content_type": [ { "content_type": "application/pdf", "min_version": "1.4" }, { "content_type": "image/pwg-raster" }, { "content_type": "image/jpeg" }, { "content_type": "*/*" } ] } }
{ "version": "1.0", "printer": { "supported_content_type": [ { "content_type": "application/pdf", "min_version": "1.4" }, { "content_type": "image/pwg-raster" }, { "content_type": "image/jpeg" } ] } }
注意:打印机使用顺序来表示支持的内容类型优先级。例如,在上述示例中,打印机指定它更偏好“application/pdf”数据,而不是“image/pwg-raster”和“image/jpeg”。客户端应尽可能遵循打印机优先级(详情请参阅 CDD 文档)。
4.5.3. 错误
/privet/capabilities API 可能会返回以下错误(详情请参阅“错误”部分):错误 | 说明 |
---|---|
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
如果设备未公开 /privet/capabilities API,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
4.6. 错误
上述 API 返回的错误采用以下格式:值名称 | 值类型 | 说明 |
---|---|---|
错误 | 字符串 | 错误类型(按 API 定义) |
说明 | 字符串(可选) | 直观易懂的错误说明。 |
server_api | 字符串(可选) | 如果出现服务器错误,此字段会包含失败的服务器 API。 |
server_code | int(可选) | 如果出现服务器错误,此字段会包含服务器返回的相应错误代码。 |
server_http_code | int(可选) | 如果出现服务器 HTTP 错误,此字段会包含服务器返回的 HTTP 错误代码。 |
超时 | int(可选) | 客户端在重试之前等待的秒数(仅针对可恢复的错误)。客户端必须将实际超时时间从此值随机化为比此值大 20% 的值。 |
如果缺少 X-Privet-Token 标头,所有 API 都必须返回 HTTP 400 错误。
HTTP/1.1 400 Missing X-Privet-Token header.
示例 1:
{ "error": "server_error", "description": "Service unavailable", "server_api": "/submit", "server_http_code": 503 }
示例 2:
{ "error": "printer_busy", "description": "Printer is currently printing other job", "timeout": 15 }
5. Printer API
此协议支持的设备类型之一是打印机。支持此类型的设备可以实现一些特定于打印机的功能。理想情况下,向云端打印机发送的打印作业将通过云打印服务器:

在某些情况下,客户可能需要在本地发送文件。当客户端没有 Google ID 或无法与云打印服务器通信时,可能需要此功能。在这种情况下,打印任务将本地提交到打印机。打印机反过来会使用云打印服务进行作业排队和转换。打印机将重新向云打印服务发布本地提交的作业,然后请求该作业,因为该作业是通过云提交的。此流程将提供灵活的服务(转换)和打印作业管理/跟踪用户体验。

由于 Cloud Print 服务实现了转换,因此打印机应在支持的内容类型列表中宣传支持所有输入格式(“*/*”):
{ "version": "1.0", "printer": { "supported_content_type": [ { "content_type": "image/pwg-raster" }, { "content_type": "*/*" } ] } }
在某些情况下,用户可能需要完全离线的解决方案。由于打印机支持的输入格式数量有限,因此客户端需要将文档转换为打印机原生支持的几种格式。

此规范要求所有打印机都必须至少支持 PWG Raster ("image/pwg-raster") 格式,以用于离线打印。打印机可能支持其他格式(例如 JPEG),如果客户端支持,则可以发送该格式的文档。打印机必须通过 /capabilities API 公开支持的类型,例如:
{ "version": "1.0", "printer": { "supported_content_type": [ { "content_type": "image/pwg-raster" }, { "content_type": "image/jpeg" } ] } }
简单打印 - 客户端通过本地网络将文档发送到 /submitdoc API(不指定 job_id 参数)。提交的文档将使用默认打印凭据设置进行打印,无需任何打印作业状态。如果打印机仅支持此类打印,则必须在 /privet /info API 响应中仅宣传/submitdoc API。
"api": [ "/privet/accesstoken", "/privet/capabilities", "/privet/printer/submitdoc", ]
高级打印 - 客户端应先通过在请求中调用 /privet/printer/createjob API 并提供有效的 CJT 作业票证,在打印机上创建打印作业。打印机必须将打印票证存储在内存中,并将 job_id 返回给客户端。然后,客户端将调用 /printer/submitdoc API 并指定之前收到的 job_id。届时,打印机将开始打印。客户端将通过调用 /privet/printer/jobstate API 轮询打印机以获取打印作业状态。
在多客户环境中,无法保证此 API 的调用方式。一个客户端可以在另一个客户端的/createjob->/submitdoc 调用之间调用 /createjob。为消除可能出现的死锁并提高易用性,我们建议在打印机上设置一个包含少量待处理打印作业的队列(至少 3-5 个):
- /createjob 会占用队列中的第一个可用位置。
- 作业生命周期(在队列中)至少为 5 分钟。
- 如果队列中的所有位置都已占用,则系统会移除最旧的非打印作业,并将新作业放置在该位置。
- 如果设备上当前有正在打印的打印作业(简单打印或高级打印),/submitdoc 应返回繁忙状态,并建议一个超时时间来重试此打印作业。
- 如果 /submitdoc 指的是已从队列中移除的作业(由于替换或超时),打印机应返回错误 invalid_print_job,并且客户端将从 /createjob 步骤重试该流程。客户端必须等待最长 5 秒的随机超时时间,然后才能重试。
如果内存限制导致无法在设备上存储多个待处理的作业,则打印作业队列的长度可能为 1。它仍应遵循与上述相同的协议。作业完成或因错误而失败后,打印机应至少存储 5 分钟的作业状态信息。用于存储已完成作业状态的队列大小应至少为 10。如果需要存储更多作业状态,则最早的作业状态可能会在 5 分钟超时之前从队列中移除。
注意:目前,客户端将轮询作业状态。未来,我们可能会要求打印机在任何打印作业状态发生变化时发送 TXT DNS 通知。
5.1. /privet/printer/createjob API
/privet/printer/createjob API 是可选的(请参阅上文中的“简单打印”)。这是一个 HTTP POST 请求。/privet/printer/createjob API 必须检查是否存在有效的“X-Privet-Token”标头。 设备必须在“/privet/printer/createjob”网址上实现此 API:
POST /privet/printer/createjob HTTP/1.1
5.1.1. 输入
/privet/printer/createjob API 在网址中没有输入参数。请求正文应包含 CJT 格式的打印作业票证数据。5.1.2. 返回
/privet/printer/createjob API 返回以下数据:值名称 | 值类型 | 说明 |
---|---|---|
job_id | 字符串 | 新创建的打印作业的 ID。 |
expires_in | int | 相应打印作业的有效秒数。 |
示例:
{ "job_id": "123", "expires_in": 600 }
5.1.3. 错误
/privet/printer/createjob API 可能会返回以下错误(详情请参阅“错误”部分):错误 | 说明 |
---|---|
invalid_ticket | 提交的打印凭据无效。 |
printer_busy | 打印机正忙,目前无法处理 /创建作业。超时后重试。 |
printer_error | 打印机处于错误状态,需要用户互动才能修复。 说明应包含更详细的解释(例如“纸盒 1 中出现卡纸”)。 |
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
如果设备未公开 /privet/printer/createjob,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
5.2. /privet/printer/submitdoc API
/privet/printer/submitdoc API 必须实现通过本地网络(离线或重新发布到 Cloud Print)进行打印。这是一个 HTTP POST 请求。/privet/printer/submitdoc API 必须检查是否存在有效的“X-Privet-Token”标头。设备必须在“/privet/printer/submitdoc”网址上实现此 API:POST /privet/printer/submitdoc HTTP/1.1
如果打印机无法将所有数据都保存在其内部缓冲区中,则应使用 TCP 机制来减慢数据传输速度,直到打印机打印完部分文档,从而使部分缓冲区再次可用。(例如,打印机可能会在 TCP 层上设置 windowsize=0,这会导致客户端等待。)
将文档提交到打印机可能需要花费大量时间。客户端应能够在打印进行时检查打印机和作业的状态(高级打印)。 为此,打印机必须允许客户端在处理 /privet/printer/submitdoc API 调用时调用/privet/info 和 /privet/printer/jobstate API。建议所有客户端都启动一个新线程来执行 /privet/printer/submitdoc API 调用,以便主线程可以使用 /privet/info 和 /privet/printer/jobstate API 来检查打印机和作业状态。
注意:本地打印作业完成或中止后,强烈建议(并且在未来版本的此规范中将要求)向 /cloudprint/submit 接口报告作业的最终状态,以用于结算和改善用户体验。需要提供“printerid”“title”“contentType”和“final_semantic_state”(采用 PrintJobState 格式)参数,以及“tag”(重复参数)和“ticket”(采用 CloudJobTicket 格式的作业凭据)参数。 请注意,所提供的 PrintJobState 必须是最终状态,即其类型必须为 DONE 或 ABORTED,并且在 ABORTED 的情况下必须提供原因(有关详情,请参阅 JobState)。另请注意,其规范中并未提及使用 /cloudprint/submit 接口报告本地打印作业,因为该部分旨在描述接口的主要用途:提交打印作业,并使用“content”参数提供要打印的文档。
5.2.1. 输入
/privet/printer/submitdoc API 具有以下输入参数:名称 | 值 |
---|---|
job_id | (可选)打印作业 ID。对于简单的打印情况(见上文),可以省略。必须与打印机返回的字符串一致。 |
user_name | (可选)直观易懂的用户名。此值并非最终值,仅用于打印作业注释。如果作业重新发布到云打印服务,则此字符串应附加到云打印作业。 |
client_name | (可选)发出此请求的客户端应用的名称。仅用于显示目的。如果作业重新发布到 Cloud Print 服务,则应将此字符串附加到 Cloud Print 作业。 |
job_name | (可选)要记录的打印作业的名称。如果作业重新发布到云打印服务,则此字符串应附加到云打印作业。 |
离线 | (可选)只能是“offline=1”。在这种情况下,打印机应仅尝试离线打印(不重新向云打印服务器发布)。 |
请求正文应包含有效的待打印文档。“Content-Length”应包含正确的请求长度。“Content-Type”标头应设置为文档 MIME 类型,并与 CDD 中的一种类型相匹配(除非 CDD 指定了“*/*”)。
强烈建议客户在此请求中提供有效的用户名(或电子邮件地址)、客户名称和作业名称。这些字段仅在界面中用于改善用户体验。
5.2.2. 返回
/privet/printer/submitdoc API 会返回以下数据:值名称 | 值类型 | 说明 |
---|---|---|
job_id | 字符串 | 新创建的打印作业(简单打印)或请求中指定的 job_id(高级打印)的 ID。 |
expires_in | int | 相应打印作业的有效秒数。 |
job_type | 字符串 | 所提交文档的内容类型。 |
job_size | int 64 位 | 打印数据的大小(以字节为单位)。 |
job_name | 字符串 | (可选)与输入中的作业名称相同(如有)。 |
示例:
{ "job_id": "123", "expires_in": 500, "job_type": "application/pdf", "job_size": 123456, "job_name": "My PDF document" }
5.2.3. 错误
/privet/printer/submitdoc API 可能会返回以下错误(有关详情,请参阅“错误”部分):错误 | 说明 |
---|---|
invalid_print_job | 请求中指定的作业 ID 无效/已过期。在超时后重试。 |
invalid_document_type | 打印机不支持文档 MIME 类型。 |
invalid_document | 提交的证件无效。 |
document_too_large | 文档超过了允许的大小上限。 |
printer_busy | 打印机正忙,目前无法处理文档。超时后重试。 |
printer_error | 打印机处于错误状态,需要用户互动才能修复。 说明应包含更详细的解释(例如“纸盒 1 中出现卡纸”)。 |
invalid_params | 请求中指定的参数无效。(未知参数应安全地忽略,以实现未来兼容性) |
user_cancel | 用户明确从设备中取消了打印流程。 |
server_error | 将文档发布到云打印失败。 |
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
如果设备未公开 /privet/printer/submitdoc,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
注意:/privet/printer/submitdoc API 可能需要在打印机端进行特殊处理(因为附加的载荷较大)。在某些情况下(取决于打印机 HTTP 服务器实现和平台),打印机可能会在返回 HTTP 错误之前关闭套接字。在其他情况下,打印机可能会返回 503 错误(而不是 Privet 错误)。打印机应尽可能尝试返回 Privet。不过,实现 Privet 规范的每个客户端都应该能够处理 /privet/printer/submitdoc API 的套接字关闭(无 HTTP 错误)和 503 HTTP 错误情况。在这种情况下,客户端应将其作为 Privet“printer_busy”错误进行处理,并将“timeout”设置为 15 秒。为避免无限重试,客户端可以在尝试合理次数(例如 3 次)后停止重试。
5.3. /privet/printer/jobstate API
/privet/printer/jobstate API 是可选的(请参阅上文中的“简单打印”)。这是一个 HTTP GET 请求。 /privet/printer/jobstate API 必须检查是否存在有效的“X-Privet-Token”标头。设备必须在“/privet/printer/jobstate”网址上实现此 API:GET /privet/printer/jobstate HTTP/1.1
5.3.1. 输入
/privet/printer/jobstate API 具有以下输入参数:名称 | 值 |
---|---|
job_id | 要返回状态的打印作业 ID。 |
5.3.2. 返回
/privet/printer/jobstate API 会返回以下数据:值名称 | 值类型 | 说明 |
---|---|---|
job_id | 字符串 | 相应状态信息所对应的打印作业 ID。 |
州 | 字符串 | 草稿 - 打印作业已在设备上创建(尚未收到任何 /privet/printer/submitdoc 调用)。
已加入队列 - 打印作业已收到并加入队列,但尚未开始打印。 in_progress - 打印作业正在打印。 已停止 - 打印作业已暂停,但可以手动或自动重新开始。 完成 - 打印作业已完成。 已中止 - 打印作业失败。 |
说明 | 字符串 | (可选)直观易懂的打印作业状态说明。如果 state< 为 stopped 或 aborted,则应包含其他信息。semantic_state 字段通常会向客户端提供更好、更有意义的说明。 |
expires_in | int | 相应打印作业的有效秒数。 |
job_type | 字符串 | (可选)所提交文档的内容类型。 |
job_size | int 64 位 | (可选)打印数据的大小(以字节为单位)。 |
job_name | 字符串 | (可选)与输入中的作业名称相同(如有)。 |
server_job_id | 字符串 | (可选)从服务器返回的作业的 ID(如果作业已发布到 Cloud Print 服务)。离线打印时省略。 |
semantic_state | JSON | (可选)作业的语义状态,采用 PrintJobState 格式。 |
示例(通过 Cloud Print 报告进行打印):
{ "job_id": "123", "state": "in_progress", "expires_in": 100, "job_type": "application/pdf", "job_size": 123456, "job_name": "My PDF document", "server_job_id": "1111-2222-3333-4444" }
示例(离线打印错误):
{ "job_id": "123", "state": "stopped", "description": "Out of paper", "expires_in": 100, "job_type": "application/pdf", "job_size": 123456, "job_name": "My PDF document" }
示例(用户中止了打印作业):
{ "job_id": "123", "state": "aborted", "description": "User action", "expires_in": 100, "job_type": "application/pdf", "job_size": 123456, "job_name": "My PDF document", "semantic_state": { "version": "1.0", "state": { "type": "ABORTED", "user_action_cause": {"action_code": "CANCELLED"} }, "pages_printed": 7 } }
示例(打印作业因缺纸而停止)。请注意对设备状态的引用。客户端需要调用 /privet/info API 才能获取有关设备状态的更多详细信息:
{ "job_id": "123", "state": "stopped", "description": "Out of paper", "expires_in": 100, "job_type": "application/pdf", "job_size": "123456", "job_name": "My PDF document", "semantic_state": { "version": "1.0", "state": { "type": "STOPPED", "device_state_cause": {"error_code": "INPUT_TRAY"} }, "pages_printed": 7 } }
5.3.3. 错误
/privet/printer/jobstate API 可能会返回以下错误(详情请参阅“错误”部分):错误 | 说明 |
---|---|
invalid_print_job | 请求中指定的作业 ID 无效/已过期。 |
server_error | 获取打印作业状态(针对发布到 Cloud Print 的打印作业)失败。 |
invalid_x_privet_token | 请求中的 X-Privet-Token 无效或为空。 |
如果设备未公开 /privet/printer/jobstate,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。
6. 附录
6.1. 默认行为和设置
本部分将说明我们希望所有 Privet 兼容设备都具备的默认行为。- 开箱即用的设备应仅支持 /privet/info 和 /privet/register API。应停用所有其他 API(例如 /privet/accesstoken、本地打印)。
- 注册需要与设备进行物理交互。
- 用户必须在设备上执行物理操作(例如按某个按钮)来确认其对设备的访问权限。
- 用户执行上述操作后,打印机应发送 /cloudprint/register 请求。在采取相应操作之前,不应发送此请求(请参阅序列图 1)。
- 如果设备正在处理 /privet/register 请求(例如,等待上述操作),则必须拒绝所有其他 /privet/register 请求。在这种情况下,设备必须返回 device_busy 错误。
- 如果设备在 60 秒内未收到上述物理操作,则应使任何 /register 请求超时。在这种情况下,设备必须返回 confirmation_timeout 错误。
- 可选:建议执行,但并非强制要求,以下操作可改善用户体验:
- 打印机可能会闪烁指示灯或屏幕,以指示用户需要采取行动来确认注册。
- 打印机可能会在其屏幕上显示“正在为用户‘abc@def.com’向 Google 云打印注册 - 按 OK 继续”,其中 abc@def.com 是 /register API 调用中的用户参数。这样一来,用户就能更清楚地了解:
- 他们确认的是自己的注册请求
- 如果对方未触发该请求,会发生什么情况。
- 除了在打印机上执行确认操作(例如,“按 OK 按钮”),打印机还可以为用户提供一个用于取消请求的按钮(例如,(按“取消”拒绝)。这样一来,未触发注册请求的用户就可以在 60 秒超时之前取消该请求。在这种情况下,设备必须返回 user_cancel 错误。
- 所有权转移:
- 设备可能已从云服务中明确删除。
- 如果设备收到成功响应,但 /cloudprint/printer(针对 GCP)调用未返回任何设备说明,则设备必须恢复为默认(开箱即用)模式。
- 如果设备的凭据不再有效(明确是因为服务器返回“凭据无效”响应),则必须恢复为默认(开箱即用)模式。
- 本地恢复出厂设置必须清除设备的凭据并将其设置为默认状态。
- 可选:设备可能会提供一个菜单项,用于清除凭据并将其置于默认模式。
- 支持 XMPP 通知的设备必须能够 ping 服务器。必须能够通过“local_settings”从服务器控制 ping 超时。
- 设备每天(24 小时)最多只能显式 ping 服务器(GCP 的 /cloudprint/printer API,以及 XMPP ping)一次,以确保它们处于同步状态。建议在 24-32 小时的时间范围内随机化检查窗口。
- 可选:对于云打印设备,建议(但并非必需)提供一种手动方式(按钮),以便用户从设备发起对新打印作业的检查。部分打印机已具备此功能。
- 可选。企业打印机可能提供完全停用本地发现的选项。在这种情况下,设备必须更新服务器上的这些本地设置。新的本地设置必须为空(将“local_discovery”设置为“false”意味着可以从 GCP 服务重新启用)。
6.1.2 默认注册图

6.2. XSSI 和 XSRF 攻击及防范
本部分将说明设备上可能发生的 XSSI 和 XSRF 攻击,以及如何防范这些攻击(包括令牌生成技术)。如需了解详情,请参阅: http://googleonlinesecurity.blogspot.com/2011/05/website-security-for-webmasters.html
通常,当网站使用 Cookie 身份验证机制时,可能会遭受 XSSI 和 XSRF 攻击。虽然 Google 的云打印服务不使用 Cookie,但此类攻击仍有可能发生。本地网络访问权限在设计上会隐式信任请求。
6.2.1. XSSI
恶意网站可能会猜测 Privet 兼容设备的 IP 地址和端口号,并尝试使用 <script> 标记内的“src=<api name>”调用 Privet API:<script type="text/javascript" src="http://192.168.1.42:8080/privet/info"></script>
为防止此类攻击,所有 Privet API 调用都必须在请求中包含“X-Privet-Token”标头。“src=<api>”脚本代码无法添加标头,从而有效防范此类攻击。
6.2.2. XSRF
http://en.wikipedia.org/wiki/Cross-site_request_forgery恶意网站可能会猜测 Privet 兼容设备的 IP 地址和端口号,并尝试使用 <iframe>、表单或其他跨网站加载机制来调用 Privet API。攻击者无法访问请求的结果,但如果请求会执行操作(例如打印),他们可以触发该操作。
为防范此类攻击,我们需要采取以下保护措施:
- 将 /privet/info API 开放给 XSRF
- /privet/info API 不得对设备执行任何操作
- 使用 /privet/info API 接收 x-privet-token
- 所有其他 API 都必须检查“X-Privet-Token”标头中是否存在有效的 x-privet-token。
- x-privet-token 的有效期应仅为 24 小时。
即使攻击者能够执行 /privet/info API,也无法从响应中读取 x-privet-token,因此无法调用任何其他 API。
强烈建议使用以下算法生成 XSRF 令牌:
XSRF_token = base64( SHA1(device_secret + DELIMITER + issue_timecounter) + DELIMITER + issue_timecounter )
XSRF 令牌生成元素:
- DELIMITER 是一个特殊字符,通常为“:”
- issue_timecounter 是自某个事件(时间戳的纪元)或设备启动时间(对于 CPU 计数器)以来的秒数。当设备启动并运行后,issue_timecounter 会不断增加(请参阅下面的令牌验证)。
- SHA1 - 使用 SHA1 算法的哈希函数
- base64 - base64 编码
- device_secret - 特定于设备的密钥。设备密钥必须在每次重启时更新。
建议采用以下方式生成设备 Secret:
- 每次重新启动时都生成新的 UUID
- 每次重新启动时生成一个 64 位随机数
设备无需存储其已发布的所有 XSRF 令牌。当设备需要验证 XSRF 令牌是否有效时,应先对令牌进行 base64 解码。从后半部分(明文)获取 issue_timecounter,并尝试生成 device_secret + DELIMITER + issue_timecounter 的 SHA1 哈希,其中 issue_timecounter 来自令牌。如果新生成的 SHA1 与令牌中的 SHA1 一致,设备现在必须检查 issue_timecounter 是否在当前时间计数器的有效期(24 小时)内。为此,设备会获取当前时间计数器(例如 CPU 计数器),然后从中减去 issue_timecounter。结果必须是自令牌签发以来的秒数。
重要提示:这是实现 XSRF 保护的推荐方式。Privet 规范的客户端不得尝试了解 XSRF 令牌,而应将其视为黑盒。图 6.2.3 展示了实现 X-Privet-Token 和验证典型请求的推荐方式。
6.2.3 X-Privet 令牌生成和验证序列图

6.3. 工作流程图
本部分将展示不同情况下的工作流程。6.3.1. 打印机开箱即用工作流程

6.3.2. 注册打印机启动

6.3.3 XMPP 通知处理工作流

6.3.4. 检查打印机设置工作流
