Jt808协议头部包解析

1. 协议结构

在这里插入图片描述
在这里插入图片描述

2. 协议说明

消息ID(0-1)   消息体属性(2-3)  终端手机号(4-9)  消息流水号(10-11)    消息包封装项(12-15)
 
byte[0-1]   消息ID word(16)
byte[2-3]   消息体属性 word(16)
        bit[0-9]    消息体长度
        bit[10-12]  数据加密方式
                        此三位都为 0,表示消息体不加密
                        第 10 位为 1,表示消息体经过 RSA 算法加密
                        其它保留
        bit[13]     分包
                        1:消息体卫长消息,进行分包发送处理,具体分包信息由消息包封装项决定
                        0:则消息头中无消息包封装项字段
        bit[14-15]  保留
byte[4-9]   终端手机号或设备ID bcd[6]
        根据安装后终端自身的手机号转换
        手机号不足12 位,则在前面补 0
byte[10-11]     消息流水号 word(16)
        按发送顺序从 0 开始循环累加
byte[12-15]     消息包封装项
        byte[0-1]   消息包总数(word(16))
                        该消息分包后得总包数
        byte[2-3]   包序号(word(16))
                        从 1 开始
        如果消息体属性中相关标识位确定消息分包处理,则该项有内容
        否则无该项

3. 协议类

public class Jt808Message extends MinaMessage {
    /** PREFIX */
    public static final byte PREFIX = 0x7E;

    /** 转移处理 */
    public static final byte UNESCAPE = 0x7D;

    /** 消息id,对应下面 body 的类型,什么样的消息id就对应什么类型的body,目前只集成 0200 类型 */
    private int messageId;
    /** 消息体属性 */
    private int attributes;
    /** 消息体包的长度 */
    private int packageLength;
    /** 加密方式:3 位表示加密方式  都为0表示不加密,100就为RSA算法加密 */
    private int entryType;
    /** 是否有子包 */
    private boolean childPackage;
    /** 保留位 */
    private int reservedBit;
    /** 手机号 */
    private String phone;
    /** 流水号 */
    private int serialNo;
    /** 消息总包数,由 childPackage 属性指定是否有值 */
    private long packageTotal;
    /** 消息编号,由 childPackage 属性指定是否有值 */
    private long packageNumber;
    /** 消息体数据 */
    private byte[] bodyBytes;
    /** 子包数据 */
    private byte[] childBytes;
    /** 校检码 */
    private byte xor;

