Replace lots of generic code with dedicated mumble-protocol
crate
This commit is contained in:
parent
353de4ec2f
commit
443712cb22
17
Cargo.lock
generated
17
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -3,9 +3,6 @@ name = "mumble-web-proxy"
|
|||
version = "0.1.0"
|
||||
authors = ["Jonas Herzig <me@johni0702.de>"]
|
||||
|
||||
[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"
|
||||
|
|
12
build.rs
12
build.rs
|
@ -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");
|
||||
}
|
|
@ -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 <https://www.mumble.info/LICENSE>.
|
||||
|
||||
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;
|
||||
}
|
|
@ -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<Stream<Item = MumbleFrame, Error = Error>>,
|
||||
outbound_client: Box<Sink<SinkItem = MumbleFrame, SinkError = Error>>,
|
||||
inbound_server: Box<Stream<Item = MumbleFrame, Error = Error>>,
|
||||
outbound_server: Box<Sink<SinkItem = MumbleFrame, SinkError = Error>>,
|
||||
next_clientbound_frame: Option<MumbleFrame>,
|
||||
next_serverbound_frame: Option<MumbleFrame>,
|
||||
inbound_client: Box<Stream<Item = ControlPacket<Serverbound>, Error = Error>>,
|
||||
outbound_client: Box<Sink<SinkItem = ControlPacket<Clientbound>, SinkError = Error>>,
|
||||
inbound_server: Box<Stream<Item = ControlPacket<Clientbound>, Error = Error>>,
|
||||
outbound_server: Box<Sink<SinkItem = ControlPacket<Serverbound>, SinkError = Error>>,
|
||||
next_clientbound_frame: Option<ControlPacket<Clientbound>>,
|
||||
next_serverbound_frame: Option<ControlPacket<Serverbound>>,
|
||||
next_rtp_frame: Option<Vec<u8>>,
|
||||
stream_to_be_sent: Option<Box<Stream<Item = Frame, Error = Error>>>,
|
||||
|
||||
|
@ -119,10 +115,10 @@ impl Connection {
|
|||
server_stream: SSt,
|
||||
) -> Self
|
||||
where
|
||||
CSi: Sink<SinkItem = MumbleFrame, SinkError = Error> + 'static,
|
||||
CSt: Stream<Item = MumbleFrame, Error = Error> + 'static,
|
||||
SSi: Sink<SinkItem = MumbleFrame, SinkError = Error> + 'static,
|
||||
SSt: Stream<Item = MumbleFrame, Error = Error> + 'static,
|
||||
CSi: Sink<SinkItem = ControlPacket<Clientbound>, SinkError = Error> + 'static,
|
||||
CSt: Stream<Item = ControlPacket<Serverbound>, Error = Error> + 'static,
|
||||
SSi: Sink<SinkItem = ControlPacket<Serverbound>, SinkError = Error> + 'static,
|
||||
SSt: Stream<Item = ControlPacket<Clientbound>, 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<Item = Frame, Error = Error> {
|
||||
let (header, buf) = match buf.split_first() {
|
||||
Some(t) => t,
|
||||
None => return EitherS::B(stream::empty()),
|
||||
fn handle_voice_packet(
|
||||
&mut self,
|
||||
packet: VoicePacket<Clientbound>,
|
||||
) -> impl Stream<Item = Frame, Error = Error> {
|
||||
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<Clientbound>,
|
||||
) -> impl Stream<Item = Frame, Error = Error> {
|
||||
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<Serverbound>,
|
||||
) -> Box<Stream<Item = Frame, Error = Error>> {
|
||||
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<u8> = 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::<Serverbound>,
|
||||
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<Serverbound>),
|
||||
Client(ControlPacket<Clientbound>),
|
||||
Rtp(MuxedPacket<RtpPacket, RtcpCompoundPacket<RtcpPacket>>),
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ pub enum Error {
|
|||
Io(std::io::Error),
|
||||
ServerTls(native_tls::Error),
|
||||
ClientConnection(websocket::result::WebSocketError),
|
||||
Protobuf(protobuf::ProtobufError),
|
||||
Misc(Box<std::error::Error>),
|
||||
}
|
||||
|
||||
|
@ -30,12 +29,6 @@ impl From<native_tls::Error> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<protobuf::ProtobufError> for Error {
|
||||
fn from(e: protobuf::ProtobufError) -> Self {
|
||||
Error::Protobuf(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tokio::timer::Error> for Error {
|
||||
fn from(e: tokio::timer::Error) -> Self {
|
||||
Error::Misc(Box::new(e))
|
||||
|
|
100
src/main.rs
100
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, Error>(OwnedMessage::Binary(buf.freeze().to_vec()))
|
||||
});
|
||||
let client_sink =
|
||||
client_sink
|
||||
.buffer(10)
|
||||
.with(|m: ControlPacket<Clientbound>| {
|
||||
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, Error>(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
|
||||
];
|
||||
|
|
103
src/mumble.rs
103
src/mumble.rs
|
@ -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<Option<MumbleFrame>, 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
|
||||
];
|
1
src/protos/.gitignore
vendored
1
src/protos/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
/Mumble.rs
|
84
src/utils.rs
84
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<T: Write>(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, B> {
|
||||
A(A),
|
||||
|
|
Loading…
Reference in a new issue