Web of Things(WoT)发现

W3C 推荐标准

关于本文档的更多详细信息
此版本:
https://www.w3.org/TR/2023/REC-wot-discovery-20231205/
最新发布版本:
https://www.w3.org/TR/wot-discovery/
最新编辑草案:
https://w3c.github.io/wot-discovery/
历史:
https://www.w3.org/standards/history/wot-discovery/
提交 历史
实现报告:
https://w3c.github.io/wot-discovery/testing/report.html
编辑:
Andrea Cimmino马德里 理工大学
Michael McCoolIntel Corp.
Farshid Tavakolizadeh受邀 专家
Kunihiko ToumuraHitachi, Ltd.
前任编辑:
Farshid TavakolizadehFraunhofer-Gesellschaft) - 直到
反馈:
GitHub w3c/wot-discovery拉取 请求新建 issue未解决的 issue
public-wot-wg@w3.org,主题行为 [wot-discovery] … 消息主题 …存档
勘误:
存在 勘误
贡献者
在 GitHub 仓库中
仓库
我们在 GitHub 上
提交 bug

另请参阅 翻译


摘要

W3C Web of Things(WoT)旨在实现跨 IoT 平台和应用领域的互操作性。 实现这一目标的一个关键机制,是定义和使用元数据, 以适当的抽象层级描述 IoT 设备或服务在网络上提供的交互。 WoT Thing Description 规范满足了这一目标。

但是,为了使用一个 Thing,首先必须获得它的 Thing Description。 本文档中描述的 WoT Discovery 过程正是为了解决这一问题。 WoT Discovery 需要支持在各种使用场景中分发 WoT Thing Description。这包括临时系统和工程化系统;开发期间和运行时; 以及本地网络和全球网络。该过程还需要与现有发现机制协同工作, 保证安全,保护私有信息,并且能够高效处理 WoT Thing Description 的更新,以及 IoT 生态系统动态且多样的特性。

WoT Discovery 过程分为两个阶段:Introduction 和 Exploration。 Introduction 阶段利用现有发现机制,但不直接暴露元数据; 它们只是用于发现 Exploration 服务,而这些服务只有在安全认证和授权之后 才会提供元数据。本文档规范性地定义了两种 Exploration 服务: 一种用于从常规 Web 服务分发单个 WoT Thing Description, 其中自描述是一种特殊情况;另一种是可搜索的 WoT Thing Description Directory 服务,用于 Thing Description 集合。 本文档还描述了多种 Introduction 服务,并在必要时给出规范性定义 以支持这些服务。

本文档状态

本节描述本文档在发布时的状态。当前 W3C 出版物列表以及本技术报告的 最新修订版本,可在 W3C 技术报告 索引 https://www.w3.org/TR/ 中找到。

本规范未来的更新可能会纳入 新特性

本文档由 Web of Things 工作组作为推荐标准, 使用 推荐标准 流程发布。

W3C 建议将本规范作为 Web 标准广泛部署。

W3C 推荐标准是一种规范,它在经过广泛的共识构建之后, 得到 W3C 及其会员的认可,并且 获得了工作组成员对实现所作出的 免版税许可承诺。

本文档由一个依据 W3C 专利政策运作的工作组制作。 W3C 维护着一份 公开的专利披露列表, 其中包含与该工作组交付成果相关的任何专利披露; 该页面还包括披露专利的说明。任何实际知晓某项专利的个人, 若其认为该专利包含 必要 权利要求,则必须按照 W3C 专利政策第 6 节披露相关信息。

本文档受 2023 年 11 月 03 日 W3C 流程 文档约束。

1. 引言

Web of Things(WoT)定义了一种架构, 支持将 Web 技术与 IoT 设备集成并加以使用。 WoT Architecture [wot-architecture11] 文档定义了受支持的基本概念和使用模式。 但是,WoT Thing Description [wot-thing-description11] 是 WoT Discovery 的一项关键规范,因为 WoT Discovery 的 目的就是使 WoT Thing Description 可用。具体而言, WoT Discovery 必须允许经过认证和授权的实体(且仅限这些实体) 查找满足一组条件的 WoT Thing Description,例如具有某些语义, 或包含某些交互。反过来,为了支持安全和隐私目标, WoT Discovery 过程不得向未授权实体泄露信息。这不仅包括 Thing Description 本身所分发的信息,也包括某个给定实体正在请求 某些信息这一事实。

已经有许多发现机制被定义 [Discovery-Categorization-IoT], 因此我们必须说明为什么还要提出一种新的机制。首先, 许多现有发现机制的安全和隐私保护相对较弱。我们的目标之一, 是建立一种不仅使用最佳实践来保护元数据,而且还能够在需要时 升级以支持未来最佳实践的机制。其次,我们以广义方式使用发现, 以同时包含本地和非本地机制。虽然本地机制可能使用广播协议, 但非本地机制可能需要超出当前网络段,因为广播无法扩展, 因此需要另一种方法,例如搜索服务。我们的方法是在需要时使用 现有机制,以自举到一个更通用且安全的元数据分发系统。第三, 我们所分发的元数据,即 WoT Thing Description,是高度结构化的, 并包含数据模式和语义注解等丰富数据。基于简单键值对列表的 现有发现机制并不适合。同时,虽然使用现有语义数据查询标准, 例如 SPARQL [SPARQL11-OVERVIEW], 对某些高级使用场景而言可能是合适的,但对于许多预期的 IoT 应用来说可能需要过多工作。因此,为了处理更基础的应用, 我们还定义了一些更简单的查询机制。

在定义一些基本术语之后,我们将总结 WoT Discovery 的 基本使用场景和要求。这些是 WoT Use Cases [wot-usecases] 和 WoT Architecture [wot-architecture11] 文档中所提出的更详细、更完整的使用场景和要求的一个子集。 然后,我们将描述 WoT Discovery 过程的基本架构, 该架构采用两阶段的 Introduction/Exploration 方法。 此架构的基本目标,是能够使用现有发现标准来为访问受保护的 发现服务进行自举,但仅向授权用户分发详细元数据, 并且还尽可能保护发起查询者免受窃听者影响。随后,我们描述 具体 Introduction 和 Exploration 机制的细节。特别是, 我们详细定义了一个用于 WoT Thing Description Directory(WoT TDD) 服务的规范性 API,该服务为 WoT Thing Description 集合提供搜索机制, 这些集合可以由 Things 或代表它们行事的实体动态注册。 不过,WoT Discovery 机制也支持从常规 Web 服务分发单个 TD, 其中一种特殊情况就是自描述。最后,我们讨论若干安全和隐私考虑, 包括一组潜在风险和缓解措施。

2. 一致性

除标记为非规范性的章节之外,本规范中的所有编写指南、 图示、示例和注释均为非规范性内容。本规范中的其他所有内容均为 规范性内容。

本文档中的关键词 MAYMUSTOPTIONALRECOMMENDEDSHOULDSHOULD NOT 应按照 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释,但仅当它们以全大写形式出现时才如此, 如此处所示。

3. 术语

本节为非规范性内容。

基本的 WoT 术语,例如 ThingThing DescriptionTD)、Thing ModelTM)、PropertyActionEventAnonymous TDDiscovererDiscoveryExplorationIntroductionThing Description ServerTD Server)、Thing Description DirectoryTDD)、Partial TDEnriched TD,均在 WoT Architecture 1.1 规范的 第 3 节中定义 [wot-architecture11]。

4. 架构

本节为非规范性内容。

1 展示了 WoT Discovery 过程的概览。Discovery 使用两阶段架构, 以调和既要开放、又要将元数据访问限制给授权实体这两种相互竞争的要求。 在第一阶段,可以使用一组相对开放的“Introduction”机制中的一个或多个, 生成一组候选 URL。这些 URL 本身不包含元数据, 但会在第二阶段用于引用“Exploration”服务,这些服务能够在认证之后, 以 Thing Description 的形式实际提供元数据。

发现过程概览
1 发现架构概览。一组开放的 Introduction 机制提供一组 URL,这些 URL 指向 Exploration 服务, 后者只有在适当认证之后才提供元数据(TD)。Thing Link 和 Thing Description Directory 提供了额外的灵活性,但是否从其中 检索进一步结果由应用自行决定。

其意图是让 Introduction 机制成为相对开放的“首次接触”机制, 为 Discovery 过程的其余部分提供起点。在本文档中,我们为若干 Introduction 机制规定细节,这些机制适用于不同使用场景, 包括本地和非本地场景,但实际上,只要某种机制能够返回 URL, 它就可以提供 Introduction。不过,Introduction 不包含任何安全或 隐私控制,因此不应直接提供元数据。相反,由 Introduction 机制 提供的 URL 会引用“Exploration”服务。Exploration 服务实际会 提供元数据,但只有在应用了适当的认证和访问控制之后才会提供。

Discovery 过程可以从其 Introduction 阶段产生一组 URL 作为输出, 即使只使用了一个 Introduction 机制(某些 Introduction 机制本身 可以返回多个 URL)。Exploration 阶段之后的最终输出也可以是一组 Thing Description。

Introduction 阶段提供的每个 URL 始终指向一个 Exploration 服务端点, 该端点将返回单个 Thing Description。在最简单的情况下, 此 URL 引用由 Web 服务器提供的普通资源,该资源提供描述 IoT 端点设备的 Thing 的 Thing Description。作为一种特殊情况, 对于自描述的 Thing,Introduction URL 可能直接指向由该 Thing 提供的、服务于其自身 Thing Description 的端点。

通常,Thing Description 可以通过各种方式提供,尤其是它们可能不是 自描述的。例如,

此类 Thing 的 Thing Description 应由独立服务提供。

本文档规定了两种允许更高灵活性的特殊情况:

Discovery 过程并不必须检索 Thing Description Directory 的内容, 并将其作为结果的一部分返回,因为通常这可能导致产生庞大的结果集。 相反,应用应扫描结果以查找 Thing Description Directory TD, 并决定是否从中检索 TD,可能还会选择性地检索。同样, 并不要求自动跟随 Thing Link;相反,应用可以选择性地跟随它们。

5. Discoverer 过程

在本节中,我们将从客户端的角度描述 WoT Discovery 过程, 以及说一个客户端支持 WoT Discovery 意味着什么。我们将使用术语 Discoverer 来表示 作为 WoT Discovery 过程客户端的实体。Discoverer 可以是完整的 Consumer,也可以不是。不过,Discoverer 确实需要读取并提取 Directory 和 Thing Link 的特殊 TD 中的信息,并使用其中提供的 特定 affordance 和链接。反过来,Consumer 可以不支持 Discovery, 虽然这是推荐的 [wot-architecture11]。

WoT Discovery 过程的设计使得几乎任何能够根据单个 URI 获取单个 TD 的客户端,都可以被称为支持 WoT Discovery。当然,Discoverer 可以 支持更强大的 Discovery 机制,但其中一些机制有额外要求。 某些 Introduction 机制可以返回多个 URL,而每个 URL 又可以用于 获取至少一个 TD。因此,即使没有 TDD,也可以发现多个 TD。

以下断言描述了 Discoverer 的具体职责:

上述过程支持一种让 Directory 引用其他 Directory 而无需复制其 TD 的方式:希望引用其他 Directory 的 Directory 应包含一个 Thing Link, 其 "describedby" 关系指向另一个 Directory 服务的 TD。然后, 上述过程将扩展 Thing Link,以获得该 Directory 的实际 TD, 然后(可选地)使用适当的 Directory affordance 来访问被链接 Directory 的内容。注意,这样的 Thing Link 指向 Directory 的 TD, 而不是指向 Directory 本身。它们可以托管在同一位置,也可以不在同一位置。

