chore: update the company name in LICENSE.txt
本仓库保存TGLogV3版本的协议,作为客户端和服务器端共享的通信协议,单独用一个仓库保存,使用的时候,C/C++/JAVA语言请用submoule的方式引用特定版本到客户端或者服务器端的项目中再使用,Go语言请直接import生成的代码。
重要说明: 如果不鉴权也不签名,可以不上报请求头; 如果鉴权或者签名,请求头需要加密传输,避免token泄漏;
重要说明:
enum Flag{ FLAG_NONE = 0; //无 FLAG_COMPRESSED = 1; //消息已压缩 FLAG_ENCRYPTED = 2; //消息已加密 FLAG_COMPRESSED_HEADER = 4; //消息头已压缩 FLAG_ENCRYPTED_HEADER = 8; //消息头已加密 }
//请求头 message ReqHeader{ string appID = 1; //业务ID string appName = 2; //业务名 string appVer = 3; //业务版本号 string sdkLang = 4; //SDK语言 string sdkVer = 5; //SDK版本号 string sdkOS = 6; //SDK操作系统 string network = 7; //网络协议,tcp/udp string protoVer = 8; //协议版本号 string hostIP = 9; //客户端IP google.protobuf.Timestamp ts = 10; //时间戳 string token = 11; //令牌,公网环境才需要 string tokenType = 12; //令牌类型,支持bearer/tglog两种token,bearer即JWT,tglog为自定义的一种token string sig = 13; //签名,公网环境才需要 } //请求 // 请求因为涉及到鉴权、签名,将请求头和请求包体分开, // 客户端构造请求包体,压缩、加密、签名,再构造请求头, // 服务器先解析请求头,鉴权、校验签名,再处理请求包体。 message Req{ string reqID = 1; //请求ID bytes appMetaData = 2; //应用层元数据,可以携带任何数据,在响应中原样返回 oneof req{ AuthReq authReq = 11; //鉴权请求 LogReq logReq = 12; //日志请求 HeartbeatReq heartbeatReq = 13; //心跳请求 } }
//响应头 message RspHeader{ int32 code = 1; //错误码 string msg = 2; //错误信息 string reqID = 3; //请求ID bytes appMetaData = 4; //应用层元数据 } //响应 message Rsp{ RspHeader header = 1; //响应头,为了简化响应的处理,响应头和响应包体合并 ,客户端直接解包即可 oneof rsp{ AuthRsp authRsp = 11; //鉴权响应 LogRsp logRsp = 12; //日志响应 HeartbeatRsp heartbeatRsp = 13; //心跳请求 } }
鉴权通过请求头的appID和token字段携带的数据实现。
sig = hex( md5( URI/ReqHeader.network + Method/ReqHeader.hostIP + token + ts + md5( body ) ) ),小写。即将URI(/tglog/v1/push或者/tglog/v3/push)或ReqHeader.network、Method(POST)或ReqHeader.hostIP、token、ts(8字节)、实际传输的数据的md5值(16字节)按字节拼接(注意拼接顺序),算md5摘要,再转成小写16进制字符串。
说明: 1、使用token而不是app_key,是因为上报日志是高频操作,每条日志都去查业务的key会有很大的性能损耗; 2、最终的sig和包体的md5分开计算,是为了避免实现的时候拼接时进行大量的内存拷贝。
说明:
1、使用token而不是app_key,是因为上报日志是高频操作,每条日志都去查业务的key会有很大的性能损耗;
2、最终的sig和包体的md5分开计算,是为了避免实现的时候拼接时进行大量的内存拷贝。
package sign import ( "bytes" "crypto/md5" "encoding/binary" "encoding/hex" "errors" "strings" "time" ) // signature errors var ( ErrExpiredSig = errors.New("expired signature") ErrInvalidSig = errors.New("invalid signature") ) // Sign signs a request func Sign(uri, method, token string, ts int64, data []byte) string { md5sum := md5.Sum(data) return signData(uri, method, token, ts, md5sum[:]) } // Verify a request func Verify(sig, uri, method, token string, data []byte, ts, timeout int64) error { if expired(ts, timeout) { return ErrExpiredSig } localSig := Sign(uri, method, token, ts, data) if localSig != sig { return ErrInvalidSig } return nil } func signData(uri, method, token string, ts int64, dataList ...[]byte) string { var bb bytes.Buffer dataLen := 0 for _, data := range dataList { dataLen += len(data) } bb.Grow(len(uri) + len(method) + len(token) + 8 + dataLen) // 按顺序拼接 bb.WriteString(uri) bb.WriteString(method) bb.WriteString(token) var tsBuf [8]byte binary.LittleEndian.PutUint64(tsBuf[:], uint64(ts)) bb.Write(tsBuf[:]) for _, data := range dataList { bb.Write(data) } md5sum := md5.Sum(bb.Bytes()) return strings.ToLower(hex.EncodeToString(md5sum[:])) } func expired(ts int64, timeout int64) bool { if timeout <= 0 { return false } return time.Now().After(time.Unix(ts+timeout, 0)) }
支持snappy压缩。
密钥分配:
由运营团队按业务分配,保存在客户端与服务器配置文件。
加密算法:AES+PKCS7填充。
如果同时压缩与加密,先压缩再加密,请遵守以下顺序编解码:
protobuf
protobuf-c
执行:make
协议
说明
本仓库保存TGLogV3版本的协议,作为客户端和服务器端共享的通信协议,单独用一个仓库保存,使用的时候,C/C++/JAVA语言请用submoule的方式引用特定版本到客户端或者服务器端的项目中再使用,Go语言请直接import生成的代码。
格式
协议包组成
帧头
标记位
请求头/请求
响应头/响应
鉴权
鉴权通过请求头的appID和token字段携带的数据实现。
签名
签名算法
sig = hex( md5( URI/ReqHeader.network + Method/ReqHeader.hostIP + token + ts + md5( body ) ) ),小写。即将URI(/tglog/v1/push或者/tglog/v3/push)或ReqHeader.network、Method(POST)或ReqHeader.hostIP、token、ts(8字节)、实际传输的数据的md5值(16字节)按字节拼接(注意拼接顺序),算md5摘要,再转成小写16进制字符串。
签名代码参考
压缩与加密
压缩
支持snappy压缩。
加密
密钥分配:
由运营团队按业务分配,保存在客户端与服务器配置文件。
加密算法:AES+PKCS7填充。
顺序
如果同时压缩与加密,先压缩再加密,请遵守以下顺序编解码:
编码
解码
生成代码
依赖
protobuf
protobuf-c
生成代码
执行:make
更新规则