/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.core.protocol.mqtt; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.mqtt.MqttDecoder; import io.netty.handler.codec.mqtt.MqttEncoder; import io.netty.handler.codec.mqtt.MqttMessage; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.BaseInterceptor; import org.apache.activemq.artemis.core.remoting.impl.netty.NettyServerConnection; import org.apache.activemq.artemis.core.server.ActiveMQServer; import org.apache.activemq.artemis.core.server.management.Notification; import org.apache.activemq.artemis.core.server.management.NotificationListener; import org.apache.activemq.artemis.spi.core.protocol.AbstractProtocolManager; import org.apache.activemq.artemis.spi.core.protocol.ConnectionEntry; import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory; import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection; import org.apache.activemq.artemis.spi.core.remoting.Acceptor; import org.apache.activemq.artemis.spi.core.remoting.Connection; /** * MQTTProtocolManager */ class MQTTProtocolManager extends AbstractProtocolManager<MqttMessage, MQTTInterceptor, MQTTConnection> implements NotificationListener { private static final List<String> websocketRegistryNames = Arrays.asList("mqtt", "mqttv3.1"); private ActiveMQServer server; private MQTTLogger log = MQTTLogger.LOGGER; private final List<MQTTInterceptor> incomingInterceptors = new ArrayList<>(); private final List<MQTTInterceptor> outgoingInterceptors = new ArrayList<>(); MQTTProtocolManager(ActiveMQServer server, List<BaseInterceptor> incomingInterceptors, List<BaseInterceptor> outgoingInterceptors) { this.server = server; this.updateInterceptors(incomingInterceptors, outgoingInterceptors); } @Override public void onNotification(Notification notification) { // TODO handle notifications } @Override public ProtocolManagerFactory getFactory() { return new MQTTProtocolManagerFactory(); } @Override public void updateInterceptors(List incoming, List outgoing) { this.incomingInterceptors.clear(); this.incomingInterceptors.addAll(getFactory().filterInterceptors(incoming)); this.outgoingInterceptors.clear(); this.outgoingInterceptors.addAll(getFactory().filterInterceptors(outgoing)); } @Override public ConnectionEntry createConnectionEntry(Acceptor acceptorUsed, Connection connection) { try { MQTTConnection mqttConnection = new MQTTConnection(connection); ConnectionEntry entry = new ConnectionEntry(mqttConnection, null, System.currentTimeMillis(), MQTTUtil.DEFAULT_KEEP_ALIVE_FREQUENCY); NettyServerConnection nettyConnection = ((NettyServerConnection) connection); MQTTProtocolHandler protocolHandler = nettyConnection.getChannel().pipeline().get(MQTTProtocolHandler.class); protocolHandler.setConnection(mqttConnection, entry); return entry; } catch (Exception e) { log.error(e); return null; } } @Override public boolean acceptsNoHandshake() { return false; } @Override public void removeHandler(String name) { // TODO add support for handlers } @Override public void handleBuffer(RemotingConnection connection, ActiveMQBuffer buffer) { connection.bufferReceived(connection.getID(), buffer); } @Override public void addChannelHandlers(ChannelPipeline pipeline) { pipeline.addLast(MqttEncoder.INSTANCE); pipeline.addLast(new MqttDecoder(MQTTUtil.MAX_MESSAGE_SIZE)); pipeline.addLast(new MQTTProtocolHandler(server, this)); } /** * The protocol handler passes us an 8 byte long array from the transport. We sniff these first 8 bytes to see * if they match the first 8 bytes from MQTT Connect packet. In many other protocols the protocol name is the first * thing sent on the wire. However, in MQTT the protocol name doesn't come until later on in the CONNECT packet. * * In order to fully identify MQTT protocol via protocol name, we need up to 12 bytes. However, we can use other * information from the connect packet to infer that the MQTT protocol is being used. This is enough to identify MQTT * and add the Netty codec in the pipeline. The Netty codec takes care of things from here. * * MQTT CONNECT PACKET: See MQTT 3.1.1 Spec for more info. * * Byte 1: Fixed Header Packet Type. 0b0001000 (16) = MQTT Connect * Byte 2-[N]: Remaining length of the Connect Packet (encoded with 1-4 bytes). * * The next set of bytes represents the UTF8 encoded string MQTT (MQTT 3.1.1) or MQIsdp (MQTT 3.1) * Byte N: UTF8 MSB must be 0 * Byte N+1: UTF8 LSB must be (4(MQTT) or 6(MQIsdp)) * Byte N+1: M (first char from the protocol name). * * Max no bytes used in the sequence = 8. */ @Override public boolean isProtocol(byte[] array) { ByteBuf buf = Unpooled.wrappedBuffer(array); if (!(buf.readByte() == 16 && validateRemainingLength(buf) && buf.readByte() == (byte) 0)) return false; byte b = buf.readByte(); return ((b == 4 || b == 6) && (buf.readByte() == 77)); } private boolean validateRemainingLength(ByteBuf buffer) { byte msb = (byte) 0b10000000; for (byte i = 0; i < 4; i++) { if ((buffer.readByte() & msb) != msb) return true; } return false; } @Override public void handshake(NettyServerConnection connection, ActiveMQBuffer buffer) { } @Override public List<String> websocketSubprotocolIdentifiers() { return websocketRegistryNames; } public void invokeIncoming(MqttMessage mqttMessage, MQTTConnection connection) { super.invokeInterceptors(this.incomingInterceptors, mqttMessage, connection); } public void invokeOutgoing(MqttMessage mqttMessage, MQTTConnection connection) { super.invokeInterceptors(this.outgoingInterceptors, mqttMessage, connection); } }