f45463a33201d311f1c3f4e3da15e95d2ccd6354
[mono.git] / mcs / class / referencesource / System.ServiceModel / System / ServiceModel / Channels / OneWayChannelFactory.cs
1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4
5 namespace System.ServiceModel.Channels
6 {
7     using System.Diagnostics;
8     using System.IO;
9     using System.Runtime;
10     using System.ServiceModel.Diagnostics;
11     using System.Xml;
12     using System.ServiceModel.Diagnostics.Application;
13
14     class PacketRoutableHeader : DictionaryHeader
15     {
16         PacketRoutableHeader()
17             : base()
18         {
19         }
20
21         public static void AddHeadersTo(Message message, MessageHeader header)
22         {
23             int index = message.Headers.FindHeader(DotNetOneWayStrings.HeaderName, DotNetOneWayStrings.Namespace);
24             if (index == -1)
25             {
26                 if (header == null)
27                 {
28                     header = PacketRoutableHeader.Create();
29                 }
30                 message.Headers.Add(header);
31             }
32         }
33
34         public static void ValidateMessage(Message message)
35         {
36             if (!TryValidateMessage(message))
37             {
38                 throw TraceUtility.ThrowHelperError(
39                     new ProtocolException(SR.GetString(SR.OneWayHeaderNotFound)), message);
40             }
41         }
42
43         public static bool TryValidateMessage(Message message)
44         {
45             int index = message.Headers.FindHeader(
46                 DotNetOneWayStrings.HeaderName, DotNetOneWayStrings.Namespace);
47
48             return (index != -1);
49         }
50
51         public static PacketRoutableHeader Create()
52         {
53             return new PacketRoutableHeader();
54         }
55
56         public override XmlDictionaryString DictionaryName
57         {
58             get { return XD.DotNetOneWayDictionary.HeaderName; }
59         }
60
61         public override XmlDictionaryString DictionaryNamespace
62         {
63             get { return XD.DotNetOneWayDictionary.Namespace; }
64         }
65
66         protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion)
67         {
68             // no contents necessary
69         }
70     }
71
72     /// <summary>
73     /// OneWayChannelFactory built on top of IRequestChannel
74     /// </summary>
75     class RequestOneWayChannelFactory : LayeredChannelFactory<IOutputChannel>
76     {
77         PacketRoutableHeader packetRoutableHeader;
78
79         public RequestOneWayChannelFactory(OneWayBindingElement bindingElement, BindingContext context)
80             : base(context.Binding, context.BuildInnerChannelFactory<IRequestChannel>())
81         {
82             if (bindingElement.PacketRoutable)
83             {
84                 this.packetRoutableHeader = PacketRoutableHeader.Create();
85             }
86         }
87
88         protected override IOutputChannel OnCreateChannel(EndpointAddress to, Uri via)
89         {
90             IRequestChannel innerChannel =
91                 ((IChannelFactory<IRequestChannel>)this.InnerChannelFactory).CreateChannel(to, via);
92
93             return new RequestOutputChannel(this, innerChannel, this.packetRoutableHeader);
94         }
95
96         class RequestOutputChannel : OutputChannel
97         {
98             IRequestChannel innerChannel;
99             MessageHeader packetRoutableHeader;
100
101             public RequestOutputChannel(ChannelManagerBase factory,
102                 IRequestChannel innerChannel, MessageHeader packetRoutableHeader)
103                 : base(factory)
104             {
105                 this.innerChannel = innerChannel;
106                 this.packetRoutableHeader = packetRoutableHeader;
107             }
108
109             #region Inner Channel delegation
110             public override EndpointAddress RemoteAddress
111             {
112                 get { return this.innerChannel.RemoteAddress; }
113             }
114
115             public override Uri Via
116             {
117                 get { return this.innerChannel.Via; }
118             }
119
120             protected override void OnAbort()
121             {
122                 this.innerChannel.Abort();
123             }
124
125             protected override void OnOpen(TimeSpan timeout)
126             {
127                 this.innerChannel.Open(timeout);
128             }
129
130             protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
131             {
132                 return this.innerChannel.BeginOpen(timeout, callback, state);
133             }
134
135             protected override void OnEndOpen(IAsyncResult result)
136             {
137                 this.innerChannel.EndOpen(result);
138             }
139
140             protected override void OnClose(TimeSpan timeout)
141             {
142                 this.innerChannel.Close(timeout);
143             }
144
145             protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
146             {
147                 return this.innerChannel.BeginClose(timeout, callback, state);
148             }
149
150             protected override void OnEndClose(IAsyncResult result)
151             {
152                 this.innerChannel.EndClose(result);
153             }
154
155             public override T GetProperty<T>()
156             {
157                 T result = base.GetProperty<T>();
158
159                 if (result == null)
160                 {
161                     result = this.innerChannel.GetProperty<T>();
162                 }
163
164                 return result;
165             }
166             #endregion
167
168             // add our oneWay header to every message (if it's not already there)
169             protected override void AddHeadersTo(Message message)
170             {
171                 base.AddHeadersTo(message);
172
173                 if (this.packetRoutableHeader != null)
174                 {
175                     PacketRoutableHeader.AddHeadersTo(message, this.packetRoutableHeader);
176                 }
177             }
178
179             protected override void OnSend(Message message, TimeSpan timeout)
180             {
181                 Message response = this.innerChannel.Request(message, timeout);
182                 using (response)
183                 {
184                     ValidateResponse(response);
185                 }
186             }
187
188             protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
189             {
190                 return this.innerChannel.BeginRequest(message, timeout, callback, state);
191             }
192
193             protected override void OnEndSend(IAsyncResult result)
194             {
195                 Message response = this.innerChannel.EndRequest(result);
196                 using (response)
197                 {
198                     ValidateResponse(response);
199                 }
200             }
201
202             void ValidateResponse(Message response)
203             {
204                 if (response != null)
205                 {
206                     if (response.Version == MessageVersion.None && response is NullMessage)
207                     {
208                         response.Close();
209                         return;
210                     }
211
212                     Exception innerException = null;
213
214                     if (response.IsFault)
215                     {
216                         try
217                         {
218                             MessageFault messageFault = MessageFault.CreateFault(response, TransportDefaults.MaxFaultSize);
219                             innerException = new FaultException(messageFault);
220                         }
221                         catch (Exception e)
222                         {
223                             if (Fx.IsFatal(e))
224                             {
225                                 throw;
226                             }
227
228                             if (e is CommunicationException ||
229                                 e is TimeoutException ||
230                                 e is XmlException ||
231                                 e is IOException)
232                             {
233                                 innerException = e; // expected exception generating fault
234                             }
235                             else
236                             {
237                                 throw;
238                             }
239                         }
240                     }
241
242                     throw TraceUtility.ThrowHelperError(
243                         new ProtocolException(SR.GetString(SR.OneWayUnexpectedResponse), innerException),
244                         response);
245                 }
246             }
247         }
248     }
249
250     // <summary>
251     // OneWayChannelFactory built on top of IDuplexChannel
252     // </summary>
253     class DuplexOneWayChannelFactory : LayeredChannelFactory<IOutputChannel>
254     {
255         IChannelFactory<IDuplexChannel> innnerFactory;
256         bool packetRoutable;
257
258         public DuplexOneWayChannelFactory(OneWayBindingElement bindingElement, BindingContext context)
259             : base(context.Binding, context.BuildInnerChannelFactory<IDuplexChannel>())
260         {
261             this.innnerFactory = (IChannelFactory<IDuplexChannel>)this.InnerChannelFactory;
262             this.packetRoutable = bindingElement.PacketRoutable;
263         }
264
265         protected override IOutputChannel OnCreateChannel(EndpointAddress address, Uri via)
266         {
267             IDuplexChannel channel = this.innnerFactory.CreateChannel(address, via);
268             return new DuplexOutputChannel(this, channel);
269         }
270
271         class DuplexOutputChannel : OutputChannel
272         {
273             IDuplexChannel innerChannel;
274             bool packetRoutable;
275
276             public DuplexOutputChannel(DuplexOneWayChannelFactory factory, IDuplexChannel innerChannel)
277                 : base(factory)
278             {
279                 this.packetRoutable = factory.packetRoutable;
280                 this.innerChannel = innerChannel;
281             }
282
283             public override EndpointAddress RemoteAddress
284             {
285                 get { return this.innerChannel.RemoteAddress; }
286             }
287
288             public override Uri Via
289             {
290                 get { return this.innerChannel.Via; }
291             }
292
293             protected override void OnAbort()
294             {
295                 this.innerChannel.Abort();
296             }
297
298             protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
299             {
300                 return this.innerChannel.BeginClose(timeout, callback, state);
301             }
302
303             protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
304             {
305                 return this.innerChannel.BeginOpen(timeout, callback, state);
306             }
307
308             protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
309             {
310                 StampMessage(message);
311                 return this.innerChannel.BeginSend(message, timeout, callback, state);
312             }
313
314             protected override void OnClose(TimeSpan timeout)
315             {
316                 this.innerChannel.Close(timeout);
317             }
318
319             protected override void OnEndClose(IAsyncResult result)
320             {
321                 this.innerChannel.EndClose(result);
322             }
323
324             protected override void OnEndOpen(IAsyncResult result)
325             {
326                 this.innerChannel.EndOpen(result);
327             }
328
329             protected override void OnEndSend(IAsyncResult result)
330             {
331                 this.innerChannel.EndSend(result);
332             }
333
334             protected override void OnOpen(TimeSpan timeout)
335             {
336                 this.innerChannel.Open(timeout);
337             }
338
339             protected override void OnSend(Message message, TimeSpan timeout)
340             {
341                 StampMessage(message);
342                 this.innerChannel.Send(message, timeout);
343             }
344
345             void StampMessage(Message message)
346             {
347                 if (this.packetRoutable)
348                 {
349                     PacketRoutableHeader.AddHeadersTo(message, null);
350                 }
351             }
352         }
353     }
354
355     /// <summary>
356     /// OneWayChannelFactory built on top of IDuplexSessionChannel
357     /// </summary>
358     class DuplexSessionOneWayChannelFactory : LayeredChannelFactory<IOutputChannel>
359     {
360         ChannelPool<IDuplexSessionChannel> channelPool;
361         ChannelPoolSettings channelPoolSettings;
362         bool packetRoutable;
363
364         public DuplexSessionOneWayChannelFactory(OneWayBindingElement bindingElement, BindingContext context)
365             : base(context.Binding, context.BuildInnerChannelFactory<IDuplexSessionChannel>())
366         {
367             this.packetRoutable = bindingElement.PacketRoutable;
368
369             ISecurityCapabilities innerSecurityCapabilities = this.InnerChannelFactory.GetProperty<ISecurityCapabilities>();
370
371             // can't pool across outer channels if the inner channels support client auth
372             if (innerSecurityCapabilities != null && innerSecurityCapabilities.SupportsClientAuthentication)
373             {
374                 this.channelPoolSettings = bindingElement.ChannelPoolSettings.Clone();
375             }
376             else
377             {
378                 this.channelPool = new ChannelPool<IDuplexSessionChannel>(bindingElement.ChannelPoolSettings);
379             }
380         }
381
382         internal ChannelPool<IDuplexSessionChannel> GetChannelPool(out bool cleanupChannelPool)
383         {
384             if (this.channelPool != null)
385             {
386                 cleanupChannelPool = false;
387                 return this.channelPool;
388             }
389             else
390             {
391                 cleanupChannelPool = true;
392                 Fx.Assert(this.channelPoolSettings != null, "Need either settings or a pool");
393                 return new ChannelPool<IDuplexSessionChannel>(this.channelPoolSettings);
394             }
395         }
396
397         protected override void OnAbort()
398         {
399             if (this.channelPool != null)
400             {
401                 this.channelPool.Close(TimeSpan.Zero);
402             }
403             base.OnAbort();
404         }
405
406         protected override void OnClose(TimeSpan timeout)
407         {
408             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
409             if (this.channelPool != null)
410             {
411                 this.channelPool.Close(timeoutHelper.RemainingTime());
412             }
413             base.OnClose(timeoutHelper.RemainingTime());
414         }
415
416         protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
417         {
418             TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
419             if (this.channelPool != null)
420             {
421                 this.channelPool.Close(timeoutHelper.RemainingTime());
422             }
423             return base.OnBeginClose(timeoutHelper.RemainingTime(), callback, state);
424         }
425
426         protected override IOutputChannel OnCreateChannel(EndpointAddress address, Uri via)
427         {
428             return new DuplexSessionOutputChannel(this, address, via);
429         }
430
431         class DuplexSessionOutputChannel : OutputChannel
432         {
433             ChannelPool<IDuplexSessionChannel> channelPool;
434             EndpointAddress remoteAddress;
435             IChannelFactory<IDuplexSessionChannel> innerFactory;
436             AsyncCallback onReceive;
437             bool packetRoutable;
438             bool cleanupChannelPool;
439             Uri via;
440
441             public DuplexSessionOutputChannel(DuplexSessionOneWayChannelFactory factory,
442                 EndpointAddress remoteAddress, Uri via)
443                 : base(factory)
444             {
445                 this.channelPool = factory.GetChannelPool(out cleanupChannelPool);
446                 this.packetRoutable = factory.packetRoutable;
447                 this.innerFactory = (IChannelFactory<IDuplexSessionChannel>)factory.InnerChannelFactory;
448                 this.remoteAddress = remoteAddress;
449                 this.via = via;
450             }
451
452             public override EndpointAddress RemoteAddress
453             {
454                 get { return this.remoteAddress; }
455             }
456
457             public override Uri Via
458             {
459                 get { return this.via; }
460             }
461
462             #region Channel Lifetime
463             protected override void OnOpen(TimeSpan timeout)
464             {
465             }
466
467             protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
468             {
469                 return new CompletedAsyncResult(callback, state);
470             }
471
472             protected override void OnEndOpen(IAsyncResult result)
473             {
474                 CompletedAsyncResult.End(result);
475             }
476
477             protected override void OnAbort()
478             {
479                 if (cleanupChannelPool)
480                 {
481                     this.channelPool.Close(TimeSpan.Zero);
482                 }
483             }
484
485             protected override void OnClose(TimeSpan timeout)
486             {
487                 if (cleanupChannelPool)
488                 {
489                     this.channelPool.Close(timeout);
490                 }
491             }
492
493             protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
494             {
495                 if (cleanupChannelPool)
496                 {
497                     this.channelPool.Close(timeout);
498                 }
499                 return new CompletedAsyncResult(callback, state);
500             }
501
502             protected override void OnEndClose(IAsyncResult result)
503             {
504                 CompletedAsyncResult.End(result);
505             }
506             #endregion
507
508             protected override IAsyncResult OnBeginSend(Message message, TimeSpan timeout, AsyncCallback callback, object state)
509             {
510                 return new SendAsyncResult(this, message, timeout, callback, state);
511             }
512
513             protected override void OnEndSend(IAsyncResult result)
514             {
515                 SendAsyncResult.End(result);
516             }
517
518             protected override void OnSend(Message message, TimeSpan timeout)
519             {
520                 TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
521                 ChannelPoolKey key = null;
522                 bool isConnectionFromPool = true;
523                 IDuplexSessionChannel innerChannel =
524                     GetChannelFromPool(ref timeoutHelper, out key, out isConnectionFromPool);
525
526                 bool success = false;
527                 try
528                 {
529                     if (!isConnectionFromPool)
530                     {
531                         StampInitialMessage(message);
532                         innerChannel.Open(timeoutHelper.RemainingTime());
533                         StartBackgroundReceive(innerChannel);
534                     }
535
536                     innerChannel.Send(message, timeoutHelper.RemainingTime());
537                     success = true;
538                 }
539                 finally
540                 {
541                     if (!success)
542                     {
543                         CleanupChannel(innerChannel, false, key, isConnectionFromPool, ref timeoutHelper);
544                     }
545                 }
546
547                 CleanupChannel(innerChannel, true, key, isConnectionFromPool, ref timeoutHelper);
548             }
549
550             // kick off an async receive so that we notice when the server is trying to shutdown
551             void StartBackgroundReceive(IDuplexSessionChannel channel)
552             {
553                 if (this.onReceive == null)
554                 {
555                     this.onReceive = Fx.ThunkCallback(new AsyncCallback(OnReceive));
556                 }
557
558                 channel.BeginReceive(TimeSpan.MaxValue, this.onReceive, channel);
559             }
560
561             void OnReceive(IAsyncResult result)
562             {
563                 IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
564                 bool success = false;
565                 try
566                 {
567                     Message message = channel.EndReceive(result);
568                     if (message == null)
569                     {
570                         channel.Close(this.channelPool.IdleTimeout);
571                         success = true;
572                     }
573                     else
574                     {
575                         message.Close();
576                     }
577                 }
578                 catch (CommunicationException e)
579                 {
580                     DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
581                 }
582                 catch (TimeoutException e)
583                 {
584                     if (TD.CloseTimeoutIsEnabled())
585                     {
586                         TD.CloseTimeout(e.Message);
587                     }
588                     DiagnosticUtility.TraceHandledException(e, TraceEventType.Information);
589                 }
590                 finally
591                 {
592                     if (!success)
593                     {
594                         channel.Abort();
595                     }
596                 }
597             }
598
599             void StampInitialMessage(Message message)
600             {
601                 if (this.packetRoutable)
602                 {
603                     PacketRoutableHeader.AddHeadersTo(message, null);
604                 }
605             }
606
607             void CleanupChannel(IDuplexSessionChannel channel, bool connectionStillGood, ChannelPoolKey key, bool isConnectionFromPool, ref TimeoutHelper timeoutHelper)
608             {
609                 if (isConnectionFromPool)
610                 {
611                     this.channelPool.ReturnConnection(key, channel, connectionStillGood, timeoutHelper.RemainingTime());
612                 }
613                 else
614                 {
615                     if (connectionStillGood)
616                     {
617                         this.channelPool.AddConnection(key, channel, timeoutHelper.RemainingTime());
618                     }
619                     else
620                     {
621                         channel.Abort();
622                     }
623                 }
624             }
625
626             IDuplexSessionChannel GetChannelFromPool(ref TimeoutHelper timeoutHelper, out ChannelPoolKey key,
627                 out bool isConnectionFromPool)
628             {
629                 isConnectionFromPool = true;
630                 while (true)
631                 {
632                     IDuplexSessionChannel pooledChannel
633                         = this.channelPool.TakeConnection(this.RemoteAddress, this.Via, timeoutHelper.RemainingTime(), out key);
634
635                     if (pooledChannel == null)
636                     {
637                         isConnectionFromPool = false;
638                         return this.innerFactory.CreateChannel(RemoteAddress, Via);
639                     }
640
641                     // only return good connections
642                     if (pooledChannel.State == CommunicationState.Opened)
643                     {
644                         return pooledChannel;
645                     }
646
647                     // Abort stale connections from the pool
648                     this.channelPool.ReturnConnection(key, pooledChannel, false, timeoutHelper.RemainingTime());
649                 }
650             }
651
652             class SendAsyncResult : AsyncResult
653             {
654                 DuplexSessionOutputChannel parent;
655                 IDuplexSessionChannel innerChannel;
656                 Message message;
657                 TimeoutHelper timeoutHelper;
658                 static AsyncCallback onOpen;
659                 static AsyncCallback onInnerSend = Fx.ThunkCallback(new AsyncCallback(OnInnerSend));
660                 ChannelPoolKey key;
661                 bool isConnectionFromPool;
662
663                 public SendAsyncResult(DuplexSessionOutputChannel parent, Message message, TimeSpan timeout,
664                     AsyncCallback callback, object state)
665                     : base(callback, state)
666                 {
667                     this.parent = parent;
668                     this.message = message;
669                     this.timeoutHelper = new TimeoutHelper(timeout);
670                     this.innerChannel =
671                         parent.GetChannelFromPool(ref this.timeoutHelper, out this.key, out this.isConnectionFromPool);
672
673                     bool success = false;
674                     bool completeSelf = true;
675                     try
676                     {
677                         if (!this.isConnectionFromPool)
678                         {
679                             completeSelf = OpenNewChannel();
680                         }
681
682                         if (completeSelf)
683                         {
684                             completeSelf = SendMessage();
685                         }
686                         success = true;
687                     }
688                     finally
689                     {
690                         if (!success)
691                         {
692                             Cleanup(false);
693                         }
694                     }
695
696                     if (completeSelf)
697                     {
698                         Cleanup(true);
699                         base.Complete(true);
700                     }
701                 }
702
703                 public static void End(IAsyncResult result)
704                 {
705                     AsyncResult.End<SendAsyncResult>(result);
706                 }
707
708                 void Cleanup(bool connectionStillGood)
709                 {
710                     parent.CleanupChannel(this.innerChannel, connectionStillGood, this.key,
711                         this.isConnectionFromPool, ref this.timeoutHelper);
712                 }
713
714                 bool OpenNewChannel()
715                 {
716                     if (onOpen == null)
717                     {
718                         onOpen = Fx.ThunkCallback(new AsyncCallback(OnOpen));
719                     }
720
721                     this.parent.StampInitialMessage(this.message);
722                     IAsyncResult result = this.innerChannel.BeginOpen(timeoutHelper.RemainingTime(), onOpen, this);
723                     if (!result.CompletedSynchronously)
724                     {
725                         return false;
726                     }
727
728                     this.CompleteOpen(result);
729                     return true;
730                 }
731
732                 void CompleteOpen(IAsyncResult result)
733                 {
734                     this.innerChannel.EndOpen(result);
735                     this.parent.StartBackgroundReceive(this.innerChannel);
736                 }
737
738                 bool SendMessage()
739                 {
740                     IAsyncResult result = innerChannel.BeginSend(this.message, onInnerSend, this);
741                     if (!result.CompletedSynchronously)
742                     {
743                         return false;
744                     }
745
746                     innerChannel.EndSend(result);
747                     return true;
748                 }
749
750                 static void OnOpen(IAsyncResult result)
751                 {
752                     if (result.CompletedSynchronously)
753                     {
754                         return;
755                     }
756
757                     SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
758
759                     Exception completionException = null;
760                     bool completeSelf = false;
761                     try
762                     {
763                         thisPtr.CompleteOpen(result);
764                         completeSelf = thisPtr.SendMessage();
765                     }
766 #pragma warning suppress 56500 // [....], transferring exception to another thread
767                     catch (Exception e)
768                     {
769                         if (Fx.IsFatal(e))
770                         {
771                             throw;
772                         }
773
774                         completeSelf = true;
775                         completionException = e;
776                     }
777
778                     if (completeSelf)
779                     {
780                         thisPtr.Cleanup(completionException == null);
781                         thisPtr.Complete(false, completionException);
782                     }
783                 }
784
785                 static void OnInnerSend(IAsyncResult result)
786                 {
787                     if (result.CompletedSynchronously)
788                     {
789                         return;
790                     }
791
792                     SendAsyncResult thisPtr = (SendAsyncResult)result.AsyncState;
793
794                     Exception completionException = null;
795                     try
796                     {
797                         thisPtr.innerChannel.EndSend(result);
798                     }
799 #pragma warning suppress 56500 // [....], transferring exception to another thread
800                     catch (Exception e)
801                     {
802                         if (Fx.IsFatal(e))
803                         {
804                             throw;
805                         }
806
807                         completionException = e;
808                     }
809
810                     thisPtr.Cleanup(completionException == null);
811                     thisPtr.Complete(false, completionException);
812                 }
813             }
814         }
815     }
816 }