	public Result decode(IoBuffer in, Charset charset) {
		log.trace("IoBuffer remaining bytes: {}, current position: {}",
				in.remaining(), in.position());
		if (in.remaining() >= ProtocolLengths.HEAD) {
			boolean sessionKeyDecoded = false;
			long cmdBodySize = 0;
			int start = in.position();
			log.trace("Parsing message header ({} bytes)..",
					ProtocolLengths.HEAD);
			PackageHeadCodec headCodec = new PackageHeadCodec();
			HeadMessage header = headCodec.decode(in);	
			log.trace("- message header: {}", header);
			log.trace("- IoBuffer remaining (for body): {}", in.remaining());
			cmdBodySize = header.getMessageSize() - ProtocolLengths.HEAD; 
			if ((header.getFlags() & HeadMessage.FLAG_SESSION_ID) != 0) {
				log.debug("flag FLAG_SESSION_ID is set in header");
				log.debug("in.remaining() = {}", in.remaining());
				if (in.remaining() >= 4) {
					short sessionKeyVersion = in.getUnsigned();
					in.getUnsigned();
					int sessionKeyLength = in.getUnsignedShort();
					log.debug("sessionKeyVersion={}", sessionKeyVersion);
					log.debug("sessionKeyLength={}", sessionKeyLength);
					cmdBodySize -= (sessionKeyLength + 4);
					if (sessionKeyVersion == 0 && sessionKeyLength == 0) {
						log.debug("version 0 session key detected, client can process session ID!");
					} else if (in.remaining() < sessionKeyLength) {
						long owe = header.getMessageSize()
								- ProtocolLengths.HEAD - 4 - in.remaining();
						in.position(start);	
						return new Result(true, owe, header, null);
					} else {
						try {
							log.debug(
									"decoding session key (version: {}, key length: {})",
									sessionKeyVersion, sessionKeyLength);
							in.position(start + 3);
							ISessionKey sessionKey = sessionKeyCodec.decode(in);
							header.setSessionKey(sessionKey);
							log.debug("session key decoded: {}", sessionKey);
							sessionKeyDecoded = true;
						} catch (CodecException e) {
							log.warn(
									"error when decoding session key (version: {}, key length: {})",
									new Object[] { sessionKeyVersion,
											sessionKeyLength }, e);
						}
					}
				} else {
					long owe = header.getMessageSize()
							- ProtocolLengths.HEAD - in.remaining();
					in.position(start);	
					return new Result(true, owe, header, null);
				}
			}
			log.debug("before parsing body: cmdBodySize={}, in.remaining={}", cmdBodySize, in.remaining());
			if (in.remaining() < cmdBodySize) {
				long owe = cmdBodySize - in.remaining();
				log.trace("More bytes are required to parse the complete message (still need {} bytes)",
						owe);
				in.position(start);	
				return new Result(true, owe, header, null);
			}
			int calculatedChecksum = 0;
			{
				in.position(start);
				byte[] byteTmp = new byte[(int)header.getMessageSize()];	
				in.get(byteTmp);
				Tools.putUnsignedShort(byteTmp, 0, 10);
				calculatedChecksum = Tools.checkSum(byteTmp, byteTmp.length);
			}
			if (calculatedChecksum != header.getChecksum()) {
				ErrorBodyMessage bodyMessage = new ErrorBodyMessage();
				bodyMessage.setErrorCode(CmdConstant.ERROR_CHECKSUM_CODE);
				Message message = new Message(header, bodyMessage);
				log.trace(
						"Received message has invalid checksum (calc: 0x{}, actual: 0x{})",
						Integer.toHexString(calculatedChecksum),
						Integer.toHexString(header.getChecksum()));
				return new Result(false, 0, header, message);
			}
			in.position(start + ProtocolLengths.HEAD);
			ICommand bodyMessage = null;
			try {
				bodyMessage = this.decodeMessageBody(in, charset, header);
				Message message = new Message(header, bodyMessage);
				return new Result(false, 0, header, message);
			} catch (Throwable e) {
				log.trace("Unexpected error when decoding message", e);
				ErrorBodyMessage errorBodyMessage = new ErrorBodyMessage();
				errorBodyMessage.setErrorCode(CmdConstant.ERROR_MESSAGE_CODE);
				Message message = new Message(header, errorBodyMessage);
				return new Result(false, 0, header, message);
			}
		} else {
			long owe = ProtocolLengths.HEAD - in.remaining();
			log.trace("More bytes are required to parse the header message (still need {} bytes)",
					owe);
			return new Result(true, owe, null, null);
		}
	}

	public Result decode(IoBuffer in, Charset charset) {
		log.trace("IoBuffer remaining bytes: {}, current position: {}",
				in.remaining(), in.position());
		if (in.remaining() >= ProtocolLengths.HEAD) {
			boolean sessionKeyDecoded = false;
			long cmdBodySize = 0;
			int start = in.position();
			log.trace("Parsing message header ({} bytes)..",
					ProtocolLengths.HEAD);
			PackageHeadCodec headCodec = new PackageHeadCodec();
			HeadMessage header = headCodec.decode(in);	
			log.trace("- message header: {}", header);
			log.trace("- IoBuffer remaining (for body): {}", in.remaining());
			cmdBodySize = header.getMessageSize() - ProtocolLengths.HEAD; 
			if ((header.getFlags() & HeadMessage.FLAG_SESSION_ID) != 0) {
				log.debug("flag FLAG_SESSION_ID is set in header");
				log.debug("in.remaining() = {}", in.remaining());
				if (in.remaining() >= 4) {
					short sessionKeyVersion = in.getUnsigned();
					in.getUnsigned();
					int sessionKeyLength = in.getUnsignedShort();
					log.debug("sessionKeyVersion={}", sessionKeyVersion);
					log.debug("sessionKeyLength={}", sessionKeyLength);
					cmdBodySize -= (sessionKeyLength + 4);
					if (sessionKeyVersion == 0 && sessionKeyLength == 0) {
						log.debug("version 0 session key detected, client can process session ID!");
					} else if (in.remaining() < sessionKeyLength) {
						long owe = header.getMessageSize()
								- ProtocolLengths.HEAD - 4 - in.remaining();
						in.position(start);	
						return new Result(true, owe, header, null);
					} else {
						try {
							log.debug(
									"decoding session key (version: {}, key length: {})",
									sessionKeyVersion, sessionKeyLength);
							in.position(start + 3);
							ISessionKey sessionKey = sessionKeyCodec.decode(in);
							header.setSessionKey(sessionKey);
							log.debug("session key decoded: {}", sessionKey);
							sessionKeyDecoded = true;
						} catch (CodecException e) {
							log.warn(
									"error when decoding session key (version: {}, key length: {})",
									new Object[] { sessionKeyVersion,
											sessionKeyLength }, e);
						}
					}
				} else {
					long owe = header.getMessageSize()
							- ProtocolLengths.HEAD - in.remaining();
					in.position(start);	
					return new Result(true, owe, header, null);
				}
			}
			log.debug("before parsing body: cmdBodySize={}, in.remaining={}", cmdBodySize, in.remaining());
			if (in.remaining() < cmdBodySize) {
				long owe = cmdBodySize - in.remaining();
				log.trace("More bytes are required to parse the complete message (still need {} bytes)",
						owe);
				in.position(start);	
				return new Result(true, owe, header, null);
			}
			int calculatedChecksum = 0;
			{
				in.position(start);
				byte[] byteTmp = new byte[(int)header.getMessageSize()];	
				in.get(byteTmp);
				Tools.putUnsignedShort(byteTmp, 0, 10);
				calculatedChecksum = Tools.checkSum(byteTmp, byteTmp.length);
			}
			if (calculatedChecksum != header.getChecksum()) {
				ErrorBodyMessage bodyMessage = new ErrorBodyMessage();
				bodyMessage.setErrorCode(CmdConstant.ERROR_CHECKSUM_CODE);
				Message message = new Message(header, bodyMessage);
				log.trace(
						"Received message has invalid checksum (calc: 0x{}, actual: 0x{})",
						Integer.toHexString(calculatedChecksum),
						Integer.toHexString(header.getChecksum()));
				return new Result(false, 0, header, message);
			}
			in.position(start
					+ ((int) header.getMessageSize() - (int) cmdBodySize));
			ICommand bodyMessage = null;
			try {
				bodyMessage = this.decodeMessageBody(in, charset, header);
				Message message = new Message(header, bodyMessage);
				return new Result(false, 0, header, message);
			} catch (Throwable e) {
				log.trace("Unexpected error when decoding message", e);
				ErrorBodyMessage errorBodyMessage = new ErrorBodyMessage();
				errorBodyMessage.setErrorCode(CmdConstant.ERROR_MESSAGE_CODE);
				Message message = new Message(header, errorBodyMessage);
				return new Result(false, 0, header, message);
			}
		} else {
			long owe = ProtocolLengths.HEAD - in.remaining();
			log.trace("More bytes are required to parse the header message (still need {} bytes)",
					owe);
			return new Result(true, owe, null, null);
		}
	}
