5. 规范性描述
本节以规范性方式描述共享外观 (shared appearances) 功能扩展. 本文档通篇使用以下定义:
外观编号 (Appearance number): 外观编号是与某个 AOR 的一个或多个对话 (dialog) 关联的正整数. 外观编号由外观代理 (Appearance Agent) 管理, 并由支持本规范的 UA 向用户显示和呈现. 当分配或请求外观编号时, 通常分配的编号是当前尚未作为该 AOR 某个对话的外观编号使用的最小正整数. 本规范不定义外观编号的上限; 但是, 使用常见整数表示方式不易表示的外观编号很可能导致失败.
占用 (Seizing): 可以通过占用某个外观, 在发起呼叫前预留该外观. 外观可以通过在实际发起对话 (即发送 INVITE) 之前传送一个人为的 "trying" 状态来占用, 使其看起来好像已经在发起对话.
选择 (Selecting, or Not-Seizing): 如果在发起对话之前没有传送这种人为的 "trying" 状态, 即状态是在实际发起对话时才传送的, 则该外观只是被选择 (即未被占用). 外观编号在 INVITE 发送之后获知.
5.1. 元素
实现此功能的完整系统由以下部分组成:
-
支持 SIP 对话事件包以及共享外观对话包扩展和行为的发布, 订阅和通知的 UA.
-
一个外观代理, 它由对话事件包的状态代理 (State Agent) 组成, 该状态代理实现事件状态组合器 (Event State Compositor, ESC) 以及共享外观对话包扩展和行为. 外观代理还具有分配和释放外观编号以及解决外观编号争用的逻辑.
-
一个能够与状态代理通信的分叉代理服务器 (forking proxy server).
-
一个支持注册事件包 (registration event package) 的注册服务器 (registrar).
在定义对话包扩展之后, 以下各节以规范性方式描述这些元素的行为.
5.2. 共享外观对话包扩展
本规范定义四个新元素, 作为 SIP 对话事件包 [RFC4235] 的扩展. 其模式在 Section 6 中定义. 这些元素是 <appearance>, <exclusive>, <joined-dialog> 和 <replaced-dialog>, 它们都是 <dialog> 元素的子元素.
5.2.1. <appearance> 元素
<appearance> 元素是 <dialog> 元素的子元素, 用于传递父 <dialog> 元素所描述对话的外观编号。当 UA 在发往外观代理的、父 <dialog> 状态属性为 "trying" 的 PUBLISH 中发送该元素时, UA 是在请求将给定的外观编号分配给具有给定对话标识符的当前或未来对话。当外观代理在 NOTIFY 中发送 <appearance> 元素时, 它表示该外观编号已分配给指定对话。
请注意, <dialog-info> 元素是从 UA (由 "entity" 属性命名) 的角度描述所含对话的, 无论包含该元素的请求是由 UA 还是由外观代理发送都是如此。特别地, 如果该请求是 UA 在所述对话内发送的, 则 To 头字段 URI 将与 <remote> <identity> 值匹配, 且 to-tag 参数将与 remote-tag 属性匹配。类似地, From 头字段 URI 将与 <local> <identity> 值匹配, 且 from-tag 参数将与 local-tag 属性匹配。
5.2.2. <exclusive> 元素
<exclusive> 元素是 <dialog> 元素的子元素, 是一个布尔值; 当其为 true 时, 表示 UA 不愿意接受以 Join 或 Replaces 头字段为目标的、指向作为 <exclusive> 元素父元素的 <dialog> 元素所描述对话的 INVITE。例如, 某些共享外观系统仅允许在呼叫处于保持状态时进行呼叫接听。在这种情况下, 当呼叫被保持时应将 <exclusive> 元素设为 "false", 当呼叫未被保持时应设为 "true", 而不是让保持状态隐含 "exclusive" 取值。
重要的是要注意, 该元素只是一个提示。为了防止另一个 UA 接管或加入呼叫, UA 除了设置 <exclusive> 标签之外, 还可以不向外观代理报告完整的对话信息。不提供完整的对话信息 (Call-ID、remote-tag 和 local-tag) 可以阻止另一个 UA 构造 Join 或 Replaces 头字段。尽管 UA 可以将 <exclusive> 设为 "true", 该 UA 仍必须准备好拒绝与该对话相关的 INVITE Join。如果这些对话标识符已经与外观代理共享, 该 UA 可以发送 INVITE Replaces 来更改它们, 然后不向外观代理报告新的标识符。
如果代理知道哪些对话被标记为独占, 代理 MAY 通过以 403 (Forbidden) 响应拒绝包含这些对话标识符的 INVITE Join 和 INVITE Replaces 请求来强制执行这种独占性。
请注意, 独占性与外观编号的选择或占用无关 -- 相反, 它关乎可以在对话上执行的呼叫控制操作。
如果 <exclusive> 元素不存在, 则假定为 false。
5.2.3. <joined-dialog> 元素
<joined-dialog> 元素是 <dialog> 元素的子元素, 用于传递与该对话已加入 (混音或桥接) 的任何其他对话的对话标识符。只有作为这些混音对话共同端点 (因而控制混音操作) 的 UA 才应在发往外观代理的发布中包含该元素。请注意, 即使未使用 Join 头字段来加入对话, 也仍应使用该元素。例如, UA 上的两个独立对话可以在没有任何 SIP 呼叫控制操作的情况下被加入。已加入的对话将共享相同的外观编号。
如果 <joined-dialog> 元素不存在, 则假定该对话未加入也不会加入任何其他对话。
5.2.4. <replaced-dialog> 元素
<replaced-dialog> 元素是 <dialog> 元素的子元素, 用于传递将被该对话替换或已被该对话替换的任何其他对话的对话标识符。例如, 组内某 UA 通过发送带 Replaces 的 INVITE 接听另一个 UA 上的呼叫时, 会为执行替换的对话包含该元素。被替换的对话将共享相同的外观编号。
如果 <replaced-dialog> 元素不存在, 则假定该对话未替换也不会替换任何其他对话。
5.3. 共享外观用户代理
支持共享外观 (shared appearances) 功能的 UA 使用带有共享外观扩展的对话状态包 [RFC4235], 以及 Section 13 中定义的 'shared' Event 头字段参数.
UA 使用 Section 5.2 中的对话包扩展, 并配合 SUBSCRIBE [RFC6665], NOTIFY [RFC6665] 和 PUBLISH [RFC3903]. 对话事件包的 SUBSCRIBE, NOTIFY 和 PUBLISH 请求按本规范要求包含 'shared' Event 头字段参数.
'shared' Event 头字段参数的存在会告知外观代理 (Appearance Agent), 该 UA 支持本规范.
初始化时, UA MUST 订阅 AOR 的对话事件包, 并按照 SIP 事件框架 (SIP Events Framework) [RFC6665] 刷新订阅. 如果 SUBSCRIBE 请求失败, 则可能不存在外观代理, 且此功能未在该 AOR 上激活. UA MAY 定期重试订阅以检查条件是否已改变, 重试间隔不得短于四小时.
选择四小时是为了将每个 UA 每天的订阅测试限制为六次. 增加此间隔会减少这种失败流量, 但也会使新激活的外观代理需要更长时间才能被发现.
UA 还可以利用 NOTIFY 中是否存在 'shared' Event 头字段参数, 来发现该 AOR 是否存在外观代理.
实现共享外观功能, 呼叫代答, 加入和桥接的 UA MUST 支持发送带有 Replaces [RFC3891] 或 Join [RFC3911] 的 INVITE. 用户代理客户端 (User Agent Client, UAC) 需要在 Replaces 或 Join 头中包含 to-tag 和 from-tag 信息, 以便用户代理服务器 (User Agent Server, UAS) 按照 RFC 3891 和 RFC 3911 中的规则匹配正确的对话.
所有实现共享外观功能并支持 INVITE 的 UA MUST 支持接收带有 Replaces [RFC3891] 或 Join [RFC3911] 头字段的 INVITE.
在发布或通知对话包信息时, UA 包含发布时可用的最大对话标识集合, 但如果 UA 希望防止其他 UA 加入或代答某个呼叫, 则可以省略信息. 对话标识包括本地和远程目标 URI, call-id, to-tag 和 from-tag. 虽然这些对话标识信息在 [RFC4235] 中是可选的, 但在共享外观功能中是必要的, 因为它们允许执行呼叫控制操作. 将呼叫置于保持状态时, 使用 "+sip.rendering=no" 特性标签在对话包通知中指示该状态. 如果改用完整的 SDP 会话描述, 会迫使端点执行大量额外解析, 不必要地使代码复杂化并引入错误.
准确呈现组内其他 UA 的 idle/active/alerting/hold 状态是共享外观功能的重要组成部分.
不需要占用特定外观编号 (或并不关心外观编号) 的 UA 只需像通常一样发送 INVITE 来发起出站呼叫.
如果该呼叫是紧急呼叫, UA MUST NOT 在发送 INVITE 前等待占用得到确认. 相反, 紧急呼叫 MUST 在不等待 PUBLISH 事务的情况下继续进行.
如果 UA 需要特定外观编号, 该 UA MUST 发送对话包 PUBLISH 请求, 并在发送 INVITE 之前等待 2xx 响应. 以下情况要求这样做:
-
用户为外呼占用特定外观编号时 (例如, 如果 UA 的用户界面使用这种隐喻, 则占用外观并进入 "off-hook" 状态).
-
用户请求某个外呼不使用外观编号时 (即在咨询呼叫期间, 在例如保持音乐 [RFC7088] 这样的 'service media' 呼叫期间, 或者针对不被视为共享外观组一部分的呼叫).
-
用户已选择加入 (或桥接) 某个现有呼叫时.
-
用户已选择替换 (或接管) 某个现有呼叫时.
注意, 当 UA 在建立对话之前占用外观时 (上表中的第 1 项和第 2 项), 并非所有对话信息都可用. 特别是, 当 UA 在知道目的地 URI 之前发布占用外观的尝试时, 可能只有极少的对话信息可用, 甚至没有任何对话信息可用. 例如, 在某些情况下, 只知道该呼叫的本地目标 URI, 而不知道任何对话信息. 如果初始 PUBLISH 中没有 From tag 和 Call-ID, 则 MUST 在这些信息可用后立即发送新的 PUBLISH.
第一次发布将使外观代理为该 UA 预留外观编号. 如果该发布不包含任何对话标识符 (例如 Call-ID 或 local-tag), 则外观代理无法将该外观编号分配给该 UA 的特定对话, 直到第二次发布携带某些对话标识符为止.
在早期对话状态期间, 此发布状态按 [RFC3903] 中的描述进行刷新, 否则外观代理可能重新分配该外观编号. 一旦对话转换到 confirmed 状态, 就不再需要刷新发布.
本规范假定外观代理除 UA 发布之外还有其他手段来获知 UA 对话状态. 在本规范中, PUBLISH 用于指示期望和意图中的外观编号操作. 一旦对话从 early 转换到 confirmed, 这个角色即告结束; 因此, 不需要刷新发布.
外观编号是与某个 AOR 相关的活动对话和待处理对话的简写标签. 使用此扩展构建的许多功能和服务都依赖于向人类用户正确呈现这些信息. 此外, 该功能的组属性意味着不同厂商和不同型号之间的呈现方式必须相似. 如果做不到这一点, 将极大降低这些协议扩展的价值和实用性. 在为此功能正确设计的用户界面中, 每个活动和待处理对话的外观编号会显式地 (即按外观编号) 或隐式地 (使用一种让用户清楚了解编号和顺序的用户界面隐喻) 呈现给用户. 每个对话的远端身份 (例如远程参与方身份) 不能有效替代外观编号. 每个外观的状态也要被呈现 (idle, active, busy, joined 等). UA 可以通过存在一个或多个包含其他 SIP 对话标识符的 <joined-dialog> 元素, 判断一组对话已加入 (桥接或混合) 在一起. 对话的外观编号可以从外观代理发送的, 包含 <appearance> 元素的对话包通知中获知, 也可以从传入 INVITE 中的 'appearance' Alert-Info 参数获知. 如果二者冲突, 以对话包通知为准.
用户可能选择了一个外观编号, 随后又放弃发起呼叫 (回到 on-hook). 在这种情况下, UA 按 [RFC3903] 中的描述, 通过使用 PUBLISH 移除事件状态来释放该外观编号. 如果未这样做, 外观代理将需要执行不必要的操作, 并且会占用本可由共享外观组中其他 UA 使用的外观编号.
UA SHOULD 仅在其可能应答传入呼叫时才向 AOR 注册. 如果 UA 主要用于监视共享外观组呼叫的状态以及代答或加入呼叫, 则该 UA SHOULD 只订阅 AOR, 而不向 AOR 注册. 如果监视 UA 注册而不只是订阅, 将产生大量不必要的网络流量.
所有已订阅的 UA 都会收到针对传入 INVITE 的 trying 状态对话包 NOTIFY.
UA MUST NOT 在 INVITE 或其他请求的 Alert-Info 头字段中插入 'appearance' 参数.
此操作仅由外观代理负责.
5.3.1. 外观编号与呼叫上下文
在某些情况下, UA 上的两个独立对话 (dialog) 并未混合, 但共享同一个 "上下文" (context). 也就是说, 它们彼此相关, 不应像组内任意另外两个对话那样处理. 一个示例是 "咨询呼叫" (consultation call): 用户先将现有对话置于保持状态, 然后呼叫另一个用户, 最后再切回原始对话. 下面描述的另一种情况发生在转接操作期间: 在一个短暂阶段内, UA 同时参与同另外两个 UA 的对话, 但这些对话彼此相关, 不应视为相互独立的对话. 处理这类情况的最佳方式是: 当新创建的对话与某个现有对话共享上下文时, 不要为该新对话分配外观编号 (appearance number). 但是, 如果先前存在的对话终止, 则应将其外观编号重新分配给新创建的对话.
如果某个 UA 要发起呼叫但尚未分配外观编号, 它会在发送 INVITE 之前发送 PUBLISH. 该 PUBLISH 不包含 'appearance' 元素, 但包含 'shared' Event 头字段参数. 如果外观代理 (Appearance Agent) 的策略不允许没有已分配外观编号的呼叫, 外观代理会发送 400 (Bad Request) 响应, 而 UA 将重新发布: 要么选择/占用一个外观编号, 要么不经发布直接发送 INVITE, 在这种情况下外观代理会为其分配一个编号.
请注意, 如果外观代理拒绝没有外观编号的呼叫, 咨询呼叫, 转接和保持音乐等某些操作可能会受到负面影响.
5.3.2. 外观编号与呼叫控制
当生成 INVITE 以尝试桥接或接管呼叫时 (即, 其中包含 Join 或 Replaces, 且该头字段带有共享外观组 (shared appearance group) 中另一个对话的对话标识符), UA MUST 首先向外观代理发送 PUBLISH. 此 PUBLISH 将包含:
-
在
<appearance>元素中包含被加入或被替换呼叫的外观编号 -
如果正在加入该对话, 则在
<joined-dialog>元素中包含来自 Join 头字段的对话信息 -
如果正在替换该对话, 则在
<replaced-dialog>元素中包含来自 Replaces 头字段的对话信息
请注意, 向外观代理提供这些信息是为了让它能够执行正确的外观分配行为. 如果未先发布就发送带有 Join 或 Replaces 的 INVITE, 外观代理可能会为该 INVITE 分配新的外观编号, 这将是错误的. 对于 Join, 发布中包含 <joined-dialog> 元素, 以防止外观代理因外观编号复用而生成 400 (Bad Request) 响应. 对于 Replaces, <replaced-dialog> 的用途是防止一种竞态条件: BYE 可能导致外观编号被释放, 而此时该编号本应继续归替换对话使用.
5.3.3. 外观编号与转接
在转接操作期间, 外观编号在操作过程中不发生变化非常重要. 设想如下示例: Alice 是共享外观组的成员, 正在与共享外观组之外的 Carol 通话. Carol 将 Alice 转接给 David, 而 David 也在共享外观组之外. 例如, 如果 Alice 与 Carol 的会话使用外观 3, 则随后与 David 建立的会话也应使用外观编号 3. 否则, 外观编号变化可能导致 UI 上出现 "跳动", 使用户感到困惑. 使用 RFC 5589 的术语, 这里有两种可能的场景: Alice 是任何类型转接中的被转接方 (transferee, 接收 REFER), 或者是咨询转接 (attended transfer) 中的转接目标 (transfer target, 接收带有 Replaces 的 INVITE).
如果 Alice 是被转接方, 则由 REFER 触发的 INVITE 会被视为咨询呼叫. Alice SHOULD 发布请求, 要求外观代理不要为此 INVITE 分配外观编号. 转接完成后, Alice SHOULD 再次发布, 将外观编号从与 Carol 的对话移动到与 David 的对话. 如果发送 PUBLISH 来移动外观编号, 则该发布 MUST 在向 Carol 发送 BYE 之前发送, 以避免一种竞态条件: 外观代理在看到 BYE 后重新分配该外观编号.
如果 Alice 是转接目标, 则传入的 INVITE 将包含 Replaces 头字段. 因此, 外观代理会复用与 Carol 的对话的外观编号, 并且在与 Carol 的对话终止后, 该外观编号将继续被使用.
5.4. 外观代理
本规范中定义的外观代理 MUST 为向该 AOR 注册的 UA 实现一个对话包状态代理。外观代理 MUST 支持第 5.2 节中定义的外观对话包扩展, 并使用 'shared' Event 头字段参数。外观代理 MUST 支持该事件包的发布和订阅。
外观代理 MUST 具备发现与该 AOR 关联的所有对话状态的方法。如果无法从呼叫有状态代理 (call stateful proxy) 或背靠背用户代理 (Back-to-Back User Agent, B2BUA) 获取此信息, 外观代理可以使用注册事件包 [RFC3680] 来获知与该 AOR 关联的 UA, 并订阅它们的对话事件状态。外观代理也可以订阅某个 UA 的对话事件状态以重建状态。因此, 注册服务器 MUST 支持注册事件包。
RFC 4235 建议对话包通知 "仅包含状态或参与信息发生变化的那些对话的信息"。本规范按如下方式扩展 RFC 4235。当 AOR 组中的 UA 发生以下事件时, 外观代理 SHOULD 发送对话事件状态通知:
-
呼叫被接收、发起、应答或终止。
-
呼叫被置于保持或解除保持。
-
呼叫被加入或替换。
-
外观编号被预留或释放。
外观代理 MUST 为所有来电分配外观编号, 并向订阅共享组 AOR 的 UA 发送即时通知。除带有 Join 或 Replaces 头字段的来电 INVITE 外, 都会分配一个新的外观编号。对于这种情况, 外观编号应与被加入或被替换对话的外观编号一致。如果 INVITE Replaces 或 Join 来自共享外观组之外, 外观代理将在 NOTIFY 中包含一个 <joined-dialog> 或 <replaced-dialog> 元素, 其中携带来自 Replaces 或 Joined 头字段的对话信息。
外观代理 MUST 能够与分叉代理通信, 以了解来电情况, 并将外观编号传递给代理, 或确保 INVITE 中包含带有适当外观编号的 Alert-Info 头字段。
请注意, UA 需要能够处理未分配外观编号的来电 INVITE。这可能是由外观代理故障或其他错误状况引起的。尽管可能无法正确呈现该 INVITE, 这也比忽略或使 INVITE 失败要好。
如果未收到用于选择/占用特定外观编号的 PUBLISH, 外观代理 SHOULD 为出站对话分配一个外观编号。
请注意, 如果共享外观组中存在不感知外观的 UA 发起呼叫, 外观代理仍会为这些 UA 发送的 INVITE 分配外观编号。
收到带外观编号的 PUBLISH 时, 外观代理会检查以确保该发布有效。除非存在 <joined-dialog> 或 <replaced-dialog> 元素表明该对话将被/已被替换或加入, 否则一个外观编号只能分配给一个对话。如果所选外观编号无效, 则返回 400 (Bad Request) 响应, 并 SHOULD 向该 UA 发送包含完整对话事件状态的即时 NOTIFY。
收到不带外观编号但包含 'shared' Event 头字段参数的 PUBLISH 时, 外观代理将其解释为该 UA 请求不分配外观编号。如果外观代理策略不允许这样做, 则返回 400 (Bad Request) 响应。如果策略允许, 则返回 200 (OK) 响应且不分配外观编号。外观代理不必向组内其他 UA 共享该对话信息 (即发送 NOTIFY), 因为该信息不会被其他 UA 呈现。
外观代理为对话分配外观编号的时间区间, 从通过 PUBLISH 请求该外观时或收到 INVITE 时开始, 直到与该外观关联的最后一个对话终止为止, 其中包括所有被加入或被替换的对话。在早期对话状态期间, 外观代理通过 PUBLISH 请求的 200 (OK) 响应中的 Expires 头字段控制对话状态发布的速率。RECOMMENDED 的时间间隔为 3 分钟。在与该发布关联的对话得到确认之后, 发布状态的过期对外观分配没有影响。如果发布中不包含任何对话状态信息, 外观代理 MUST 为该 UA 预留该外观编号, 但不能将该外观分配给该 UA 的任何特定对话。当发布状态被更新并包含任何对话信息时, 该外观编号即可分配给特定对话。已通过 PUBLISH 获得外观编号的 UA MAY 按照 [RFC3903] 所述, 通过 PUBLISH 移除事件状态来释放该外观编号。
如果组内成员向共享 AOR 发送 INVITE (即他们呼叫自己的 AOR), 外观代理 MUST 分配两个外观编号。第一个外观编号将是为该出站 INVITE 选择或分配的编号。第二个外观编号将是外观代理在该 INVITE 被分叉回组成员时为其分配的另一个编号。
这样做是为了在传统系统中保持一致的行为。
如果组内成员使用共享 AOR 发送 INVITE, 或有 INVITE 被发送至共享 AOR 而没有可用的外观编号, 代理 MAY 以 403 (Forbidden) 响应码拒绝该 INVITE。
外观编号仅用于与组 AOR 关联的一个或多个 UA 作为参与者的对话。如果发往组 AOR 的来电 INVITE 被转发到另一个 AOR, 该外观编号会立即被释放, 并可以分配给另一个对话。