/* * Copyright 2014, The Sporting Exchange Limited * * Licensed 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 com.betfair.cougar.netutil.nio; import com.betfair.cougar.netutil.nio.message.*; import com.betfair.cougar.netutil.nio.message.ProtocolMessage.ProtocolMessageType; import com.betfair.cougar.util.jmx.Exportable; import com.betfair.cougar.util.jmx.JMXControl; import org.apache.mina.common.ByteBuffer; import org.apache.mina.common.IoSession; import org.apache.mina.filter.codec.CumulativeProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedResource; import java.util.concurrent.atomic.AtomicLong; import static com.betfair.cougar.netutil.nio.NioLogger.LoggingLevel.ALL; @ManagedResource public class CougarProtocolDecoder extends CumulativeProtocolDecoder implements Exportable { private static final Logger LOG = LoggerFactory.getLogger(CougarProtocolDecoder.class); private final NioLogger nioLogger; private final AtomicLong badMessagesReceived = new AtomicLong(); private final AtomicLong acceptsReceived = new AtomicLong(); private final AtomicLong connectsReceived = new AtomicLong(); private final AtomicLong rejectsReceived = new AtomicLong(); private final AtomicLong messageRequestsReceived = new AtomicLong(); private final AtomicLong messageResponsesReceived = new AtomicLong(); private final AtomicLong eventsReceived = new AtomicLong(); private final AtomicLong keepAlivesReceived = new AtomicLong(); private final AtomicLong incompleteMessagesReceived = new AtomicLong(); private final AtomicLong disconnectsReceived = new AtomicLong(); private final AtomicLong suspendsReceived = new AtomicLong(); private final AtomicLong tlsRequestsReceived = new AtomicLong(); private final AtomicLong tlsResponsesReceived = new AtomicLong(); public CougarProtocolDecoder(NioLogger nioLogger) { this.nioLogger = nioLogger; export(nioLogger.getJmxControl()); } @Override protected boolean doDecode(IoSession session, ByteBuffer buffer, ProtocolDecoderOutput out) throws Exception { if (buffer.prefixedDataAvailable(4)) { int msgLen = buffer.getInt() - 1; // the message type is not included in the payload. ProtocolMessageType pm = ProtocolMessageType.getMessageByMessageType(buffer.get()); byte[] messageBody; // we need to know if we're acting as a client or a server and treat appropriately if (pm == ProtocolMessageType.MESSAGE) { Boolean b = (Boolean) session.getAttribute(CougarProtocol.IS_SERVER_ATTR_NAME); if (b != null) { pm = b ? ProtocolMessageType.MESSAGE_REQUEST : ProtocolMessageType.MESSAGE_RESPONSE; } else { throw new IllegalStateException("Received MESSAGE, but yet don't know whether I'm a client or a server"); } } switch (pm) { case MESSAGE_REQUEST: messageRequestsReceived.incrementAndGet(); messageBody = new byte[msgLen - 8]; nioLogger.log(ALL, session, "CougarProtocolDecoder: MESSAGE_REQUEST: Message of length %s received", msgLen); long reqCorrelationId = buffer.getLong(); buffer.get(messageBody); RequestMessage req = new RequestMessage(reqCorrelationId, messageBody); out.write(req); break; case MESSAGE_RESPONSE: messageResponsesReceived.incrementAndGet(); messageBody = new byte[msgLen - 8]; nioLogger.log(ALL, session, "CougarProtocolDecoder: MESSAGE_RESPONSE: Message of length %s received", msgLen); long respCorrelationId = buffer.getLong(); buffer.get(messageBody); ResponseMessage res = new ResponseMessage(respCorrelationId, messageBody); out.write(res); break; case EVENT: eventsReceived.incrementAndGet(); messageBody = new byte[msgLen]; nioLogger.log(ALL, session, "CougarProtocolDecoder: EVENT: Message of length %s received", msgLen); buffer.get(messageBody); EventMessage em = new EventMessage(messageBody); out.write(em); break; case CONNECT: connectsReceived.incrementAndGet(); byte[] versionsRequested = NioUtils.getVersionSet(buffer); ConnectMessage cm = new ConnectMessage(versionsRequested); out.write(cm); break; case ACCEPT: //Client Side - server has accepted our connection request acceptsReceived.incrementAndGet(); byte versionAccepted = buffer.get(); AcceptMessage acceptMessage = new AcceptMessage(versionAccepted); out.write(acceptMessage); break; case REJECT: //Client Side - server has said foxtrot oscar rejectsReceived.incrementAndGet(); byte rejectReasonCode = buffer.get(); RejectMessageReason reason = RejectMessageReason.getByReasonCode(rejectReasonCode); byte[] versionsAccepted = NioUtils.getVersionSet(buffer); RejectMessage rejectMessage = new RejectMessage(reason, versionsAccepted); out.write(rejectMessage); break; case KEEP_ALIVE: keepAlivesReceived.incrementAndGet(); KeepAliveMessage keepAlive = new KeepAliveMessage(); out.write(keepAlive); break; case DISCONNECT: disconnectsReceived.incrementAndGet(); DisconnectMessage disconnect = new DisconnectMessage(); out.write(disconnect); break; case SUSPEND: suspendsReceived.incrementAndGet(); SuspendMessage suspend = new SuspendMessage(); out.write(suspend); break; case START_TLS_REQUEST: //Client Side - server has said foxtrot oscar tlsRequestsReceived.incrementAndGet(); byte requestValue = buffer.get(); TLSRequirement requirement = TLSRequirement.getByValue(requestValue); StartTLSRequestMessage requestMessage = new StartTLSRequestMessage(requirement); out.write(requestMessage); break; case START_TLS_RESPONSE: //Client Side - server has said foxtrot oscar tlsResponsesReceived.incrementAndGet(); byte responseValue = buffer.get(); TLSResult result = TLSResult.getByValue(responseValue); StartTLSResponseMessage responseMessage = new StartTLSResponseMessage(result); out.write(responseMessage); break; default: badMessagesReceived.incrementAndGet(); LOG.error("Unknown message type " + pm + " - Ignoring"); } return true; } else { incompleteMessagesReceived.incrementAndGet(); nioLogger.log(ALL, session, "CougarProtocolDecoder: Returning FALSE, remaining was %s", buffer.remaining()); return false; } } private String getAsString(byte[] versions) { StringBuilder sb = new StringBuilder("{"); boolean first = true; for (byte b : versions) { if (first) { first = false; } else { sb.append(","); } sb.append(b); } sb.append("}"); return sb.toString(); } /** * Exports this service as an MBean, if the JMXControl is available */ @Override public void export(JMXControl jmxControl) { if (jmxControl != null) { jmxControl.registerMBean("CoUGAR.socket.transport:name=decoder", this); } } @ManagedAttribute public long getBadMessagesReceived() { return badMessagesReceived.get(); } @ManagedAttribute public long getAcceptsReceived() { return acceptsReceived.get(); } @ManagedAttribute public long getConnectsReceived() { return connectsReceived.get(); } @ManagedAttribute public long getRejectsReceived() { return rejectsReceived.get(); } @ManagedAttribute public long getMessageRequestsReceived() { return messageRequestsReceived.get(); } @ManagedAttribute public long getMessageResponsesReceived() { return messageResponsesReceived.get(); } @ManagedAttribute public long getEventsReceived() { return eventsReceived.get(); } @ManagedAttribute public long getKeepAlivesReceived() { return keepAlivesReceived.get(); } @ManagedAttribute public long getSuspendsReceived() { return suspendsReceived.get(); } @ManagedAttribute public long getIncompleteMessagesReceived() { return incompleteMessagesReceived.get(); } }