mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
Reduce core dependencies, move https binding to optional assembly. (#1157)
* implement transport binding factory * split https transport into seperate nuget package * new Opc.Ua.Binding.Https assembly * Https binding is loaded on demand, if available * reduce core dependencies to kestrel and others fixes #600
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The <b>Opc.Ua.Bindings</b> namespace contains classes that implement the WCF bindings for the
|
||||
/// The <b>Opc.Ua.Bindings</b> namespace contains classes that implement the bindings for the
|
||||
/// mappings described in Part 6 of the UA specification. It also includes an implementation for
|
||||
/// the UA TCP protocol.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
<PackageId>$(PackageId).Debug</PackageId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DefineConstants Condition=" '$(NoHttps)' == 'true' ">$(DefineConstants);NO_HTTPS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(SignAssembly)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);SIGNASSEMBLY</DefineConstants>
|
||||
</PropertyGroup>
|
||||
@@ -47,33 +43,20 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||
<Reference Include="System.IdentityModel" />
|
||||
<Reference Include="System.IdentityModel.Selectors" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$(DefineConstants.Contains('NO_HTTPS'))=='false'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="4.5.3" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
|
||||
<PackageReference Include="System.Data.Common" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceModel.Primitives" Version="4.7.0" />
|
||||
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -102,7 +102,12 @@ namespace Opc.Ua
|
||||
/// Communicates with UA TCP, UA Security and UA Binary.
|
||||
/// </summary>
|
||||
public const string UaTcpTransport = "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Communicates with UA TCP over secure Websockets, UA Security and UA Binary.
|
||||
/// </summary>
|
||||
public const string UaWssTransport = "http://opcfoundation.org/UA-Profile/Transport/uawss-uasc-uabinary";
|
||||
|
||||
/// <summary>
|
||||
/// Communicates with UA Binary over HTTPS.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,14 +10,12 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System.ServiceModel.Channels;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The binding for the UA native stack
|
||||
/// </summary>
|
||||
public abstract class BaseBinding : Binding
|
||||
public abstract class BaseBinding
|
||||
{
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an interface to the scheme of a transport binding.
|
||||
/// </summary>
|
||||
public interface ITransportBindingScheme
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol scheme supported by the binding.
|
||||
/// </summary>
|
||||
string UriScheme { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is an interface to the factory of a transport binding.
|
||||
/// </summary>
|
||||
public interface ITransportBindingFactory<T>
|
||||
: ITransportBindingScheme
|
||||
{
|
||||
/// <summary>
|
||||
/// The factory to create a new transport.
|
||||
/// </summary>
|
||||
/// <returns>The transport.</returns>
|
||||
T Create();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interface to manage transport bindings.
|
||||
/// </summary>
|
||||
public interface ITransportBindings<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a transport binding for a uri scheme.
|
||||
/// </summary>
|
||||
/// <param name="uriScheme">The uri scheme.</param>
|
||||
T GetBinding(string uriScheme);
|
||||
|
||||
/// <summary>
|
||||
/// Return if there is a transport listener for a uri scheme.
|
||||
/// </summary>
|
||||
/// <param name="uriScheme">The uri scheme.</param>
|
||||
bool HasBinding(string uriScheme);
|
||||
|
||||
/// <summary>
|
||||
/// Set the transport factory to the binding.
|
||||
/// Overrides other bindings with the same uri scheme.
|
||||
/// </summary>
|
||||
void SetBinding(T binding);
|
||||
|
||||
/// <summary>
|
||||
/// Add all bindings with interface <see cref="T"/> exported from a assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly with the bindings.</param>
|
||||
IEnumerable<Type> AddBindings(Assembly assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Add all bindings with interface <see cref="T"/> from a list.
|
||||
/// </summary>
|
||||
/// <param name="bindings">The array of binding types with interface <see cref="T"/>.</param>
|
||||
IEnumerable<Type> AddBindings(IEnumerable<Type> bindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the transport listener factory interface for a binding (server).
|
||||
/// </summary>
|
||||
public interface ITransportListenerFactory :
|
||||
ITransportBindingFactory<ITransportListener>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create the service host for a server using <see cref="ServerBase"/>
|
||||
/// </summary>
|
||||
/// <param name="serverBase">The server base.</param>
|
||||
/// <param name="hosts">The service hosts are added to this list.</param>
|
||||
/// <param name="configuration">The application configuration.</param>
|
||||
/// <param name="baseAddresses">The base addreses for the service host.</param>
|
||||
/// <param name="serverDescription">The server description.</param>
|
||||
/// <param name="securityPolicies">The list of supported security policies.</param>
|
||||
/// <param name="instanceCertificate">The server certificate.</param>
|
||||
/// <param name="instanceCertificateChain">The cert cahin of the server certificate.</param>
|
||||
List<EndpointDescription> CreateServiceHost(
|
||||
ServerBase serverBase,
|
||||
IDictionary<string, Task> hosts,
|
||||
ApplicationConfiguration configuration,
|
||||
IList<string> baseAddresses,
|
||||
ApplicationDescription serverDescription,
|
||||
List<ServerSecurityPolicy> securityPolicies,
|
||||
X509Certificate2 instanceCertificate,
|
||||
X509Certificate2Collection instanceCertificateChain
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the transport channel factory interface for a binding (client).
|
||||
/// </summary>
|
||||
public interface ITransportChannelFactory :
|
||||
ITransportBindingFactory<ITransportChannel>
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The transpaort bindings available for teh stack.
|
||||
/// </summary>
|
||||
public static class TransportBindings
|
||||
{
|
||||
static TransportBindings()
|
||||
{
|
||||
Channels = new TransportChannelBindings(new Type[] { typeof(TcpTransportChannelFactory) });
|
||||
Listeners = new TransportListenerBindings(new Type[] { typeof(TcpTransportListenerFactory) });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The bindings for transport channels (client).
|
||||
/// </summary>
|
||||
public static TransportChannelBindings Channels { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bindings for transport listeners (server).
|
||||
/// </summary>
|
||||
public static TransportListenerBindings Listeners { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bindings for the transport channels.
|
||||
/// </summary>
|
||||
public class TransportChannelBindings :
|
||||
TransportBindingsBase<ITransportChannelFactory>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the transport listener.
|
||||
/// </summary>
|
||||
/// <param name="defaultBindings">List of known default bindings.</param>
|
||||
public TransportChannelBindings(Type[] defaultBindings) : base(defaultBindings)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a transport channel for the specified uri scheme.
|
||||
/// </summary>
|
||||
/// <param name="uriScheme">The uri scheme of the transport.</param>
|
||||
public ITransportChannel GetChannel(string uriScheme)
|
||||
{
|
||||
var binding = GetBinding(uriScheme);
|
||||
return binding?.Create();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The bindings for the transport listeners.
|
||||
/// </summary>
|
||||
public class TransportListenerBindings :
|
||||
TransportBindingsBase<ITransportListenerFactory>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize the transport listener.
|
||||
/// </summary>
|
||||
/// <param name="defaultBindings">List of known default bindings.</param>
|
||||
public TransportListenerBindings(Type[] defaultBindings) : base(defaultBindings)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a transport listener for the specified uri scheme.
|
||||
/// </summary>
|
||||
/// <param name="uriScheme">The uri scheme of the transport.</param>
|
||||
public ITransportListener GetListener(string uriScheme)
|
||||
{
|
||||
var binding = GetBinding(uriScheme);
|
||||
return binding?.Create();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The bindings for the transport listeners.
|
||||
/// </summary>
|
||||
public class TransportBindingsBase<T> :
|
||||
ITransportBindings<T> where T : class, ITransportBindingScheme
|
||||
{
|
||||
/// <summary>
|
||||
/// Implement the default constructor.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The default constructor adds all interfaces T.
|
||||
/// </remarks>
|
||||
protected TransportBindingsBase()
|
||||
{
|
||||
Bindings = new Dictionary<string, T>();
|
||||
AddBindings(typeof(TransportBindingsBase<T>).Assembly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize object with default list of bindings.
|
||||
/// </summary>
|
||||
protected TransportBindingsBase(Type[] defaultBindings)
|
||||
{
|
||||
Bindings = new Dictionary<string, T>();
|
||||
AddBindings(defaultBindings);
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
/// <summary>
|
||||
/// Dictionary of bindings of type <see cref="T"/>.
|
||||
/// </summary>
|
||||
protected Dictionary<string, T> Bindings { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region ITransportBindings
|
||||
/// <inheritdoc/>
|
||||
public T GetBinding(string uriScheme)
|
||||
{
|
||||
T binding;
|
||||
if (!Bindings.TryGetValue(uriScheme, out binding))
|
||||
{
|
||||
TryAddDefaultTransportBindings(uriScheme);
|
||||
if (!Bindings.TryGetValue(uriScheme, out binding))
|
||||
{
|
||||
return default(T);
|
||||
}
|
||||
}
|
||||
return binding;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool HasBinding(string uriScheme)
|
||||
{
|
||||
T binding;
|
||||
if (Bindings.TryGetValue(uriScheme, out binding))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void SetBinding(T binding)
|
||||
{
|
||||
Bindings[binding.UriScheme] = binding;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Type> AddBindings(Assembly assembly)
|
||||
{
|
||||
var bindings = assembly.GetExportedTypes().Where(type => IsBindingType(type));
|
||||
return AddBindings(bindings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<Type> AddBindings(IEnumerable<Type> bindings)
|
||||
{
|
||||
var result = new List<Type>();
|
||||
foreach (Type bindingType in bindings)
|
||||
{
|
||||
var binding = Activator.CreateInstance(bindingType) as T;
|
||||
if (binding != null)
|
||||
{
|
||||
Bindings[binding.UriScheme] = binding;
|
||||
result.Add(bindingType);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Validate the type is a transport listener.
|
||||
/// </summary>
|
||||
protected static bool IsBindingType(System.Type bindingType)
|
||||
{
|
||||
if (bindingType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bindingTypeInfo = bindingType.GetTypeInfo();
|
||||
if (bindingTypeInfo.IsAbstract ||
|
||||
!typeof(T).GetTypeInfo().IsAssignableFrom(bindingTypeInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var listener = Activator.CreateInstance(bindingType) as T;
|
||||
if (listener == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to load a binding from well known assemblies at runtime.
|
||||
/// </summary>
|
||||
/// <param name="scheme">The uri scheme of the binding.</param>
|
||||
private bool TryAddDefaultTransportBindings(string scheme)
|
||||
{
|
||||
string assemblyName;
|
||||
if (Utils.DefaultBindings.TryGetValue(scheme, out assemblyName))
|
||||
{
|
||||
Assembly assembly = null;
|
||||
string fullName = Utils.DefaultOpcUaCoreAssemblyFullName.Replace(Utils.DefaultOpcUaCoreAssemblyName, assemblyName);
|
||||
try
|
||||
{
|
||||
assembly = Assembly.Load(fullName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Utils.Trace(Utils.TraceMasks.Error, "Failed to load the assembly {0} for transport binding {1}.",
|
||||
fullName, scheme
|
||||
);
|
||||
}
|
||||
|
||||
if (assembly != null)
|
||||
{
|
||||
var listeners = AddBindings(assembly);
|
||||
return listeners.Count() > 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Trace(Utils.TraceMasks.Error, "The transport binding {0} is unsupported.", scheme);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// The binding for the transport listeners.
|
||||
/// </summary>
|
||||
public static class TransportListenerBindings
|
||||
{
|
||||
static TransportListenerBindings()
|
||||
{
|
||||
TransportListeners = new Dictionary<string, Type>();
|
||||
var listeners = typeof(TransportListenerBindings).Assembly.GetExportedTypes().Where(type => IsTransportListenerType(type));
|
||||
foreach (var listenerType in listeners)
|
||||
{
|
||||
Add(listenerType);
|
||||
}
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
/// <summary>
|
||||
/// Dictionary of transported listeners.
|
||||
/// </summary>
|
||||
public static Dictionary<string, Type> TransportListeners { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get a transport listener for a uri scheme.
|
||||
/// </summary>
|
||||
/// <param name="uriScheme">The uri scheme.</param>
|
||||
public static ITransportListener GetTransportListener(string uriScheme)
|
||||
{
|
||||
Type listenerType;
|
||||
if (TransportListeners.TryGetValue(uriScheme, out listenerType))
|
||||
{
|
||||
return Activator.CreateInstance(listenerType) as ITransportListener;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a transport listener to the binding.
|
||||
/// </summary>
|
||||
public static void Add(Type listenerType)
|
||||
{
|
||||
if (IsTransportListenerType(listenerType))
|
||||
{
|
||||
var listener = Activator.CreateInstance(listenerType) as ITransportListener;
|
||||
TransportListeners.Add(listener.UriScheme, listenerType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate the type is a transport listener.
|
||||
/// </summary>
|
||||
private static bool IsTransportListenerType(System.Type systemType)
|
||||
{
|
||||
if (systemType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var systemTypeInfo = systemType.GetTypeInfo();
|
||||
if (systemTypeInfo.IsAbstract ||
|
||||
!typeof(ITransportListener).GetTypeInfo().IsAssignableFrom(systemTypeInfo))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var listener = Activator.CreateInstance(systemType) as ITransportListener;
|
||||
if (listener == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -32,9 +32,13 @@ namespace Opc.Ua
|
||||
{
|
||||
if (url == null) throw new ArgumentNullException(nameof(url));
|
||||
|
||||
var listener = TransportListenerBindings.GetTransportListener(url.Scheme);
|
||||
|
||||
if (listener == null) throw new ArgumentException(nameof(url), "No suitable listener found.");
|
||||
var listener = TransportBindings.Listeners.GetListener(url.Scheme);
|
||||
if (listener == null)
|
||||
{
|
||||
throw ServiceResultException.Create(
|
||||
StatusCodes.BadProtocolVersionUnsupported,
|
||||
"Unsupported transport profile for scheme {0}\r\n", url.Scheme);
|
||||
}
|
||||
|
||||
m_listener = listener;
|
||||
Url = url;
|
||||
|
||||
@@ -692,28 +692,14 @@ namespace Opc.Ua
|
||||
X509Certificate2Collection clientCertificateChain,
|
||||
ServiceMessageContext messageContext)
|
||||
{
|
||||
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
|
||||
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
|
||||
|
||||
// initialize the channel which will be created with the server.
|
||||
ITransportChannel channel = null;
|
||||
|
||||
if (useUaTcp)
|
||||
{
|
||||
channel = new TcpTransportChannel();
|
||||
}
|
||||
#if !NO_HTTPS
|
||||
else if (useHttps)
|
||||
{
|
||||
channel = new HttpsTransportChannel();
|
||||
}
|
||||
#endif
|
||||
|
||||
string uriScheme = new Uri(description.EndpointUrl).Scheme;
|
||||
ITransportChannel channel = TransportBindings.Channels.GetChannel(uriScheme);
|
||||
if (channel == null)
|
||||
{
|
||||
throw ServiceResultException.Create(
|
||||
StatusCodes.BadProtocolVersionUnsupported,
|
||||
"Unsupported transport profile\r\n");
|
||||
"Unsupported transport profile for scheme {0}\r\n", uriScheme);
|
||||
}
|
||||
|
||||
// create a UA channel.
|
||||
@@ -780,42 +766,37 @@ namespace Opc.Ua
|
||||
X509Certificate2Collection clientCertificateChain,
|
||||
ServiceMessageContext messageContext)
|
||||
{
|
||||
bool useUaTcp = description.EndpointUrl.StartsWith(Utils.UriSchemeOpcTcp);
|
||||
bool useHttps = description.EndpointUrl.StartsWith(Utils.UriSchemeHttps);
|
||||
string uriScheme = new Uri(description.EndpointUrl).Scheme;
|
||||
|
||||
switch (description.TransportProfileUri)
|
||||
{
|
||||
case Profiles.UaTcpTransport:
|
||||
{
|
||||
useUaTcp = true;
|
||||
uriScheme = Utils.UriSchemeOpcTcp;
|
||||
break;
|
||||
}
|
||||
|
||||
case Profiles.HttpsBinaryTransport:
|
||||
{
|
||||
useHttps = true;
|
||||
uriScheme = Utils.UriSchemeHttps;
|
||||
break;
|
||||
}
|
||||
|
||||
case Profiles.UaWssTransport:
|
||||
{
|
||||
uriScheme = Utils.UriSchemeOpcWss;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// initialize the channel which will be created with the server.
|
||||
ITransportChannel channel = null;
|
||||
if (useUaTcp)
|
||||
{
|
||||
channel = new TcpTransportChannel();
|
||||
}
|
||||
#if !NO_HTTPS
|
||||
else if (useHttps)
|
||||
{
|
||||
channel = new HttpsTransportChannel();
|
||||
}
|
||||
#endif
|
||||
|
||||
ITransportChannel channel = TransportBindings.Channels.GetChannel(uriScheme);
|
||||
if (channel == null)
|
||||
{
|
||||
throw ServiceResultException.Create(
|
||||
StatusCodes.BadProtocolVersionUnsupported,
|
||||
"Unsupported transport profile\r\n");
|
||||
"Unsupported transport profile for scheme {0}\r\n", uriScheme);
|
||||
}
|
||||
|
||||
// create a UA-TCP channel.
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Reciprocal Community License ("RCL") Version 1.00
|
||||
*
|
||||
* Unless explicitly acquired and licensed from Licensor under another
|
||||
* license, the contents of this file are subject to the Reciprocal
|
||||
* Community License ("RCL") Version 1.00, or subsequent versions
|
||||
* as allowed by the RCL, and You may not copy or use this file in either
|
||||
* source code or executable form, except in compliance with the terms and
|
||||
* conditions of the RCL.
|
||||
*
|
||||
* All software distributed under the RCL is provided strictly on an
|
||||
* "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
|
||||
* AND LICENSOR HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
|
||||
* LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
* PURPOSE, QUIET ENJOYMENT, OR NON-INFRINGEMENT. See the RCL for specific
|
||||
* language governing rights and limitations under the RCL.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/RCL/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
#if !NO_HTTPS
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Https;
|
||||
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the startup of the Https listener.
|
||||
/// </summary>
|
||||
public class Startup
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Https listener.
|
||||
/// </summary>
|
||||
public static UaHttpsChannelListener Listener { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configure the request pipeline for the listener.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
public void Configure(IApplicationBuilder appBuilder)
|
||||
{
|
||||
appBuilder.Run(async context => {
|
||||
if (context.Request.Method != "POST")
|
||||
{
|
||||
context.Response.ContentLength = 0;
|
||||
context.Response.ContentType = "text/plain";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.MethodNotAllowed;
|
||||
await context.Response.WriteAsync(string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Listener.SendAsync(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the connections for a UA HTTPS server.
|
||||
/// </summary>
|
||||
public partial class UaHttpsChannelListener : ITransportListener
|
||||
{
|
||||
#region Constructors
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UaHttpsChannelListener"/> class.
|
||||
/// </summary>
|
||||
public UaHttpsChannelListener()
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable Members
|
||||
/// <summary>
|
||||
/// Frees any unmanaged resources.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overrideable version of the Dispose.
|
||||
/// </summary>
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "m_simulator")]
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
ConnectionStatusChanged = null;
|
||||
ConnectionWaiting = null;
|
||||
Utils.SilentDispose(m_host);
|
||||
m_host = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ITransportListener Members
|
||||
/// <summary>
|
||||
/// The URI scheme handled by the listener.
|
||||
/// </summary>
|
||||
public string UriScheme => Utils.UriSchemeHttps;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the listener and starts accepting connection.
|
||||
/// </summary>
|
||||
/// <param name="baseAddress">The base address.</param>
|
||||
/// <param name="settings">The settings to use when creating the listener.</param>
|
||||
/// <param name="callback">The callback to use when requests arrive via the channel.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if any parameter is null.</exception>
|
||||
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
|
||||
public void Open(
|
||||
Uri baseAddress,
|
||||
TransportListenerSettings settings,
|
||||
ITransportListenerCallback callback)
|
||||
{
|
||||
// assign a unique guid to the listener.
|
||||
m_listenerId = Guid.NewGuid().ToString();
|
||||
|
||||
m_uri = baseAddress;
|
||||
m_descriptions = settings.Descriptions;
|
||||
var configuration = settings.Configuration;
|
||||
|
||||
// initialize the quotas.
|
||||
m_quotas = new ChannelQuotas();
|
||||
m_quotas.MaxBufferSize = configuration.MaxBufferSize;
|
||||
m_quotas.MaxMessageSize = configuration.MaxMessageSize;
|
||||
m_quotas.ChannelLifetime = configuration.ChannelLifetime;
|
||||
m_quotas.SecurityTokenLifetime = configuration.SecurityTokenLifetime;
|
||||
|
||||
m_quotas.MessageContext = new ServiceMessageContext();
|
||||
|
||||
m_quotas.MessageContext.MaxArrayLength = configuration.MaxArrayLength;
|
||||
m_quotas.MessageContext.MaxByteStringLength = configuration.MaxByteStringLength;
|
||||
m_quotas.MessageContext.MaxMessageSize = configuration.MaxMessageSize;
|
||||
m_quotas.MessageContext.MaxStringLength = configuration.MaxStringLength;
|
||||
m_quotas.MessageContext.NamespaceUris = settings.NamespaceUris;
|
||||
m_quotas.MessageContext.ServerUris = new StringTable();
|
||||
m_quotas.MessageContext.Factory = settings.Factory;
|
||||
|
||||
m_quotas.CertificateValidator = settings.CertificateValidator;
|
||||
|
||||
// save the callback to the server.
|
||||
m_callback = callback;
|
||||
|
||||
m_serverCert = settings.ServerCertificate;
|
||||
|
||||
// start the listener
|
||||
Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the listener and stops accepting connection.
|
||||
/// </summary>
|
||||
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
|
||||
public void Close()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a new connection is waiting for a client.
|
||||
/// </summary>
|
||||
public event ConnectionWaitingHandlerAsync ConnectionWaiting;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a monitored connection's status changed.
|
||||
/// </summary>
|
||||
public event EventHandler<ConnectionStatusEventArgs> ConnectionStatusChanged;
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Reverse connect for the https transport listener is not implemeted.
|
||||
/// </remarks>
|
||||
public void CreateReverseConnection(Uri url, int timeout)
|
||||
{
|
||||
// suppress warnings
|
||||
ConnectionWaiting = null;
|
||||
ConnectionWaiting?.Invoke(null, null);
|
||||
ConnectionStatusChanged = null;
|
||||
ConnectionStatusChanged?.Invoke(null, null);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// Gets the URL for the listener's endpoint.
|
||||
/// </summary>
|
||||
/// <value>The URL for the listener's endpoint.</value>
|
||||
public Uri EndpointUrl => m_uri;
|
||||
|
||||
/// <summary>
|
||||
/// Starts listening at the specified port.
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
Startup.Listener = this;
|
||||
m_hostBuilder = new WebHostBuilder();
|
||||
|
||||
HttpsConnectionAdapterOptions httpsOptions = new HttpsConnectionAdapterOptions();
|
||||
httpsOptions.CheckCertificateRevocation = false;
|
||||
httpsOptions.ClientCertificateMode = ClientCertificateMode.NoCertificate;
|
||||
httpsOptions.ServerCertificate = m_serverCert;
|
||||
httpsOptions.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
|
||||
m_hostBuilder.UseKestrel(options => {
|
||||
options.Listen(IPAddress.Any, m_uri.Port, listenOptions => {
|
||||
listenOptions.NoDelay = true;
|
||||
listenOptions.UseHttps(httpsOptions);
|
||||
});
|
||||
});
|
||||
|
||||
m_hostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
|
||||
m_hostBuilder.UseStartup<Startup>();
|
||||
m_host = m_hostBuilder.Start(Utils.ReplaceLocalhost(m_uri.ToString()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening.
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// Handles requests arriving from a channel.
|
||||
/// </summary>
|
||||
public async Task SendAsync(HttpContext context)
|
||||
{
|
||||
IAsyncResult result = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (m_callback == null)
|
||||
{
|
||||
context.Response.ContentLength = 0;
|
||||
context.Response.ContentType = "text/plain";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.NotImplemented;
|
||||
await context.Response.WriteAsync(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Request.ContentType != "application/octet-stream")
|
||||
{
|
||||
context.Response.ContentLength = 0;
|
||||
context.Response.ContentType = "text/plain";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
await context.Response.WriteAsync("HTTPSLISTENER - Unsupported content type.");
|
||||
return;
|
||||
}
|
||||
|
||||
int length = (int)context.Request.ContentLength;
|
||||
byte[] buffer = await ReadBodyAsync(context.Request);
|
||||
|
||||
if (buffer.Length != length)
|
||||
{
|
||||
context.Response.ContentLength = 0;
|
||||
context.Response.ContentType = "text/plain";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
|
||||
await context.Response.WriteAsync("HTTPSLISTENER - Couldn't decode buffer.");
|
||||
return;
|
||||
}
|
||||
|
||||
IServiceRequest input = (IServiceRequest)BinaryDecoder.DecodeMessage(buffer, null, m_quotas.MessageContext);
|
||||
|
||||
// extract the JWT token from the HTTP headers.
|
||||
if (input.RequestHeader == null)
|
||||
{
|
||||
input.RequestHeader = new RequestHeader();
|
||||
}
|
||||
|
||||
if (NodeId.IsNull(input.RequestHeader.AuthenticationToken) && input.TypeId != DataTypeIds.CreateSessionRequest)
|
||||
{
|
||||
if (context.Request.Headers.Keys.Contains("Authorization"))
|
||||
{
|
||||
foreach (string value in context.Request.Headers["Authorization"])
|
||||
{
|
||||
if (value.StartsWith("Bearer"))
|
||||
{
|
||||
input.RequestHeader.AuthenticationToken = new NodeId(value.Substring("Bearer ".Length).Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EndpointDescription endpoint = null;
|
||||
|
||||
foreach (var ep in m_descriptions)
|
||||
{
|
||||
if (ep.EndpointUrl.StartsWith(Utils.UriSchemeHttps))
|
||||
{
|
||||
endpoint = ep;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
result = m_callback.BeginProcessRequest(
|
||||
m_listenerId,
|
||||
endpoint,
|
||||
input as IServiceRequest,
|
||||
null,
|
||||
null);
|
||||
|
||||
IServiceResponse output = m_callback.EndProcessRequest(result);
|
||||
|
||||
byte[] response = BinaryEncoder.EncodeMessage(output, m_quotas.MessageContext);
|
||||
context.Response.ContentLength = response.Length;
|
||||
context.Response.ContentType = context.Request.ContentType;
|
||||
context.Response.StatusCode = (int)HttpStatusCode.OK;
|
||||
await context.Response.Body.WriteAsync(response, 0, response.Length);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utils.Trace(e, "HTTPSLISTENER - Unexpected error processing request.");
|
||||
context.Response.ContentLength = e.Message.Length;
|
||||
context.Response.ContentType = "text/plain";
|
||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||
await context.Response.WriteAsync(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a UpdateCertificate event occured.
|
||||
/// </summary>
|
||||
public void CertificateUpdate(
|
||||
ICertificateValidator validator,
|
||||
X509Certificate2 serverCertificate,
|
||||
X509Certificate2Collection serverCertificateChain)
|
||||
{
|
||||
Stop();
|
||||
|
||||
m_quotas.CertificateValidator = validator;
|
||||
m_serverCert = serverCertificate;
|
||||
foreach (var description in m_descriptions)
|
||||
{
|
||||
if (description.ServerCertificate != null)
|
||||
{
|
||||
description.ServerCertificate = serverCertificate.RawData;
|
||||
}
|
||||
}
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
private async Task<byte[]> ReadBodyAsync(HttpRequest req)
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
using (var reader = new StreamReader(req.Body))
|
||||
{
|
||||
await reader.BaseStream.CopyToAsync(memory);
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private string m_listenerId;
|
||||
private Uri m_uri;
|
||||
private EndpointDescriptionCollection m_descriptions;
|
||||
private ChannelQuotas m_quotas;
|
||||
private ITransportListenerCallback m_callback;
|
||||
private IWebHostBuilder m_hostBuilder;
|
||||
private IWebHost m_host;
|
||||
private X509Certificate2 m_serverCert;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,348 +0,0 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
#if !NO_HTTPS
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the HttpsTransportChannel and provides an ITransportChannel implementation.
|
||||
/// </summary>
|
||||
public class HttpsTransportChannel : ITransportChannel
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public TransportChannelFeatures SupportedFeatures
|
||||
{
|
||||
get { return TransportChannelFeatures.Open | TransportChannelFeatures.Reconnect | TransportChannelFeatures.BeginSendRequest; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public EndpointDescription EndpointDescription
|
||||
{
|
||||
get { return m_settings.Description; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public EndpointConfiguration EndpointConfiguration
|
||||
{
|
||||
get { return m_settings.Configuration; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ServiceMessageContext MessageContext
|
||||
{
|
||||
get { return m_quotas.MessageContext; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ChannelToken CurrentToken
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int OperationTimeout
|
||||
{
|
||||
get { return m_operationTimeout; }
|
||||
set { m_operationTimeout = value; }
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Initialize(
|
||||
Uri url,
|
||||
TransportChannelSettings settings)
|
||||
{
|
||||
SaveSettings(url, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a secure channel with a waiting reverse connection.
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection to use.</param>
|
||||
/// <param name="settings">The settings to use when creating the channel.</param>
|
||||
/// <exception cref="ServiceResultException">Thrown if any communication error occurs.</exception>
|
||||
public void Initialize(
|
||||
ITransportWaitingConnection connection,
|
||||
TransportChannelSettings settings)
|
||||
{
|
||||
SaveSettings(connection.EndpointUrl, settings);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Open()
|
||||
{
|
||||
try
|
||||
{
|
||||
// auto validate server cert, if supported
|
||||
// if unsupported, the TLS server cert must be trusted by a root CA
|
||||
var handler = new HttpClientHandler();
|
||||
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
|
||||
|
||||
// send client certificate for servers that require TLS client authentication
|
||||
if (m_settings.ClientCertificate != null)
|
||||
{
|
||||
handler.ClientCertificates.Add(m_settings.ClientCertificate);
|
||||
}
|
||||
|
||||
// OSX platform cannot auto validate certs and throws
|
||||
// on PostAsync, do not set validation handler
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
try
|
||||
{
|
||||
handler.ServerCertificateCustomValidationCallback =
|
||||
(httpRequestMessage, cert, chain, policyErrors) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
m_quotas.CertificateValidator?.Validate(cert);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.Trace("HTTPS: Failed to validate server cert: " + cert.Subject);
|
||||
Utils.Trace("HTTPS: Exception:" + ex.Message);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
catch (PlatformNotSupportedException)
|
||||
{
|
||||
// client may throw if not supported (e.g. UWP)
|
||||
handler.ServerCertificateCustomValidationCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
m_client = new HttpClient(handler);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.Trace("Exception creating HTTPS Client: " + ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Close()
|
||||
{
|
||||
if (m_client != null)
|
||||
{
|
||||
m_client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The async result class for the Https transport.
|
||||
/// </summary>
|
||||
private class AsyncResult : AsyncResultBase
|
||||
{
|
||||
public IServiceRequest Request;
|
||||
public HttpResponseMessage Response;
|
||||
|
||||
public AsyncResult(
|
||||
AsyncCallback callback,
|
||||
object callbackData,
|
||||
int timeout,
|
||||
IServiceRequest request,
|
||||
HttpResponseMessage response)
|
||||
:
|
||||
base(callback, callbackData, timeout)
|
||||
{
|
||||
Request = request;
|
||||
Response = response;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IAsyncResult BeginSendRequest(IServiceRequest request, AsyncCallback callback, object callbackData)
|
||||
{
|
||||
HttpResponseMessage response = null;
|
||||
|
||||
try
|
||||
{
|
||||
ByteArrayContent content = new ByteArrayContent(BinaryEncoder.EncodeMessage(request, m_quotas.MessageContext));
|
||||
content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
|
||||
|
||||
AsyncResult result = new AsyncResult(callback, callbackData, m_operationTimeout, request, null);
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
response = await m_client.PostAsync(m_url, content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.Trace("Exception sending HTTPS request: " + ex.Message);
|
||||
result.Exception = ex;
|
||||
response = null;
|
||||
}
|
||||
result.Response = response;
|
||||
result.OperationCompleted();
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.Trace("Exception sending HTTPS request: " + ex.Message);
|
||||
AsyncResult result = new AsyncResult(callback, callbackData, m_operationTimeout, request, response);
|
||||
result.Exception = ex;
|
||||
result.OperationCompleted();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IServiceResponse EndSendRequest(IAsyncResult result)
|
||||
{
|
||||
AsyncResult result2 = result as AsyncResult;
|
||||
if (result2 == null)
|
||||
{
|
||||
throw new ArgumentException("Invalid result object passed.", nameof(result));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
result2.WaitForComplete();
|
||||
if (result2.Response != null)
|
||||
{
|
||||
Stream responseContent = result2.Response.Content.ReadAsStreamAsync().Result;
|
||||
return BinaryDecoder.DecodeMessage(responseContent, null, m_quotas.MessageContext) as IServiceResponse;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Utils.Trace("Exception reading HTTPS response: " + ex.Message);
|
||||
result2.Exception = ex;
|
||||
}
|
||||
return result2 as IServiceResponse;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public void EndOpen(IAsyncResult result)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public void Reconnect()
|
||||
{
|
||||
Utils.Trace("HttpsTransportChannel RECONNECT: Reconnecting to {0}.", m_url);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
void ITransportChannel.Reconnect(ITransportWaitingConnection connection)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public IAsyncResult BeginReconnect(AsyncCallback callback, object callbackData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public void EndReconnect(IAsyncResult result)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public IAsyncResult BeginClose(AsyncCallback callback, object callbackData)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>Not implemented here.</remarks>
|
||||
public void EndClose(IAsyncResult result)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IServiceResponse SendRequest(IServiceRequest request)
|
||||
{
|
||||
IAsyncResult result = BeginSendRequest(request, null, null);
|
||||
return EndSendRequest(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save the settings for a connection.
|
||||
/// </summary>
|
||||
/// <param name="url">The server url.</param>
|
||||
/// <param name="settings">The settings for the transport channel.</param>
|
||||
private void SaveSettings(Uri url, TransportChannelSettings settings)
|
||||
{
|
||||
m_url = new Uri(url.ToString());
|
||||
|
||||
m_settings = settings;
|
||||
m_operationTimeout = settings.Configuration.OperationTimeout;
|
||||
|
||||
// initialize the quotas.
|
||||
m_quotas = new ChannelQuotas();
|
||||
|
||||
m_quotas.MaxBufferSize = m_settings.Configuration.MaxBufferSize;
|
||||
m_quotas.MaxMessageSize = m_settings.Configuration.MaxMessageSize;
|
||||
m_quotas.ChannelLifetime = m_settings.Configuration.ChannelLifetime;
|
||||
m_quotas.SecurityTokenLifetime = m_settings.Configuration.SecurityTokenLifetime;
|
||||
|
||||
m_quotas.MessageContext = new ServiceMessageContext();
|
||||
|
||||
m_quotas.MessageContext.MaxArrayLength = m_settings.Configuration.MaxArrayLength;
|
||||
m_quotas.MessageContext.MaxByteStringLength = m_settings.Configuration.MaxByteStringLength;
|
||||
m_quotas.MessageContext.MaxMessageSize = m_settings.Configuration.MaxMessageSize;
|
||||
m_quotas.MessageContext.MaxStringLength = m_settings.Configuration.MaxStringLength;
|
||||
m_quotas.MessageContext.NamespaceUris = m_settings.NamespaceUris;
|
||||
m_quotas.MessageContext.ServerUris = new StringTable();
|
||||
m_quotas.MessageContext.Factory = m_settings.Factory;
|
||||
|
||||
m_quotas.CertificateValidator = settings.CertificateValidator;
|
||||
}
|
||||
|
||||
private Uri m_url;
|
||||
private int m_operationTimeout;
|
||||
private TransportChannelSettings m_settings;
|
||||
private ChannelQuotas m_quotas;
|
||||
private HttpClient m_client;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -699,282 +699,60 @@ namespace Opc.Ua
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new service host for UA TCP.
|
||||
/// Create the transport listener for the service host endpoint.
|
||||
/// </summary>
|
||||
protected List<EndpointDescription> CreateUaTcpServiceHost(
|
||||
IDictionary<string, Task> hosts,
|
||||
ApplicationConfiguration configuration,
|
||||
IList<string> baseAddresses,
|
||||
ApplicationDescription serverDescription,
|
||||
List<ServerSecurityPolicy> securityPolicies)
|
||||
/// <param name="endpointUri">The endpoint Uri.</param>
|
||||
/// <param name="endpoints">The description of the endpoints.</param>
|
||||
/// <param name="endpointConfiguration">The configuration of the endpoints.</param>
|
||||
/// <param name="listener">The transport listener.</param>
|
||||
/// <param name="certificateValidator">The certificate validator for the transport.</param>
|
||||
public virtual void CreateServiceHostEndpoint(
|
||||
Uri endpointUri,
|
||||
EndpointDescriptionCollection endpoints,
|
||||
EndpointConfiguration endpointConfiguration,
|
||||
ITransportListener listener,
|
||||
ICertificateValidator certificateValidator
|
||||
)
|
||||
{
|
||||
// generate a unique host name.
|
||||
string hostName = String.Empty;
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
// create the stack listener.
|
||||
try
|
||||
{
|
||||
hostName = "/Tcp";
|
||||
}
|
||||
TransportListenerSettings settings = new TransportListenerSettings();
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
settings.Descriptions = endpoints;
|
||||
settings.Configuration = endpointConfiguration;
|
||||
settings.ServerCertificate = InstanceCertificate;
|
||||
settings.CertificateValidator = certificateValidator;
|
||||
settings.NamespaceUris = MessageContext.NamespaceUris;
|
||||
settings.Factory = MessageContext.Factory;
|
||||
|
||||
listener.Open(
|
||||
endpointUri,
|
||||
settings,
|
||||
GetEndpointInstance(this));
|
||||
|
||||
TransportListeners.Add(listener);
|
||||
|
||||
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
hostName += Utils.Format("/{0}", hosts.Count);
|
||||
string message = $"Could not load {endpointUri.Scheme} Stack Listener.";
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
message += (" " + e.InnerException.Message);
|
||||
}
|
||||
Utils.Trace(e, message);
|
||||
}
|
||||
|
||||
// build list of uris.
|
||||
List<Uri> uris = new List<Uri>();
|
||||
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
|
||||
|
||||
// create the endpoint configuration to use.
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
|
||||
string computerName = Utils.GetHostName();
|
||||
|
||||
for (int ii = 0; ii < baseAddresses.Count; ii++)
|
||||
{
|
||||
// UA TCP and HTTPS endpoints support multiple policies.
|
||||
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
|
||||
|
||||
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
uri.Host = computerName;
|
||||
}
|
||||
|
||||
uris.Add(uri.Uri);
|
||||
|
||||
foreach (ServerSecurityPolicy policy in securityPolicies)
|
||||
{
|
||||
// create the endpoint description.
|
||||
EndpointDescription description = new EndpointDescription();
|
||||
|
||||
description.EndpointUrl = uri.ToString();
|
||||
description.Server = serverDescription;
|
||||
|
||||
description.SecurityMode = policy.SecurityMode;
|
||||
description.SecurityPolicyUri = policy.SecurityPolicyUri;
|
||||
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(policy.SecurityMode, policy.SecurityPolicyUri);
|
||||
description.UserIdentityTokens = GetUserTokenPolicies(configuration, description);
|
||||
description.TransportProfileUri = Profiles.UaTcpTransport;
|
||||
|
||||
bool requireEncryption = RequireEncryption(description);
|
||||
|
||||
if (requireEncryption)
|
||||
{
|
||||
description.ServerCertificate = InstanceCertificate.RawData;
|
||||
|
||||
// check if complete chain should be sent.
|
||||
if (configuration.SecurityConfiguration.SendCertificateChain &&
|
||||
InstanceCertificateChain != null &&
|
||||
InstanceCertificateChain.Count > 0)
|
||||
{
|
||||
List<byte> serverCertificateChain = new List<byte>();
|
||||
|
||||
for (int i = 0; i < InstanceCertificateChain.Count; i++)
|
||||
{
|
||||
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
|
||||
}
|
||||
|
||||
description.ServerCertificate = serverCertificateChain.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
endpoints.Add(description);
|
||||
}
|
||||
|
||||
// create the UA-TCP stack listener.
|
||||
try
|
||||
{
|
||||
TransportListenerSettings settings = new TransportListenerSettings();
|
||||
|
||||
settings.Descriptions = endpoints;
|
||||
settings.Configuration = endpointConfiguration;
|
||||
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
|
||||
settings.NamespaceUris = this.MessageContext.NamespaceUris;
|
||||
settings.Factory = this.MessageContext.Factory;
|
||||
settings.ServerCertificate = this.InstanceCertificate;
|
||||
|
||||
if (configuration.SecurityConfiguration.SendCertificateChain)
|
||||
{
|
||||
settings.ServerCertificateChain = this.InstanceCertificateChain;
|
||||
}
|
||||
|
||||
ITransportListener listener = new Opc.Ua.Bindings.TcpListener();
|
||||
|
||||
listener.Open(
|
||||
uri.Uri,
|
||||
settings,
|
||||
GetEndpointInstance(this));
|
||||
|
||||
TransportListeners.Add(listener);
|
||||
|
||||
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Utils.Trace(e, "Could not load UA-TCP Stack Listener.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
#if !NO_HTTPS
|
||||
/// <summary>
|
||||
/// Create a new service host for UA HTTPS.
|
||||
/// </summary>
|
||||
protected List<EndpointDescription> CreateHttpsServiceHost(
|
||||
IDictionary<string, Task> hosts,
|
||||
ApplicationConfiguration configuration,
|
||||
IList<string> baseAddresses,
|
||||
ApplicationDescription serverDescription,
|
||||
List<ServerSecurityPolicy> securityPolicies)
|
||||
{
|
||||
// generate a unique host name.
|
||||
string hostName = String.Empty;
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
{
|
||||
hostName = "/Https";
|
||||
}
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
{
|
||||
hostName += Utils.Format("/{0}", hosts.Count);
|
||||
}
|
||||
|
||||
// build list of uris.
|
||||
List<Uri> uris = new List<Uri>();
|
||||
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
|
||||
|
||||
// create the endpoint configuration to use.
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
|
||||
string computerName = Utils.GetHostName();
|
||||
|
||||
for (int ii = 0; ii < baseAddresses.Count; ii++)
|
||||
{
|
||||
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeHttps, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
|
||||
|
||||
if (uri.Path[uri.Path.Length - 1] != '/')
|
||||
{
|
||||
uri.Path += "/";
|
||||
}
|
||||
|
||||
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
uri.Host = computerName;
|
||||
}
|
||||
|
||||
uris.Add(uri.Uri);
|
||||
|
||||
if (uri.Scheme == Utils.UriSchemeHttps)
|
||||
{
|
||||
// Can only support one policy with HTTPS
|
||||
// So pick the first policy with security mode sign and encrypt
|
||||
ServerSecurityPolicy bestPolicy = null;
|
||||
foreach (ServerSecurityPolicy policy in securityPolicies)
|
||||
{
|
||||
if (policy.SecurityMode != MessageSecurityMode.SignAndEncrypt)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bestPolicy = policy;
|
||||
break;
|
||||
}
|
||||
|
||||
// Pick the first policy from the list if no policies with sign and encrypt defined
|
||||
if (bestPolicy == null)
|
||||
{
|
||||
bestPolicy = securityPolicies[0];
|
||||
}
|
||||
|
||||
EndpointDescription description = new EndpointDescription();
|
||||
|
||||
description.EndpointUrl = uri.ToString();
|
||||
description.Server = serverDescription;
|
||||
|
||||
if (InstanceCertificate != null)
|
||||
{
|
||||
description.ServerCertificate = InstanceCertificate.RawData;
|
||||
|
||||
// check if complete chain should be sent.
|
||||
if (configuration.SecurityConfiguration.SendCertificateChain && InstanceCertificateChain != null && InstanceCertificateChain.Count > 0)
|
||||
{
|
||||
List<byte> serverCertificateChain = new List<byte>();
|
||||
|
||||
for (int i = 0; i < InstanceCertificateChain.Count; i++)
|
||||
{
|
||||
serverCertificateChain.AddRange(InstanceCertificateChain[i].RawData);
|
||||
}
|
||||
|
||||
description.ServerCertificate = serverCertificateChain.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
description.SecurityMode = bestPolicy.SecurityMode;
|
||||
description.SecurityPolicyUri = bestPolicy.SecurityPolicyUri;
|
||||
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(bestPolicy.SecurityMode, bestPolicy.SecurityPolicyUri);
|
||||
description.UserIdentityTokens = GetUserTokenPolicies(configuration, description);
|
||||
description.TransportProfileUri = Profiles.HttpsBinaryTransport;
|
||||
|
||||
endpoints.Add(description);
|
||||
}
|
||||
|
||||
// create the stack listener.
|
||||
try
|
||||
{
|
||||
TransportListenerSettings settings = new TransportListenerSettings();
|
||||
|
||||
settings.Descriptions = endpoints;
|
||||
settings.Configuration = endpointConfiguration;
|
||||
settings.ServerCertificate = this.InstanceCertificate;
|
||||
settings.CertificateValidator = configuration.CertificateValidator.GetChannelValidator();
|
||||
settings.NamespaceUris = this.MessageContext.NamespaceUris;
|
||||
settings.Factory = this.MessageContext.Factory;
|
||||
|
||||
ITransportListener listener = new Opc.Ua.Bindings.UaHttpsChannelListener();
|
||||
|
||||
listener.Open(
|
||||
uri.Uri,
|
||||
settings,
|
||||
GetEndpointInstance(this));
|
||||
|
||||
TransportListeners.Add(listener);
|
||||
|
||||
listener.ConnectionStatusChanged += OnConnectionStatusChanged;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string message = "Could not load HTTPS Stack Listener.";
|
||||
if (e.InnerException != null)
|
||||
{
|
||||
message += (" " + e.InnerException.Message);
|
||||
}
|
||||
Utils.Trace(e, message);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns the UserTokenPolicies supported by the server.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration.</param>
|
||||
/// <param name="description">The description.</param>
|
||||
/// <returns>Returns a collection of UserTokenPolicy objects, the return type is <seealso cref="UserTokenPolicyCollection"/> . </returns>
|
||||
protected virtual UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
|
||||
public virtual UserTokenPolicyCollection GetUserTokenPolicies(ApplicationConfiguration configuration, EndpointDescription description)
|
||||
{
|
||||
UserTokenPolicyCollection policies = new UserTokenPolicyCollection();
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System.IdentityModel.Selectors;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -39,6 +39,11 @@ namespace Opc.Ua.Bindings
|
||||
/// </summary>
|
||||
public class TcpTransportChannelFactory : ITransportChannelFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol supported by the channel.
|
||||
/// </summary>
|
||||
public string UriScheme => Utils.UriSchemeOpcTcp;
|
||||
|
||||
/// <summary>
|
||||
/// The method creates a new instance of a TCP transport channel
|
||||
/// </summary>
|
||||
@@ -311,7 +316,6 @@ namespace Opc.Ua.Bindings
|
||||
/// <value>The socket handle.</value>
|
||||
public int Handle => m_socket != null ? m_socket.GetHashCode() : -1;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transport channel features implemented by this message socket.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
/* Copyright (c) 1996-2020 The OPC Foundation. All rights reserved.
|
||||
The source code in this file is covered under a dual-license scenario:
|
||||
- RCL: for OPC Foundation members in good-standing
|
||||
- GPL V2: everybody else
|
||||
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
||||
GNU General Public License as published by the Free Software Foundation;
|
||||
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
||||
This source code is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new Tcp service host.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class can be used by a transport which
|
||||
/// implements the <see cref="ITransportListenerFactory"/>
|
||||
/// </remarks>
|
||||
public abstract class TcpServiceHost : ITransportListenerFactory
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public abstract string UriScheme { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract ITransportListener Create();
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <summary>
|
||||
/// Create a new service host for UA TCP.
|
||||
/// </summary>
|
||||
public List<EndpointDescription> CreateServiceHost(
|
||||
ServerBase serverBase,
|
||||
IDictionary<string, Task> hosts,
|
||||
ApplicationConfiguration configuration,
|
||||
IList<string> baseAddresses,
|
||||
ApplicationDescription serverDescription,
|
||||
List<ServerSecurityPolicy> securityPolicies,
|
||||
X509Certificate2 instanceCertificate,
|
||||
X509Certificate2Collection instanceCertificateChain)
|
||||
{
|
||||
// generate a unique host name.
|
||||
string hostName = String.Empty;
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
{
|
||||
hostName = "/Tcp";
|
||||
}
|
||||
|
||||
if (hosts.ContainsKey(hostName))
|
||||
{
|
||||
hostName += Utils.Format("/{0}", hosts.Count);
|
||||
}
|
||||
|
||||
// build list of uris.
|
||||
List<Uri> uris = new List<Uri>();
|
||||
EndpointDescriptionCollection endpoints = new EndpointDescriptionCollection();
|
||||
|
||||
// create the endpoint configuration to use.
|
||||
EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);
|
||||
string computerName = Utils.GetHostName();
|
||||
|
||||
for (int ii = 0; ii < baseAddresses.Count; ii++)
|
||||
{
|
||||
// UA TCP and HTTPS endpoints support multiple policies.
|
||||
if (!baseAddresses[ii].StartsWith(Utils.UriSchemeOpcTcp, StringComparison.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UriBuilder uri = new UriBuilder(baseAddresses[ii]);
|
||||
|
||||
if (String.Compare(uri.Host, "localhost", StringComparison.OrdinalIgnoreCase) == 0)
|
||||
{
|
||||
uri.Host = computerName;
|
||||
}
|
||||
|
||||
ITransportListener listener = this.Create();
|
||||
if (listener != null)
|
||||
{
|
||||
uris.Add(uri.Uri);
|
||||
|
||||
foreach (ServerSecurityPolicy policy in securityPolicies)
|
||||
{
|
||||
// create the endpoint description.
|
||||
EndpointDescription description = new EndpointDescription();
|
||||
|
||||
description.EndpointUrl = uri.ToString();
|
||||
description.Server = serverDescription;
|
||||
|
||||
description.SecurityMode = policy.SecurityMode;
|
||||
description.SecurityPolicyUri = policy.SecurityPolicyUri;
|
||||
description.SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(policy.SecurityMode, policy.SecurityPolicyUri);
|
||||
description.UserIdentityTokens = serverBase.GetUserTokenPolicies(configuration, description);
|
||||
description.TransportProfileUri = Profiles.UaTcpTransport;
|
||||
|
||||
bool requireEncryption = ServerBase.RequireEncryption(description);
|
||||
|
||||
if (requireEncryption)
|
||||
{
|
||||
description.ServerCertificate = instanceCertificate.RawData;
|
||||
|
||||
// check if complete chain should be sent.
|
||||
if (configuration.SecurityConfiguration.SendCertificateChain &&
|
||||
instanceCertificateChain != null &&
|
||||
instanceCertificateChain.Count > 0)
|
||||
{
|
||||
List<byte> serverCertificateChain = new List<byte>();
|
||||
|
||||
for (int i = 0; i < instanceCertificateChain.Count; i++)
|
||||
{
|
||||
serverCertificateChain.AddRange(instanceCertificateChain[i].RawData);
|
||||
}
|
||||
|
||||
description.ServerCertificate = serverCertificateChain.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
endpoints.Add(description);
|
||||
}
|
||||
|
||||
serverBase.CreateServiceHostEndpoint(uri.Uri, endpoints, endpointConfiguration, listener,
|
||||
configuration.CertificateValidator.GetChannelValidator()
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.Trace(Utils.TraceMasks.Error, "Failed to create endpoint {0} because the transport profile is unsupported.", uri);
|
||||
}
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-2
@@ -20,9 +20,24 @@ using System.Threading.Tasks;
|
||||
namespace Opc.Ua.Bindings
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the connections for a UA TCP server.
|
||||
/// Creates a new TcpTransportListener with ITransportListener interface.
|
||||
/// </summary>
|
||||
public class TcpListener : ITransportListener, ITcpChannelListener
|
||||
public class TcpTransportListenerFactory : TcpServiceHost
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public override string UriScheme => Utils.UriSchemeOpcTcp;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ITransportListener Create()
|
||||
{
|
||||
return new TcpTransportListener();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manages the transport for a UA TCP server.
|
||||
/// </summary>
|
||||
public class TcpTransportListener : ITransportListener, ITcpChannelListener
|
||||
{
|
||||
#region IDisposable Members
|
||||
/// <summary>
|
||||
@@ -15,18 +15,6 @@ using Opc.Ua.Bindings;
|
||||
|
||||
namespace Opc.Ua
|
||||
{
|
||||
/// <summary>
|
||||
/// This is an interface to a channel which supports a factory
|
||||
/// </summary>
|
||||
public interface ITransportChannelFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The method creates a new transport channel
|
||||
/// </summary>
|
||||
/// <returns> the transport channel</returns>
|
||||
ITransportChannel Create();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is an interface to a channel which supports
|
||||
/// </summary>
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using System.IdentityModel.Selectors;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
|
||||
namespace Opc.Ua
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Opc.Ua
|
||||
public EncodeableFactory(bool shared)
|
||||
{
|
||||
m_encodeableTypes = new Dictionary<ExpandedNodeId, System.Type>();
|
||||
AddEncodeableTypes("Opc.Ua.Core");
|
||||
AddEncodeableTypes(Utils.DefaultOpcUaCoreAssemblyFullName);
|
||||
|
||||
#if DEBUG
|
||||
m_instanceId = Interlocked.Increment(ref m_globalInstanceCount);
|
||||
|
||||
@@ -25,7 +25,7 @@ using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Net;
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Opc.Ua
|
||||
{
|
||||
@@ -50,11 +50,30 @@ namespace Opc.Ua
|
||||
/// </summary>
|
||||
public const string UriSchemeOpcTcp = "opc.tcp";
|
||||
|
||||
/// <summary>
|
||||
/// The URI scheme for the UA TCP protocol over Secure WebSockets.
|
||||
/// </summary>
|
||||
public const string UriSchemeOpcWss = "opc.wss";
|
||||
|
||||
/// <summary>
|
||||
/// The URI schemes which are supported in the core server.
|
||||
/// </summary>
|
||||
public static readonly string[] DefaultUriSchemes = new string []
|
||||
{
|
||||
Utils.UriSchemeOpcTcp,
|
||||
Utils.UriSchemeHttps
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The default port for the UA TCP protocol.
|
||||
/// </summary>
|
||||
public const int UaTcpDefaultPort = 4840;
|
||||
|
||||
/// <summary>
|
||||
/// The default port for the UA TCP protocol over WebSockets.
|
||||
/// </summary>
|
||||
public const int UaWebSocketsDefaultPort = 4843;
|
||||
|
||||
/// <summary>
|
||||
/// The urls of the discovery servers on a node.
|
||||
/// </summary>
|
||||
@@ -67,11 +86,6 @@ namespace Opc.Ua
|
||||
"http://{0}/UADiscovery/Default.svc"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The class that provides the default implementation for the UA TCP protocol.
|
||||
/// </summary>
|
||||
public const string UaTcpBindingDefault = "Opc.Ua.Bindings.UaTcpBinding";
|
||||
|
||||
/// <summary>
|
||||
/// The default certificate store's type.
|
||||
/// </summary>
|
||||
@@ -87,6 +101,23 @@ namespace Opc.Ua
|
||||
/// </summary>
|
||||
public static string DefaultLocalFolder = Directory.GetCurrentDirectory();
|
||||
|
||||
/// <summary>
|
||||
/// The full name of the Opc.Ua.Core assembly.
|
||||
/// </summary>
|
||||
public static readonly string DefaultOpcUaCoreAssemblyFullName = typeof(Utils).Assembly.GetName().FullName;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the Opc.Ua.Core assembly.
|
||||
/// </summary>
|
||||
public static readonly string DefaultOpcUaCoreAssemblyName = typeof(Utils).Assembly.GetName().Name;
|
||||
|
||||
/// <summary>
|
||||
/// List of known default bindings hosted in other assemblies.
|
||||
/// </summary>
|
||||
public static ReadOnlyDictionary<string, string> DefaultBindings = new ReadOnlyDictionary<string, string>(
|
||||
new Dictionary<string, string>() {
|
||||
{ Utils.UriSchemeHttps, "Opc.Ua.Bindings.Https"}
|
||||
});
|
||||
#endregion
|
||||
|
||||
#region Trace Support
|
||||
@@ -854,7 +885,6 @@ namespace Opc.Ua
|
||||
|
||||
#region String, Object and Data Convienence Functions
|
||||
private const int MAX_MESSAGE_LENGTH = 1024;
|
||||
|
||||
private const uint FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
|
||||
private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user