Privet

Privet 是云服务使用的 Cloud Device Local Discovery API。本文档分为以下几个部分:

  1. 简介:Privet 简介
  2. 发现:本地发现机制
  3. 公告:本地发现公告
  4. API:适用于常规云设备的 Privet API
  5. Printer API:打印机使用的 Privet API
  6. 附录:补充图表

1. 简介

云联网设备具有诸多优势。他们可以使用在线转化服务,在设备离线时托管作业队列,并且可从世界各地访问这些数据。不过,对于一个给定用户可以访问的许多云设备,我们需要提供一种方法来根据位置查找最近的设备。Privet 协议的目的是将云端设备的灵活性与适当的本地发现机制绑定,以便在新环境中轻松发现设备。

此协议的目标是:
  • 让云端设备可供本地发现
  • 向云服务注册云端设备
  • 将已注册的设备与其云表示法相关联
  • 启用离线功能
  • 简化实现流程,以便小型设备使用

Privet 协议由两大部分组成:发现和 API。发现广告用于在本地网络中查找设备,而 API 则用于获取设备的相关信息并执行一些操作。在本文档中,设备是指实现 Privet 协议的云连接设备。

2. Discovery

Discovery 是基于零配置 (mDNS + DNS-SD) 的协议。设备必须实现 IPv4 链路本地寻址。设备必须符合 mDNS 和 DNS-SD 规范。

设备必须根据上述规范执行名称冲突解决。

2.1. 服务类型

DNS Service Discovery 对服务类型使用以下格式:_applicationprotocol._transportprotocol。对于 Privet 协议,DNS-SD 的服务类型应为:_privet._tcp

设备也可以实现其他服务类型。建议对设备实现的所有服务类型使用相同的服务实例名称。例如:打印机可以实现“打印机 XYZ._privet._tcp”和“打印机 XYZ._printer._tcp”服务。这将简化用户的设置。但是,Privet 客户端只会查找“_privet._tcp”。

除了主服务类型之外,设备还必须针对其对应的子类型通告 PTR 记录(请参阅 DNS-SD 规范:7.1 级)。选择性实例枚举(子类型)”。格式应如下所示: _<subtype>._sub._privet._tcp

目前唯一支持的设备子类型是打印机。因此,所有打印机都必须通告两条 PTR 记录:

  • _privet._tcp.local。
  • _printer._sub._privet._tcp.local。

2.2. TXT 记录

