Skip to content

Event Spec

Nathan Pennie edited this page Jul 1, 2020 · 2 revisions

ListDocumentModel (Anchor Logoot implementation)

In this format, the events are compressed by reducing the size of keys to single letters.

Compressed insertion (net.kb1rd.anchorlogoot0.ins)

The following data is verified, decompressed, then passed onto the algorithm:

  • a - Anchors. An object containing the following:
    • v - Version of the LogootPosition algorithm. Since this determines the resulting position from two anchors, it is essential that this algorithm is identical across all clients. This is currently fixed at 0.
    • o - Reference branch order. Since it's inefficient to send each user's full MXID multiple times, the indexes of this array are used to identify users in the Logoot positions. It is an array of the following elements:
      • [mxid] or [mxid, userbranch] - The MXID is just the user's Matrix ID. The optional userbranch is a custom, user-defined branch such as a suggestion branch. There is currently no implementation for this
    • l - Left anchor, optional. When undefined, it is assumed to be DocStart. When defined, it is an array of the following:
      • [num, id] - num is the numeric portion of the Logoot position. id is the branch portion of the position. It corresponds to an entry in the order (o) above.
    • r - Right anchor, optional. When undefined, it is assumed to be DocEnd. When defined, it is the same as above.
  • c - The Lamport clock.
  • d - The data array. This is an array of iterables. The elements in the data are the combination of the elements of all the child elements. This means that if there are two strings, then they are combined. If there's a string and an array of objects, then the resulting array would have a bunch of single characters (from inside the string) and the contents of the array of objects. This makes rich text much easier to implement.

Example Event

{
  "a": {
    "v": 0,
    "o": [
      ["@alice:example.org"],
      ["@bob:example.org"]
    ],
    "l": [
      [1, 0],
      [0, 1]
    ],
    "r": [
      [1, 0],
      [2, 1]
    ]
  },
  "c": 5,
  "d": ["hi"]
}

Notepad Restrictions

The Notepad places the following restrictions on the above schema:

  1. The userbranch must not be defined. Events with a userbranch defined will be rejected since there is no support currently.
  2. The data array must only contain strings. This is a requirement of the net.kb1rd.plaintext restrictions. Events with arrays mixed in will be rejected. In the future, they may be displayed as unrenderable characters.

Notepad Schema

The following jsonschema is used in the Notepad:

// LogootInt.JSON.Schema is defined as a `number`

const OrderLookupArray = {
  type: 'array',
  items: { type: 'array', items: [{ type: 'string' }] }
}
const MappedLogootPosition = {
  type: 'array',
  items: {
    type: 'array',
    items: [LogootInt.JSON.Schema, { type: 'number' }]
  }
}

InsertionEvent.JSON = {}
InsertionEvent.JSON.Schema = {
  type: 'object',
  properties: {
    // Anchors
    a: {
      type: 'object',
      properties: {
        // Version for start resolution. Currently 0
        v: { type: 'number' },
        // Left anchor
        l: MappedLogootPosition,
        // Right anchor
        r: MappedLogootPosition,
        // Order lookup. Entries in the positions are mapped to branches here.
        o: OrderLookupArray
      },
      required: ['v', 'o']
    },
    // Data, in this case an array of strings
    // The strings are `join`ed. This is rich text future proofing.
    d: { type: 'array', items: { type: 'string' } },
    // Lamport clock
    c: LogootInt.JSON.Schema,
    // Branch information (currently not used. May be used in the future for
    // including device ID or user-defined branch information)
    br: { type: 'object' }
  },
  required: ['a', 'd', 'c']
}

(Variables are used instead of jsonschema's custom types since it's easier ATM. Ok, it's a bit weird, fine)

Compressed removal (net.kb1rd.anchorlogoot0.rem)

The following data is verified, decompressed, then passed onto the algorithm:

  • o - Reference branch order. Since it's inefficient to send each user's full MXID multiple times, the indexes of this array are used to identify users in the Logoot positions. It is an array of the following elements:
    • [mxid] or [mxid, userbranch] - The MXID is just the user's Matrix ID. The optional userbranch is a custom, user-defined branch such as a suggestion branch. There is currently no implementation for this
  • r - An array of the following objects:
    • s - Starting position. It is an array of the following:
      • [num, id] - num is the numeric portion of the Logoot position. id is the branch portion of the position. It corresponds to an entry in the order (o) above.elements.
    • l - Length of text to remove. Offsets the number portion of the last element of the s array to find the end.
    • c - Lamport clock to remove. Removals take priority over insertions, so the example below will actually remove data at Lamport clock 5.

Example Event

This removal will remove the insertion above. Note that a removal's start will not always be equal to the left anchor of the insertion that allocated data. It just happens to be in this case.

{
  "o": [
    ["@alice:example.org"],
    ["@bob:example.org"]
  ],
  "r": [
    {
      "c": 5,
      "l": 2,
      "s": [
        [1, 0],
        [0, 1]
      ]
    }
  ]
}

Notepad Restrictions

The Notepad places the following restrictions on the above schema:

  1. The userbranch must not be defined. Events with a userbranch defined will be rejected since there is no support currently.

Notepad Schema

The following jsonschema is used in the Notepad: (The current schema actually contains a low-priority typo, so this is not line-by-line equal)

// LogootInt.JSON.Schema is defined as a `number`

const OrderLookupArray = {
  type: 'array',
  items: { type: 'array', items: [{ type: 'string' }] }
}
const MappedLogootPosition = {
  type: 'array',
  items: {
    type: 'array',
    items: [LogootInt.JSON.Schema, { type: 'number' }]
  }
}

// ...

RemovalEvent.JSON.Schema = {
  type: 'object',
  properties: {
    r: {
      type: 'array',
      items: {
        type: 'object',
        properties: {
          s: MappedLogootPosition,
          l: { type: 'number' },
          c: LogootInt.JSON.Schema
        },
        required: ['s', 'l', 'c']
      }
    },
    o: OrderLookupArray
  },
  required: ['r', 'o']
}