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 }