mqtt/M2Mqtt/Net/MqttNetworkChannel.cs

427 lines
16 KiB
C#
Raw Normal View History

2018-06-07 22:49:54 +02:00
/*
Copyright (c) 2013, 2014 Paolo Patierno
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
and Eclipse Distribution License v1.0 which accompany this distribution.
The Eclipse Public License is available at
http://www.eclipse.org/legal/epl-v10.html
and the Eclipse Distribution License is available at
http://www.eclipse.org/org/documents/edl-v10.php.
Contributors:
Paolo Patierno - initial API and implementation and/or initial documentation
*/
#if SSL
2019-11-26 15:34:16 +01:00
#if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3
2018-06-07 22:49:54 +02:00
using Microsoft.SPOT.Net.Security;
#else
using System.Security.Authentication;
2019-11-26 15:34:16 +01:00
#if NETCOREAPP
using System.Net.Security;
#endif
2018-06-07 22:49:54 +02:00
#endif
#endif
2019-11-26 15:34:16 +01:00
2018-06-07 22:49:54 +02:00
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System;
//using System.Security.Authentication;
//using System.Net.Security;
2019-11-26 15:34:16 +01:00
namespace uPLibrary.Networking.M2Mqtt {
/// <summary>
/// Channel to communicate over the network
/// </summary>
public class MqttNetworkChannel : IMqttNetworkChannel {
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
2019-11-26 15:34:16 +01:00
private readonly RemoteCertificateValidationCallback userCertificateValidationCallback;
private readonly LocalCertificateSelectionCallback userCertificateSelectionCallback;
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
// remote host information
// socket for communication
private Socket socket;
// using SSL
private readonly Boolean secure;
// CA certificate (on client)
private readonly X509Certificate caCert;
// Server certificate (on broker)
private readonly X509Certificate serverCert;
// client certificate (on client)
private readonly X509Certificate clientCert;
// SSL/TLS protocol version
private readonly MqttSslProtocols sslProtocol;
/// <summary>
/// Remote host name
/// </summary>
public String RemoteHostName { get; }
/// <summary>
/// Remote IP address
/// </summary>
public IPAddress RemoteIpAddress { get; }
/// <summary>
/// Remote port
/// </summary>
public Int32 RemotePort { get; }
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
// SSL stream
private SslStream sslStream;
#if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3
private NetworkStream netStream;
2018-06-07 22:49:54 +02:00
#endif
#endif
2019-11-26 15:34:16 +01:00
/// <summary>
/// Data available on the channel
/// </summary>
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
#if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3
public Boolean DataAvailable => this.secure ? this.sslStream.DataAvailable : this.socket.Available > 0;
2018-06-07 22:49:54 +02:00
#else
2019-11-26 15:34:16 +01:00
public Boolean DataAvailable => this.secure ? this.netStream.DataAvailable : this.socket.Available > 0;
2018-06-07 22:49:54 +02:00
#endif
#else
2019-11-26 15:34:16 +01:00
public Boolean DataAvailable => this.socket.Available > 0;
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="socket">Socket opened with the client</param>
public MqttNetworkChannel(Socket socket)
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
: this(socket, false, null, MqttSslProtocols.None, null, null)
#else
: this(socket, false, null, MqttSslProtocols.None)
#endif
{
2019-11-26 15:34:16 +01:00
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="socket">Socket opened with the client</param>
/// <param name="secure">Secure connection (SSL/TLS)</param>
/// <param name="serverCert">Server X509 certificate for secure connection</param>
/// <param name="sslProtocol">SSL/TLS protocol version</param>
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
2019-11-26 15:34:16 +01:00
/// <param name="userCertificateSelectionCallback">A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party</param>
/// <param name="userCertificateValidationCallback">A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication</param>
public MqttNetworkChannel(Socket socket, Boolean secure, X509Certificate serverCert, MqttSslProtocols sslProtocol,
RemoteCertificateValidationCallback userCertificateValidationCallback,
LocalCertificateSelectionCallback userCertificateSelectionCallback)
2018-06-07 22:49:54 +02:00
#else
public MqttNetworkChannel(Socket socket, bool secure, X509Certificate serverCert, MqttSslProtocols sslProtocol)
#endif
{
2019-11-26 15:34:16 +01:00
this.socket = socket;
this.secure = secure;
this.serverCert = serverCert;
this.sslProtocol = sslProtocol;
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
2019-11-26 15:34:16 +01:00
this.userCertificateValidationCallback = userCertificateValidationCallback;
this.userCertificateSelectionCallback = userCertificateSelectionCallback;
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="remoteHostName">Remote Host name</param>
/// <param name="remotePort">Remote port</param>
public MqttNetworkChannel(String remoteHostName, Int32 remotePort)
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
: this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None, null, null)
#else
: this(remoteHostName, remotePort, false, null, null, MqttSslProtocols.None)
#endif
{
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Constructor
/// </summary>
/// <param name="remoteHostName">Remote Host name</param>
/// <param name="remotePort">Remote port</param>
/// <param name="secure">Using SSL</param>
/// <param name="caCert">CA certificate</param>
/// <param name="clientCert">Client certificate</param>
/// <param name="sslProtocol">SSL/TLS protocol version</param>
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
2019-11-26 15:34:16 +01:00
/// <param name="userCertificateSelectionCallback">A RemoteCertificateValidationCallback delegate responsible for validating the certificate supplied by the remote party</param>
/// <param name="userCertificateValidationCallback">A LocalCertificateSelectionCallback delegate responsible for selecting the certificate used for authentication</param>
public MqttNetworkChannel(String remoteHostName, Int32 remotePort, Boolean secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol,
RemoteCertificateValidationCallback userCertificateValidationCallback,
LocalCertificateSelectionCallback userCertificateSelectionCallback)
2018-06-07 22:49:54 +02:00
#else
public MqttNetworkChannel(string remoteHostName, int remotePort, bool secure, X509Certificate caCert, X509Certificate clientCert, MqttSslProtocols sslProtocol)
#endif
{
2019-11-26 15:34:16 +01:00
IPAddress remoteIpAddress = null;
try {
// check if remoteHostName is a valid IP address and get it
remoteIpAddress = IPAddress.Parse(remoteHostName);
} catch {
}
// in this case the parameter remoteHostName isn't a valid IP address
if (remoteIpAddress == null) {
IPHostEntry hostEntry = Dns.GetHostEntry(remoteHostName);
if (hostEntry != null && hostEntry.AddressList.Length > 0) {
// check for the first address not null
// it seems that with .Net Micro Framework, the IPV6 addresses aren't supported and return "null"
Int32 i = 0;
while (hostEntry.AddressList[i] == null) {
i++;
}
remoteIpAddress = hostEntry.AddressList[i];
} else {
throw new Exception("No address found for the remote host name");
}
}
this.RemoteHostName = remoteHostName;
this.RemoteIpAddress = remoteIpAddress;
this.RemotePort = remotePort;
this.secure = secure;
this.caCert = caCert;
this.clientCert = clientCert;
this.sslProtocol = sslProtocol;
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3 || COMPACT_FRAMEWORK)
2019-11-26 15:34:16 +01:00
this.userCertificateValidationCallback = userCertificateValidationCallback;
this.userCertificateSelectionCallback = userCertificateSelectionCallback;
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Connect to remote server
/// </summary>
public void Connect() {
this.socket = new Socket(IPAddressUtility.GetAddressFamily(this.RemoteIpAddress), SocketType.Stream, ProtocolType.Tcp);
// try connection to the broker
this.socket.Connect(new IPEndPoint(this.RemoteIpAddress, this.RemotePort));
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
// secure channel requested
if (this.secure) {
// create SSL stream
#if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3
2018-06-07 22:49:54 +02:00
this.sslStream = new SslStream(this.socket);
#else
2019-11-26 15:34:16 +01:00
this.netStream = new NetworkStream(this.socket);
this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback);
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
// server authentication (SSL/TLS handshake)
#if MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3
2018-06-07 22:49:54 +02:00
this.sslStream.AuthenticateAsClient(this.remoteHostName,
this.clientCert,
new X509Certificate[] { this.caCert },
SslVerification.CertificateRequired,
MqttSslUtility.ToSslPlatformEnum(this.sslProtocol));
#else
2019-11-26 15:34:16 +01:00
X509CertificateCollection clientCertificates = null;
// check if there is a client certificate to add to the collection, otherwise it's null (as empty)
if (this.clientCert != null) {
clientCertificates = new X509CertificateCollection(new X509Certificate[] { this.clientCert });
}
this.sslStream.AuthenticateAsClient(this.RemoteHostName,
clientCertificates,
MqttSslUtility.ToSslPlatformEnum(this.sslProtocol),
false);
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Send data on the network channel
/// </summary>
/// <param name="buffer">Data buffer to send</param>
/// <returns>Number of byte sent</returns>
public Int32 Send(Byte[] buffer) {
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
if (this.secure) {
this.sslStream.Write(buffer, 0, buffer.Length);
this.sslStream.Flush();
return buffer.Length;
} else {
return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None);
}
2018-06-07 22:49:54 +02:00
#else
return this.socket.Send(buffer, 0, buffer.Length, SocketFlags.None);
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Receive data from the network
/// </summary>
/// <param name="buffer">Data buffer for receiving data</param>
/// <returns>Number of bytes received</returns>
public Int32 Receive(Byte[] buffer) {
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
if (this.secure) {
// read all data needed (until fill buffer)
Int32 idx = 0, read = 0;
while (idx < buffer.Length) {
// fixed scenario with socket closed gracefully by peer/broker and
// Read return 0. Avoid infinite loop.
read = this.sslStream.Read(buffer, idx, buffer.Length - idx);
if (read == 0) {
return 0;
}
idx += read;
}
return buffer.Length;
} else {
// read all data needed (until fill buffer)
Int32 idx = 0, read = 0;
while (idx < buffer.Length) {
// fixed scenario with socket closed gracefully by peer/broker and
// Read return 0. Avoid infinite loop.
read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None);
if (read == 0) {
return 0;
}
idx += read;
}
return buffer.Length;
}
2018-06-07 22:49:54 +02:00
#else
// read all data needed (until fill buffer)
int idx = 0, read = 0;
while (idx < buffer.Length)
{
// fixed scenario with socket closed gracefully by peer/broker and
// Read return 0. Avoid infinite loop.
read = this.socket.Receive(buffer, idx, buffer.Length - idx, SocketFlags.None);
if (read == 0)
return 0;
idx += read;
}
return buffer.Length;
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Receive data from the network channel with a specified timeout
/// </summary>
/// <param name="buffer">Data buffer for receiving data</param>
/// <param name="timeout">Timeout on receiving (in milliseconds)</param>
/// <returns>Number of bytes received</returns>
public Int32 Receive(Byte[] buffer, Int32 timeout) =>
// check data availability (timeout is in microseconds)
this.socket.Poll(timeout * 1000, SelectMode.SelectRead) ? this.Receive(buffer) : 0;
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Close the network channel
/// </summary>
public void Close() {
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
if (this.secure) {
#if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3
this.netStream.Close();
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
this.sslStream.Close();
}
this.socket.Close();
2018-06-07 22:49:54 +02:00
#else
this.socket.Close();
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// Accept connection from a remote client
/// </summary>
public void Accept() {
2018-06-07 22:49:54 +02:00
#if SSL
2019-11-26 15:34:16 +01:00
// secure channel requested
if (this.secure) {
2018-06-07 22:49:54 +02:00
#if !(MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3)
2019-11-26 15:34:16 +01:00
this.netStream = new NetworkStream(this.socket);
this.sslStream = new SslStream(this.netStream, false, this.userCertificateValidationCallback, this.userCertificateSelectionCallback);
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
this.sslStream.AuthenticateAsServer(this.serverCert, false, MqttSslUtility.ToSslPlatformEnum(this.sslProtocol), false);
2018-06-07 22:49:54 +02:00
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
return;
2018-06-07 22:49:54 +02:00
#else
return;
#endif
}
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
2019-11-26 15:34:16 +01:00
/// <summary>
/// IPAddress Utility class
/// </summary>
public static class IPAddressUtility {
2018-06-07 22:49:54 +02:00
/// <summary>
2019-11-26 15:34:16 +01:00
/// Return AddressFamily for the IP address
2018-06-07 22:49:54 +02:00
/// </summary>
2019-11-26 15:34:16 +01:00
/// <param name="ipAddress">IP address to check</param>
/// <returns>Address family</returns>
public static AddressFamily GetAddressFamily(IPAddress ipAddress) =>
#if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3
ipAddress.AddressFamily;
2018-06-07 22:49:54 +02:00
#else
return (ipAddress.ToString().IndexOf(':') != -1) ?
AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
#endif
2018-04-01 19:31:18 +02:00
2019-11-26 15:34:16 +01:00
}
/// <summary>
/// MQTT SSL utility class
/// </summary>
public static class MqttSslUtility {
#if !MF_FRAMEWORK_VERSION_V4_2 && !MF_FRAMEWORK_VERSION_V4_3 && !COMPACT_FRAMEWORK
public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol) => mqttSslProtocol switch
2018-06-07 22:49:54 +02:00
{
2019-11-26 15:34:16 +01:00
MqttSslProtocols.None => SslProtocols.None,
//MqttSslProtocols.SSLv3 => SslProtocols.Ssl3,
MqttSslProtocols.TLSv1_0 => SslProtocols.Tls,
MqttSslProtocols.TLSv1_1 => SslProtocols.Tls11,
MqttSslProtocols.TLSv1_2 => SslProtocols.Tls12,
_ => throw new ArgumentException("SSL/TLS protocol version not supported"),
};
#elif MF_FRAMEWORK_VERSION_V4_2 || MF_FRAMEWORK_VERSION_V4_3
public static SslProtocols ToSslPlatformEnum(MqttSslProtocols mqttSslProtocol)
2018-06-07 22:49:54 +02:00
{
switch (mqttSslProtocol)
{
case MqttSslProtocols.None:
return SslProtocols.None;
case MqttSslProtocols.SSLv3:
return SslProtocols.SSLv3;
case MqttSslProtocols.TLSv1_0:
return SslProtocols.TLSv1;
case MqttSslProtocols.TLSv1_1:
case MqttSslProtocols.TLSv1_2:
default:
throw new ArgumentException("SSL/TLS protocol version not supported");
}
}
#endif
2019-11-26 15:34:16 +01:00
}
2018-06-07 22:49:54 +02:00
}