[Network] Improve bindings for NWProtocolMetadata. (#6389)

* [Network] Improve bindings for NWProtocolMetadata.

It turns out the NWProtocolMetadata can contain metadata for different
protocols (Ip/Tls/Tcp). This is important; if someone tries to get a value for
one protocol and the metadata is for another protocol, then they invoke the
wrath of superior beings who will smite that poor someone with uninitialized
memory.

At that point there's not much left but to pray.

I don't like to depend on divine intervention, so I've modified the API here
to check if the metadata's protocol is the required type for the native API
we're calling, and if the check fails, we throw a nice and dependable managed
exception.

This is a functional breaking change; but if there are any lost souls who
likes to pray, they can always re-implement the P/Invokes themselves and skip
the sanity checks.

In addition I've renamed a few properties whose name didn't clearly specify
which protocol type they operate on.

Ref: Apple feedback FB6155967.
Ref: https://trello.com/c/1TW0BSKJ/145-fb6155967-nwipcreatemetadata-returns-uninitialized-metadata-in-ios-13

* Fix casing in exception message.

* [tests] Adjust the SecProtocolMetadataTest according to new knowledge about the Network API.

* Fix alternative property name in obsolete attribute.
This commit is contained in:
Rolf Bjarne Kvinge 2019-06-21 15:00:19 +02:00 коммит произвёл GitHub
Родитель 57d44d120a
Коммит 85ec17a506
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 123 добавлений и 46 удалений

Просмотреть файл

@ -87,7 +87,35 @@ namespace Network {
[DllImport (Constants.NetworkLibrary)]
static extern IntPtr nw_tls_copy_sec_protocol_metadata (IntPtr handle);
public SecProtocolMetadata SecProtocolMetadata => new SecProtocolMetadata (nw_tls_copy_sec_protocol_metadata (GetCheckedHandle ()), owns: true);
void CheckIsIP ()
{
if (!IsIP)
throw new InvalidOperationException ("This metadata is not IP metadata.");
}
void CheckIsTcp ()
{
if (!IsTcp)
throw new InvalidOperationException ("This metadata is not TCP metadata.");
}
void CheckIsTls ()
{
if (!IsTls)
throw new InvalidOperationException ("This metadata is not TLS metadata.");
}
#if !XAMCORE_4_0
[Obsolete ("Use 'TlsSecProtocolMetadata' instead.")]
public SecProtocolMetadata SecProtocolMetadata => TlsSecProtocolMetadata;
#endif
public SecProtocolMetadata TlsSecProtocolMetadata {
get {
CheckIsTls ();
return new SecProtocolMetadata (nw_tls_copy_sec_protocol_metadata (GetCheckedHandle ()), owns: true);
}
}
[DllImport (Constants.NetworkLibrary)]
static extern void nw_ip_metadata_set_ecn_flag (OS_nw_protocol_metadata metadata, NWIPEcnFlag ecn_flag);
@ -96,15 +124,24 @@ namespace Network {
static extern NWIPEcnFlag nw_ip_metadata_get_ecn_flag (OS_nw_protocol_metadata metadata);
public NWIPEcnFlag IPMetadataEcnFlag {
get => nw_ip_metadata_get_ecn_flag (GetCheckedHandle ());
set => nw_ip_metadata_set_ecn_flag (GetCheckedHandle (), value);
get {
CheckIsIP ();
return nw_ip_metadata_get_ecn_flag (GetCheckedHandle ());
}
set {
CheckIsIP ();
nw_ip_metadata_set_ecn_flag (GetCheckedHandle (), value);
}
}
[DllImport (Constants.NetworkLibrary)]
static extern /* uint64_t */ ulong nw_ip_metadata_get_receive_time (OS_nw_protocol_metadata metadata);
public ulong IPMetadataReceiveTime {
get => nw_ip_metadata_get_receive_time (GetCheckedHandle ());
get {
CheckIsIP ();
return nw_ip_metadata_get_receive_time (GetCheckedHandle ());
}
}
[DllImport (Constants.NetworkLibrary)]
@ -113,19 +150,41 @@ namespace Network {
[DllImport (Constants.NetworkLibrary)]
static extern NWServiceClass nw_ip_metadata_get_service_class (OS_nw_protocol_metadata metadata);
#if !XAMCORE_4_0
[Obsolete ("Use 'IPServiceClass' instead.")]
public NWServiceClass ServiceClass {
get => nw_ip_metadata_get_service_class (GetCheckedHandle ());
set => nw_ip_metadata_set_service_class (GetCheckedHandle (), value);
get => IPServiceClass;
set => IPServiceClass = value;
}
#endif
public NWServiceClass IPServiceClass {
get {
CheckIsIP ();
return nw_ip_metadata_get_service_class (GetCheckedHandle ());
}
set {
CheckIsIP ();
nw_ip_metadata_set_service_class (GetCheckedHandle (), value);
}
}
[DllImport (Constants.NetworkLibrary)]
extern static /* uint32_t */ uint nw_tcp_get_available_receive_buffer (IntPtr handle);
public uint TcpGetAvailableReceiveBuffer () => nw_tcp_get_available_receive_buffer (GetCheckedHandle ());
public uint TcpGetAvailableReceiveBuffer ()
{
CheckIsTcp ();
return nw_tcp_get_available_receive_buffer (GetCheckedHandle ());
}
[DllImport (Constants.NetworkLibrary)]
extern static /* uint32_t */ uint nw_tcp_get_available_send_buffer (IntPtr handle);
public uint TcpGetAvailableSendBuffer () => nw_tcp_get_available_send_buffer (GetCheckedHandle ());
public uint TcpGetAvailableSendBuffer ()
{
CheckIsTcp ();
return nw_tcp_get_available_send_buffer (GetCheckedHandle ());
}
}
}

Просмотреть файл

@ -28,8 +28,10 @@ namespace MonoTouchFixtures.Network {
Assert.False (m.IsTcp, "IsTcp");
Assert.False (m.IsUdp, "IsUdp");
Assert.NotNull (m.ProtocolDefinition, "ProtocolDefinition");
Assert.NotNull (m.SecProtocolMetadata, "SecProtocolMetadata");
Assert.Throws<InvalidOperationException> (() => { var x = m.SecProtocolMetadata; }, "SecProtocolMetadata");
Assert.Throws<InvalidOperationException> (() => { var x = m.TlsSecProtocolMetadata; }, "TlsSecProtocolMetadata");
Assert.That (m.ServiceClass, Is.EqualTo (NWServiceClass.BestEffort), "ServiceClass");
Assert.That (m.IPServiceClass, Is.EqualTo (NWServiceClass.BestEffort), "IPServiceClass");
}
}
@ -37,16 +39,18 @@ namespace MonoTouchFixtures.Network {
public void Udp ()
{
using (var m = NWProtocolMetadata.CreateUdpMetadata ()) {
Assert.That (m.IPMetadataEcnFlag, Is.EqualTo (NWIPEcnFlag.NonEct), "IPMetadataEcnFlag");
Assert.That (m.IPMetadataReceiveTime, Is.EqualTo (0), "IPMetadataReceiveTime");
Assert.Throws<InvalidOperationException> (() => { var x = m.IPMetadataEcnFlag; }, "IPMetadataEcnFlag");
Assert.Throws<InvalidOperationException> (() => { var x = m.IPMetadataReceiveTime; }, "IPMetadataReceiveTime");
Assert.False (m.IsIP, "IsIP");
Assert.False (m.IsTcp, "IsTcp");
Assert.True (m.IsUdp, "IsUdp");
Assert.NotNull (m.ProtocolDefinition, "ProtocolDefinition");
Assert.NotNull (m.SecProtocolMetadata, "SecProtocolMetadata");
Assert.That (m.ServiceClass, Is.EqualTo (NWServiceClass.BestEffort), "ServiceClass");
Assert.Throws<InvalidOperationException> (() => { var x = m.SecProtocolMetadata; }, "SecProtocolMetadata");
Assert.Throws<InvalidOperationException> (() => { var x = m.TlsSecProtocolMetadata; }, "TlsSecProtocolMetadata");
Assert.Throws<InvalidOperationException> (() => { var x = m.ServiceClass; }, "ServiceClass");
Assert.Throws<InvalidOperationException> (() => { var x = m.IPServiceClass; }, "IPServiceClass");
}
}
}
}
#endif
#endif

