This commit is contained in:
Elijah R 2025-01-10 14:14:41 -05:00
commit 45ee7f4091
20 changed files with 814 additions and 0 deletions

10
.editorconfig Normal file
View file

@ -0,0 +1,10 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
# specifically for YAML
[{yml, yaml}]
indent_style = space

32
.gitignore vendored Normal file
View file

@ -0,0 +1,32 @@
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
.vs/
project.lock.json
project.fragment.lock.json
artifacts/
## Temp for now so that the js package can be workspaced until we get it on npm or something
js/yarn.lock
js/node_modules/
js/src/gen/
js/dist/
js/package.tgz
js/.yarn/*
js/!.yarn/patches
js/!.yarn/plugins
js/!.yarn/releases
js/!.yarn/sdks
js/!.yarn/versions
js/.pnp.*

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# CollabVM 3 Protocol
Current Version: v0.0
These files make up the Protocol Buffers-based messages that the CollabVM 3 server sends.
**NOTICE**: The protocol is currently not in a released state, so it can/will change. Do not rely on it being the same or compatible in this stage of development.
# Protocol Versions
We will release a major version bump *every time* we make a change (once we have released v1.0) to the protocol that will break older clients (by replacing/removing fields or other protobuf breaks). Non-breaking changes will be a minor version bump.
Despite the fact Protobuf has data format stability guarantees, we allow breaks on purpose, since if a hacky workaround has to be made, it's probably time for a version bump.
## Protocol Documentation
### Convention
All given pieces of the protocol are neatly organized into individual `collabvm.proto.[X]` packages.
This is to make it a lot less convoluted to work with.
This may make it a bit harder to build protobufs for but IMO it's better than not caring.

50
SPEC.md Normal file
View file

@ -0,0 +1,50 @@
# Flow/protocol spec (TEMP)
## Connected
Once connected to the VM WebSocket or WebTransport instance, the server will send the following messages wrapped as ServerMessage:
- Server hello, which contains the protocol version.
The client will, upon recieving the server hello send a hello message with its protocol version. If the protocol version is too low or incompatible with the server, the server will simply close the connection.
Otherwise the server will send initalization data:
- Chat history
- User list (including role colors)
- Your own user
- Vote states (if votes are currently active)
- Turn state (if applicable)
- a list of Remoting display formats that the VM can use
## Normal usage
The client is expected to send a SelectFormat message (wrapped in ClientMessage) with the format they want to use within 10 seconds.
Otherwise, the server disconnects with a Disconnect message and closes the WebSocket connection.
A client is free to select any format that the server itself has advertised to the client.
Once the client selects the format, the client will start getting Remoting display messages in the format they requested. Currently that is:
| format enum | description |
| --------------------------- | ----------------------------- |
| `LEGACY_UGLY_BAD_UGLY_JPEG` | Legacy JPEG bandwidth-waste. |
| `STREAM_H264` | An H.264 video stream. |
| `STREAM_H265` | An H.265 (HEVC) video stream. |
If the client selects a video stream format, then the server will not send display size messages.
This is because the intraframes that are sent by the server upon the screen being resized or a user joining will contain the required parameters to size the display.
## View mode
The client does not need to send a SelectFormat message; in fact it actually will be ignored if the client DOES send one, since it would defeat the purpose to handle it.
The client is required to send screenshot requests to look at the display, since Remoting messages are not sent.
This is useful for utility bots since they can simply hold a connection and then simply save the response; they do not need to implement JPEG decoding, H.264 decoding, or anything. They just save some data to disk or whatever. Easy!
## If the user is kicked (or banned)
The server will send a Disconnect message with an appropiate reason and then close the connection. Otherwise if the connection closes it is assumed the user can simply reconnect.

18
check Normal file
View file

@ -0,0 +1,18 @@
#!/bin/bash
# check all //protocol protos generate properly
# protos to check
PROTOS=(
admin.proto
remoting.proto
vm.proto
user.proto
vote.proto
client.proto
server.proto
)
mkdir out
protoc --cpp_out=out ${PROTOS[@]} || echo "FAILS TO GENERATE! do not check in or I will beat your ass";
rm -rf out

View file

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>CollabVM.Protocol</PackageId>
<Version>0.1.0</Version>
<Authors>Computernewb</Authors>
<Description>Protocol buffers for CollabVM 3.0</Description>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://git.computernewb.com/collabvm/collabvm3</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.29.2" />
<PackageReference Include="Grpc.Tools" Version="2.68.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\src\*.proto" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>

3
csharp/README.md Normal file
View file

@ -0,0 +1,3 @@
# CollabVM.Protocol (C#)
C# binding package for the CollabVM3 protocol buffers. See the [CollabVM protocol documentation](../README.md) and the [protobuf documentation](https://protobuf.dev/getting-started/csharptutorial/) for more information.

1
js/.yarnrc.yml Normal file
View file

@ -0,0 +1 @@
nodeLinker: node-modules

3
js/README.md Normal file
View file

@ -0,0 +1,3 @@
# @collabvm3/protocol (JavaScript)
Provides TypeScript bindings for the CollabVM3 protocol buffers. See the [CollabVM protocol documentation](../README.md) and the [protobuf documentation](https://github.com/bufbuild/protobuf-es/blob/main/MANUAL.md) for more information.

8
js/buf.gen.yaml Normal file
View file

@ -0,0 +1,8 @@
version: v2
clean: true
inputs:
- directory: ../src
plugins:
- local: protoc-gen-es
out: src/gen
opt: target=ts

26
js/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "@collabvm3/protocol",
"packageManager": "yarn@4.4.1",
"version": "0.1.0",
"author": {
"name": "Computernewb",
"url": "https://computernewb.com/"
},
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"scripts": {
"build-buf": "buf generate",
"build": "tsc",
"prepack": "yarn build-buf && yarn build"
},
"devDependencies": {
"@bufbuild/buf": "^1.49.0",
"@bufbuild/protoc-gen-es": "^2.2.3",
"typescript": "^5.7.3"
},
"dependencies": {
"@bufbuild/protobuf": "^2.2.3"
}
}

7
js/src/index.ts Normal file
View file

@ -0,0 +1,7 @@
export * from "./gen/admin_pb.js";
export * from "./gen/client_pb.js";
export * from "./gen/remoting_pb.js";
export * from "./gen/server_pb.js";
export * from "./gen/user_pb.js";
export * from "./gen/vm_pb.js";
export * from "./gen/vote_pb.js";

15
js/tsconfig.json Normal file
View file

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"esModuleInterop": true,
"lib": [
"ES2020",
"DOM"
],
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"declaration": true
},
}

132
src/admin.proto Normal file
View file

@ -0,0 +1,132 @@
//! Moderation and administration definitions.
edition = "2023";
package collabvm.protocol.admin;
option csharp_namespace = "CollabVM.Protocol.Admin";
import "vote.proto";
enum ControlCommand {
/// Reboot the VM.
REBOOT_VM = 0;
RESET_VM = 1;
/// Pause turns.
PAUSE_TURNS = 2;
UNPAUSE_TURNS = 3;
}
message AdminServerMessage {
uint32 uid = 1;
message StatelessResponse {
bool success = 1;
}
message ExecuteResponse {
string output = 1;
}
message UserInfoResponse {
string ip = 1;
string subject_id = 2;
}
oneof Resp {
/// Used when we do not need to convey state to the calling user.
StatelessResponse stateless_response = 2;
ExecuteResponse execute_response = 3;
UserInfoResponse user_info_response = 4;
}
}
message AdminClientMessage {
/// The uID/serial of this message. This is returned to the user with an AdminResponse.
uint32 uid = 1;
/// Kick a user.
message Kick {
/// The user to kick or ban.
string user = 1;
/// The reason for the kick. Shown to the user.
string reason = 2;
}
/// Mute a user.
message Mute {
/// The user to mute.
string user = 1;
/// The duration of the mute in milliseconds.
uint32 mute_duration = 2;
}
/// Unmute a user.
message Unmute {
/// The user to unmute.
string user = 1;
}
message ExecuteCommand {
string command = 1;
}
message EndTurn {}
message EndUserTurn {
string user = 1;
}
message BypassTurn {}
message InfiniteTurn {}
message ClearTurnQueue {}
message ForceEndVote {
vote.VoteKind vote_id = 1;
/// Choice of vote to force. Optional; a vote can be ended
/// without any choice simply by not sending this field.
bool choice = 2;
}
message Control {
/// The command to run. Later on more inputs probably
ControlCommand command = 1;
}
/// Request information about a user. Currently, their IP address, and Subject ID if they are logged in.
message UserInfo {
string user = 1;
}
oneof Request {
// execute cmds
ExecuteCommand execute_command = 2;
Kick kick = 3;
Mute mute = 4;
// turn-jacking
EndTurn end_turn = 5;
EndUserTurn end_user_turn = 6;
BypassTurn bypass_turn = 7;
InfiniteTurn infinite_turn = 8;
// vote
ForceEndVote force_end_vote = 9;
Control control = 10;
UserInfo user_info = 11;
ClearTurnQueue clear_turn_queue = 12;
Unmute unmute = 13;
}
}

67
src/client.proto Normal file
View file

@ -0,0 +1,67 @@
//! Declares "ClientMessage", the super-message the client sends to the server.
edition = "2023";
package collabvm.protocol;
option csharp_namespace = "CollabVM.Protocol";
import "admin.proto";
import "vote.proto";
import "remoting.proto";
message ClientMessage {
message ClientHello {
/// The protocol version of the client.
uint32 protocol_version_major = 1;
uint32 protocol_version_minor = 2;
// The username requested by the client. The server is not required to honor this request.
string username = 3;
// Desired display format
remoting.DisplayFormat display_format = 4;
}
message TurnRequest {
/// Set to true if the user wants to end their turn early,
/// or exit the turn queue.
bool forfeit_turn = 1;
}
message StartVote {
vote.VoteKind vote_kind = 1;
}
message Vote {
/// The vote to vote for
vote.VoteKind vote_kind = 1;
/// The choice you are voting on. True for yes, false for no.
bool choice = 2;
}
message SendChat {
string message = 1;
}
/// Requests a screenshot.
message RequestScreenshot {}
/// Keepalive message.
message Keepalive {}
oneof Union {
ClientHello hello = 1;
/// A remoting message.
remoting.RemotingClientMessage remoting_message = 2;
admin.AdminClientMessage admin_message = 3;
// general cvm stuff
TurnRequest turn = 4;
SendChat chat = 5;
// vote
StartVote start_vote = 6;
Vote vote = 7;
RequestScreenshot request_screenshot = 8;
Keepalive keepalive = 9;
}
}

145
src/remoting.proto Normal file
View file

@ -0,0 +1,145 @@
//! Remoting. Mostly from CrustTest, modified to be better.
edition = "2023";
package collabvm.protocol.remoting;
option csharp_namespace = "CollabVM.Protocol.Remoting";
enum DisplayFormat {
/// The server will not send any display updates.
NONE = 0;
/// Legacy JPEG rectangles. Slow, bad, and a total bandwidth waste.
/// Assumed to be the default for all clients.
LEGACY_UGLY_BAD_UGLY_JPEG = 1;
/// H.264 video stream.
STREAM_H264 = 2;
// STREAM_HEVC? Not implementing right now
// maybe once we have webtransport...
}
message KeyboardLEDState {
bool scroll_lock = 1;
bool num_lock = 2;
bool caps_lock = 3;
}
/// a remoting client message
message RemotingClientMessage {
message KeyMessage { // stable
sint32 keysym = 1;
bool pressed = 2;
}
message MouseMessage {
uint32 mouse_x = 1;
uint32 mouse_y = 2;
uint32 buttons = 3; // :(
}
/// Used by the client to select a preferred format for the display.
/// Display updates are not sent until a format has been selected; however
/// selecting one is required within 10 seconds, outside of view mode.
message SelectFormat {
DisplayFormat selected_format = 1;
}
oneof OneOf {
KeyMessage key = 1;
MouseMessage mouse = 2;
SelectFormat display_select_format = 3;
bool enable_audio = 4;
}
}
/// A remoting server message
message RemotingServerMessage {
message DisplaySize {
uint32 width = 1;
uint32 height = 2;
}
message DisplaySupportedFormats {
/// Supported formats. The client is free to select a format from this list.
repeated DisplayFormat supported_formats = 1;
}
/* Cursor handling; Currently not used
message Cursor {
message Move {
uint32 cursorX = 1;
uint32 cursorY = 2;
}
message Update {
uint32 cursorW = 1;
uint32 cursorH = 2;
bytes cursorImage = 3; // RGBA8888 image of the cursor, possibly zlib packed
}
oneof Kind {
Move move = 1;
Update update = 2;
}
}
*/
message Update_LegacyBadUglyRect {
message Rect {
uint32 x = 1;
uint32 y = 2;
bytes data = 3; // JPEG encoded rect data
}
repeated Rect rects = 1;
}
message Update_Stream {
enum FrameType {
/// A key frame.
KEY = 0;
/// A inter (P in h264) frame.
INTER = 1;
}
FrameType type = 1;
bytes data = 2; // Loosely wrapped data stream; based on DisplayFormat selected by the client.
}
message Audio {
message Format {
uint32 sampleRate = 1;
uint32 nrChannels = 2;
}
message Data {
bytes data = 1; // Opus data stream
}
}
oneof OneOf {
DisplaySize display_size = 1;
/// Legacy JPEG based rectangles. Slow and bad
Update_LegacyBadUglyRect update_legacy_jpeg = 2;
/// Stream-based update
Update_Stream update_stream = 3;
// 4..5 resvd. for Cursor
// Cursor.Move cursor_move = 4;
// Cursor.Update cursor_update = 5;
// 5..10 reserved for audio messages
//Audio.Format audioFormat = 5;
Audio.Data audioData = 6;
// 10 ... reserved for future expansion
KeyboardLEDState keyboard_led_state = 10;
}
}

