001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.transport.amqp;
018
019import java.io.DataInput;
020import java.io.DataInputStream;
021import java.io.DataOutput;
022import java.io.DataOutputStream;
023import java.io.IOException;
024import java.io.OutputStream;
025import java.nio.ByteBuffer;
026import java.nio.channels.Channels;
027import java.nio.channels.WritableByteChannel;
028
029import org.apache.activemq.util.ByteArrayInputStream;
030import org.apache.activemq.util.ByteArrayOutputStream;
031import org.apache.activemq.util.ByteSequence;
032import org.apache.activemq.wireformat.WireFormat;
033import org.fusesource.hawtbuf.Buffer;
034
035public class AmqpWireFormat implements WireFormat {
036
037    public static final long DEFAULT_MAX_FRAME_SIZE = Long.MAX_VALUE;
038    public static final int NO_AMQP_MAX_FRAME_SIZE = -1;
039    private static final int SASL_PROTOCOL = 3;
040
041    private int version = 1;
042    private long maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
043    private int maxAmqpFrameSize = NO_AMQP_MAX_FRAME_SIZE;
044
045    private boolean magicRead = false;
046    private ResetListener resetListener;
047
048    public interface ResetListener {
049        void onProtocolReset();
050    }
051
052    private boolean allowNonSaslConnections = true;
053
054    @Override
055    public ByteSequence marshal(Object command) throws IOException {
056        ByteArrayOutputStream baos = new ByteArrayOutputStream();
057        DataOutputStream dos = new DataOutputStream(baos);
058        marshal(command, dos);
059        dos.close();
060        return baos.toByteSequence();
061    }
062
063    @Override
064    public Object unmarshal(ByteSequence packet) throws IOException {
065        ByteArrayInputStream stream = new ByteArrayInputStream(packet);
066        DataInputStream dis = new DataInputStream(stream);
067        return unmarshal(dis);
068    }
069
070    @Override
071    public void marshal(Object command, DataOutput dataOut) throws IOException {
072        if (command instanceof ByteBuffer) {
073            ByteBuffer buffer = (ByteBuffer) command;
074
075            if (dataOut instanceof OutputStream) {
076                WritableByteChannel channel = Channels.newChannel((OutputStream) dataOut);
077                channel.write(buffer);
078            } else {
079                while (buffer.hasRemaining()) {
080                    dataOut.writeByte(buffer.get());
081                }
082            }
083        } else {
084            Buffer frame = (Buffer) command;
085            frame.writeTo(dataOut);
086        }
087    }
088
089    @Override
090    public Object unmarshal(DataInput dataIn) throws IOException {
091        if (!magicRead) {
092            Buffer magic = new Buffer(8);
093            magic.readFrom(dataIn);
094            magicRead = true;
095            return new AmqpHeader(magic, false);
096        } else {
097            int size = dataIn.readInt();
098            if (size > maxFrameSize) {
099                throw new AmqpProtocolException("Frame size exceeded max frame length.");
100            }
101            Buffer frame = new Buffer(size);
102            frame.bigEndianEditor().writeInt(size);
103            frame.readFrom(dataIn);
104            frame.clear();
105            return frame;
106        }
107    }
108
109    /**
110     * Given an AMQP header validate that the AMQP magic is present and
111     * if so that the version and protocol values align with what we support.
112     *
113     * @param header
114     *        the header instance received from the client.
115     *
116     * @return true if the header is valid against the current WireFormat.
117     */
118    public boolean isHeaderValid(AmqpHeader header) {
119        if (!header.hasValidPrefix()) {
120            return false;
121        }
122
123        if (!isAllowNonSaslConnections() && header.getProtocolId() != SASL_PROTOCOL) {
124            return false;
125        }
126
127        if (header.getMajor() != 1 || header.getMinor() != 0 || header.getRevision() != 0) {
128            return false;
129        }
130
131        return true;
132    }
133
134    /**
135     * Returns an AMQP Header object that represents the minimally protocol
136     * versions supported by this transport.  A client that attempts to
137     * connect with an AMQP version that doesn't at least meat this value
138     * will receive this prior to the connection being closed.
139     *
140     * @return the minimal AMQP version needed from the client.
141     */
142    public AmqpHeader getMinimallySupportedHeader() {
143        AmqpHeader header = new AmqpHeader();
144        if (!isAllowNonSaslConnections()) {
145            header.setProtocolId(3);
146        }
147
148        return header;
149    }
150
151    @Override
152    public void setVersion(int version) {
153        this.version = version;
154    }
155
156    @Override
157    public int getVersion() {
158        return this.version;
159    }
160
161    public void resetMagicRead() {
162        this.magicRead = false;
163        if (resetListener != null) {
164            resetListener.onProtocolReset();
165        }
166    }
167
168    public void setProtocolResetListener(ResetListener listener) {
169        this.resetListener = listener;
170    }
171
172    public boolean isMagicRead() {
173        return this.magicRead;
174    }
175
176    public long getMaxFrameSize() {
177        return maxFrameSize;
178    }
179
180    public void setMaxFrameSize(long maxFrameSize) {
181        this.maxFrameSize = maxFrameSize;
182    }
183
184    public int getMaxAmqpFrameSize() {
185        return maxAmqpFrameSize;
186    }
187
188    public void setMaxAmqpFrameSize(int maxAmqpFrameSize) {
189        this.maxAmqpFrameSize = maxAmqpFrameSize;
190    }
191
192    public boolean isAllowNonSaslConnections() {
193        return allowNonSaslConnections;
194    }
195
196    public void setAllowNonSaslConnections(boolean allowNonSaslConnections) {
197        this.allowNonSaslConnections = allowNonSaslConnections;
198    }
199}