    /**
     * Converter message
     *
     * @param bytes bytes
     * @return the jt 808 message
     * @since 2022.1.1
     */
    public Jt808Message(byte[] bytes) {
        super.setPacketDescribe(bytes);
        bytes = unEscape(bytes);
        //校检码检验
        validateXor(bytes);
        int index = 0;
        //在解析之前需要跳过分隔符
        if (bytes[0] == PREFIX) {
            index++;
        }
        //读取messageId 2个字节
        this.messageId = BitOperator.parseIntFromBytes(bytes, index, 2);
        String idFormat = String.format("[%04x] 消息id:%d", messageId, messageId);
        index += 2;

        //读取消息体属性 2个字节
        this.attributes = BitOperator.parseIntFromBytes(bytes, index, 2);
        //低10位表示消息体长度
        this.packageLength = attributes & 0x1ff;
        //3位表示加密方式:000表示不加密,100就为RSA算法加密
        this.entryType = (attributes & 0xe00) >> 10;
        //1位表示是否有子包
        this.childPackage = (attributes & 0x2000) >> 13 == 1;
        //2位保留位
        this.reservedBit = (attributes & 0xc000) >> 14;
        index += 2;
        String attributesFormat = String.format("[%04x] 终端消息体属性,[%x] 消息长度:%d,[%03x] 是否加密:%d,[%x] 是否有子包:%b",
                                                attributes,
                                                packageLength, packageLength, entryType, entryType, attributes & 0x2000, childPackage);

        //读取手机号
        this.phone = readPhoneForBytes(bytes, index, 6);
        String phoneFormat = String.format("[%s] 手机号:%s", BitOperator.bytesToHexString(BitOperator.rangeBytes(bytes, index, 6)), phone);
        index += 6;
        //流水号
        this.serialNo = BitOperator.parseIntFromBytes(bytes, index, 2);
        index += 2;
        String serialNoFormat = String.format("[%02x] 流水号:%s", serialNo, serialNo);
        //如果分包的话,只需要将数据包保存起来,不进行解析
        if (childPackage) {
            //如果有子包需要获取下面的总包数以及包的编号
            this.packageTotal = BitOperator.parseIntFromBytes(bytes, index, 2);
            index += 2;
            this.packageNumber = BitOperator.parseIntFromBytes(bytes, index, 2);
            index += 2;
            this.childBytes = new byte[packageLength];
            System.arraycopy(bytes, index, this.childBytes, 0, packageLength);
        } else {
            //读取到消息体
            this.bodyBytes = new byte[packageLength];
            System.arraycopy(bytes, index, this.bodyBytes, 0, packageLength);
        }
        index += packageLength;
        //获取到校检码
        this.xor = bytes[index];
        String xorFormat = String.format("[%02x] 校检码:%d", xor, xor);
        index++;
        if (bytes[index] != PREFIX) {
            throw new IllegalArgumentException("数据格式异常");
        }
        log.info("数据包16进制:{}", getPacketDescribe());
        log.info("{}", idFormat);
        log.info("{}", attributesFormat);
        log.info("{}", phoneFormat);
        log.info("{}", serialNoFormat);
        log.info("{}", xorFormat);
    }

    /**
     * 数据包处理转义符
     * 设在在封装完消息后,对还未拼接首位的 7E 时需要对消息进行转义一次,具体就是将消息中 0X7E全部替换为 7D02,所有的7D全部替换为 7D01
     *
     * @param data data
     * @return byte [ ]
     * @since 1.0
     */
    private byte[] unEscape(byte[] data) {
        IoBuffer ioBuffer = IoBuffer.allocate(data.length);
        for (int i = 0; i < data.length; i++) {
            if (data[i] == UNESCAPE) {
                byte datum = data[i + 1];
                if (datum == 0x01) {
                    ioBuffer.put(UNESCAPE);
                    i++;
                } else if (datum == 0x02) {
                    ioBuffer.put(PREFIX);
                    i++;
                } else {
                    //错误包有可能只有7D,暂时不处理
                }
            } else {
                ioBuffer.put(data[i]);
            }
        }
        return ioBuffer.array();
    }

    /**
     * Read phone for bytes
     *
     * @param bytes      bytes
     * @param startIndex start index
     * @param length     length
     * @return the string
     * @since 2022.1.1
     */
    private String readPhoneForBytes(byte[] bytes, int startIndex, int length) {
        byte[] tmp = new byte[length];
        System.arraycopy(bytes, startIndex, tmp, 0, length);
        return BitOperator.bcd2String(tmp);
    }


    /**
     * Validate xor
     *
     * @param bytes bytes
     * @since 2022.1.1
     */
    public void validateXor(byte[] bytes) {
        int index = 0;
        int endIndex = 0;
        //跳过分割符
        if (bytes[0] == PREFIX) {
            index++;
        }
        if (bytes[bytes.length - 1] == PREFIX) {
            endIndex = bytes.length - 2;
        }
        //获取到所有的数据异
        byte xor = BitOperator.getCheckSum4JT808(bytes, index, endIndex);
        byte packetXor = bytes[endIndex];
        if (xor != packetXor) {
            throw new IllegalArgumentException(String.format("packet校检码错误,包传递xor:%d,实际解析xor:%d", packetXor, xor));
        }
    }

}

4. 定义抽象类

public abstract class MinaMessage {
    /** 数据包的16进制表现形式 */
    private String packetDescribe;


