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 ;
2021-04-10 18:57:53 +02:00
#if MF_FRAMEWORK_VERSION_V4_2 | | MF_FRAMEWORK_VERSION_V4_3
2019-11-26 15:34:16 +01:00
// CA certificate (on client)
private readonly X509Certificate caCert ;
2021-04-10 18:57:53 +02:00
#endif
2019-11-26 15:34:16 +01:00
// 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 ;
2021-04-10 18:57:53 +02:00
#if MF_FRAMEWORK_VERSION_V4_2 | | MF_FRAMEWORK_VERSION_V4_3
2019-11-26 15:34:16 +01:00
this . caCert = caCert ;
2021-04-10 18:57:53 +02:00
#endif
2019-11-26 15:34:16 +01:00
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
}