/************************************************************************************
|
|
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
|
|
|
|
Licensed under the Oculus Utilities SDK License Version 1.31 (the "License"); you may not use
|
|
the Utilities SDK except in compliance with the License, which is provided at the time of installation
|
|
or download, or which otherwise accompanies this software in either electronic or hard copy form.
|
|
|
|
You may obtain a copy of the License at
|
|
https://developer.oculus.com/licenses/utilities-1.31
|
|
|
|
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
|
|
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
ANY KIND, either express or implied. See the License for the specific language governing
|
|
permissions and limitations under the License.
|
|
************************************************************************************/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using UnityEngine;
|
|
|
|
using Debug = UnityEngine.Debug;
|
|
|
|
public class OVRNetwork
|
|
{
|
|
public const int MaxBufferLength = 65536;
|
|
public const int MaxPayloadLength = MaxBufferLength - FrameHeader.StructSize;
|
|
|
|
public const uint FrameHeaderMagicIdentifier = 0x5283A76B;
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
struct FrameHeader
|
|
{
|
|
public uint protocolIdentifier;
|
|
public int payloadType;
|
|
public int payloadLength;
|
|
|
|
public const int StructSize = sizeof(uint) + sizeof(int) + sizeof(int);
|
|
|
|
// endianness conversion is NOT handled since all our current mobile/PC devices are little-endian
|
|
public byte[] ToBytes()
|
|
{
|
|
int size = Marshal.SizeOf(this);
|
|
Trace.Assert(size == StructSize);
|
|
|
|
byte[] arr = new byte[size];
|
|
|
|
IntPtr ptr = Marshal.AllocHGlobal(size);
|
|
Marshal.StructureToPtr(this, ptr, true);
|
|
Marshal.Copy(ptr, arr, 0, size);
|
|
Marshal.FreeHGlobal(ptr);
|
|
return arr;
|
|
}
|
|
|
|
public static FrameHeader FromBytes(byte[] arr)
|
|
{
|
|
FrameHeader header = new FrameHeader();
|
|
|
|
int size = Marshal.SizeOf(header);
|
|
Trace.Assert(size == StructSize);
|
|
|
|
IntPtr ptr = Marshal.AllocHGlobal(size);
|
|
|
|
Marshal.Copy(arr, 0, ptr, size);
|
|
|
|
header = (FrameHeader)Marshal.PtrToStructure(ptr, header.GetType());
|
|
Marshal.FreeHGlobal(ptr);
|
|
|
|
return header;
|
|
}
|
|
}
|
|
|
|
public class OVRNetworkTcpServer
|
|
{
|
|
public TcpListener tcpListener = null;
|
|
|
|
private readonly object clientsLock = new object();
|
|
public readonly List<TcpClient> clients = new List<TcpClient>();
|
|
|
|
public void StartListening(int listeningPort)
|
|
{
|
|
if (tcpListener != null)
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpServer] tcpListener is not null");
|
|
return;
|
|
}
|
|
|
|
IPAddress localAddr = IPAddress.Any;
|
|
|
|
tcpListener = new TcpListener(localAddr, listeningPort);
|
|
try
|
|
{
|
|
tcpListener.Start();
|
|
Debug.LogFormat("TcpListener started. Local endpoint: {0}", tcpListener.LocalEndpoint.ToString());
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] Unsable to start TcpListener. Socket exception: {0}", e.Message);
|
|
Debug.LogWarning("It could be caused by multiple instances listening at the same port, or the port is forwarded to the Android device through ADB");
|
|
Debug.LogWarning("If the port is forwarded through ADB, use the Android Tools in Tools/Oculus/System Metrics Profiler to kill the server");
|
|
tcpListener = null;
|
|
}
|
|
|
|
if (tcpListener != null)
|
|
{
|
|
Debug.LogFormat("[OVRNetworkTcpServer] Start Listening on port {0}", listeningPort);
|
|
|
|
try
|
|
{
|
|
tcpListener.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), tcpListener);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] can't accept new client: {0}", e.Message);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void StopListening()
|
|
{
|
|
if (tcpListener == null)
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpServer] tcpListener is null");
|
|
return;
|
|
}
|
|
|
|
lock (clientsLock)
|
|
{
|
|
clients.Clear();
|
|
}
|
|
tcpListener.Stop();
|
|
tcpListener = null;
|
|
|
|
Debug.Log("[OVRNetworkTcpServer] Stopped listening");
|
|
}
|
|
|
|
private void DoAcceptTcpClientCallback(IAsyncResult ar)
|
|
{
|
|
TcpListener listener = ar.AsyncState as TcpListener;
|
|
try
|
|
{
|
|
TcpClient client = listener.EndAcceptTcpClient(ar);
|
|
lock (clientsLock)
|
|
{
|
|
clients.Add(client);
|
|
Debug.Log("[OVRNetworkTcpServer] client added");
|
|
}
|
|
|
|
try
|
|
{
|
|
tcpListener.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), tcpListener);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] can't accept new client: {0}", e.Message);
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
// Do nothing. It happens when stop preview in editor, which is normal behavior.
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] EndAcceptTcpClient failed: {0}", e.Message);
|
|
}
|
|
}
|
|
|
|
public bool HasConnectedClient()
|
|
{
|
|
lock (clientsLock)
|
|
{
|
|
foreach (TcpClient client in clients)
|
|
{
|
|
if (client.Connected)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void Broadcast(int payloadType, byte[] payload)
|
|
{
|
|
if (payload.Length > OVRNetwork.MaxPayloadLength)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] drop payload because it's too long: {0} bytes", payload.Length);
|
|
}
|
|
|
|
FrameHeader header = new FrameHeader();
|
|
header.protocolIdentifier = FrameHeaderMagicIdentifier;
|
|
header.payloadType = payloadType;
|
|
header.payloadLength = payload.Length;
|
|
|
|
byte[] headerBuffer = header.ToBytes();
|
|
|
|
byte[] dataBuffer = new byte[headerBuffer.Length + payload.Length];
|
|
headerBuffer.CopyTo(dataBuffer, 0);
|
|
payload.CopyTo(dataBuffer, headerBuffer.Length);
|
|
|
|
lock (clientsLock)
|
|
{
|
|
foreach (TcpClient client in clients)
|
|
{
|
|
if (client.Connected)
|
|
{
|
|
try
|
|
{
|
|
client.GetStream().BeginWrite(dataBuffer, 0, dataBuffer.Length, new AsyncCallback(DoWriteDataCallback), client.GetStream());
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpServer] close client because of socket error: {0}", e.Message);
|
|
client.GetStream().Close();
|
|
client.Close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DoWriteDataCallback(IAsyncResult ar)
|
|
{
|
|
NetworkStream stream = ar.AsyncState as NetworkStream;
|
|
stream.EndWrite(ar);
|
|
}
|
|
}
|
|
|
|
public class OVRNetworkTcpClient
|
|
{
|
|
public Action connectionStateChangedCallback;
|
|
public Action<int, byte[], int, int> payloadReceivedCallback;
|
|
|
|
public enum ConnectionState
|
|
{
|
|
Disconnected,
|
|
Connected,
|
|
Connecting
|
|
}
|
|
|
|
public ConnectionState connectionState
|
|
{
|
|
get
|
|
{
|
|
if (tcpClient == null)
|
|
{
|
|
return ConnectionState.Disconnected;
|
|
}
|
|
else
|
|
{
|
|
if (tcpClient.Connected)
|
|
{
|
|
return ConnectionState.Connected;
|
|
}
|
|
else
|
|
{
|
|
return ConnectionState.Connecting;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Connected
|
|
{
|
|
get
|
|
{
|
|
return connectionState == ConnectionState.Connected;
|
|
}
|
|
}
|
|
|
|
TcpClient tcpClient = null;
|
|
|
|
byte[][] receivedBuffers = { new byte[OVRNetwork.MaxBufferLength], new byte[OVRNetwork.MaxBufferLength] };
|
|
int receivedBufferIndex = 0;
|
|
int receivedBufferDataSize = 0;
|
|
ManualResetEvent readyReceiveDataEvent = new ManualResetEvent(true);
|
|
|
|
public void Connect(int listeningPort)
|
|
{
|
|
if (tcpClient == null)
|
|
{
|
|
receivedBufferIndex = 0;
|
|
receivedBufferDataSize = 0;
|
|
readyReceiveDataEvent.Set();
|
|
|
|
string remoteAddress = "127.0.0.1";
|
|
tcpClient = new TcpClient(AddressFamily.InterNetwork);
|
|
tcpClient.BeginConnect(remoteAddress, listeningPort, new AsyncCallback(ConnectCallback), tcpClient);
|
|
|
|
if (connectionStateChangedCallback != null)
|
|
{
|
|
connectionStateChangedCallback();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] already connected");
|
|
}
|
|
}
|
|
|
|
void ConnectCallback(IAsyncResult ar)
|
|
{
|
|
try
|
|
{
|
|
TcpClient client = ar.AsyncState as TcpClient;
|
|
client.EndConnect(ar);
|
|
Debug.LogFormat("[OVRNetworkTcpClient] connected to {0}", client.ToString());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpClient] connect error {0}", e.Message);
|
|
}
|
|
|
|
if (connectionStateChangedCallback != null)
|
|
{
|
|
connectionStateChangedCallback();
|
|
}
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
if (tcpClient != null)
|
|
{
|
|
if (!readyReceiveDataEvent.WaitOne(5))
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] readyReceiveDataEvent not signaled. data receiving timeout?");
|
|
}
|
|
|
|
Debug.Log("[OVRNetworkTcpClient] close tcpClient");
|
|
try
|
|
{
|
|
tcpClient.GetStream().Close();
|
|
tcpClient.Close();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] " + e.Message);
|
|
}
|
|
tcpClient = null;
|
|
|
|
if (connectionStateChangedCallback != null)
|
|
{
|
|
connectionStateChangedCallback();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] not connected");
|
|
}
|
|
}
|
|
|
|
public void Tick()
|
|
{
|
|
if (tcpClient == null || !tcpClient.Connected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (readyReceiveDataEvent.WaitOne(TimeSpan.Zero))
|
|
{
|
|
if (tcpClient.GetStream().DataAvailable)
|
|
{
|
|
if (receivedBufferDataSize >= OVRNetwork.MaxBufferLength)
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] receive buffer overflow. It should not happen since we have the constraint on message size");
|
|
Disconnect();
|
|
return;
|
|
}
|
|
|
|
readyReceiveDataEvent.Reset();
|
|
int maximumDataSize = OVRSystemPerfMetrics.MaxBufferLength - receivedBufferDataSize;
|
|
|
|
tcpClient.GetStream().BeginRead(receivedBuffers[receivedBufferIndex], receivedBufferDataSize, maximumDataSize, new AsyncCallback(OnReadDataCallback), tcpClient.GetStream());
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnReadDataCallback(IAsyncResult ar)
|
|
{
|
|
NetworkStream stream = ar.AsyncState as NetworkStream;
|
|
try
|
|
{
|
|
int numBytes = stream.EndRead(ar);
|
|
receivedBufferDataSize += numBytes;
|
|
|
|
while (receivedBufferDataSize >= FrameHeader.StructSize)
|
|
{
|
|
FrameHeader header = FrameHeader.FromBytes(receivedBuffers[receivedBufferIndex]);
|
|
if (header.protocolIdentifier != OVRNetwork.FrameHeaderMagicIdentifier)
|
|
{
|
|
Debug.LogWarning("[OVRNetworkTcpClient] header mismatch");
|
|
Disconnect();
|
|
return;
|
|
}
|
|
|
|
if (header.payloadLength < 0 || header.payloadLength > OVRNetwork.MaxPayloadLength)
|
|
{
|
|
Debug.LogWarningFormat("[OVRNetworkTcpClient] Sanity check failed. PayloadLength %d", header.payloadLength);
|
|
Disconnect();
|
|
return;
|
|
}
|
|
|
|
if (receivedBufferDataSize >= FrameHeader.StructSize + header.payloadLength)
|
|
{
|
|
if (payloadReceivedCallback != null)
|
|
{
|
|
payloadReceivedCallback(header.payloadType, receivedBuffers[receivedBufferIndex], FrameHeader.StructSize, header.payloadLength);
|
|
}
|
|
|
|
// swap receive buffer
|
|
int newBufferIndex = 1 - receivedBufferIndex;
|
|
int newBufferDataSize = receivedBufferDataSize - (FrameHeader.StructSize + header.payloadLength);
|
|
if (newBufferDataSize > 0)
|
|
{
|
|
Array.Copy(receivedBuffers[receivedBufferIndex], (FrameHeader.StructSize + header.payloadLength), receivedBuffers[newBufferIndex], 0, newBufferDataSize);
|
|
}
|
|
receivedBufferIndex = newBufferIndex;
|
|
receivedBufferDataSize = newBufferDataSize;
|
|
}
|
|
}
|
|
readyReceiveDataEvent.Set();
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
Debug.LogErrorFormat("[OVRNetworkTcpClient] OnReadDataCallback: socket error: {0}", e.Message);
|
|
Disconnect();
|
|
}
|
|
}
|
|
}
|
|
}
|