diff --git a/Source/MQTTnet/Client/Options/MqttClientOptions.cs b/Source/MQTTnet/Client/Options/MqttClientOptions.cs index ce5aed59..931ece6f 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptions.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptions.cs @@ -98,6 +98,16 @@ namespace MQTTnet.Client /// public ushort TopicAliasMaximum { get; set; } + /// + /// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary + /// client. + /// If successful, this means that loop detection will be more effective and that retained messages will be propagated + /// correctly. + /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect + /// properly. + /// + public bool TryPrivate { get; set; } = true; + /// /// Gets or sets the user properties. /// In MQTT 5, user properties are basic UTF-8 string key-value pairs that you can append to almost every type of MQTT diff --git a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs index 4896d2b6..3ed60a24 100644 --- a/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs +++ b/Source/MQTTnet/Client/Options/MqttClientOptionsBuilder.cs @@ -96,16 +96,6 @@ namespace MQTTnet.Client return this; } - /// - /// Sets the timeout which will be applied at socket level and internal operations. - /// The default value is the same as for sockets in .NET in general. - /// - public MqttClientOptionsBuilder WithTimeout(TimeSpan value) - { - _options.Timeout = value; - return this; - } - public MqttClientOptionsBuilder WithConnectionUri(Uri uri) { if (uri == null) @@ -289,6 +279,16 @@ namespace MQTTnet.Client return this; } + /// + /// Sets the timeout which will be applied at socket level and internal operations. + /// The default value is the same as for sockets in .NET in general. + /// + public MqttClientOptionsBuilder WithTimeout(TimeSpan value) + { + _options.Timeout = value; + return this; + } + public MqttClientOptionsBuilder WithTls(MqttClientOptionsBuilderTlsParameters parameters) { _tlsParameters = parameters; @@ -322,6 +322,20 @@ namespace MQTTnet.Client return this; } + /// + /// If set to true, the bridge will attempt to indicate to the remote broker that it is a bridge not an ordinary + /// client. + /// If successful, this means that loop detection will be more effective and that retained messages will be propagated + /// correctly. + /// Not all brokers support this feature so it may be necessary to set it to false if your bridge does not connect + /// properly. + /// + public MqttClientOptionsBuilder WithTryPrivate(bool tryPrivate = true) + { + _options.TryPrivate = true; + return this; + } + public MqttClientOptionsBuilder WithUserProperty(string name, string value) { if (name is null) diff --git a/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs index d818630a..09096b68 100644 --- a/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs +++ b/Source/MQTTnet/Formatter/MqttConnectPacketFactory.cs @@ -33,7 +33,8 @@ namespace MQTTnet.Formatter RequestResponseInformation = clientOptions.RequestResponseInformation, SessionExpiryInterval = clientOptions.SessionExpiryInterval, TopicAliasMaximum = clientOptions.TopicAliasMaximum, - UserProperties = clientOptions.UserProperties + UserProperties = clientOptions.UserProperties, + TryPrivate = clientOptions.TryPrivate }; if (!string.IsNullOrEmpty(clientOptions.WillTopic)) diff --git a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs index 2dcf00be..580c59b3 100644 --- a/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs +++ b/Source/MQTTnet/Formatter/MqttPacketFormatterAdapter.cs @@ -95,6 +95,10 @@ namespace MQTTnet.Formatter var protocolName = _bufferReader.ReadString(); var protocolLevel = _bufferReader.ReadByte(); + // Remove the mosquitto try_private flag (MQTT 3.1.1 Bridge). + // This flag is accepted but not yet used. + protocolLevel &= 0x7F; + if (protocolName == "MQTT") { if (protocolLevel == 5) diff --git a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs index d8276bb6..7d315433 100644 --- a/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs +++ b/Source/MQTTnet/Formatter/V3/MqttV3PacketFormatter.cs @@ -175,12 +175,18 @@ namespace MQTTnet.Formatter.V3 throw new MqttProtocolViolationException("MQTT protocol name do not match MQTT v3."); } + var tryPrivate = (protocolVersion & 0x80) > 0; + protocolVersion &= 0x7F; + if (protocolVersion != 3 && protocolVersion != 4) { throw new MqttProtocolViolationException("MQTT protocol version do not match MQTT v3."); } - var packet = new MqttConnectPacket(); + var packet = new MqttConnectPacket + { + TryPrivate = tryPrivate + }; var connectFlags = _bufferReader.ReadByte(); if ((connectFlags & 0x1) > 0) @@ -409,7 +415,14 @@ namespace MQTTnet.Formatter.V3 ValidateConnectPacket(packet); bufferWriter.WriteString("MQIsdp"); - bufferWriter.WriteByte(3); // Protocol Level 3 + + var protocolVersion = 3; + if (packet.TryPrivate) + { + protocolVersion |= 0x80; + } + + bufferWriter.WriteByte((byte)protocolVersion); byte connectFlags = 0x0; if (packet.CleanSession) diff --git a/Source/MQTTnet/Packets/MqttConnectPacket.cs b/Source/MQTTnet/Packets/MqttConnectPacket.cs index a543f5b8..f9e83f35 100644 --- a/Source/MQTTnet/Packets/MqttConnectPacket.cs +++ b/Source/MQTTnet/Packets/MqttConnectPacket.cs @@ -64,6 +64,8 @@ namespace MQTTnet.Packets public List WillUserProperties { get; set; } + public bool TryPrivate { get; set; } + public override string ToString() { var passwordText = string.Empty;