mirror of
https://github.com/EnderIce2/SDR-RPC.git
synced 2025-07-11 19:49:15 +00:00
First commit
This commit is contained in:
23
DiscordAPI/IO/Handshake.cs
Normal file
23
DiscordAPI/IO/Handshake.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordRPC.IO
|
||||
{
|
||||
internal class Handshake
|
||||
{
|
||||
/// <summary>
|
||||
/// Version of the IPC API we are using
|
||||
/// </summary>
|
||||
[JsonProperty("v")]
|
||||
public int Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the app.
|
||||
/// </summary>
|
||||
[JsonProperty("client_id")]
|
||||
public string ClientID { get; set; }
|
||||
}
|
||||
}
|
56
DiscordAPI/IO/INamedPipeClient.cs
Normal file
56
DiscordAPI/IO/INamedPipeClient.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using DiscordRPC.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordRPC.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Pipe Client used to communicate with Discord.
|
||||
/// </summary>
|
||||
public interface INamedPipeClient : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The logger for the Pipe client to use
|
||||
/// </summary>
|
||||
ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is the pipe client currently connected?
|
||||
/// </summary>
|
||||
bool IsConnected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The pipe the client is currently connected too
|
||||
/// </summary>
|
||||
int ConnectedPipe { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to connect to the pipe. If 0-9 is passed to pipe, it should try to only connect to the specified pipe. If -1 is passed, the pipe will find the first available pipe.
|
||||
/// </summary>
|
||||
/// <param name="pipe">If -1 is passed, the pipe will find the first available pipe, otherwise it connects to the pipe that was supplied</param>
|
||||
/// <returns></returns>
|
||||
bool Connect(int pipe);
|
||||
|
||||
/// <summary>
|
||||
/// Reads a frame if there is one available. Returns false if there is none. This should be non blocking (aka use a Peek first).
|
||||
/// </summary>
|
||||
/// <param name="frame">The frame that has been read. Will be <code>default(PipeFrame)</code> if it fails to read</param>
|
||||
/// <returns>Returns true if a frame has been read, otherwise false.</returns>
|
||||
bool ReadFrame(out PipeFrame frame);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the frame to the pipe. Returns false if any errors occur.
|
||||
/// </summary>
|
||||
/// <param name="frame">The frame to be written</param>
|
||||
bool WriteFrame(PipeFrame frame);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the connection
|
||||
/// </summary>
|
||||
void Close();
|
||||
|
||||
}
|
||||
}
|
509
DiscordAPI/IO/ManagedNamedPipeClient.cs
Normal file
509
DiscordAPI/IO/ManagedNamedPipeClient.cs
Normal file
@ -0,0 +1,509 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using DiscordRPC.Logging;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
|
||||
namespace DiscordRPC.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// A named pipe client using the .NET framework <see cref="NamedPipeClientStream"/>
|
||||
/// </summary>
|
||||
public sealed class ManagedNamedPipeClient : INamedPipeClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Name format of the pipe
|
||||
/// </summary>
|
||||
const string PIPE_NAME = @"discord-ipc-{0}";
|
||||
|
||||
/// <summary>
|
||||
/// The logger for the Pipe client to use
|
||||
/// </summary>
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the client is connected
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
//This will trigger if the stream is disabled. This should prevent the lock check
|
||||
if (_isClosed) return false;
|
||||
lock (l_stream)
|
||||
{
|
||||
//We cannot be sure its still connected, so lets double check
|
||||
return _stream != null && _stream.IsConnected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pipe we are currently connected too.
|
||||
/// </summary>
|
||||
public int ConnectedPipe { get { return _connectedPipe; } }
|
||||
|
||||
private int _connectedPipe;
|
||||
private NamedPipeClientStream _stream;
|
||||
|
||||
private byte[] _buffer = new byte[PipeFrame.MAX_SIZE];
|
||||
|
||||
private Queue<PipeFrame> _framequeue = new Queue<PipeFrame>();
|
||||
private object _framequeuelock = new object();
|
||||
|
||||
private volatile bool _isDisposed = false;
|
||||
private volatile bool _isClosed = true;
|
||||
|
||||
private object l_stream = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of a Managed NamedPipe client. Doesn't connect to anything yet, just setups the values.
|
||||
/// </summary>
|
||||
public ManagedNamedPipeClient()
|
||||
{
|
||||
_buffer = new byte[PipeFrame.MAX_SIZE];
|
||||
Logger = new NullLogger();
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the pipe
|
||||
/// </summary>
|
||||
/// <param name="pipe"></param>
|
||||
/// <returns></returns>
|
||||
public bool Connect(int pipe)
|
||||
{
|
||||
Logger.Trace("ManagedNamedPipeClient.Connection(" + pipe + ")");
|
||||
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException("NamedPipe");
|
||||
|
||||
if (pipe > 9)
|
||||
throw new ArgumentOutOfRangeException("pipe", "Argument cannot be greater than 9");
|
||||
|
||||
if (pipe < 0)
|
||||
{
|
||||
//Iterate until we connect to a pipe
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
if (AttemptConnection(i) || AttemptConnection(i, true))
|
||||
{
|
||||
BeginReadStream();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Attempt to connect to a specific pipe
|
||||
if (AttemptConnection(pipe) || AttemptConnection(pipe, true))
|
||||
{
|
||||
BeginReadStream();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//We failed to connect
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts a new connection
|
||||
/// </summary>
|
||||
/// <param name="pipe">The pipe number to connect too.</param>
|
||||
/// <param name="isSandbox">Should the connection to a sandbox be attempted?</param>
|
||||
/// <returns></returns>
|
||||
private bool AttemptConnection(int pipe, bool isSandbox = false)
|
||||
{
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException("_stream");
|
||||
|
||||
//If we are sandbox but we dont support sandbox, then skip
|
||||
string sandbox = isSandbox ? GetPipeSandbox() : "";
|
||||
if (isSandbox && sandbox == null)
|
||||
{
|
||||
Logger.Trace("Skipping sandbox connection.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Prepare the pipename
|
||||
Logger.Trace("Connection Attempt " + pipe + " (" + sandbox + ")");
|
||||
string pipename = GetPipeName(pipe, sandbox);
|
||||
|
||||
try
|
||||
{
|
||||
//Create the client
|
||||
lock (l_stream)
|
||||
{
|
||||
Logger.Info("Attempting to connect to " + pipename);
|
||||
_stream = new NamedPipeClientStream(".", pipename, PipeDirection.InOut, PipeOptions.Asynchronous);
|
||||
_stream.Connect(1000);
|
||||
|
||||
//Spin for a bit while we wait for it to finish connecting
|
||||
Logger.Trace("Waiting for connection...");
|
||||
do { Thread.Sleep(10); } while (!_stream.IsConnected);
|
||||
}
|
||||
|
||||
//Store the value
|
||||
Logger.Info("Connected to " + pipename);
|
||||
_connectedPipe = pipe;
|
||||
_isClosed = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Something happened, try again
|
||||
//TODO: Log the failure condition
|
||||
Logger.Error("Failed connection to {0}. {1}", pipename, e.Message);
|
||||
Close();
|
||||
}
|
||||
|
||||
Logger.Trace("Done. Result: {0}", _isClosed);
|
||||
return !_isClosed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts a read. Can be executed in another thread.
|
||||
/// </summary>
|
||||
private void BeginReadStream()
|
||||
{
|
||||
if (_isClosed) return;
|
||||
try
|
||||
{
|
||||
lock (l_stream)
|
||||
{
|
||||
//Make sure the stream is valid
|
||||
if (_stream == null || !_stream.IsConnected) return;
|
||||
|
||||
Logger.Trace("Begining Read of {0} bytes", _buffer.Length);
|
||||
_stream.BeginRead(_buffer, 0, _buffer.Length, new AsyncCallback(EndReadStream), _stream.IsConnected);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Logger.Warning("Attempted to start reading from a disposed pipe");
|
||||
return;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//The pipe has been closed
|
||||
Logger.Warning("Attempted to start reading from a closed pipe");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("An exception occured while starting to read a stream: {0}", e.Message);
|
||||
Logger.Error(e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends a read. Can be executed in another thread.
|
||||
/// </summary>
|
||||
/// <param name="callback"></param>
|
||||
private void EndReadStream(IAsyncResult callback)
|
||||
{
|
||||
Logger.Trace("Ending Read");
|
||||
int bytes = 0;
|
||||
|
||||
try
|
||||
{
|
||||
//Attempt to read the bytes, catching for IO exceptions or dispose exceptions
|
||||
lock (l_stream)
|
||||
{
|
||||
//Make sure the stream is still valid
|
||||
if (_stream == null || !_stream.IsConnected) return;
|
||||
|
||||
//Read our btyes
|
||||
bytes = _stream.EndRead(callback);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
Logger.Warning("Attempted to end reading from a closed pipe");
|
||||
return;
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
Logger.Warning("Attempted to read from a null pipe");
|
||||
return;
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Logger.Warning("Attemped to end reading from a disposed pipe");
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("An exception occured while ending a read of a stream: {0}", e.Message);
|
||||
Logger.Error(e.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
//How much did we read?
|
||||
Logger.Trace("Read {0} bytes", bytes);
|
||||
|
||||
//Did we read anything? If we did we should enqueue it.
|
||||
if (bytes > 0)
|
||||
{
|
||||
//Load it into a memory stream and read the frame
|
||||
using (MemoryStream memory = new MemoryStream(_buffer, 0, bytes))
|
||||
{
|
||||
try
|
||||
{
|
||||
PipeFrame frame = new PipeFrame();
|
||||
if (frame.ReadStream(memory))
|
||||
{
|
||||
Logger.Trace("Read a frame: {0}", frame.Opcode);
|
||||
|
||||
//Enqueue the stream
|
||||
lock (_framequeuelock)
|
||||
_framequeue.Enqueue(frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: Enqueue a pipe close event here as we failed to read something.
|
||||
Logger.Error("Pipe failed to read from the data received by the stream.");
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error("A exception has occured while trying to parse the pipe data: " + e.Message);
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we read 0 bytes, its probably a broken pipe. However, I have only confirmed this is the case for MacOSX.
|
||||
// I have added this check here just so the Windows builds are not effected and continue to work as expected.
|
||||
if (IsUnix())
|
||||
{
|
||||
Logger.Error("Empty frame was read on " + Environment.OSVersion.ToString() + ", aborting.");
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning("Empty frame was read. Please send report to Lachee.");
|
||||
}
|
||||
}
|
||||
|
||||
//We are still connected, so continue to read
|
||||
if (!_isClosed && IsConnected)
|
||||
{
|
||||
Logger.Trace("Starting another read");
|
||||
BeginReadStream();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a frame, returning false if none are available
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadFrame(out PipeFrame frame)
|
||||
{
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException("_stream");
|
||||
|
||||
//Check the queue, returning the pipe if we have anything available. Otherwise null.
|
||||
lock (_framequeuelock)
|
||||
{
|
||||
if (_framequeue.Count == 0)
|
||||
{
|
||||
//We found nothing, so just default and return null
|
||||
frame = default(PipeFrame);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Return the dequed frame
|
||||
frame = _framequeue.Dequeue();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a frame to the pipe
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
/// <returns></returns>
|
||||
public bool WriteFrame(PipeFrame frame)
|
||||
{
|
||||
if (_isDisposed)
|
||||
throw new ObjectDisposedException("_stream");
|
||||
|
||||
//Write the frame. We are assuming proper duplex connection here
|
||||
if (_isClosed || !IsConnected)
|
||||
{
|
||||
Logger.Error("Failed to write frame because the stream is closed");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//Write the pipe
|
||||
//This can only happen on the main thread so it should be fine.
|
||||
frame.WriteStream(_stream);
|
||||
return true;
|
||||
}
|
||||
catch (IOException io)
|
||||
{
|
||||
Logger.Error("Failed to write frame because of a IO Exception: {0}", io.Message);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Logger.Warning("Failed to write frame as the stream was already disposed");
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Logger.Warning("Failed to write frame because of a invalid operation");
|
||||
}
|
||||
|
||||
//We must have failed the try catch
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the pipe
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
//If we are already closed, jsut exit
|
||||
if (_isClosed)
|
||||
{
|
||||
Logger.Warning("Tried to close a already closed pipe.");
|
||||
return;
|
||||
}
|
||||
|
||||
//flush and dispose
|
||||
try
|
||||
{
|
||||
//Wait for the stream object to become available.
|
||||
lock (l_stream)
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
//Stream isn't null, so flush it and then dispose of it.\
|
||||
// We are doing a catch here because it may throw an error during this process and we dont care if it fails.
|
||||
_stream.Flush();
|
||||
_stream.Dispose();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//We caught an error, but we dont care anyways because we are disposing of the stream.
|
||||
}
|
||||
|
||||
//Make the stream null and set our flag.
|
||||
_stream = null;
|
||||
_isClosed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//The stream is already null?
|
||||
Logger.Warning("Stream was closed, but no stream was available to begin with!");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
//ITs already been disposed
|
||||
Logger.Warning("Tried to dispose already disposed stream");
|
||||
}
|
||||
finally
|
||||
{
|
||||
//For good measures, we will mark the pipe as closed anyways
|
||||
_isClosed = true;
|
||||
_connectedPipe = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of the stream
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
//Prevent double disposing
|
||||
if (_isDisposed) return;
|
||||
|
||||
//Close the stream (disposing of it too)
|
||||
if (!_isClosed) Close();
|
||||
|
||||
//Dispose of the stream if it hasnt been destroyed already.
|
||||
lock (l_stream)
|
||||
{
|
||||
if (_stream != null)
|
||||
{
|
||||
_stream.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
}
|
||||
|
||||
//Set our dispose flag
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a platform specific path that Discord is hosting the IPC on.
|
||||
/// </summary>
|
||||
/// <param name="pipe">The pipe number.</param>
|
||||
/// <param name="sandbox">The sandbox the pipe is in. Leave blank for no sandbox.</param>
|
||||
/// <returns></returns>
|
||||
public static string GetPipeName(int pipe, string sandbox = "")
|
||||
{
|
||||
if (!IsUnix()) return sandbox + string.Format(PIPE_NAME, pipe);
|
||||
return Path.Combine(GetTemporaryDirectory(), sandbox + string.Format(PIPE_NAME, pipe));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the possible sandbox enviroment the pipe might be located within. If the platform doesn't support sandboxed Discord, then it will return null.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetPipeSandbox()
|
||||
{
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
default:
|
||||
return null;
|
||||
case PlatformID.Unix:
|
||||
return "snap.discord/";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the temporary path for the current enviroment. Only applicable for UNIX based systems.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private static string GetTemporaryDirectory()
|
||||
{
|
||||
string temp = null;
|
||||
temp = temp ?? Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR");
|
||||
temp = temp ?? Environment.GetEnvironmentVariable("TMPDIR");
|
||||
temp = temp ?? Environment.GetEnvironmentVariable("TMP");
|
||||
temp = temp ?? Environment.GetEnvironmentVariable("TEMP");
|
||||
temp = temp ?? "/tmp";
|
||||
return temp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current OS platform is Unix based (Unix or MacOSX).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static bool IsUnix()
|
||||
{
|
||||
switch (Environment.OSVersion.Platform)
|
||||
{
|
||||
default:
|
||||
return false;
|
||||
|
||||
case PlatformID.Unix:
|
||||
case PlatformID.MacOSX:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
DiscordAPI/IO/Opcode.cs
Normal file
33
DiscordAPI/IO/Opcode.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace DiscordRPC.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// The operation code that the <see cref="PipeFrame"/> was sent under. This defines the type of frame and the data to expect.
|
||||
/// </summary>
|
||||
public enum Opcode : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Initial handshake frame
|
||||
/// </summary>
|
||||
Handshake = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Generic message frame
|
||||
/// </summary>
|
||||
Frame = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Discord has closed the connection
|
||||
/// </summary>
|
||||
Close = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Ping frame (not used?)
|
||||
/// </summary>
|
||||
Ping = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Pong frame (not used?)
|
||||
/// </summary>
|
||||
Pong = 4
|
||||
}
|
||||
}
|
204
DiscordAPI/IO/PipeFrame.cs
Normal file
204
DiscordAPI/IO/PipeFrame.cs
Normal file
@ -0,0 +1,204 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace DiscordRPC.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// A frame received and sent to the Discord client for RPC communications.
|
||||
/// </summary>
|
||||
public struct PipeFrame
|
||||
{
|
||||
/// <summary>
|
||||
/// The maxium size of a pipe frame (16kb).
|
||||
/// </summary>
|
||||
public static readonly int MAX_SIZE = 16 * 1024;
|
||||
|
||||
/// <summary>
|
||||
/// The opcode of the frame
|
||||
/// </summary>
|
||||
public Opcode Opcode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the frame data
|
||||
/// </summary>
|
||||
public uint Length { get { return (uint) Data.Length; } }
|
||||
|
||||
/// <summary>
|
||||
/// The data in the frame
|
||||
/// </summary>
|
||||
public byte[] Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The data represented as a string.
|
||||
/// </summary>
|
||||
public string Message
|
||||
{
|
||||
get { return GetMessage(); }
|
||||
set { SetMessage(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new pipe frame instance
|
||||
/// </summary>
|
||||
/// <param name="opcode">The opcode of the frame</param>
|
||||
/// <param name="data">The data of the frame that will be serialized as JSON</param>
|
||||
public PipeFrame(Opcode opcode, object data)
|
||||
{
|
||||
//Set the opcode and a temp field for data
|
||||
Opcode = opcode;
|
||||
Data = null;
|
||||
|
||||
//Set the data
|
||||
SetObject(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the encoding used for the pipe frames
|
||||
/// </summary>
|
||||
public Encoding MessageEncoding { get { return Encoding.UTF8; } }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the data based of a string
|
||||
/// </summary>
|
||||
/// <param name="str"></param>
|
||||
private void SetMessage(string str) { Data = MessageEncoding.GetBytes(str); }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a string based of the data
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private string GetMessage() { return MessageEncoding.GetString(Data); }
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the object into json string then encodes it into <see cref="Data"/>.
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
public void SetObject(object obj)
|
||||
{
|
||||
string json = JsonConvert.SerializeObject(obj);
|
||||
SetMessage(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the opcodes and serializes the object into a json string.
|
||||
/// </summary>
|
||||
/// <param name="opcode"></param>
|
||||
/// <param name="obj"></param>
|
||||
public void SetObject(Opcode opcode, object obj)
|
||||
{
|
||||
Opcode = opcode;
|
||||
SetObject(obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes the data into the supplied type using JSON.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to deserialize into</typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetObject<T>()
|
||||
{
|
||||
string json = GetMessage();
|
||||
return JsonConvert.DeserializeObject<T>(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to read the contents of the frame from the stream
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
public bool ReadStream(Stream stream)
|
||||
{
|
||||
//Try to read the opcode
|
||||
uint op;
|
||||
if (!TryReadUInt32(stream, out op))
|
||||
return false;
|
||||
|
||||
//Try to read the length
|
||||
uint len;
|
||||
if (!TryReadUInt32(stream, out len))
|
||||
return false;
|
||||
|
||||
uint readsRemaining = len;
|
||||
|
||||
//Read the contents
|
||||
using (var mem = new MemoryStream())
|
||||
{
|
||||
byte[] buffer = new byte[Min(2048, len)]; // read in chunks of 2KB
|
||||
int bytesRead;
|
||||
while ((bytesRead = stream.Read(buffer, 0, Min(buffer.Length, readsRemaining))) > 0)
|
||||
{
|
||||
readsRemaining -= len;
|
||||
mem.Write(buffer, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] result = mem.ToArray();
|
||||
if (result.LongLength != len)
|
||||
return false;
|
||||
|
||||
Opcode = (Opcode)op;
|
||||
Data = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
//fun
|
||||
//if (a != null) { do { yield return true; switch (a) { case 1: await new Task(); default: lock (obj) { foreach (b in c) { for (int d = 0; d < 1; d++) { a++; } } } while (a is typeof(int) || (new Class()) != null) } goto MY_LABEL;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns minimum value between a int and a unsigned int
|
||||
/// </summary>
|
||||
private int Min(int a, uint b)
|
||||
{
|
||||
if (b >= a) return a;
|
||||
return (int) b;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to read a UInt32
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private bool TryReadUInt32(Stream stream, out uint value)
|
||||
{
|
||||
//Read the bytes available to us
|
||||
byte[] bytes = new byte[4];
|
||||
int cnt = stream.Read(bytes, 0, bytes.Length);
|
||||
|
||||
//Make sure we actually have a valid value
|
||||
if (cnt != 4)
|
||||
{
|
||||
value = default(uint);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = BitConverter.ToUInt32(bytes, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the frame into the target frame as one big byte block.
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
public void WriteStream(Stream stream)
|
||||
{
|
||||
//Get all the bytes
|
||||
byte[] op = BitConverter.GetBytes((uint) Opcode);
|
||||
byte[] len = BitConverter.GetBytes(Length);
|
||||
|
||||
//Copy it all into a buffer
|
||||
byte[] buff = new byte[op.Length + len.Length + Data.Length];
|
||||
op.CopyTo(buff, 0);
|
||||
len.CopyTo(buff, op.Length);
|
||||
Data.CopyTo(buff, op.Length + len.Length);
|
||||
|
||||
//Write it to the stream
|
||||
stream.Write(buff, 0, buff.Length);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user