房间版本 1
此房间版本是房间的第一个版本,包含了其他房间版本的构建基础。
客户端注意事项
本地实现删除算法的客户端应参考下方的删除部分。
删除
在接收到删除事件后,服务器必须移除除以下列表外的所有键:
event_idtyperoom_idsenderstate_keycontenthashessignaturesdepthprev_eventsprev_stateauth_eventsoriginorigin_server_tsmembership
对于 content 对象,也必须移除除下列类型允许外的所有键:
m.room.member允许键membership。m.room.create允许键creator。m.room.join_rules允许键join_rule。m.room.power_levels允许的键有ban、events、events_default、kick、redact、state_default、users、users_default。m.room.aliases允许键aliases。m.room.history_visibility允许 键history_visibility。
服务器实现组成部分
本节信息仅供服务器实现者参考。使用客户端-服务器 API 的应用通常不受此处细节的影响。针对客户端注意事项的上一节才是客户端-服务器 API 相关用例应参考的资源。
此处定义的算法仅适用于版本 1 的房间。其他房间版本可能会采用其他算法,因此服务器在执行相关算法前应先确认所处理的是哪一版本的房间。
尽管目前有许多房间在使用房间版本 1,但已知其会出现一些不理想的效果。支持房间版本 1 的服务器应注意,其限制通常应当更为宽松,同时可能会出现不一致的情况。
删除
见上文。
事件 ID
一个事件只能有一个事件ID。在此房间版本中,事件ID的格式为:
$opaque_id:domain
其中,domain 是创建该房间的主服务器的服务器名称,而 opaque_id 是在本地唯一的字符串。
domain 仅用于命名空间,以避免不同主服务器之间标识符发生冲突的风险。并不意味着相关房间或事件一定还在对应的主服务器上可用。
事件格式
版本 1 房间中的事件结构如下:
Persistent Data Unit
Persistent Data Unit
A persistent data unit (event) for room versions 1 and 2.
| Name | Type | Description |
|---|---|---|
auth_events |
[[string|Event Hash]] |
Required: Event IDs and reference hashes for the authorization events that would allow this event to be in the room. Must contain less than or equal to 10 events. Note that if the relevant auth event selection rules are used, this restriction should never be encountered. |
content |
object |
Required: The content of the event. |
depth |
integer |
Required: The maximum depth of the |
event_id |
string |
Required: The event ID for the PDU. |
hashes |
Event Hash |
Required: Content hashes of the PDU, following the algorithm specified in Signing Events. |
origin_server_ts |
integer |
Required: Timestamp in milliseconds on origin homeserver when this event was created. |
prev_events |
[[string|Event Hash]] |
Required: Event IDs and reference hashes for the most recent events in the room that the homeserver was aware of when it made this event. Must contain less than or equal to 20 events. |
redacts |
string |
For redaction events, the ID of the event being redacted. |
room_id |
string |
Required: Room identifier. |
sender |
string |
Required: The ID of the user sending the event. |
signatures |
{string: {string: string}} |
Required: Signatures for the PDU, following the algorithm specified in Signing Events. |
state_key |
string |
If this key is present, the event is a state event, and it will replace previous events
with the same |
type |
string |
Required: Event type |
unsigned |
UnsignedData |
Additional data added by the origin server but not covered by the |
| Name | Type | Description |
|---|---|---|
sha256 |
string |
Required: The hash. |
| Name | Type | Description |
|---|---|---|
age |
integer |
The number of milliseconds that have passed since this message was sent. |
Examples
{
"auth_events": [
[
"$af232176:example.org",
{
"sha256": "abase64encodedsha256hashshouldbe43byteslong"
}
]
],
"content": {
"key": "value"
},
"depth": 12,
"event_id": "$a4ecee13e2accdadf56c1025:example.com",
"hashes": {
"sha256": "thishashcoversallfieldsincasethisisredacted"
},
"origin_server_ts": 1404838188000,
"prev_events": [
[
"$af232176:example.org",
{
"sha256": "abase64encodedsha256hashshouldbe43byteslong"
}
]
],
"room_id": "!UcYsUzyxTGDxLBEvLy:example.org",
"sender": "@alice:example.com",
"signatures": {
"example.com": {
"ed25519:key_version": "these86bytesofbase64signaturecoveressentialfieldsincludinghashessocancheckredactedpdus"
}
},
"type": "m.room.message",
"unsigned": {
"age": 4612
}
}
已弃用的事件内容 schema
发送到此版本房间中的事件,其格式可能与其常规模式不同。此类情况将在此处进行说明。
此处描述的行为仅为严格保留向后兼容性。服务器应采取合理措施,防止用户发送这些所谓的“格式错误”事件,并且绝不能以此处描述的行为作为默认行为。
m.room.power_levels 事件支持以字符串形式传递数值
为了与早期实现保持向后兼容,
m.room.power_levels 事件中的每个整数值属性
都可以被编码为字符串而非整数。这包括 events、notifications 和 users
属性中的嵌套值。例如,以下是在此房间版本中有效的
m.room.power_levels 事件:
{
"content": {
"ban": "50",
"events": {
"m.room.power_levels": "100"
},
"events_default": "0",
"state_default": "50",
"users": {
"@example:localhost": "100"
},
"users_default": "0"
},
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "",
"type": "m.room.power_levels"
}
当该值表示整数时,必须符合以下格式:
- 仅包含一个十进制整数,不允许浮点数或小数点,可以带任意数量的前导零(如
"100"、"000100"); - 可选地在整数前面加一个
-或+字符(如"+100"、"-100"); - 可选地在前后添加任意数量的空白字符(如
" 100 "、" 00100 "、" +100 "、" -100 ")。
授权规则
影响授权的状态事件类型包括:
如果未明确给出权限等级,则从默认值推断。
例如,提及 sender 的权限等级时,也可指代房间用户的默认权限等级。
规则如下:
- 若类型为
m.room.create:- 若有任何
prev_events,则拒绝。 - 若
room_id的域名与sender的域名不符,则拒绝。 - 若
content.room_version存在但不是识别的版本,则拒绝。 - 若
content没有creator属性,则拒绝。 - 否则,允许。
- 若有任何
- 针对事件的
auth_events:- 若某一
type与state_key组合存在重复,则拒绝。 - 若存在其
type与state_key不符合 认证事件选择 算法(详见服务器规范)的项,则拒绝。 - 若有事件本身因 接收 PDU 时执行的检查 被拒绝,则拒绝。
- 若未能在条目中找到
m.room.create事件,则拒绝。
- 若某一
- 若房间状态中的
m.room.create事件的content包含m.federate属性且值为false,且此事件的sender域名不与创建事件的sender域名一致,则拒绝。 - 若类型为
m.room.aliases:- 若事件无
state_key,则拒绝。 - 若发送者域名与
state_key不符,则拒绝。 - 否则,允许。
- 若事件无
- 若类型为
m.room.member:- 若没有
state_key属性,或content中无membership属性,则拒绝。 - 若
membership为join:- 若唯一的前序事件为
m.room.create且state_key为创建者,则允许。 - 若
sender与state_key不符,则拒绝。 - 若
sender被封禁,则拒绝。 - 若
join_rule为invite,则当membership状态是invite或join时,允许。 - 若
join_rule为public,则允许。 - 否则,拒绝。
- 若唯一的前序事件为
- 若
membership为invite:- 若
content含有third_party_invite属性:- 若 目标用户 被封禁,则拒绝。
- 若
content.third_party_invite无signed属性,则拒绝。 - 若
signed无mxid和token属性,则拒绝。 - 若
mxid与state_key不符,则拒绝。 - 若当前房间状态中无
state_key为token的m.room.third_party_invite事件,则拒绝。 - 若
sender与该m.room.third_party_invite的sender不符,则拒绝。 - 若
signed中的任意签名与该m.room.third_party_invite事件中的任一公钥匹配,则允许。公钥位于m.room.third_party_invite的content中:- 单个公钥存于
public_key属性; - 公钥列表存于
public_keys属性。
- 单个公钥存于
- 否则,拒绝。
- 若
sender当前成员状态不是join,则拒绝。 - 若 目标用户 当前成员状态是
join或ban,则拒绝。 - 若
sender权限等级大于或等于 邀请等级,则允许。 - 否则,拒绝。
- 若
- 若
membership为leave:- 若
sender与state_key匹配,则仅当其当前成员状态为invite或join时允许。 - 若
sender当前成员状态不是join,则拒绝。 - 若 目标用户 当前成员状态为
ban且sender权限等级低于 封禁等级,则拒绝。 - 若
sender权限等级大于等于 踢人等级,且 目标用户 权限等级小于sender权限等级,则允许。 - 否则,拒绝。
- 若
- 若
membership为ban:- 若
sender当前成员状态不是join,则拒绝。 - 若
sender权限等级大于等于 封禁等级,且 目标用户 权限等级低于sender权限等级,则允许。 - 否则,拒绝。
- 若
- 其他情况下,未知成员状态。拒绝。
- 若没有
- 若
sender当前成员状态不是join,则拒绝。 - 若类型为
m.room.third_party_invite:- 仅当
sender当前权限等级大于等于 邀请等级 时允许。
- 仅当
- 若事件类型的 所需权限等级 大于
sender权限等级,则拒绝。 - 若事件的
state_key以@开头且与sender不符,则拒绝。 - 若类型为
m.room.power_levels:- 若
content中users属性不是键为有效用户ID且值为整数(或整数字符串)的对象,则拒绝。 - 若房间中没有前序的
m.room.power_levels事件,则允许。 - 针对
users_default、events_default、state_default、ban、redact、kick、invite属性,在添加、更改或移除时,针对每个变动:- 若当前值大于
sender当前权限等级,则拒绝。 - 若新值大于
sender当前权限等级,则拒绝。
- 若当前值大于
- 针对
events属性中被更改或移除的每一项:- 若当前值大于
sender当前权限等级,则拒绝。
- 若当前值大于
- 针对
events属性中被添加或更改的每一项:- 若新值大于
sender当前权限等级,则拒绝。
- 若新值大于
- 针对
users属性(除sender本人)的被更改或移除的每一项:- 若当前值大于等于
sender当前权限等级,则拒绝。
- 若当前值大于等于
- 针对
users属性被添加或更改的每一项:- 若新值大于
sender当前权限等级,则拒绝。
- 若新值大于
- 否则,允许。
- 若
- 若类型为
m.room.redaction:- 若
sender权限等级大于等于 撤回等级,则允许。 - 若被撤回事件的
event_id域名与m.room.redaction的event_id域名一致,则允许。 - 否则,拒绝。
- 若
- 否则,允许。
这些规则的部分后果:
- 除非你是房间成员,否则唯一被允许的操作(除首次创建/加入外)为:加入公共房间、接受或拒绝对房间的邀请。
- 取消封禁某人,你必须拥有大于等于踢人和封禁等级的权限,且你的权限等级高于目标用户。
状态解析
已知房间版本 1 存在一些 bug 可能导致房间状态回滚到之前的旧版本。例如,这可能导致已加入房间的用户被移除,管理员和版主丢失其权限,甚至被封禁的用户能够重新加入。其它状态事件,如房间名称或主题,也可能会回滚到之前的版本。
这些问题在房间版本 2 引入的状态解析算法中已被修复。
事件 E 之后的房间状态 S′(E),以 E 之前的房间状态 S(E) 为基础定义,并且取决于 E 是状态事件还是消息事件:
- 如果 E 是消息事件,则 S′(E) = S(E)。
- 如果 E 是状态事件,则 S′(E) 等于 S(E),但将对应于 E 的
event_type和state_key的条目替换为 E 的event_id。
事件 E 之前的房间状态 S(E),等于 E 的所有 prev_events {E′, E″, …} 之后生成的状态集 {S′(E′), S′(E″), …} 的解析结果。
多状态的解析规则如下。最终解析出的状态通过多轮遍历构建;我们以 R 表示当前已解析的中间结果。
- 首先,将 R 设为所有需要解析的状态的并集,排除任何冲突事件。
- 第一步先解决
m.room.power_levels相关事件的冲突。如果没有冲突,则跳过此步骤;否则:- 把要解析的状态中的所有
m.room.power_levels事件收集成一个列表。 - 按照
depth升序、sha1(event_id)降序排序。 - 将列表中的第一个事件加入 R。
- 对于列表后续每个事件,检查该事件是否被授权在状态 R 的房间中触发。如果允许,则用该事件更新 R,继续处理下一个事件;如不允许,则终止,跳过到下一步解决
m.room.join_rules事件。
- 把要解析的状态中的所有
- 针对
m.room.join_rules事件的冲突,重复上述过程。 - 针对
m.room.member事件的冲突,也同样重复上述过程。 - 其余事件对授权规则没有影响,因此对于其它所有冲突,只需选择通过 R 中的身份验证、且拥有最大 depth 和最小
sha1(event_id)的事件,将其加入 R。
冲突指的是两个状态针对同一 (event_type, state_key) 拥有不同 event_id。因此受影响的事件被称为冲突事件。
标准 JSON
出于附录中所述的原因,服务器不得强制严格遵循所规定的 JSON 格式。