Replace lots of generic code with dedicated mumble-protocol crate

This commit is contained in:
Jonas Herzig 2019-02-03 23:34:09 +01:00
parent 353de4ec2f
commit 443712cb22
10 changed files with 112 additions and 1028 deletions

17
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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");
}

View file

@ -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;
}

View file

@ -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>>),
}

View file

@ -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))

View file

@ -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
];

View file

@ -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
];

View file

@ -1 +0,0 @@
/Mumble.rs

View file

@ -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),