ProtocolCodec discussion
Skip to end of metadata
Go to start of metadata

Introduction

This filter is one of the most important one. In many case, writing a MINA based application without having such a filter added is not really meaningful.

Events handled

Here is a list of all the handled events in this filter :

event

Handled

Description

exceptionCaught

(error)

N/A

filterClose

(error)

N/A

filterSetTrafficMask

(error)

Will be removed

filterWrite

(tick)

 

onPostRemove

(tick)

Invoked when the filter is removed from the chain

onPreAdd

(tick)

Invoked when this filter is added to the chain

sessionOpened

(error)

Useless. May be removed

messageReceived

(tick)

A message has been read and need to be decoded

messageSent

(tick)

???

sessionClosed

(tick)

An event received when the session has been closed

sessionCreated

(tick)

An event received when a new session has been created

sessionIdle

(error)

N/A

Description

Constructor

In order to be able to encode or decode a message, we need to pass the filter a factory, which will be used to create those two parts of the codec :

  • an encoder
  • a decoder

The factory is pretty simple. It offers two methods :

public interface ProtocolCodecFactory {
    /**
     * Returns a new (or reusable) instance of {@link ProtocolEncoder} which
     * encodes message objects into binary or protocol-specific data.
     */
    ProtocolEncoder getEncoder(IoSession session) throws Exception;

    /**
     * Returns a new (or reusable) instance of {@link ProtocolDecoder} which
     * decodes binary or protocol-specific data into message objects.
     */
    ProtocolDecoder getDecoder(IoSession session) throws Exception;
}
Icon

We can see that those two methods have a single parameter, an IoSession, which is a bad idea. A codec should not depend on a session. In fact, not a single codec uses this session, and I think that it should be removed.

Moreover, it forces the encoded and decoder instances to be created only when the first message is received, which can be a hassle, as ithe encoder/decoder creation has to be synchronized. It would be way better to create those instances when the filter is created.

It's also possible to pass the encoder and decoder directly, as a factory will be created internally to encapsulate those two methods.

SessionCreated event

The current handling for this event is pretty simple : it stores the Encoder and Decoder instances in the session attributes.

public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
        // Creates the decoder and stores it into the newly created session
        session.setAttribute(DECODER, factory.getDecoder(session));

        // Creates the encoder and stores it into the newly created session
        session.setAttribute(ENCODER, factory.getEncoder(session));

        // Call the next filter
        nextFilter.sessionCreated(session);
    }
Icon

The encoder and decoder are stateless, we don't need to pass a session object to the getEncoder/Decoder() methods.

 

Icon

 Thre is another problem : if we add this codec in the chain after the session has been created, then the encoder and decoder are never added into the session attributes, which is bad. This is addressed in
https://issues.apache.org/jira/browse/DIRMINA-635

SessionClosed event

 This event is received when the session is closed. We have to remove the Encoder and Decoder instances from the session's attributes, and to dispose those elements. As we may have remaining messages to decode, we have to process them first.

 We have different cases, as of MINA 2.0.0-M3 :

  1. We don't have anymore bytes in the incoming request : just close everything
  2. We have some more bytes, and we can decode a full message out of them : decode the message, and go back to step (1)
  3. We have some more bytes, but we can't decode a full message out of them :throw an exception
    Icon

     I'm not sure that we should decode anything when we receive a sessionClosed message : The session is already closed, and any other treatment may violate the client's will.

OnPreAdd

This event is received when we try to insert this filter during a session. What it does is very simple :

- Checks that the filter is not already present in the chain. If so, generates an exception
- Initialize the encoder and decoder.

Nothing much to tell about this simple handler

OnPostRemove

This event is received when this filter is removed from the chain. We simply :

  • removes the encoder, decoder and callback from the session's attributes
  • dispose thos three instances

Nothing much to tell about this simple handler

MessageReceived event

This is the heart of the decoding part. We receive some bytes, and we must produce some messages out of it. Again, we have many cases to consider :

  1. We received an empty byte buffer
  2. We received a byte buffer which is not enough to generate a message
  3. We received a byte buffer containing exactly one message
  4. We received a byte buffer containing one message plus some remaining bytes
  5. We received a byte buffer containing more than one message exactly
  6. We received a byte buffer containing more than one message plus some extra bytes

We also have a special case : we received an object which is not a byte buffer.

Icon

Seems like a nonsense to me. How possibly can we go through the decoder with something we don't want to decode ?

Generally speaking, the way the decoder works is exposed in the following pseudo-code :

  process messageReceived :
    while we have bytes in the incoming buffer
      do
        call the decoder(callback)
        flush the accumulated decoded messages the decoder has stored in a queue
      done

  decode(callback) :
    while we have more bytes
      do
        decode a message from the bytes
        push it into the callback queue
      done

  flush :
    while we have messages in the queue
      do
        get the first message from the queue
        call next filter.messageReceived( message )
      done
        
Icon

The callback system is a bit annoying. The idea is that once the decoder has found a message, it pushes it into a queue, and at the end, we have to do a flush() on the resulting queue : the callback holds the decoded message and the mechanism to process them (here, to call the next filter).

I think it would be way easier to get a list of decoded messages as a return from the decoder, and then loop on this list in order to keep the logic into the Codec filter, instead of delegating this logic to an external class.

A better solution, IMHO, would be to call the decoder, which will return a message or nothing (if we don't have enough bytes), and push this message to the next filter, until we can't decode anything else :

while ( ( message = decode( bytes ) ) != null ) {
nextFilter.messageReceive( message )
}

Icon

Another aspect would be to make the decoder to push the result as a stream, which will be handled by the next filter : this will allow a multi-layered decoder to be written. Decoding ASN.1 encoded PDU will typically benefit from such an approach.

Let's now analyze those 6 cases.

1) The incoming buffer is empty

We just do nothing in this case. We don't even have to call the next filter. 

Icon

I don't think this is a possible case, as this filter will be invoked after some bytes has been read from the socket. Anyway ...

2) We received a byte buffer which is not enough to generate a message

In this case - as in the other case when we have remaining bytes - everything will depend on the capacity the decoder has to keep a transitioanl state until we can decode the message with some more bytes. The decoder has to be stateful, which all the decoder aren't.

In many cases, we must rely on a cumulative decoder, which will gather bytes up to a boundary (like a \n, for instance). The decoding process will be done in two steps :

  • gather as many bytes as necessary, until we reach the message boundary
  • then call the real decoder.

This is not that easy, considering that many protocol do not define a clear boundary, or make it complicated to find it. For instance :

  • An HTTP decoder won't be able to find the end of a message using a cumulative decoder alone
  • A LDAP decoder has to be able to read the L value of the received PDU, which means it is able to deal with the T part (a PDU is a TLV, if the message is {B/D/C}ER encoded

We may need to have a stateful decoder to avoid such problems, but we won't discuss this aspect here.

So what we do is to call the decoder, and if we didn't got an exception, we just call the callback which will do nothing.

3) We received a byte buffer containing exactly one message

This is an easy case : we simply have to decode the message, and send it to the next filter, before returning.

4) We received a byte buffer containing one message plus some remaining bytes

We have to loop after having processed the first message. We will generate an exception if the decoder can't handle partial messages, or isntead stores the remaining bytes in a context associated with the session, waiting for more bytes to come.

5) We received a byte buffer containing more than one message exactly

This is the exact same case as (3), but we will loop for each message, sending them one by one to the next filter.

6) We received a byte buffer containing more than one message plus some extra bytes

We loop on each messages, as in (5), and fallback to case (2)

Labels
  • No labels