[Tizen.Applications.Cion] Fix Cion (#3682)
[platform/core/csapi/tizenfx.git] / src / Tizen.Applications.Cion / Tizen.Applications.Cion / ClientBase.cs
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.Generic;
19 using System.Runtime.InteropServices;
20 using System.Threading.Tasks;
21
22 namespace Tizen.Applications.Cion
23 {
24     /// <summary>
25     /// An abstract class to represent cion client.
26     /// </summary>
27     /// <since_tizen> 9 </since_tizen>
28     public abstract class ClientBase : IDisposable
29     {
30         private readonly string LogTag = "Tizen.Applications.Cion";
31         private readonly ClientSafeHandle _handle;
32
33         private PeerInfo _peer;
34
35         private Interop.CionClient.CionClientServerDiscoveredCb _discoveredCb;
36         private Interop.CionClient.CionClientConnectionResultCb _connectionResultCb;
37         private Interop.CionClient.CionClientPayloadReceivedCb _payloadRecievedCb;
38         private Interop.CionClient.CionClientDisconnectedCb _disconnectedCb;
39         private Dictionary<string, TaskCompletionSource<PayloadAsyncResult>> _tcsDictionary = new Dictionary<string, TaskCompletionSource<PayloadAsyncResult>>();
40         private Dictionary<string, Interop.CionClient.CionClientPayloadAsyncResultCb> _payloadAsyncCbDictionary = new Dictionary<string, Interop.CionClient.CionClientPayloadAsyncResultCb>();
41
42         /// <summary>
43         /// Gets the service name of current cion client.
44         /// </summary>
45         /// <since_tizen> 9 </since_tizen>
46         public string ServiceName { get; }
47
48         /// <summary>
49         /// Gets peer info of connected cion server.
50         /// </summary>
51         /// <since_tizen> 9 </since_tizen>
52         public PeerInfo PeerInfo
53         {
54             get
55             {
56                 return _peer;
57             }
58         }
59
60         /// <summary>
61         /// The constructor of ClientBase class.
62         /// </summary>
63         /// <param name="serviceName">The name of service.</param>
64         /// <remarks>The maximum length of service name is 512.</remarks>
65         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
66         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
67         /// <since_tizen> 9 </since_tizen>
68         public ClientBase(string serviceName) : this(serviceName, null) { }
69
70         /// <summary>
71         /// The constructor of ClientBase class.
72         /// </summary>
73         /// <param name="serviceName">The name of service.</param>
74         /// <param name="security">The security configuration.</param>
75         /// <remarks>The maximum length of service name is 512.</remarks>
76         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
77         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
78         /// <since_tizen> 9 </since_tizen>
79         public ClientBase(string serviceName, Cion.SecurityInfo security)
80         {
81             ServiceName = serviceName;
82
83             SecuritySafeHandle handle = security?._handle;
84             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientCreate(out _handle, serviceName, handle?.DangerousGetHandle() ?? IntPtr.Zero);
85             if (ret != Interop.Cion.ErrorCode.None)
86             {
87                 throw CionErrorFactory.GetException(ret, "Failed to create client.");
88             }
89
90             _connectionResultCb = new Interop.CionClient.CionClientConnectionResultCb(
91                 (string service, IntPtr peerInfo, IntPtr result, IntPtr userData) =>
92                 {
93                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
94                     if (clone_ret != Interop.Cion.ErrorCode.None)
95                     {
96                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
97                         return;
98                     }
99
100                     PeerInfo peer = new PeerInfo(clone);
101                     ConnectionResult connectionResult = new ConnectionResult(result);
102                     if (connectionResult.Status == ConnectionStatus.OK)
103                     {
104                         _peer = peer;
105                     }
106
107                     OnConnectionResult(peer, connectionResult);
108                 });
109             ret = Interop.CionClient.CionClientAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero);
110             if (ret != Interop.Cion.ErrorCode.None)
111             {
112                 _handle.Dispose();
113                 throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback.");
114             }
115
116             _payloadRecievedCb = new Interop.CionClient.CionClientPayloadReceivedCb(
117                 (string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
118                 {
119                     Payload receivedPayload;
120                     Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
121                     switch (type)
122                     {
123                         case Interop.CionPayload.PayloadType.Data:
124                             receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
125                             break;
126                         case Interop.CionPayload.PayloadType.File:
127                             receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
128                             break;
129                         default:
130                             Log.Error(LogTag, "Invalid payload type received.");
131                             return;
132                     }
133                     OnPayloadReceived(receivedPayload, (PayloadTransferStatus)status);
134                 });
135             ret = Interop.CionClient.CionClientAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
136             if (ret != Interop.Cion.ErrorCode.None)
137             {
138                 _handle.Dispose();
139                 throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
140             }
141
142             _disconnectedCb = new Interop.CionClient.CionClientDisconnectedCb(
143                 (string service, IntPtr peerInfo, IntPtr userData) =>
144                 {
145                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
146                     if (clone_ret != Interop.Cion.ErrorCode.None)
147                     {
148                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
149                         return;
150                     }
151                     OnDisconnected(new PeerInfo(clone));
152                 });
153             ret = Interop.CionClient.CionClientAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
154             if (ret != Interop.Cion.ErrorCode.None)
155             {
156                 _handle.Dispose();
157                 throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
158             }
159         }
160
161         /// <summary>
162         /// Starts discovering cion servers.
163         /// </summary>
164         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
165         /// <privilege>http://tizen.org/privilege/internet</privilege>
166         /// <privlevel>public</privlevel>
167         /// <exception cref="InvalidOperationException">Thrown when the discovery operation is already in progress.</exception>
168         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
169         /// <since_tizen> 9 </since_tizen>
170         public void TryDiscovery()
171         {
172             if (_discoveredCb == null)
173             {
174                 Interop.CionClient.CionClientServerDiscoveredCb cb = new Interop.CionClient.CionClientServerDiscoveredCb(
175                     (string serviceName, IntPtr peerInfo, IntPtr userData) =>
176                     {
177                         Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
178                         if (clone_ret != Interop.Cion.ErrorCode.None)
179                         {
180                             Log.Error(LogTag, "Failed to clone peer info.");
181                             return;
182                         }
183                         OnDiscovered(new PeerInfo(clone));
184                     });
185                 _discoveredCb = cb;
186             }
187
188             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientTryDiscovery(_handle, _discoveredCb, IntPtr.Zero);
189             if (ret != Interop.Cion.ErrorCode.None)
190             {
191                 throw CionErrorFactory.GetException(ret, "Failed to try discovery.");
192             }
193         }
194
195         /// <summary>
196         /// Stops discovering.
197         /// </summary>
198         /// <exception cref="InvalidOperationException">Thrown when the client is not discovering.</exception>
199         /// <since_tizen> 9 </since_tizen>
200         public void StopDiscovery()
201         {
202             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientStopDiscovery(_handle);
203             if (ret != Interop.Cion.ErrorCode.None)
204             {
205                 throw CionErrorFactory.GetException(ret, "Failed to stop discovery.");
206             }
207         }
208
209         /// <summary>
210         /// Connects with the cion server.
211         /// </summary>
212         /// <param name="peer">The peer to connect.</param>
213         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
214         /// <privilege>http://tizen.org/privilege/internet</privilege>
215         /// <privlevel>public</privlevel>
216         /// <exception cref="InvalidOperationException">Thrown when the client cannot connect to server.</exception>
217         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
218         /// <since_tizen> 9 </since_tizen>
219         public void Connect(PeerInfo peer)
220         {
221             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientConnect(_handle, peer?._handle);
222             if (ret != Interop.Cion.ErrorCode.None)
223             {
224                 throw CionErrorFactory.GetException(ret, "Failed to connect.");
225             }
226         }
227
228         /// <summary>
229         /// Disconnects from the cion server.
230         /// </summary>
231         /// <since_tizen> 9 </since_tizen>
232         public void Disconnect()
233         {
234             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientDisconnect(_handle);
235             if (ret != Interop.Cion.ErrorCode.None)
236             {
237                 Log.Error(LogTag, string.Format("Failed to disconnect: {0}", ret));
238             }
239             _peer = null;
240         }
241
242         /// <summary>
243         /// Sends data synchronously to the connected cion server.
244         /// </summary>
245         /// <param name="data">The data to send.</param>
246         /// <param name="timeout">The timeout of sending operation.</param>
247         /// <exception cref="ArgumentException">Thrown when the given data is invalid.</exception>
248         /// <exception cref="InvalidOperationException">Thrown when there is no connected cion server or failed to receive reply.</exception>
249         /// <exception cref="TimeoutException">Thrown when a timeout occurred.</exception>
250         /// <since_tizen> 9 </since_tizen>
251         public byte[] SendData(byte[] data, int timeout)
252         {
253             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendData(_handle, data, data?.Length ?? -1, timeout, out IntPtr returnDataPtr, out int returnDataSize);
254             if (ret != Interop.Cion.ErrorCode.None)
255             {
256                 throw CionErrorFactory.GetException(ret, "Failed to send data.");
257             }
258             byte[] returnData = new byte[returnDataSize];
259             Marshal.Copy(returnDataPtr, returnData, 0, returnDataSize);
260             Log.Info(LogTag, string.Format("Returned data size: {0}", returnDataSize));
261
262             return returnData;
263         }
264
265         /// <summary>
266         /// Sends payload asynchronously to the connected cion server.
267         /// </summary>
268         /// <param name="payload">The payload to send.</param>
269         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
270         /// <exception cref="InvalidOperationException">Thrown when there is no connected cion server or failed to send payload.</exception>
271         /// <since_tizen> 9 </since_tizen>
272         public Task<PayloadAsyncResult> SendPayloadAsync(Payload payload)
273         {
274             if (payload == null || payload.Id.Length == 0)
275             {
276                 throw new ArgumentException("Payload is invalid.");
277             }
278
279             if (_tcsDictionary.ContainsKey(payload.Id))
280             {
281                 throw new InvalidOperationException("Payload is already sent.");
282             }
283
284             TaskCompletionSource<PayloadAsyncResult> tcs = new TaskCompletionSource<PayloadAsyncResult>();
285             _tcsDictionary[payload.Id] = tcs;
286
287             Interop.CionClient.CionClientPayloadAsyncResultCb cb = new Interop.CionClient.CionClientPayloadAsyncResultCb(
288                 (IntPtr result, IntPtr userData) =>
289                 {
290                     TaskCompletionSource<PayloadAsyncResult> tcsToReturn = _tcsDictionary[payload.Id];
291                     PayloadAsyncResult resultPayload = null;
292                     try
293                     {
294                         resultPayload = PayloadAsyncResult.CreateFromHandle(result);
295                     }
296                     catch (Exception e)
297                     {
298                         Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
299                         tcsToReturn.SetException(e);
300                         return;
301                     }
302                     tcsToReturn.SetResult(resultPayload);
303                     _payloadAsyncCbDictionary.Remove(resultPayload.PayloadId);
304                     _tcsDictionary.Remove(resultPayload.PayloadId);
305                 });
306
307             Interop.Cion.ErrorCode ret = Interop.CionClient.CionClientSendPayloadAsync(_handle, payload?._handle, cb, IntPtr.Zero);
308             if (ret != Interop.Cion.ErrorCode.None)
309             {
310                 throw CionErrorFactory.GetException(ret, "Failed to send payload.");
311             }
312
313             _payloadAsyncCbDictionary[payload?.Id] = cb;
314
315             return tcs.Task;
316         }
317
318         /// <summary>
319         /// The result callback of connection request.
320         /// </summary>
321         /// <param name="peerInfo">The peer info of the cion server.</param>
322         /// <param name="result">The result of the connection.</param>
323         /// <since_tizen> 9 </since_tizen>
324         protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
325
326         /// <summary>
327         /// The callback invoked when received payload.
328         /// </summary>
329         /// <param name="payload">The received payload.</param>
330         /// <param name="status">The status of sent payload.</param>
331         /// <since_tizen> 9 </since_tizen>
332         protected abstract void OnPayloadReceived(Payload payload, PayloadTransferStatus status);
333
334         /// <summary>
335         /// The callback invoked when the cion server discovered.
336         /// </summary>
337         /// <param name="peerInfo">The peer info of discovered cion server.</param>
338         /// <since_tizen> 9 </since_tizen>
339         protected abstract void OnDiscovered(PeerInfo peerInfo);
340
341         /// <summary>
342         /// The callback invoked when disconnected with cion client.
343         /// </summary>
344         /// <param name="peerInfo">The peer info of the cion server.</param>
345         /// <since_tizen> 9 </since_tizen>
346         protected abstract void OnDisconnected(PeerInfo peerInfo);
347
348         #region IDisposable Support
349         private bool disposedValue = false;
350
351         /// <summary>
352         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
353         /// </summary>
354         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
355         /// <since_tizen> 9 </since_tizen>
356         protected virtual void Dispose(bool disposing)
357         {
358             if (!disposedValue)
359             {
360                 if (disposing)
361                 {
362                     _handle.Dispose();
363                 }
364                 disposedValue = true;
365             }
366         }
367
368         /// <summary>
369         /// Releases all resources used by the ClientBase class.
370         /// </summary>
371         /// <since_tizen> 9 </since_tizen>
372         public void Dispose()
373         {
374             Dispose(true);
375             GC.SuppressFinalize(this);
376         }
377         #endregion
378     }
379 }