DNS Service Discovery 定义了在 TXT 记录中添加有关服务的可选信息的字段。TXT 记录由键值对组成。每个键值对从长度字节开始,后跟最多 255 个字节的文本。键是第一个“=”字符之前的文本,值是第一个“=”字符之后的文字,直到结尾。该规范不允许在记录中出现任何值,在这种情况下,既不会包含“=”字符,也不会包含“=”字符之后的任何文字。(请参阅 DNS-SD 规范:6.1 版本。DNS TXT 记录常规格式规则”和“6.2. DNS-SD TXT Record Size”(推荐长度)。

Privet 要求设备在 TXT 记录中发送以下键值对。键值对字符串不区分大小写,例如“CS=online”和“cs=Online”是相同的。TXT 记录中的信息必须与通过 /info API 访问的信息相同(请参阅 4.1。 API 部分)。

建议将 TXT 记录大小保持在 512 字节以下。

2.2.1. txt

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=https://www.google.com/cloudprint

2.2.5. type

此设备支持的设备类型子类型列表(以英文逗号分隔)。格式为:"type=_subtype1,_subtype2&quot>。目前,唯一受支持的设备子类型是 printer

type=printer

列出的每个子类型都应使用相应的 PTR 记录进行通告。对于每种受支持的服务子类型,都应该有一个对应的项。服务子类型名称 (<subtype>._sub._privet._tcp) 应与此处列出的设备类型相同。

2.2.6. ID

设备 ID。如果设备尚未注册,则应存在此键,但值应为空。例如:

  id=11111111-2222-3333-4444-555555555555
  id=

2.2.7 cs

指示设备的当前连接状态。本规范中定义了 4 个可能的值。

  • "online" 表示设备当前连接到了云。
  • "离线”表示设备在本地网络中可用,但无法与服务器通信。
  • "正在连接”表示设备正在执行其启动序列,尚未完全在线。
  • "not-configured" 表示设备的互联网访问权限尚未配置。此值目前未使用,但可能在本规范的未来版本中很有用。
例如:
  • cs=online
  • cs=离线
  • cs=连接

如果设备已注册云,则在启动时应检查与服务器的连接,以检测其连接状态(例如,调用 Cloud 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”标头为空或无效,设备必须在响应中提供“X-Privet-Token 错误无效”(invalid_x_privet_token,详见“错误”部分)。唯一的例外是 /info API。如需详细了解为何要执行此操作以及应如何生成令牌,请参阅附录 A:XSSI 和 XSRF 攻击与预防。

如果请求的 API 不存在或不受支持,设备必须返回 HTTP 404 错误。

4.1. API 可用性

在提供任何 API(包括 /info API)之前,设备必须联系服务器以检查本地设置。重启之间的设备必须保留本地设置。如果服务器不可用,则应使用最后的已知本地设置。如果设备尚未注册,应采用默认设置。

云打印设备必须按照以下步骤注册、接收和更新本地设置。

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”。
访问令牌已启用布尔值(可选)指示是否应在本地网络上公开 /accesstoken API。默认情况下,值应为“true”。
打印机/本地打印_已启用布尔值(可选)指示是否应在本地网络上提供本地打印功能(/printer/createjob、/printer/submitdoc、/printer/jobstate)。默认情况下,值应为“true”。
打印机/转化_已启用布尔值(可选)指示本地打印是否可以将作业发送到服务器进行转换。只有在启用本地打印后才有意义。
超时时间值int(可选)表示 XMPP 通道 ping 之间的秒数。默认情况下,不少于 300 分钟(5 分钟)。

重要提示:缺少任何可选值表示设备完全不支持对应的功能。

4.1.2. 启动

设备启动时,它应联系服务器以检查本地网络中有哪些 API 可供公开。对于连接到云打印的打印机,他们应调用:

/cloudprint/printer?printerid=<printer_id>
/cloudprint/list

首选的 /fitbit/printer 优于 /fitbit/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>

设备因 /fitbit/printer API 启动后或启动时显示“待处理”部分时,必须更新其内部状态以记住新设置。它必须调用服务器 API 以确认新设置。对于云端打印机,设备必须调用 /借用/更新 API,并在注册期间使用“local_settings”参数。

重新连接到 XMPP 通道时,设备必须调用 /fitbit/printer API,以检查自上次以来本地设置是否发生了变化。

4.1.3.1. 本地设置待处理

设备用于调用服务器 API 的“local_settings”参数绝不能包含“待处理”部分。

4.1.3.2. 当前本地设置

只有设备可以更改“local_settings”的“当前”部分。 其他人会更改“待处理”部分,并等到设备将更改应用到“当前”部分。

4.1.4. 线下

如果在启动过程中无法连接服务器,设备必须在通知后使用上次的已知本地设置。

4.1.5. 从服务中删除设备

如果设备已从服务中删除(例如 GCP),系统会向设备发送 XMPP 通知。通知采用以下格式:

<device_id>/delete

收到此类通知后,设备必须前往服务器查看其状态。云打印设备必须使用:

/cloudprint/printer?printerid=<printer_id>

设备必须收到成功 HTTP 响应且成功=false 且无设备/打印机说明。这意味着设备已从服务器中移除,设备必须清除其凭据并进入默认出厂设置模式。

每当设备收到回复(表明设备因 /fitbit/printer API 而被删除,如启动、更新设置通知、每日 ping)时,都必须删除其凭据并进入默认模式。

4.2. /privet/info API

信息 API 是必需的,必须由所有设备实现。它是针对“/privet/info”网址的 HTTP GET 请求,网址为:GET /privet/info HTTP/1.1

info API 会返回有关其支持的设备和功能的基本信息。此 API 绝不能更改设备状态或执行任何操作,因为它容易受到 XSRF 攻击。 这是唯一一个可以拥有空“X-Privet-Token”标头的 API。客户端应使用“X-Privet-Token”标头设置为 X-Privet-Token 来调用 /privet/info API: “"”

发现期间,info API 返回的数据必须与 TXT 记录中提供的数据一致。

4.2.1.输入

/privet/info API 没有输入参数。

4.2.2. 返回

/privet/info API 会返回有关设备和支持的功能的基本信息。

TXT 列指示 DNS-SD TXT 记录中的相应字段。

值名称值类型说明TXT
version字符串支持的 API 的最高版本 (major.minor),当前为 1.0
name字符串简单易懂的设备名称。ty
广告内容描述字符串(可选)设备说明。应可由用户修改。note
url字符串此设备正在通信的服务器的网址。网址必须包含协议规范,例如:https://www.google.com/fitbit。url
类型字符串列表支持的设备类型列表。类型
id字符串设备 ID,如果设备尚未注册,则为空。 id
设备状态字符串设备的状态。
“空闲”表示设备已准备就绪
处理表示设备处于忙碌状态,功能可能在一段时间内受到限制
“停止”表示设备未工作,需要用户干预
连接状态字符串到服务器的连接状态(base_url)
在线 - 可用连接
离线 - 无连接
连接 - 执行启动步骤
未配置 - 连接尚未配置
注册的设备可能会根据通知渠道的状态(例如 XMPP 连接状态)报告其连接状态。
cs
制造商字符串设备制造商的名称
模式字符串设备的型号
序列号字符串唯一设备标识符。在本规范中,此值必须是 UUID。(GCP 1.1 规范)
(可选)我们强烈建议您在任何位置使用相同的序列号,以便不同的客户端可以识别同一设备。例如,实现 IPP 的打印机可以在“printer-device-id”字段中使用此序列号。
固件字符串设备固件版本
uptimeint设备启动的秒数。
设置网址字符串(可选)包含设置说明的网页网址(包括协议)
support_url字符串(可选)支持页面的网址(包括协议)、常见问题解答信息
更新网址字符串(可选)包含更新固件说明的网页网址(包括协议)
x-privet-token字符串必须传递给所有 API 的 X-Privet-Token 标头的值,以防止 XSSI 和 XSRF 攻击。如需了解详情,请参阅 6.1。
APIAPI 说明支持的 API 列表(如下所述)
语义状态JSON(可选)设备的语义状态,采用 CloudDeviceState 格式。

api - 是一个 JSON 列表,其中包含可通过本地网络使用的 API 列表。请注意,并非所有 API 都可通过本地网络同时使用。例如,新连接的设备应仅支持 /register API:

"api": [
        "/privet/register",
]
设备注册完成后,设备应停止支持 /register API。设备还应与服务联系,以确定可以通过本地网络公开哪些 API。例如:
"api": [
        "/privet/accesstoken",
        "/privet/capabilities",
        "/privet/printer/submitdoc",
]

目前提供以下 API:

  • /privet/register - 用于通过本地网络注册设备的 API。(如需了解详情,请参阅 /privet/register API)。设备成功注册到云端后,必须隐藏此 API。
  • /privet/accesstoken - 用于从设备请求访问令牌的 API(如需了解详情,请参阅 /privet/accesstoken API)。
  • /privet/features - 用于检索设备功能的 API(如需了解详情,请参阅 /privet/features API)。
  • /privet/printer/* - 设备类型“printer”专用 API,如需了解详情,请参阅打印机专用 API。
下面是一个 /privet/info 响应示例。(请注意,由于相应设备已被注册,因此缺少 /privet/register 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",
        ]
}
以下是已耗尽墨水的打印机的 /privet/info 响应示例(请注意 semantic_state 字段):
{
        "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. 错误

/privet/info API 应该仅在缺少 X-Privet-Token 标头时返回错误。必须是 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 个步骤(请参阅云打印的匿名注册)。

  1. 启动匿名注册流程。
  2. 客户端通过调用 /privet/register API 来启动此流程。此时,设备可能会等待用户确认。
  3. 获取声明令牌。

客户端会轮询以确定设备何时可以继续运行。设备准备就绪后,会向服务器发送请求以检索注册令牌和注册网址。接收到的令牌和网址应返回给客户端。在此步骤中,如果设备再次收到初始化注册的调用,则:

  • 如果该用户是开始注册的用户,请删除所有先前的数据(如果有),并开始新的注册流程。
  • 如果这是其他用户 - 返回 device_忙碌 错误和 30 秒超时。

完成注册流程。

在客户端认领设备后,客户端应通知设备完成注册。注册流程完成后,设备应发送更新通知,包括新获取的设备 ID。

注意:当设备正在处理 /privet/register API 调用时,无法同时处理其他 /privet/register API 调用。设备必须返回 device_忙碌错误和 30 秒超时。

强烈建议在设备上确认用户注册。实现后,设备在收到 /privet/register?action=start API 调用后必须等待用户确认。客户端将调用 /privet/register?action=getClaimToken API 来确定用户确认是否已完成以及声明令牌何时可用。如果用户在设备上取消注册(例如,按“取消”按钮),必须返回 user_cancel 错误。如果用户在特定时间范围内未确认注册,必须返回 Confirm_timeout 错误。如需了解详情,请参阅“默认”部分。

4.3.1. 输入

/privet/register API 具有以下输入参数:
名称
操作可以是以下任意一项:
start - 开始注册流程
getClaimToken - 检索设备的声明令牌
cancel - 取消注册流程
complete - 完成注册流程
用户认领此设备的用户的电子邮件地址。

设备必须检查来自所有操作(启动、getClaimToken、取消、完成)的电子邮件地址是否匹配。

4.3.2. 返回

/privet/register API 会返回以下数据:
值名称值类型说明
操作字符串与输入参数的操作相同。
用户字符串(可选)与输入参数中的用户相同(如果在输入中省略,则可能会缺失)。
token字符串(可选)注册令牌(对于“getClaimToken”响应是必需的,对于“start”、“complete”和“cancel”除外)。
声明网址字符串(可选)注册网址(对于“getClaimToken”响应为必需参数,对于“start”、“complete”和“cancel”除外)。对于云端打印机,它必须是从服务器收到的“complete_appeal_url”。
automated_claim_url字符串(可选)注册网址(对于“getClaimToken”响应为必需参数,对于“start”、“complete”和“cancel”除外)。对于云端打印机,它必须是从服务器收到的“auto_appeal_url”。
device_id字符串(可选)新设备 ID(对于“开始”响应省略,对于“完成”为必需参数)。

只有在完成注册后,设备才能在 /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 可能会返回以下错误(如需了解详情,请参阅“错误”部分):
错误说明
设备繁忙设备正忙,无法执行请求的操作。请在超时后重试。
待处理用户操作为响应“getClaimToken”,此错误表示设备仍处于待处理状态,应在超时后重试“getClaimToken”请求。
user_cancel用户明确取消了设备的注册流程。
确认超时用户确认超时。
无效操作调用无效操作。例如,如果客户端在调用 action=start 和 action=getClaimToken 之前调用了 action=complete。
参数无效请求中指定的参数无效。(应安全忽略未知参数,以确保未来的兼容性)。例如,如果客户端调用 action=unknown 或 user=,则返回此值。
device_config_error设备端的日期/时间(或其他一些设置)有误。用户需要转到设备内部网站并配置设备设置。
离线设备目前处于离线状态,无法与服务器通信。
服务器错误注册过程中服务器出错。
无效令牌令牌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 调用后,应调用服务器来检索指定用户的访问令牌,然后将令牌返回给客户端。然后,客户端会使用访问令牌通过云端访问此设备。

云打印设备必须调用以下 API:

/cloudprint/proximitytoken
并传递本地 API 中的“printerid=<printer_id”和“user”参数。如果成功,服务器响应将包含以下对象:
"proximity_token": {
        "user": "user@domain.com",
        "token": "AAA111222333444555666777",
        "expires_in": 600
}
云打印设备必须将响应中“neighborhood_token”对象的值传递给本地 /privet/accesstoken API 调用。如果设备可以传递所有参数(包括本规范中未介绍的参数),则更加有利(未来)。

4.4.1. 输入

/privet/accesstoken API 具有以下输入参数:
名称
用户打算使用此访问令牌的用户的电子邮件地址。请求可以为空。

4.4.2. 返回

/privet/accesstoken API 会返回以下数据:
值名称值类型说明
token字符串服务器返回的访问令牌
用户字符串与输入参数中的用户相同。
expires_inint此令牌到期前的秒数。从服务器接收并在此响应中传递。

示例:

{
        "token": "AAA111222333444555666777",
        "user": "user@domain.com",
        "expires_in": 600
}

4.4.3. 错误

/privet/accesstoken API 可能会返回以下错误(有关详情,请参阅“错误”部分):
错误说明
离线设备目前处于离线状态,无法与服务器通信。
拒绝访问权限不足。访问遭拒。如果服务器明确拒绝了请求,设备应返回此错误。
参数无效请求中指定的参数无效。(应安全忽略未知参数,以确保未来的兼容性)。例如,如果客户端调用 /accesstoken?user= 或 /accesstoken。
服务器错误服务器错误。
无效令牌令牌X-Privet-Token 在请求中无效或为空。

如果设备未公开 /privet/accesstoken API,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。

4.5. /privet/Capabilities API

/privet/features API 是可选的。这是一个 HTTP GET 请求。/privet/features API 必须检查有效的“X-Privet-Token”标头。设备必须在“/privet/features”网址中实现此 API:
GET /privet/capabilities HTTP/1.1
当设备收到 /features API 调用时,如果设备可以,它应该与服务器联系以获取更新后的功能。例如,如果打印机支持通过云打印服务向自身发布打印任务,则应返回云打印服务返回的功能。在这种情况下,云打印可能会添加原始打印机的功能,因为这些功能可能会添加在将作业发送到打印机之前执行的新功能。最常见的情况是受支持的文档类型列表。如果打印机处于离线状态,应返回其支持的文档类型。但是,如果打印机在线并注册了云打印,则必须返回“*/*”作为受支持的类型之一。在这种情况下,云打印服务将执行必要的转换。对于离线打印,打印机必须至少支持“image/pwg-raster”格式。

4.5.1. 输入

/privet/Capabilities API 具有以下输入参数:
名称
离线(可选)只能设为“离线=1”。在这种情况下,设备应返回相应功能以供离线使用(如果这些功能与“在线”功能不同)。

4.5.2. 返回

/privet/Capabilities API 会以 Cloud Device description (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" }
                ]
        }
}

