@@ -110,6 +110,9 @@ | |||||
<Compile Include="..\Discord.Net\Helpers\JsonHttpClient.cs"> | <Compile Include="..\Discord.Net\Helpers\JsonHttpClient.cs"> | ||||
<Link>Helpers\JsonHttpClient.cs</Link> | <Link>Helpers\JsonHttpClient.cs</Link> | ||||
</Compile> | </Compile> | ||||
<Compile Include="..\Discord.Net\Helpers\JsonHttpClient.Events.cs"> | |||||
<Link>Helpers\JsonHttpClient.Events.cs</Link> | |||||
</Compile> | |||||
<Compile Include="..\Discord.Net\HttpException.cs"> | <Compile Include="..\Discord.Net\HttpException.cs"> | ||||
<Link>HttpException.cs</Link> | <Link>HttpException.cs</Link> | ||||
</Compile> | </Compile> | ||||
@@ -10,7 +10,10 @@ namespace Discord | |||||
WebSocketRawInput, //TODO: Make Http instanced and add a rawoutput event | WebSocketRawInput, //TODO: Make Http instanced and add a rawoutput event | ||||
WebSocketUnknownOpCode, | WebSocketUnknownOpCode, | ||||
WebSocketUnknownEvent, | WebSocketUnknownEvent, | ||||
VoiceOutput | |||||
XHRRawOutput, | |||||
XHRTiming, | |||||
VoiceInput, | |||||
VoiceOutput, | |||||
} | } | ||||
public sealed class LogMessageEventArgs : EventArgs | public sealed class LogMessageEventArgs : EventArgs | ||||
{ | { | ||||
@@ -90,8 +90,6 @@ namespace Discord | |||||
_blockEvent = new ManualResetEventSlim(true); | _blockEvent = new ManualResetEventSlim(true); | ||||
_config = config ?? new DiscordClientConfig(); | _config = config ?? new DiscordClientConfig(); | ||||
_rand = new Random(); | _rand = new Random(); | ||||
_http = new JsonHttpClient(); | |||||
_api = new DiscordAPI(_http); | |||||
_serializer = new JsonSerializer(); | _serializer = new JsonSerializer(); | ||||
#if TEST_RESPONSES | #if TEST_RESPONSES | ||||
@@ -367,7 +365,12 @@ namespace Discord | |||||
} | } | ||||
); | ); | ||||
_webSocket = new DiscordDataSocket(this, _config.ConnectionTimeout, _config.WebSocketInterval); | |||||
_http = new JsonHttpClient(config.EnableDebug); | |||||
_api = new DiscordAPI(_http); | |||||
if (_config.EnableDebug) | |||||
_http.OnDebugMessage += (s, e) => RaiseOnDebugMessage(e.Type, e.Message); | |||||
_webSocket = new DiscordDataSocket(this, config.ConnectionTimeout, config.WebSocketInterval, config.EnableDebug); | |||||
_webSocket.Connected += (s, e) => RaiseConnected(); | _webSocket.Connected += (s, e) => RaiseConnected(); | ||||
_webSocket.Disconnected += async (s, e) => | _webSocket.Disconnected += async (s, e) => | ||||
{ | { | ||||
@@ -406,7 +409,7 @@ namespace Discord | |||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
if (_config.EnableVoice) | if (_config.EnableVoice) | ||||
{ | { | ||||
_voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval); | |||||
_voiceWebSocket = new DiscordVoiceSocket(this, _config.VoiceConnectionTimeout, _config.WebSocketInterval, config.EnableDebug); | |||||
_voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); | _voiceWebSocket.Connected += (s, e) => RaiseVoiceConnected(); | ||||
_voiceWebSocket.Disconnected += async (s, e) => | _voiceWebSocket.Disconnected += async (s, e) => | ||||
{ | { | ||||
@@ -1,5 +1,4 @@ | |||||
using Discord.API.Models; | using Discord.API.Models; | ||||
using Discord.Helpers; | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||
using System; | using System; | ||||
@@ -13,8 +12,8 @@ namespace Discord | |||||
{ | { | ||||
private readonly ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | private readonly ManualResetEventSlim _connectWaitOnLogin, _connectWaitOnLogin2; | ||||
public DiscordDataSocket(DiscordClient client, int timeout, int interval) | |||||
: base(client, timeout, interval) | |||||
public DiscordDataSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||||
: base(client, timeout, interval, isDebug) | |||||
{ | { | ||||
_connectWaitOnLogin = new ManualResetEventSlim(false); | _connectWaitOnLogin = new ManualResetEventSlim(false); | ||||
_connectWaitOnLogin2 = new ManualResetEventSlim(false); | _connectWaitOnLogin2 = new ManualResetEventSlim(false); | ||||
@@ -72,7 +71,8 @@ namespace Discord | |||||
} | } | ||||
break; | break; | ||||
default: | default: | ||||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown DataSocket op: " + msg.Operation); | |||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown DataSocket op: " + msg.Operation); | |||||
break; | break; | ||||
} | } | ||||
#if DNXCORE | #if DNXCORE | ||||
@@ -49,8 +49,8 @@ namespace Discord | |||||
#endif | #endif | ||||
#endif | #endif | ||||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval) | |||||
: base(client, timeout, interval) | |||||
public DiscordVoiceSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||||
: base(client, timeout, interval, isDebug) | |||||
{ | { | ||||
_connectWaitOnLogin = new ManualResetEventSlim(false); | _connectWaitOnLogin = new ManualResetEventSlim(false); | ||||
#if !DNXCORE50 | #if !DNXCORE50 | ||||
@@ -287,7 +287,8 @@ namespace Discord | |||||
break; | break; | ||||
#endif | #endif | ||||
default: | default: | ||||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown VoiceSocket op: " + msg.Operation); | |||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.WebSocketUnknownOpCode, "Unknown VoiceSocket op: " + msg.Operation); | |||||
break; | break; | ||||
} | } | ||||
#if DNXCORE50 | #if DNXCORE50 | ||||
@@ -322,25 +323,42 @@ namespace Discord | |||||
else | else | ||||
{ | { | ||||
//Parse RTP Data | //Parse RTP Data | ||||
/*if (length < 12) | |||||
throw new Exception($"Unexpected message length. Expected >= 12, got {length}."); | |||||
if (length < 12) | |||||
{ | |||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected message length. Expected >= 12, got {length}."); | |||||
return; | |||||
} | |||||
byte flags = buffer[0]; | byte flags = buffer[0]; | ||||
if (flags != 0x80) | if (flags != 0x80) | ||||
throw new Exception("Unexpected Flags"); | |||||
{ | |||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Flags: {flags}"); | |||||
return; | |||||
} | |||||
byte payloadType = buffer[1]; | byte payloadType = buffer[1]; | ||||
if (payloadType != 0x78) | if (payloadType != 0x78) | ||||
throw new Exception("Unexpected Payload Type"); | |||||
{ | |||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Unexpected Payload Type: {flags}"); | |||||
return; | |||||
} | |||||
ushort sequenceNumber = (ushort)((buffer[2] << 8) | buffer[3]); | |||||
uint timestamp = (uint)((buffer[4] << 24) | (buffer[5] << 16) | | |||||
(buffer[6] << 8) | (buffer[7] << 0)); | |||||
uint ssrc = (uint)((buffer[8] << 24) | (buffer[9] << 16) | | |||||
(buffer[10] << 8) | (buffer[11] << 0)); | |||||
ushort sequenceNumber = (ushort)((buffer[2] << 8) | | |||||
buffer[3] << 0); | |||||
uint timestamp = (uint)((buffer[4] << 24) | | |||||
(buffer[5] << 16) | | |||||
(buffer[6] << 8) | | |||||
(buffer[7] << 0)); | |||||
uint ssrc = (uint)((buffer[8] << 24) | | |||||
(buffer[9] << 16) | | |||||
(buffer[10] << 8) | | |||||
(buffer[11] << 0)); | |||||
//Decrypt | //Decrypt | ||||
if (_mode == "xsalsa20_poly1305") | |||||
/*if (_mode == "xsalsa20_poly1305") | |||||
{ | { | ||||
if (length < 36) //12 + 24 | if (length < 36) //12 + 24 | ||||
throw new Exception($"Unexpected message length. Expected >= 36, got {length}."); | throw new Exception($"Unexpected message length. Expected >= 36, got {length}."); | ||||
@@ -362,6 +380,8 @@ namespace Discord | |||||
buffer = newBuffer; | buffer = newBuffer; | ||||
}*/ | }*/ | ||||
if (_isDebug) | |||||
RaiseOnDebugMessage(DebugMessageType.VoiceInput, $"Received {buffer.Length - 12} bytes."); | |||||
//TODO: Use Voice Data | //TODO: Use Voice Data | ||||
} | } | ||||
} | } | ||||
@@ -14,22 +14,24 @@ namespace Discord | |||||
private const int SendChunkSize = 4096; | private const int SendChunkSize = 4096; | ||||
protected readonly DiscordClient _client; | protected readonly DiscordClient _client; | ||||
protected volatile CancellationTokenSource _disconnectToken; | |||||
protected int _timeout, _heartbeatInterval; | |||||
protected readonly int _sendInterval; | protected readonly int _sendInterval; | ||||
protected string _host; | |||||
protected readonly bool _isDebug; | |||||
private readonly ConcurrentQueue<byte[]> _sendQueue; | |||||
protected volatile CancellationTokenSource _disconnectToken; | |||||
private volatile ClientWebSocket _webSocket; | private volatile ClientWebSocket _webSocket; | ||||
private volatile Task _tasks; | private volatile Task _tasks; | ||||
private ConcurrentQueue<byte[]> _sendQueue; | |||||
protected string _host; | |||||
protected int _timeout, _heartbeatInterval; | |||||
private DateTime _lastHeartbeat; | private DateTime _lastHeartbeat; | ||||
private bool _isConnected; | private bool _isConnected; | ||||
public DiscordWebSocket(DiscordClient client, int timeout, int interval) | |||||
public DiscordWebSocket(DiscordClient client, int timeout, int interval, bool isDebug) | |||||
{ | { | ||||
_client = client; | _client = client; | ||||
_timeout = timeout; | _timeout = timeout; | ||||
_sendInterval = interval; | _sendInterval = interval; | ||||
_isDebug = isDebug; | |||||
_sendQueue = new ConcurrentQueue<byte[]>(); | _sendQueue = new ConcurrentQueue<byte[]>(); | ||||
} | } | ||||
@@ -0,0 +1,14 @@ | |||||
using System; | |||||
namespace Discord.Helpers | |||||
{ | |||||
internal partial class JsonHttpClient | |||||
{ | |||||
public event EventHandler<LogMessageEventArgs> OnDebugMessage; | |||||
protected void RaiseOnDebugMessage(DebugMessageType type, string message) | |||||
{ | |||||
if (OnDebugMessage != null) | |||||
OnDebugMessage(this, new LogMessageEventArgs(type, message)); | |||||
} | |||||
} | |||||
} |
@@ -1,7 +1,6 @@ | |||||
using Discord.API; | using Discord.API; | ||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System; | using System; | ||||
using System.Diagnostics; | |||||
using System.IO; | using System.IO; | ||||
using System.Globalization; | using System.Globalization; | ||||
using System.Net.Http; | using System.Net.Http; | ||||
@@ -9,24 +8,22 @@ using System.Net; | |||||
using System.Reflection; | using System.Reflection; | ||||
using System.Text; | using System.Text; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using System.Diagnostics; | |||||
namespace Discord.Helpers | namespace Discord.Helpers | ||||
{ | { | ||||
internal class JsonHttpClient | |||||
internal partial class JsonHttpClient | |||||
{ | { | ||||
#if TEST_RESPONSES | |||||
private const bool _isDebug = true; | |||||
#else | |||||
private const bool _isDebug = false; | |||||
#endif | |||||
private bool _isDebug; | |||||
private readonly HttpClient _client; | private readonly HttpClient _client; | ||||
private readonly HttpMethod _patch; | private readonly HttpMethod _patch; | ||||
#if TEST_RESPONSES | #if TEST_RESPONSES | ||||
private readonly JsonSerializerSettings _settings; | private readonly JsonSerializerSettings _settings; | ||||
#endif | #endif | ||||
public JsonHttpClient() | |||||
public JsonHttpClient(bool isDebug) | |||||
{ | { | ||||
_isDebug = isDebug; | |||||
_patch = new HttpMethod("PATCH"); //Not sure why this isn't a default... | _patch = new HttpMethod("PATCH"); //Not sure why this isn't a default... | ||||
_client = new HttpClient(new HttpClientHandler | _client = new HttpClient(new HttpClientHandler | ||||
@@ -131,8 +128,8 @@ namespace Discord.Helpers | |||||
private async Task<string> Send(HttpMethod method, string path, HttpContent content) | private async Task<string> Send(HttpMethod method, string path, HttpContent content) | ||||
{ | { | ||||
string responseJson = await SendRequest(method, path, content, true); | string responseJson = await SendRequest(method, path, content, true); | ||||
if (path.StartsWith(Endpoints.BaseApi)) | |||||
CheckEmptyResponse(responseJson); | |||||
if (path.StartsWith(Endpoints.BaseApi) && !string.IsNullOrEmpty(responseJson)) | |||||
throw new Exception("API check failed: Response is not empty."); | |||||
return responseJson; | return responseJson; | ||||
} | } | ||||
#else | #else | ||||
@@ -142,9 +139,25 @@ namespace Discord.Helpers | |||||
private async Task<string> SendRequest(HttpMethod method, string path, HttpContent content, bool hasResponse) | private async Task<string> SendRequest(HttpMethod method, string path, HttpContent content, bool hasResponse) | ||||
{ | { | ||||
#if TEST_RESPONSES | |||||
Stopwatch stopwatch = Stopwatch.StartNew(); | |||||
#endif | |||||
Stopwatch stopwatch = null; | |||||
if (_isDebug) | |||||
{ | |||||
if (content != null) | |||||
{ | |||||
if (content is StringContent) | |||||
{ | |||||
string json = await (content as StringContent).ReadAsStringAsync(); | |||||
RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {json}"); | |||||
} | |||||
else | |||||
{ | |||||
byte[] bytes = await content.ReadAsByteArrayAsync(); | |||||
RaiseOnDebugMessage(DebugMessageType.XHRRawOutput, $"{method} {path}: {bytes.Length} bytes"); | |||||
} | |||||
} | |||||
stopwatch = Stopwatch.StartNew(); | |||||
} | |||||
string result; | string result; | ||||
using (HttpRequestMessage msg = new HttpRequestMessage(method, path)) | using (HttpRequestMessage msg = new HttpRequestMessage(method, path)) | ||||
{ | { | ||||
@@ -168,21 +181,14 @@ namespace Discord.Helpers | |||||
} | } | ||||
} | } | ||||
#if TEST_RESPONSES | |||||
stopwatch.Stop(); | |||||
Debug.WriteLine($"{method} {path}: {Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2)}ms"); | |||||
#endif | |||||
if (_isDebug) | |||||
{ | |||||
stopwatch.Stop(); | |||||
RaiseOnDebugMessage(DebugMessageType.XHRTiming, $"{method} {path}: {Math.Round(stopwatch.ElapsedTicks / (double)TimeSpan.TicksPerMillisecond, 2)}ms"); | |||||
} | |||||
return result; | return result; | ||||
} | } | ||||
#if TEST_RESPONSES | |||||
private void CheckEmptyResponse(string json) | |||||
{ | |||||
if (!string.IsNullOrEmpty(json)) | |||||
throw new Exception("API check failed: Response is not empty."); | |||||
} | |||||
#endif | |||||
private StringContent AsJson(object obj) | private StringContent AsJson(object obj) | ||||
{ | { | ||||
return new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); | return new StringContent(JsonConvert.SerializeObject(obj), Encoding.UTF8, "application/json"); | ||||