# Protocol v2.0

# Client

Minimal client protocol version: 2.0

# Statement

{
  // Version of API (not protocol)
  "api": 0,
  // Client local time in ms
  "client_time": 16500123000,
  // Which format to use for object encoding: JSON | YAML | TOML
  "scheme_format": "JSON",
  // Compression algorithms, supported on client side: gzip | zlib
  "compressors": [
    "gzip",
    "zlib"
  ],
  // Optional. compressors[0] used by default
  "default_compression": "zlib",
}
api: 0
client_time: 16500123000
scheme_format: JSON
compressors:
  - gzip
  - zlib
default_compression: zlib
api = 0.0
client_time = 16500123000.0
scheme_format = "JSON"
compressors = [
  "gzip",
  "zlib"
]
default_compression = "zlib"
{
  // Server local time in ms [UTC]
  "server_time": 165000123000,
}
server_time: 165000123000
server_time = 165000123000.0

# Actions

# 0x00 Action

Common request/response action.

# Structure

Action consists of:

  • HEAD: 18 bytes
  • HEADERS + SEPARATOR + PAYLOAD: HEAD.data_len bytes

Where SEPARATOR is 00 00 (two empty bytes)

# Head

  • Handler ID: uInt2 - Endpoint ID - treat as URL alternative
  • Message ID: uInt2 - Message ID
  • Send Time: uInt8 - Client send time in ms
  • Data Type: uInt1 - What type PAYLOAD is
  • Compression: uInt1 - What compression was used on PAYLOAD
  • Data Len: uInt4 - Length of HEADERS + HEADER_SEPARATOR + PAYLOAD

# Headers

Message header works like in HTTP: It contains META information that can be used at protocol level to change up server behavior w/o changing business logic.

Message header is a simple UTF-8 encoded JSON (YAML or TOML) dictionary followed by two empty bytes 00 00

Request only Headers

  • "Offset": int [1] - tells the server to skip N amount of first bytes of <Data> section

Response only Headers

There are no response-only header currently supported

Common Headers

  • "Files": [{"key": str, "name": str, "size": int, "type": str?}] [1] - This header is being used when <Data Type> in packet header is set to FILES - 0x02
  • "Status": int - HTTP Status code analog. Usually only used by a server to show client if there was any error or not.

[1] Using "Offset" header for the handler that returns FILES will also decrease "size" fields in "Files" response header. If "size" will drop to zero, then file won't appear in "Files" header.

# Payload

Whatever Handler accepts/returns

# 0x01 StreamAction

Common request/response streaming action. Basically same as Action

# Structure

StreamAction consists of:

  • HEAD: 14 bytes
  • Headers size: 4 bytes
  • HEADERS: Headers size
  • PAYLOAD: multiple parts (optional)
    • Payload part size: 4 bytes
    • Payload part
  • END: 4 empty bytes

Client should read payload until payload part size is 00 00 00 00 (4 empty bytes), then stop.

# Head

  • Handler ID: uInt2 - Endpoint ID - treat as URL alternative
  • Message ID: uInt2 - Message ID
  • Send Time: uInt8 - Client send time in ms
  • Data Type: uInt1 - What type PAYLOAD and HEADERS are
  • Compression: uInt1 - What compression was used on PAYLOAD and HEADERS

# 0x02 InputAction

Small version of Action, that is initially sent by server to ask client for more data, when handling request.

Client must send InputAction back to server with requested information (or empty) to continue.

InputAction may come to client multiple times during single request

# Structure

InputAction consists of:

  • HEAD: 8 bytes
  • HEADERS + SEPARATOR + PAYLOAD: HEAD.data_len bytes

Where SEPARATOR is 00 00 (two empty bytes)

# Head

  • Message ID: uInt2 - Message ID
  • Data Type: uInt1 - What type PAYLOAD
  • Compression: uInt1 - What compression was used on PAYLOAD
  • Data Len: uInt4 - Length of HEADERS + HEADER_SEPARATOR + PAYLOAD

