/* * 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 client. /// /// 9 public abstract class ClientBase : IDisposable { private readonly string LogTag = "Tizen.Applications.Cion"; private readonly ClientSafeHandle _handle; private PeerInfo _peer; private Interop.CionClient.CionClientServerDiscoveredCb _discoveredCb; private Interop.CionClient.CionClientConnectionResultCb _connectionResultCb; private Interop.CionClient.CionClientPayloadReceivedCb _payloadRecievedCb; private Interop.CionClient.CionClientDisconnectedCb _disconnectedCb; private Dictionary> _tcsDictionary = new Dictionary>(); private Dictionary _payloadAsyncCbDictionary = new Dictionary(); /// /// Gets the service name of current cion client. /// /// 9 public string ServiceName { get; } /// /// Gets peer info of connected cion server. /// /// 9 public PeerInfo PeerInfo { get { return _peer; } } /// /// The constructor of ClientBase class. /// /// The 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 ClientBase(string serviceName) : this(serviceName, null) { } /// /// The constructor of ClientBase class. /// /// The 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 ClientBase(string serviceName, Cion.SecurityInfo security) { ServiceName = serviceName; SecuritySafeHandle handle = security?._handle; Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientCreate(out _handle, serviceName, handle?.DangerousGetHandle() ?? IntPtr.Zero); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to create client."); } _connectionResultCb = new Interop.CionClient.CionClientConnectionResultCb( (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, string.Format("Failed to clone peer info.")); return; } PeerInfo peer = new PeerInfo(clone); ConnectionResult connectionResult = new ConnectionResult(result); if (connectionResult.Status == ConnectionStatus.OK) { _peer = peer; } OnConnectionResult(peer, connectionResult); }); ret = Interop.CionClient.CionClientAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero); if (ret != Interop.Cion.ErrorCode.None) { _handle.Dispose(); throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback."); } _payloadRecievedCb = new Interop.CionClient.CionClientPayloadReceivedCb( (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; } OnPayloadReceived(receivedPayload, (PayloadTransferStatus)status); }); ret = Interop.CionClient.CionClientAddPayloadReceivedCb(_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.CionClient.CionClientDisconnectedCb( (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.CionClient.CionClientAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero); if (ret != Interop.Cion.ErrorCode.None) { _handle.Dispose(); throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback."); } } /// /// Starts discovering cion servers. /// /// http://tizen.org/privilege/d2d.datasharing /// http://tizen.org/privilege/internet /// public /// Thrown when the discovery operation is already in progress. /// Thrown when an application does not have the privilege to access this method. /// 9 public void TryDiscovery() { if (_discoveredCb == null) { Interop.CionClient.CionClientServerDiscoveredCb cb = new Interop.CionClient.CionClientServerDiscoveredCb( (string serviceName, 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, "Failed to clone peer info."); return; } OnDiscovered(new PeerInfo(clone)); }); _discoveredCb = cb; } Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientTryDiscovery(_handle, _discoveredCb, IntPtr.Zero); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to try discovery."); } } /// /// Stops discovering. /// /// Thrown when the client is not discovering. /// 9 public void StopDiscovery() { Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientStopDiscovery(_handle); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to stop discovery."); } } /// /// Connects with the cion server. /// /// The peer to connect. /// http://tizen.org/privilege/d2d.datasharing /// http://tizen.org/privilege/internet /// public /// Thrown when the client cannot connect to server. /// Thrown when an application does not have the privilege to access this method. /// 9 public void Connect(PeerInfo peer) { Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientConnect(_handle, peer?._handle); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to connect."); } } /// /// Disconnects from the cion server. /// /// 9 public void Disconnect() { Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientDisconnect(_handle); if (ret != Interop.Cion.ErrorCode.None) { Log.Error(LogTag, string.Format("Failed to disconnect: {0}", ret)); } _peer = null; } /// /// Sends data synchronously to the connected cion server. /// /// The data to send. /// The timeout of sending operation. /// Thrown when the given data is invalid. /// Thrown when there is no connected cion server or failed to receive reply. /// Thrown when a timeout occurred. /// 9 public byte[] SendData(byte[] data, int timeout) { Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendData(_handle, data, data?.Length ?? -1, timeout, out IntPtr returnDataPtr, out int returnDataSize); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to send data."); } byte[] returnData = new byte[returnDataSize]; Marshal.Copy(returnDataPtr, returnData, 0, returnDataSize); Log.Info(LogTag, string.Format("Returned data size: {0}", returnDataSize)); return returnData; } /// /// Sends payload asynchronously to the connected cion server. /// /// The payload to send. /// Thrown when the payload is not valid. /// Thrown when there is no connected cion server or failed to send payload. /// 9 public Task SendPayloadAsync(Payload payload) { if (payload == null || payload.Id.Length == 0) { throw new ArgumentException("Payload is invalid."); } if (_tcsDictionary.ContainsKey(payload.Id)) { throw new InvalidOperationException("Payload is already sent."); } TaskCompletionSource tcs = new TaskCompletionSource(); _tcsDictionary[payload.Id] = tcs; Interop.CionClient.CionClientPayloadAsyncResultCb cb = new Interop.CionClient.CionClientPayloadAsyncResultCb( (IntPtr result, IntPtr userData) => { TaskCompletionSource tcsToReturn = _tcsDictionary[payload.Id]; 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)); tcsToReturn.SetException(e); return; } tcsToReturn.SetResult(resultPayload); _payloadAsyncCbDictionary.Remove(resultPayload.PayloadId); _tcsDictionary.Remove(resultPayload.PayloadId); }); Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendPayloadAsync(_handle, payload?._handle, cb, IntPtr.Zero); if (ret != Interop.Cion.ErrorCode.None) { throw CionErrorFactory.GetException(ret, "Failed to send payload."); } _payloadAsyncCbDictionary[payload?.Id] = cb; return tcs.Task; } /// /// The result callback of connection request. /// /// The peer info of the cion server. /// The result of the connection. /// 9 protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result); /// /// The callback invoked when received payload. /// /// The received payload. /// The status of sent payload. /// 9 protected abstract void OnPayloadReceived(Payload payload, PayloadTransferStatus status); /// /// The callback invoked when the cion server discovered. /// /// The peer info of discovered cion server. /// 9 protected abstract void OnDiscovered(PeerInfo peerInfo); /// /// The callback invoked when disconnected with cion client. /// /// The peer info of the cion server. /// 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 ClientBase class. /// /// 9 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion } }