public static IByteBuffer encode(SNMessage message)
        {
            int         length = message.getLength();
            IByteBuffer buf    = Unpooled.Buffer(length);

            if (length <= 255)
            {
                buf.WriteByte(length);
            }
            else
            {
                buf.WriteByte(THREE_OCTET_LENGTH_SUFFIX);
                buf.WriteShort(length);
            }
            SNType type = message.getType();

            buf.WriteByte((byte)(int)type);

            switch (type)
            {
            case SNType.ADVERTISE:
                Advertise advertise = (Advertise)message;
                buf.WriteByte(advertise.gwID);
                buf.WriteShort(advertise.duration);
                break;

            case SNType.SEARCHGW:
                SearchGW searchGw = (SearchGW)message;
                buf.WriteByte((byte)(int)searchGw.Radius);
                break;

            case SNType.GWINFO:
                GWInfo gwInfo = (GWInfo)message;
                buf.WriteByte(gwInfo.gwID);
                if (gwInfo.gwAddress != null)
                {
                    buf.WriteBytes(Encoding.UTF8.GetBytes(gwInfo.gwAddress));
                }
                break;

            case SNType.CONNECT:
                SNConnect connect          = (SNConnect)message;
                byte      connectFlagsByte = Flags.encode(false, null, false, connect.WillPresent, connect.CleanSession, null);
                buf.WriteByte(connectFlagsByte);
                buf.WriteByte(connect.ProtocolID);
                buf.WriteShort(connect.Duration);
                buf.WriteBytes(Encoding.UTF8.GetBytes(connect.ClientID));
                break;

            case SNType.CONNACK:
            case SNType.WILL_TOPIC_RESP:
            case SNType.WILL_MSG_RESP:
                ResponseMessage responseMessage = (ResponseMessage)message;
                buf.WriteByte((byte)(int)responseMessage.ReturnCode);
                break;

            case SNType.WILL_TOPIC:
                WillTopic willTopic = (WillTopic)message;
                if (willTopic.Topic != null)
                {
                    byte willTopicFlagsByte = Flags.encode(false, willTopic.Topic.getQos(), willTopic.Retain, false, false, willTopic.Topic.getType());
                    buf.WriteByte(willTopicFlagsByte);
                    buf.WriteBytes(Encoding.UTF8.GetBytes(willTopic.Topic.Value));
                }
                break;

            case SNType.WILL_MSG:
                WillMsg willMsg = (WillMsg)message;
                buf.WriteBytes(willMsg.Content);
                break;

            case SNType.REGISTER:
                Register register = (Register)message;
                buf.WriteShort(register.topicID);
                buf.WriteShort(register.MessageID.Value);
                buf.WriteBytes(Encoding.UTF8.GetBytes(register.TopicName));
                break;

            case SNType.REGACK:
                Regack regack = (Regack)message;
                buf.WriteShort(regack.topicID);
                buf.WriteShort(regack.MessageID.Value);
                buf.WriteByte((byte)(int)regack.code);
                break;

            case SNType.PUBLISH:
                SNPublish publish          = (SNPublish)message;
                byte      publishFlagsByte = Flags.encode(publish.Dup, publish.SnTopic.getQos(), publish.Retain, false, false, publish.SnTopic.getType());
                buf.WriteByte(publishFlagsByte);
                buf.WriteBytes(publish.SnTopic.encode());
                if (publish.MessageID.HasValue)
                {
                    buf.WriteShort(publish.MessageID.Value);
                }
                else
                {
                    buf.WriteShort(0);
                }
                buf.WriteBytes(publish.Content);
                break;

            case SNType.PUBACK:
                SNPuback puback = (SNPuback)message;
                buf.WriteShort(puback.topicID);
                buf.WriteShort(puback.MessageID.Value);
                buf.WriteByte((byte)(int)puback.ReturnCode);
                break;

            case SNType.PUBREC:
            case SNType.PUBREL:
            case SNType.PUBCOMP:
            case SNType.UNSUBACK:
                CountableMessage contableMessage = (CountableMessage)message;
                buf.WriteShort(contableMessage.MessageID.Value);
                break;

            case SNType.SUBSCRIBE:
                SNSubscribe subscribe      = (SNSubscribe)message;
                byte        subscribeFlags = Flags.encode(subscribe.Dup, subscribe.SnTopic.getQos(), false, false, false, subscribe.SnTopic.getType());
                buf.WriteByte(subscribeFlags);
                buf.WriteShort(subscribe.MessageID.Value);
                buf.WriteBytes(subscribe.SnTopic.encode());
                break;

            case SNType.SUBACK:
                SNSuback suback     = (SNSuback)message;
                byte     subackByte = Flags.encode(false, suback.AllowedQos, false, false, false, null);
                buf.WriteByte(subackByte);
                buf.WriteShort(suback.topicID);
                buf.WriteShort(suback.MessageID.Value);
                buf.WriteByte((byte)(int)suback.ReturnCode);
                break;

            case SNType.UNSUBSCRIBE:
                SNUnsubscribe unsubscribe      = (SNUnsubscribe)message;
                byte          unsubscribeFlags = Flags.encode(false, null, false, false, false, unsubscribe.SnTopic.getType());
                buf.WriteByte(unsubscribeFlags);
                buf.WriteShort(unsubscribe.MessageID.Value);
                buf.WriteBytes(unsubscribe.SnTopic.encode());
                break;

            case SNType.PINGREQ:
                if (length > 2)
                {
                    SNPingreq pingreq = (SNPingreq)message;
                    buf.WriteBytes(Encoding.UTF8.GetBytes(pingreq.ClientID));
                }
                break;

            case SNType.DISCONNECT:
                if (length > 2)
                {
                    SNDisconnect disconnect = (SNDisconnect)message;
                    buf.WriteShort(disconnect.Duration);
                }
                break;

            case SNType.WILL_TOPIC_UPD:
                WillTopicUpd willTopicUpd = (WillTopicUpd)message;
                if (willTopicUpd.Topic != null)
                {
                    byte willTopicUpdByte = Flags.encode(false, willTopicUpd.Topic.getQos(), willTopicUpd.Retain, false, false, null);
                    buf.WriteByte(willTopicUpdByte);
                    buf.WriteBytes(Encoding.UTF8.GetBytes(willTopicUpd.Topic.Value));
                }
                break;

            case SNType.WILL_MSG_UPD:
                WillMsgUpd willMsgUpd = (WillMsgUpd)message;
                buf.WriteBytes(willMsgUpd.Content);
                break;

            case SNType.WILL_TOPIC_REQ:
            case SNType.WILL_MSG_REQ:
            case SNType.PINGRESP:
                break;

            case SNType.ENCAPSULATED:
                Encapsulated encapsulated = (Encapsulated)message;

                buf.WriteByte(Controls.encode(encapsulated.radius));
                buf.WriteBytes(Encoding.UTF8.GetBytes(encapsulated.wirelessNodeID));
                buf.WriteBytes(SNParser.encode(encapsulated.message));
                break;

            default:
                break;
            }

            if (type != SNType.ENCAPSULATED && message.getLength() != buf.ReadableBytes)
            {
                throw new MalformedMessageException("invalid message encoding: expected length-" + message.getLength() + ",actual-" + buf.ReadableBytes);
            }

            return(buf);
        }
        public static SNMessage decode(IByteBuffer buf)
        {
            SNMessage message       = null;
            int       currIndex     = buf.ReaderIndex;
            int       messageLength = decodeContentLength(buf);
            int       bytesLeft     = messageLength - (buf.ReaderIndex - currIndex);

            short  typeByte = (short)(0x00FF & (short)buf.ReadByte());
            SNType?type     = null;

            try
            {
                type = (SNType)(int)(typeByte);
            }
            catch (Exception)
            {
                throw new MalformedMessageException("invalid packet type encoding:" + typeByte);
            }

            bytesLeft--;

            switch (type.Value)
            {
            case SNType.ADVERTISE:
                int advertiseGwID     = 0x00FF & (short)buf.ReadByte();
                int advertiseDuration = buf.ReadUnsignedShort();
                message = new Advertise(advertiseGwID, advertiseDuration);
                break;

            case SNType.SEARCHGW:
                int radius = 0x00FF & (short)buf.ReadByte();
                message = new SearchGW((Radius)radius);
                break;

            case SNType.GWINFO:
                int gwInfoGwID = 0x00FF & (short)buf.ReadByte();
                bytesLeft--;
                String gwInfoGwAddress = null;
                if (bytesLeft > 0)
                {
                    byte[] gwInfoGwAddressBytes = new byte[bytesLeft];
                    buf.ReadBytes(gwInfoGwAddressBytes);
                    gwInfoGwAddress = Encoding.UTF8.GetString(gwInfoGwAddressBytes);
                }
                message = new GWInfo(gwInfoGwID, gwInfoGwAddress);
                break;

            case SNType.CONNECT:
                Flags connectFlags = Flags.decode(buf.ReadByte(), type.Value);
                bytesLeft--;
                int protocolID = buf.ReadByte();
                bytesLeft--;
                if (protocolID != SNConnect.MQTT_SN_PROTOCOL_ID)
                {
                    throw new MalformedMessageException("Invalid protocolID " + protocolID);
                }
                int connectDuration = buf.ReadUnsignedShort();
                bytesLeft -= 2;
                if (!ValuesValidator.canRead(buf, bytesLeft))
                {
                    throw new MalformedMessageException(type + ", clientID can't be empty");
                }
                byte[] connectClientIDBytes = new byte[bytesLeft];
                buf.ReadBytes(connectClientIDBytes);
                String connectClientID = Encoding.UTF8.GetString(connectClientIDBytes);
                message = new SNConnect(connectFlags.CleanSession, connectDuration, connectClientID, connectFlags.Will);
                break;

            case SNType.CONNACK:
                ReturnCode connackCode = (ReturnCode)(int)(buf.ReadByte());
                message = new SNConnack(connackCode);
                break;

            case SNType.WILL_TOPIC_REQ:
                message = WILL_TOPIC_REQ;
                break;

            case SNType.WILL_TOPIC:
                Boolean   willTopicRetain = false;
                FullTopic willTopic       = null;
                if (bytesLeft > 0)
                {
                    Flags willTopicFlags = Flags.decode(buf.ReadByte(), type.Value);
                    bytesLeft--;
                    willTopicRetain = willTopicFlags.Retain;
                    if (!ValuesValidator.canRead(buf, bytesLeft))
                    {
                        throw new MalformedMessageException(type + " invalid topic encoding");
                    }
                    byte[] willTopicBytes = new byte[bytesLeft];
                    buf.ReadBytes(willTopicBytes);
                    String willTopicValue = Encoding.UTF8.GetString(willTopicBytes);
                    willTopic = new FullTopic(willTopicValue, willTopicFlags.Qos.Value);
                }
                message = new WillTopic(willTopicRetain, willTopic);
                break;

            case SNType.WILL_MSG_REQ:
                message = WILL_MSG_REQ;
                break;

            case SNType.WILL_MSG:
                if (!ValuesValidator.canRead(buf, bytesLeft))
                {
                    throw new MalformedMessageException(type + " content must not be empty");
                }
                byte[] willMessageContent = new byte[bytesLeft];
                buf.ReadBytes(willMessageContent);
                message = new WillMsg(willMessageContent);
                break;

            case SNType.REGISTER:
                int registerTopicID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateRegistrationTopicID(registerTopicID))
                {
                    throw new MalformedMessageException(type + " invalid topicID value " + registerTopicID);
                }
                bytesLeft -= 2;
                int registerMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(registerMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + registerMessageID);
                }
                bytesLeft -= 2;
                if (!ValuesValidator.canRead(buf, bytesLeft))
                {
                    throw new MalformedMessageException(type + " must contain a valid topic");
                }
                byte[] registerTopicBytes = new byte[bytesLeft];
                buf.ReadBytes(registerTopicBytes);
                String registerTopicName = Encoding.UTF8.GetString(registerTopicBytes);
                message = new Register(registerTopicID, registerMessageID, registerTopicName);
                break;

            case SNType.REGACK:
                int regackTopicID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateRegistrationTopicID(regackTopicID))
                {
                    throw new MalformedMessageException(type + " invalid topicID value " + regackTopicID);
                }
                int regackMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(regackMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + regackMessageID);
                }
                ReturnCode regackCode = (ReturnCode)(int)(buf.ReadByte());
                message = new Regack(regackTopicID, regackMessageID, regackCode);
                break;

            case SNType.PUBLISH:
                Flags publishFlags = Flags.decode(buf.ReadByte(), type.Value);
                bytesLeft--;
                int publishTopicID = buf.ReadUnsignedShort();
                bytesLeft -= 2;
                int publishMessageID = buf.ReadUnsignedShort();
                bytesLeft -= 2;
                if (publishFlags.Qos.Value != SNQoS.AT_MOST_ONCE && publishMessageID == 0)
                {
                    throw new MalformedMessageException("invalid PUBLISH QoS-0 messageID:" + publishMessageID);
                }
                SNTopic publishTopic = null;
                if (publishFlags.TopicType == TopicType.SHORT)
                {
                    publishTopic = new ShortTopic(publishTopicID.ToString(), publishFlags.Qos.Value);
                }
                else
                {
                    if (!ValuesValidator.validateTopicID(publishTopicID))
                    {
                        throw new MalformedMessageException(type + " invalid topicID value " + publishTopicID);
                    }
                    publishTopic = new IdentifierTopic(publishTopicID, publishFlags.Qos.Value);
                }

                byte[] data = new byte[bytesLeft];
                if (bytesLeft > 0)
                {
                    buf.ReadBytes(data);
                }

                message = new SNPublish(publishMessageID, publishTopic, data, publishFlags.Dup, publishFlags.Retain);
                break;

            case SNType.PUBACK:
                int pubackTopicID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateTopicID(pubackTopicID))
                {
                    throw new MalformedMessageException(type + " invalid topicID value " + pubackTopicID);
                }
                int pubackMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(pubackMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + pubackMessageID);
                }
                ReturnCode pubackCode = (ReturnCode)(int)(buf.ReadByte());
                message = new SNPuback(pubackTopicID, pubackMessageID, pubackCode);
                break;

            case SNType.PUBREC:
                int pubrecMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(pubrecMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + pubrecMessageID);
                }
                message = new SNPubrec(pubrecMessageID);
                break;

            case SNType.PUBREL:
                int pubrelMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(pubrelMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + pubrelMessageID);
                }
                message = new SNPubrel(pubrelMessageID);
                break;

            case SNType.PUBCOMP:
                int pubcompMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(pubcompMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + pubcompMessageID);
                }
                message = new SNPubcomp(pubcompMessageID);
                break;

            case SNType.SUBSCRIBE:
                Flags subscribeFlags = Flags.decode(buf.ReadByte(), type.Value);
                bytesLeft--;
                int subscribeMessageID = buf.ReadUnsignedShort();
                if (subscribeMessageID == 0)
                {
                    throw new MalformedMessageException(type + " invalid messageID " + subscribeMessageID);
                }
                bytesLeft -= 2;
                if (!ValuesValidator.canRead(buf, bytesLeft) || bytesLeft < 2)
                {
                    throw new MalformedMessageException(type + " invalid topic encoding");
                }
                byte[] subscribeTopicBytes = new byte[bytesLeft];
                buf.ReadBytes(subscribeTopicBytes);
                SNTopic subscribeTopic = null;
                switch (subscribeFlags.TopicType)
                {
                case TopicType.NAMED:
                    String subscribeTopicName = Encoding.UTF8.GetString(subscribeTopicBytes);
                    subscribeTopic = new FullTopic(subscribeTopicName, subscribeFlags.Qos.Value);
                    break;

                case TopicType.ID:
                    int subscribeTopicID = BitConverter.ToInt16(subscribeTopicBytes, 0);
                    if (!ValuesValidator.validateTopicID(subscribeTopicID))
                    {
                        throw new MalformedMessageException(type + " invalid topicID value " + subscribeTopicID);
                    }
                    subscribeTopic = new IdentifierTopic(subscribeTopicID, subscribeFlags.Qos.Value);
                    break;

                case TopicType.SHORT:
                    String subscribeTopicShortName = Encoding.UTF8.GetString(subscribeTopicBytes);
                    subscribeTopic = new ShortTopic(subscribeTopicShortName, subscribeFlags.Qos.Value);
                    break;
                }
                message = new SNSubscribe(subscribeMessageID, subscribeTopic, subscribeFlags.Dup);
                break;

            case SNType.SUBACK:
                Flags subackFlags   = Flags.decode(buf.ReadByte(), type.Value);
                int   subackTopicID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateRegistrationTopicID(subackTopicID))
                {
                    throw new MalformedMessageException(type + " invalid topicID value " + subackTopicID);
                }
                int subackMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(subackMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + subackMessageID);
                }
                ReturnCode subackCode = (ReturnCode)(int)(buf.ReadByte());
                message = new SNSuback(subackTopicID, subackMessageID, subackCode, subackFlags.Qos.Value);
                break;

            case SNType.UNSUBSCRIBE:
                Flags unsubscribeFlags = Flags.decode(buf.ReadByte(), type.Value);
                bytesLeft--;
                int unsubscribeMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(unsubscribeMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + unsubscribeMessageID);
                }
                bytesLeft -= 2;
                byte[] unsubscribeTopicBytes = new byte[bytesLeft];
                buf.ReadBytes(unsubscribeTopicBytes);
                SNTopic unsubscribeTopic = null;
                switch (unsubscribeFlags.TopicType)
                {
                case TopicType.NAMED:
                    String unsubscribeTopicName = Encoding.UTF8.GetString(unsubscribeTopicBytes);
                    unsubscribeTopic = new FullTopic(unsubscribeTopicName, unsubscribeFlags.Qos.Value);
                    break;

                case TopicType.ID:
                    int unsubscribeTopicID = BitConverter.ToInt16(unsubscribeTopicBytes, 0);
                    if (!ValuesValidator.validateTopicID(unsubscribeTopicID))
                    {
                        throw new MalformedMessageException(type + " invalid topicID value " + unsubscribeTopicID);
                    }
                    unsubscribeTopic = new IdentifierTopic(unsubscribeTopicID, unsubscribeFlags.Qos.Value);
                    break;

                case TopicType.SHORT:
                    String unsubscribeTopicShortName = Encoding.UTF8.GetString(unsubscribeTopicBytes);
                    unsubscribeTopic = new ShortTopic(unsubscribeTopicShortName, unsubscribeFlags.Qos.Value);
                    break;
                }
                message = new SNUnsubscribe(unsubscribeMessageID, unsubscribeTopic);
                break;

            case SNType.UNSUBACK:
                int unsubackMessageID = buf.ReadUnsignedShort();
                if (!ValuesValidator.validateMessageID(unsubackMessageID))
                {
                    throw new MalformedMessageException(type + " invalid messageID " + unsubackMessageID);
                }
                message = new SNUnsuback(unsubackMessageID);
                break;

            case SNType.PINGREQ:
                String pingreqClientID = null;
                if (bytesLeft > 0)
                {
                    byte[] pingreqClientIDValue = new byte[bytesLeft];
                    buf.ReadBytes(pingreqClientIDValue);
                    pingreqClientID = Encoding.UTF8.GetString(pingreqClientIDValue);
                }
                message = new SNPingreq(pingreqClientID);
                break;

            case SNType.PINGRESP:
                message = PING_RESP;
                break;

            case SNType.DISCONNECT:
                int duration = 0;
                if (bytesLeft > 0)
                {
                    duration = buf.ReadUnsignedShort();
                }
                message = new SNDisconnect(duration);
                break;

            case SNType.WILL_TOPIC_UPD:
                FullTopic willTopicUpdTopic     = null;
                Boolean   willTopicUpdateRetain = false;
                if (bytesLeft > 0)
                {
                    Flags willTopicUpdFlags = Flags.decode(buf.ReadByte(), type.Value);
                    willTopicUpdateRetain = willTopicUpdFlags.Retain;
                    bytesLeft--;
                    byte[] willTopicUpdTopicBytes = new byte[bytesLeft];
                    buf.ReadBytes(willTopicUpdTopicBytes);
                    String willTopicUpdTopicValue = Encoding.UTF8.GetString(willTopicUpdTopicBytes);
                    willTopicUpdTopic = new FullTopic(willTopicUpdTopicValue, willTopicUpdFlags.Qos.Value);
                }
                message = new WillTopicUpd(willTopicUpdateRetain, willTopicUpdTopic);
                break;

            case SNType.WILL_MSG_UPD:
                if (!ValuesValidator.canRead(buf, bytesLeft))
                {
                    throw new MalformedMessageException(type + " must contain content data");
                }
                byte[] willMsgUpdContent = new byte[bytesLeft];
                buf.ReadBytes(willMsgUpdContent);
                message = new WillMsgUpd(willMsgUpdContent);
                break;

            case SNType.WILL_TOPIC_RESP:
                ReturnCode willTopicRespCode = (ReturnCode)(int)buf.ReadByte();
                message = new WillTopicResp(willTopicRespCode);
                break;

            case SNType.WILL_MSG_RESP:
                ReturnCode willMsgRespCode = (ReturnCode)(int)buf.ReadByte();
                message = new WillMsgResp(willMsgRespCode);
                break;

            case SNType.ENCAPSULATED:
                Controls control = Controls.decode(buf.ReadByte());
                bytesLeft--;
                byte[] wirelessNodeIDBytes = new byte[bytesLeft];
                buf.ReadBytes(wirelessNodeIDBytes);
                String    wirelessNodeID      = Encoding.UTF8.GetString(wirelessNodeIDBytes);
                SNMessage encapsulatedMessage = SNParser.decode(buf);
                message = new Encapsulated(control.Radius, wirelessNodeID, encapsulatedMessage);
                break;
            }

            if (buf.IsReadable())
            {
                throw new MalformedMessageException("not all bytes have been read from buffer:" + buf.ReadableBytes);
            }

            if (messageLength != message.getLength())
            {
                throw new MalformedMessageException(String.Format("Invalid length. Encoded: %d, actual: %d", messageLength, message.getLength()));
            }

            return(message);
        }