commit 45ee7f409155f95ff3a8d3d3c5b911bb65724d05 Author: Elijah R Date: Fri Jan 10 14:14:41 2025 -0500 Init diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..da4e246 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f83b0d2 --- /dev/null +++ b/.gitignore @@ -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.* diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d0ac05 --- /dev/null +++ b/README.md @@ -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. diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..e05aec5 --- /dev/null +++ b/SPEC.md @@ -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. diff --git a/check b/check new file mode 100644 index 0000000..f310ab4 --- /dev/null +++ b/check @@ -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 diff --git a/csharp/CollabVM.Protocol.csproj b/csharp/CollabVM.Protocol.csproj new file mode 100644 index 0000000..68f870c --- /dev/null +++ b/csharp/CollabVM.Protocol.csproj @@ -0,0 +1,28 @@ + + + CollabVM.Protocol + 0.1.0 + Computernewb + Protocol buffers for CollabVM 3.0 + MIT + https://git.computernewb.com/collabvm/collabvm3 + README.md + true + net8.0 + enable + enable + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/csharp/README.md b/csharp/README.md new file mode 100644 index 0000000..9d8bcee --- /dev/null +++ b/csharp/README.md @@ -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. diff --git a/js/.yarnrc.yml b/js/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/js/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/js/README.md b/js/README.md new file mode 100644 index 0000000..5afb83b --- /dev/null +++ b/js/README.md @@ -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. diff --git a/js/buf.gen.yaml b/js/buf.gen.yaml new file mode 100644 index 0000000..dca1290 --- /dev/null +++ b/js/buf.gen.yaml @@ -0,0 +1,8 @@ +version: v2 +clean: true +inputs: + - directory: ../src +plugins: + - local: protoc-gen-es + out: src/gen + opt: target=ts diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..f45c80f --- /dev/null +++ b/js/package.json @@ -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" + } +} diff --git a/js/src/index.ts b/js/src/index.ts new file mode 100644 index 0000000..81d9d8c --- /dev/null +++ b/js/src/index.ts @@ -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"; diff --git a/js/tsconfig.json b/js/tsconfig.json new file mode 100644 index 0000000..23fd656 --- /dev/null +++ b/js/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "esModuleInterop": true, + "lib": [ + "ES2020", + "DOM" + ], + "moduleResolution": "node", + "rootDir": "./src", + "outDir": "./dist", + "declaration": true + }, +} diff --git a/src/admin.proto b/src/admin.proto new file mode 100644 index 0000000..b4084d0 --- /dev/null +++ b/src/admin.proto @@ -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; + + } +} diff --git a/src/client.proto b/src/client.proto new file mode 100644 index 0000000..d00097c --- /dev/null +++ b/src/client.proto @@ -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; + } +} diff --git a/src/remoting.proto b/src/remoting.proto new file mode 100644 index 0000000..3bdad67 --- /dev/null +++ b/src/remoting.proto @@ -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; + } +} diff --git a/src/server.proto b/src/server.proto new file mode 100644 index 0000000..ac3fb3b --- /dev/null +++ b/src/server.proto @@ -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; + } +} diff --git a/src/user.proto b/src/user.proto new file mode 100644 index 0000000..7d5ff71 --- /dev/null +++ b/src/user.proto @@ -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; +} diff --git a/src/vm.proto b/src/vm.proto new file mode 100644 index 0000000..0fe48f2 --- /dev/null +++ b/src/vm.proto @@ -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; +} diff --git a/src/vote.proto b/src/vote.proto new file mode 100644 index 0000000..33166c7 --- /dev/null +++ b/src/vote.proto @@ -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; +}