Skip to content

Commit

Permalink
Adding a header for WebSocket messages to allow future extensibility
Browse files Browse the repository at this point in the history
Resolves #133

Thi is ane extensibility mechanism - a header for WebSocket messages. Every WebSocket message is composed of 2 parts: varint-encoded unsigned 64 bit integer header, followed by the serialized Protobuf AgentToServer/ServerToAgent messages.

The current value of the header is equal to 0. In the future when non-zero values of headers are introduced their use should be negotiated during the connection phase (via HTTP headers or via Capabilities fields). This is necessary to ensure interoperability between different OpAMP versions. Non-zero header values will not be a breaking change, they will only used after successful negotiation between the Client and Server.

Note: we don't need the header for plain HTTP transport. Any extensibility for HTTP can be done via HTTP headers (not suitable for WebSocket since it cannot work per message - HTTP headers are sent once per connection).

## Example Usage

The header can be used in the future for example in the following ways.

### Message Fragmentation

We found that some WebSocket implementations (e.g. [AWS API Gateway](#60 (comment))) limit the message size to 128KB. The only way to overcome this limitation is to perform message fragmentation and reassembly in OpAMP itself. This can be done by using one bit in the header to indicate whether the message is the last fragment.

### Support Other Compression Algorithms

WebSocket has built-in compression. What compression is used depends on the WebSocket implementation and realistically only "deflate" compression implementations are available. There are more modern compression algorithms (e.g. zstd), but it is impossible to use in OpAMP since most known WebSocket implementations simply don't support them.

The header can easily allow supporting these other compression algorithms. We can use one or more of the header bits to indicate the compression algorithm used.

Note: this sort of extension can be also done via HTTP header negotiation. However, not all WebSocket implementations allow custom HTTP headers (browsers don't).
  • Loading branch information
tigrannajaryan committed Nov 9, 2022
1 parent aaace74 commit 32dd947
Showing 1 changed file with 55 additions and 7 deletions.
62 changes: 55 additions & 7 deletions specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ Note: this document requires a simplification pass to reduce the scope, size and
- [Introduction](#introduction)
- [Communication Model](#communication-model)
* [WebSocket Transport](#websocket-transport)
+ [WebSocket Message Format](#websocket-message-format)
+ [WebSocket Message Exchange](#websocket-message-exchange)
* [Plain HTTP Transport](#plain-http-transport)
* [AgentToServer and ServerToAgent Messages](#agenttoserver-and-servertoagent-messages)
+ [AgentToServer Message](#agenttoserver-message)
Expand Down Expand Up @@ -288,22 +290,68 @@ Supervisor is part of that entity.
One of the supported transports for OpAMP protocol is
[WebSocket](https://datatracker.ietf.org/doc/html/rfc6455). The OpAMP Client
is a WebSocket client and the Server is a WebSocket Server. The Client and the Server
communicate using binary data WebSocket messages. The payload of each WebSocket
message is a
[binary serialized Protobuf](https://developers.google.com/protocol-buffers/docs/encoding)
message. On behalf of the Agent, the Client sends AgentToServer Protobuf messages and the Server sends
ServerToAgent Protobuf messages:
communicate using binary data WebSocket messages. The content of each WebSocket
message is an encoded `header`, followed by a
[binary encoded Protobuf](https://developers.google.com/protocol-buffers/docs/encoding)
message `data` (see [WebSocket Message Format](#websocket-message-format)).

On behalf of the Agent, the Client sends AgentToServer message data
and the Server sends ServerToAgent Protobuf message data:

```
┌────────────\ \────────┐ ┌──────────────┐
│ / / │ AgentToServer │ │
│ / / │ Data:AgentToServer │ │
│ \ \ OpAmp ├───────────────────────►│ │
│ Agent / / │ │ Server │
│ \ \ Client │ ServerToAgent │ │
│ \ \ Client │ Data:ServerToAgent │ │
│ / / │◄───────────────────────┤ │
└────────────\ \────────┘ └──────────────┘
```

### WebSocket Message Format

The format of each WebSocket message is the following:

```
┌────────────┬────────────────────────────────────────┬───────────────────┐
│ header │ Varint encoded unsigned 64 bit integer │ 1-10 bytes │
├────────────┼────────────────────────────────────────┼───────────────────┤
│ data │ Encoded Protobuf message, │ 0 or more bytes │
│ │ either AgentToServer or ServerToAgent │ │
└────────────┴────────────────────────────────────────┴───────────────────┘
```

The unencoded `header` is a 64 bit unsigned integer. In the WebSocket message the 64 bit
unencoded `header` value is encoded into bytes using [Base 128 Varint](
https://developers.google.com/protocol-buffers/docs/encoding#varints) format. The
number of the bytes that the encoded `header` uses depends on the value of unencoded
`header` and can be anything between 1 and 10 bytes.

The value of the unencoded `header` is set equal to 0 in this version of the specification.
All other `header` values are reserved for future use. Such values will be defined in
future versions of OpAMP specification. OpAMP WebSocket message decoders that are
compliant with this specification SHOULD check that the value of the `header` is equal
to 0 and if it is not SHOULD assume that the WebSocket message is malformed.

The `data` field contains the bytes that represent the AgentToServer or ServerToAgent
message encoded in [Protobuf binary wire format](
https://developers.google.com/protocol-buffers/docs/encoding).

Note that both `header` and `data` fields contain a variable number of bytes.
The decoding Base 128 Varint algorithm for the `header` knows when to stop based on the
bytes it reads.

To decode the `data` field using Protobuf decoding logic the implementation needs
to know the number of the bytes of the `data` field. To calculate this the implementation
MUST deduct the size of the `header` in bytes from the size of the WebSocket message
in bytes.

Note that due to the way Protobuf wire format is designed the size of the `data` in
bytes can be 0 if the encoded AgentToServer or ServerToAgent message is empty (i.e. all
fields are unset). This is a valid situation.

### WebSocket Message Exchange

OpAMP over WebSocket is an asynchronous, full-duplex message exchange protocol. The order and
sequence of messages exchanged by the OpAMP Client and the Server is defined for each
particular capability in the corresponding section of this specification.
Expand Down

0 comments on commit 32dd947

Please sign in to comment.