96
src/server.proto Normal file
View file

@ -0,0 +1,96 @@
//! Declares "ServerMessage", the super-message the server sends to clients.
edition = "2023";
package collabvm.protocol;
option csharp_namespace = "CollabVM.Protocol";
import "user.proto";
import "admin.proto";
import "vm.proto";
import "vote.proto";
import "remoting.proto";
message ServerMessage {
message ServerHello {
/// The protocol version.
uint32 protocol_version_major = 1;
uint32 protocol_version_minor = 2;
// server version?
}
message ServerInit {
// The client's username
string username = 1;
user.UserList user_list = 2;
vm.ChatHistory chat_history = 3;
string motd = 4;
uint32 max_chat_length = 5;
uint32 rank = 6;
remoting.KeyboardLEDState keyboard_led_state = 7;
}
message Disconnect {
/// Optional reason.
string reason = 1;
}
message ScreenshotResponse {
/// Screenshot data as WebP. (This is the same format regardless of display format.)
bytes screenshot_data = 1;
}
/// Keepalive message.
message Keepalive {}
/// Rate limited
message RateLimited {
/// The action that was rate limited.
string action = 1;
/// Milliseconds until the client may perform the action again.
uint32 limit_time = 2;
}
message Muted {
/// Duration of the mute in milliseconds.
uint32 duration = 1;
}
message Unmuted {}
oneof Union {
ServerHello hello = 1;
ServerInit init = 2;
remoting.RemotingServerMessage remoting_message = 3;
admin.AdminServerMessage admin_message = 4;
Disconnect disconnect = 5;
// user stuff
// user.UserList user_list = 6;
user.UserJoined add_user = 7;
user.UserLeft remove_user = 8;
// VM turn
vm.TurnQueue turn_queue = 10;
// VM chat
vm.ChatMessage chat_message = 11;
vm.ChatHistory chat_history = 12;
vm.SystemChatMessage system_chat_message = 13;
// vote messages
vote.VoteInformation vote_information = 14;
vote.VoteTally vote_tally = 15;
vote.VoteEnded vote_ended = 16;
vote.VoteStartFailure vote_start_failure = 17;
ScreenshotResponse screenshot_response = 18;
Keepalive keepalive = 19;
RateLimited rate_limited = 20;
Muted muted = 21;
Unmuted unmuted = 22;
}
}

