提供商广告信号

广告:可发现时

当提供方设备可被 BR/EDR 发现(即处于配对模式)时,应通过 BLE 播发快速配对型号 ID 数据,并且 BLE 地址不得轮换。

广播间隔:可被发现时

广告之间的间隔时间不应超过 100 毫秒(10 赫兹)。快速速率可让搜索者快速找到提供者,即使在低功耗模式下扫描也是如此。

广告载荷:快速配对模型 ID 数据

通告应包含服务数据数据类型,同上,第 1.11 条。该 UUID 应为快速配对服务 UUID 0xFE2C。服务数据应包含以下内容:

八位字节 数据类型 说明 值
0-2 uint24 24 位模型 ID 各不相同

LE 音频共享广告载荷:快速配对模型 ID 数据和 FP 功能映射

服务数据应包含以下内容:

八位字节 数据类型 说明 值
0 uint8 版本和标志
0bVVVVFFFF
  • V = 版本
  • F = 标志
0x00
1 uint8 字段长度和类型
0bLLLLTTTT
  • L = 模型 ID 的长度(以字节为单位)
  • T = 类型
0bLLLL0111
  • length = 0bLLLL = 可变
  • type = 0b0111,模型 ID
2 - s 模型 ID 各不相同
s + 1 uint8 字段长度和类型
0bLLLLTTTT
  • L = 长度(以字节为单位)
  • T = 类型
0bLLLL1000
s + 2 uint8 FP 功能映射 各不相同
FP 功能映射

FP 功能映射是一个位图,用于表示提供程序支持哪些 FP 功能。位图的第一个字节定义如下:

位图 说明
0bRRRRRRSO R - 保留位,目前会被搜索器忽略,应设置为 0。
S - FP LE 音频共享支持(如果已启用,则设置为 1,否则设置为 0)。
O - 开箱 (OOB) 位,如果设备是全新出厂的,则设置为 1。

如果提供方支持 FP LE 音频共享规范(即支持临时配对、LE 音频共享广告载荷和可穿戴设备控制版本 2),则将 FP LE 音频共享支持位设置为 1;否则,将其设置为 0。

如果提供方尚未与任何 FP 寻求方配对,并且是全新出厂的(意味着提供方上没有 FP 账号密钥),请将 OOB 位设置为 1;否则,请将其设置为 0。

广告:不可被发现时

当提供方设备不可被发现(即未处于配对模式)时,应按照以下准则播报快速配对账号数据。

通过宣传账号数据,附近的搜索者可以识别提供方是否属于其账号,并启动配对,而无需先强制提供方返回配对模式,这通常是用户投诉的原因。搜索器将提供机会,让用户能够在以下情况下忽略此广播:用户不等待与提供方配对,或者广播不相关(例如,用户已配对)。搜索者还会自动过滤掉明显不好的广播,例如当账号数据配置错误时。

广告间隔:不可发现时

广告之间的间隔应不超过 250 毫秒(4 赫兹)。

通告载荷:快速配对账号数据

通告应包含服务数据数据类型,同上,第 1.11 条。该 UUID 应为快速配对服务 UUID 0xFE2C。服务数据应包含以下内容:

八位字节 数据类型 说明 值
0 uint8 版本和标志
0bVVVVFFFF
  • V = 版本
  • F = 标志
0x00
(预留以供日后使用)
1 - 各不相同 账号密钥数据 不定

账号密钥数据包含:

八位字节 数据类型 说明 值
0 uint8 字段长度和类型
0bLLLLTTTT
  • L = 账号密钥过滤器的长度(以字节为单位)
  • T = 类型
0bLLLL0000
  • length = 0bLLLL = 可变
  • type = 0b0000(显示界面指示)或 0b0010(隐藏界面指示),账号密钥过滤条件
1 - s 账号密钥过滤条件 各不相同
s + 1 uint8 字段长度和类型
0bLLLLTTTT
  • L = 长度(以字节为单位)
  • T = 类型
0b00100001
  • length = 0b0010 = 2
  • type = 0b0001,Salt
s + 2 - s + 3 uint16 Salt 各不相同

