mercuriod 0.4.2

Mercurio MQTT broker daemon
// Versioning: the broker matches a plugin's imports and exports to these
// types by structure, so any change to a function, record, variant, enum or
// flags breaks plugins built for the old shape. New hooks, fields or cases
// therefore need a new minor version, 0.2.0. Releases 0.1.x change only
// documentation.
package mercurio:plugin@0.1.0;

interface types {
  enum level { trace, debug, info, warn, error }
  enum access { read, write, subscribe }
  enum decision { allow, deny, defer }
  enum qos { at-most-once, at-least-once, exactly-once }

  /// `username` is set only for a user the broker verified, except in
  /// `authenticate` and in `auth-step` during CONNECT, where it is the user
  /// name the client claims.
  record client-info {
    client-id: string,
    username: option<string>,
    peer: option<string>,
  }

  /// A published message. The properties are MQTT 5's application
  /// properties; MQTT 3.x messages have none.
  record message {
    topic: string,
    payload: list<u8>,
    qos: qos,
    retain: bool,
    payload-is-utf8: bool,
    message-expiry: option<u32>,
    content-type: option<string>,
    response-topic: option<string>,
    correlation-data: option<list<u8>>,
    user-properties: list<tuple<string, string>>,
  }

  /// `replace` carries the whole changed message.
  variant message-action { keep, drop, replace(message) }

  record disconnected { client: client-info, reason: option<u8> }
  record subscribed { client: client-info, filter: string, qos: qos }
  record unsubscribed { client: client-info, filter: string }

  variant event {
    connected(client-info),
    disconnected(disconnected),
    subscribed(subscribed),
    unsubscribed(unsubscribed),
    session-expired(string),
  }

  /// The hooks a plugin implements; the broker calls no others.
  flags hooks { authenticate, authorize, on-message, on-event, on-tick, on-control }

  record registration {
    hooks: hooks,
    /// MQTT 5 enhanced authentication methods it answers `auth-step` for
    auth-methods: list<string>,
  }

  /// What `continue` sends: `data` goes to the client, and the broker
  /// keeps `state` for the next step.
  record challenge { data: list<u8>, state: list<u8> }
  /// The result of one step of enhanced authentication. `continue` sends
  /// a challenge to the client; `success` accepts it, as the user it
  /// names, if any; `failed` refuses it with an MQTT reason code.
  variant auth-result { %continue(challenge), success(option<string>), failed(u8) }
}

interface host {
  use types.{level};

  log: func(level: level, message: string);

  /// A store shared by every copy of the plugin, kept in memory until the
  /// broker stops. It is shared but not transactional: a get followed by a
  /// set can race with another copy.
  kv-get: func(key: string) -> option<list<u8>>;
  /// Fails when the store refuses the value, for example over a quota.
  kv-set: func(key: string, value: list<u8>) -> result<_, string>;
  kv-delete: func(key: string);
  kv-list: func(prefix: string) -> list<string>;
}

interface guest {
  use types.{client-info, access, decision, message, message-action, event, registration, auth-result};

  init: func(config: list<tuple<string, string>>) -> result<registration, string>;
  authenticate: func(client: client-info, username: string, password: list<u8>) -> decision;
  /// `state` is None on the first step of an exchange. During CONNECT,
  /// `client` carries the user name the client claims; on
  /// re-authentication, the verified user.
  auth-step: func(client: client-info, method: string, state: option<list<u8>>, data: option<list<u8>>) -> auth-result;
  authorize: func(client: client-info, topic: string, access: access) -> decision;
  on-message: func(client: client-info, message: message) -> message-action;
  on-event: func(event: event);
  on-tick: func();
  on-control: func(client: client-info, path: string, payload: list<u8>) -> option<list<u8>>;
}

world plugin {
  import host;
  export guest;
}