29
src/user.proto Normal file
View file

@ -0,0 +1,29 @@
//! User protos.
edition = "2023";
package collabvm.protocol.user;
option csharp_namespace = "CollabVM.Protocol.User";
// Used anywhere we want to reference a user.
message UserReference {
// Currently used always.
string username = 1;
/// The user's rank
uint32 rank = 2;
/// The user's ISO 3166-1 country code.
string country = 3;
}
/// The user list.
message UserList {
repeated UserReference users = 1;
}
message UserJoined {
UserReference user = 1;
}
message UserLeft {
string username = 1;
}

50
src/vm.proto Normal file
View file

@ -0,0 +1,50 @@
//! VM-specific protocol stuff.
edition = "2023";
package collabvm.protocol.vm;
option csharp_namespace = "CollabVM.Protocol.VM";
/// A chat message.
message ChatMessage {
string username = 1;
string message_content = 2;
}
/// A chat message from the server itself.
message SystemChatMessage {
string message_content = 1;
}
message ChatHistory {
repeated ChatMessage message_history = 1;
}
enum TurnState {
/// The turn queue is active.
ACTIVE = 0;
/// The turn queue has been paused.
PAUSED = 1;
/// Paused because there is only one user in the queue.
/// If another user takes a turn, the queue will re-start
/// automatically and switch back to ACTIVE.
PAUSED_SOLE_USER = 2;
/// Paused because an admin is taking an indefinite turn.
PAUSED_ADMIN = 3;
}
message TurnQueue {
TurnState turn_state = 1;
/// The queue of users in the turn queue. Index 0 is the user who is currently taking their turn.
repeated string queue = 2;
/// Time in MS for when the next turn will be
uint32 next_turn_time_ms = 3;
/// Time in milliseconds for when your position
uint32 turn_queue_time_ms = 4;
}

