@Override
  public void messageReceived(Object in, IoSession session) throws Exception {
    RTMPConnection conn = (RTMPConnection) session.getAttribute(RTMPConnection.RTMP_CONNECTION_KEY);
    RTMP state = (RTMP) session.getAttribute(ProtocolState.SESSION_KEY);
    IRTMPEvent message = null;
    final Packet packet = (Packet) in;
    message = packet.getMessage();
    final Header header = packet.getHeader();
    final Channel channel = conn.getChannel(header.getChannelId());

    // Increase number of received messages
    conn.messageReceived();

    if (header.getDataType() == TYPE_BYTES_READ) {
      // TODO need to sync the bytes read on edge and origin
      onStreamBytesRead(conn, channel, header, (BytesRead) message);
    }

    if (header.getDataType() == TYPE_INVOKE) {
      final IServiceCall call = ((Invoke) message).getCall();
      final String action = call.getServiceMethodName();
      if (call.getServiceName() == null
          && !conn.isConnected()
          && StreamAction.valueOf(action).equals(StreamAction.CONNECT)) {
        handleConnect(conn, channel, header, (Invoke) message, (RTMP) state);
        return;
      }
    }

    switch (header.getDataType()) {
      case TYPE_CHUNK_SIZE:
      case TYPE_INVOKE:
      case TYPE_FLEX_MESSAGE:
      case TYPE_NOTIFY:
      case TYPE_AUDIO_DATA:
      case TYPE_VIDEO_DATA:
      case TYPE_FLEX_SHARED_OBJECT:
      case TYPE_FLEX_STREAM_SEND:
      case TYPE_SHARED_OBJECT:
      case TYPE_BYTES_READ:
        forwardPacket(conn, packet);
        break;
      case TYPE_PING:
        onPing(conn, channel, header, (Ping) message);
        break;

      default:
        if (log.isDebugEnabled()) {
          log.debug("Unknown type: {}", header.getDataType());
        }
    }
    if (message instanceof Unknown) {
      log.info(message.toString());
    }
    if (message != null) {
      message.release();
    }
  }
 /** {@inheritDoc} */
 public void messageReceived(RTMPConnection conn, Packet packet) throws Exception {
   log.trace("messageReceived connection: {}", conn.getSessionId());
   if (conn != null) {
     IRTMPEvent message = null;
     try {
       message = packet.getMessage();
       final Header header = packet.getHeader();
       final Number streamId = header.getStreamId();
       final Channel channel = conn.getChannel(header.getChannelId());
       final IClientStream stream = conn.getStreamById(streamId);
       if (log.isTraceEnabled()) {
         log.trace("Message received - header: {}", header);
       }
       // set stream id on the connection
       conn.setStreamId(streamId);
       // increase number of received messages
       conn.messageReceived();
       // set the source of the message
       message.setSource(conn);
       // process based on data type
       final byte headerDataType = header.getDataType();
       if (log.isTraceEnabled()) {
         log.trace("Header / message data type: {}", headerDataType);
       }
       switch (headerDataType) {
         case TYPE_AGGREGATE:
           log.debug(
               "Aggregate type data - header timer: {} size: {}",
               header.getTimer(),
               header.getSize());
         case TYPE_AUDIO_DATA:
         case TYPE_VIDEO_DATA:
           // mark the event as from a live source
           // log.trace("Marking message as originating from a Live source");
           message.setSourceType(Constants.SOURCE_TYPE_LIVE);
           // NOTE: If we respond to "publish" with "NetStream.Publish.BadName",
           // the client sends a few stream packets before stopping. We need to ignore them
           if (stream != null) {
             ((IEventDispatcher) stream).dispatchEvent(message);
           }
           break;
         case TYPE_FLEX_SHARED_OBJECT:
         case TYPE_SHARED_OBJECT:
           onSharedObject(conn, channel, header, (SharedObjectMessage) message);
           break;
         case TYPE_INVOKE:
         case TYPE_FLEX_MESSAGE:
           onCommand(conn, channel, header, (Invoke) message);
           IPendingServiceCall call = ((Invoke) message).getCall();
           if (message.getHeader().getStreamId().intValue() != 0
               && call.getServiceName() == null
               && StreamAction.PUBLISH.equals(call.getServiceMethodName())) {
             if (stream != null) {
               // Only dispatch if stream really was created
               ((IEventDispatcher) stream).dispatchEvent(message);
             }
           }
           break;
         case TYPE_NOTIFY: // like an invoke, but does not return
           // anything and has a invoke / transaction
           // id of 0
         case TYPE_FLEX_STREAM_SEND:
           if (((Notify) message).getData() != null && stream != null) {
             // Stream metadata
             ((IEventDispatcher) stream).dispatchEvent(message);
           } else {
             onCommand(conn, channel, header, (Notify) message);
           }
           break;
         case TYPE_PING:
           onPing(conn, channel, header, (Ping) message);
           break;
         case TYPE_BYTES_READ:
           onStreamBytesRead(conn, channel, header, (BytesRead) message);
           break;
         case TYPE_CHUNK_SIZE:
           onChunkSize(conn, channel, header, (ChunkSize) message);
           break;
         case Constants.TYPE_CLIENT_BANDWIDTH: // onBWDone / peer bw
           log.debug("Client bandwidth: {}", message);
           onClientBandwidth(conn, channel, (ClientBW) message);
           break;
         case Constants.TYPE_SERVER_BANDWIDTH: // window ack size
           log.debug("Server bandwidth: {}", message);
           onServerBandwidth(conn, channel, (ServerBW) message);
           break;
         default:
           log.debug("Unknown type: {}", header.getDataType());
       }
       if (message instanceof Unknown) {
         log.info("Message type unknown: {}", message);
       }
     } catch (Throwable t) {
       log.error("Exception", t);
     }
     // XXX this may be causing 'missing' data if previous methods are
     // not making copies before buffering etc..
     if (message != null) {
       message.release();
     }
   }
 }
 /**
  * Breaks-up the aggregate into its individual parts and returns them as a list. The parts are
  * returned based on the ordering of the aggregate itself.
  *
  * @return list of IRTMPEvent objects
  */
 public LinkedList<IRTMPEvent> getParts() {
   LinkedList<IRTMPEvent> parts = new LinkedList<IRTMPEvent>();
   log.trace("Aggregate data length: {}", data.limit());
   int position = data.position();
   do {
     try {
       // read the header
       // log.trace("Hex: {}", data.getHexDump());
       byte subType = data.get();
       // when we run into subtype 0 break out of here
       if (subType == 0) {
         log.debug("Subtype 0 encountered within this aggregate, processing with exit");
         break;
       }
       int size = IOUtils.readUnsignedMediumInt(data);
       log.debug("Data subtype: {} size: {}", subType, size);
       // TODO ensure the data contains all the bytes to support the specified size
       int timestamp = IOUtils.readExtendedMediumInt(data);
       /*timestamp = ntohap((GETIBPOINTER(buffer) + 4)); 0x12345678 == 34 56 78 12*/
       int streamId = IOUtils.readUnsignedMediumInt(data);
       log.debug("Data timestamp: {} stream id: {}", timestamp, streamId);
       Header partHeader = new Header();
       partHeader.setChannelId(header.getChannelId());
       partHeader.setDataType(subType);
       partHeader.setSize(size);
       // use the stream id from the aggregate's header
       partHeader.setStreamId(header.getStreamId());
       partHeader.setTimer(timestamp);
       // timer delta == time stamp - timer base
       // the back pointer may be used to verify the size of the individual part
       // it will be equal to the data size + header size
       int backPointer = 0;
       switch (subType) {
         case TYPE_AUDIO_DATA:
           AudioData audio = new AudioData(data.getSlice(size));
           audio.setTimestamp(timestamp);
           audio.setHeader(partHeader);
           log.debug("Audio header: {}", audio.getHeader());
           parts.add(audio);
           // log.trace("Hex: {}", data.getHexDump());
           // ensure 4 bytes left to read an int
           if (data.position() < data.limit() - 4) {
             backPointer = data.getInt();
             // log.trace("Back pointer: {}", backPointer);
             if (backPointer != (size + 11)) {
               log.debug("Data size ({}) and back pointer ({}) did not match", size, backPointer);
             }
           }
           break;
         case TYPE_VIDEO_DATA:
           VideoData video = new VideoData(data.getSlice(size));
           video.setTimestamp(timestamp);
           video.setHeader(partHeader);
           log.debug("Video header: {}", video.getHeader());
           parts.add(video);
           // log.trace("Hex: {}", data.getHexDump());
           // ensure 4 bytes left to read an int
           if (data.position() < data.limit() - 4) {
             backPointer = data.getInt();
             // log.trace("Back pointer: {}", backPointer);
             if (backPointer != (size + 11)) {
               log.debug("Data size ({}) and back pointer ({}) did not match", size, backPointer);
             }
           }
           break;
         default:
           log.debug("Non-A/V subtype: {}", subType);
           Unknown unk = new Unknown(subType, data.getSlice(size));
           unk.setTimestamp(timestamp);
           unk.setHeader(partHeader);
           parts.add(unk);
           // ensure 4 bytes left to read an int
           if (data.position() < data.limit() - 4) {
             backPointer = data.getInt();
           }
       }
       position = data.position();
     } catch (Exception e) {
       log.error("Exception decoding aggregate parts", e);
       break;
     }
     log.trace("Data position: {}", position);
   } while (position < data.limit());
   log.trace("Aggregate processing complete, {} parts extracted", parts.size());
   return parts;
 }
 /** {@inheritDoc} */
 @Override
 protected void onCommand(RTMPConnection conn, Channel channel, Header source, ICommand command) {
   log.trace("onCommand: {}, id: {}", command, command.getTransactionId());
   final IServiceCall call = command.getCall();
   final String methodName = call.getServiceMethodName();
   log.debug(
       "Service name: {} args[0]: {}",
       methodName,
       (call.getArguments().length != 0 ? call.getArguments()[0] : ""));
   if ("_result".equals(methodName) || "_error".equals(methodName)) {
     final IPendingServiceCall pendingCall = conn.getPendingCall(command.getTransactionId());
     log.debug("Received result for pending call - {}", pendingCall);
     if (pendingCall != null) {
       if ("connect".equals(methodName)) {
         Integer encoding = (Integer) connectionParams.get("objectEncoding");
         if (encoding != null && encoding.intValue() == 3) {
           log.debug("Setting encoding to AMF3");
           conn.getState().setEncoding(IConnection.Encoding.AMF3);
         }
       }
     }
     handlePendingCallResult(conn, (Invoke) command);
     return;
   }
   // potentially used twice so get the value once
   boolean onStatus = "onStatus".equals(methodName);
   log.debug("onStatus {}", onStatus);
   if (onStatus) {
     Integer streamId = source.getStreamId();
     if (log.isDebugEnabled()) {
       log.debug("Stream id from header: {}", streamId);
       // XXX create better to serialize ObjectMap to Status object
       ObjectMap<?, ?> objMap = (ObjectMap<?, ?>) call.getArguments()[0];
       // should keep this as an Object to stay compatible with FMS3 etc
       log.debug("Client id from status: {}", objMap.get("clientid"));
     }
     if (streamId != null) {
       // try lookup by stream id
       NetStreamPrivateData streamData = streamDataMap.get(streamId);
       // if null return the first one in the map
       if (streamData == null) {
         log.debug("Stream data was not found by id. Map contents: {}", streamDataMap);
         if (!streamDataMap.isEmpty()) {
           streamData = streamDataMap.values().iterator().next();
         }
       }
       if (streamData == null) {
         log.warn("Stream data was null for id: {}", streamId);
       }
       if (streamData != null && streamData.handler != null) {
         log.debug("Got stream data and handler");
         streamData.handler.onStreamEvent((Notify) command);
       }
     }
   }
   // if this client supports service methods, forward the call
   if (serviceProvider == null) {
     // client doesn't support calling methods on him
     call.setStatus(Call.STATUS_METHOD_NOT_FOUND);
     call.setException(new MethodNotFoundException(methodName));
     log.info(
         "No service provider / method not found; to handle calls like onBWCheck, add a service provider");
   } else {
     serviceInvoker.invoke(call, serviceProvider);
   }
   if (call instanceof IPendingServiceCall) {
     IPendingServiceCall psc = (IPendingServiceCall) call;
     Object result = psc.getResult();
     log.debug("Pending call result is: {}", result);
     if (result instanceof DeferredResult) {
       DeferredResult dr = (DeferredResult) result;
       dr.setTransactionId(command.getTransactionId());
       dr.setServiceCall(psc);
       dr.setChannel(channel);
       conn.registerDeferredResult(dr);
     } else if (!onStatus) {
       if ("onBWCheck".equals(methodName)) {
         onBWCheck(call.getArguments().length > 0 ? call.getArguments()[0] : null);
         Invoke reply = new Invoke();
         reply.setCall(call);
         reply.setTransactionId(command.getTransactionId());
         channel.write(reply);
       } else if ("onBWDone".equals(methodName)) {
         onBWDone(call.getArguments().length > 0 ? call.getArguments()[0] : null);
       } else {
         Invoke reply = new Invoke();
         reply.setCall(call);
         reply.setTransactionId(command.getTransactionId());
         log.debug("Sending empty call reply: {}", reply);
         channel.write(reply);
       }
     }
   }
 }