/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* 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.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace Tizen.Applications.Cion
{
///
/// An abstract class to represent cion server.
///
/// 9
public abstract class ServerBase : IDisposable
{
private readonly string LogTag = "Tizen.Applications.Cion";
private string _displayName;
private readonly ServerSafeHandle _handle;
private Interop.CionServer.CionServerConnectionRequestCb _connectionRequestCb;
private Interop.CionServer.CionServerConnectionResultCb _connectionResultCb;
private Interop.CionServer.CionServerDataReceivedCb _dataReceivedCb;
private Interop.CionServer.CionServerPayloadReceivedCb _payloadRecievedCb;
private Interop.CionServer.CionServerDisconnectedCb _disconnectedCb;
private Interop.CionServer.CionServerPayloadAsyncResultCb _payloadAsyncResultCb;
private Dictionary, TaskCompletionSource> _tcsDictionary = new Dictionary, TaskCompletionSource>();
///
/// Gets the service name of current cion server.
///
/// 9
public string ServiceName { get; }
///
/// Gets or sets the display name of current cion server.
///
/// 9
public string DisplayName
{
get
{
return _displayName;
}
set
{
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSetDisplayName(_handle, value);
if (ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, string.Format("Failed to set display name: {0}", ret));
}
else
{
_displayName = value;
}
}
}
///
/// The constructor of ServerBase class.
///
/// The name of service.
/// The display name of service.
/// The maximum length of service name is 512.
/// Thrown when the given service name is too long.
/// Thrown when there is not enough memory to continue the execution of the method.
/// 9
public ServerBase(string serviceName, string displayName) : this(serviceName, displayName, null) { }
///
/// The constructor of ServerBase class.
///
/// The name of service.
/// The display name of service.
/// The security configuration.
/// The maximum length of service name is 512.
/// Thrown when the given service name is too long.
/// Thrown when there is not enough memory to continue the execution of the method.
/// 9
public ServerBase(string serviceName, string displayName, Cion.SecurityInfo security)
{
ServiceName = serviceName;
_displayName = displayName;
Cion.SecuritySafeHandle handle = security?._handle;
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerCreate(out _handle, serviceName, displayName, handle?.DangerousGetHandle() ?? IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to create server handle.");
}
_connectionResultCb = new Interop.CionServer.CionServerConnectionResultCb(
(string service, IntPtr peerInfo, IntPtr result, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info.");
return;
}
OnConnectionResult(new PeerInfo(clone), new ConnectionResult(result));
});
ret = Interop.CionServer.CionServerAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback.");
}
_dataReceivedCb = new Interop.CionServer.CionServerDataReceivedCb(
(string service, IntPtr peerInfo, byte[] data, int dataSize, out IntPtr returnData, out int returnDataSize, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info.");
returnData = IntPtr.Zero;
returnDataSize = -1;
}
byte[] returnDataRaw = OnDataReceived(data, new PeerInfo(clone));
returnDataSize = returnDataRaw.Length;
returnData = Interop.Cion.Malloc(returnDataSize);
Marshal.Copy(returnDataRaw, 0, returnData, returnDataSize);
});
ret = Interop.CionServer.CionServerSetDataReceivedCb(_handle, _dataReceivedCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to set data received callback.");
}
_payloadRecievedCb = new Interop.CionServer.CionServerPayloadReceivedCb(
(string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
{
Payload receivedPayload;
Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
switch (type)
{
case Interop.CionPayload.PayloadType.Data:
receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
break;
case Interop.CionPayload.PayloadType.File:
receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
break;
default:
Log.Error(LogTag, "Invalid payload type received.");
return;
}
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info.");
return;
}
OnPayloadReceived(receivedPayload, new PeerInfo(clone), (PayloadTransferStatus)status);
});
ret = Interop.CionServer.CionServerAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
}
_disconnectedCb = new Interop.CionServer.CionServerDisconnectedCb(
(string service, IntPtr peerInfo, IntPtr userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, string.Format("Failed to clone peer info."));
return;
}
OnDisconnected(new PeerInfo(clone));
});
ret = Interop.CionServer.CionServerAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
_handle.Dispose();
throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
}
}
///
/// Starts server and listens for requests from cion clients.
///
/// http://tizen.org/privilege/d2d.datasharing
/// http://tizen.org/privilege/internet
/// public
/// Thrown when the listen operation is already in progress.
/// Thrown when an application does not have the privilege to access this method.
/// 9
public void Listen()
{
if (_connectionRequestCb == null)
{
Interop.CionServer.CionServerConnectionRequestCb cb = new Interop.CionServer.CionServerConnectionRequestCb(
(serviceName, peerInfo, userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info");
return;
}
OnConnectionRequest(new PeerInfo(clone));
});
_connectionRequestCb = cb;
}
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerListen(_handle, _connectionRequestCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to listen server.");
}
}
///
/// Stops the listen operation.
///
/// Thrown when the server is not listening.
/// 9
public void Stop()
{
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerStop(_handle);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to stop server.");
}
}
///
/// Disconnects with the peer.
///
/// The peer to disconnect.
/// Thrown when the given peer info is invalid.
/// 9
public void Disconnect(PeerInfo peerInfo)
{
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerDisconnect(_handle, peerInfo?._handle);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to stop server.");
}
}
///
/// Sends the payload to a peer asynchronously.
///
/// The payload to send.
/// The peer to send payload.
/// Thrown when the payload is not valid.
/// Thrown when there is no such connected cion client or failed to send payload.
/// 9
public Task SendPayloadAsync(Payload payload, PeerInfo peerInfo)
{
if (payload == null || payload.Id.Length == 0 || peerInfo == null || peerInfo.UUID.Length == 0)
{
throw new ArgumentException("Payload or peerinfo is invalid.");
}
TaskCompletionSource tcs = new TaskCompletionSource();
_tcsDictionary[Tuple.Create(payload.Id, peerInfo.UUID)] = tcs;
if (_payloadAsyncResultCb == null)
{
Interop.CionServer.CionServerPayloadAsyncResultCb cb = new Interop.CionServer.CionServerPayloadAsyncResultCb(
(IntPtr result, IntPtr userData) =>
{
PayloadAsyncResult resultPayload = null;
try
{
resultPayload = PayloadAsyncResult.CreateFromHandle(result);
}
catch (Exception e)
{
Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
return;
}
TaskCompletionSource tcsToReturn = _tcsDictionary[Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID)];
tcsToReturn.SetResult(resultPayload);
_tcsDictionary.Remove(Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID));
});
_payloadAsyncResultCb = cb;
}
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSendPayloadAsync(_handle, peerInfo?._handle, payload?._handle, _payloadAsyncResultCb, IntPtr.Zero);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to send payload.");
}
return tcs.Task;
}
///
/// Sends the payload to all of connected peer asynchronously.
///
/// The payload to send.
/// Thrown when the payload is not valid.
/// Thrown when failed to send payload.
/// 9
public void SendPayloadAsync(Payload payload)
{
var peerList = GetConnectedPeerList();
foreach (var peer in peerList)
{
SendPayloadAsync(payload, peer);
}
}
///
/// Accepts the connection request from the peer.
///
/// The peer to accept the connection request.
/// 9
public void Accept(PeerInfo peerInfo)
{
Interop.CionServer.CionServerAccept(_handle, peerInfo?._handle);
}
///
/// Rejects the connection request from the peer.
///
/// The peer to reject the connection request.
/// The reason why reject the connection request.
/// 9
public void Reject(PeerInfo peerInfo, string reason)
{
Interop.CionServer.CionServerReject(_handle, peerInfo?._handle, reason);
}
///
/// Gets connected peers.
///
/// Thrown when there is not enough memory to continue the execution of the method.
/// 9
public IEnumerable GetConnectedPeerList()
{
List peerInfoList = new List();
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerForeachConnectedPeerInfo(_handle, (peer, userData) =>
{
Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peer, out PeerInfoSafeHandle clone);
if (clone_ret != Interop.Cion.ErrorCode.None)
{
Log.Error(LogTag, "Failed to clone peer info.");
return false;
}
peerInfoList.Add(new PeerInfo(clone));
return true;
}, IntPtr.Zero);
return peerInfoList;
}
///
/// Sets ondemand launch enabled flag.
///
/// Whether ondemand launch is enabled or not.
/// http://tizen.org/privilege/d2d.remotelaunch
/// public
/// Thrown when an application does not have the privilege to access this method.
/// 9
public void SetOndemandLaunchEnabled(bool enable)
{
Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSetOnDemandLaunchEnabled(_handle, enable);
if (ret != Interop.Cion.ErrorCode.None)
{
throw CionErrorFactory.GetException(ret, "Failed to set ondemand launch enable");
}
}
///
/// The result callback of connection request.
///
/// The peer info of the cion client.
/// The result of the connection.
/// 9
protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
///
/// The callback invoked when received data.
///
/// The received data.
/// The peer info of the cion client.
/// 9
protected abstract byte[] OnDataReceived(byte[] data, PeerInfo peerInfo);
///
/// The callback invoked when received payload.
///
/// The received data.
/// The peer info of the cion client.
/// The status of payload transfer.
/// 9
protected abstract void OnPayloadReceived(Payload data, PeerInfo peerInfo, PayloadTransferStatus status);
///
/// The callback invoked when connection requested from the cion client.
///
/// The peer info of the cion client.
/// 9
protected abstract void OnConnectionRequest(PeerInfo peerInfo);
///
/// The callback invoked when disconnected with cion client.
///
/// The peer info of the cion client.
/// 9
protected abstract void OnDisconnected(PeerInfo peerInfo);
#region IDisposable Support
private bool disposedValue = false;
///
/// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
///
/// If true, disposes any disposable objects. If false, does not dispose disposable objects.
/// 9
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_handle.Dispose();
}
disposedValue = true;
}
}
///
/// Releases all resources used by the ServerBase class.
///
/// 9
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}