[Tizen.Applications.Cion] Fix Cion (#3667)
[platform/core/csapi/tizenfx.git] / src / Tizen.Applications.Cion / Tizen.Applications.Cion / ServerBase.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 server.
26     /// </summary>
27     /// <since_tizen> 9 </since_tizen>
28     public abstract class ServerBase : IDisposable
29     {
30         private readonly string LogTag = "Tizen.Applications.Cion";
31
32         private string _displayName;
33         private readonly ServerSafeHandle _handle;
34         private Interop.CionServer.CionServerConnectionRequestCb _connectionRequestCb;
35         private Interop.CionServer.CionServerConnectionResultCb _connectionResultCb;
36         private Interop.CionServer.CionServerDataReceivedCb _dataReceivedCb;
37         private Interop.CionServer.CionServerPayloadReceivedCb _payloadRecievedCb;
38         private Interop.CionServer.CionServerDisconnectedCb _disconnectedCb;
39         private Interop.CionServer.CionServerPayloadAsyncResultCb _payloadAsyncResultCb;
40         private Dictionary<Tuple<string, string>, TaskCompletionSource<PayloadAsyncResult>> _tcsDictionary = new Dictionary<Tuple<string, string>, TaskCompletionSource<PayloadAsyncResult>>();
41
42         /// <summary>
43         /// Gets the service name of current cion server.
44         /// </summary>
45         /// <since_tizen> 9 </since_tizen>
46         public string ServiceName { get; }
47
48         /// <summary>
49         /// Gets or sets the display name of current cion server.
50         /// </summary>
51         /// <since_tizen> 9 </since_tizen>
52         public string DisplayName
53         {
54             get
55             {
56                 return _displayName;
57             }
58
59             set
60             {
61                 Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSetDisplayName(_handle, value);
62                 if (ret != Interop.Cion.ErrorCode.None)
63                 {
64                     Log.Error(LogTag, string.Format("Failed to set display name: {0}", ret));
65                 }
66                 else
67                 {
68                     _displayName = value;
69                 }
70             }
71         }
72
73         /// <summary>
74         /// The constructor of ServerBase class.
75         /// </summary>
76         /// <param name="serviceName">The name of service.</param>
77         /// <param name="displayName">The display name of service.</param>
78         /// <remarks>The maximum length of service name is 512.</remarks>
79         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
80         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
81         /// <since_tizen> 9 </since_tizen>
82         public ServerBase(string serviceName, string displayName) : this(serviceName, displayName, null) { }
83
84         /// <summary>
85         /// The constructor of ServerBase class.
86         /// </summary>
87         /// <param name="serviceName">The name of service.</param>
88         /// <param name="displayName">The display name of service.</param>
89         /// <param name="security">The security configuration.</param>
90         /// <remarks>The maximum length of service name is 512.</remarks>
91         /// <exception cref="ArgumentException">Thrown when the given service name is too long.</exception>
92         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception>
93         /// <since_tizen> 9 </since_tizen>
94         public ServerBase(string serviceName, string displayName, Cion.SecurityInfo security)
95         {
96             ServiceName = serviceName;
97             _displayName = displayName;
98
99             Cion.SecuritySafeHandle handle = security?._handle;
100             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerCreate(out _handle, serviceName, displayName, handle?.DangerousGetHandle() ?? IntPtr.Zero);
101             if (ret != Interop.Cion.ErrorCode.None)
102             {
103                 throw CionErrorFactory.GetException(ret, "Failed to create server handle.");
104             }
105
106             _connectionResultCb = new Interop.CionServer.CionServerConnectionResultCb(
107                 (string service, IntPtr peerInfo, IntPtr result, IntPtr userData) =>
108                 {
109                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
110                     if (clone_ret != Interop.Cion.ErrorCode.None)
111                     {
112                         Log.Error(LogTag, "Failed to clone peer info.");
113                         return;
114                     }
115                     OnConnectionResult(new PeerInfo(clone), new ConnectionResult(result));
116                 });
117             ret = Interop.CionServer.CionServerAddConnectionResultCb(_handle, _connectionResultCb, IntPtr.Zero);
118             if (ret != Interop.Cion.ErrorCode.None)
119             {
120                 _handle.Dispose();
121                 throw CionErrorFactory.GetException(ret, "Failed to add connection status changed callback.");
122             }
123
124             _dataReceivedCb = new Interop.CionServer.CionServerDataReceivedCb(
125                 (string service, IntPtr peerInfo, byte[] data, int dataSize, out IntPtr returnData, out int returnDataSize, IntPtr userData) =>
126                 {
127                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
128                     if (clone_ret != Interop.Cion.ErrorCode.None)
129                     {
130                         Log.Error(LogTag, "Failed to clone peer info.");
131                         returnData = IntPtr.Zero;
132                         returnDataSize = -1;
133                     }
134                     byte[] returnDataRaw = OnDataReceived(data, new PeerInfo(clone));
135                     returnDataSize = returnDataRaw.Length;
136                     returnData = Interop.Cion.Malloc(returnDataSize);
137                     Marshal.Copy(returnDataRaw, 0, returnData, returnDataSize);
138                 });
139             ret = Interop.CionServer.CionServerSetDataReceivedCb(_handle, _dataReceivedCb, IntPtr.Zero);
140             if (ret != Interop.Cion.ErrorCode.None)
141             {
142                 _handle.Dispose();
143                 throw CionErrorFactory.GetException(ret, "Failed to set data received callback.");
144             }     
145
146             _payloadRecievedCb = new Interop.CionServer.CionServerPayloadReceivedCb(
147                 (string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
148                 {
149                     Payload receivedPayload;
150                     Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
151                     switch (type)
152                     {
153                         case Interop.CionPayload.PayloadType.Data:
154                             receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
155                             break;
156                         case Interop.CionPayload.PayloadType.File:
157                             receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
158                             break;
159                         default:
160                             Log.Error(LogTag, "Invalid payload type received.");
161                             return;
162                     }
163                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
164                     if (clone_ret != Interop.Cion.ErrorCode.None)
165                     {
166                         Log.Error(LogTag, "Failed to clone peer info.");
167                         return;
168                     }
169                     OnPayloadReceived(receivedPayload, new PeerInfo(clone), (PayloadTransferStatus)status);
170                 });
171             ret = Interop.CionServer.CionServerAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
172             if (ret != Interop.Cion.ErrorCode.None)
173             {
174                 _handle.Dispose();
175                 throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
176             }
177
178             _disconnectedCb = new Interop.CionServer.CionServerDisconnectedCb(
179                 (string service, IntPtr peerInfo, IntPtr userData) =>
180                 {
181                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
182                     if (clone_ret != Interop.Cion.ErrorCode.None)
183                     {
184                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
185                         return;
186                     }
187                     OnDisconnected(new PeerInfo(clone));
188                 });
189             ret = Interop.CionServer.CionServerAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
190             if (ret != Interop.Cion.ErrorCode.None)
191             {
192                 _handle.Dispose();
193                 throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
194             }
195         }
196
197         /// <summary>
198         /// Starts server and listens for requests from cion clients.
199         /// </summary>
200         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
201         /// <privilege>http://tizen.org/privilege/internet</privilege>
202         /// <privlevel>public</privlevel>
203         /// <exception cref="InvalidOperationException">Thrown when the listen operation is already in progress.</exception>
204         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
205         /// <since_tizen> 9 </since_tizen>
206         public void Listen()
207         {
208             if (_connectionRequestCb == null)
209             {
210                 Interop.CionServer.CionServerConnectionRequestCb cb = new Interop.CionServer.CionServerConnectionRequestCb(
211                     (serviceName, peerInfo, userData) =>
212                     {
213                         Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
214                         if (clone_ret != Interop.Cion.ErrorCode.None)
215                         {
216                             Log.Error(LogTag, "Failed to clone peer info");
217                             return;
218                         }
219                         OnConnectionRequest(new PeerInfo(clone));
220                     });
221                 _connectionRequestCb = cb;
222             }
223
224             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerListen(_handle, _connectionRequestCb, IntPtr.Zero);
225             if (ret != Interop.Cion.ErrorCode.None)
226             {
227                 throw CionErrorFactory.GetException(ret, "Failed to listen server.");
228             }
229         }
230
231         /// <summary>
232         /// Stops the listen operation.
233         /// </summary>
234         /// <exception cref="InvalidOperationException">Thrown when the server is not listening.</exception>
235         /// <since_tizen> 9 </since_tizen>
236         public void Stop()
237         {
238             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerStop(_handle);
239             if (ret != Interop.Cion.ErrorCode.None)
240             {
241                 throw CionErrorFactory.GetException(ret, "Failed to stop server.");
242             }
243         }
244
245         /// <summary>
246         /// Disconnects with the peer.
247         /// </summary>
248         /// <param name="peerInfo">The peer to disconnect.</param>
249         /// <exception cref="ArgumentException">Thrown when the given peer info is invalid.</exception>
250         /// <since_tizen> 9 </since_tizen>
251         public void Disconnect(PeerInfo peerInfo)
252         {
253             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerDisconnect(_handle, peerInfo?._handle);
254             if (ret != Interop.Cion.ErrorCode.None)
255             {
256                 throw CionErrorFactory.GetException(ret, "Failed to stop server.");
257             }
258         }
259
260         /// <summary>
261         /// Sends the payload to a peer asynchronously.
262         /// </summary>
263         /// <param name="payload">The payload to send.</param>
264         /// <param name="peerInfo">The peer to send payload.</param>
265         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
266         /// <exception cref="InvalidOperationException">Thrown when there is no such connected cion client or failed to send payload.</exception>
267         /// <since_tizen> 9 </since_tizen>
268         public Task<PayloadAsyncResult> SendPayloadAsync(Payload payload, PeerInfo peerInfo)
269         {
270             if (payload == null || payload.Id.Length == 0 || peerInfo == null || peerInfo.UUID.Length == 0)
271             {
272                 throw new ArgumentException("Payload or peerinfo is invalid.");
273             }
274
275             TaskCompletionSource<PayloadAsyncResult> tcs = new TaskCompletionSource<PayloadAsyncResult>();
276             _tcsDictionary[Tuple.Create(payload.Id, peerInfo.UUID)] = tcs;
277
278             if (_payloadAsyncResultCb == null)
279             {
280                 Interop.CionServer.CionServerPayloadAsyncResultCb cb = new Interop.CionServer.CionServerPayloadAsyncResultCb(
281                     (IntPtr result, IntPtr userData) =>
282                     {
283                         PayloadAsyncResult resultPayload = null;
284                         try
285                         {
286                             resultPayload = PayloadAsyncResult.CreateFromHandle(result);
287                         }
288                         catch (Exception e)
289                         {
290                             Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
291                             return;
292                         }
293                         TaskCompletionSource<PayloadAsyncResult> tcsToReturn = _tcsDictionary[Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID)];
294                         tcsToReturn.SetResult(resultPayload);
295                         _tcsDictionary.Remove(Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID));
296                     });
297                 _payloadAsyncResultCb = cb;
298             }
299
300             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSendPayloadAsync(_handle, peerInfo?._handle, payload?._handle, _payloadAsyncResultCb, IntPtr.Zero);
301             if (ret != Interop.Cion.ErrorCode.None)
302             {
303                 throw CionErrorFactory.GetException(ret, "Failed to send payload.");
304             }
305
306             return tcs.Task;
307         }
308
309         /// <summary>
310         /// Sends the payload to all of connected peer asynchronously.
311         /// </summary>
312         /// <param name="payload">The payload to send.</param>
313         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
314         /// <exception cref="InvalidOperationException">Thrown when failed to send payload.</exception>
315         /// <since_tizen> 9 </since_tizen>
316         public void SendPayloadAsync(Payload payload)
317         {
318             var peerList = GetConnectedPeerList();
319             foreach (var peer in peerList)
320             {
321                 SendPayloadAsync(payload, peer);
322             }
323         }
324
325         /// <summary>
326         /// Accepts the connection request from the peer.
327         /// </summary>
328         /// <param name="peerInfo">The peer to accept the connection request.</param>
329         /// <since_tizen> 9 </since_tizen>
330         public void Accept(PeerInfo peerInfo)
331         {
332             Interop.CionServer.CionServerAccept(_handle, peerInfo?._handle);
333         }
334
335         /// <summary>
336         /// Rejects the connection request from the peer.
337         /// </summary>
338         /// <param name="peerInfo">The peer to reject the connection request.</param>
339         /// <param name="reason">The reason why reject the connection request.</param>
340         /// <since_tizen> 9 </since_tizen>
341         public void Reject(PeerInfo peerInfo, string reason)
342         {
343             Interop.CionServer.CionServerReject(_handle, peerInfo?._handle, reason);
344         }
345
346         /// <summary>
347         /// Gets connected peers.
348         /// </summary>
349         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
350         /// <since_tizen> 9 </since_tizen>
351         public IEnumerable<PeerInfo> GetConnectedPeerList()
352         {
353             List<PeerInfo> peerInfoList = new List<PeerInfo>();
354             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerForeachConnectedPeerInfo(_handle, (peer, userData) =>
355             {
356                 Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peer, out PeerInfoSafeHandle clone);
357                 if (clone_ret != Interop.Cion.ErrorCode.None)
358                 {
359                     Log.Error(LogTag, "Failed to clone peer info.");
360                     return false;
361                 }
362                 peerInfoList.Add(new PeerInfo(clone));
363                 return true;
364             }, IntPtr.Zero);
365             return peerInfoList;
366         }
367
368         /// <summary>
369         /// Sets ondemand launch enabled flag.
370         /// </summary>
371         /// <param name="enable">Whether ondemand launch is enabled or not.</param>
372         /// <privilege>http://tizen.org/privilege/d2d.remotelaunch</privilege>
373         /// <privlevel>public</privlevel>
374         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
375         /// <since_tizen> 9 </since_tizen>
376         public void SetOndemandLaunchEnabled(bool enable)
377         {
378             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSetOnDemandLaunchEnabled(_handle, enable);
379             if (ret != Interop.Cion.ErrorCode.None)
380             {
381                 throw CionErrorFactory.GetException(ret, "Failed to set ondemand launch enable");
382             }
383         }
384
385         /// <summary>
386         /// The result callback of connection request.
387         /// </summary>
388         /// <param name="peerInfo">The peer info of the cion client.</param>
389         /// <param name="result">The result of the connection.</param>
390         /// <since_tizen> 9 </since_tizen>
391         protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
392
393         /// <summary>
394         /// The callback invoked when received data.
395         /// </summary>
396         /// <param name="data">The received data.</param>
397         /// <param name="peerInfo">The peer info of the cion client.</param>
398         /// <since_tizen> 9 </since_tizen>
399         protected abstract byte[] OnDataReceived(byte[] data, PeerInfo peerInfo);
400
401         /// <summary>
402         /// The callback invoked when received payload.
403         /// </summary>
404         /// <param name="data">The received data.</param>
405         /// <param name="peerInfo">The peer info of the cion client.</param>
406         /// <param name="status">The status of payload transfer.</param>
407         /// <since_tizen> 9 </since_tizen>
408         protected abstract void OnPayloadReceived(Payload data, PeerInfo peerInfo, PayloadTransferStatus status);
409
410         /// <summary>
411         /// The callback invoked when connection requested from the cion client.
412         /// </summary>
413         /// <param name="peerInfo">The peer info of the cion client.</param>
414         /// <since_tizen> 9 </since_tizen>
415         protected abstract void OnConnectionRequest(PeerInfo peerInfo);
416
417         /// <summary>
418         /// The callback invoked when disconnected with cion client.
419         /// </summary>
420         /// <param name="peerInfo">The peer info of the cion client.</param>
421         /// <since_tizen> 9 </since_tizen>
422         protected abstract void OnDisconnected(PeerInfo peerInfo);
423
424         #region IDisposable Support
425         private bool disposedValue = false;
426
427         /// <summary>
428         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
429         /// </summary>
430         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
431         /// <since_tizen> 9 </since_tizen>
432         protected virtual void Dispose(bool disposing)
433         {
434             if (!disposedValue)
435             {
436                 if (disposing)
437                 {
438                     _handle.Dispose();
439                 }
440                 disposedValue = true;
441             }
442         }
443
444         /// <summary>
445         /// Releases all resources used by the ServerBase class.
446         /// </summary>
447         /// <since_tizen> 9 </since_tizen>
448         public void Dispose()
449         {
450             Dispose(true);
451             GC.SuppressFinalize(this);
452         }
453         #endregion
454     }
455 }