[Applications.Cion] Fix bug of sending data (#3716)
[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, IntPtr 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[] receivedData = new byte[dataSize];
135                     Marshal.Copy(data, receivedData, 0, dataSize);
136                     byte[] returnDataRaw = OnDataReceived(receivedData, new PeerInfo(clone));
137                     returnDataSize = returnDataRaw.Length;
138                     returnData = Interop.Cion.Malloc(returnDataSize);
139                     Marshal.Copy(returnDataRaw, 0, returnData, returnDataSize);
140                 });
141             ret = Interop.CionServer.CionServerSetDataReceivedCb(_handle, _dataReceivedCb, IntPtr.Zero);
142             if (ret != Interop.Cion.ErrorCode.None)
143             {
144                 _handle.Dispose();
145                 throw CionErrorFactory.GetException(ret, "Failed to set data received callback.");
146             }     
147
148             _payloadRecievedCb = new Interop.CionServer.CionServerPayloadReceivedCb(
149                 (string service, IntPtr peerInfo, IntPtr payload, int status, IntPtr userData) =>
150                 {
151                     Payload receivedPayload;
152                     Interop.CionPayload.CionPayloadGetType(payload, out Interop.CionPayload.PayloadType type);
153                     switch (type)
154                     {
155                         case Interop.CionPayload.PayloadType.Data:
156                             receivedPayload = new DataPayload(new PayloadSafeHandle(payload, false));
157                             break;
158                         case Interop.CionPayload.PayloadType.File:
159                             receivedPayload = new FilePayload(new PayloadSafeHandle(payload, false));
160                             break;
161                         default:
162                             Log.Error(LogTag, "Invalid payload type received.");
163                             return;
164                     }
165                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
166                     if (clone_ret != Interop.Cion.ErrorCode.None)
167                     {
168                         Log.Error(LogTag, "Failed to clone peer info.");
169                         return;
170                     }
171                     OnPayloadReceived(receivedPayload, new PeerInfo(clone), (PayloadTransferStatus)status);
172                 });
173             ret = Interop.CionServer.CionServerAddPayloadReceivedCb(_handle, _payloadRecievedCb, IntPtr.Zero);
174             if (ret != Interop.Cion.ErrorCode.None)
175             {
176                 _handle.Dispose();
177                 throw CionErrorFactory.GetException(ret, "Failed to add payload received callback.");
178             }
179
180             _disconnectedCb = new Interop.CionServer.CionServerDisconnectedCb(
181                 (string service, IntPtr peerInfo, IntPtr userData) =>
182                 {
183                     Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
184                     if (clone_ret != Interop.Cion.ErrorCode.None)
185                     {
186                         Log.Error(LogTag, string.Format("Failed to clone peer info."));
187                         return;
188                     }
189                     OnDisconnected(new PeerInfo(clone));
190                 });
191             ret = Interop.CionServer.CionServerAddDisconnectedCb(_handle, _disconnectedCb, IntPtr.Zero);
192             if (ret != Interop.Cion.ErrorCode.None)
193             {
194                 _handle.Dispose();
195                 throw CionErrorFactory.GetException(ret, "Failed to add disconnected callback.");
196             }
197         }
198
199         /// <summary>
200         /// Starts server and listens for requests from cion clients.
201         /// </summary>
202         /// <privilege>http://tizen.org/privilege/d2d.datasharing</privilege>
203         /// <privilege>http://tizen.org/privilege/internet</privilege>
204         /// <privlevel>public</privlevel>
205         /// <exception cref="InvalidOperationException">Thrown when the listen operation is already in progress.</exception>
206         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
207         /// <since_tizen> 9 </since_tizen>
208         public void Listen()
209         {
210             if (_connectionRequestCb == null)
211             {
212                 Interop.CionServer.CionServerConnectionRequestCb cb = new Interop.CionServer.CionServerConnectionRequestCb(
213                     (serviceName, peerInfo, userData) =>
214                     {
215                         Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peerInfo, out PeerInfoSafeHandle clone);
216                         if (clone_ret != Interop.Cion.ErrorCode.None)
217                         {
218                             Log.Error(LogTag, "Failed to clone peer info");
219                             return;
220                         }
221                         OnConnectionRequest(new PeerInfo(clone));
222                     });
223                 _connectionRequestCb = cb;
224             }
225
226             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerListen(_handle, _connectionRequestCb, IntPtr.Zero);
227             if (ret != Interop.Cion.ErrorCode.None)
228             {
229                 throw CionErrorFactory.GetException(ret, "Failed to listen server.");
230             }
231         }
232
233         /// <summary>
234         /// Stops the listen operation.
235         /// </summary>
236         /// <exception cref="InvalidOperationException">Thrown when the server is not listening.</exception>
237         /// <since_tizen> 9 </since_tizen>
238         public void Stop()
239         {
240             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerStop(_handle);
241             if (ret != Interop.Cion.ErrorCode.None)
242             {
243                 throw CionErrorFactory.GetException(ret, "Failed to stop server.");
244             }
245         }
246
247         /// <summary>
248         /// Disconnects with the peer.
249         /// </summary>
250         /// <param name="peerInfo">The peer to disconnect.</param>
251         /// <exception cref="ArgumentException">Thrown when the given peer info is invalid.</exception>
252         /// <since_tizen> 9 </since_tizen>
253         public void Disconnect(PeerInfo peerInfo)
254         {
255             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerDisconnect(_handle, peerInfo?._handle);
256             if (ret != Interop.Cion.ErrorCode.None)
257             {
258                 throw CionErrorFactory.GetException(ret, "Failed to stop server.");
259             }
260         }
261
262         /// <summary>
263         /// Sends the payload to a peer asynchronously.
264         /// </summary>
265         /// <param name="payload">The payload to send.</param>
266         /// <param name="peerInfo">The peer to send payload.</param>
267         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
268         /// <exception cref="InvalidOperationException">Thrown when there is no such connected cion client or failed to send payload.</exception>
269         /// <since_tizen> 9 </since_tizen>
270         public Task<PayloadAsyncResult> SendPayloadAsync(Payload payload, PeerInfo peerInfo)
271         {
272             if (payload == null || payload.Id.Length == 0 || peerInfo == null || peerInfo.UUID.Length == 0)
273             {
274                 throw new ArgumentException("Payload or peerinfo is invalid.");
275             }
276
277             TaskCompletionSource<PayloadAsyncResult> tcs = new TaskCompletionSource<PayloadAsyncResult>();
278             _tcsDictionary[Tuple.Create(payload.Id, peerInfo.UUID)] = tcs;
279
280             if (_payloadAsyncResultCb == null)
281             {
282                 Interop.CionServer.CionServerPayloadAsyncResultCb cb = new Interop.CionServer.CionServerPayloadAsyncResultCb(
283                     (IntPtr result, IntPtr userData) =>
284                     {
285                         PayloadAsyncResult resultPayload = null;
286                         try
287                         {
288                             resultPayload = PayloadAsyncResult.CreateFromHandle(result);
289                         }
290                         catch (Exception e)
291                         {
292                             Log.Error(LogTag, string.Format("Failed to create PayloadAsyncResult from result handle: {0}.", e.Message));
293                             return;
294                         }
295                         TaskCompletionSource<PayloadAsyncResult> tcsToReturn = _tcsDictionary[Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID)];
296                         tcsToReturn.SetResult(resultPayload);
297                         _tcsDictionary.Remove(Tuple.Create(resultPayload.PayloadId, resultPayload.PeerInfo.UUID));
298                     });
299                 _payloadAsyncResultCb = cb;
300             }
301
302             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSendPayloadAsync(_handle, peerInfo?._handle, payload?._handle, _payloadAsyncResultCb, IntPtr.Zero);
303             if (ret != Interop.Cion.ErrorCode.None)
304             {
305                 throw CionErrorFactory.GetException(ret, "Failed to send payload.");
306             }
307
308             return tcs.Task;
309         }
310
311         /// <summary>
312         /// Sends the payload to all of connected peer asynchronously.
313         /// </summary>
314         /// <param name="payload">The payload to send.</param>
315         /// <exception cref="ArgumentException">Thrown when the payload is not valid.</exception>
316         /// <exception cref="InvalidOperationException">Thrown when failed to send payload.</exception>
317         /// <since_tizen> 9 </since_tizen>
318         public void SendPayloadAsync(Payload payload)
319         {
320             var peerList = GetConnectedPeerList();
321             foreach (var peer in peerList)
322             {
323                 SendPayloadAsync(payload, peer);
324             }
325         }
326
327         /// <summary>
328         /// Accepts the connection request from the peer.
329         /// </summary>
330         /// <param name="peerInfo">The peer to accept the connection request.</param>
331         /// <since_tizen> 9 </since_tizen>
332         public void Accept(PeerInfo peerInfo)
333         {
334             Interop.CionServer.CionServerAccept(_handle, peerInfo?._handle);
335         }
336
337         /// <summary>
338         /// Rejects the connection request from the peer.
339         /// </summary>
340         /// <param name="peerInfo">The peer to reject the connection request.</param>
341         /// <param name="reason">The reason why reject the connection request.</param>
342         /// <since_tizen> 9 </since_tizen>
343         public void Reject(PeerInfo peerInfo, string reason)
344         {
345             Interop.CionServer.CionServerReject(_handle, peerInfo?._handle, reason);
346         }
347
348         /// <summary>
349         /// Gets connected peers.
350         /// </summary>
351         /// <exception cref="InvalidOperationException">Thrown when there is not enough memory to continue the execution of the method.</exception> 
352         /// <since_tizen> 9 </since_tizen>
353         public IEnumerable<PeerInfo> GetConnectedPeerList()
354         {
355             List<PeerInfo> peerInfoList = new List<PeerInfo>();
356             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerForeachConnectedPeerInfo(_handle, (peer, userData) =>
357             {
358                 Interop.Cion.ErrorCode clone_ret = Interop.CionPeerInfo.CionPeerInfoClone(peer, out PeerInfoSafeHandle clone);
359                 if (clone_ret != Interop.Cion.ErrorCode.None)
360                 {
361                     Log.Error(LogTag, "Failed to clone peer info.");
362                     return false;
363                 }
364                 peerInfoList.Add(new PeerInfo(clone));
365                 return true;
366             }, IntPtr.Zero);
367             return peerInfoList;
368         }
369
370         /// <summary>
371         /// Sets ondemand launch enabled flag.
372         /// </summary>
373         /// <param name="enable">Whether ondemand launch is enabled or not.</param>
374         /// <privilege>http://tizen.org/privilege/d2d.remotelaunch</privilege>
375         /// <privlevel>public</privlevel>
376         /// <exception cref="UnauthorizedAccessException">Thrown when an application does not have the privilege to access this method.</exception>
377         /// <since_tizen> 9 </since_tizen>
378         public void SetOndemandLaunchEnabled(bool enable)
379         {
380             Interop.Cion.ErrorCode ret = Interop.CionServer.CionServerSetOnDemandLaunchEnabled(_handle, enable);
381             if (ret != Interop.Cion.ErrorCode.None)
382             {
383                 throw CionErrorFactory.GetException(ret, "Failed to set ondemand launch enable");
384             }
385         }
386
387         /// <summary>
388         /// The result callback of connection request.
389         /// </summary>
390         /// <param name="peerInfo">The peer info of the cion client.</param>
391         /// <param name="result">The result of the connection.</param>
392         /// <since_tizen> 9 </since_tizen>
393         protected abstract void OnConnectionResult(PeerInfo peerInfo, ConnectionResult result);
394
395         /// <summary>
396         /// The callback invoked when received data.
397         /// </summary>
398         /// <param name="data">The received data.</param>
399         /// <param name="peerInfo">The peer info of the cion client.</param>
400         /// <since_tizen> 9 </since_tizen>
401         protected abstract byte[] OnDataReceived(byte[] data, PeerInfo peerInfo);
402
403         /// <summary>
404         /// The callback invoked when received payload.
405         /// </summary>
406         /// <param name="data">The received data.</param>
407         /// <param name="peerInfo">The peer info of the cion client.</param>
408         /// <param name="status">The status of payload transfer.</param>
409         /// <since_tizen> 9 </since_tizen>
410         protected abstract void OnPayloadReceived(Payload data, PeerInfo peerInfo, PayloadTransferStatus status);
411
412         /// <summary>
413         /// The callback invoked when connection requested from the cion client.
414         /// </summary>
415         /// <param name="peerInfo">The peer info of the cion client.</param>
416         /// <since_tizen> 9 </since_tizen>
417         protected abstract void OnConnectionRequest(PeerInfo peerInfo);
418
419         /// <summary>
420         /// The callback invoked when disconnected with cion client.
421         /// </summary>
422         /// <param name="peerInfo">The peer info of the cion client.</param>
423         /// <since_tizen> 9 </since_tizen>
424         protected abstract void OnDisconnected(PeerInfo peerInfo);
425
426         #region IDisposable Support
427         private bool disposedValue = false;
428
429         /// <summary>
430         /// Releases any unmanaged resources used by this object. Can also dispose any other disposable objects.
431         /// </summary>
432         /// <param name="disposing">If true, disposes any disposable objects. If false, does not dispose disposable objects.</param>
433         /// <since_tizen> 9 </since_tizen>
434         protected virtual void Dispose(bool disposing)
435         {
436             if (!disposedValue)
437             {
438                 if (disposing)
439                 {
440                     _handle.Dispose();
441                 }
442                 disposedValue = true;
443             }
444         }
445
446         /// <summary>
447         /// Releases all resources used by the ServerBase class.
448         /// </summary>
449         /// <since_tizen> 9 </since_tizen>
450         public void Dispose()
451         {
452             Dispose(true);
453             GC.SuppressFinalize(this);
454         }
455         #endregion
456     }
457 }