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}