|
|
@@ -0,0 +1,256 @@
|
|
|
+package packet
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "compress/zlib"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "hash/crc32"
|
|
|
+ "io"
|
|
|
+ "log"
|
|
|
+ "mtp20access/utils"
|
|
|
+ "net"
|
|
|
+)
|
|
|
+
|
|
|
+/************协议格式************************************
|
|
|
+
|
|
|
+WTAS协议是交易系统与管理客户端/会员系统之间的通信协议,由报文头 + 报文体两部分组成
|
|
|
+标识 类型 字节数 内容 说明
|
|
|
+HeadTag byte 1 0xFF 头标记
|
|
|
+Length uint 4 包总长度,包括头长度
|
|
|
+FunCode uint 4 功能号(为0代表心跳,心跳数据体长度为0)
|
|
|
+SessionId uint 4 会话ID,由交易接入(代理)维护
|
|
|
+Mode byte 1 内容类型,0:ProtoBuff,1:Json,2:Zip + ProtBuff,…
|
|
|
+Version byte 1 版本号
|
|
|
+SerialNumber uint 4 通讯报文序号
|
|
|
+数据体 业务结构体,ProtoBuf. 数据体格式: 头部 4 节字 + 内容 + 8 节字 掩码, 解密的时候只解内容部分
|
|
|
+CRC byte 4 报文校验和
|
|
|
+FootTag byte 1 0x00 尾标记
|
|
|
+
|
|
|
+数据体前,包头长度=19, 空包长度=24
|
|
|
+#define MAXKEY "B0FB83E39A5EBFAABE471362A58393FF"
|
|
|
+#define TRANSKEY "F7A72DE7D6264530F01BA49BC73EB873"
|
|
|
+*******************************************************/
|
|
|
+
|
|
|
+// MiPacket 协议包结构体
|
|
|
+type MiPacket struct {
|
|
|
+ Length uint32 // 包总长度
|
|
|
+ FunCode uint32 // 功能能
|
|
|
+ SessionId uint32 // 数据包的sid
|
|
|
+ Mode uint32 // 业务数据体的格式 0:ProtoBuff,1:Json,2:Zip + ProtoBuff
|
|
|
+ SerialNumber uint32 // 通信流水号
|
|
|
+ Data []byte // 业务数据体
|
|
|
+ CRC uint32 // CRC
|
|
|
+ OriMsg []byte // 原始包数据
|
|
|
+}
|
|
|
+
|
|
|
+// EnPack 打包,根据现有的数据内容重新打包,无任何数据时打出来的是心跳包
|
|
|
+// 这里不对数据体进行加密, 如需加密,请先加密再设置进来
|
|
|
+func (p *MiPacket) EnPack() []byte {
|
|
|
+ p.Length = uint32(len(p.Data)) + 24 // 空包长度24
|
|
|
+
|
|
|
+ buf := make([]byte, 0) // 缓冲区
|
|
|
+ buf = append(buf, byte(0xFF)) // HeadTag 0xFF
|
|
|
+ buf = append(buf, utils.UintToBytes(p.Length)...) // Length
|
|
|
+ buf = append(buf, utils.UintToBytes(p.FunCode)...) // FunCode
|
|
|
+ buf = append(buf, utils.UintToBytes(p.SessionId)...) // SessionId
|
|
|
+ buf = append(buf, byte(0)) // Mode
|
|
|
+ buf = append(buf, byte(0)) // Version
|
|
|
+ buf = append(buf, utils.UintToBytes(p.SerialNumber)...) // SerialNumber
|
|
|
+ buf = append(buf, p.Data...) // 数据体 body
|
|
|
+ p.CRC = crc32.Update(58861227, crc32.IEEETable, buf[0:19]) //
|
|
|
+ buf = append(buf, utils.UintToBytes(p.CRC)...) // CRC
|
|
|
+ buf = append(buf, byte(0)) // FootTag
|
|
|
+
|
|
|
+ return buf
|
|
|
+}
|
|
|
+
|
|
|
+// UnPackHead 解包头
|
|
|
+// @buf length must be >=19
|
|
|
+func (p *MiPacket) UnPackHead(buf []byte) error {
|
|
|
+ if len(buf) < 19 {
|
|
|
+ return errors.New("header len error")
|
|
|
+ }
|
|
|
+
|
|
|
+ if buf[0] != 0xFF {
|
|
|
+ return errors.New("header flag error")
|
|
|
+ }
|
|
|
+
|
|
|
+ p.Length = utils.BytesToUint32(buf[1:5]) // Length
|
|
|
+ p.FunCode = utils.BytesToUint32(buf[5:9]) // FunCode
|
|
|
+ p.SessionId = utils.BytesToUint32(buf[9:13]) // SessionId
|
|
|
+ p.SerialNumber = utils.BytesToUint32(buf[15:19]) // SerialNumber
|
|
|
+ p.Mode = uint32(buf[13]) // Mode
|
|
|
+
|
|
|
+ if p.Length > 1024*10000 || p.Length < 24 {
|
|
|
+ fmt.Println("packet too big or len error, len:", p.Length)
|
|
|
+ return fmt.Errorf("invalid len, must in[24,1024000]")
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// HeaderLen 包头长度, HeaderTag~SerialNumber 的长度
|
|
|
+// 用于tcp接收数据时第一部分的长度
|
|
|
+func (p *MiPacket) HeaderLen() uint32 {
|
|
|
+ return 19
|
|
|
+}
|
|
|
+
|
|
|
+// BodyLen 除包头外的长度, 含FootTag
|
|
|
+// 用于tcp接收数据时第二部分的长度
|
|
|
+func (p *MiPacket) BodyLen() uint32 {
|
|
|
+ return p.Length - 19
|
|
|
+}
|
|
|
+
|
|
|
+// FuncCode 功能号
|
|
|
+func (p *MiPacket) FuncCode() int {
|
|
|
+ return int(p.FunCode)
|
|
|
+}
|
|
|
+
|
|
|
+// UnPack 从指定的缓冲区解包
|
|
|
+// 注意:不解密业务数据体, 可用 DecodeData 方法进行解密
|
|
|
+// @buf 完整的包数据内容
|
|
|
+func (p *MiPacket) UnPack(buf []byte) error {
|
|
|
+ err := p.UnPackHead(buf[:19])
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if len(buf) != int(p.Length) {
|
|
|
+ err := fmt.Errorf("packet length err")
|
|
|
+ log.Println(err)
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ //长度24为心跳包, 非心跳包时保存数据体内容
|
|
|
+ //包头前面19字节+4个字节数据体加密长度, 所以从23开始
|
|
|
+ //包尾5个字节,但是加密的数据体后8个字节掩码不需要, 所以是 p.length-13
|
|
|
+ if len(buf) != 24 {
|
|
|
+ p.Data = buf[23 : p.Length-13]
|
|
|
+ }
|
|
|
+ p.OriMsg = buf[:]
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// DecodeData 解密业务数据体内容
|
|
|
+// @mode 数据体类型 0:ProtoBuff,1:Json,2:Zip + ProtoBuff
|
|
|
+// @msg 业务数据体
|
|
|
+func (p *MiPacket) DecodeData(mode uint32, msg []byte) ([]byte, error) {
|
|
|
+ if mode == 2 {
|
|
|
+ b := bytes.NewReader(msg)
|
|
|
+ r, err := zlib.NewReader(b)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ var out bytes.Buffer
|
|
|
+ _, _ = io.Copy(&out, r)
|
|
|
+ _ = r.Close()
|
|
|
+ msg = out.Bytes()
|
|
|
+ }
|
|
|
+
|
|
|
+ return Decrypt(msg, AESKey, true)
|
|
|
+}
|
|
|
+
|
|
|
+// EncodeData 加密码业务数据体内容
|
|
|
+func (p *MiPacket) EncodeData(mode uint32, msg []byte) ([]byte, error) {
|
|
|
+ buf := msg
|
|
|
+ if mode == 2 {
|
|
|
+ var b bytes.Buffer
|
|
|
+ w := zlib.NewWriter(&b)
|
|
|
+ _, err := w.Write(msg)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ _ = w.Close()
|
|
|
+ buf = b.Bytes()
|
|
|
+ }
|
|
|
+ return Encrypt(buf, AESKey, true)
|
|
|
+}
|
|
|
+
|
|
|
+// SetData 设置业务数据, 不含通信包头、CRC等字段
|
|
|
+func (p *MiPacket) SetData(buf []byte) {
|
|
|
+ p.Data = buf[:]
|
|
|
+}
|
|
|
+
|
|
|
+// SetOriMsg 设置原始包数据
|
|
|
+func (p *MiPacket) SetOriMsg(arg ...[]byte) {
|
|
|
+ if p.OriMsg == nil {
|
|
|
+ p.OriMsg = make([]byte, 0)
|
|
|
+ }
|
|
|
+ for i := range arg {
|
|
|
+ p.OriMsg = append(p.OriMsg, arg[i]...)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// GetOriMsg 获取解包的原始数据
|
|
|
+func (p *MiPacket) GetOriMsg() []byte {
|
|
|
+ return p.OriMsg
|
|
|
+}
|
|
|
+
|
|
|
+// RebuildForNewSid 设置新的通信流水号(sessionId)且重新打包
|
|
|
+func (p *MiPacket) RebuildForNewSid(sessionId uint32) {
|
|
|
+ // 从某个位置开始, 替换一段内容
|
|
|
+ f := func(buf []byte, pos int, newBuf []byte) {
|
|
|
+ for i := 0; i < len(newBuf); i++ {
|
|
|
+ buf[pos] = newBuf[i]
|
|
|
+ pos++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ p.SessionId = sessionId
|
|
|
+ s := utils.UintToBytes(p.SessionId)
|
|
|
+ f(p.OriMsg, 9, s)
|
|
|
+ p.CRC = crc32.Update(58861227, crc32.IEEETable, p.OriMsg[0:19])
|
|
|
+ crc := utils.UintToBytes(p.CRC)
|
|
|
+ f(p.OriMsg, len(p.OriMsg)-5, crc)
|
|
|
+}
|
|
|
+
|
|
|
+// ReadMessage 从指定tcp链接读取一个协议包
|
|
|
+// @返回值 []byte 未解包的原始数据包, 如果需获取业务数据内容,
|
|
|
+// 请调用UnPack方法后取成员变量 Data 的内容
|
|
|
+func (p *MiPacket) ReadMessage(conn *net.Conn) ([]byte, error) {
|
|
|
+ p.OriMsg = make([]byte, 0)
|
|
|
+ if conn == nil {
|
|
|
+ return p.OriMsg, fmt.Errorf("conn is nil")
|
|
|
+ }
|
|
|
+
|
|
|
+ headerBuf := make([]byte, p.HeaderLen())
|
|
|
+ nRead, err := io.ReadFull(*conn, headerBuf)
|
|
|
+ if err != nil || nRead != len(headerBuf) {
|
|
|
+ return p.OriMsg, fmt.Errorf("read header data error, maybe conn closed:%v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ err = p.UnPackHead(headerBuf)
|
|
|
+ if err != nil {
|
|
|
+ return p.OriMsg, err
|
|
|
+ }
|
|
|
+
|
|
|
+ dataBuf := make([]byte, p.BodyLen())
|
|
|
+ nRead, err = io.ReadFull(*conn, dataBuf)
|
|
|
+ if err != nil || nRead != len(dataBuf) {
|
|
|
+ return p.OriMsg, fmt.Errorf("read data error, maybe conn closed:%v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ p.SetOriMsg(headerBuf, dataBuf)
|
|
|
+ return p.OriMsg, nil
|
|
|
+}
|
|
|
+
|
|
|
+// HeaderInfo 头部信息, 功能号、sid、流水号、长度等
|
|
|
+func (p *MiPacket) HeaderInfo() string {
|
|
|
+ return fmt.Sprintf("funcode[%d] sid[%d] serial[%d] iLen:%d",
|
|
|
+ p.FunCode, p.SessionId, p.SerialNumber, p.Length)
|
|
|
+}
|
|
|
+
|
|
|
+// BuildPacket 创建包
|
|
|
+// @bCrypto 是否对数据进行加密
|
|
|
+func BuildPacket(funCode, sessionId, serialNum uint32, msg []byte, bCrypto bool) ([]byte, error) {
|
|
|
+ p := new(MiPacket)
|
|
|
+ p.FunCode = funCode
|
|
|
+ p.SessionId = sessionId
|
|
|
+ p.SerialNumber = serialNum
|
|
|
+ if bCrypto && len(msg) > 0 {
|
|
|
+ buf, _ := Encrypt(msg, AESKey, true)
|
|
|
+ p.Data = buf
|
|
|
+ return p.EnPack(), nil
|
|
|
+ }
|
|
|
+
|
|
|
+ p.Data = msg
|
|
|
+ return p.EnPack(), nil
|
|
|
+}
|