[SslContext] Subclass NativeObject + numerous other code updates (#13105)
* Subclass NativeObject to reuse object lifetime code. * Enable nullability and fix code accordingly. * Use 'is' and 'is not' instead of '==' and '!=' for object identity. * Use the null-safe NativeObjectExtensions.GetHandle extension method to get the handle instead of checking for null (avoids some code duplication). * Use 'nameof (parameter)' instead of string constants.
This commit is contained in:
Родитель
959c65d06c
Коммит
44f88fcd92
|
@ -7,6 +7,8 @@
|
||||||
// Copyright 2014-2016 Xamarin Inc.
|
// Copyright 2014-2016 Xamarin Inc.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
@ -37,60 +39,36 @@ namespace Security {
|
||||||
[Obsolete ("Starting with macos10.15 use 'Network.framework' instead.", DiagnosticId = "BI1234", UrlFormat = "https://github.com/xamarin/xamarin-macios/wiki/Obsolete")]
|
[Obsolete ("Starting with macos10.15 use 'Network.framework' instead.", DiagnosticId = "BI1234", UrlFormat = "https://github.com/xamarin/xamarin-macios/wiki/Obsolete")]
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
public class SslContext : INativeObject, IDisposable {
|
public class SslContext : NativeObject {
|
||||||
|
|
||||||
SslConnection connection;
|
SslConnection? connection;
|
||||||
SslStatus result;
|
SslStatus result;
|
||||||
|
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern static /* SSLContextRef */ IntPtr SSLCreateContext (/* CFAllocatorRef */ IntPtr alloc, SslProtocolSide protocolSide, SslConnectionType connectionType);
|
extern static /* SSLContextRef */ IntPtr SSLCreateContext (/* CFAllocatorRef */ IntPtr alloc, SslProtocolSide protocolSide, SslConnectionType connectionType);
|
||||||
|
|
||||||
public SslContext (SslProtocolSide protocolSide, SslConnectionType connectionType)
|
public SslContext (SslProtocolSide protocolSide, SslConnectionType connectionType)
|
||||||
|
: base (SSLCreateContext (IntPtr.Zero, protocolSide, connectionType), true)
|
||||||
{
|
{
|
||||||
Handle = SSLCreateContext (IntPtr.Zero, protocolSide, connectionType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern static /* OSStatus */ SslStatus SSLClose (/* SSLContextRef */ IntPtr context);
|
extern static /* OSStatus */ SslStatus SSLClose (/* SSLContextRef */ IntPtr context);
|
||||||
|
|
||||||
~SslContext ()
|
protected override void Dispose (bool disposing)
|
||||||
{
|
{
|
||||||
Dispose (false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose ()
|
|
||||||
{
|
|
||||||
Dispose (true);
|
|
||||||
GC.SuppressFinalize (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool disposed;
|
|
||||||
|
|
||||||
protected virtual void Dispose (bool disposing)
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
disposed = true;
|
|
||||||
|
|
||||||
if (Handle != IntPtr.Zero)
|
if (Handle != IntPtr.Zero)
|
||||||
result = SSLClose (Handle);
|
result = SSLClose (Handle);
|
||||||
|
|
||||||
// don't remove the read/write delegates before we closed the connection, i.e.
|
// don't remove the read/write delegates before we closed the connection, i.e.
|
||||||
// the SSLClose will send an Alert for a "close notify"
|
// the SSLClose will send an Alert for a "close notify"
|
||||||
if (connection != null) {
|
if (connection is not null) {
|
||||||
connection.Dispose ();
|
connection.Dispose ();
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSLCreateContext -> CFRelease (not SSLDisposeContext)
|
base.Dispose (disposing);
|
||||||
if (Handle != IntPtr.Zero) {
|
|
||||||
CFObject.CFRelease (Handle);
|
|
||||||
Handle = IntPtr.Zero;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public IntPtr Handle { get; private set; }
|
|
||||||
|
|
||||||
public SslStatus GetLastStatus ()
|
public SslStatus GetLastStatus ()
|
||||||
{
|
{
|
||||||
|
@ -149,11 +127,11 @@ namespace Security {
|
||||||
extern static /* OSStatus */ SslStatus SSLSetConnection (/* SSLContextRef */ IntPtr context, /* SSLConnectionRef */ IntPtr connection);
|
extern static /* OSStatus */ SslStatus SSLSetConnection (/* SSLContextRef */ IntPtr context, /* SSLConnectionRef */ IntPtr connection);
|
||||||
|
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern static /* OSStatus */ SslStatus SSLSetIOFuncs (/* SSLContextRef */ IntPtr context, /* SSLReadFunc */ SslReadFunc readFunc, /* SSLWriteFunc */ SslWriteFunc writeFunc);
|
extern static /* OSStatus */ SslStatus SSLSetIOFuncs (/* SSLContextRef */ IntPtr context, /* SSLReadFunc */ SslReadFunc? readFunc, /* SSLWriteFunc */ SslWriteFunc? writeFunc);
|
||||||
|
|
||||||
public SslConnection Connection {
|
public SslConnection? Connection {
|
||||||
get {
|
get {
|
||||||
if (connection == null)
|
if (connection is null)
|
||||||
return null;
|
return null;
|
||||||
IntPtr value;
|
IntPtr value;
|
||||||
result = SSLGetConnection (Handle, out value);
|
result = SSLGetConnection (Handle, out value);
|
||||||
|
@ -162,14 +140,14 @@ namespace Security {
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
// the read/write delegates needs to be set before set set the connection id
|
// the read/write delegates needs to be set before setting the connection id
|
||||||
if (value == null)
|
if (value is null)
|
||||||
result = SSLSetIOFuncs (Handle, null, null);
|
result = SSLSetIOFuncs (Handle, null, null);
|
||||||
else
|
else
|
||||||
result = SSLSetIOFuncs (Handle, value.ReadFunc, value.WriteFunc);
|
result = SSLSetIOFuncs (Handle, value.ReadFunc, value.WriteFunc);
|
||||||
|
|
||||||
if (result == SslStatus.Success) {
|
if (result == SslStatus.Success) {
|
||||||
result = SSLSetConnection (Handle, value == null ? IntPtr.Zero : value.ConnectionId);
|
result = SSLSetConnection (Handle, value is null ? IntPtr.Zero : value.ConnectionId);
|
||||||
connection = value;
|
connection = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,7 +206,7 @@ namespace Security {
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLSetPeerID (/* SSLContextRef */ IntPtr context, /* const void* */ byte* peerID, /* size_t */ nint peerIDLen);
|
extern unsafe static /* OSStatus */ SslStatus SSLSetPeerID (/* SSLContextRef */ IntPtr context, /* const void* */ byte* peerID, /* size_t */ nint peerIDLen);
|
||||||
|
|
||||||
public unsafe byte[] PeerId {
|
public unsafe byte[]? PeerId {
|
||||||
get {
|
get {
|
||||||
nint length;
|
nint length;
|
||||||
IntPtr id;
|
IntPtr id;
|
||||||
|
@ -240,7 +218,7 @@ namespace Security {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
nint length = (value == null) ? 0 : value.Length;
|
nint length = (value is null) ? 0 : value.Length;
|
||||||
fixed (byte *p = value) {
|
fixed (byte *p = value) {
|
||||||
result = SSLSetPeerID (Handle, p, length);
|
result = SSLSetPeerID (Handle, p, length);
|
||||||
}
|
}
|
||||||
|
@ -263,8 +241,8 @@ namespace Security {
|
||||||
|
|
||||||
internal unsafe SslStatus Read (byte[] data, int offset, int size, out nint processed)
|
internal unsafe SslStatus Read (byte[] data, int offset, int size, out nint processed)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (data is null)
|
||||||
throw new ArgumentNullException ("data");
|
throw new ArgumentNullException (nameof (data));
|
||||||
fixed (byte *d = &data [offset])
|
fixed (byte *d = &data [offset])
|
||||||
result = SSLRead (Handle, d, size, out processed);
|
result = SSLRead (Handle, d, size, out processed);
|
||||||
return result;
|
return result;
|
||||||
|
@ -272,7 +250,7 @@ namespace Security {
|
||||||
|
|
||||||
public unsafe SslStatus Read (byte[] data, out nint processed)
|
public unsafe SslStatus Read (byte[] data, out nint processed)
|
||||||
{
|
{
|
||||||
int size = data == null ? 0 : data.Length;
|
int size = data is null ? 0 : data.Length;
|
||||||
fixed (byte *d = data)
|
fixed (byte *d = data)
|
||||||
result = SSLRead (Handle, d, size, out processed);
|
result = SSLRead (Handle, d, size, out processed);
|
||||||
return result;
|
return result;
|
||||||
|
@ -283,8 +261,8 @@ namespace Security {
|
||||||
|
|
||||||
internal unsafe SslStatus Write (byte[] data, int offset, int size, out nint processed)
|
internal unsafe SslStatus Write (byte[] data, int offset, int size, out nint processed)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (data is null)
|
||||||
throw new ArgumentNullException ("data");
|
throw new ArgumentNullException (nameof (data));
|
||||||
fixed (byte *d = &data [offset])
|
fixed (byte *d = &data [offset])
|
||||||
result = SSLWrite (Handle, d, size, out processed);
|
result = SSLWrite (Handle, d, size, out processed);
|
||||||
return result;
|
return result;
|
||||||
|
@ -292,7 +270,7 @@ namespace Security {
|
||||||
|
|
||||||
public unsafe SslStatus Write (byte[] data, out nint processed)
|
public unsafe SslStatus Write (byte[] data, out nint processed)
|
||||||
{
|
{
|
||||||
int size = data == null ? 0 : data.Length;
|
int size = data is null ? 0 : data.Length;
|
||||||
fixed (byte *d = data)
|
fixed (byte *d = data)
|
||||||
result = SSLWrite (Handle, d, size, out processed);
|
result = SSLWrite (Handle, d, size, out processed);
|
||||||
return result;
|
return result;
|
||||||
|
@ -305,7 +283,7 @@ namespace Security {
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLGetSupportedCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref nint numCiphers);
|
extern unsafe static /* OSStatus */ SslStatus SSLGetSupportedCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref nint numCiphers);
|
||||||
|
|
||||||
public unsafe IList<SslCipherSuite> GetSupportedCiphers ()
|
public unsafe IList<SslCipherSuite>? GetSupportedCiphers ()
|
||||||
{
|
{
|
||||||
nint n;
|
nint n;
|
||||||
result = SSLGetNumberSupportedCiphers (Handle, out n);
|
result = SSLGetNumberSupportedCiphers (Handle, out n);
|
||||||
|
@ -327,7 +305,7 @@ namespace Security {
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLGetEnabledCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref nint numCiphers);
|
extern unsafe static /* OSStatus */ SslStatus SSLGetEnabledCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref nint numCiphers);
|
||||||
|
|
||||||
public unsafe IList<SslCipherSuite> GetEnabledCiphers ()
|
public unsafe IList<SslCipherSuite>? GetEnabledCiphers ()
|
||||||
{
|
{
|
||||||
nint n;
|
nint n;
|
||||||
result = SSLGetNumberEnabledCiphers (Handle, out n);
|
result = SSLGetNumberEnabledCiphers (Handle, out n);
|
||||||
|
@ -348,8 +326,8 @@ namespace Security {
|
||||||
|
|
||||||
public unsafe SslStatus SetEnabledCiphers (IEnumerable<SslCipherSuite> ciphers)
|
public unsafe SslStatus SetEnabledCiphers (IEnumerable<SslCipherSuite> ciphers)
|
||||||
{
|
{
|
||||||
if (ciphers == null)
|
if (ciphers is null)
|
||||||
throw new ArgumentNullException ("ciphers");
|
throw new ArgumentNullException (nameof (ciphers));
|
||||||
|
|
||||||
var array = ciphers.ToArray ();
|
var array = ciphers.ToArray ();
|
||||||
fixed (SslCipherSuite *p = array)
|
fixed (SslCipherSuite *p = array)
|
||||||
|
@ -401,7 +379,7 @@ namespace Security {
|
||||||
|
|
||||||
public unsafe SslStatus SetDatagramHelloCookie (byte[] cookie)
|
public unsafe SslStatus SetDatagramHelloCookie (byte[] cookie)
|
||||||
{
|
{
|
||||||
nint len = cookie == null ? 0 : cookie.Length;
|
nint len = cookie is null ? 0 : cookie.Length;
|
||||||
fixed (byte *p = cookie)
|
fixed (byte *p = cookie)
|
||||||
result = SSLSetDatagramHelloCookie (Handle, p, len);
|
result = SSLSetDatagramHelloCookie (Handle, p, len);
|
||||||
return result;
|
return result;
|
||||||
|
@ -411,10 +389,10 @@ namespace Security {
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainNameLength (/* SSLContextRef */ IntPtr context, /* size_t* */ out nint peerNameLen);
|
extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainNameLength (/* SSLContextRef */ IntPtr context, /* size_t* */ out nint peerNameLen);
|
||||||
|
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[] peerName, /* size_t */ ref nint peerNameLen);
|
extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[]? peerName, /* size_t */ ref nint peerNameLen);
|
||||||
|
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLSetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[] peerName, /* size_t */ nint peerNameLen);
|
extern unsafe static /* OSStatus */ SslStatus SSLSetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[]? peerName, /* size_t */ nint peerNameLen);
|
||||||
|
|
||||||
public string PeerDomainName {
|
public string PeerDomainName {
|
||||||
get {
|
get {
|
||||||
|
@ -427,7 +405,7 @@ namespace Security {
|
||||||
return result == SslStatus.Success ? Encoding.UTF8.GetString (bytes) : String.Empty;
|
return result == SslStatus.Success ? Encoding.UTF8.GetString (bytes) : String.Empty;
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
if (value == null) {
|
if (value is null) {
|
||||||
result = SSLSetPeerDomainName (Handle, null, 0);
|
result = SSLSetPeerDomainName (Handle, null, 0);
|
||||||
} else {
|
} else {
|
||||||
var bytes = Encoding.UTF8.GetBytes (value);
|
var bytes = Encoding.UTF8.GetBytes (value);
|
||||||
|
@ -443,7 +421,7 @@ namespace Security {
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLCopyDistinguishedNames (/* SSLContextRef */ IntPtr context, /* CFArrayRef* */ out IntPtr names);
|
extern unsafe static /* OSStatus */ SslStatus SSLCopyDistinguishedNames (/* SSLContextRef */ IntPtr context, /* CFArrayRef* */ out IntPtr names);
|
||||||
|
|
||||||
// TODO: need to setup a server that requires client-side certificates
|
// TODO: need to setup a server that requires client-side certificates
|
||||||
public IList<T> GetDistinguishedNames<T> ()
|
public IList<T>? GetDistinguishedNames<T> ()
|
||||||
{
|
{
|
||||||
IntPtr p;
|
IntPtr p;
|
||||||
result = SSLCopyDistinguishedNames (Handle, out p);
|
result = SSLCopyDistinguishedNames (Handle, out p);
|
||||||
|
@ -480,15 +458,17 @@ namespace Security {
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLSetCertificate (/* SSLContextRef */ IntPtr context, /* _Nullable CFArrayRef */ IntPtr certRefs);
|
extern unsafe static /* OSStatus */ SslStatus SSLSetCertificate (/* SSLContextRef */ IntPtr context, /* _Nullable CFArrayRef */ IntPtr certRefs);
|
||||||
|
|
||||||
NSArray Bundle (SecIdentity identity, IEnumerable<SecCertificate> certificates)
|
NSArray Bundle (SecIdentity? identity, IEnumerable<SecCertificate>? certificates)
|
||||||
{
|
{
|
||||||
int i = identity == null ? 0 : 1;
|
int i = identity is null ? 0 : 1;
|
||||||
int n = certificates == null ? 0 : certificates.Count ();
|
int n = certificates is null ? 0 : certificates.Count ();
|
||||||
var ptrs = new IntPtr [n + i];
|
var ptrs = new IntPtr [n + i];
|
||||||
if (i == 1)
|
if (i == 1)
|
||||||
ptrs [0] = identity.Handle;
|
ptrs [0] = identity!.Handle;
|
||||||
|
if (certificates is not null) {
|
||||||
foreach (var certificate in certificates)
|
foreach (var certificate in certificates)
|
||||||
ptrs [i++] = certificate.Handle;
|
ptrs [i++] = certificate.Handle;
|
||||||
|
}
|
||||||
return NSArray.FromIntPtrs (ptrs);
|
return NSArray.FromIntPtrs (ptrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -549,7 +529,7 @@ namespace Security {
|
||||||
[DllImport (Constants.SecurityLibrary)]
|
[DllImport (Constants.SecurityLibrary)]
|
||||||
extern unsafe static /* OSStatus */ SslStatus SSLCopyPeerTrust (/* SSLContextRef */ IntPtr context, /* SecTrustRef */ out IntPtr trust);
|
extern unsafe static /* OSStatus */ SslStatus SSLCopyPeerTrust (/* SSLContextRef */ IntPtr context, /* SecTrustRef */ out IntPtr trust);
|
||||||
|
|
||||||
public SecTrust PeerTrust {
|
public SecTrust? PeerTrust {
|
||||||
get {
|
get {
|
||||||
IntPtr value;
|
IntPtr value;
|
||||||
result = SSLCopyPeerTrust (Handle, out value);
|
result = SSLCopyPeerTrust (Handle, out value);
|
||||||
|
@ -605,7 +585,7 @@ namespace Security {
|
||||||
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
[EditorBrowsable (EditorBrowsableState.Advanced)]
|
||||||
public int SetSessionConfig (NSString config)
|
public int SetSessionConfig (NSString config)
|
||||||
{
|
{
|
||||||
if (config == null)
|
if (config is null)
|
||||||
throw new ArgumentNullException (nameof (config));
|
throw new ArgumentNullException (nameof (config));
|
||||||
|
|
||||||
return SSLSetSessionConfig (Handle, config.Handle);
|
return SSLSetSessionConfig (Handle, config.Handle);
|
||||||
|
@ -617,7 +597,7 @@ namespace Security {
|
||||||
#endif
|
#endif
|
||||||
public int SetSessionConfig (SslSessionConfig config)
|
public int SetSessionConfig (SslSessionConfig config)
|
||||||
{
|
{
|
||||||
return SetSessionConfig (config.GetConstant ());
|
return SetSessionConfig (config.GetConstant ()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NET
|
#if !NET
|
||||||
|
@ -722,7 +702,7 @@ namespace Security {
|
||||||
#endif
|
#endif
|
||||||
public int SetOcspResponse (NSData response)
|
public int SetOcspResponse (NSData response)
|
||||||
{
|
{
|
||||||
if (response == null)
|
if (response is null)
|
||||||
throw new ArgumentNullException (nameof (response));
|
throw new ArgumentNullException (nameof (response));
|
||||||
return SSLSetOCSPResponse (Handle, response.Handle);
|
return SSLSetOCSPResponse (Handle, response.Handle);
|
||||||
}
|
}
|
||||||
|
@ -768,15 +748,13 @@ namespace Security {
|
||||||
[SupportedOSPlatform ("tvos11.0")]
|
[SupportedOSPlatform ("tvos11.0")]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public string[] GetAlpnProtocols (out int error)
|
public string?[] GetAlpnProtocols (out int error)
|
||||||
{
|
{
|
||||||
IntPtr protocols = IntPtr.Zero; // must be null, CFArray allocated by SSLCopyALPNProtocols
|
IntPtr protocols = IntPtr.Zero; // must be null, CFArray allocated by SSLCopyALPNProtocols
|
||||||
error = SSLCopyALPNProtocols (Handle, ref protocols);
|
error = SSLCopyALPNProtocols (Handle, ref protocols);
|
||||||
if (protocols == IntPtr.Zero)
|
if (protocols == IntPtr.Zero)
|
||||||
return Array.Empty<string> ();
|
return Array.Empty<string> ();
|
||||||
var result = CFArray.StringArrayFromHandle (protocols);
|
return CFArray.StringArrayFromHandle (protocols, true)!;
|
||||||
CFObject.CFRelease (protocols);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !NET
|
#if !NET
|
||||||
|
@ -786,7 +764,7 @@ namespace Security {
|
||||||
[SupportedOSPlatform ("ios11.0")]
|
[SupportedOSPlatform ("ios11.0")]
|
||||||
[SupportedOSPlatform ("tvos11.0")]
|
[SupportedOSPlatform ("tvos11.0")]
|
||||||
#endif
|
#endif
|
||||||
public string[] GetAlpnProtocols ()
|
public string?[] GetAlpnProtocols ()
|
||||||
{
|
{
|
||||||
int error;
|
int error;
|
||||||
return GetAlpnProtocols (out error);
|
return GetAlpnProtocols (out error);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче