Simulator first commit
This commit is contained in:
BIN
node_modules/mumble-streams/lib/.voice.js.un~
generated
vendored
Normal file
BIN
node_modules/mumble-streams/lib/.voice.js.un~
generated
vendored
Normal file
Binary file not shown.
561
node_modules/mumble-streams/lib/Mumble.proto
generated
vendored
Normal file
561
node_modules/mumble-streams/lib/Mumble.proto
generated
vendored
Normal file
@ -0,0 +1,561 @@
|
||||
// Copyright 2005-2016 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>.
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
NestingLimit = 10;
|
||||
}
|
||||
// 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;
|
||||
}
|
192
node_modules/mumble-streams/lib/data.js
generated
vendored
Normal file
192
node_modules/mumble-streams/lib/data.js
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
var fs = require('fs'),
|
||||
protobufjs = require('protobufjs'),
|
||||
util = require('util'),
|
||||
Transform = require('stream').Transform;
|
||||
|
||||
var nameById = {
|
||||
0: 'Version',
|
||||
1: 'UDPTunnel',
|
||||
2: 'Authenticate',
|
||||
3: 'Ping',
|
||||
4: 'Reject',
|
||||
5: 'ServerSync',
|
||||
6: 'ChannelRemove',
|
||||
7: 'ChannelState',
|
||||
8: 'UserRemove',
|
||||
9: 'UserState',
|
||||
10: 'BanList',
|
||||
11: 'TextMessage',
|
||||
12: 'PermissionDenied',
|
||||
13: 'ACL',
|
||||
14: 'QueryUsers',
|
||||
15: 'CryptSetup',
|
||||
16: 'ContextActionModify',
|
||||
17: 'ContextAction',
|
||||
18: 'UserList',
|
||||
19: 'VoiceTarget',
|
||||
20: 'PermissionQuery',
|
||||
21: 'CodecVersion',
|
||||
22: 'UserStats',
|
||||
23: 'RequestBlob',
|
||||
24: 'ServerConfig',
|
||||
25: 'SuggestConfig'
|
||||
};
|
||||
var idByName = {};
|
||||
for (var id in nameById) {
|
||||
idByName[nameById[id]] = id;
|
||||
}
|
||||
|
||||
// Explicitly reading with readFileSync to support brfs
|
||||
var mumbleProto = fs.readFileSync(__dirname + '/Mumble.proto');
|
||||
var messages = protobufjs.loadProto(mumbleProto).build('MumbleProto');
|
||||
|
||||
/**
|
||||
* Encodes the given message.
|
||||
*
|
||||
* @param {string} name The name of the message.
|
||||
* @param {object} payload The message to be encoded.
|
||||
* @return {Buffer} The encoded message.
|
||||
*/
|
||||
function encode(name, payload) {
|
||||
var encoded = new messages[name](payload || {}).toBuffer();
|
||||
// toBuffer returns an ArrayBuffer when called in the browser
|
||||
if (!Buffer.isBuffer(encoded)) {
|
||||
encoded = Buffer.from(encoded);
|
||||
}
|
||||
return encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message object.
|
||||
* @typedef {object} Message
|
||||
* @property {string} name - Name of the message
|
||||
* @property {object} [payload={}] - Payload of the message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Decodes the given message.
|
||||
*
|
||||
* @param {number} id The id of the message.
|
||||
* @param {Buffer} payload The encoded message.
|
||||
* @return {object} The decoded message.
|
||||
*/
|
||||
function decode(id, payload) {
|
||||
var name = nameById[id];
|
||||
return new messages[name].decode(payload || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform stream for encoding {@link Message Mumble messages}.
|
||||
*
|
||||
* @constructor
|
||||
* @constructs Encoder
|
||||
*/
|
||||
function Encoder() {
|
||||
// Allow use without new
|
||||
if (!(this instanceof Encoder)) return new Encoder();
|
||||
|
||||
Transform.call(this, {
|
||||
writableObjectMode: true
|
||||
});
|
||||
}
|
||||
util.inherits(Encoder, Transform);
|
||||
|
||||
Encoder.prototype._transform = function(chunk, encoding, callback) {
|
||||
if (typeof chunk.name !== 'string') {
|
||||
return callback(new TypeError('chunk.name is not a string'));
|
||||
}
|
||||
chunk.payload = chunk.payload || {};
|
||||
|
||||
// First, encode the payload
|
||||
var data;
|
||||
if (chunk.name == 'UDPTunnel') {
|
||||
// UDPTunnel message doesn't need encoding
|
||||
data = chunk.payload;
|
||||
} else {
|
||||
try {
|
||||
// Encode the message payload
|
||||
data = encode(chunk.name, chunk.payload);
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Then create the header
|
||||
var header = new Buffer(6);
|
||||
header.writeUInt16BE(idByName[chunk.name], 0);
|
||||
header.writeUInt32BE(data.length, 2);
|
||||
|
||||
callback(null, Buffer.concat([header, data]));
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform stream for decoding {@link Message Mumble messages}.
|
||||
*
|
||||
* @constructor
|
||||
* @constructs Decoder
|
||||
*/
|
||||
function Decoder() {
|
||||
// Allow use without new
|
||||
if (!(this instanceof Decoder)) return new Decoder();
|
||||
|
||||
Transform.call(this, {
|
||||
readableObjectMode: true
|
||||
});
|
||||
|
||||
this._buffer = new Buffer(1024);
|
||||
this._bufferSize = 0;
|
||||
}
|
||||
util.inherits(Decoder, Transform);
|
||||
|
||||
Decoder.prototype._transform = function(chunk, encoding, callback) {
|
||||
// Add incoming chunk to internal buffer
|
||||
if (this._buffer.length - this._bufferSize < chunk.length) {
|
||||
// Old buffer is too small, replace with bigger one
|
||||
var oldBuffer = this._buffer;
|
||||
this._buffer = new Buffer(this._bufferSize + chunk.length);
|
||||
oldBuffer.copy(this._buffer, 0, 0, this._bufferSize);
|
||||
}
|
||||
this._bufferSize += chunk.copy(this._buffer, this._bufferSize);
|
||||
|
||||
|
||||
// Try to decode messages while we still have enough bytes
|
||||
while (this._bufferSize >= 6) {
|
||||
var type = this._buffer.readUInt16BE(0);
|
||||
var size = this._buffer.readUInt32BE(2);
|
||||
if (this._bufferSize < 6 + size) {
|
||||
break; // Not enough bytes in internal buffer for the expected payload
|
||||
}
|
||||
|
||||
var typeName = nameById[type];
|
||||
var data = this._buffer.slice(6, 6 + size);
|
||||
// Decode payload
|
||||
var message;
|
||||
if (typeName == 'UDPTunnel') {
|
||||
// UDPTunnel payload is not encoded
|
||||
message = new Buffer(data);
|
||||
} else {
|
||||
try {
|
||||
message = decode(type, data);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Shift remaining bytes to start of internal buffer
|
||||
this._buffer.copy(this._buffer, 0, 6 + size, this._bufferSize);
|
||||
this._bufferSize -= 6 + size;
|
||||
|
||||
this.push({
|
||||
name: typeName,
|
||||
payload: message
|
||||
});
|
||||
}
|
||||
callback();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Encoder: Encoder,
|
||||
Decoder: Decoder,
|
||||
messages: messages
|
||||
};
|
333
node_modules/mumble-streams/lib/udp-crypto.js
generated
vendored
Normal file
333
node_modules/mumble-streams/lib/udp-crypto.js
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
// This module is a port of the original CryptState class to Node.js
|
||||
// The original file can be found at
|
||||
// https://github.com/mumble-voip/mumble/blob/master/src/CryptState.cpp
|
||||
|
||||
// Copyright notice of the original source:
|
||||
// Copyright 2005-2016 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>.
|
||||
|
||||
var crypto = require('crypto');
|
||||
|
||||
var BLOCK_SIZE = 16;
|
||||
|
||||
function UdpCrypt(stats) {
|
||||
this._decryptHistory = new Array(100);
|
||||
this._stats = stats || {};
|
||||
}
|
||||
|
||||
UdpCrypt.prototype.getKey = function() { return this._key; };
|
||||
UdpCrypt.prototype.getDecryptIV = function() { return this._decryptIV; };
|
||||
UdpCrypt.prototype.getEncryptIV = function() { return this._encryptIV; };
|
||||
UdpCrypt.prototype.ready = function() {
|
||||
return this._key && this._decryptIV && this._encryptIV;
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.setKey = function(key) {
|
||||
if (key.length != BLOCK_SIZE) {
|
||||
throw new Error('key must be exactly ' + BLOCK_SIZE + ' bytes');
|
||||
}
|
||||
this._key = key;
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.setDecryptIV = function(decryptIV) {
|
||||
if (decryptIV.length != BLOCK_SIZE) {
|
||||
throw new Error('decryptIV must be exactly ' + BLOCK_SIZE + ' bytes');
|
||||
}
|
||||
this._decryptIV = decryptIV;
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.setEncryptIV = function(encryptIV) {
|
||||
if (encryptIV.length != BLOCK_SIZE) {
|
||||
throw new Error('encryptIV must be exactly ' + BLOCK_SIZE + ' bytes');
|
||||
}
|
||||
this._encryptIV = encryptIV;
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.generateKey = function(callback) {
|
||||
crypto.randomBytes(BLOCK_SIZE * 3, function(err, buf) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
}
|
||||
|
||||
this._key = buf.slice(0, BLOCK_SIZE);
|
||||
this._decryptIV = buf.slice(BLOCK_SIZE, BLOCK_SIZE * 2);
|
||||
this._encryptIV = buf.slice(BLOCK_SIZE * 2);
|
||||
callback();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.encrypt = function(plainText) {
|
||||
// First, increase our IV
|
||||
for (var i = 0; i < BLOCK_SIZE; i++) {
|
||||
if (++this._encryptIV[i] == 256) {
|
||||
this._encryptIV[i] = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
var cipher = crypto.createCipheriv('AES-128-ECB', this._key, '')
|
||||
.setAutoPadding(false);
|
||||
|
||||
var cipherText = new Buffer(plainText.length + 4);
|
||||
var tag = ocbEncrypt(plainText, cipherText.slice(4), this._encryptIV,
|
||||
cipher.update.bind(cipher));
|
||||
cipherText[0] = this._encryptIV[0];
|
||||
cipherText[1] = tag[0];
|
||||
cipherText[2] = tag[1];
|
||||
cipherText[3] = tag[2];
|
||||
|
||||
return cipherText;
|
||||
};
|
||||
|
||||
UdpCrypt.prototype.decrypt = function(cipherText) {
|
||||
if (cipherText.length < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var saveiv = Buffer.from(this._decryptIV);
|
||||
var ivbyte = cipherText[0];
|
||||
var restore = false;
|
||||
var lost = 0;
|
||||
var late = 0;
|
||||
var i;
|
||||
|
||||
if (((this._decryptIV[0] + 1) & 0xFF) == ivbyte) {
|
||||
// In order as expected
|
||||
if (ivbyte > this._decryptIV[0]) {
|
||||
this._decryptIV[0] = ivbyte;
|
||||
} else if (ivbyte < this._decryptIV[0]) {
|
||||
this._decryptIV[0] = ivbyte;
|
||||
for (i = 1; i < BLOCK_SIZE; i++) {
|
||||
if (++this._decryptIV[i] == 256) {
|
||||
this._encryptIV[i] = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// This is either out of order or a repeat.
|
||||
|
||||
var diff = ivbyte - this._decryptIV[0];
|
||||
if (diff > 128) {
|
||||
diff = diff - 256;
|
||||
} else if (diff < -128) {
|
||||
diff = diff + 256;
|
||||
}
|
||||
|
||||
if ((ivbyte < this._decryptIV[0]) && (diff > -30) && (diff < 0)) {
|
||||
// Late packet, but no wraparound
|
||||
late++;
|
||||
lost--;
|
||||
this._decryptIV[0] = ivbyte;
|
||||
restore = true;
|
||||
} else if ((ivbyte > this._decryptIV[0]) && (diff > -30) && (diff < 0)) {
|
||||
// Late was 0x02, here comes 0xff from last round
|
||||
late++;
|
||||
lost--;
|
||||
this._decryptIV[0] = ivbyte;
|
||||
for (i = 0; i < BLOCK_SIZE; i++) {
|
||||
if (this._decryptIV[i]-- == -1) {
|
||||
this._decryptIV[i] = 255;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
restore = true;
|
||||
} else if ((ivbyte > this._decryptIV[0]) && (diff > 0)) {
|
||||
// Lost a few packets, but beyond that we're good.
|
||||
lost += ivbyte - this._decryptIV[0] - 1;
|
||||
this._decryptIV[0] = ivbyte;
|
||||
} else if ((ivbyte < this._decryptIV[0]) && (diff > 0)) {
|
||||
// Lost a few packets, and wrapped around
|
||||
lost += 256 - this._decryptIV[0] + ivbyte - 1;
|
||||
this._decryptIV[0] = ivbyte;
|
||||
for (i = 0; i < BLOCK_SIZE; i++) {
|
||||
if (++this._decryptIV[i] == 256) {
|
||||
this._encryptIV[i] = 0;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this._decryptHistory[this._decryptIV[0]] == this._decryptIV[1]) {
|
||||
this._decryptIV = saveiv;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var encrypt = crypto.createCipheriv('AES-128-ECB', this._key, '')
|
||||
.setAutoPadding(false);
|
||||
var decrypt = crypto.createDecipheriv('AES-128-ECB', this._key, '')
|
||||
.setAutoPadding(false);
|
||||
|
||||
var plainText = new Buffer(cipherText.length - 4);
|
||||
var tag = ocbDecrypt(cipherText.slice(4), plainText, this._decryptIV,
|
||||
encrypt.update.bind(encrypt), decrypt.update.bind(decrypt));
|
||||
|
||||
if (tag.compare(cipherText, 1, 4, 0, 3) !== 0) {
|
||||
this._decryptIV = saveiv;
|
||||
return null;
|
||||
}
|
||||
this._decryptHistory[this._decryptIV[0]] = this._decryptIV[1];
|
||||
|
||||
if (restore) {
|
||||
this._decryptIV = saveiv;
|
||||
}
|
||||
|
||||
this._stats.good++;
|
||||
this._stats.late += late;
|
||||
this._stats.lost += lost;
|
||||
return plainText;
|
||||
};
|
||||
|
||||
function ocbEncrypt(plainText, cipherText, nonce, aesEncrypt) {
|
||||
var checksum = new Buffer(BLOCK_SIZE);
|
||||
var tmp = new Buffer(BLOCK_SIZE);
|
||||
|
||||
var delta = aesEncrypt(nonce);
|
||||
ZERO(checksum);
|
||||
|
||||
var len = plainText.length;
|
||||
while (len > BLOCK_SIZE) {
|
||||
S2(delta);
|
||||
XOR(tmp, delta, plainText);
|
||||
tmp = aesEncrypt(tmp);
|
||||
XOR(cipherText, delta, tmp);
|
||||
XOR(checksum, checksum, plainText);
|
||||
len -= BLOCK_SIZE;
|
||||
plainText = plainText.slice(BLOCK_SIZE);
|
||||
cipherText = cipherText.slice(BLOCK_SIZE);
|
||||
}
|
||||
|
||||
S2(delta);
|
||||
ZERO(tmp);
|
||||
tmp[BLOCK_SIZE - 1] = len * 8;
|
||||
XOR(tmp, tmp, delta);
|
||||
var pad = aesEncrypt(tmp);
|
||||
plainText.copy(tmp, 0, 0, len);
|
||||
pad.copy(tmp, len, len, BLOCK_SIZE);
|
||||
XOR(checksum, checksum, tmp);
|
||||
XOR(tmp, pad, tmp);
|
||||
tmp.copy(cipherText, 0, 0, len);
|
||||
|
||||
S3(delta);
|
||||
XOR(tmp, delta, checksum);
|
||||
var tag = aesEncrypt(tmp);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
function ocbDecrypt(cipherText, plainText, nonce, aesEncrypt, aesDecrypt) {
|
||||
var checksum = new Buffer(BLOCK_SIZE);
|
||||
var tmp = new Buffer(BLOCK_SIZE);
|
||||
|
||||
// Initialize
|
||||
var delta = aesEncrypt(nonce);
|
||||
ZERO(checksum);
|
||||
|
||||
var len = plainText.length;
|
||||
while (len > BLOCK_SIZE) {
|
||||
S2(delta);
|
||||
XOR(tmp, delta, cipherText);
|
||||
tmp = aesDecrypt(tmp);
|
||||
XOR(plainText, delta, tmp);
|
||||
XOR(checksum, checksum, plainText);
|
||||
len -= BLOCK_SIZE;
|
||||
plainText = plainText.slice(BLOCK_SIZE);
|
||||
cipherText = cipherText.slice(BLOCK_SIZE);
|
||||
}
|
||||
|
||||
S2(delta);
|
||||
ZERO(tmp);
|
||||
tmp[BLOCK_SIZE - 1] = len * 8;
|
||||
XOR(tmp, tmp, delta);
|
||||
var pad = aesEncrypt(tmp);
|
||||
ZERO(tmp);
|
||||
cipherText.copy(tmp, 0, 0, len);
|
||||
XOR(tmp, tmp, pad);
|
||||
XOR(checksum, checksum, tmp);
|
||||
tmp.copy(plainText, 0, 0, len);
|
||||
|
||||
S3(delta);
|
||||
XOR(tmp, delta, checksum);
|
||||
var tag = aesEncrypt(tmp);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
function XOR(dst, a, b) {
|
||||
for (var i = 0; i < BLOCK_SIZE; i++) {
|
||||
dst[i] = a[i] ^ b[i];
|
||||
}
|
||||
}
|
||||
|
||||
function S2(block) {
|
||||
var carry = block[0] >> 7;
|
||||
for (var i = 0; i < BLOCK_SIZE - 1; i++) {
|
||||
block[i] = block[i] << 1 | block[i+1] >> 7;
|
||||
}
|
||||
block[BLOCK_SIZE-1] = block[BLOCK_SIZE-1] << 1 ^ (carry * 0x87);
|
||||
}
|
||||
|
||||
// Equivalent to: XOR(block, block, R2(block))
|
||||
function S3(block) {
|
||||
var carry = block[0] >> 7;
|
||||
for (var i = 0; i < BLOCK_SIZE - 1; i++) {
|
||||
block[i] ^= block[i] << 1 | block[i+1] >> 7;
|
||||
}
|
||||
block[BLOCK_SIZE-1] ^= block[BLOCK_SIZE-1] << 1 ^ (carry * 0x87);
|
||||
}
|
||||
|
||||
function ZERO(block) {
|
||||
block.fill(0, 0, BLOCK_SIZE);
|
||||
}
|
||||
|
||||
// End of port
|
||||
|
||||
var util = require('util'),
|
||||
Transform = require('stream').Transform;
|
||||
|
||||
module.exports = UdpCrypt;
|
||||
module.exports.BLOCK_SIZE = BLOCK_SIZE;
|
||||
module.exports.ocbEncrypt = ocbEncrypt;
|
||||
module.exports.ocbDecrypt = ocbDecrypt;
|
||||
|
||||
/**
|
||||
* @typedef {object} States
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transform stream for encrypting Mumble UDP packets.
|
||||
*
|
||||
* @constructor
|
||||
* @constructs Encrypt
|
||||
* @param {Stats} [stats] - Object into which network statistics are written
|
||||
*/
|
||||
function Encrypt(stats) {
|
||||
// Allow use without new
|
||||
if (!(this instanceof Encrypt)) return new Encrypt(dest);
|
||||
|
||||
Transform.call(this, {});
|
||||
|
||||
this._block = new UdpCrypt(stats);
|
||||
}
|
||||
util.inherits(Encrypt, Transform);
|
||||
|
||||
Encrypt.prototype._transform = function(chunk, encoding, callback) {
|
||||
callback(null, this._block.encrypt(chunk));
|
||||
};
|
||||
|
||||
/**
|
||||
* @return The underlying block cipher.
|
||||
*/
|
||||
Encrypt.prototype.getBlockCipher = function() {
|
||||
return this._block;
|
||||
};
|
364
node_modules/mumble-streams/lib/voice.js
generated
vendored
Normal file
364
node_modules/mumble-streams/lib/voice.js
generated
vendored
Normal file
@ -0,0 +1,364 @@
|
||||
var util = require('util'),
|
||||
Transform = require('stream').Transform;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {('Opus'|'Speex'|'CELT_Alpha'|'CELT_Beta')} Codec
|
||||
*/
|
||||
|
||||
/**
|
||||
* The mode of voice transmission.
|
||||
* 0 is normal talking.
|
||||
* 31 is server loopback.
|
||||
* 1-30 when sent from the client is the whisper target.
|
||||
* 1-30 when sent from the server: 1 for channel whisper, 2 for direct whisper
|
||||
*
|
||||
* @typedef {number} VoiceMode
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data for a Mumble voice packet.
|
||||
* The {@link #source source property} is ignored if this packet is not
|
||||
* clientbound otherwise it is required.
|
||||
*
|
||||
* @typedef {object} VoiceData
|
||||
* @property {number} [source] - Session ID of source user
|
||||
* @property {VoiceMode} mode - Mode of the voice transmission
|
||||
* @property {Codec} codec - Codec used for encoding the voice data
|
||||
* @property {number} seqNum - Sequence number of the first voice frame
|
||||
* @property {boolean} end - Whether this is the last packet in this transmission
|
||||
* @property {Buffer} frames[] - Encoded voice frame
|
||||
* @property {object} [position] - Spacial position of the source
|
||||
* @property {number} position.x - X coordinate
|
||||
* @property {number} position.y - Y coordinate
|
||||
* @property {number} position.z - Z coordinate
|
||||
*/
|
||||
|
||||
/**
|
||||
* Data for an audio channel ping packet.
|
||||
*
|
||||
* @typedef {object} PingData
|
||||
* @property timestamp The timestamp for this ping packet.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Transform stream for encoding {@link VoiceData Mumble voice packets}
|
||||
* and {@link PingData audio channel ping packets}.
|
||||
*
|
||||
* @constructor
|
||||
* @constructs Encoder
|
||||
* @param {('server'|'client')} dest - Where encoded packets are headed to.
|
||||
*/
|
||||
function Encoder(dest) {
|
||||
// Allow use without new
|
||||
if (!(this instanceof Encoder)) return new Encoder(dest);
|
||||
|
||||
if (dest != 'server' && dest != 'client') {
|
||||
throw new TypeError('dest has to be either "server" or "client"');
|
||||
}
|
||||
|
||||
Transform.call(this, {
|
||||
writableObjectMode: true
|
||||
});
|
||||
|
||||
this._dest = dest;
|
||||
}
|
||||
util.inherits(Encoder, Transform);
|
||||
|
||||
Encoder.prototype._transform = function(chunk, encoding, callback) {
|
||||
var buffer;
|
||||
var offset = 0;
|
||||
|
||||
// Special case: Ping packets
|
||||
if (chunk.timestamp !== undefined) {
|
||||
// Header byte + Timestamp
|
||||
buffer = new Buffer(1 + 9);
|
||||
offset += buffer.writeUInt8(0x20, offset); // Ping packet header
|
||||
offset += toVarint(chunk.timestamp).value.copy(buffer, offset);
|
||||
return callback(null, buffer.slice(0, offset));
|
||||
}
|
||||
|
||||
var codecId; // Network ID of the codec
|
||||
var voiceData; // All voice frames encoded into a single buffer
|
||||
if (chunk.codec == 'Opus') {
|
||||
if (chunk.frames.length > 1) {
|
||||
return callback(new Error('Opus only supports a single frame per packet'));
|
||||
}
|
||||
var endBit = chunk.end ? 0x2000 : 0
|
||||
if (chunk.frames.length == 0) {
|
||||
voiceData = toVarint(endBit).value;
|
||||
} else {
|
||||
var frameSize = toVarint(chunk.frames[0].length | endBit);
|
||||
// Opus packets are just the size and the data concatenated
|
||||
voiceData = Buffer.concat([frameSize.value, chunk.frames[0]]);
|
||||
}
|
||||
codecId = 4;
|
||||
} else if (['CELT_Alpha', 'CELT_Beta', 'Speex'].indexOf(chunk.codec) >= 0) {
|
||||
codecId = {'CELT_Alpha': 0, 'Speex': 2, 'CELT_Beta': 3}[chunk.codec]
|
||||
voiceData = []
|
||||
if (chunk.frames.length == 0 && !chunk.end) {
|
||||
return callback(new Error('No frames given but end bit is not set'));
|
||||
}
|
||||
for (var i = 0; i < chunk.frames.length; i++) {
|
||||
var frame = chunk.frames[i]
|
||||
if (frame.length > 127) {
|
||||
return callback(new Error('Frame size is greater than 127 bytes'));
|
||||
}
|
||||
voiceData.push(Buffer.from([frame.length | 0x80]))
|
||||
voiceData.push(frame)
|
||||
}
|
||||
// Append empty frame if end bit is set
|
||||
if (chunk.end) {
|
||||
voiceData.push(Buffer.from([0]))
|
||||
voiceData.push(Buffer.from([]))
|
||||
}
|
||||
// Unset continuation bit of last frame
|
||||
voiceData[voiceData.length - 2][0] &= 0x7F
|
||||
// Concat all frames
|
||||
voiceData = Buffer.concat(voiceData)
|
||||
} else {
|
||||
return callback(new TypeError('Unknown codec: ' + chunk.codec));
|
||||
}
|
||||
|
||||
// Header byte + Source Session Id + Sequence Number + Voice + Position Data
|
||||
buffer = new Buffer(1 + 9 + 9 + voiceData.length + 3 * 4);
|
||||
offset += buffer.writeUInt8(codecId << 5 | chunk.mode, offset);
|
||||
if (this._dest == 'client') {
|
||||
// Only server needs to send the source as the client is not allowed
|
||||
// to send voice for anyone besides itself
|
||||
offset += toVarint(chunk.source).value.copy(buffer, offset);
|
||||
}
|
||||
offset += toVarint(chunk.seqNum).value.copy(buffer, offset);
|
||||
offset += voiceData.copy(buffer, offset);
|
||||
if (chunk.position) {
|
||||
offset += buffer.writeFloatBE(chunk.position.x, offset);
|
||||
offset += buffer.writeFloatBE(chunk.position.y, offset);
|
||||
offset += buffer.writeFloatBE(chunk.position.z, offset);
|
||||
}
|
||||
// Trim buffer to actual length and pass through
|
||||
callback(null, buffer.slice(0, offset));
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform stream for decoding {@link VoiceData Mumble voice packets}
|
||||
* and {@link PingData audio channel ping packets}.
|
||||
*
|
||||
* @constructor
|
||||
* @constructs Decoder
|
||||
* @param {('server'|'client')} orig - Where encoded packets are coming from.
|
||||
*/
|
||||
function Decoder(orig) {
|
||||
// Allow use without new
|
||||
if (!(this instanceof Decoder)) return new Decoder(orig);
|
||||
|
||||
if (orig != 'server' && orig != 'client') {
|
||||
throw new TypeError('orig has to be either "server" or "client"');
|
||||
}
|
||||
|
||||
Transform.call(this, {
|
||||
readableObjectMode: true
|
||||
});
|
||||
|
||||
this._orig = orig;
|
||||
}
|
||||
util.inherits(Decoder, Transform);
|
||||
|
||||
Decoder.prototype._transform = function(chunk, encoding, callback) {
|
||||
var self = this
|
||||
var reject = function(reason) {
|
||||
self.emit('debug', 'Failed to parse voice packet', reason, chunk);
|
||||
callback();
|
||||
};
|
||||
|
||||
var packet = {};
|
||||
try {
|
||||
if (chunk.length == 0) return reject('empty');
|
||||
var codecId = chunk[0] >> 5;
|
||||
if (codecId == 1) { // Ping packet
|
||||
var val = fromVarint(chunk.slice(1));
|
||||
if (!val) return reject('invalid timestamp');
|
||||
packet.timestamp = val.value;
|
||||
} else { // Voice packet
|
||||
var target = chunk[0] & 0x1f
|
||||
packet.target = ['normal', 'shout', 'whisper'][target] || 'loopback';
|
||||
var offset = 1;
|
||||
|
||||
// Parse source if this packet originated from the server
|
||||
if (this._orig == 'server') {
|
||||
var source = fromVarint(chunk.slice(offset));
|
||||
if (!source) return reject('invalid source');
|
||||
offset += source.length;
|
||||
packet.source = source.value;
|
||||
}
|
||||
|
||||
// Parse the sequence number of the first audio packet
|
||||
var sequenceNumber = fromVarint(chunk.slice(offset));
|
||||
if (!sequenceNumber) return reject('invalid sequence number');
|
||||
offset += sequenceNumber.length;
|
||||
packet.seqNum = sequenceNumber.value;
|
||||
|
||||
// Parse the voice frames depending on the audio codec
|
||||
if (codecId == 4) {
|
||||
var voiceLength = fromVarint(chunk.slice(offset));
|
||||
if (!voiceLength) return reject('invalid voice length');
|
||||
packet.end = (voiceLength.value & 0x2000) > 0;
|
||||
voiceLength.value &= 0x1fff;
|
||||
offset += voiceLength.length;
|
||||
if (chunk.length < offset + voiceLength.value) {
|
||||
return reject('not enough voice data')
|
||||
}
|
||||
var voice = chunk.slice(offset, offset + voiceLength.value);
|
||||
offset += voiceLength.value;
|
||||
packet.frames = voice.length ? [voice] : [];
|
||||
packet.codec = 'Opus';
|
||||
} else if (codecId == 0 || codecIf == 2 || codecId == 3) {
|
||||
packet.codec = ['CELT_Alpha', '', 'Speex', 'CELT_Beta'][codecId];
|
||||
packet.frames = [];
|
||||
while (true) {
|
||||
if (chunk.length < offset + 1) return reject('missing frame header');
|
||||
var header = chunk[offset++];
|
||||
if (header == 0) {
|
||||
packet.end = true;
|
||||
break;
|
||||
}
|
||||
var more = (header & 0x80) > 0;
|
||||
var frameLength = header & 0x7F;
|
||||
|
||||
if (chunk.length < offset + frameLength) {
|
||||
return reject('not enough voice data');
|
||||
}
|
||||
packet.frames.push(chunk.slice(offset, offset += frameLength));
|
||||
|
||||
if (!more) {
|
||||
packet.end = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.emit('unknown_codec', codecId)
|
||||
return reject('unknown codec ' + codecId)
|
||||
}
|
||||
|
||||
// Parse positional data if existent
|
||||
if (chunk.length > offset + 12) {
|
||||
packet.position = {
|
||||
x: chunk.readFloatBE(offset),
|
||||
y: chunk.readFloatBE(offset + 4),
|
||||
z: chunk.readFloatBE(offset + 8)
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
return callback(null, packet);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Encoder: Encoder,
|
||||
Decoder: Decoder
|
||||
};
|
||||
|
||||
// Functions below from node-mumble
|
||||
// https://github.com/Rantanen/node-mumble/blob/master/LICENSE
|
||||
|
||||
/**
|
||||
* @summary Converts a number to Mumble varint.
|
||||
*
|
||||
* @see {@link http://mumble-protocol.readthedocs.org/en/latest/voice_data.html#variable-length-integer-encoding}
|
||||
*
|
||||
* @param {number} i - Integer to convert
|
||||
* @returns {Buffer} Varint encoded number
|
||||
*/
|
||||
function toVarint( i ) {
|
||||
|
||||
var arr = [];
|
||||
if( i < 0 ) {
|
||||
i = ~i;
|
||||
if( i <= 0x3 ) { return new Buffer( [ 0xFC | i ] ); }
|
||||
|
||||
arr.push( 0xF8 );
|
||||
}
|
||||
|
||||
if( i < 0x80 ) {
|
||||
arr.push( i );
|
||||
} else if( i < 0x4000 ) {
|
||||
arr.push( ( i >> 8 ) | 0x80 );
|
||||
arr.push( i & 0xFF );
|
||||
} else if( i < 0x200000 ) {
|
||||
arr.push( ( i >> 16 ) | 0xC0 );
|
||||
arr.push( ( i >> 8 ) & 0xFF );
|
||||
arr.push( i & 0xFF );
|
||||
} else if( i < 0x10000000 ) {
|
||||
arr.push( ( i >> 24 ) | 0xE0 );
|
||||
arr.push( ( i >> 16 ) & 0xFF );
|
||||
arr.push( ( i >> 8 ) & 0xFF );
|
||||
arr.push( i & 0xFF );
|
||||
} else if( i < 0x100000000 ) {
|
||||
arr.push( 0xF0 );
|
||||
arr.push( ( i >> 24 ) & 0xFF );
|
||||
arr.push( ( i >> 16 ) & 0xFF );
|
||||
arr.push( ( i >> 8 ) & 0xFF );
|
||||
arr.push( i & 0xFF );
|
||||
} else {
|
||||
throw new TypeError( 'Non-integer values are not supported. (' + i + ')' );
|
||||
}
|
||||
|
||||
return {
|
||||
value: new Buffer( arr ),
|
||||
length: arr.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Converts a Mumble varint to an integer.
|
||||
*
|
||||
* @see {@link http://mumble-protocol.readthedocs.org/en/latest/voice_data.html#variable-length-integer-encoding}
|
||||
*
|
||||
* @param {Buffer} b - Varint to convert
|
||||
* @returns {number} Decoded integer
|
||||
*/
|
||||
function fromVarint( b ) {
|
||||
if (b.length == 0) return null;
|
||||
var length = 1;
|
||||
var i, v = b[ 0 ];
|
||||
if( ( v & 0x80 ) === 0x00 ) {
|
||||
i = ( v & 0x7F );
|
||||
} else if( ( v & 0xC0 ) === 0x80 ) {
|
||||
i = ( v & 0x3F ) << 8 | b[ 1 ];
|
||||
length = 2;
|
||||
} else if( ( v & 0xF0 ) === 0xF0 ) {
|
||||
switch( v & 0xFC ) {
|
||||
case 0xF0:
|
||||
i = b[ 1 ] << 24 | b[ 2 ] << 16 | b[ 3 ] << 8 | b[ 4 ];
|
||||
length = 5;
|
||||
break;
|
||||
case 0xF8:
|
||||
var ret = fromVarint( b.slice( 1 ) );
|
||||
if (!ret) return ret;
|
||||
return {
|
||||
value: ~ret.value,
|
||||
length: 1 + ret.length
|
||||
};
|
||||
case 0xFC:
|
||||
i = v & 0x03;
|
||||
i = ~i;
|
||||
break;
|
||||
default:
|
||||
return null
|
||||
}
|
||||
} else if( ( v & 0xF0 ) === 0xE0 ) {
|
||||
i = ( v & 0x0F ) << 24 | b[ 1 ] << 16 | b[ 2 ] << 8 | b[ 3 ];
|
||||
length = 4;
|
||||
} else if( ( v & 0xE0 ) === 0xC0 ) {
|
||||
i = ( v & 0x1F ) << 16 | b[ 1 ] << 8 | b[ 2 ];
|
||||
length = 3;
|
||||
}
|
||||
|
||||
return {
|
||||
value: i,
|
||||
length: length
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user