账号密钥过滤条件

广告的账号密钥过滤条件可让搜索者在进一步互动之前,快速检查提供者是否可能拥有某个账号密钥(误报概率较低,平均远低于 0.5%)。当 Seeker 看到广播的过滤器类型为 0(即显示界面指示)时,可能会自动连接并尝试启动该程序,以进一步降低误报率。该过滤器可能包含 Seeker 的某个账号密钥。在某些情况下,提供方可能希望在未准备好配对时被搜寻方识别。例如,当耳机放回耳机盒时,我们希望停止显示后续的配对通知,因为耳机可能会拒绝该配对。

账号密钥过滤条件是一个可变长度的 Bloom 过滤器,按如下方式构建:

  1. 假设持久性 Account Key list 中的账号密钥数量为 n(n >= 1)。
  2. 假设过滤器的字节大小 s 为 (1.2*n + 3) 的截断值。例如,如果持久保留 1 个密钥,则 s = 4 字节。
    uint8_t s = (((uint8_t)(( float )1.2 * n)) + 3);
  3. 将过滤条件 F 初始化为一个包含 s 个字节的数组,每个字节都设置为 0。
    uint8_t F[s] = {0};
  4. 对于持久性 Account Key list 中的每个账号密钥 K:
    a. 令 V 为 concat(K, Salt)。

    // In the sample code, the size of salt is 2 bytes.
    #define SALT_SIZE 2
    
    uint8_t V[FASTPAIR_ACCOUNT_KEY_SIZE + SALT_SIZE];
    for (uint8_t keyIndex = 0; keyIndex < n; keyIndex++)
      {
         // concat (K, Salt)
          fastpair_get_account_key_by_index(keyIndex, V);
    
          uint8_t randomSalt = (uint8_t)rand();
          V[FASTPAIR_ACCOUNT_KEY_SIZE] = randomSalt;
          ... }
    

    b. 使用 SHA256 对 V 进行哈希处理,获得一个 32 字节的值 H = {H0, …, H31}。

    uint8_t H[32] = {0};
    SHA256_hash_function(V, H);
    

    c. 将 H 分为 8 个 4 字节的大端序无符号整数,X = {X0, …, X7},其中 X0 = 0xH0H1H2H3。

         uint32_t X[8];
         for (index = 0; index < 8; index++)
         {
            X[index] = (((uint32_t)(H[index * 4])) << 24) |
                        (((uint32_t)(H[index * 4 + 1])) << 16) |
                        (((uint32_t)(H[index * 4 + 2])) << 8) |
                        (((uint32_t)(H[index * 4 + 3])) << 0);
         }
    

    d. 对于每个 Xi:
    i. 设 M 为 Xi 对过滤器的位数(即 s * 8)取模的结果。
    ii. 获取 F 中索引为 (M / 8)(向下舍入)的字节。
    iii. 在相应字节内,将索引为 (M % 8) 的位设置为 1。
    iv. 换句话说:

        // M = Xi % (s * 8)
        // F[M/8] = F[M/8] | (1 << (M % 8))
        for (index = 0; index < 8; index++)
        {
            uint32_t M    = X[index] % (s * 8);
            F[M / 8] = F[M / 8] | (1 << (M % 8));
        }
    

在广告数据中,将过滤条件 F 作为“账号键过滤条件”字段包含在内。 请注意,此值没有“字节序”,因为没有更重要或更不重要的字节,因此请勿更改字节顺序。

Salt field

盐值是在构建 Bloom 过滤器时附加到账号密钥的随机值。每次更新提供程序的 RPA 时,都应重新生成此 salt,以避免在地址轮换时进行跟踪。

使用 salt 生成账号密钥过滤条件:

  1. 生成一个随机的 2 字节 S。请注意,此值没有“字节序”,因为没有更重要或不太重要的字节 - 请勿更改字节顺序。
  2. 使用 2 字节的 S 作为 Salt。
  3. 在宣传的快速配对账号数据中,将生成的过滤条件包含在“账号密钥过滤条件”字段中,并将 S 包含在“盐”字段中。