递归获取此类被链接 Directory 的内容,尤其是在没有特定查询或过滤器的情况下, 很容易导致下载大量数据。此类递归扩展应限于需要它的使用场景, 例如库存管理、审计或索引。

Directory 服务的 URL 还可以与下文提到的 SPARQL 查询的联邦能力一起使用, 在大多数情况下,这将是一种从一组分布式 Directory 服务中收集特定信息的 更高效方式。不过,SPARQL 需要此类联邦所用 SPARQL 端点的 URL, 该 URL 可以在支持 SPARQL 查询的 Directory 的 TD 中找到。 这与指向 Directory 的 TD 的 URL 并不相同。

6. Introduction 机制

本章描述用于与 Things 或 Thing Description Directory 进行初始接触的机制。 Thing 或 Thing Description Directory 可以向 Consumer 提供以下任一机制。 introduction 机制的结果始终是一个 exploration 服务的 URL(地址), 该服务可用于在适当认证之后获得详细元数据(TD)。也可以使用多个 introduction 机制并合并结果。只要能以某种方式获得至少一个 exploration 服务的 URL,就不强制要求使用特定的 introduction 机制。

6.1 Direct

为了获得 exploration 服务的 URL,可以使用任何会产生单个 URL 的机制 MAY 这包括 Bluetooth beacon、二维码,以及由用户输入的书面 URL。 对所有此类 URL 的请求 MUST 产生一个 TD, 如 7. Exploration 机制 中所规定。 对于自描述的 Thing,这可以是 Thing 自身的 TD。 如果该 URL 引用一个 Thing Description Directory,则这 MUST 是该 Thing Description Directory 的 Thing Description。

6.2 Well-Known URI

Thing 或 Thing Description Directory MAY 使用 Well-Known Uniform Resource Identifier [RFC8615] 来通告其存在。 如果 Thing 或 Thing Description Directory 使用 Well-Known Uniform Resource Identifier [RFC8615] 来通告其存在,则它 MUST 将自己的 Thing Description 注册到以下路径: /.well-known/wot

当对上述 Well-Known URI 发起请求时,服务器 MUST 返回一个 Thing Description,如 7. Exploration 机制 中所规定。

6.3 基于 DNS 的服务发现

Thing 或 Thing Description Directory MAY 使用 DNS-Based Service Discovery(DNS-SD)[RFC6763]。 这也可以在同一本地网络中与 Multicast DNS(mDNS)[RFC6762] 结合使用。

下表列出了用于通告其存在的服务名称和协议。 这些是规范性的,因为每一项都可用于现有 exploration 机制:

服务名称 Thing 或 TDD 协议
_wot._tcp Thing HTTP over TCP、HTTP over TLS/TCP、CoAP over TCP,或 CoAP over TLS/TCP
_directory._sub._wot._tcp TDD HTTP over TCP、HTTP over TLS/TCP、CoAP over TCP,或 CoAP over TLS/TCP
_wot._udp Thing CoAP over UDP 或 CoAP over DTLS/UDP

以下附加服务名称已被定义供未来使用。不过,该定义是资料性的, 因为目前尚未定义使用 CoAP over UDP 的 directory 服务:

服务名称 Thing 或 TDD 协议
_directory._sub._wot._udp TDD CoAP over UDP 或 CoAP over DTLS/UDP

对于基于 TCP 的服务,以下信息 MUST 包含在由 Service Instance Name 指向的 TXT 记录中:

td
Thing 的 Thing Description 的绝对路径名, 或 Thing Description Directory 的 Thing Description 的绝对路径名。
type
Thing Description 的类型,即 ThingDirectory。如果省略, 则假定类型为 Thing
scheme
URL 的 scheme 部分。可以指定以下值之一, 并采用标准注册 URI 解释 [RFC7595]: http(HTTP over TCP)、https (HTTP over TLS/TCP)、coap+tcp(CoAP over TCP),或 coaps+tcp(CoAP over TLS/TCP)。 如果省略,则假定 scheme 为 http

对于基于 UDP 的服务,以下信息 MUST 包含在由 Service Instance Name 指向的 TXT 记录中:

td
Thing 的 Thing Description 的绝对路径名, 或 Thing Description Directory 的 Thing Description 的绝对路径名。
type
Thing Description 的类型,即 ThingDirectory。如果省略, 则假定类型为 Thing
scheme
URL 的 scheme 部分。可以指定以下值之一, 并采用标准注册 URI 解释 [RFC7595]: coap(CoAP over UDP)或 coaps (CoAP over DTLS/UDP)。如果省略,则假定 scheme 为 coap

23 展示了使用 DNS-SD 和 mDNS 支持发现 Thing 或 Thing Description Directory 的示例序列。

使用 DNS-SD 和 mDNS 发现 Thing 的示例序列
2 使用 mDNS 发现 Thing 的 Thing Description
使用 DNS-SD 和 mDNS 发现 directory 服务的示例序列
3 使用 mDNS 发现 Thing Description Directory(TDD)的 Thing Description

Thing 或 Thing Description Directory MAY 使用 Constrained RESTful Environment(CoRE)Link Format [RFC6690] 通告其存在。 ThingThing Description Directory MAY 使用 CoRE Resource Directory [RFC9176] 来注册指向其对应 Thing Description 的链接。

