public enum ModbusRegisterType {
COIL, DISCRETE_INPUT, HOLDING, INPUT
} public class Utils {
    public static int crc(ByteBuf byteBuf, int start, int numBytes) {
        int polynomial = 0xA001;
        int crc = 0xFFFF;
        for (int i = start; i < start + numBytes; i++) {
            byte b = byteBuf.getByte(i);
            int low = (crc & 0xFF) ^ (b & 0xFF);
            crc &= (0xFF00);
            crc |= (low);
            for (int j = 0; j < 8; j++) {
                int t = crc & 0x01;
                crc >>= 1;
                if (t == 0) {
                    continue;
                }
                crc ^= polynomial;
            }
        }
        int first = (crc & 0xFF) << 8;
        crc = (crc & 0xFF00) >> 8 | first;
        return crc;
    }
} public class MF {

    public static ModbusFunction readRegisters(ModbusRegisterType registerType, int startingAddress, int quantity) {
        ReadRegistersFunction function;
        switch (registerType) {
        case COIL:
        case DISCRETE_INPUT:
            function = new ReadBooleanRegistersFunction();
            break;
        case HOLDING:
        case INPUT:
            function = new ReadWordRegistersFunction();
            break;
        default:
            throw new IllegalStateException();
        }
        function.setRegisterType(registerType);
        function.setStartingAddress(startingAddress);
        function.setQuantity(quantity);
        return function;
    }

    public static ModbusFunction writeRegister(int address, int unsignedShortValue) {
        WriteHoldingFunction result = new WriteHoldingFunction();
        result.setAddress(address);
        result.setValue(unsignedShortValue);
        return result;
    }

    public static ModbusFunction writeRegister(int address, boolean booleanValue) {
        WriteCoilFunction result = new WriteCoilFunction();
        result.setAddress(address);
        result.setValue(booleanValue);
        return result;
    }
} public class ExceptionResponse extends ModbusResponse {
    private int functionCode;
    private int exceptionCode;

    public ExceptionResponse(int transectionId, int unitId, int functionCode, int exceptionCode) {
        super(transectionId, unitId);
        setFunctionCode(functionCode);
        this.exceptionCode = exceptionCode;
    }

    public ExceptionResponse() {

    }

    @Override
    public int getFunctionCode() {
        return functionCode;
    }

    public void setFunctionCode(int functionCode) {
        if ((functionCode & 0x80) == 0) {
            throw new IllegalArgumentException();
        }
        this.functionCode = functionCode;
    }

    public int getExceptionCode() {
        return exceptionCode;
    }

    public void setExceptionCode(int exceptionCode) {
        this.exceptionCode = exceptionCode;
    }

    @Override
    public void writePduCore(ByteBuf out) {
        out.writeByte(exceptionCode);
    }

    @Override
    public int getPduCoreLength() {
        return 1;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + exceptionCode;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        ExceptionResponse other = (ExceptionResponse) obj;
        if (exceptionCode != other.exceptionCode)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "ExceptionResponse [functionCode=" + functionCode + ", exceptionCode=" + exceptionCode + "]";
    }

} public class MissMatchResponse extends ModbusResponse {

    public MissMatchResponse() {
    };

    public MissMatchResponse(int transectionId) {
        setTransectionId(transectionId);
    }

    @Override
    public void writePduCore(ByteBuf out) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getPduCoreLength() {
        throw new UnsupportedOperationException();
    }

    @Override
    public int getFunctionCode() {
        return 0x80;
    }

    @Override
    public String toString() {
        return "MissMatchResponse [transectionId=" + transectionId + ", unitId=" + unitId + "]";
    }

} public class ModbusRequest {
    private ModbusFunction function;
    private int transectionId;
    private int unitId;

    public ModbusFunction getFunction() {
        return function;
    }

    public void setFunction(ModbusFunction function) {
        this.function = function;
    }

    public int getTransectionId() {
        return transectionId;
    }

    public void setTransectionId(int transectionId) {
        this.transectionId = transectionId;
    }

    public int getUnitId() {
        return unitId;
    }

    public void setUnitId(int unitId) {
        this.unitId = unitId;
    }

    public ModbusRequest(int transectionId, int unitId, ModbusFunction function) {
        this.transectionId = transectionId;
        this.unitId = unitId;
        this.function = function;
    }

    public ModbusRequest() {
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((function == null) ? 0 : function.hashCode());
        result = prime * result + transectionId;
        result = prime * result + unitId;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ModbusRequest other = (ModbusRequest) obj;
        if (function == null) {
            if (other.function != null)
                return false;
        } else if (!function.equals(other.function))
            return false;
        if (transectionId != other.transectionId)
            return false;
        if (unitId != other.unitId)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "ModbusRequest [transectionId=" + transectionId + ", unitId=" + unitId + ", function=" + function + "]";
    }

} public abstract class ModbusResponse {
    protected int transectionId;
    protected int unitId;

    public ModbusResponse(int transectionId, int unitId) {
        this.transectionId = transectionId;
        this.unitId = unitId;
    }

    public ModbusResponse() {
    }

    public int getTransectionId() {
        return transectionId;
    }

    public void setTransectionId(int transectionId) {
        this.transectionId = transectionId;
    }

    public int getUnitId() {
        return unitId;
    }

    public void setUnitId(int unitId) {
        this.unitId = unitId;
    }

    public abstract int getFunctionCode();

    public void encode(ByteBuf out) {
        out.writeShort(transectionId);
        out.writeShort(0);
        out.writeShort(getPduCoreLength() + 2);
        out.writeByte(unitId);
        out.writeByte(getFunctionCode());
        writePduCore(out);
    }

    public abstract void writePduCore(ByteBuf out);

    public abstract int getPduCoreLength();

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + getFunctionCode();
        result = prime * result + transectionId;
        result = prime * result + unitId;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ModbusResponse other = (ModbusResponse) obj;
        if (getFunctionCode() != other.getFunctionCode())
            return false;
        if (transectionId != other.transectionId)
            return false;
        if (unitId != other.unitId)
            return false;
        return true;
    }

} public class ReadBooleanRegistersResponse extends ReadRegistersResponse {

    private TByteArrayList values;

    public ReadBooleanRegistersResponse() {
    }

    public ReadBooleanRegistersResponse(int transectionId, int unitId, ModbusRegisterType registerType,
            int startingAddress, boolean[] values) {
        super(transectionId, unitId, registerType, startingAddress);
        this.values = new TByteArrayList(values.length);
        quantity = values.length;
        for (boolean v : values) {
            this.values.add((byte) (v ? 1 : 0));
        }
    }

    @Override
    public void setQuantityAndAllocate(int quantity) {
        this.quantity = quantity;
        if (values == null) {
            values = new TByteArrayList(quantity);
        } else {
            values.clear();
            values.ensureCapacity(quantity);
        }
        values.fill(0, quantity, (byte) 0);
    }

    public boolean getValue(int index) {
        return values.get(index) != 0;
    }

    public void setValue(int index, boolean value) {
        values.set(index, value ? (byte) 1 : (byte) 0);
    }

    @Override
    public void writePduCore(ByteBuf out) {
        out.writeByte(getPduCoreLength() - 1);
        int mask = 1;
        int dataByte = 0;
        for (int i = 0; i < quantity; i++) {
            if (getValue(i)) {
                dataByte |= mask;
            }
            mask <<= 1;
            if (mask == 0x100) {
                out.writeByte(dataByte);
                mask = 1;
                dataByte = 0;
            }
        }
        if (mask != 1) {
            out.writeByte(dataByte);
        }

    }

    @Override
    public int getPduCoreLength() {
        return 1 + (7 + quantity) / 8;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((values == null) ? 0 : values.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        ReadBooleanRegistersResponse other = (ReadBooleanRegistersResponse) obj;
        if (values == null) {
            if (other.values != null)
                return false;
        } else if (!values.equals(other.values))
            return false;
        return true;
    }

    @Override
    protected void checkRegisterType(ModbusRegisterType registerType) {
        if (registerType != ModbusRegisterType.COIL && registerType != ModbusRegisterType.DISCRETE_INPUT) {
            throw new IllegalArgumentException();
        }

    }

    @Override
    public String toString() {
        return "ReadBooleanRegistersResponse [transectionId=" + transectionId + ", unitId=" + unitId + ", registerType="
                + registerType + ", startingAddress=" + startingAddress + ", quantity=" + quantity + ", values="
                + values + "]";
    }

} public class ReadWordRegistersResponse extends ReadRegistersResponse {
    private TShortArrayList values;

    public ReadWordRegistersResponse() {
    }

    public ReadWordRegistersResponse(int transectionId, int unitId, ModbusRegisterType registerType,
            int startingAddress, int[] values) {
        super(transectionId, unitId, registerType, startingAddress);
        this.quantity = values.length;
        this.values = new TShortArrayList(values.length);
        for (int v : values) {
            this.values.add((short) v);
        }
    }

    @Override
    public void setQuantityAndAllocate(int quantity) {
        if (null == values) {
            values = new TShortArrayList(quantity);
        } else {
            values.clear();
            values.ensureCapacity(quantity);
        }
        this.quantity = quantity;
        values.fill(0, quantity, (short) 0);
    }

    public void setValue(int index, int value) {
        values.set(index, (short) value);
    }

    public int getValue(int offset) {
        return values.get(offset) & 0xFFFF;
    }

    @Override
    public void writePduCore(ByteBuf out) {
        out.writeByte(2 * quantity);
        for (int i = 0; i < quantity; i++) {
            out.writeShort(getValue(i));
        }
    }

    @Override
    public int getPduCoreLength() {
        return 1 + 2 * quantity;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((values == null) ? 0 : values.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        ReadWordRegistersResponse other = (ReadWordRegistersResponse) obj;
        if (values == null) {
            if (other.values != null)
                return false;
        } else if (!values.equals(other.values))
            return false;
        return true;
    }

    @Override
    protected void checkRegisterType(ModbusRegisterType registerType) {
        if (registerType != ModbusRegisterType.INPUT && registerType != ModbusRegisterType.HOLDING) {
            throw new IllegalArgumentException();
        }

    }

    @Override
    public String toString() {
        return "ReadWordRegistersResponse [transectionId=" + transectionId + ", unitId=" + unitId + ", registerType="
                + registerType + ", startingAddress=" + startingAddress + ", quantity=" + quantity + ", values="
                + values + "]";
    }

} public class WriteCoilResponse extends ModbusResponse {

    private boolean value;
    private int address;

    public WriteCoilResponse(int transectionId, int unitId, int address, boolean value) {
        super(transectionId, unitId);
        this.address = address;
        this.value = value;
    }

    public WriteCoilResponse() {
    }

    public boolean getValue() {
        return value;
    }

    public void setValue(boolean value) {
        this.value = value;
    }

    public int getAddress() {
        return address;
    }

    public void setAddress(int address) {
        this.address = address;
    }

    @Override
    public void writePduCore(ByteBuf out) {
        out.writeShort(address);
        out.writeShort(value ? 0xFF00 : 0x0000);

    }

    @Override
    public int getPduCoreLength() {
        return 4;
    }

    @Override
    public int getFunctionCode() {
        return 0x05;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + address;
        result = prime * result + (value ? 1231 : 1237);
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        WriteCoilResponse other = (WriteCoilResponse) obj;
        if (address != other.address)
            return false;
        if (value != other.value)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "WriteCoilResponse [transectionId=" + transectionId + ", unitId=" + unitId + ", address=" + address
                + ", value=" + value + "]";
    }

} public class WriteHoldingResponse extends ModbusResponse {

    private int address;
    private int value;

    public WriteHoldingResponse(int transectionId, int unitId, int address, int value) {
        super(transectionId, unitId);
        this.address = address;
        this.value = value;
    }

    public WriteHoldingResponse() {
    };

    @Override
    public void writePduCore(ByteBuf out) {
        out.writeShort(address);
        out.writeShort(value);
    }

    @Override
    public int getPduCoreLength() {
        return 4;
    }

    @Override
    public int getFunctionCode() {
        return 0x06;
    }

    public int getAddress() {
        return address;
    }

    public void setAddress(int address) {
        this.address = address;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + address;
        result = prime * result + value;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        WriteHoldingResponse other = (WriteHoldingResponse) obj;
        if (address != other.address)
            return false;
        if (value != other.value)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return "WriteHoldingResponse [transectionId=" + transectionId + ", unitId=" + unitId + ", address=" + address
                + ", value=" + value + "]";
    }

}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import org.junit.Test;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import net.epsilony.utils.codec.modbus.ModbusRegisterType;
import net.epsilony.utils.codec.modbus.Utils;
import net.epsilony.utils.codec.modbus.func.MF;
import net.epsilony.utils.codec.modbus.reqres.ExceptionResponse;
import net.epsilony.utils.codec.modbus.reqres.MissMatchResponse;
import net.epsilony.utils.codec.modbus.reqres.ModbusRequest;
import net.epsilony.utils.codec.modbus.reqres.ModbusResponse;
import net.epsilony.utils.codec.modbus.reqres.ReadBooleanRegistersResponse;
import net.epsilony.utils.codec.modbus.reqres.ReadWordRegistersResponse;
import net.epsilony.utils.codec.modbus.reqres.WriteCoilResponse;
import net.epsilony.utils.codec.modbus.reqres.WriteHoldingResponse;
        }

        public void writeBufferWithCrc(ByteBuf buf) {
            int from = buf.writerIndex();
            writeBuffer(buf);
            buf.writeShort(Utils.crc(buf, from, buf.writerIndex() - from));
        }

    }

    public static ByteBuf createBuf() {
        ByteBuf buf = Unpooled.buffer(1024);
        int randShift = new Random().nextInt(10) + 7;
        buf.writerIndex(randShift);
        buf.readerIndex(randShift);
        return buf;
    }

    @Test
    public void testDecoding() {
        EmbeddedChannel channel = new EmbeddedChannel(new ModbusMasterResponseDecoder(requests::remove));
        TestData[] datas = initTestData();

        ByteBuf buf = createBuf();

        for (TestData data : datas) {
            data.writeBuffer(buf);
        }

        channel.writeInbound(buf);

        for (TestData data : datas) {
            Object decoded = channel.readInbound();
            assertEquals(data.response, decoded);
        }
        assertEquals(null, channel.readInbound());
        assertTrue(buf.refCnt() <= 0);
        assertTrue(requests.isEmpty());
    }

    @Test
    public void testDecodingWithCheckSome() {
        ModbusMasterResponseDecoder decoder = new ModbusMasterResponseDecoder(requests::remove);
        decoder.setWithCheckSum(true);
        EmbeddedChannel channel = new EmbeddedChannel(decoder);
        TestData[] datas = initTestData();

        ByteBuf buf = createBuf();

        for (TestData data : datas) {
            data.writeBufferWithCrc(buf);
        }

        channel.writeInbound(buf);

        for (TestData data : datas) {
            Object decoded = channel.readInbound();
            assertEquals(data.response, decoded);
        }
        assertEquals(null, channel.readInbound());
        assertTrue(buf.refCnt() <= 0);
        assertTrue(requests.isEmpty());
    }

    private TestData[] initTestData() {
        return new TestData[] {
                new TestData(new ModbusRequest(0xAC01, 0xFB, MF.writeRegister(0xFEDC, 0xDAFE)),
                        new WriteHoldingResponse(0xAC01, 0xFB, 0xFEDC, 0xDAFE),
                        new int[] { 0xAC, 0x01, 0x00, 0x00, 0x00, 0x06, 0xFB, 0x06, 0xFE, 0xDC, 0xDA, 0xFE }),
                new TestData(new ModbusRequest(0xAC02, 0xFB, MF.writeRegister(0xFEDC, false)),
                        new WriteCoilResponse(0xAC02, 0xFB, 0xFEDC, false),
                        new int[] { 0xAC, 0x02, 0x00, 0x00, 0x00, 0x06, 0xFB, 0x05, 0xFE, 0xDC, 0x00, 0x00 }),
                new TestData(new ModbusRequest(0xAC03, 0xFB, MF.writeRegister(0xFEDC, true)),
                        new WriteCoilResponse(0xAC03, 0xFB, 0xFEDC, true),
                        new int[] { 0xAC, 0x03, 0x00, 0x00, 0x00, 0x06, 0xFB, 0x05, 0xFE, 0xDC, 0xff, 0x00 }),
                new TestData(new ModbusRequest(0xAB01, 0xFB, MF.readRegisters(ModbusRegisterType.INPUT, 0xFEDC, 3)),
                        new ReadWordRegistersResponse(0xAB01, 0xFB, ModbusRegisterType.INPUT, 0xFEDC,
                                new int[] { 0xFF01, 0xFF02, 0xFF03 }),
                        new int[] {
                                0xAB,
                                0x01,
                                0x00,
                                0x00,
                                0x00,
                                0x09,
                                0xFB,
                                0x04,
                                0x06,
                                0xff,
                                0x01,
                                0xff,
                                0x02,
                                0xff,
                                0x03 }),
                new TestData(new ModbusRequest(0xAB02, 0xFB, MF.readRegisters(ModbusRegisterType.HOLDING, 0xFEDC, 3)),
                        new ReadWordRegistersResponse(0xAB02, 0xFB, ModbusRegisterType.HOLDING, 0xFEDC,
                                new int[] { 0xFF01, 0xFF02, 0xFF03 }),
                        new int[] {
                                0xAB,
                                0x02,
                                0x00,
                                0x00,
                                0x00,
                                0x09,
                                0xFB,
                                0x03,
                                0x06,
                                0xff,
                                0x01,
                                0xff,
                                0x02,
                                0xff,
                                0x03 }),
                new TestData(
                        new ModbusRequest(0xAB04, 0xFB, MF.readRegisters(ModbusRegisterType.DISCRETE_INPUT, 0xFEDC, 3)),
                        new ReadBooleanRegistersResponse(0xAB04, 0xFB, ModbusRegisterType.DISCRETE_INPUT, 0xFEDC,
                                new boolean[] { true, false, true }),
                        new int[] { 0xAB, 0x04, 0x00, 0x00, 0x00, 0x04, 0xFB, 0x02, 0x01, 0x05 }),
                new TestData(
                        new ModbusRequest(0xAB05, 0xFB, MF.readRegisters(ModbusRegisterType.DISCRETE_INPUT, 0xFEDC, 3)),
                        new ExceptionResponse(0xAB05, 0xFB, 0x82, 0x01),