/* * 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.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 byte[] 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 = null; returnDataSize = -1; } returnData = OnDataReceived(data, new PeerInfo(clone)); returnDataSize = returnData.Length; }); 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 } }