注意:打印机会使用顺序表示支持的内容类型优先级。例如,在上面的示例中,打印机指定“product/pdf”数据优于“image/pwg-raster”和“image/jpeg”。如有可能,客户端应遵循打印机优先级设置(如需了解详情,请参阅 CDD 文档)。

4.5.3. 错误

/privet/Capabilities API 可能会返回以下错误(如需了解详情,请参阅“错误”部分):
错误说明
无效令牌令牌X-Privet-Token 在请求中无效或为空。

如果设备未公开 /privet/features API,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。

4.6. 错误

上述 API 会返回错误,格式如下:
值名称值类型说明
错误字符串错误类型(按 API 定义)
广告内容描述字符串(可选)直观易懂的错误说明。
server_api字符串(可选)如果发生服务器错误,此字段包含失败的服务器 API。
服务器代码int(可选)如果发生服务器错误,此字段包含服务器返回的错误代码。
server_http_codeint(可选)如果发生服务器 HTTP 错误,此字段将包含返回的 HTTP 错误代码服务器。
timeoutint(可选)客户端在重试前等待的秒数(仅适用于可恢复的错误)。客户端必须将此值的实际超时时间随机设置为 + 20% 的值。

如果缺少 X-Privet-Token 标头,所有 API 都必须返回 HTTP 400 错误。