以 Thing 的 Thing Description 为目标的 Link 的资源类型 (rtMUSTwot.thing Thing Description Directory 的 Thing Description 为目标的 Link 的资源类型 MUSTwot.directory

6.5 DID 文档

使用 Decentralized Identifier (DID)[DID-CORE] 的 ThingThing Description Directory MAY 通过在该 TD 标识符解析到的 DID Document 中分别包含类型为 WotThingWotDirectory 的 DID Service Endpoint,来通告其 TD 的位置。

为了定义用于 WoT Discovery 的 Service Endpoint,通过解析 ThingThing Description Directory 的 DID 所获得的 DID Document MUST 在其 @context 中包含 URL https://www.w3.org/2022/wot/discovery-did [did-spec-registries]。

如果通过解析 ThingThing Description Directory 的 DID 所获得的 DID Document 包含类型为 WotThingWotDirectory 的 Service Endpoint, 则该 Service Endpoint MUST 分别引用描述 该 Thing 的 TD(当使用 WotThing 服务名称时),或描述该 Thing Description DirectoryTD (当使用 WotDirectory 服务名称时) [did-spec-registries]。

7. Exploration 机制

本节在提供一些通用背景材料之后,定义受支持的 exploration 机制。

7.1 概述

Exploration 机制的高层类图
4 exploration 机制的 高层类图,描绘 Thing Description Server 和 Thing Description Directory 如何提供 TD。Self-describing Thing 是 Thing Description Server 的一种特殊情况,它本身也是一个 Thing。Thing Description Directory 作为其所包含的每个 Thing Description 的 Thing Description Server。

4 描绘了 TD Server(提供单个 TD,包括用于自描述的 TD)和 Thing Description Directory 服务的高层信息模型。Thing Description Directory 可以包含 TD,同时它本身也是一个 Thing,这意味着它拥有自己的 TD。Directory 还托管用于检索其他 Thing 的单独 TD 的 Web 服务端点,并且其中每一个都可以用作 TD Server。 Thing 通常可以托管自己的 TD,在这种情况下它就是 Self-Describing Thing。Directory 不强制要求自描述,但也可能存在 同时是 Thing Description Directory 和 Self-Describing Thing 的 Self-Describing Thing Description Directory。

两个基本的 exploration 机制在 7.2 Thing Description Server7.3 Thing Description Directory 中描述。

7.1.1 本体

discovery 上下文中的 TD 本体
5 Discovery 上下文中 Thing Description 的本体。

5 将 Discovery 本体展示为 Thing 本体的扩展。

该本体包含一个类,用于表示与 Directory 中存储的 TD 相关联的元数据。该类称为 RegistrationInformation, 并作为 Directory 规范的一部分在 7.3.1.1 注册 信息中描述。

Discovery 本体还定义了两个新的 Thing Description 类,它们在以下各节中描述,可用于建模特殊的 exploratory 元数据: ThingDirectoryThingLink

7.1.1.1 ThingDirectory
描述 Thing Description Directory 实例的 TD MUST 使用 discovery context 中的类型 ThingDirectory,或使用 URI https://www.w3.org/2022/wot/discovery#ThingDirectory

此类的 TD 可以从 Directory 的 Thing Model 派生;参见 7.3.2.4 API 规范(Thing Model)

7.1.2 安全自举

exploration 服务的目的是提供 TD, 但只能在适当认证之后,并且只向授权方提供。不过,在某些情况下, Discoverer 可能不知道通过 exploration 服务访问 TD 需要哪些安全凭据, 尤其是在临时场景中。由于在首次访问 exploration 服务时,如果未提供适当的认证凭据,Discoverer 还不能访问 TD, 因此 Discoverer 不能依赖 TD 中保存的安全元数据来了解需要哪种 认证和授权信息。如果 Discoverer 没有先验知识, 它就必须依赖现有的安全协商支持来为访问进行自举, 至少要先访问 TD 本身。

我们为 HTTP 协议定义以下内容, 对于该协议,安全协商过程已经存在。不过,大多数 HTTP 协商过程都假定 有人类用户参与,但这也适用于 WoT Discovery,因为此问题通常会在用户 尝试访问公共 WoT 服务或执行新设备集成时出现。在这种情况下, 协商的目的是就访问系统需要哪些凭据提供指引。

在使用 exploration 服务来自动化系统管理的情况下, 最好预先确立访问相关 exploration 服务所需的凭据 (以及认证机制),这样就不需要安全自举。因此, 安全自举不是强制性特性,并且可以在将与预先确立的安全机制一起使用的 设备上省略或禁用。

安全自举也可能只在首次访问 TD 时才是必要的。 一旦 Discoverer 已经确定访问某个特定 exploration 服务需要哪些凭据和 认证机制,它们就可以保留此信息,并尝试在未来访问中使用。 但请注意,根据所使用的安全方案,凭据本身可能会过期, 并且可能需要定期重新确立。

任何提供 TD 的 HTTP 端点都 MAY 提供安全自举。 如上所述,如果安全机制已经预先确立,则允许禁用或省略安全自举。 例如,如果某个安装环境希望使用 OAuth2 client flow,并提前向潜在客户端提供要使用的认证服务器地址, 那么可以禁用安全自举,因为替代方案将是包含其他(并且可能更弱的) 认证形式。

在 HTTP 协议中,通常可以通过 HTTP 服务器返回 “401 (Unauthorized)”响应码,并结合指定所需信息的 WWW-Authenticate header,来协商要使用的认证和 授权机制。为了获得访问权限,客户端随后需要使用必要信息再次发起请求。 IANA 注册了若干认证方案。不过,并非所有这些方案都被广泛使用, 有些是实验性的,并且它们与 TD 支持的方案只有部分重叠。 此外,请注意,IANA 注册中的 oauth 方案指的是 OAuth1,它已被弃用,因此不应使用。相关的 OAuth2 flow, 即 code flow,不是以 401 响应开始, 而是以重定向到认证服务器开始,最终产生可用于访问的凭据 (在 WoT 中为 bearer token)。

基于这些考虑,为了在各种设备以及浏览器上启用安全自举, 应遵守以下约束:

  • 如果在 exploration 服务上启用了 安全自举,则在使用 introduction 机制提供的 URL 进行初始接触之后,如果尚未提供认证信息, 但在提供认证信息后可以授予访问权限,则 exploration 服务 MUST 使用适用于 Basic、Bearer、Digest 或 OAuth2 安全方案之一的 HTTP 响应码进行回复。 请注意,如果 exploration 服务由于某种原因不想提供访问, 或者安全自举被禁用,它可以忽略该请求,或使用其他代码回复, 例如 404 或 403。此外,如果不需要认证,则系统可以像已经提供 认证信息一样,立即使用所请求的 TD 进行回复。不过, 只有在作为响应提供的 TD 不包含且不能用于推断 Personally Identifiable Information 时,绕过认证才是合适的; 参见 9. 隐私 考虑
  • 如果在 exploration 服务上使用以下 IANA 注册的 HTTP Authentication Scheme 之一启用了安全自举: Basic、Bearer 或 Digest,则(遵循这些认证方案的标准) 旨在提供 TD 的 API 端点上的 401 HTTP 响应必须包含 WWW-Authenticate header,以及描述所需授权的任何其他 header。关于要求的细节,应查阅上述每种认证方案的 IANA 注册表。 同样,按照 OAuth2 规范,如果在安全自举期间使用 OAuth2 code flow,则必须使用 “302 (Found)” 或 “303 (See Other)” 响应码重定向到认证服务器,访问凭据最终以 bearer token 表示。 请注意,WoT Thing Description 1.1 中支持的另一个 OAuth2 flow client 期望初始访问的是认证服务器, 而不是最终端点,因此不能通过安全自举使用。这些要求也只适用于 可能需要支持安全自举的 exploration 服务端点,即提供 TD 的端点, 而不适用于同一 exploration 服务可能提供的其他端点。 特别是,这些要求只适用于可以由 introduction 机制引用的 URL, 而不适用于(例如)事件订阅端点。

在 [wot-architecture11] 和 [wot-thing-description11] 中,有与访问 TD 何时需要认证以及使用安全传输相关的安全和隐私考虑。 另见 9. 隐私考虑。总之, 公共服务要求使用安全传输(例如 TLS),即使在私有网络上也强烈建议使用 安全传输(即使没有认证要求,也可保护查询的机密性), 并且只有在不存在 Personally Identifiable Information, 或无法推断出此类信息的有限情况下,才应考虑在没有认证和授权的情况下 提供请求。

7.2 Thing Description Server

任何可以由 URL 引用,并在适当认证和访问控制下返回 TD 的 Web 服务, 都可以用作 exploration 机制。我们将其称为 Thing Description Server 或 TD Server。TD Server 不需要是 Thing。特别是,TD 可以托管在普通 Web 服务器上,并通过其 URL 引用。

TD Server 可用于支持自描述。对于自描述, Thing 托管 其自身的 TD, 并通过由 URL 标识的 Web 资源使其可用。 不过,这样的 Web 资源不会作为 TD 本身中的 affordance 被包含。 该 Web 资源可以与 6.2 Well-Known URI 中定义的、作为 Introduction 机制使用的 well-known URL 相同,也可以不同。

安全传输的使用受 [wot-architecture11] 和 [wot-thing-description11] 规范的安全考虑和隐私考虑章节中给出的断言约束, 这些断言定义了推荐或强制使用安全传输,以及推荐使用相互认证的场景。

使用以下协议分发 TD 的 TD Server 受以下约束:

HTTP

提供 TD 的基于 HTTP 的 TD Server MUST 使用 GET 方法提供该资源。 提供 TD 的基于 HTTP 的 TD Server 的成功响应 MUST 具有 200 (OK) 状态,并在 body 中包含该 TD 使用 JSON 序列化的成功 响应 MUST 在 Content-Type header 中包含 application/jsonapplication/td+json 这里首选 application/td+json,因为它更具体,并且隐含 application/json成功响应 body 的默认 序列化格式 MUST 为 JSON,并采用 JSON-LD 1.1 [JSON-LD11] 语法。 JSON-LD 语法允许语义扩展和处理。 提供 TD 的基于 HTTP 的 TD Server MAY 通过服务器驱动的内容协商提供替代表征,即遵循请求的 Accept 和 Accept-Encoding header,并使用受支持的 TD 序列化以及等价的 Content-Type 和 Content-Encoding header 进行响应。 此外, 按照 WoT Thing Description 1.1 规范中规范性定义的过程 [wot-thing-description11], 提供 TD 的基于 HTTP 的 TD Server 可以在服务器驱动的内容协商之后,使用不同的默认语言提供修改后的 TD 或 错误响应,即遵循请求的 Accept-Language header。

提供 TD 的基于 HTTP 的 TD Server MUST 通过仅返回与对同一端点的 GET 请求所返回的 header 等价的 header,来响应 HEAD 请求。 这使客户端能够提前检索 Content-Length 等 HTTP header,以了解该 TD 的大小(以字节为单位),并决定高效的查询策略。

在受限环境中,单个 TD 对服务器或客户端来说可能过大而无法处理。 关于所请求 payload 的增量传输的特定协议建议,参见 10.1 增量 传输

错误响应:

  • 401 (Unauthorized):未认证。
  • 403 (Forbidden):对该资源的权限不足。
CoAP

提供 TD 的基于 CoAP 的 TD Server MUST 使用 GET 方法提供该资源。 提供 TD 的基于 CoAP 的 TD Server 的成功响应 MUST 具有 2.05 (Content) 状态,包含值为 50 (application/json)或 432 (application/td+json)的 Content-Format option, 并在 payload 中包含该 TD 首选 Content-Format 432,因为它更具体,并且隐含 Content-Format 50。请注意,payload 可能会使用 block-wise transfer [RFC7959] 分拆到多个消息交换中。 提供 TD 的基于 CoAP 的 TD Server MAY 通过服务器驱动的内容协商提供替代表征,即遵循请求的 Accept option,并使用受支持的 TD 序列化和等价的 Content-Format option 进行响应。

提供 TD 的基于 CoAP 的 TD Server SHOULD 通过在其下一个响应中包含 TD 的大小估计值,来响应包含 Size2 option 的请求。 这在使用 block-wise transfer 获取 TD 时是相关的,并使客户端能够在总 payload 大小对其而言过大而无法处理时 中止检索。

在受限环境中,单个 TD 对服务器或客户端来说可能过大而无法处理。 关于所请求 payload 的增量传输的特定协议建议,参见 10.1 增量 传输

错误响应:

  • 4.01 (Unauthorized):未认证。
  • 4.03 (Forbidden):对该资源的权限不足。

7.3 Thing Description Directory

Thing Description Directory(简称 TDD 或 Directory)是一个 Thing, 它提供用于 管理一组描述其他 Thing 的 TD 的服务。

7.3.1 信息模型

4 所示,Thing Description Directory 可以包含零个或多个 TD。对于每个 TD,directory 会维护用于记录和搜索目的的额外元数据。这些元数据在 7.3.1.1 注册 信息7.3.1.3 匿名 TD 标识符中描述。一个 TD 如果在与 directory 的交互过程中嵌入这类额外元数据,则称为 Enriched TD

7.3.1.1 注册 信息

Discovery 上下文中 TD 的本体已在 5 中介绍。 RegistrationInformation 类与存储在 directory 中的 TD 相关联。下表列出了注册信息属性,供嵌入或 引用 Discovery 上下文的 TD 使用。请注意,只有 Enriched TD 会嵌入注册信息。一个 Enriched TD MUST 在其 @context 中包含 URI https://www.w3.org/2022/wot/discovery 在此表中,client 指 TD 的生产者或消费者, server 指 Thing Description Directory

每当使用 dateTime 表示绝对时间时,都 MUST 将其解释为 [RFC3339] 中规定的 date-time。 具体而言,时区偏移不是可选的。

编辑注: dateTime 与 date-time

我们应当更新本体,使其只使用 date-time 和 RFC3339,而不是这种奇怪的 dateTime 与 date-time 组合,但这也应在 TD 规范中 解决。不过,我们需要一个带有这一附加 限制的新 SHACL shape。

词汇术语 描述 Client 赋值 Server 赋值 类型
created 提供该 TD 实例在 directory 内创建时的绝对时间。

MAY 由 directory 设置,并返回给消费者。

只读 可选 dateTime
modified 提供该 TD 实例在 directory 内最后修改时的绝对时间。

MAY 由 directory 设置,并返回给消费者。

只读 可选 dateTime
expires 提供该 TD 实例注册过期时的绝对时间。

生产者 MAY 设置 此项,以在注册期间指示绝对过期时间。

对于支持可过期 TD 的 server:如果 ttl(相对过期时间)存在, server MUST 忽略 client 对 expires 的赋值, 而是在内部计算并设置它。

可选 可选 dateTime
ttl 生存时间:从注册时间起到 TD 实例注册过期时为止的相对时间量,单位为秒。

生产者 MAY 设置 此项,以在注册期间指示相对过期时间。

对于支持可过期 TD 的 server: server MUST 使用 ttl 计算 expires(绝对过期时间)值。

可选 只读 number
retrieved 从 server 检索 TD 时的绝对时间。

这对于打算处理其他绝对时间戳、 但没有内部时钟或其他获取当前时间手段的 client 很有用。

只读 可选 dateTime
7.3.1.2 注册过期
7.3.1.1 注册 信息介绍了一些属性,用于指定和 发现已注册 TD 的过期时间。

生产者可以设置过期时间,以告知 directory 和其他消费者 TD 注册的有效性。 对于动态 TD 的过期,expiry 也是一个有用的指示器, 例如当地理位置或属性等元数据的变化预期仅在有限时间内有效时。 消费者可以依赖过期时间来了解检索到的 TD 将有效多久,以及何时 需要请求更新的版本。检索到已过期 TD 的消费者, 可以将其视为非活动 client 的元数据。

对于 server,过期时间有助于实现对过时或意外 注册的自动移除。Server SHOULD 定期清除已超过 其过期时间的 TD。 规定全局性的强制要求或 过期时间上限是特定于应用的,超出本规范范围。 Server 可以强制规定或设置可配置的过期时间上限, 并拒绝不符合要求的请求。Server 的清除操作在与无法显式注销其 TD 的 client(例如 IoT 设备)交互时尤其有益。这可能是由于 特定协议的限制、故障、损毁或非正常退役导致的。 此类 client 应设置一个合理较短的过期时间,并在正常运行期间 定期延长它。可以通过完全或部分更新注册来延长过期时间, 包括不对 TD 做任何更改的更新;参见 7.3.2.1.3 更新。 如果 client 停止运行,具有清除能力的 directory 将自动移除其 注册。

7.3.1.3 匿名 TD 标识符
Directory 会为 Anonymous TD 分配本地标识符,以便从 directory 管理和检索此类 TD。 在 server 暴露 Anonymous TD(例如检索、列表、 搜索)的情况下,它 MUST 将本地 标识符添加为该 TD 的 id,以允许本地 引用。 该本地 标识符 SHOULD 是 UUID Version 4, 并以 URN [RFC4122] 形式呈现。 UUID Version 4 是随机数或伪随机数, 不会携带有关 host 或 resource 的非预期信息。

7.3.2 Directory Service API

Directory 服务提供对 System User Data 的访问,并且需要提供适当的安全和 隐私保护。WoT Directory Service API 实现中用于 真实性和机密性的安全传输协议和访问控制的使用, 受 [wot-architecture11] 中给出的 安全考虑和隐私考虑约束。

HTTP API 响应必须为成功响应和错误响应使用本节中 描述的适当状态码。 HTTP API MUST 使用 Problem Details [RFC7807] 格式 在 HTTP 客户端错误(4xx)和服务器错误(5xx)响应中 携带错误详情。 这使机器和人类都能了解高层错误类别和 细粒度详情。所有使用 Problem Details 描述的 HTTP API 错误 响应 MUST 使用 UTF-8 编码。 如果 HTTP 请求中设置了 Accept-Language header field,HTTP API 错误响应 MAY 使用主动协商以不同语言 报告详情 [RFC7231]。

这些 API 按 [RFC7231] 第 6 节中的定义设置 HTTP 状态码。 使用的错误码列表包括(但不限于)以下内容:

  • 400 (Bad Request):body、query 或 header 中的 client 输入无效。这会附带适当的响应消息。
  • 401 (Unauthorized):请求缺少有效的 认证凭据。如 7.1.2 安全 自举中所述,这是认证协商的第一步, 是为安全访问 TD 进行自举所必需的。有关所需凭据的信息 将包含在 WWW-Authenticate header 中。
  • 403 (Forbidden):访问该资源的权限不足。
  • 404 (Not Found):TD 或端点不存在。这会附带 适当的响应消息。

对于 每个响应 GET 方法的 HTTP 端点,server MUST 接受 HEAD 请求并且只返回 header。 这允许 client 在不接收 body 的情况下检索 Content-Length 等 header,并决定合适的策略来查询信息。 例如,受限 client 可以只请求对象的 必要部分(使用适当的搜索查询),或以较小子集检索项目列表。

在受限环境中,单个 TD 对 server 或 client 来说可能过大而无法处理。 这会影响读取(即检索一个或多个 TD 或 TD 片段) 和写入(即提交 TD 或 Partial TD)操作。 关于 payload 的增量传输的特定协议建议,参见 10.1 增量 传输

为确保数据通过 URL 安全传输,预期 client 和 server 都会对 与 URL 其余部分中的分隔符冲突的字符进行百分号编码/解码。 这些字符在 [RFC1738] 第 2.2 节中被定义为 不安全。 如果不安全字符出现在 URL 中,可能导致非预期行为, 例如,当 path 中包含的 resource ID 本身就是 URL, 或者出现在搜索 query string 中时。

directory API 包括强制、推荐和 可选特性。当 directory 因不支持推荐特性或 可选特性而无法回答请求时,它 SHOULD 通过返回适当的 HTTP 错误告知 client 缺少这些特性。 以下 示例可作为实现指南:

  • 如果缺少的特性是自定义现有 API 的功能, 使用 400 (Bad Request) 或 501 (Not Implemented)。
  • 如果未提供 API 端点(例如 /search/sparql 端点),使用 404 (Not Found)。
  • 如果现有 API 端点不支持某个方法 (例如 /things 端点上的 PATCH),使用 405 (Method Not Allowed)。

使用 TD 中已经提供的翻译来修改 TD 默认语言的过程, 在 WoT Thing Description 1.1 规范中作了规范性描述 [wot-thing-description11]。 使用此过程,Directory server 可以在服务器驱动的内容协商之后, 使用不同的默认语言提供修改后的 TD 或其自身 TD, 即遵循请求的 Accept-Language header。

7.3.2.1 Things API

Things API 是在 /things 端点提供的 RESTful HTTP API, 提供用于创建、检索、更新、删除和列表(CRUDL) TD 的接口。 该 API 的设计符合 [RFC7231] 和 [REST-IOT]。

HTTP API 遵循以下通用规则:

  • API MUST 提供列出 TD 的接口。 Search API 允许对此列表进行过滤和 选择;参见 7.3.2.3 Search API
  • API MAY 提供用于创建、读取、更新和 删除(CRUD)单个 TD 的接口。
  • 同时提供基于 HTTP 的读取和写入的 directory 被认为是完整 HTTP directory。 完整 HTTP directory SHOULD 实现全部 CRUDL(创建、 读取、更新、删除和列表)接口。 如果 directory 提供静态 TD 集合, 那么只实现读取和列表接口也是实用的。 对于打算仅通过 HTTP 暴露检索操作, 并通过带外机制执行其他操作的 directory 来说,这也很有用。 为了暴露只读 访问,directory MUST 对创建、更新和删除接口强制实施访问控制。
  • 所有请求和成功 响应 body 的默认序列化格式 MUST 为 JSON, 并使用 JSON-LD 1.1 [JSON-LD11] 语法以支持 扩展和语义处理。
  • Directory MAY 基于请求所指示的 Content-Type 或 Content-Encoding header 接受替代表征。 这 对需要向 directory 提供原始 JSON 以外的 表征作为输入的应用很有用。
  • Directory MAY 通过服务器驱动的内容 协商提供替代表征,即遵循请求的 Accept 和 Accept-Encoding header,并使用受支持的 TD 表征以及等价的 Content-Type 和 Content-Encoding header 进行响应。 这对于 需要从 directory 检索原始 JSON 以外的表征的应用很有用, 例如 Gzip 压缩的 JSON。

CRUDL 操作在以下章节中描述:

7.3.2.1.1 创建

创建指在 directory 内注册新的 TD

TD 对象按照 7.3.2.1.6 验证进行验证。请注意,TD 可以由其所描述的 Thing 生成,也可以不由它生成。 尤其对于 brownfield 设备,可能需要一个单独的 Discoverer 过程 或服务代表某个 Thing 生成并注册 TD。

具有 id 属性标识的 TD MUST 与没有标识符的 TD(Anonymous TD)区别处理。 创建操作详述如下:

  • 具有 id 的 TD MUST 在 HTTP PUT 请求的 body 中提交到 directory 的 /things/{id} 端点,其中 id 是唯一 TD 标识符, 存在于 TD 对象内部。 Anonymous TD 会以不同方式处理;见下文。 请求 SHOULD 包含用于 TD 的 JSON 序列化的 application/td+json Content-Type header。 TD 对象按照 7.3.2.1.6 验证进行验证。成功 处理后,server MUST 以 201 (Created) 状态响应。

    注:如果目标位置对应于已有 TD, 则该请求应改为作为更新操作继续,并响应 适当的状态码(参见更新章节)。

    具有标识符的 TD 的创建操作 在 7.3.2.4 API 规范(Thing Model) 中指定为 createThing action。

  • Anonymous TD MUST 在 HTTP POST 请求的 body 中提交到 directory 的 /things 端点。 该 请求 SHOULD 包含用于 TD 的 JSON 序列化的 application/td+json Content-Type header。 TD 对象按照 7.3.2.1.6 验证进行验证。Directory MUST 为任何 Anonymous TD 分配本地 标识符,以便从 directory 进行本地管理和检索。 系统生成 ID 的 scheme 在 7.3.1.3 匿名 TD 标识符中描述。成功 处理后,server MUST 以 201 (Created) 状态响应,并带有一个包含已创建 TD resource 的 系统生成 URI 的 Location header。 该系统生成 URI 包含 TD 的 系统生成标识符,并可随后用于从 directory 查询该 TD。

    Anonymous TD 的创建操作在 7.3.2.4 API 规范(Thing Model) 中指定为 createAnonymousThing action。

支持可过期 TD 的 server 将按 7.3.1.2 注册过期中描述的方式实现此类功能。 特别是,如果在创建期间给出 ttl(相对过期时间),此类 server 将计算并存储 expires 值。

7.3.2.1.2 检索

必须使用 HTTP GET 请求在 /things/{id} 端点执行已有 TD 的检索 MUST, 其中 id 是唯一 TD 标识符。 成功 响应 MUST 具有 200 (OK) 状态,并在 body 中包含所请求的 TD。 使用 JSON 序列化的成功 响应 MUST 在 Content-Type header 中包含 application/jsonapplication/td+json 这里首选 application/td+json,因为它更具体,并且隐含 application/json。请注意,默认 序列化为带 JSON-LD 语法的 JSON,并且 可以协商替代序列化;参见 7.3.2.1 Things API

retrieve 操作在 7.3.2.4 API 规范(Thing Model)中指定为 retrieveThing action。

以下是检索到的 TD 的示例:

这是一个 Enriched TD,它包含注册信息,例如 TD 在 directory 内的创建和修改时间。

下面的示例展示了一个检索到的 Anonymous TD,它采用 Enriched TD 形式,并具有本地标识符 urn:uuid:48951ff3-4019-4e67-b217-dbbf011873dc

以下是检索到的 TD 的示例,该 TD 注册时使用了 3600 秒(一小时)的相对过期时间。server 已将 绝对过期时间计算为修改时间之后的一小时。

为便于阅读,本示例中的时间值设置为精确数字。 在现实设置中,时间值可以包含小数部分。

7.3.2.1.3 更新

更新操作用于替换或部分 修改已有 TD。

更新操作如下所述:

  • 修改后的 TD 在使用 HTTP PUT 请求提交到 /things/{id} 端点时,MUST 替换已有 TD, 其中 id 是已有 TD 的标识符。 该 请求 SHOULD 包含用于 TD 的 JSON 序列化的 application/td+json Content-Type header。 TD 对象按照 7.3.2.1.6 验证进行验证。成功后,server MUST 以 204 (No Content) 状态响应。

    此操作在 7.3.2.4 API 规范(Thing Model)中指定为 updateThing property。

    支持可过期 TD 的 server 将按 7.3.1.2 注册过期中描述的方式实现此类功能。 如果在更新操作期间设置了 ttl (相对过期时间),server 将计算并设置 expires(绝对过期时间)值。

    注:如果目标位置不对应于已有 TD, 则该请求应改为作为创建操作继续,并响应 适当的状态码(参见创建章节)。换句话说, HTTP PUT 请求同时作为 创建或更新操作。

  • 当修改部分使用 HTTP PATCH 请求提交到 /things/{id} 端点时,已有 TD MUST 被部分修改, 其中 id 是已有 TD 的标识符。 部分 更新 MUST 使用 [RFC7396] 中描述的 JSON merge patch 格式处理。 请求 MUST 包含 application/merge-patch+json Content-Type header,用于 merge patch 文档的 JSON 序列化。 输入 MUST 采用 Partial TD 形式,并符合原始 TD 结构。 如果输入包含出现在原始 TD 中的成员, 则会替换其值。如果某个成员没有出现在 原始 TD 中,则添加该成员。如果成员 被设置为 null 且出现在原始 TD 中,则移除该成员。具有对象 值的成员会递归处理。应用 修改之后,TD 对象按照 7.3.2.1.6 验证进行验证。成功后, server MUST 以 204 (No Content) 状态响应。

    此操作在 7.3.2.4 API 规范(Thing Model)中指定为 partiallyUpdateThing property。

    支持可过期 TD 的 server 将按 7.3.1.2 注册过期中描述的方式实现此类功能。 在部分更新操作期间,如果生成的 TD 具有 ttl (相对过期时间),server 将计算并 设置新的 expires(绝对过期时间) 值。

    patch 操作对于高效延长使用 ttl(相对过期时间) 值的注册的过期时间尤其有用。这通常通过提交一个 空 merge patch 文档来完成,即空 JSON 对象。这实际上等价于执行一个不更新任何内容的 部分更新操作,但会触发重新计算 expires (绝对过期时间)值。此过期功能 只有在 server 按 7.3.1.2 注册过期中的定义支持它时才有效。

    以下示例是一个 merge patch 文档, 仅用于更新 TD 的 base 和 registration expires 字段:

7.3.2.1.4 删除

删除操作 MUST 使用 HTTP DELETE 请求在 /things/{id} 执行,其中 id 是 已有 TD 的标识符。 成功 响应 MUST 具有 204 (No Content) 状态。 retrieve 操作 在 7.3.2.4 API 规范(Thing Model)中指定为 deleteThing property。

7.3.2.1.5 列表

listing 端点提供从 directory 查询完整 TD 对象集合的不同方式。

在许多场景中,相比完整 TD 对象,更倾向于检索部分内容,因为只需要 元素的子集(例如所有 TD 的某个 property 的 idhref),并且可以 节省网络资源。Search API 允许 查询 TD 对象的部分内容;参见 7.3.2.3 Search API

directory MUST 允许使用 HTTP GET 请求在 /things 端点检索已有 TD。 成功 响应 MUST 具有 200 (OK) 状态,并在 body 中包含 TD 数组。 使用 JSON 序列化的成功 响应 MUST 在 Content-Type header 中包含 application/jsonapplication/ld+json 这里首选 application/ld+json,因为它更具体,并且隐含 application/json。请注意,默认 序列化是带 JSON-LD 语法的 JSON,并且 可以协商替代序列化;参见 7.3.2.1 Things API

可能存在 client 需要以较小 TD 子集 检索集合的场景。虽然 Search API(7.3.2.3 Search API)确实提供了查询特定 范围的能力,但它可能不是最优的,也不够开发者友好。 server MAY 支持分页,以小子集返回 集合。 分页必须 基于以下规则:

  • limit query 参数设置为 正整数时,server MAY 响应一个 TD 子集, 其总数小于或等于所请求的 数量。
  • 当 返回的集合子集之后还有更多 TD 时, 响应 MUST 包含一个 next Link header [RFC8288],其中带有 下一个子集的 URL。 next link MUST 包含生成相同数据集及其排序所需的所有参数, 尤其是初始请求中给定的相同 limit 参数,以及以 下一个子集开头为锚点的从零开始的 offset 参数。 该 link MUST 是绝对的,或 相对于 directory API 的 base URL。 此外,它可以包含排序或 session 管理所必需的额外参数。
  • 所有 分页响应 MUST 包含 一个 canonical Link header [RFC8288],指向 该集合,并包含一个 etag 参数来表示集合的当前状态。 该 link 可以是绝对的,也可以 相对于 directory API 的 base URL。 etag 值可以是修订号、 时间戳或 UUID Version 4,并在 TD 集合以影响 TD 排序的方式发生变化时设置。 client 可以依赖 etag 值来了解该 集合在分页检索过程中是否保持一致。 例如,TD 的创建或删除,或用于 排序的 TD 字段的更新,可能会移动计算出的分页窗口。
  • 集合 MUST 按 TD 的唯一标识符, 使用 Unicode code point 顺序升序排序。

上述规范遵循 Linked Data Paging [LDP-Paging] 的一个子集,以允许 JSON-LD 数组的可选分页。 可以实现 Linked Data Paging 的其他部分,例如用于 遵循 client 的查询偏好,或添加其他 link relation 以进行语义 注解和替代导航链接。

以下示例提供了分页检索 TD 的演练:

作为 将 TD 数组作为响应 body 的替代方式, server MAY 发送 更详细的 payload,使 server 端信息(例如分页信息) 可以在实际数据之外一并包含。

替代分页格式的灵感来自 Hydra Advanced Concepts,更具体地说,是 Partial Collection View。根据我们的目的进行调整, 并使用 members 字段来容纳 TD 数组,listing 端点的形式如下:

为了告诉 server 发送哪种格式, 可以向请求添加额外 query 参数 ?format=array|collection?format=array 是默认 参数,不必显式提供,并会产生由纯 TD 数组构成的 server 响应。 ?format=collection 应产生 如 示例 9 中所述格式的 server 响应。

listing 操作在 7.3.2.4 API 规范(Thing Model)中指定为 things property。

7.3.2.1.6 验证

存储之前对 TD 对象进行句法验证是 RECOMMENDED 的, 以防止常见的错误 提交。 server SHOULD 至少使用 [wot-thing-description11] 中定义的 Minimal Validation 来验证 TD,包括使用 WoT Thing Description (1.0) JSON Schema WoT Thing Description 1.1 JSON Schema,以及 A. WoT Discovery TD 扩展的 JSON Schema 中定义的 JSON schema,以便根据 @context 的值适当地验证 Enriched TD

可以添加额外形式的验证来 支持各种使用场景。例如,某个使用场景可能 需要对输入 TD 进行有状态验证,以确保 version 值根据预定义规则初始化并 更新。

如果 server 未能验证 TD 对象,它 MUST 用必要详情告知 client,以识别并 解决错误。 验证错误 MUST 描述为 Problem Details [RFC7807], 并带有名为 validationErrors 的扩展字段, 该字段设置为由具有 fielddescription 字段的对象组成的数组。 这 对以机器可读方式表示错误是必要的。 所有使用 Problem Details 描述的验证错误 响应 MUST 使用 UTF-8 编码。 如 在错误报告通用规则中已经说明的那样, 如果 HTTP 请求中设置了 Accept-Language header field, 验证错误响应可以使用主动协商以 不同语言报告详情 [RFC7231]。

示例 10 是一个 包含两个验证错误的错误响应示例。

7.3.2.2 Events API

Notification API 用于通知 client 有关 directory 内维护的 TD 的变更。Directory MAY 实现 Notification API。

Notification API MUST 遵循 Server-Sent Events(SSE) [HTML] 规范,在 /events 端点向 client 提供事件。 特别是, server 对成功请求以 200 (OK) 状态和 text/event-stream Content Type 响应。 重新连接的 client 可以通过将最后一个事件 ID 作为 Last-Event-ID header 值提供,从最后一个事件继续。 server SHOULD 在每个事件中将事件 ID 作为 id 字段提供,并通过交付所有错过的 事件来响应重新连接的 client。

本节其余部分描述在 SSE 协议之上的实现 细节。使用 MQTT 等其他协议实现 notification 功能是可能的,并且可能在本规范未来 版本中形式化。

事件类型
server MUST 使用 thing_createdthing_updatedthing_deleted event type, 产生归因于 directory 内 Thing Description 生命周期的事件。
事件过滤
该 API 通过只交付 client 需要的事件, 支持 server 端事件过滤以 减少资源消耗。client library 可以 在 client 端提供额外过滤能力。

server MUST 支持基于 client 在订阅时给出的 event type 进行事件过滤。

例如,给定 URI Template /events{/type}

  • /events/thing_created 指示 server 只交付类型为 thing_created 的事件
  • /events 指示 server 交付所有事件

client 需要分别订阅,以便从 server 接收事件子集(例如仅 thing_createdthing_deleted)。使用 HTTP/2 时,同一 domain 上的多个订阅(HTTP stream)会在单个 连接上多路复用。

事件数据

event data MUST 包含 event object 的 JSON 序列化。 event data object 根据请求可以是 Partial TD,也可以是完整 TD object:

  • event data object MUST 至少 以 Partial TD 形式包含在该事件中被创建、 更新或删除的 TD 的标识符。
  • diff query 参数设置为 true 且事件具有 thing_created 类型时,server MAY 将完整 TD object 作为 event data 返回。
  • diff query 参数设置为 true 且事件具有 thing_updated 类型时,server MAY 按照 JSON Merge Patch [RFC7396] 格式告知 client 更新的部分。 基于 JSON Merge Patch [RFC7396] 的 thing_updated event data MUST 始终包含 TD 的标识符, 无论它是否被 更改。

    以下示例展示了对 示例 12 中的 TD 执行更新时触发的事件:

  • 对于 thing_deleted 事件, diff query 参数 MUST 被忽略。 换句话说,当 diff 设置为 true 时, server 不应在 thing_deleted 事件的 payload 中包含额外 property。
  • 当不支持 diff query 参数的 server 被请求使用 该 query 参数时,它应拒绝该请求。 这是为了在连接时告知 client 缺少此类 功能,并避免因缺失 event data 属性而导致运行时异常。

Notification API 在 7.3.2.4 API 规范(Thing Model) 中指定为三个 event affordance,分别为: thingCreatedthingUpdatedthingDeleted

:SSE Authorization Header

某些早期 SSE 实现(包括 HTML5 EventSource)不允许在初始 HTTP 请求中设置自定义 header。 Authorization header 在少数 OAuth2 flow 中是 必需的,而将其作为 query 参数传递是 不建议的。浏览器有 polyfill, 现代库也允许设置 Authorization header。

7.3.2.4 API 规范 (Thing Model)

这里以 Thing Model 的形式给出 Thing Description Directory 的 API 模板。该 Thing Model 是规范性的(除非另有说明),但不应被视为 实现 Thing Description Directory 或与其交互的 唯一参考。另请参阅 7.3.2 Directory Service API 中给出的规范。

此 Thing Model 中给出的 searchJSONPathsearchXPath affordance 不是规范性的, 仅供参考。

请注意,WoT TD 1.1 规范 [wot-thing-description11] 要求 ExpectedResponse class 的实例 (即与 form 中 key response 对应的 JSON object)具有 contentType,即使 HTTP response 的 body 应为空。在这种情况下, 可以使用 application/x-empty 以确保 由下面 TM 生成的 TD 是有效的。

{
    "@context": [
        "https://www.w3.org/2022/wot/td/v1.1",
        "https://www.w3.org/2022/wot/discovery"
    ],
    "@type": [
        "tm:ThingModel",
        "ThingDirectory"
    ],
    "title": "Thing Description Directory (TDD) Thing Model",
    "version": {
        "model": "1.0.0"
    },
    "base": "{{DIRECTORY_BASE_URL}}",
    "tm:optional": [
        "/actions/createThing",
        "/actions/createAnonymousThing",
        "/actions/retrieveThing",
        "/actions/updateThing",
        "/actions/partiallyUpdateThing",
        "/actions/deleteThing",
        "/actions/searchJSONPath",
        "/actions/searchXPath",
        "/actions/searchSPARQL",
        "/events/thingCreated",
        "/events/thingUpdated",
        "/events/thingDeleted"
    ],
    "properties": {
        "things": {
            "description": "Retrieve all Thing Descriptions",
            "uriVariables": {
                "offset": {
                    "title": "Number of TDs to skip before the page",
                    "type": "number",
                    "default": 0
                },
                "limit": {
                    "title": "Number of TDs in a page",
                    "type": "number"
                },
                "format": {
                    "title": "Payload format",
                    "type": "string",
                    "enum": [
                        "array",
                        "collection"
                    ],
                    "default": "array"
                }
            },
            "forms": [
                {
                    "href": "/things{?offset,limit,format}",
                    "htv:methodName": "GET",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 200,
                        "contentType": "application/ld+json",
                        "htv:headers": [
                            {
                                "htv:fieldName": "Link"
                            }
                        ]
                    },
                    "additionalResponses": [
                        {
                            "description": "Invalid query arguments",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        }
    },
    "actions": {
        "createThing": {
            "description": "Create a Thing Description",
            "uriVariables": {
                "id": {
                    "@type": "ThingID",
                    "title": "Thing Description ID",
                    "type": "string",
                    "format": "iri-reference"
                }
            },
            "input": {
                "description": "The schema is implied by the content type",
                "type": "object"
            },
            "forms": [
                {
                    "href": "/things/{id}",
                    "htv:methodName": "PUT",
                    "contentType": "application/td+json",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 201
                    },
                    "additionalResponses": [
                        {
                            "description": "Invalid serialization or TD",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        },
        "createAnonymousThing": {
            "description": "Create an anonymous Thing Description",
            "input": {
                "description": "The schema is implied by the content type",
                "type": "object"
            },
            "forms": [
                {
                    "href": "/things",
                    "htv:methodName": "POST",
                    "contentType": "application/td+json",
                    "response": {
                        "description": "Success response including the system-generated URI",
                        "htv:headers": [
                            {
                                "description": "System-generated URI",
                                "htv:fieldName": "Location"
                            }
                        ],
                        "htv:statusCodeValue": 201
                    },
                    "additionalResponses": [
                        {
                            "description": "Invalid serialization or TD",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        },
        "retrieveThing": {
            "description": "Retrieve a Thing Description",
            "uriVariables": {
                "id": {
                    "@type": "ThingID",
                    "title": "Thing Description ID",
                    "type": "string",
                    "format": "iri-reference"
                }
            },
            "output": {
                "description": "The schema is implied by the content type",
                "type": "object"
            },
            "safe": true,
            "idempotent": true,
            "forms": [
                {
                    "href": "/things/{id}",
                    "htv:methodName": "GET",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 200,
                        "contentType": "application/td+json"
                    },
                    "additionalResponses": [
                        {
                            "description": "TD with the given id not found",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 404
                        }
                    ]
                }
            ]
        },
        "updateThing": {
            "description": "Update a Thing Description",
            "uriVariables": {
                "id": {
                    "@type": "ThingID",
                    "title": "Thing Description ID",
                    "type": "string",
                    "format": "iri-reference"
                }
            },
            "input": {
                "description": "The schema is implied by the content type",
                "type": "object"
            },
            "forms": [
                {
                    "href": "/things/{id}",
                    "htv:methodName": "PUT",
                    "contentType": "application/td+json",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 204
                    },
                    "additionalResponses": [
                        {
                            "description": "Invalid serialization or TD",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        },
        "partiallyUpdateThing": {
            "description": "Partially update a Thing Description",
            "uriVariables": {
                "id": {
                    "@type": "ThingID",
                    "title": "Thing Description ID",
                    "type": "string",
                    "format": "iri-reference"
                }
            },
            "input": {
                "description": "The schema is implied by the content type",
                "type": "object"
            },
            "forms": [
                {
                    "href": "/things/{id}",
                    "htv:methodName": "PATCH",
                    "contentType": "application/merge-patch+json",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 204
                    },
                    "additionalResponses": [
                        {
                            "description": "Invalid serialization or TD",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        },
                        {
                            "description": "TD with the given id not found",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 404
                        }
                    ]
                }
            ]
        },
        "deleteThing": {
            "description": "Delete a Thing Description",
            "uriVariables": {
                "id": {
                    "@type": "ThingID",
                    "title": "Thing Description ID",
                    "type": "string",
                    "format": "iri-reference"
                }
            },
            "forms": [
                {
                    "href": "/things/{id}",
                    "htv:methodName": "DELETE",
                    "response": {
                        "description": "Success response",
                        "htv:statusCodeValue": 204
                    },
                    "additionalResponses": [
                        {
                            "description": "TD with the given id not found",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 404
                        }
                    ]
                }
            ]
        },
        "searchJSONPath": {
            "description": "JSONPath syntactic search.  This affordance is not normative and is provided for information only.",
            "uriVariables": {
                "query": {
                    "title": "A valid JSONPath expression",
                    "type": "string"
                }
            },
            "output": {
                "description": "The schema depends on the given query",
                "type": "object"
            },
            "safe": true,
            "idempotent": true,
            "forms": [
                {
                    "href": "/search/jsonpath?query={query}",
                    "htv:methodName": "GET",
                    "response": {
                        "description": "Success response",
                        "contentType": "application/json",
                        "htv:statusCodeValue": 200
                    },
                    "additionalResponses": [
                        {
                            "description": "JSONPath expression not provided or contains syntax errors",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        },
        "searchXPath": {
            "description": "XPath syntactic search.  This affordance is not normative and is provided for information only.",
            "uriVariables": {
                "query": {
                    "title": "A valid XPath expression",
                    "type": "string"
                }
            },
            "output": {
                "description": "The schema depends on the given query",
                "type": "object"
            },
            "safe": true,
            "idempotent": true,
            "forms": [
                {
                    "href": "/search/xpath?query={query}",
                    "htv:methodName": "GET",
                    "response": {
                        "description": "Success response",
                        "contentType": "application/json",
                        "htv:statusCodeValue": 200
                    },
                    "additionalResponses": [
                        {
                            "description": "XPath expression not provided or contains syntax errors",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        },
        "searchSPARQL": {
            "description": "SPARQL semantic search",
            "uriVariables": {
                "query": {
                    "title": "A valid SPARQL 1.1. query",
                    "type": "string"
                }
            },
            "output": {
                "description": "The schema depends on the given query",
                "type": "object"
            },
            "safe": true,
            "idempotent": true,
            "forms": [
                {
                    "href": "/search/sparql?query={query}",
                    "htv:methodName": "GET",
                    "response": {
                        "description": "Success response",
                        "contentType": "application/json",
                        "htv:statusCodeValue": 200
                    },
                    "additionalResponses": [
                        {
                            "description": "SPARQL query not provided or contains syntax errors",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                },
                {
                    "href": "/search/sparql",
                    "htv:methodName": "POST",
                    "response": {
                        "description": "Success response",
                        "contentType": "application/json",
                        "htv:statusCodeValue": 200
                    },
                    "additionalResponses": [
                        {
                            "description": "SPARQL query not provided or contains syntax errors",
                            "contentType": "application/problem+json",
                            "htv:statusCodeValue": 400
                        }
                    ]
                }
            ]
        }
    },
    "events": {
        "thingCreated": {
            "description": "Registration of Thing Descriptions inside the directory",
            "uriVariables": {
                "diff": {
                    "description": "Receive the full created TD as event data",
                    "type": "boolean"
                }
            },
            "data": {
                "title": "Partial/Full TD",
                "type": "object"
            },
            "forms": [
                {
                    "op": "subscribeevent",
                    "href": "/events/thing_created{?diff}",
                    "subprotocol": "sse",
                    "htv:headers": [
                        {
                            "description": "ID of the last event for reconnection",
                            "htv:fieldName": "Last-Event-ID"
                        }
                    ],
                    "response": {
                        "contentType": "text/event-stream"
                    }
                }
            ]
        },
        "thingUpdated": {
            "description": "Updates to Thing Descriptions within the directory",
            "uriVariables": {
                "diff": {
                    "description": "Include TD changes inside event data",
                    "type": "boolean"
                }
            },
            "data": {
                "title": "Partial TD",
                "type": "object",
                "contentMediaType": "application/merge-patch+json"
            },
            "forms": [
                {
                    "op": "subscribeevent",
                    "href": "/events/thing_updated{?diff}",
                    "subprotocol": "sse",
                    "htv:headers": [
                        {
                            "description": "ID of the last event for reconnection",
                            "htv:fieldName": "Last-Event-ID"
                        }
                    ],
                    "response": {
                        "contentType": "text/event-stream"
                    }
                }
            ]
        },
        "thingDeleted": {
            "description": "Deletion of Thing Descriptions from the directory",
            "data": {
                "title": "Partial TD",
                "type": "object"
            },
            "forms": [
                {
                    "op": "subscribeevent",
                    "href": "/events/thing_deleted",
                    "subprotocol": "sse",
                    "htv:headers": [
                        {
                            "description": "ID of the last event for reconnection",
                            "htv:fieldName": "Last-Event-ID"
                        }
                    ],
                    "response": {
                        "contentType": "text/event-stream"
                    }
                }
            ]
        }
    }
}

8. 安全考虑

安全是一个横切问题,需要在所有 WoT 构建块和 WoT 实现中加以考虑。 本章总结了一些通用问题和指南,以帮助维护具体 WoT discovery 实现的安全性。关于安全和隐私问题更详细、更完整的分析, 请参阅 WoT Security and Privacy Guidelines 规范 [WOT-SECURITY]。 WoT Thing 和 WoT TDD 也都是 Web 服务,应该使用 Web 服务的最佳实践来实现。 除了下面列出的具体安全考虑之外,还应评估 OWASP Top 10 [OWASP-Top-10] 等指南中讨论的安全 风险和缓解措施,并在适用时加以处理。

8.1 拒绝服务

Directory 服务的某些功能,特别是 搜索查询,可能需要大量资源才能执行, 这一点可被用于对 WoT Thing Description Directory 服务发起拒绝服务(DoS) 攻击。在这种攻击中,WoT Directory 会因攻击者的请求而过载, 无法服务其他请求。

缓解措施:
  • WoT Thing Description Directory 实现 应限制同一请求者在单位时间内的查询数量。
  • WoT Thing Description Directory 实现 应限制查询的复杂度(例如,查询表达式的总长度或其 深度)。
  • WoT Thing Description Directory 实现 应使用 watchdog timer 来中止耗时超过某个最大值 (由实现配置)的查询。

8.2 放大和分布式 拒绝服务

也可能使用 WoT Discovery 机制的元素, 对其他目标发起分布式拒绝服务(DDoS)攻击。 在这种攻击中,WoT Discovery 服务本身并不是目标。 相反,WoT Discovery 服务的某个方面会被利用, 以生成放大的网络流量,从而使实际目标——第三方——过载。 这种攻击有两个要求:第一,能够将流量重定向到 第三方;第二,存在一个可被利用来放大攻击者网络流量的 intermediary 服务。在某些协议中可以重定向网络流量, 例如通过修改 header 中的源信息来重定向未受保护的 CoAP。 放大可以通过利用三个乘数因子实现:请求与 响应 payload 大小的比率、在 CoAP 等协议中使用 "observe" (它可以为一个请求给出多个结果),以及使用 multicast (它可以允许多个 server 响应一个请求)。不支持认证的服务 是此类间接攻击的理想 intermediary。不幸的是,WoT Discovery 的 Introduction 机制本意就是提供开放访问机制以启动 discovery,因此可能会被用于这一目的。

缓解措施:
  • Introduction 机制的开放实现 SHOULD NOT 支持 observe 或类似的扩展结果 subprotocol。
  • Introduction 机制的开放实现 不应响应 multicast 请求,除非协议绝对要求这样做。 如果必须支持 multicast,在 CoAP 的情况下, [AMPLIFICATION-ATTACKS] 中的观察是相关的,因为在 discovery 期间可能响应 multicast 请求的 server 数量通常无法提前知道。
  • 将响应大小限制到最小。公共互联网 (受保护本地网络之外)上对 Introduction 的响应总大小 应小于请求总大小的 3 倍,并且这应包括任何错误响应。 这与 [RFC9000] (QUIC)和 HTTP/3 中的 DDOS 缓解措施一致。 这里的“总大小”包括协议本身所需的任何 header, 以及请求中用于允许更大响应的 padding。
  • Introduction 应对来自任何特定请求源的响应进行速率限制。
  • 防火墙后分段网络 (例如 LAN)上的 Introduction 机制 SHOULD NOT 响应 (看似)来自该 LAN 之外的请求。
特别值得关注的是能够返回多个结果的 Introduction 机制, 例如 CoRE-RD 和 DID。如果上述其他缓解措施不足, 也可能需要在此类 Introduction 机制上使用认证/授权。 推荐的替代方案是将此类 Introduction 中的多个结果转移到 WoT TDD 中, 然后可以通过适当的认证和授权措施保护该 TDD。 这样,开放的 Introduction 机制只需返回一个结果, 即 TDD 的 URL。对开放互联网可见的 Introduction 机制 应特别谨慎地实现上述缓解措施,并且可能完全避免使用 能返回多个 URL 的 Introduction 机制。

8.3 LAN 上的自发现

在 LAN 上,证书和浏览器可能无法为 HTTPS 正确设置 TLS,因为浏览器期望证书指向一个公开可见的 URL。 在 LAN 内部使用 HTTP 是常见实践,但与 自描述结合时,这意味着 WoT Thing 基本上会让 TD 对所有 能访问该私有 LAN 的人可见。即使使用了 HTTP 密码等安全机制,在没有传输安全的情况下,这些机制也并不有效 (它们可以很容易被流量分析器发现)。

缓解措施:

在 LAN 上如果可能, SHOULD 使用 PSK (pre-shared keys),即使用 [RFC4279] 中的某个 ciphersuite。 这确实要求为 Things 在一个共同的安全域中分配 PSK, 这通常通过遵循 onboarding 过程完成。不幸的是, 具体 onboarding 过程目前超出 WoT 规范的范围。

另一种选择是依赖本地网络安全 (即 WEP)。从安全或隐私角度看,这不是最佳解决方案, 但在某些上下文中可能可以接受。不过请注意, 所有能访问该网络的用户,反过来都可以通过 自描述访问所有 TD。如果 Things 无法通过 传输安全、认证和授权分别保护,则 SHOULD 设置一个单独的网络,即使用替代 SSID,并仅用于 IoT 设备。 使用 分段网络可以减少将此网络密码分发给需要访问连接到该网络的 IoT 设备集合的人员的需要。

另一种替代方案是使用位于云中的反向代理服务。 如果 IoT 设备可以访问云,则可以完成安全设置, 因为 proxy server 可以拥有公共 URL,初始连接可以使用 HTTPS, 然后通过 websocket 打开安全隧道。proxy 可以进一步重新暴露一个安全 端点,并可能添加认证。这种方法的缺点包括依赖外部云服务, 以及需要暴露外部访问点(这本身就是一种安全风险)。 第一个缺点可以通过在本地托管 proxy 服务并在本地 server 通过 ISP 连接时使用例如 dynamic DNS 暴露公共 URL 来解决。如果 Things 无法通过 传输安全、认证和授权分别保护,则可以通过能够提供合适访问控制的 proxy 使它们可供一般访问。

9. 隐私考虑

隐私是一个横切问题,需要在所有 WoT 构建块和 WoT 实现中加以考虑。 本章总结了一些通用问题和指南,以帮助维护具体 WoT discovery 实现的隐私性。关于安全和隐私问题更详细、更完整的分析, 请参阅 WoT Security and Privacy Guidelines 规范 [WOT-SECURITY]。

WoT discovery 架构通过采用两阶段方法,并允许在释放元数据之前强制执行 授权,从而避免依赖现有 discovery scheme 的隐私性。 然而,仍然存在若干隐私风险。下面列出了这些风险以及可能的 缓解措施。隐私风险的级别尤其取决于使用场景,以及是否存在这样一种风险: 与某个人有关的信息可能会以不符合该人隐私意愿的方式被分发。 对于隐私,我们区分以下几类广泛的使用场景:

机构
产生元数据的 Things 和该元数据的 Consumers 都由某个机构或机构代表拥有和控制。示例:工厂自动化中, 控制系统访问装配线状态以评估质量。
服务
产生元数据的 Things 由某个机构或机构代表拥有和控制, 而消费者是个人。示例:电动汽车驾驶员访问充电站的 TD, 以检查充电状态。
个人
产生元数据的 Things 和该元数据的 Consumers 都由同一个个人拥有和控制。示例:用于从家庭附属太阳能板为电动汽车充电的 smart home 控制系统,住宅和汽车都归同一个人所有。
个人点对点
产生元数据的 Things 和该元数据的 Consumers 由不同个人拥有和控制。示例:用于从家庭附属太阳能板为访客电动汽车充电的 smart home 控制系统。
机构点对点
产生元数据的 Things 和该元数据的 Consumers 由不同机构拥有和控制。示例:公用事业公司提供和管理交付给工厂的电力, 工厂提供一个接口供公用事业公司协商按需降低用电量。
Client
产生元数据的 Things 由个人拥有和控制,而消费者是某个机构或机构代表。 示例:个人电动汽车向公共充电站暴露接口, 以便充电站评估该车辆的充电状态。

事实上,所有这些场景都带有隐私风险。 即使在工厂自动化的情况下,也有可能捕获有关员工绩效的数据, 并且必须对这些数据进行适当管理。

在下文中,我们会频繁提到“tracking”。 该术语涵盖多种隐私风险,包括位置跟踪和行为画像。 一般而言,GDPR [GDPR-Defs] 第 4 条给出的“profiling”定义,应被视为等同于本文档中使用的 “tracking”。

在确立这些定义和类别之后,我们现在将讨论一些具体的隐私风险和潜在的 缓解措施。

9.1 位置跟踪和 画像

Discovery 服务可能允许在未经某人同意的情况下确定其大致位置。 这种风险出现在某些特定情形中,可以避免或缓解。 它也类似于 DHCP 和 DNS 等其他网络服务带来的风险。

要发生这种风险,首先必须存在一个可与某人的位置可靠关联的 IoT 设备,例如必要的医疗设备或车辆。请注意, 该风险只适用于个人使用场景,而不适用于机构使用场景。 其次,该设备必须被配置为自动注册到最近的 directory 服务。 在这种情况下,可以根据 directory 服务的网络范围推断设备位置, 并根据设备位置推断该人的位置。

这有几种变体:

位置跟踪并不是唯一的画像风险。一般而言, “profiling”包括任何用于评估有关某人信息的机制, 包括经济状况、健康、偏好、兴趣、可靠性和行为。 如果所描述的 Thing 可以与某个人相关联, TD 中的一些元数据可用于推断此类信息。 下面的一些缓解措施也适用于这种更一般的画像定义。

其中一些风险与类似服务共有。例如, DCHP 会自动响应本地网络上的 IP 地址请求, 设备通常会在此过程中提供一个标识符(MAC 地址), 并且 DHCP server 会维护注册表。理论上, 能访问例如咖啡馆中 DHCP server 的人,可以使用这些信息跟踪某人的手机, 并推断其位置。

缓解措施:
有几种选项可以缓解这些风险:
  • 为了避免位置跟踪和其他形式的 画像,与某个人相关联的 WoT Thing 可以 禁止向公共 directory 注册。仍然可以向个人 directory 注册,例如 home gateway, 但用户可以在其他位置禁用注册。其缺点是功能会丧失: 个人设备无法在公共位置被发现。可以通过具有互联网可访问性的 private discovery service 来解决这一点。例如,用户的 home gateway 可以提供互联网可访问服务, 但通过访问控制限制仅授权用户使用。
  • 为了避免位置跟踪和其他形式的 画像,与某个人相关联的 WoT Thing 在注册到公共 directory 时应使用匿名 TD。在某些情况下,可以使用 anonymous TD,并省略提交给 TDD 的 TD 中的显式 ID。 在这种情况下,TDD 将生成仅在该 TDD 中有效的本地 ID。 不过,这会使更新变得复杂,因为 client 需要记住 TDD 分配的本地 ID。 Anonymous TD 也不能防止通过其他方式进行跟踪, 例如 fingerprinting。
  • 为了避免位置跟踪和其他形式的 画像,与某个人相关联的 WoT Thing 可以 定期生成新 ID。使用固定 ID 会使设备极易被跟踪。 该问题也发生在带有 MAC 地址的 DHCP 中,并且有一种类似的 部分缓解措施:定期生成新的随机 ID。不过存在一些问题。 首先,TD 中的其他标识信息需要隐藏。 例如,如果 CSP 为 API 安全签发的 client ID 不能轻易更改, 则应从 TD 中省略。其次,如果设备 生成新 ID,用户可能仍需要知道当前 ID 才能通过 discovery 找到设备。 这可以通过使用当前时间函数的确定性密码学生成器来生成新 ID 实现。 但请注意,仅重新生成 ID 并不会使跟踪 不可能,因为 TD 可能被 fingerprint。此外, 更新 ID 可能被 directory 服务的所有者观察到, 后者可以跟踪并记录更新后的 ID。 即使 TD 被删除并重新插入,也可能推断出这种关联。 不过,这与 DHCP 和 MAC 地址轮换的情况完全平行。 一般而言,至少为每个服务或接收 TD 的人员生成新标识符, 可以更难将不同地点和时间的注册事件连接起来。 在配置发生重大变化时生成新标识符也是谨慎的做法, 例如从本地网络或 hub 注销并注册到新的网络或 hub (这通常表示所有权发生变化)。长寿命 IP 地址也存在相关问题, 可能需要定期更新以缓解跟踪。在 ipv6 [RFC8981] 上下文中对此进行了讨论。最后, 对于需要不可变标识符的设备(例如某些司法管辖区中的 医疗设备)存在一个问题。这在 [wot-thing-description11] 中有讨论,但总结来说,如果此类不可变标识符仅作为 受保护的 property 提供,例如通过需要认证的 affordance, 而不是在 TD 中提供,并且 TD 标识符本身(如果使用) 独立于不可变标识符、因此可以变更,则可以避免该问题。
  • 为了降低负面位置推断的风险, 应通过访问控制限制对 private directory 的访问 SHOULD 如果攻击者无法 访问该服务,就无法检索信息以推断位置。 提供给访客的访问权限(例如用于 Peer-to-Peer Personal 场景) 应有适当的时间限制。在其他情况下,使用较长的 time-to-live 值可能是合适的。此外,TD 只有在变化时才应在 directory 中更新。 例如,汽车的 TD 可能只在有提供新服务的新车载固件可用时更新, 并且 time-to-live 可以设置为一个月(覆盖大多数离开情况)。
  • 当显式位置信息可用时, 无论是存储在 TD 中还是在 property 中可用,都 SHOULD 额外谨慎,只与可信伙伴共享 TD 和/或设备访问权限,包括 directory。 如果必须与公共 directory 共享 TD,则可以剥离位置信息。

9.2 查询跟踪

Directory 服务可能记录并跟踪某个人的查询, 通过其提供的认证身份识别该个人。然后, 与某个人相关联的一组查询可用于对该个人进行画像, 特定查询也可能泄露有关个人的个人信息。

缓解措施:

访问公共 directory 时,像访问任何其他公共 Web 服务一样, 用户和实现应使用匿名身份提供者。特别是,OAuth2 可以 提供不识别具体个人的 token,它们只断言在其他地方证明的访问权限。

10. 性能考虑

本节为非规范性内容。

10.1 增量传输

TD 对象的 大小不受约束。单独或整体处理和传输它们可能会变得成本高昂。 对于受限设备而言,单个 TD 或 TD 列表可能过大,无法由其向消费者提供自身 TD、将其提交给 directory,或消费其他 TD。为了满足这些要求, server 应使用特定于协议的机制支持 payload 的增量传输:

大多数 HTTP server 和 client 会自动处理以 chunk 传输的数据。 内存受限的 client 应考虑增量消费接收到的数据, 而不是尝试将整个对象加载到内存中进行反序列化。

11. IANA 考虑

11.1 Well-Known URI 注册

将请求 IANA 将以下值分配到 [RFC8615] 中定义的 Well-Known URI。

11.2 服务名称注册

将请求 IANA 将以下值分配到 [RFC6335] 中定义的 Service Name and Transport Protocol Port Number Registry。

11.3 CoRE 资源类型 注册

将请求 IANA 将以下值分配到 [RFC6690] 中定义的 Constrained Restful Environments (CoRE) Parameters registry 的 Resource Type(rt=) Link Target Attribute Values 子注册表。

描述 引用
wot.thing Thing 的 Thing Description 6.4 CoRE Link Format 和 CoRE Resource Directory
wot.directory Thing Description Directory 的 Thing Description 6.4 CoRE Link Format 和 CoRE Resource Directory

12. 其他名称注册 考虑

12.1 DID 服务名称 注册

将为 DID Documents 中使用的 Service Type 分配以下值。

描述 引用
WotThing Thing 的 Thing Description 6.5 DID Documents
WotDirectory Thing Description Directory 的 Thing Description 6.5 DID Documents

A. WoT Discovery TD 扩展的 JSON Schema

以下 JSON Schema 指定了 Enriched TD 中使用的扩展。它可供 TDD7.3.2.1.6 验证中的规定验证 TD
{
  "title": "WoT Discovery TD-extensions Schema - 21 May 2021",
  "description": "JSON Schema for validating TD instances with WoT Discovery extensions",
  "$schema ": "https://json-schema.org/draft/2019-09/schema#",
  "type": "object",
  "properties": {
    "registration": {
      "type": "object",
      "properties": {
        "created": {
          "type": "string",
          "format": "date-time"
        },
        "expires": {
          "type": "string",
          "format": "date-time"
        },
        "retrieved": {
          "type": "string",
          "format": "date-time"
        },
        "modified": {
          "type": "string",
          "format": "date-time"
        },
        "ttl": {
          "type": "number"
        }
      }
    }
  }
}

B. 近期规范变更

B.1 相对于 2023 年 7 月 11 日提案 推荐标准的变更

B.2 相对于 2023 年 1 月 19 日候选 推荐标准的变更

B.3 相对于 2022 年 8 月 10 日工作 草案的变更

B.4 相对于 2021 年 6 月 2 日工作 草案的变更

B.5 相对于 2020 年 11 月 24 日首份公开 工作草案的变更

C. 致谢

特别感谢 Arne Broering、Soumya Kanti Datta 和 Christian Bonnet 对 IoT 中现有 discovery 机制所做的调研工作。特别感谢 Philipp Blum、Victor Charpeney、Ben Francis、Christian Glomb、Daniel Peintner、 Christine Perey、Jan Romann 和 Elodie Thieblin 对本文档的 贡献。

非常感谢 W3C staff,尤其是 Kazuyuki Ashimura 和 Dominique Hazael-Massieux,以及 W3C Web of Things Interest Group(WoT IG)和 Working Group(WoT WG)的所有其他活跃 参与者,他们的支持、技术输入和建议促成了 本文档的改进。

D. 参考文献

D.1 规范性引用

[AMPLIFICATION-ATTACKS]
Amplification Attacks Using the Constrained Application Protocol (CoAP). John Preuß Mattsson; Göran Selander; Christian Amsüss. IETF. 草案。 URL: https://datatracker.ietf.org/doc/html/draft-irtf-t2trg-amplification-attacks
[DID-CORE]
Decentralized Identifiers (DIDs) v1.0. Manu Sporny; Amy Guy; Markus Sabadello; Drummond Reed. W3C. 2022 年 7 月 19 日。W3C 推荐标准。URL:https://www.w3.org/TR/did-core/
[did-spec-registries]
DID Specification Registries. Orie Steele; Manu Sporny. W3C. 2023 年 9 月 11 日。W3C 工作组说明。 URL:https://www.w3.org/TR/did-spec-registries/
[Discovery-Categorization-IoT]
A Categorization of Discovery Technologies for the Internet of Things. Arne Broering; Soumya Kanti Datta; Christian Bonnet. Association of Computing Machinery (ACM): IoT'16 Proceedings. 2016 年 11 月 7-9 日。URL: https://dl.acm.org/doi/10.1145/2991561.2991570
[GDPR-Defs]
General Data Protection Regulation (GDPR) Article 4 - Definitions. European Union (EU) and the European Economic Area (EEA). URL:https://gdpr-info.eu/art-4-gdpr/
[HTML]
HTML Standard. Anne van Kesteren; Domenic Denicola; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. 现行标准。URL:https://html.spec.whatwg.org/multipage/
[JSON-LD11]
JSON-LD 1.1. Gregg Kellogg; Pierre-Antoine Champin; Dave Longley. W3C. 2020 年 7 月 16 日。W3C 推荐标准。URL: https://www.w3.org/TR/json-ld11/
[OWASP-Top-10]
OWASP Top Ten. OWASP. URL:https://owasp.org/www-project-top-ten/
[RFC1738]
Uniform Resource Locators (URL). T. Berners-Lee; L. Masinter; M. McCahill. IETF. 1994 年 12 月。提案 标准。URL:https://www.rfc-editor.org/rfc/rfc1738
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. 1997 年 3 月。最佳 当前实践。URL:https://www.rfc-editor.org/rfc/rfc2119
[RFC3339]
Date and Time on the Internet: Timestamps. G. Klyne; C. Newman. IETF. 2002 年 7 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc3339
[RFC4122]
A Universally Unique IDentifier (UUID) URN Namespace. P. Leach; M. Mealling; R. Salz. IETF. 2005 年 7 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc4122
[RFC4279]
Pre-Shared Key Ciphersuites for Transport Layer Security (TLS). P. Eronen, Ed.; H. Tschofenig, Ed.. IETF. 2005 年 12 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc4279
[RFC6335]
Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registry. M. Cotton; L. Eggert; J. Touch; M. Westerlund; S. Cheshire. IETF. 2011 年 8 月。 最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc6335
[RFC6690]
Constrained RESTful Environments (CoRE) Link Format. Z. Shelby. IETF. 2012 年 8 月。提案标准。URL: https://www.rfc-editor.org/rfc/rfc6690
[RFC6762]
Multicast DNS. S. Cheshire; M. Krochmal. IETF. 2013 年 2 月。 提案标准。URL:https://www.rfc-editor.org/rfc/rfc6762
[RFC6763]
DNS-Based Service Discovery. S. Cheshire; M. Krochmal. IETF. 2013 年 2 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc6763
[RFC7231]
Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content. R. Fielding, Ed.; J. Reschke, Ed.. IETF. 2014 年 6 月。提案标准。URL:https://httpwg.org/specs/rfc7231.html
[RFC7396]
JSON Merge Patch. P. Hoffman; J. Snell. IETF. 2014 年 10 月。 提案标准。URL:https://www.rfc-editor.org/rfc/rfc7396
[RFC7595]
Guidelines and Registration Procedures for URI Schemes. D. Thaler, Ed.; T. Hansen; T. Hardie. IETF. 2015 年 6 月。 最佳当前实践。URL:https://www.rfc-editor.org/rfc/rfc7595
[RFC7807]
Problem Details for HTTP APIs. M. Nottingham; E. Wilde. IETF. 2016 年 3 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc7807
[RFC7959]
Block-Wise Transfers in the Constrained Application Protocol (CoAP). C. Bormann; Z. Shelby, Ed.. IETF. 2016 年 8 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc7959
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. 2017 年 5 月。最佳当前 实践。URL:https://www.rfc-editor.org/rfc/rfc8174
[RFC8288]
Web Linking. M. Nottingham. IETF. 2017 年 10 月。 提案标准。URL:https://httpwg.org/specs/rfc8288.html
[RFC8615]
Well-Known Uniform Resource Identifiers (URIs). M. Nottingham. IETF. 2019 年 5 月。提案标准。URL: https://www.rfc-editor.org/rfc/rfc8615
[RFC8981]
Temporary Address Extensions for Stateless Address Autoconfiguration in IPv6. F. Gont; S. Krishnan; T. Narten; R. Draves. IETF. 2021 年 2 月。 提案标准。URL:https://www.rfc-editor.org/rfc/rfc8981
[RFC9000]
QUIC: A UDP-Based Multiplexed and Secure Transport. J. Iyengar, Ed.; M. Thomson, Ed.. IETF. 2021 年 5 月。提案 标准。URL:https://www.rfc-editor.org/rfc/rfc9000
[RFC9176]
Constrained RESTful Environments (CoRE) Resource Directory. C. Amsüss, Ed.; Z. Shelby; M. Koster; C. Bormann; P. van der Stok. IETF. 2022 年 4 月。 提案标准。URL:https://www.rfc-editor.org/rfc/rfc9176
[SPARQL11-OVERVIEW]
SPARQL 1.1 Overview. The W3C SPARQL Working Group. W3C. 2013 年 3 月 21 日。W3C 推荐标准。URL:https://www.w3.org/TR/sparql11-overview/
[wot-architecture11]
Web of Things (WoT) Architecture 1.1. Michael Lagally; Ryuichi Matsukura; Kunihiko Toumura; Michael McCool. W3C. 2023 年 12 月 5 日。W3C 推荐标准。 URL:https://www.w3.org/TR/wot-architecture11/
[wot-thing-description11]
Web of Things (WoT) Thing Description 1.1. Sebastian Käbisch; Michael McCool; Ege Korkan. W3C. 2023 年 12 月 5 日。 W3C 推荐标准。URL:https://www.w3.org/TR/wot-thing-description11/
[wot-usecases]
Web of Things (WoT): Use Cases. Michael Lagally; Michael McCool; Ryuichi Matsukura; Tomoaki Mizushima. W3C. 2020 年 10 月 15 日。编辑草案。URL:https://w3c.github.io/wot-usecases/
[xpath-31]
XML Path Language (XPath) 3.1. Jonathan Robie; Michael Dyck; Josh Spiegel. W3C. 2017 年 3 月 21 日。W3C 推荐标准。URL:https://www.w3.org/TR/xpath-31/

D.2 资料性引用

[JSONPATH]
JSONPath: Query expressions for JSON. Stefan Gössner; Glyn Normington; Carsten Bormann. IETF. 草案。URL: https://datatracker.ietf.org/doc/html/draft-ietf-jsonpath-base
[LDP-Paging]
Linked Data Platform Paging 1.0. Steve Speicher; John Arwe; Ashok Malhotra. W3C. 2015 年 6 月 30 日。W3C 工作组 说明。URL:https://www.w3.org/TR/ldp-paging/
[REST-IOT]
RESTful Design for Internet of Things Systems. Ari Keranen; Matthias Kovatsch; Klaus Hartke. IETF. 2020 年 5 月 11 日。URL: https://datatracker.ietf.org/doc/html/draft-irtf-t2trg-rest-iot-06
[RFC7230]
Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing. R. Fielding, Ed.; J. Reschke, Ed.. IETF. 2014 年 6 月。提案标准。URL:https://httpwg.org/specs/rfc7230.html
[RFC7540]
Hypertext Transfer Protocol Version 2 (HTTP/2). M. Belshe; R. Peon; M. Thomson, Ed.. IETF. 2015 年 5 月。 提案标准。URL:https://httpwg.org/specs/rfc7540.html
[RFC8323]
CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets. C. Bormann; S. Lemay; H. Tschofenig; K. Hartke; B. Silverajan; B. Raymor, Ed.. IETF. 2018 年 2 月。提案标准。URL:https://www.rfc-editor.org/rfc/rfc8323
[WOT-SECURITY]
Web of Things (WoT) Security and Privacy Guidelines. Elena Reshetova; Michael McCool. W3C. 2019 年 11 月 6 日。W3C 工作组说明。URL: https://www.w3.org/TR/wot-security/