Merge pull request #3678 from mono/seq-read
[mono.git] / mcs / class / System / Mono.Net.Security / LegacySslStream.cs
1 //
2 // System.Net.Security.SslStream.cs
3 //
4 // Authors:
5 //      Tim Coleman (tim@timcoleman.com)
6 //      Atsushi Enomoto (atsushi@ximian.com)
7 //      Marek Safar (marek.safar@gmail.com)
8 //
9 // Copyright (C) Tim Coleman, 2004
10 // (c) 2004,2007 Novell, Inc. (http://www.novell.com)
11 // Copyright 2011 Xamarin Inc.
12 //
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 #if SECURITY_DEP
36
37 #if MONO_SECURITY_ALIAS
38 extern alias MonoSecurity;
39 #endif
40
41 #if MONO_SECURITY_ALIAS
42 using MonoCipherAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.CipherAlgorithmType;
43 using MonoHashAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.HashAlgorithmType;
44 using MonoExchangeAlgorithmType = MonoSecurity::Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
45 using MonoSecurityProtocolType = MonoSecurity::Mono.Security.Protocol.Tls.SecurityProtocolType;
46 using MonoSecurity::Mono.Security.Protocol.Tls;
47 using MonoSecurity::Mono.Security.Interface;
48 #else
49 using MonoCipherAlgorithmType = Mono.Security.Protocol.Tls.CipherAlgorithmType;
50 using MonoHashAlgorithmType = Mono.Security.Protocol.Tls.HashAlgorithmType;
51 using MonoExchangeAlgorithmType = Mono.Security.Protocol.Tls.ExchangeAlgorithmType;
52 using MonoSecurityProtocolType = Mono.Security.Protocol.Tls.SecurityProtocolType;
53 using Mono.Security.Protocol.Tls;
54 using Mono.Security.Interface;
55 #endif
56
57 using CipherAlgorithmType = System.Security.Authentication.CipherAlgorithmType;
58 using HashAlgorithmType = System.Security.Authentication.HashAlgorithmType;
59 using ExchangeAlgorithmType = System.Security.Authentication.ExchangeAlgorithmType;
60
61 using System;
62 using System.IO;
63 using System.Net;
64 using System.Net.Security;
65 using System.Security.Authentication;
66 using System.Security.Cryptography.X509Certificates;
67 using System.Security.Principal;
68 using System.Security.Cryptography;
69
70 using System.Threading.Tasks;
71
72 namespace Mono.Net.Security.Private
73 {
74         /*
75          * Strictly private - do not use outside the Mono.Net.Security directory.
76          */
77         [MonoTODO ("Non-X509Certificate2 certificate is not supported")]
78         internal class LegacySslStream : AuthenticatedStream, IMonoSslStream
79         {
80                 #region Fields
81
82                 SslStreamBase ssl_stream;
83                 ICertificateValidator certificateValidator;
84                 MonoTlsProvider provider;
85
86                 #endregion // Fields
87
88                 #region Constructors
89
90                 public LegacySslStream (Stream innerStream, bool leaveInnerStreamOpen, MonoTlsProvider provider, MonoTlsSettings settings)
91                         : base (innerStream, leaveInnerStreamOpen)
92                 {
93                         this.provider = provider;
94                         certificateValidator = ChainValidationHelper.GetInternalValidator (provider, settings);
95                 }
96                 #endregion // Constructors
97
98                 #region Properties
99
100                 public override bool CanRead {
101                         get { return InnerStream.CanRead; }
102                 }
103
104                 public override bool CanSeek {
105                         get { return InnerStream.CanSeek; }
106                 }
107
108                 public override bool CanTimeout {
109                         get { return InnerStream.CanTimeout; }
110                 }
111
112                 public override bool CanWrite {
113                         get { return InnerStream.CanWrite; }
114                 }
115
116                 public override long Length {
117                         get { return InnerStream.Length; }
118                 }
119
120                 public override long Position {
121                         get { return InnerStream.Position; }
122                         set {
123                                 throw new NotSupportedException ("This stream does not support seek operations");
124                         }
125                 }
126
127                 // AuthenticatedStream overrides
128
129                 public override bool IsAuthenticated { 
130                         get { return ssl_stream != null; }
131                 }
132
133                 public override bool IsEncrypted { 
134                         get { return IsAuthenticated; }
135                 }
136
137                 public override bool IsMutuallyAuthenticated { 
138                         get { return IsAuthenticated && (IsServer ? RemoteCertificate != null : LocalCertificate != null); }
139                 }
140
141                 public override bool IsServer { 
142                         get { return ssl_stream is SslServerStream; }
143                 }
144
145                 public override bool IsSigned { 
146                         get { return IsAuthenticated; }
147                 }
148
149                 public override int ReadTimeout {
150                         get { return InnerStream.ReadTimeout; }
151                         set { InnerStream.ReadTimeout = value; }
152                 }
153
154                 public override int WriteTimeout {
155                         get { return InnerStream.WriteTimeout; }
156                         set { InnerStream.WriteTimeout = value; }
157                 }
158
159                 // SslStream
160
161                 public virtual bool CheckCertRevocationStatus {
162                         get {
163                                 if (!IsAuthenticated)
164                                         return false;
165
166                                 return ssl_stream.CheckCertRevocationStatus;
167                         }
168                 }
169
170                 public virtual CipherAlgorithmType CipherAlgorithm  {
171                         get {
172                                 CheckConnectionAuthenticated ();
173
174                                 switch (ssl_stream.CipherAlgorithm) {
175                                 case MonoCipherAlgorithmType.Des:
176                                         return CipherAlgorithmType.Des;
177                                 case MonoCipherAlgorithmType.None:
178                                         return CipherAlgorithmType.None;
179                                 case MonoCipherAlgorithmType.Rc2:
180                                         return CipherAlgorithmType.Rc2;
181                                 case MonoCipherAlgorithmType.Rc4:
182                                         return CipherAlgorithmType.Rc4;
183                                 case MonoCipherAlgorithmType.SkipJack:
184                                         break;
185                                 case MonoCipherAlgorithmType.TripleDes:
186                                         return CipherAlgorithmType.TripleDes;
187                                 case MonoCipherAlgorithmType.Rijndael:
188                                         switch (ssl_stream.CipherStrength) {
189                                         case 128:
190                                                 return CipherAlgorithmType.Aes128;
191                                         case 192:
192                                                 return CipherAlgorithmType.Aes192;
193                                         case 256:
194                                                 return CipherAlgorithmType.Aes256;
195                                         }
196                                         break;
197                                 }
198
199                                 throw new InvalidOperationException ("Not supported cipher algorithm is in use. It is likely a bug in SslStream.");
200                         }
201                 }
202
203                 public virtual int CipherStrength  {
204                         get {
205                                 CheckConnectionAuthenticated ();
206
207                                 return ssl_stream.CipherStrength;
208                         }
209                 }
210
211                 public virtual HashAlgorithmType HashAlgorithm  {
212                         get {
213                                 CheckConnectionAuthenticated ();
214
215                                 switch (ssl_stream.HashAlgorithm) {
216                                 case MonoHashAlgorithmType.Md5:
217                                         return HashAlgorithmType.Md5;
218                                 case MonoHashAlgorithmType.None:
219                                         return HashAlgorithmType.None;
220                                 case MonoHashAlgorithmType.Sha1:
221                                         return HashAlgorithmType.Sha1;
222                                 }
223
224                                 throw new InvalidOperationException ("Not supported hash algorithm is in use. It is likely a bug in SslStream.");
225                         }
226                 }
227
228                 public virtual int HashStrength  {
229                         get {
230                                 CheckConnectionAuthenticated ();
231
232                                 return ssl_stream.HashStrength;
233                         }
234                 }
235
236                 public virtual ExchangeAlgorithmType KeyExchangeAlgorithm { 
237                         get {
238                                 CheckConnectionAuthenticated ();
239
240                                 switch (ssl_stream.KeyExchangeAlgorithm) {
241                                 case MonoExchangeAlgorithmType.DiffieHellman:
242                                         return ExchangeAlgorithmType.DiffieHellman;
243                                 case MonoExchangeAlgorithmType.Fortezza:
244                                         break;
245                                 case MonoExchangeAlgorithmType.None:
246                                         return ExchangeAlgorithmType.None;
247                                 case MonoExchangeAlgorithmType.RsaKeyX:
248                                         return ExchangeAlgorithmType.RsaKeyX;
249                                 case MonoExchangeAlgorithmType.RsaSign:
250                                         return ExchangeAlgorithmType.RsaSign;
251                                 }
252
253                                 throw new InvalidOperationException ("Not supported exchange algorithm is in use. It is likely a bug in SslStream.");
254                         }
255                 }
256
257                 public virtual int KeyExchangeStrength { 
258                         get {
259                                 CheckConnectionAuthenticated ();
260
261                                 return ssl_stream.KeyExchangeStrength;
262                         }
263                 }
264
265                 X509Certificate IMonoSslStream.InternalLocalCertificate {
266                         get {
267                                 return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
268                         }
269                 }
270
271                 public virtual X509Certificate LocalCertificate {
272                         get {
273                                 CheckConnectionAuthenticated ();
274
275                                 return IsServer ? ssl_stream.ServerCertificate : ((SslClientStream) ssl_stream).SelectedClientCertificate;
276                         }
277                 }
278
279                 public virtual X509Certificate RemoteCertificate {
280                         get {
281                                 CheckConnectionAuthenticated ();
282                                 return !IsServer ? ssl_stream.ServerCertificate : ((SslServerStream) ssl_stream).ClientCertificate;
283                         }
284                 }
285
286                 public virtual SslProtocols SslProtocol {
287                         get {
288                                 CheckConnectionAuthenticated ();
289
290                                 switch (ssl_stream.SecurityProtocol) {
291                                 case MonoSecurityProtocolType.Default:
292                                         return SslProtocols.Default;
293                                 case MonoSecurityProtocolType.Ssl2:
294                                         return SslProtocols.Ssl2;
295                                 case MonoSecurityProtocolType.Ssl3:
296                                         return SslProtocols.Ssl3;
297                                 case MonoSecurityProtocolType.Tls:
298                                         return SslProtocols.Tls;
299                                 }
300
301                                 throw new InvalidOperationException ("Not supported SSL/TLS protocol is in use. It is likely a bug in SslStream.");
302                         }
303                 }
304
305                 #endregion // Properties
306
307                 #region Methods
308
309 /*
310                 AsymmetricAlgorithm GetPrivateKey (X509Certificate cert, string targetHost)
311                 {
312                         // FIXME: what can I do for non-X509Certificate2 ?
313                         X509Certificate2 cert2 = cert as X509Certificate2;
314                         return cert2 != null ? cert2.PrivateKey : null;
315                 }
316 */
317                 X509Certificate OnCertificateSelection (X509CertificateCollection clientCerts, X509Certificate serverCert, string targetHost, X509CertificateCollection serverRequestedCerts)
318                 {
319 #pragma warning disable 618
320                         string [] acceptableIssuers = new string [serverRequestedCerts != null ? serverRequestedCerts.Count : 0];
321                         for (int i = 0; i < acceptableIssuers.Length; i++)
322                                 acceptableIssuers [i] = serverRequestedCerts [i].GetIssuerName ();
323                         X509Certificate clientCertificate;
324                         certificateValidator.SelectClientCertificate (targetHost, clientCerts, serverCert, acceptableIssuers, out clientCertificate);
325                         return clientCertificate;
326 #pragma warning restore 618
327                 }
328
329                 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
330                 {
331                         return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false, asyncCallback, asyncState);
332                 }
333
334                 public virtual IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
335                 {
336                         if (IsAuthenticated)
337                                 throw new InvalidOperationException ("This SslStream is already authenticated");
338
339                         SslClientStream s = new SslClientStream (InnerStream, targetHost, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols), clientCertificates);
340                         s.CheckCertRevocationStatus = checkCertificateRevocation;
341
342                         // Due to the Mono.Security internal, it cannot reuse
343                         // the delegated argument, as Mono.Security creates 
344                         // another instance of X509Certificate which lacks 
345                         // private key but is filled the private key via this
346                         // delegate.
347                         s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string host) {
348                                 string hash = cert.GetCertHashString ();
349                                 // ... so, we cannot use the delegate argument.
350                                 foreach (X509Certificate cc in clientCertificates) {
351                                         if (cc.GetCertHashString () != hash)
352                                                 continue;
353                                         X509Certificate2 cert2 = cc as X509Certificate2;
354                                         cert2 = cert2 ?? new X509Certificate2 (cc);
355                                         return cert2.PrivateKey;
356                                 }
357                                 return null;
358                         };
359
360                         // Even if validation_callback is null this allows us to verify requests where the user
361                         // does not provide a verification callback but attempts to authenticate with the website
362                         // as a client (see https://bugzilla.xamarin.com/show_bug.cgi?id=18962 for an example)
363                         s.ServerCertValidation2 += (mcerts) => {
364                                 X509CertificateCollection certs = null;
365                                 if (mcerts != null) {
366                                         certs = new X509CertificateCollection ();
367                                         for (int i = 0; i < mcerts.Count; i++)
368                                                 certs.Add (new X509Certificate2 (mcerts [i].RawData));
369                                 }
370                                 return ((ChainValidationHelper)certificateValidator).ValidateCertificate (targetHost, false, certs);
371                         };
372                         s.ClientCertSelectionDelegate = OnCertificateSelection;
373
374                         ssl_stream = s;
375
376                         return BeginWrite (new byte [0], 0, 0, asyncCallback, asyncState);
377                 }
378
379                 public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
380                 {
381                         CheckConnectionAuthenticated ();
382
383                         return ssl_stream.BeginRead (buffer, offset, count, asyncCallback, asyncState);
384                 }
385
386                 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
387                 {
388                         return BeginAuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false, asyncCallback, asyncState);
389                 }
390
391                 public virtual IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
392                 {
393                         if (IsAuthenticated)
394                                 throw new InvalidOperationException ("This SslStream is already authenticated");
395
396                         SslServerStream s = new SslServerStream (InnerStream, serverCertificate, false, clientCertificateRequired, !LeaveInnerStreamOpen, GetMonoSslProtocol (enabledSslProtocols));
397                         s.CheckCertRevocationStatus = checkCertificateRevocation;
398                         // Due to the Mono.Security internal, it cannot reuse
399                         // the delegated argument, as Mono.Security creates 
400                         // another instance of X509Certificate which lacks 
401                         // private key but is filled the private key via this
402                         // delegate.
403                         s.PrivateKeyCertSelectionDelegate = delegate (X509Certificate cert, string targetHost) {
404                                 // ... so, we cannot use the delegate argument.
405                                 X509Certificate2 cert2 = serverCertificate as X509Certificate2 ?? new X509Certificate2 (serverCertificate);
406                                 return cert2 != null ? cert2.PrivateKey : null;
407                         };
408
409                         s.ClientCertValidationDelegate = delegate (X509Certificate cert, int[] certErrors) {
410                                 var errors = certErrors.Length > 0 ? MonoSslPolicyErrors.RemoteCertificateChainErrors : MonoSslPolicyErrors.None;
411                                 return ((ChainValidationHelper)certificateValidator).ValidateClientCertificate (cert, errors);
412                         };
413
414                         ssl_stream = s;
415
416                         return BeginWrite (new byte[0], 0, 0, asyncCallback, asyncState);
417                 }
418
419                 MonoSecurityProtocolType GetMonoSslProtocol (SslProtocols ms)
420                 {
421                         switch (ms) {
422                         case SslProtocols.Ssl2:
423                                 return MonoSecurityProtocolType.Ssl2;
424                         case SslProtocols.Ssl3:
425                                 return MonoSecurityProtocolType.Ssl3;
426                         case SslProtocols.Tls:
427                                 return MonoSecurityProtocolType.Tls;
428                         default:
429                                 return MonoSecurityProtocolType.Default;
430                         }
431                 }
432
433                 public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
434                 {
435                         CheckConnectionAuthenticated ();
436
437                         return ssl_stream.BeginWrite (buffer, offset, count, asyncCallback, asyncState);
438                 }
439
440                 public virtual void AuthenticateAsClient (string targetHost)
441                 {
442                         AuthenticateAsClient (targetHost, new X509CertificateCollection (), SslProtocols.Tls, false);
443                 }
444
445                 public virtual void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
446                 {
447                         EndAuthenticateAsClient (BeginAuthenticateAsClient (
448                                 targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, null, null));
449                 }
450
451                 public virtual void AuthenticateAsServer (X509Certificate serverCertificate)
452                 {
453                         AuthenticateAsServer (serverCertificate, false, SslProtocols.Tls, false);
454                 }
455
456                 public virtual void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
457                 {
458                         EndAuthenticateAsServer (BeginAuthenticateAsServer (
459                                 serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, null, null));
460                 }
461
462                 protected override void Dispose (bool disposing)
463                 {
464                         if (disposing) {
465                                 if (ssl_stream != null)
466                                         ssl_stream.Dispose ();
467                                 ssl_stream = null;
468                         }
469                         base.Dispose (disposing);
470                 }
471
472                 public virtual void EndAuthenticateAsClient (IAsyncResult asyncResult)
473                 {
474                         CheckConnectionAuthenticated ();
475
476                         if (CanRead)
477                                 ssl_stream.EndRead (asyncResult);
478                         else
479                                 ssl_stream.EndWrite (asyncResult);
480                 }
481
482                 public virtual void EndAuthenticateAsServer (IAsyncResult asyncResult)
483                 {
484                         CheckConnectionAuthenticated ();
485
486                         if (CanRead)
487                                 ssl_stream.EndRead (asyncResult);
488                         else
489                                 ssl_stream.EndWrite (asyncResult);
490                 }
491
492                 public override int EndRead (IAsyncResult asyncResult)
493                 {
494                         CheckConnectionAuthenticated ();
495
496                         return ssl_stream.EndRead (asyncResult);
497                 }
498
499                 public override void EndWrite (IAsyncResult asyncResult)
500                 {
501                         CheckConnectionAuthenticated ();
502
503                         ssl_stream.EndWrite (asyncResult);
504                 }
505
506                 public override void Flush ()
507                 {
508                         CheckConnectionAuthenticated ();
509
510                         InnerStream.Flush ();
511                 }
512
513                 public override int Read (byte[] buffer, int offset, int count)
514                 {
515                         return EndRead (BeginRead (buffer, offset, count, null, null));
516                 }
517
518                 public override long Seek (long offset, SeekOrigin origin)
519                 {
520                         throw new NotSupportedException ("This stream does not support seek operations");
521                 }
522
523                 public override void SetLength (long value)
524                 {
525                         InnerStream.SetLength (value);
526                 }
527
528                 public override void Write (byte[] buffer, int offset, int count)
529                 {
530                         EndWrite (BeginWrite (buffer, offset, count, null, null));
531                 }
532
533                 public void Write (byte[] buffer)
534                 {
535                         Write (buffer, 0, buffer.Length);
536                 }
537
538                 void CheckConnectionAuthenticated ()
539                 {
540                         if (!IsAuthenticated)
541                                 throw new InvalidOperationException ("This operation is invalid until it is successfully authenticated");
542                 }
543
544                 public virtual Task AuthenticateAsClientAsync (string targetHost)
545                 {
546                         return Task.Factory.FromAsync (BeginAuthenticateAsClient, EndAuthenticateAsClient, targetHost, null);
547                 }
548
549                 public virtual Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
550                 {
551                         var t = Tuple.Create (targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, this);
552
553                         return Task.Factory.FromAsync ((callback, state) => {
554                                 var d = (Tuple<string, X509CertificateCollection, SslProtocols, bool, LegacySslStream>) state;
555                                 return d.Item5.BeginAuthenticateAsClient (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
556                         }, EndAuthenticateAsClient, t);
557                 }
558
559                 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate)
560                 {
561                         return Task.Factory.FromAsync (BeginAuthenticateAsServer, EndAuthenticateAsServer, serverCertificate, null);
562                 }
563
564                 public virtual Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
565                 {
566                         var t = Tuple.Create (serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, this);
567
568                         return Task.Factory.FromAsync ((callback, state) => {
569                                 var d = (Tuple<X509Certificate, bool, SslProtocols, bool, LegacySslStream>) state;
570                                 return d.Item5.BeginAuthenticateAsServer (d.Item1, d.Item2, d.Item3, d.Item4, callback, null);
571                         }, EndAuthenticateAsServer, t);
572                 }
573
574                 #endregion // Methods
575
576                 #region IMonoSslStream
577
578                 AuthenticatedStream IMonoSslStream.AuthenticatedStream {
579                         get { return this; }
580                 }
581
582                 TransportContext IMonoSslStream.TransportContext {
583                         get { throw new NotSupportedException (); }
584                 }
585
586                 MonoTlsProvider IMonoSslStream.Provider {
587                         get { return provider; }
588                 }
589
590                 MonoTlsConnectionInfo IMonoSslStream.GetConnectionInfo ()
591                 {
592                         return null;
593                 }
594
595                 #endregion
596         }
597 }
598
599 #endif