    /**
     * Gets packet describe *
     *
     * @return the packet describe
     * @since 2022.1.1
     */
    public String getPacketDescribe() {
        return packetDescribe;
    }

    /**
     * Sets packet describe *
     *
     * @param bytes bytes
     * @since 2022.1.1
     */
    public void setPacketDescribe(byte[] bytes) {
        this.packetDescribe = BitOperator.bytesToHexString(bytes);
    }
}

5. 工具类

public class BitOperator {

    /** DIGITAL */
    public static final String DIGITAL = "0123456789ABCDEF";

    /**
     * 把byte[]转化位整形,通常为指令用
     *
     * @param value value
     * @return int int
     * @throws Exception
     * @since 2022.1.1
     */
    public static int byteToInteger(byte[] value) {
        int result;
        if (value.length == 1) {
            result = oneByteToInteger(value[0]);
        } else if (value.length == 2) {
            result = twoBytesToInteger(value);
        } else if (value.length == 3) {
            result = threeBytesToInteger(value);
        } else if (value.length == 4) {
            result = fourBytesToInteger(value);
        } else {
            result = fourBytesToInteger(value);
        }
        return result;
    }

    /**
     * 把一个byte转化位整形,通常为指令用
     *
     * @param value value
     * @return int int
     * @throws Exception
     * @since 2022.1.1
     */
    private static int oneByteToInteger(byte value) {
        return (int) value & 0xFF;
    }

    /**
     * 把一个2位的数组转化位整形
     *
     * @param value value
     * @return int int
     * @throws Exception
     * @since 2022.1.1
     */
    private static int twoBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        return ((temp0 << 8) + temp1);
    }

    /**
     * 把一个3位的数组转化位整形
     *
     * @param value value
     * @return int int
     * @throws Exception
     * @since 2022.1.1
     */
    private static int threeBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        int temp2 = value[2] & 0xFF;
        return ((temp0 << 16) + (temp1 << 8) + temp2);
    }

    /**
     * 把一个4位的数组转化位整形,通常为指令用
     *
     * @param value value
     * @return int int
     * @throws Exception
     * @since 2022.1.1
     */
    private static int fourBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        int temp2 = value[2] & 0xFF;
        int temp3 = value[3] & 0xFF;
        return ((temp0 << 24) + (temp1 << 16) + (temp2 << 8) + temp3);
    }

    /**
     * Gets check sum 4 jt 808 *
     *
     * @param bs    bs
     * @param start start
     * @param end   end
     * @return the check sum 4 jt 808
     * @since 2022.1.1
     */
    public static byte getCheckSum4JT808(byte[] bs, int start, int end) {
        if (start < 0 || end > bs.length) {
            throw new ArrayIndexOutOfBoundsException("getCheckSum4JT808 error : index out of bounds(start=" + start
                                                     + ",end=" + end + ",bytes length=" + bs.length + ")");
        }
        byte cs = 0;
        for (int i = start; i < end; i++) {
            cs ^= bs[i];
        }
        return cs;
    }

    /**
     * BCD字节数组===>String
     *
     * @param bytes bytes
     * @return 十进制字符串 string
     * @since 2022.1.1
     */
    public static String bcd2String(byte[] bytes) {
        StringBuilder temp = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            // 高四位
            temp.append((bytes[i] & 0xf0) >>> 4);
            // 低四位
            temp.append(bytes[i] & 0x0f);
        }
        //忽略0开头
        return temp.substring(0, 1).equalsIgnoreCase("0") ? temp.substring(1) : temp.toString();
    }

    /**
     * 截取数组指定返回的数据,将其转换为int类型
     *
     * @param data       data
     * @param startIndex start index
     * @param length     length
     * @return int int
     * @since 2022.1.1
     */
    public static int parseIntFromBytes(byte[] data, int startIndex, int length) {
        // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
        final int len = Math.min(length, 4);
        byte[] tmp = new byte[len];
        System.arraycopy(data, startIndex, tmp, 0, len);
        return byteToInteger(tmp);
    }

    /**
     * byte数组转换成16进制字符串
     *
     * @param src src
     * @return string string
     * @since 2022.1.1
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * 将int转换为 16进制数据
     *
     * @param data    data
     * @param byteNum byte num
     * @return the string
     * @since 2022.1.1
     */
    public static String intToHexString(int data, int byteNum) {
        StringBuilder sb = new StringBuilder();
        for (int m = 0; m < byteNum; m++) {
            sb.append("00");
        }
        int totalLen = byteNum * 2;
        String tmp = Integer.toHexString(data);
        sb.replace(totalLen - tmp.length(), totalLen, tmp);
        return sb.toString();
    }

    /**
     * string转换为字节数组
     *
     * @param hex hex
     * @return byte [ ]
     * @since 1.0
     */
    public static byte[] stringToBytes(String hex) {
        String hex1 = hex.replace(" ", "");
        char[] hex2char = hex1.toCharArray();
        byte[] bytes = new byte[hex1.length() / 2];
        byte temp;
        for (int p = 0; p < bytes.length; p++) {
            temp = (byte) (DIGITAL.indexOf(hex2char[2 * p]) * 16);
            temp += DIGITAL.indexOf(hex2char[2 * p + 1]);
            bytes[p] = (byte) (temp & 0xff);
        }
        return bytes;
    }

    /**
     * 截取指定范围
     * @param data
     * @param start
     * @param length
     * @return
     */
    public static byte[] rangeBytes(byte[] data, int start, int length) {
        byte[] bytes = new byte[length];
        System.arraycopy(data, start, bytes, 0, length);
        return bytes;
    }
}