Просмотреть файл

@ -1,6 +1,9 @@
#if !__WATCHOS__
using System;
using System.Runtime.InteropServices;
using System.Threading;
using CoreFoundation;
using Foundation;
using Network;
using ObjCRuntime;
@ -21,44 +24,55 @@ namespace MonoTouchFixtures.Security {
}
[Test]
public void IPDefaults ()
public void TlsDefaults ()
{
if (TestRuntime.CheckXcodeVersion (11, 0))
Assert.Ignore ("NWProtocolMetadata.CreateIPMetadata () returns a metadata object with uninitialized metadata, which means the asserts here fail randomly.");
using (var ep = NWEndpoint.Create ("www.microsoft.com", "https"))
using (var parameters = NWParameters.CreateSecureTcp ())
using (var queue = new DispatchQueue (GetType ().FullName)) {
var connection = new NWConnection (ep, parameters);
using (var m = NWProtocolMetadata.CreateIPMetadata ()) {
var s = m.SecProtocolMetadata;
// This is mostly, but not always, returning false
// Assert.False (s.EarlyDataAccepted, "EarlyDataAccepted");
Assert.That (s.NegotiatedCipherSuite, Is.EqualTo (SslCipherSuite.SSL_NULL_WITH_NULL_NULL), "NegotiatedCipherSuite");
Assert.Null (s.NegotiatedProtocol, "NegotiatedProtocol");
Assert.That (s.NegotiatedProtocolVersion, Is.EqualTo (SslProtocol.Unknown), "NegotiatedProtocolVersion");
Assert.Null (s.PeerPublicKey, "PeerPublicKey");
#if false
Assert.True (SecProtocolMetadata.ChallengeParametersAreEqual (s, s), "ChallengeParametersAreEqual");
Assert.True (SecProtocolMetadata.PeersAreEqual (s, s), "PeersAreEqual");
#endif
}
}
var ready = new ManualResetEvent (false);
connection.SetStateChangeHandler ((state, error) => {
Console.WriteLine (state);
switch (state) {
case NWConnectionState.Cancelled:
case NWConnectionState.Failed:
// We can't dispose until the connection has been closed or it failed.
connection.Dispose ();
break;
case NWConnectionState.Invalid:
case NWConnectionState.Preparing:
case NWConnectionState.Waiting:
break;
case NWConnectionState.Ready:
ready.Set ();
break;
default:
break;
}
});
#if false
[DllImport (Constants.CoreFoundationLibrary)]
extern static nint CFGetRetainCount (IntPtr handle);
connection.SetQueue (queue);
connection.Start ();
[Test]
public void CreateSecret ()
{
using (var npm = NWProtocolMetadata.CreateIPMetadata ()) {
// `npm` and `spm` have the same handle - same internal object satistfy both protocols
Console.WriteLine ($"{CFGetRetainCount (npm.Handle)}");
using (var spm = npm.SecProtocolMetadata) {
Console.WriteLine ($"{CFGetRetainCount (npm.Handle)}");
Console.WriteLine ($"{CFGetRetainCount (spm.Handle)}");
var secret = spm.CreateSecret ("test", 16); // crash
// Wait until the connection is ready.
Assert.True (ready.WaitOne (TimeSpan.FromSeconds (10)), "Connection is ready");
using (var m = connection.GetProtocolMetadata (NWProtocolDefinition.TlsDefinition)) {
var s = m.TlsSecProtocolMetadata;
Assert.False (s.EarlyDataAccepted, "EarlyDataAccepted");
Assert.That (s.NegotiatedCipherSuite, Is.Not.EqualTo (SslCipherSuite.SSL_NULL_WITH_NULL_NULL), "NegotiatedCipherSuite");
Assert.Null (s.NegotiatedProtocol, "NegotiatedProtocol");
Assert.That (s.NegotiatedProtocolVersion, Is.EqualTo (SslProtocol.Tls_1_2).Or.EqualTo (SslProtocol.Tls_1_3), "NegotiatedProtocolVersion");
Assert.NotNull (s.PeerPublicKey, "PeerPublicKey");
Assert.True (SecProtocolMetadata.ChallengeParametersAreEqual (s, s), "ChallengeParametersAreEqual");
Assert.True (SecProtocolMetadata.PeersAreEqual (s, s), "PeersAreEqual");
}
connection.Cancel ();
}
}
#endif
}
}
#endif
#endif