From 443712cb220c6a994b571e76bebe18f835488236 Mon Sep 17 00:00:00 2001 From: Jonas Herzig Date: Sun, 3 Feb 2019 23:34:09 +0100 Subject: [PATCH] Replace lots of generic code with dedicated `mumble-protocol` crate --- Cargo.lock | 17 +- Cargo.toml | 5 +- build.rs | 12 - protos/Mumble.proto | 640 ------------------------------------------ src/connection.rs | 171 +++++------ src/error.rs | 7 - src/main.rs | 100 ++----- src/mumble.rs | 103 ------- src/protos/.gitignore | 1 - src/utils.rs | 84 ------ 10 files changed, 112 insertions(+), 1028 deletions(-) delete mode 100644 build.rs delete mode 100644 protos/Mumble.proto delete mode 100644 src/mumble.rs delete mode 100644 src/protos/.gitignore diff --git a/Cargo.lock b/Cargo.lock index e96a0e5..8d6e51c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -455,6 +455,19 @@ dependencies = [ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mumble-protocol" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "protobuf-codegen-pure 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mumble-web-proxy" version = "0.1.0" @@ -464,10 +477,9 @@ dependencies = [ "bytes 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", "libnice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mumble-protocol 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "openssl 0.10.15 (registry+https://github.com/rust-lang/crates.io-index)", - "protobuf 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "protobuf-codegen-pure 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rtp 0.1.0 (git+https://github.com/johni0702/rtp?rev=ee8be93)", "tokio 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1463,6 +1475,7 @@ dependencies = [ "checksum mio 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mumble-protocol 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "978073c4ba6935bd08bf79ffe1f9914a5b4de127fa25cd56749cc511c3ca5d72" "checksum native-tls 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff8e08de0070bbf4c31f452ea2a70db092f36f6f2e4d897adf5674477d488fb2" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" diff --git a/Cargo.toml b/Cargo.toml index 60da5b1..54bc984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,9 +3,6 @@ name = "mumble-web-proxy" version = "0.1.0" authors = ["Jonas Herzig "] -[build-dependencies] -protobuf-codegen-pure = "2.0" - [dependencies] argparse = "0.2.2" bytes = "0.4" @@ -16,7 +13,7 @@ tokio-core = "0.1" tokio-codec = "0.1" tokio-tls = "0.2" native-tls = "0.2" -protobuf = "2.0" +mumble-protocol = { version = "0.1", features = ["webrtc-extensions"] } websocket = "0.21.1" rtp = { git = "https://github.com/johni0702/rtp", rev = "ee8be93", features = ["openssl", "tokio"] } libnice = "0.1" diff --git a/build.rs b/build.rs deleted file mode 100644 index f82a5e2..0000000 --- a/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -extern crate protobuf_codegen_pure; - -fn main() { - protobuf_codegen_pure::run(protobuf_codegen_pure::Args { - out_dir: "src/protos", - input: &["protos/Mumble.proto"], - includes: &["protos"], - customize: protobuf_codegen_pure::Customize { - ..Default::default() - }, - }).expect("protoc"); -} diff --git a/protos/Mumble.proto b/protos/Mumble.proto deleted file mode 100644 index 0f19a97..0000000 --- a/protos/Mumble.proto +++ /dev/null @@ -1,640 +0,0 @@ -// Copyright 2005-2018 The Mumble Developers. All rights reserved. -// Use of this source code is governed by a BSD-style license -// that can be found in the LICENSE file at the root of the -// Mumble source tree or at . - -syntax = "proto2"; - -package MumbleProto; - -option optimize_for = SPEED; - -message Version { - // 2-byte Major, 1-byte Minor and 1-byte Patch version number. - optional uint32 version = 1; - // Client release name. - optional string release = 2; - // Client OS name. - optional string os = 3; - // Client OS version. - optional string os_version = 4; -} - -// Not used. Not even for tunneling UDP through TCP. -message UDPTunnel { - // Not used. - required bytes packet = 1; -} - -// Used by the client to send the authentication credentials to the server. -message Authenticate { - // UTF-8 encoded username. - optional string username = 1; - // Server or user password. - optional string password = 2; - // Additional access tokens for server ACL groups. - repeated string tokens = 3; - // A list of CELT bitstream version constants supported by the client. - repeated int32 celt_versions = 4; - optional bool opus = 5 [default = false]; - // Whether to use WebRTC instead of native UDP packets. - optional bool webrtc = 6 [default = false]; -} - -// Sent by the client to notify the server that the client is still alive. -// Server must reply to the packet with the same timestamp and its own -// good/late/lost/resync numbers. None of the fields is strictly required. -message Ping { - // Client timestamp. Server should not attempt to decode. - optional uint64 timestamp = 1; - // The amount of good packets received. - optional uint32 good = 2; - // The amount of late packets received. - optional uint32 late = 3; - // The amount of packets never received. - optional uint32 lost = 4; - // The amount of nonce resyncs. - optional uint32 resync = 5; - // The total amount of UDP packets received. - optional uint32 udp_packets = 6; - // The total amount of TCP packets received. - optional uint32 tcp_packets = 7; - // UDP ping average. - optional float udp_ping_avg = 8; - // UDP ping variance. - optional float udp_ping_var = 9; - // TCP ping average. - optional float tcp_ping_avg = 10; - // TCP ping variance. - optional float tcp_ping_var = 11; -} - -// Sent by the server when it rejects the user connection. -message Reject { - enum RejectType { - // The rejection reason is unknown (details should be available - // in Reject.reason). - None = 0; - // The client attempted to connect with an incompatible version. - WrongVersion = 1; - // The user name supplied by the client was invalid. - InvalidUsername = 2; - // The client attempted to authenticate as a user with a password but it - // was wrong. - WrongUserPW = 3; - // The client attempted to connect to a passworded server but the password - // was wrong. - WrongServerPW = 4; - // Supplied username is already in use. - UsernameInUse = 5; - // Server is currently full and cannot accept more users. - ServerFull = 6; - // The user did not provide a certificate but one is required. - NoCertificate = 7; - AuthenticatorFail = 8; - } - // Rejection type. - optional RejectType type = 1; - // Human readable rejection reason. - optional string reason = 2; -} - -// ServerSync message is sent by the server when it has authenticated the user -// and finished synchronizing the server state. -message ServerSync { - // The session of the current user. - optional uint32 session = 1; - // Maximum bandwidth that the user should use. - optional uint32 max_bandwidth = 2; - // Server welcome text. - optional string welcome_text = 3; - // Current user permissions in the root channel. - optional uint64 permissions = 4; -} - -// Sent by the client when it wants a channel removed. Sent by the server when -// a channel has been removed and clients should be notified. -message ChannelRemove { - required uint32 channel_id = 1; -} - -// Used to communicate channel properties between the client and the server. -// Sent by the server during the login process or when channel properties are -// updated. Client may use this message to update said channel properties. -message ChannelState { - // Unique ID for the channel within the server. - optional uint32 channel_id = 1; - // channel_id of the parent channel. - optional uint32 parent = 2; - // UTF-8 encoded channel name. - optional string name = 3; - // A collection of channel id values of the linked channels. Absent during - // the first channel listing. - repeated uint32 links = 4; - // UTF-8 encoded channel description. Only if the description is less than - // 128 bytes - optional string description = 5; - // A collection of channel_id values that should be added to links. - repeated uint32 links_add = 6; - // A collection of channel_id values that should be removed from links. - repeated uint32 links_remove = 7; - // True if the channel is temporary. - optional bool temporary = 8 [default = false]; - // Position weight to tweak the channel position in the channel list. - optional int32 position = 9 [default = 0]; - // SHA1 hash of the description if the description is 128 bytes or more. - optional bytes description_hash = 10; - // Maximum number of users allowed in the channel. If this value is zero, - // the maximum number of users allowed in the channel is given by the - // server's "usersperchannel" setting. - optional uint32 max_users = 11; -} - -// Used to communicate user leaving or being kicked. May be sent by the client -// when it attempts to kick a user. Sent by the server when it informs the -// clients that a user is not present anymore. -message UserRemove { - // The user who is being kicked, identified by their session, not present - // when no one is being kicked. - required uint32 session = 1; - // The user who initiated the removal. Either the user who performs the kick - // or the user who is currently leaving. - optional uint32 actor = 2; - // Reason for the kick, stored as the ban reason if the user is banned. - optional string reason = 3; - // True if the kick should result in a ban. - optional bool ban = 4; -} - -// Sent by the server when it communicates new and changed users to client. -// First seen during login procedure. May be sent by the client when it wishes -// to alter its state. -message UserState { - // Unique user session ID of the user whose state this is, may change on - // reconnect. - optional uint32 session = 1; - // The session of the user who is updating this user. - optional uint32 actor = 2; - // User name, UTF-8 encoded. - optional string name = 3; - // Registered user ID if the user is registered. - optional uint32 user_id = 4; - // Channel on which the user is. - optional uint32 channel_id = 5; - // True if the user is muted by admin. - optional bool mute = 6; - // True if the user is deafened by admin. - optional bool deaf = 7; - // True if the user has been suppressed from talking by a reason other than - // being muted. - optional bool suppress = 8; - // True if the user has muted self. - optional bool self_mute = 9; - // True if the user has deafened self. - optional bool self_deaf = 10; - // User image if it is less than 128 bytes. - optional bytes texture = 11; - // The positional audio plugin identifier. - // Positional audio information is only sent to users who share - // identical plugin contexts. - // - // This value is not trasmitted to clients. - optional bytes plugin_context = 12; - // The user's plugin-specific identity. - // This value is not transmitted to clients. - optional string plugin_identity = 13; - // User comment if it is less than 128 bytes. - optional string comment = 14; - // The hash of the user certificate. - optional string hash = 15; - // SHA1 hash of the user comment if it 128 bytes or more. - optional bytes comment_hash = 16; - // SHA1 hash of the user picture if it 128 bytes or more. - optional bytes texture_hash = 17; - // True if the user is a priority speaker. - optional bool priority_speaker = 18; - // True if the user is currently recording. - optional bool recording = 19; - // Unique SSRC from which the user's audio is sent when using WebRTC. - // - // As opposed to `session`, this value must not be monotonically increasing - // but must instead be re-used where possible (i.e. after a user disconnects). - // The WebRTC implementation must keep track of all SSRCs ever used for the - // entirety of the WebRTC session, so that number needs to be kept low. - optional uint32 ssrc = 20; -} - -// Relays information on the bans. The client may send the BanList message to -// either modify the list of bans or query them from the server. The server -// sends this list only after a client queries for it. -message BanList { - message BanEntry { - // Banned IP address. - required bytes address = 1; - // The length of the subnet mask for the ban. - required uint32 mask = 2; - // User name for identification purposes (does not affect the ban). - optional string name = 3; - // The certificate hash of the banned user. - optional string hash = 4; - // Reason for the ban (does not affect the ban). - optional string reason = 5; - // Ban start time. - optional string start = 6; - // Ban duration in seconds. - optional uint32 duration = 7; - } - // List of ban entries currently in place. - repeated BanEntry bans = 1; - // True if the server should return the list, false if it should replace old - // ban list with the one provided. - optional bool query = 2 [default = false]; -} - -// Used to send and broadcast text messages. -message TextMessage { - // The message sender, identified by its session. - optional uint32 actor = 1; - // Target users for the message, identified by their session. - repeated uint32 session = 2; - // The channels to which the message is sent, identified by their - // channel_ids. - repeated uint32 channel_id = 3; - // The root channels when sending message recursively to several channels, - // identified by their channel_ids. - repeated uint32 tree_id = 4; - // The UTF-8 encoded message. May be HTML if the server allows. - required string message = 5; -} - -message PermissionDenied { - enum DenyType { - // Operation denied for other reason, see reason field. - Text = 0; - // Permissions were denied. - Permission = 1; - // Cannot modify SuperUser. - SuperUser = 2; - // Invalid channel name. - ChannelName = 3; - // Text message too long. - TextTooLong = 4; - // The flux capacitor was spelled wrong. - H9K = 5; - // Operation not permitted in temporary channel. - TemporaryChannel = 6; - // Operation requires certificate. - MissingCertificate = 7; - // Invalid username. - UserName = 8; - // Channel is full. - ChannelFull = 9; - // Channels are nested too deply. - NestingLimit = 10; - // Maximum channel count reached. - ChannelCountLimit = 11; - } - // The denied permission when type is Permission. - optional uint32 permission = 1; - // channel_id for the channel where the permission was denied when type is - // Permission. - optional uint32 channel_id = 2; - // The user who was denied permissions, identified by session. - optional uint32 session = 3; - // Textual reason for the denial. - optional string reason = 4; - // Type of the denial. - optional DenyType type = 5; - // The name that is invalid when type is UserName. - optional string name = 6; -} - -message ACL { - message ChanGroup { - // Name of the channel group, UTF-8 encoded. - required string name = 1; - // True if the group has been inherited from the parent (Read only). - optional bool inherited = 2 [default = true]; - // True if the group members are inherited. - optional bool inherit = 3 [default = true]; - // True if the group can be inherited by sub channels. - optional bool inheritable = 4 [default = true]; - // Users explicitly included in this group, identified by user_id. - repeated uint32 add = 5; - // Users explicitly removed from this group in this channel if the group - // has been inherited, identified by user_id. - repeated uint32 remove = 6; - // Users inherited, identified by user_id. - repeated uint32 inherited_members = 7; - } - message ChanACL { - // True if this ACL applies to the current channel. - optional bool apply_here = 1 [default = true]; - // True if this ACL applies to the sub channels. - optional bool apply_subs = 2 [default = true]; - // True if the ACL has been inherited from the parent. - optional bool inherited = 3 [default = true]; - // ID of the user that is affected by this ACL. - optional uint32 user_id = 4; - // ID of the group that is affected by this ACL. - optional string group = 5; - // Bit flag field of the permissions granted by this ACL. - optional uint32 grant = 6; - // Bit flag field of the permissions denied by this ACL. - optional uint32 deny = 7; - } - // Channel ID of the channel this message affects. - required uint32 channel_id = 1; - // True if the channel inherits its parent's ACLs. - optional bool inherit_acls = 2 [default = true]; - // User group specifications. - repeated ChanGroup groups = 3; - // ACL specifications. - repeated ChanACL acls = 4; - // True if the message is a query for ACLs instead of setting them. - optional bool query = 5 [default = false]; -} - -// Client may use this message to refresh its registered user information. The -// client should fill the IDs or Names of the users it wants to refresh. The -// server fills the missing parts and sends the message back. -message QueryUsers { - // user_ids. - repeated uint32 ids = 1; - // User names in the same order as ids. - repeated string names = 2; -} - -// Used to initialize and resync the UDP encryption. Either side may request a -// resync by sending the message without any values filled. The resync is -// performed by sending the message with only the client or server nonce -// filled. -message CryptSetup { - // Encryption key. - optional bytes key = 1; - // Client nonce. - optional bytes client_nonce = 2; - // Server nonce. - optional bytes server_nonce = 3; -} - -message ContextActionModify { - enum Context { - // Action is applicable to the server. - Server = 0x01; - // Action can target a Channel. - Channel = 0x02; - // Action can target a User. - User = 0x04; - } - enum Operation { - Add = 0; - Remove = 1; - } - // The action name. - required string action = 1; - // The display name of the action. - optional string text = 2; - // Context bit flags defining where the action should be displayed. - optional uint32 context = 3; - optional Operation operation = 4; -} - -// Sent by the client when it wants to initiate a Context action. -message ContextAction { - // The target User for the action, identified by session. - optional uint32 session = 1; - // The target Channel for the action, identified by channel_id. - optional uint32 channel_id = 2; - // The action that should be executed. - required string action = 3; -} - -// Lists the registered users. -message UserList { - message User { - // Registered user ID. - required uint32 user_id = 1; - // Registered user name. - optional string name = 2; - optional string last_seen = 3; - optional uint32 last_channel = 4; - } - // A list of registered users. - repeated User users = 1; -} - -// Sent by the client when it wants to register or clear whisper targets. -// -// Note: The first available target ID is 1 as 0 is reserved for normal -// talking. Maximum target ID is 30. -message VoiceTarget { - message Target { - // Users that are included as targets. - repeated uint32 session = 1; - // Channel that is included as a target. - optional uint32 channel_id = 2; - // ACL group that is included as a target. - optional string group = 3; - // True if the voice should follow links from the specified channel. - optional bool links = 4 [default = false]; - // True if the voice should also be sent to children of the specific - // channel. - optional bool children = 5 [default = false]; - } - // Voice target ID. - optional uint32 id = 1; - // The receivers that this voice target includes. - repeated Target targets = 2; -} - -// Sent by the client when it wants permissions for a certain channel. Sent by -// the server when it replies to the query or wants the user to resync all -// channel permissions. -message PermissionQuery { - // channel_id of the channel for which the permissions are queried. - optional uint32 channel_id = 1; - // Channel permissions. - optional uint32 permissions = 2; - // True if the client should drop its current permission information for all - // channels. - optional bool flush = 3 [default = false]; -} - -// Sent by the server to notify the users of the version of the CELT codec they -// should use. This may change during the connection when new users join. -message CodecVersion { - // The version of the CELT Alpha codec. - required int32 alpha = 1; - // The version of the CELT Beta codec. - required int32 beta = 2; - // True if the user should prefer Alpha over Beta. - required bool prefer_alpha = 3 [default = true]; - optional bool opus = 4 [default = false]; -} - -// Used to communicate user stats between the server and clients. -message UserStats { - message Stats { - // The amount of good packets received. - optional uint32 good = 1; - // The amount of late packets received. - optional uint32 late = 2; - // The amount of packets never received. - optional uint32 lost = 3; - // The amount of nonce resyncs. - optional uint32 resync = 4; - } - - // User whose stats these are. - optional uint32 session = 1; - // True if the message contains only mutable stats (packets, ping). - optional bool stats_only = 2 [default = false]; - // Full user certificate chain of the user certificate in DER format. - repeated bytes certificates = 3; - // Packet statistics for packets received from the client. - optional Stats from_client = 4; - // Packet statistics for packets sent by the server. - optional Stats from_server = 5; - - // Amount of UDP packets sent. - optional uint32 udp_packets = 6; - // Amount of TCP packets sent. - optional uint32 tcp_packets = 7; - // UDP ping average. - optional float udp_ping_avg = 8; - // UDP ping variance. - optional float udp_ping_var = 9; - // TCP ping average. - optional float tcp_ping_avg = 10; - // TCP ping variance. - optional float tcp_ping_var = 11; - - // Client version. - optional Version version = 12; - // A list of CELT bitstream version constants supported by the client of this - // user. - repeated int32 celt_versions = 13; - // Client IP address. - optional bytes address = 14; - // Bandwith used by this client. - optional uint32 bandwidth = 15; - // Connection duration. - optional uint32 onlinesecs = 16; - // Duration since last activity. - optional uint32 idlesecs = 17; - // True if the user has a strong certificate. - optional bool strong_certificate = 18 [default = false]; - optional bool opus = 19 [default = false]; -} - -// Used by the client to request binary data from the server. By default large -// comments or textures are not sent within standard messages but instead the -// hash is. If the client does not recognize the hash it may request the -// resource when it needs it. The client does so by sending a RequestBlob -// message with the correct fields filled with the user sessions or channel_ids -// it wants to receive. The server replies to this by sending a new -// UserState/ChannelState message with the resources filled even if they would -// normally be transmitted as hashes. -message RequestBlob { - // sessions of the requested UserState textures. - repeated uint32 session_texture = 1; - // sessions of the requested UserState comments. - repeated uint32 session_comment = 2; - // channel_ids of the requested ChannelState descriptions. - repeated uint32 channel_description = 3; -} - -// Sent by the server when it informs the clients on server configuration -// details. -message ServerConfig { - // The maximum bandwidth the clients should use. - optional uint32 max_bandwidth = 1; - // Server welcome text. - optional string welcome_text = 2; - // True if the server allows HTML. - optional bool allow_html = 3; - // Maximum text message length. - optional uint32 message_length = 4; - // Maximum image message length. - optional uint32 image_message_length = 5; - // The maximum number of users allowed on the server. - optional uint32 max_users = 6; -} - -// Sent by the server to inform the clients of suggested client configuration -// specified by the server administrator. -message SuggestConfig { - // Suggested client version. - optional uint32 version = 1; - // True if the administrator suggests positional audio to be used on this - // server. - optional bool positional = 2; - // True if the administrator suggests push to talk to be used on this server. - optional bool push_to_talk = 3; -} - -// Used to exchange WebRTC session details between client and server. -// The client may only send these if it received one from the server beforehand. -// The server may only send these if the client has indicated its support in -// the `webrtc` field of the `Authenticate` message and, if the server chooses to -// support WebRTC for the client, it MUST send the message right after receiving -// the `Authenticate` message, before any other message (especially before any -// `UserState`, `TalkingState` or `IceCandidate` messages). -// -// No actual SDP is exchanged, it is instead built separately by both, the client -// and the server, on every user join/part. -// All audio is bundled via "a=group:BUNDLE audio$ssrc ..." over a single -// DTLS-SRTP over ICE connection. -// The client should be in controlling mode for ICE, the server should act as -// the server/passive for DTLS and as the offerer for WebRTC. -// Note: -// WebRTC demands the offerer to offer both active and passive modes "actpass". -// The client should modify the browser's answer to use the correct one before applying it. -// -// Global parameters (ice_pwd, ice_ufrag and dtls_fingerprint) are sent via an initial -// `WebRTC` packet. Transmitting further `WebRTC` packets should trigger ICE-restarts. -// -// There needs to be one unidirectional media section per connected user. The -// SSRC for each media section is sent with the initial `UserState` message. -// -// SDP for each media segment: -// m=audio 0 UDP/TLS/RTP/SAVPF 97 -// c=IN IP4 0.0.0.0 -// a=fingerprint:sha-256 $dtls_fingerprint -// a=ice-pwd:$ice_pwd -// a=ice-ufrag:$ice_ufrag -// a=rtpmap:97 OPUS/48000/2 -// a=rtcp-mux -// a=setup:actpass -// a=bundle-only -// a=ssrc:$ssrc cname:audio$ssrc -// -// There also needs to be one unidirectional media section which describes -// the stream containing the client's voice. This section's SDP is similar -// to the above one except in the opposite direction (e.g. different ice and -// dtls credentials) und no specific SSRC (considering how the server chooses -// SSRCs for its users, 0xffffffff should be a safe bet to prevent collisions). -message WebRTC { - optional string ice_pwd = 1; - optional string ice_ufrag = 2; - optional string dtls_fingerprint = 3; -} - -// Used to exchange ICE candidates. -message IceCandidate { - required string content = 1; -} - -// Indicates whether a user is currently talking (or whispering or shouting). -// Also used to set own talking state. -// Only sent when WebRTC is used, otherwise this information can be deduced -// from the UDP packets. -message TalkingState { - // User whose state this is - optional uint32 session = 1; - // Target, as used in UDP packets: - // Clientbound: 0 is normal talking, 1 is shout, 2 is whisper, 31 is server loopback - // Serverbound: 0 is normal talking, 1-30 as per VoiceTarget, 31 is server loopback - optional uint32 target = 2; -} diff --git a/src/connection.rs b/src/connection.rs index c0a5bf0..257483b 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -1,13 +1,18 @@ use futures::stream; use futures::{Future, Sink, Stream}; use libnice::ice; +use mumble_protocol::control::msgs; +use mumble_protocol::control::ControlPacket; +use mumble_protocol::voice::VoicePacket; +use mumble_protocol::voice::VoicePacketPayload; +use mumble_protocol::Clientbound; +use mumble_protocol::Serverbound; use openssl::asn1::Asn1Time; use openssl::hash::MessageDigest; use openssl::pkey::{PKey, Private}; use openssl::rsa::Rsa; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslMethod}; use openssl::x509::X509; -use protobuf::Message; use rtp::rfc3550::{ RtcpCompoundPacket, RtcpPacket, RtcpPacketReader, RtcpPacketWriter, RtpFixedHeader, RtpPacket, RtpPacketReader, RtpPacketWriter, @@ -24,10 +29,7 @@ use tokio::timer::Delay; use webrtc_sdp::attribute_type::SdpAttribute; use error::Error; -use mumble; -use mumble::MumbleFrame; -use protos::Mumble; -use utils::{read_varint, write_varint32, EitherS}; +use utils::EitherS; type SessionId = u32; @@ -54,12 +56,9 @@ impl User { self.start_voice_seq_num = 0; self.highest_voice_seq_num = 0; - let mut msg = Mumble::TalkingState::new(); + let mut msg = msgs::TalkingState::new(); msg.set_session(self.session); - EitherS::A(stream::once(Ok(Frame::Client(MumbleFrame { - id: mumble::MSG_TALKING_STATE, - bytes: msg.write_to_bytes().unwrap().into(), - })))) + EitherS::A(stream::once(Ok(Frame::Client(msg.into())))) } else { EitherS::B(stream::empty()) } @@ -74,24 +73,21 @@ impl User { } else { self.active = true; - let mut msg = Mumble::TalkingState::new(); + let mut msg = msgs::TalkingState::new(); msg.set_session(self.session); msg.set_target(target.into()); - EitherS::B(stream::once(Ok(Frame::Client(MumbleFrame { - id: mumble::MSG_TALKING_STATE, - bytes: msg.write_to_bytes().unwrap().into(), - })))) + EitherS::B(stream::once(Ok(Frame::Client(msg.into())))) } } } pub struct Connection { - inbound_client: Box>, - outbound_client: Box>, - inbound_server: Box>, - outbound_server: Box>, - next_clientbound_frame: Option, - next_serverbound_frame: Option, + inbound_client: Box, Error = Error>>, + outbound_client: Box, SinkError = Error>>, + inbound_server: Box, Error = Error>>, + outbound_server: Box, SinkError = Error>>, + next_clientbound_frame: Option>, + next_serverbound_frame: Option>, next_rtp_frame: Option>, stream_to_be_sent: Option>>, @@ -119,10 +115,10 @@ impl Connection { server_stream: SSt, ) -> Self where - CSi: Sink + 'static, - CSt: Stream + 'static, - SSi: Sink + 'static, - SSt: Stream + 'static, + CSi: Sink, SinkError = Error> + 'static, + CSt: Stream, Error = Error> + 'static, + SSi: Sink, SinkError = Error> + 'static, + SSt: Stream, Error = Error> + 'static, { let rsa = Rsa::generate(2048).unwrap(); let key = PKey::from_rsa(rsa).unwrap(); @@ -205,7 +201,7 @@ impl Connection { let component = stream.take_components().pop().expect("one component"); // Send WebRTC details to the client - let mut msg = Mumble::WebRTC::new(); + let mut msg = msgs::WebRTC::new(); msg.set_dtls_fingerprint( self.dtls_cert .digest(MessageDigest::sha256()) @@ -217,10 +213,6 @@ impl Connection { ); msg.set_ice_pwd(stream.get_local_pwd().to_owned()); msg.set_ice_ufrag(stream.get_local_ufrag().to_owned()); - let webrtc_msg = MumbleFrame { - id: mumble::MSG_WEBRTC, - bytes: msg.write_to_bytes().unwrap().into(), - }; // Store ice agent and stream for later use self.ice = Some((agent, stream)); @@ -232,37 +224,23 @@ impl Connection { // FIXME: verify remote fingerprint self.dtls_srtp_future = Some(DtlsSrtp::handshake(component, acceptor)); - stream::once(Ok(Frame::Client(webrtc_msg))) + stream::once(Ok(Frame::Client(msg.into()))) } - fn handle_voice_packet(&mut self, buf: &[u8]) -> impl Stream { - let (header, buf) = match buf.split_first() { - Some(t) => t, - None => return EitherS::B(stream::empty()), + fn handle_voice_packet( + &mut self, + packet: VoicePacket, + ) -> impl Stream { + let (target, session_id, seq_num, opus_data, last_bit) = match packet { + VoicePacket::Audio { + target, + session_id, + seq_num, + payload: VoicePacketPayload::Opus(data, last_bit), + .. + } => (target, session_id, seq_num, data, last_bit), + _ => return EitherS::B(stream::empty()), }; - if (header >> 5_u8) != 4_u8 { - // only opus - return EitherS::B(stream::empty()); - } - let target = header & 0x1f; - let (session_id, buf) = match read_varint(buf) { - Some(t) => t, - None => return EitherS::B(stream::empty()), - }; - let (seq_num, buf) = match read_varint(buf) { - Some(t) => t, - None => return EitherS::B(stream::empty()), - }; - let (opus_header, buf) = match read_varint(buf) { - Some(t) => t, - None => return EitherS::B(stream::empty()), - }; - let length = (opus_header & 0x1fff) as usize; - let last_bit = opus_header & 0x2000 != 0; - if length > buf.len() { - return EitherS::B(stream::empty()); - } - let (opus_data, _) = buf.split_at(length); // NOTE: the mumble packet id increases by 1 per 10ms of audio contained // whereas rtp seq_num should increase by 1 per packet, regardless of audio, @@ -349,39 +327,32 @@ impl Connection { fn process_packet_from_server( &mut self, - mut frame: MumbleFrame, + packet: ControlPacket, ) -> impl Stream { - match frame.id { - mumble::MSG_UDP_TUNNEL => EitherS::A(self.handle_voice_packet(&frame.bytes)), - mumble::MSG_USER_STATE => { - let mut message: Mumble::UserState = - protobuf::parse_from_bytes(&frame.bytes).unwrap(); + match packet { + ControlPacket::UDPTunnel(voice) => EitherS::A(self.handle_voice_packet(*voice)), + ControlPacket::UserState(mut message) => { let session_id = message.get_session(); if !self.sessions.contains_key(&session_id) { let user = self.allocate_ssrc(session_id); message.set_ssrc(user.ssrc); } - frame.bytes = message.write_to_bytes().unwrap().as_slice().into(); - EitherS::B(stream::once(Ok(Frame::Client(frame)))) + EitherS::B(stream::once(Ok(Frame::Client((*message).into())))) } - mumble::MSG_USER_REMOVE => { - let mut message: Mumble::UserRemove = - protobuf::parse_from_bytes(&frame.bytes).unwrap(); + ControlPacket::UserRemove(message) => { self.free_ssrc(message.get_session()); - EitherS::B(stream::once(Ok(Frame::Client(frame)))) + EitherS::B(stream::once(Ok(Frame::Client((*message).into())))) } - _ => EitherS::B(stream::once(Ok(Frame::Client(frame)))), + other => EitherS::B(stream::once(Ok(Frame::Client(other)))), } } fn process_packet_from_client( &mut self, - mut frame: MumbleFrame, + packet: ControlPacket, ) -> Box> { - match frame.id { - mumble::MSG_AUTHENTICATE => { - let mut message: Mumble::Authenticate = - protobuf::parse_from_bytes(&frame.bytes).unwrap(); + match packet { + ControlPacket::Authenticate(mut message) => { println!("MSG Authenticate: {:?}", message); if message.get_webrtc() { // strip webrtc support from the connection (we will be providing it) @@ -391,14 +362,12 @@ impl Connection { let stream = self.setup_ice(); - frame.bytes = message.write_to_bytes().unwrap().as_slice().into(); - Box::new(stream::once(Ok(Frame::Server(frame))).chain(stream)) + Box::new(stream::once(Ok(Frame::Server((*message).into()))).chain(stream)) } else { - Box::new(stream::once(Ok(Frame::Server(frame)))) + Box::new(stream::once(Ok(Frame::Server((*message).into())))) } } - mumble::MSG_WEBRTC => { - let mut message: Mumble::WebRTC = protobuf::parse_from_bytes(&frame.bytes).unwrap(); + ControlPacket::WebRTC(mut message) => { println!("Got WebRTC: {:?}", message); if let Some((_, stream)) = &mut self.ice { if let (Ok(ufrag), Ok(pwd)) = ( @@ -412,9 +381,7 @@ impl Connection { } Box::new(stream::empty()) } - mumble::MSG_ICE_CANDIDATE => { - let mut message: Mumble::IceCandidate = - protobuf::parse_from_bytes(&frame.bytes).unwrap(); + ControlPacket::IceCandidate(mut message) => { let candidate = message.take_content(); println!("Got ice candidate: {:?}", candidate); if let Some((_, stream)) = &mut self.ice { @@ -434,9 +401,7 @@ impl Connection { } Box::new(stream::empty()) } - mumble::MSG_TALKING_STATE => { - let mut message: Mumble::TalkingState = - protobuf::parse_from_bytes(&frame.bytes).unwrap(); + ControlPacket::TalkingState(message) => { self.target = if message.has_target() { Some(message.get_target() as u8) } else { @@ -444,7 +409,7 @@ impl Connection { }; Box::new(stream::empty()) } - _ => Box::new(stream::once(Ok(Frame::Server(frame)))), + other => Box::new(stream::once(Ok(Frame::Server(other)))), } } @@ -456,17 +421,16 @@ impl Connection { // packet reordering and loss (done). But maybe keep it low? let seq_num = rtp.header.timestamp / 480; - let header = 128_u8 | target; - let mut vec: Vec = Vec::new(); - vec.push(header); - write_varint32(&mut vec, seq_num as u32).unwrap(); - write_varint32(&mut vec, rtp.payload.len() as u32).unwrap(); - vec.extend(rtp.payload); + let voice_packet = VoicePacket::Audio { + _dst: std::marker::PhantomData::, + target, + session_id: (), + seq_num: seq_num.into(), + payload: VoicePacketPayload::Opus(rtp.payload.into(), false), + position_info: None, + }; - Some(Ok(Frame::Server(MumbleFrame { - id: mumble::MSG_UDP_TUNNEL, - bytes: vec.into(), - }))) + Some(Ok(Frame::Server(voice_packet.into()))) } else { None } @@ -571,12 +535,9 @@ impl Future for Connection { let candidate = format!("candidate:{}", candidate.to_string()); println!("Local ice candidate: {}", candidate); // Got a new candidate, send it to the client - let mut msg = Mumble::IceCandidate::new(); + let mut msg = msgs::IceCandidate::new(); msg.set_content(candidate.to_string()); - let frame = Frame::Client(MumbleFrame { - id: mumble::MSG_ICE_CANDIDATE, - bytes: msg.write_to_bytes().unwrap().into(), - }); + let frame = Frame::Client(msg.into()); self.stream_to_be_sent = Some(Box::new(stream::once(Ok(frame)))); continue 'poll; } @@ -632,7 +593,7 @@ impl Future for Connection { #[derive(Clone)] enum Frame { - Server(MumbleFrame), - Client(MumbleFrame), + Server(ControlPacket), + Client(ControlPacket), Rtp(MuxedPacket>), } diff --git a/src/error.rs b/src/error.rs index a32ebb9..b36f179 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,7 +8,6 @@ pub enum Error { Io(std::io::Error), ServerTls(native_tls::Error), ClientConnection(websocket::result::WebSocketError), - Protobuf(protobuf::ProtobufError), Misc(Box), } @@ -30,12 +29,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: protobuf::ProtobufError) -> Self { - Error::Protobuf(e) - } -} - impl From for Error { fn from(e: tokio::timer::Error) -> Self { Error::Misc(Box::new(e)) diff --git a/src/main.rs b/src/main.rs index a3046f1..d5b40ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(try_from)] // FIXME don't just unwrap protobuf results // FIXME for some reason, reconnecting without reloading the page fails DTLS handshake (FF) extern crate argparse; @@ -5,9 +6,9 @@ extern crate byteorder; extern crate bytes; extern crate futures; extern crate libnice; +extern crate mumble_protocol; extern crate native_tls; extern crate openssl; -extern crate protobuf; extern crate rtp; extern crate tokio; extern crate tokio_codec; @@ -20,7 +21,12 @@ use argparse::{ArgumentParser, Store}; use byteorder::{BigEndian, ByteOrder}; use bytes::{BufMut, BytesMut}; use futures::{Future, Sink, Stream}; +use mumble_protocol::control::ClientControlCodec; +use mumble_protocol::control::ControlPacket; +use mumble_protocol::control::RawControlPacket; +use mumble_protocol::Clientbound; use std::convert::Into; +use std::convert::TryInto; use std::net::ToSocketAddrs; use tokio::net::TcpStream; use tokio_codec::Decoder; @@ -32,14 +38,9 @@ use websocket::server::InvalidConnection; mod connection; mod error; -mod mumble; mod utils; -mod protos { - pub mod Mumble; -} use connection::Connection; use error::Error; -use mumble::{MumbleCodec, MumbleFrame}; fn main() { let mut ws_port = 0_u16; @@ -104,31 +105,35 @@ fn main() { let (client_sink, client_stream) = client.split(); // buffered client sink to prevent temporary lag on the control // channel from lagging the otherwise independent audio channel - let client_sink = client_sink.buffer(10).with(|m: MumbleFrame| { - let bytes = &m.bytes; - let len = bytes.len(); - let mut buf = BytesMut::with_capacity(6 + len); - buf.put_u16_be(m.id); - buf.put_u32_be(len as u32); - buf.put(bytes); - Ok::(OwnedMessage::Binary(buf.freeze().to_vec())) - }); + let client_sink = + client_sink + .buffer(10) + .with(|m: ControlPacket| { + let m = RawControlPacket::from(m); + let bytes = &m.bytes; + let len = bytes.len(); + let mut buf = BytesMut::with_capacity(6 + len); + buf.put_u16_be(m.id); + buf.put_u32_be(len as u32); + buf.put(bytes); + Ok::(OwnedMessage::Binary( + buf.freeze().to_vec(), + )) + }); let client_stream = client_stream .from_err() .take_while(|m| Ok(!m.is_close())) - .filter_map(|m| { - match m { - OwnedMessage::Binary(ref b) if b.len() >= 6 => { - let id = BigEndian::read_u16(b); - // b[2..6] is length which is implicit in websocket msgs - let bytes = b[6..].into(); - Some(MumbleFrame { id, bytes }) - } - _ => None, + .filter_map(|m| match m { + OwnedMessage::Binary(ref b) if b.len() >= 6 => { + let id = BigEndian::read_u16(b); + // b[2..6] is length which is implicit in websocket msgs + let bytes = b[6..].into(); + RawControlPacket { id, bytes }.try_into().ok() } + _ => None, }); - let server = MumbleCodec::new().framed(server); + let server = ClientControlCodec::new().framed(server); let (server_sink, server_stream) = server.split(); let server_sink = server_sink.sink_from_err(); let server_stream = server_stream.from_err(); @@ -142,48 +147,3 @@ fn main() { }); core.run(f).unwrap(); } - -macro_rules! define_packet_mappings { - ( $id:expr, $head:ident ) => { - #[allow(dead_code)] - const $head: u16 = $id; - }; - ( $id:expr, $head:ident, $( $tail:ident ),* ) => { - #[allow(dead_code)] - const $head: u16 = $id; - define_packet_mappings!($id + 1, $($tail),*); - }; -} - -define_packet_mappings![ - 0, - MSG_VERSION, - MSG_UDP_TUNNEL, - MSG_AUTHENTICATE, - MSG_PING, - MSG_REJECT, - MSG_SERVER_SYNC, - MSG_CHANNEL_REMOVE, - MSG_CHANNEL_STATE, - MSG_USER_REMOVE, - MSG_USER_STATE, - MSG_BAN_LIST, - MSG_TEXT_MESSAGE, - MSG_PERMISSION_DENIED, - MSG_ACL, - MSG_QUERY_USERS, - MSG_CRYPT_SETUP, - MSG_CONTEXT_ACTION_MODIFY, - MSG_CONTEXT_ACTION, - MSG_USER_LIST, - MSG_VOICE_TARGET, - MSG_PERMISSION_QUERY, - MSG_CODEC_VERSION, - MSG_USER_STATS, - MSG_REQUEST_BLOB, - MSG_SERVER_CONFIG, - MSG_SUGGEST_CONFIG, - MSG_WEBRTC, - MSG_ICE_CANDIDATE, - MSG_TALKING_STATE -]; diff --git a/src/mumble.rs b/src/mumble.rs deleted file mode 100644 index ed8c34f..0000000 --- a/src/mumble.rs +++ /dev/null @@ -1,103 +0,0 @@ -use bytes::{Buf, BufMut, Bytes, BytesMut}; -use std::io::Cursor; -use tokio::io; -use tokio_codec::{Decoder, Encoder}; - -#[derive(Clone, Debug)] -pub struct MumbleFrame { - pub id: u16, - pub bytes: Bytes, -} - -pub struct MumbleCodec; - -impl MumbleCodec { - pub fn new() -> Self { - Self {} - } -} - -impl Decoder for MumbleCodec { - type Item = MumbleFrame; - type Error = io::Error; - - fn decode(&mut self, buf: &mut BytesMut) -> Result, io::Error> { - let buf_len = buf.len(); - if buf_len >= 6 { - let mut buf = Cursor::new(buf); - let id = buf.get_u16_be(); - let len = buf.get_u32_be() as usize; - if buf_len >= 6 + len { - let mut bytes = buf.into_inner().split_to(6 + len); - bytes.advance(6); - let bytes = bytes.freeze(); - Ok(Some(MumbleFrame { id, bytes })) - } else { - Ok(None) - } - } else { - Ok(None) - } - } -} - -impl Encoder for MumbleCodec { - type Item = MumbleFrame; - type Error = io::Error; - - fn encode(&mut self, item: MumbleFrame, dst: &mut BytesMut) -> Result<(), io::Error> { - let id = item.id; - let bytes = &item.bytes; - let len = bytes.len(); - dst.reserve(6 + len); - dst.put_u16_be(id); - dst.put_u32_be(len as u32); - dst.put(bytes); - Ok(()) - } -} - -macro_rules! define_packet_mappings { - ( $id:expr, $head:ident ) => { - #[allow(dead_code)] - pub const $head: u16 = $id; - }; - ( $id:expr, $head:ident, $( $tail:ident ),* ) => { - #[allow(dead_code)] - pub const $head: u16 = $id; - define_packet_mappings!($id + 1, $($tail),*); - }; -} - -define_packet_mappings![ - 0, - MSG_VERSION, - MSG_UDP_TUNNEL, - MSG_AUTHENTICATE, - MSG_PING, - MSG_REJECT, - MSG_SERVER_SYNC, - MSG_CHANNEL_REMOVE, - MSG_CHANNEL_STATE, - MSG_USER_REMOVE, - MSG_USER_STATE, - MSG_BAN_LIST, - MSG_TEXT_MESSAGE, - MSG_PERMISSION_DENIED, - MSG_ACL, - MSG_QUERY_USERS, - MSG_CRYPT_SETUP, - MSG_CONTEXT_ACTION_MODIFY, - MSG_CONTEXT_ACTION, - MSG_USER_LIST, - MSG_VOICE_TARGET, - MSG_PERMISSION_QUERY, - MSG_CODEC_VERSION, - MSG_USER_STATS, - MSG_REQUEST_BLOB, - MSG_SERVER_CONFIG, - MSG_SUGGEST_CONFIG, - MSG_WEBRTC, - MSG_ICE_CANDIDATE, - MSG_TALKING_STATE -]; diff --git a/src/protos/.gitignore b/src/protos/.gitignore deleted file mode 100644 index 749e481..0000000 --- a/src/protos/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/Mumble.rs diff --git a/src/utils.rs b/src/utils.rs index f7de8f2..602583e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,90 +1,6 @@ use futures::Stream; use tokio::prelude::*; -pub fn read_varint(buf: &[u8]) -> Option<(u64, &[u8])> { - let (b0, buf) = buf.split_first()?; - let result = if (b0 & 0x80) == 0x00 { - (u64::from(b0 & 0x7F), buf) - } else { - let (b1, buf) = buf.split_first()?; - if (b0 & 0xC0) == 0x80 { - (u64::from(b0 & 0x3F) << 8 | u64::from(*b1), buf) - } else { - let (b2, buf) = buf.split_first()?; - if (b0 & 0xF0) == 0xF0 { - match b0 & 0xFC { - 0xF0 => { - let (b3, buf) = buf.split_first()?; - let (b4, buf) = buf.split_first()?; - ( - u64::from(*b1) << 24 - | u64::from(*b2) << 16 - | u64::from(*b3) << 8 - | u64::from(*b4), - buf, - ) - } - 0xF4 => { - let (b3, buf) = buf.split_first()?; - let (b4, buf) = buf.split_first()?; - let (b5, buf) = buf.split_first()?; - let (b6, buf) = buf.split_first()?; - let (b7, buf) = buf.split_first()?; - let (b8, buf) = buf.split_first()?; - ( - u64::from(*b1) << 56 - | u64::from(*b2) << 48 - | u64::from(*b3) << 40 - | u64::from(*b4) << 32 - | u64::from(*b5) << 24 - | u64::from(*b6) << 16 - | u64::from(*b7) << 8 - | u64::from(*b8), - buf, - ) - } - 0xF8 => { - let (val, buf) = read_varint(buf)?; - (!val, buf) - } - 0xFC => (!u64::from(b0 & 0x03), buf), - _ => { - return None; - } - } - } else if (b0 & 0xF0) == 0xE0 { - let (b3, buf) = buf.split_first()?; - ( - u64::from(b0 & 0x0F) << 24 - | u64::from(*b1) << 16 - | u64::from(*b2) << 8 - | u64::from(*b3), - buf, - ) - } else if (b0 & 0xE0) == 0xC0 { - ( - u64::from(b0 & 0x1F) << 16 | u64::from(*b1) << 8 | u64::from(*b2), - buf, - ) - } else { - return None; - } - } - }; - Some(result) -} - -pub fn write_varint32(buf: &mut T, value: u32) -> std::io::Result<()> { - // FIXME: actually implement the variable part - buf.write_all(&[ - 240, - (value >> 24) as u8, - (value >> 16) as u8, - (value >> 8) as u8, - value as u8, - ]) -} - /// Like `futures::future::Either` but for Streams pub enum EitherS { A(A),