7. Mina框架解析

CumulativeProtocolDecoder 类提供了TCP粘包拆包的功能实现

public class Jt808ProtocolDecoder extends CumulativeProtocolDecoder {

    /** TAG */
    private static final byte TAG = 0X7E;

    /**
     * Do decode
     *
     * @param ioSession             io session
     * @param ioBuffer              io buffer
     * @param protocolDecoderOutput protocol decoder output
     * @return the boolean
     * @since 2022.1.1
     */
    @Override
    protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput protocolDecoderOutput) {
        if (ioBuffer.remaining() < 1) {
            return false;
        }
        while (ioBuffer.remaining() > 0) {
            //标记开始位置
            ioBuffer.mark();
            //读取数据
            byte tag = ioBuffer.get();
            //读取到分隔符
            if (tag == TAG && ioBuffer.remaining() > 0) {
                //在读取一次分隔符,防止两个分隔符一起的情况,取后面分隔符为开始
                tag = ioBuffer.get();
                while (tag != TAG) {
                    //如果不为分隔符,并且数据包已经小于等于0 证明没有数据了,出现了分包的情况,直接返回false等待下一个包
                    if (ioBuffer.remaining() <= 0) {
                        //需要重置到标记开始的位置
                        ioBuffer.reset();
                        return false;
                    }
                    tag = ioBuffer.get();
                }
                //获取到读取的位置
                int position = ioBuffer.position();
                //通过读取完的包位置减去上面一开始标记的位置,可以获取到数据包的长度
                int packetSize = position - ioBuffer.markValue();
                //只有在包大于2时,才是正常的包,如果小于或者等于2那么就出现了两个 分隔符一起以及分包的情况
                if (packetSize > 2) {
                    byte[] data = new byte[packetSize];
                    ioBuffer.reset();
                    ioBuffer.get(data);
                    Jt808Message jt808Message = new Jt808Message(data);
                    protocolDecoderOutput.write(jt808Message);
                } else {
                    //数据重置一下
                    ioBuffer.reset();
                    //出现了分隔符,先读取一次,取后面的分隔符为包头
                    ioBuffer.get();
                }
            }
        }
        return false;
    }

}

8. 参考文档

https://blog.csdn.net/NiceSzy/article/details/120741750

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值