HTTP/1.1 400 缺少 X-Privet-Token 标头。

示例 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. 打印机 API

此协议支持的设备类型之一是打印机类型。支持此类的设备可以实现一些打印机专用的功能。理想情况下,通过云端打印机进行打印时,将通过云端服务器进行打印:

在某些情况下,客户端可能需要在本地发送文档。如果客户端没有 Google ID 或无法与云打印服务器通信,则可能需要使用此方法。在这种情况下,系统会将打印作业在本地提交至打印机。反过来,打印机将使用云打印服务进行作业排队和转换。由于通过云端提交作业,打印机会将作业重新发布到本地的云打印服务,然后提出请求。此过程会在服务(转化)和打印任务管理/跟踪方面提供灵活的用户体验。

由于云打印服务实现了转换,因此打印机应在支持的内容类型列表中通告支持所有输入格式 ("*/*"):

{
        "version": "1.0",
        "printer": {
                "supported_content_type": [
                        { "content_type": "image/pwg-raster" },
                        { "content_type": "*/*" }
                ]
        }
}

在某些情况下,需要使用完全离线的解决方案。由于打印机支持有限数量的输入格式,因此客户端需要将文档转换为多种原生支持的打印机格式。

该规范要求所有打印机至少支持离线打印保护套的 PWG 光栅格式 (“qu/>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
收到 /privet/printer/createjob API 调用后,打印机必须创建新的打印任务 ID,以 CJT 格式存储接收到的打印票据,然后将打印任务 ID 返回给客户端。

5.1.1. 输入

/privet/printer/createjob API 在网址中没有输入参数。请求正文应包含 CJT 格式的打印任务票证数据。

5.1.2. 返回

/privet/printer/createjob API 会返回以下数据:
值名称值类型说明
作业 ID字符串新创建的打印任务的 ID。
expires_inint此打印任务的秒数。

示例:

{
        "job_id": "123",
        "expires_in": 600
}

5.1.3. 错误

/privet/printer/createjob API 可能会返回以下错误(如需了解详情,请参阅“错误”部分):
错误说明
无效票提交的打印票无效。
打印机处于忙碌状态打印机繁忙,目前无法处理 /createjob。请在超时后重试。
打印机出错打印机处于错误状态,需要用户互动才能修复。 说明应包含更详细的说明(例如“卡纸 1 中的卡纸”)。
无效令牌令牌X-Privet-Token 在请求中无效或为空。

如果设备未公开 /privet/printer/createjob,则必须返回 HTTP 404 错误。如果缺少 X-Privet-Token 标头,设备必须返回 HTTP 400 错误。

5.2. /privet/printer/submitdoc API

要通过本地网络实现打印(离线或发布到云打印),需要使用 /privet/printer/submitdoc API。这是一个 HTTP POST 请求。/privet/printer/submitdoc API 必须检查有效的“X-Privet-Token”标头。设备必须在“/privet/printer/submitdoc”网址上实现此 API:
POST /privet/printer/submitdoc HTTP/1.1
收到 /privet/printer/submitdoc API 调用后,打印机应开始打印。如果无法开始打印,则必须返回错误 print_strict 以及建议的超时时长,供客户端在重试前等待。

如果打印机无法将所有数据保留在其内部缓冲区中,则应使用 TCP 机制减慢数据传输过程,直到打印到文档的一部分,以便让缓冲区的一部分再次可用。(例如,打印机可能会在 TCP 层上设置 windowsize=0,这会导致客户端处于等待状态。)

向打印机提交文档可能需要大量时间。在打印过程中,客户端应该能够检查打印机的状态和作业(高级打印)。为此,打印机必须允许客户端在处理 /privet/printer/submitdoc API 调用时调用/privet/info 和 /privet/printer/jobstate API。建议所有客户端启动一个新线程来执行 /privet/printer/submitdoc API 调用,以便主线程可以使用 /privet/info 和 /privet/printer/jobstate API 检查打印机和作业状态。

注意:在本地打印作业完成或中止后,强烈建议您(并在此规范的未来版本中强制要求)将作业的最终状态报告给 /fitbit/submit 界面,以便进行结算和提供用户体验。必须提供参数“printerid”、“title”、“contentType”和“final_semantic_state”(采用 PrintJobState 格式),以及参数“tag”(重复参数)和“ticket”(工单格式,是工单格式)。请注意,提供的 PrintJobState 实际上必须是 final,也就是说,其类型必须为 DONE 或 ABORTED,并且在 ABORTED 的情况下必须提供原因(如需了解详情,请参阅 JobState)。另请注意,其规范未提及这个使用 /fitbit/submit 接口报告本地打印任务的情况,因为该部分旨在描述该接口的主要用途:提交打印作业,并在 "content" 参数中提供要打印的文档。

5.2.1. 输入

/privet/printer/submitdoc API 具有以下输入参数:
名称
作业 ID(可选)打印任务 ID。对于简单打印的情况,可以省略(参见上文)。必须与打印机返回的标签一致。
用户名(可选)简单易懂的用户名。此设置并不完整,应该仅用于打印任务注释。如果作业重新发布到云打印服务,则应将此字符串附加到云打印作业。
客户名称(可选)发出请求的客户端应用的名称。仅用于显示目的。如果作业重新发布到云打印服务,则应将此字符串附加到云打印作业。
作业名称(可选)要记录的打印任务的名称。如果作业重新发布到云打印服务,则应将此字符串附加到云打印作业。
离线(可选)只能设为“离线=1”。在这种情况下,打印机应仅尝试离线打印(不得重新发布到云打印服务器)。

请求正文应包含有效的打印文档。&Content.Length 应包含请求的正确长度。"Content-Type”标头应设为文档 MIME 类型,并且与 CDD 中的某个类型匹配(除非 CDD 指定了“*/*”)。

强烈建议客户端在此请求中提供有效的用户名(或电子邮件地址)、客户端名称和作业名称。这些字段仅用于界面,以改善用户体验。

5.2.2. 返回

/privet/printer/submitdoc API 会返回以下数据:
值名称值类型说明
作业 ID字符串新建的打印任务(简单打印)或请求中指定的 job_id(高级打印)的 ID。
expires_inint此打印任务的秒数。
作业类型字符串所提交文件的内容类型。
职位大小整型 64 位打印数据的大小(以字节为单位)。
作业名称字符串(可选)输入的名称与输入的内容相同(如有)。

示例:

{
        "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 可能会返回以下错误(如需了解详情,请参阅“错误”部分):
错误说明
打印作业无效请求中指定的作业 ID 无效/已过期。请在超时后重试。
文档类型无效打印机不支持文档 MIME 类型。
无效文档提交的文件无效。
document_too_large文件超出了大小上限。
打印机处于忙碌状态打印机繁忙,目前无法处理文档。请在超时后重试。
打印机出错打印机处于错误状态,需要用户互动才能修复。 说明应包含更详细的说明(例如“卡纸 1 中的卡纸”)。
参数无效请求中指定的参数无效。(应安全忽略未知参数,以便将来兼容)
user_cancel用户明确取消了设备的打印流程。
服务器错误未能将文档发布到云打印。
无效令牌令牌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”打印机“打印机繁忙”错误进行处理,并将“超时”设置为 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
收到 /privet/printer/jobstate API 调用时,打印机应返回请求的打印任务的状态或 invalid_print_job 错误的状态。

5.3.1. 输入

/privet/printer/jobstate API 具有以下输入参数:
名称
作业 ID要返回其状态的打印任务 ID。

5.3.2. 返回

/privet/printer/jobstate API 会返回以下数据:
值名称值类型说明
作业 ID字符串包含状态信息的打印任务 ID。
state字符串草稿 - 已在设备上创建打印任务(尚未收到 /privet/printer/submitdoc 调用)。
已加入队列 - 已接收打印任务并将其加入队列,但打印尚未开始。
in_progress - 打印任务正在进行打印。
已停止 - 打印任务已暂停,但可以手动或自动重启。
done - 表示打印任务已完成。
已中止 - 打印作业失败。
广告内容描述字符串(可选)简单易懂的打印任务状态说明。如果 state< 为 stopaborted,则应添加其他信息。semantic_state 字段通常用于为客户端提供更优质、更有意义的说明。
expires_inint此打印任务的秒数。
作业类型字符串(可选)所提交文件的内容类型。
职位大小整型 64 位(可选)打印数据的大小(以字节为单位)。
作业名称字符串(可选)输入的名称与输入的内容相同(如有)。
server_job_id字符串(可选)从服务器返回的作业的 ID(如果作业已发布到云打印服务)。离线打印时省略。
语义状态JSON(可选)作业的语义状态,采用 PrintJobState 格式。

示例(通过云打印进行打印):

{
        "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 可能会返回以下错误(如需了解详情,请参阅“错误”部分):
错误说明
打印作业无效请求中指定的作业 ID 无效/已过期。
服务器错误未能获取打印任务状态(适用于发布到云打印的打印任务)。
无效令牌令牌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、本地打印)应停用。
  • 注册需要与设备进行实际互动。
    • 用户必须对设备进行实际操作(例如,按下按钮),以确认他们对设备的访问。
    • 在用户执行上述操作后,打印机应发送 /fitbit/register 请求。在执行此操作之前,不应发送此请求(请参阅序列图 1)。
    • 如果设备正在处理 /privet/register 请求(例如,等待上述操作),则必须拒绝所有其他 /privet/register 请求。在这种情况下,设备必须返回 device_忙碌错误。
    • 任何未收到上述物理操作的 /register 请求都应在 60 秒内超时。在这种情况下,设备必须返回 Confirm_timeout 错误。
    • 可选:建议但不强制要求,以下几点可改善用户体验:
      • 打印机可能会闪烁指示灯或屏幕,以表明用户需要执行操作以确认注册。
      • 打印机可能会在屏幕上声明“它正在注册用户 Google 云打印‘abc@def.com’- 按‘确定’以继续”,其中 abc@def.com 是 /register API 调用中的用户参数。这会让用户更清楚地知道:
        • 他们的确认请求是
        • 如果他/她没有触发请求,会发生什么情况。
      • 除了通过打印机进行确认之外(例如“按下 OK 按钮”后,打印机可能也会向用户提供取消请求的按钮(例如,按“取消”以拒绝请求。这样一来,未触发注册请求的用户会在 60 秒超时之前取消该请求。在这种情况下,设备必须返回 user_cancel 错误。
  • 所有权转让:
    • 系统可能会从 Cloud 服务中明确删除该设备。
      • 如果设备收到成功请求,但由于 /借用/打印机(用于 GCP)调用而没有显示设备说明,则必须恢复到默认(开箱即用)模式。
      • 如果设备凭据不再工作(由于服务器发送的“凭据无效”响应),必须恢复到默认(开箱即用)模式。
    • 本地恢复出厂设置必须清除设备的凭据,并将其设为默认状态。
    • 可选:设备可能会提供一个菜单项,用于清除凭据并将其置于默认模式。
  • 支持 XMPP 通知的设备必须具备 ping 服务器的功能。ping 超时必须可从服务器通过“local_settings”进行控制。
  • 设备可能会每天 24 小时明确 ping 一次服务器(对于 GCP,除了 XMPP ping 以外,还会针对 GCP 使用 /借用/打印 API),以确保它们保持同步。建议在 24-32 小时内对检查窗口进行随机化处理。
  • 可选:对于云打印设备,建议(而非强制要求)提供手动方式(按钮),以便用户从设备开始检查新的打印任务。部分打印机已启用此功能。
  • (可选)企业打印机可能会完全停用本地发现功能。在这种情况下,设备必须更新服务器上的这些本地设置。新的本地设置必须为空(将“local_discovery”设置为“false”,表示可以通过 GCP 服务将其重新启用)。

6.1.2 默认注册图

6.2. XSSI 和 XSRF 攻击与预防

本部分将介绍设备上可能会出现 XSSI 和 XSRF 攻击的方法,以及如何防范这些攻击(包括令牌生成技术)。
如需了解详情,请参阅: http://chromebooksecurity.blogspot.com/2011/05/website-security-for-webmasters.html
通常,当网站使用 Cookie 身份验证机制时,可能会发生 XSSI 和 XSRF 攻击。虽然 Google 不会通过其云打印服务使用 Cookie,但此类攻击仍有可能。从设计上来说,本地网络访问权限会隐式信任请求。

6.2.1. XSSI

恶意网站可能会猜测与 Privet 兼容的设备的 IP 地址和端口号,并尝试使用 <script=> 标记内的 API 调用 Privet API:
<script type="text/javascript" src="http://192.168.1.42:8080/privet/info"></script>
如果不保护,恶意网站将无法执行 API 调用和访问结果。
为了防止此类攻击,所有 Privet API 调用都必须在请求中添加“X-Privet-Token”标头。"src=<api>” 脚本标记无法添加标头,有效防范了此类攻击。

6.2.2. XSRF

http://en.wikipedia.org/wiki/Cross-site_request_forgery
恶意网站可能会猜测与 Privet 兼容设备的 IP 地址和端口号,并尝试使用 &iframe;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 - 设备专用密钥。每次重启时都必须更新设备密钥。

生成设备密钥的推荐方法:

  • 每次重启时生成新的 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. 检查打印机设置工作流