70
src/vote.proto Normal file
View file

@ -0,0 +1,70 @@
edition = "2023";
package collabvm.protocol.vote;
option csharp_namespace = "CollabVM.Protocol.Vote";
/// The kind of a vote.
enum VoteKind {
/// A vote to reset the VM.
VOTE_RESET = 0;
/// A user-defined (in plugins or such) vote.
/// This is reasonably far away from any internally defined votes
/// so that we don't accidentally run into it. Any value from 1000 onwards
/// is treated as a user-defined vote.
VOTE_USER_START = 1000;
}
enum VoteStartFailure {
/// An ongoing vote is in progress.
VOTE_IN_PROGRESS = 0;
/// You are muted, and thus cannot start a vote.
CURRENTLY_MUTED = 1;
/// A vote was held too recently.
VOTE_COOLDOWN = 2;
/// This VM does not support voting.
VM_VOTES_DISABLED = 3;
}
message Tally {
repeated string yesVotes = 1;
repeated string noVotes = 2;
}
/// Sent only once to all connected users on the start of a vote (and additionally new users) to indicate the possible choices for a ongoing vote.
/// Multiple votes are currently not allowed; but could be at some point.
message VoteInformation {
/// The kind of the vote.
VoteKind kind = 1;
/// The time until the vote ends.
uint32 time_left = 2;
/// The user who started the vote.
string started_by = 3;
/// Current tally for the vote.
Tally tally = 4;
}
/// Sent to update the current tally
message VoteTally {
/// The kind of the vote.
VoteKind kind = 1;
/// Current tally for the vote.
Tally tally = 2;
}
/// Sent to indicate a vote has ended
message VoteEnded {
/// The kind of the vote.
VoteKind kind = 1;
/// The choice which won. True for yes, false for no.
bool choice = 2;
/// Final tally
Tally tally = 3;
}