# 0x05 DownloadSpeed

Separate action, that tells server to set send rate to specific bps limit.

# Structure

DownloadSpeedAction consists of:

  • HEAD: 4 bytes

# Head

  • Download speed: uInt4 - Amount of bytes server is allowed to send per second

# 0x06 CancelInput

Separate action, that tells server to stop waiting for answer to InputAction. Used instead of Input response.

# Structure

CancelInputAction consists of:

  • HEAD: 2 bytes

# Head

  • Message ID: uInt2 - Message ID - message_id of request that is being processed by server

# 0xFF PingPong

Separate action, that tells server that client is still alive. Server will respond with same action, but updated send time.

# Structure

PingAction consists of:

  • HEAD: 8 bytes

# Head

  • Send Time: uInt8 - Client send time in ms

# Endpoints

In HTTP servers, endpoints are functions, that are located with method: GET|POST|etc. and URL: https://....

In CATS we use uInt2 ID to locate endpoints.

Endpoints are triggered with Action and StreamAction messages, and they can ask issuer for additional info using Inputs

# Inputs

Inputs - is a mechanism that lets the server ask client for additional information. They also may be used to send partial data on client on demand

Instead of client sending the request first, inputs are sent firstly by server.

Inputs will convey same Message ID as client's request.

# Broadcast

If you want to send a client an action, but don't want to wait for request, you can send it as usual reply, but with a different Message ID.

Broadcasts are one-sided, so client don't need to reply on it. Client should subscribe (locally) on broadcast in order to handle them, otherwise they will be silently dropped.

# Message ID

Message ID - is uInt2 random number, generated by sending party. It is used to track request-response and inputs.

  • Message ID must be unique for active requests
  • Message ID must be 0x0000 to 0x7FFF for request-reply scenario
  • Message ID must be 0x8000 to 0xFFFF for broadcast/publish scenario
  • You may use increment counter or RNG to generator Message ID
  • Response will contain the same Message ID as request

# Data Types

# 0x00 Binary

Plain byte buffer, how to use/parse it, should be described in API endpoint description

# 0x01 Scheme

JSON (YAML or TOML) encoded object. Which format is used declared at initialisation stage

# 0x02 Files

One or multiple files, concatenated together in a single byte buffer. Information about their respective sizes, names, formats, etc. is located in HEADERS

Headers
{
  // List of files (order is same as byte buffers in payload) 
  "Files": [
    {
      // Hashmap key, may equal to name
      "key": "avatar", 
      // Selected file name
      "name": "Anakin.jpg",
      // Size of byte buffer, that went into payload
      "size": 51231, 
      // MIME (optional)
      "type": "image/jpeg",
    },
    // ... more files
  ]
}
Headers
Files:
  - key: avatar
    name: Anakin.jpg
    size: 51231
    type: image/jpeg
Headers
[[Files]]
key = "avatar"
name = "Anakin.jpg"
size = 51231.0
type = "image/jpeg"

# 0x03 Byte Scheme

Custom data scheme, encoded in plain bytes (like action head)

# Handshake

Handshake allows server to verify that client is some-what official.

  1. Client send N bytes to server
  2. Server verifies it
  3. If handshake is OK, server send 01 to client
  4. If handshake is NO, server send 00 to client and abort connection

# Handshake: SHA256

SHA256 handshake is, as name suggests, just a sha256 hash comparison. It is time-bound, so old hashes won't work, but still have time window of ±10 seconds for connection latency check.

SHA256 is calculated from combined bytes of:

  • SECRET_KEY - secret symmetric key that is set by server and must be present at client
  • TIME - decimal string representation of time encoded in bytes (UTF-8 as always)

TIME - is a string of decimal integer, with last digit set to 0. Time used on client must take difference between local and server one into account.

Hence, server_time sent by server in statement can be used to calculate actual server time.