init commit

This commit is contained in:
flswld
2022-11-20 15:38:00 +08:00
parent eda2b643b9
commit 3efed3defe
5834 changed files with 636508 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
http_port = 9005
[hk4e]
kcp_addr = "hk4e.flswld.com"
kcp_port = 22103
[logger]
level = "DEBUG"
method = "CONSOLE"
track_line = true
[air]
addr = "air"
port = 8086
service_name = "hk4e-gateway"
[light]
port = 10005
[database]
url = "mongodb://mongo:27017"
[mq]
nats_url = "nats://nats1:4222,nats://nats2:4222,nats://nats3:4222"
+90
View File
@@ -0,0 +1,90 @@
package main
import (
"flswld.com/common/config"
"flswld.com/gate-hk4e-api/proto"
"flswld.com/light"
"flswld.com/logger"
"gate-hk4e/controller"
"gate-hk4e/dao"
"gate-hk4e/forward"
"gate-hk4e/mq"
"gate-hk4e/net"
"gate-hk4e/rpc"
"github.com/arl/statsviz"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
filePath := "./application.toml"
config.InitConfig(filePath)
logger.InitLogger()
logger.LOG.Info("gate hk4e start")
go func() {
// 性能检测
err := statsviz.RegisterDefault()
if err != nil {
logger.LOG.Error("statsviz init error: %v", err)
}
err = http.ListenAndServe("0.0.0.0:2345", nil)
if err != nil {
logger.LOG.Error("perf debug http start error: %v", err)
}
}()
db := dao.NewDao()
// 用户服务
rpcUserConsumer := light.NewRpcConsumer("annie-user-app")
_ = controller.NewController(db, rpcUserConsumer)
kcpEventInput := make(chan *net.KcpEvent)
kcpEventOutput := make(chan *net.KcpEvent)
protoMsgInput := make(chan *net.ProtoMsg, 10000)
protoMsgOutput := make(chan *net.ProtoMsg, 10000)
netMsgInput := make(chan *proto.NetMsg, 10000)
netMsgOutput := make(chan *proto.NetMsg, 10000)
connectManager := net.NewKcpConnectManager(protoMsgInput, protoMsgOutput, kcpEventInput, kcpEventOutput)
connectManager.Start()
forwardManager := forward.NewForwardManager(db, protoMsgInput, protoMsgOutput, kcpEventInput, kcpEventOutput, netMsgInput, netMsgOutput)
forwardManager.Start()
gameServiceConsumer := light.NewRpcConsumer("game-hk4e-app")
rpcManager := rpc.NewRpcManager(forwardManager)
rpcMsgProvider := light.NewRpcProvider(rpcManager)
messageQueue := mq.NewMessageQueue(netMsgInput, netMsgOutput)
messageQueue.Start()
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
s := <-c
logger.LOG.Info("get a signal %s", s.String())
switch s {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
logger.LOG.Info("gate hk4e exit")
messageQueue.Close()
rpcMsgProvider.CloseRpcProvider()
gameServiceConsumer.CloseRpcConsumer()
rpcUserConsumer.CloseRpcConsumer()
db.CloseDao()
time.Sleep(time.Second)
return
case syscall.SIGHUP:
default:
return
}
}
}
+1
View File
@@ -0,0 +1 @@
* binary
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDHPnAvEbJfMUwHXmRLiNDH1qFeGm0U/D6n7BjzEmJl5VtMKBZF
hnz+aKsyMo9aAowi2Fe/6iWUuzcbnAJS+4iLUxaeqOdvPe5LuR3wQxHKGJ8XsDkH
kt3T1operE1rpw9wX3xuUi0CA5aHqpC0ho0zMsk7nvxWQogv1G8uqcXmfQIDAQAB
AoGBALElFmEC/vAbyFkU119A+T9z2GzuWeW6j4qFI3mZ8tpdnVqMmaCe/irDrNIo
mcORWD7y0rHS4C7odQqbHoXhFXgXfrGJXcMu977uIxBKGj0UBz6YIciznk/8DrMo
o3q6+SGsNj5zvlU8oY6cpfC663VoQb7VWveUGN4zshdnvyiRAkEA8nlq/LEuQPCj
lp5wbUizJ3Uwll5N51N6Kzm1wRQ0vUtIzRK940lGMxlhihnJfifTColAnnzmWj/X
dWIULqIc0wJBANJbrnq1iim9Jue0UOhQn6hV8vvWHgLjK7zuEsUPDqzxfhmpmBEh
BwAaH3li6bGCbIfSJazs+LmNLB4YtMo6nW8CQAMtmjxjqiKJxOslen3ENSzwOUnP
RKAilPhaEkrMlABjKzoc48ZF4Jis3X1s5xozNW3u7JznMDHAondUaMVPtKcCQG45
9lp8aBJo+ErvlHm3TYHiz7kgwIcYzKFqStGRi0oaHM6LrJBFMyrdhWKQ7w3B3ubo
ui872TU5gUWgApP5VOcCQQDDvU76TpLQ2v2LO8D0L/Ds+t6HdGcPpKvlAm/YQYHL
X6Q435tFNbeWo3JzpGElb25zAQfXU5cvzYvg37f36iM6
-----END RSA PRIVATE KEY-----
+5
View File
@@ -0,0 +1,5 @@
{"remoteName": "blocks/00/22551915.blk", "md5": "0cf3d6b599d443c7cfa4a3e6189a7757", "fileSize": 3184}
{"remoteName": "blocks/00/25060239.blk", "md5": "f3cf18d697e3380b2f833ccf2c7d4194", "fileSize": 3328}
{"remoteName": "blocks/00/29342328.blk", "md5": "3b8e2c23e33ce92f68e40196c574ae94", "fileSize": 14103}
{"remoteName": "blocks/00/32070509.blk", "md5": "4bf2a81d7565301269d1c90e14393045", "fileSize": 698}
{"remoteName": "blocks/00/33067900.blk", "md5": "0b4d781c1633537d2fa4b4f0f567d2ca", "fileSize": 454}
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
ElIKBm9zX3VzYRIHQW1lcmljYRoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc3VzYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElMKB29zX2V1cm8SBkV1cm9wZRoKREVWX1BVQkxJQyI0aHR0cHM6Ly9vc2V1cm9kaXNwYXRjaC55dWFuc2hlbi5jb20vcXVlcnlfY3VyX3JlZ2lvbhJRCgdvc19hc2lhEgRBc2lhGgpERVZfUFVCTElDIjRodHRwczovL29zYXNpYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElUKBm9zX2NodBIKVFcsIEhLLCBNTxoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc2NodGRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uKpwQRWMyYhAAAABbrAvbhfIRHfaSCN24qQyVAAgAAMs68ZiMdPfEj41O2wBCYqGiC/WdovvJvaw4t3/m1zIYDrt3/ftK9GKFb7C+2E8FmaHqOnwjJYBg2wI1sXpGmuSxkeWw8Avr36wlNtQjhXNV9zoNKstuZYuheyLlpbPRbYZ3UA6/BzTVsjIhjR1lcqFrigQnpV6MgRR9KqxakCaffK6qIzMlodx4ZPKlqseQhCiyVAvLWQSRqCRcZipzotXsmgLQbpDFtRzhgukXPjfW5dAlzMwswPuu7ZQsf1AKipI34dVQLu6gtXthGgbjn89h/79VR5AokLCPGqIV7/2s+gHfykrjDtyp5rwCcmGQqwV3gHy5LGrHl8Zm12jNd7Qcng51ydqtX4xzet6J2iMF6Dw5nPd/hTyxn+i3Ttk6fop9rbCq3iNgEw3+0cSDal1I1ThYdVnMgPhZgQkZc5/SpTaR+8vfDzRIKbSSrrPSEgLnQvWZOOugXhNdyuiaBc8rJveno7vvktmnhDUF3xWi6osj75j2KghRrdHfDR3Zuh4COrGZDRBSKHft2AvfrxaMT9O8hPzzzYk0U2iicVCDlNP/8wqaT9Vqt1kHmruLxqh377iyp0mxKfNt0+SNRzLyRoyvOar/z3AT6TU9LRoCFrkcJpVsUN+2MVeT52PfMbv5O/Nw9sqsFDlofCJJ/EknY0wDc+tNarYOhDM67/ojn/p6W3ZPBJxb2wcF1TOh9dpAeZdCGJusqhMIj5lpoW8nENTFhkEgMUv2Lh5Z6WpeOAKAu9eDpBMhlRNCccDaNYUgo6TdVDtWxtPrS3NRYqtkvb2I2SEFP0apht954oKdG3ncxyOgHRUkwgtxbCMAngzWo9+VWV3H3OlqeEOv7DdO2o0y95EvlHYb/qtosXPI2jC+6FPa+yl4xmLqcENRTUrU23dsmX3SyBEmZvML4dNeyC53B+mh7DUFtPFJFndxj2tGO9mTSDgy8eCmKG90AiJOMoxaLB2HpnDXN1sTiIcd3WraiE6ZCt4E54hKXvXHPyN52CHkxq1y/TeXHEq4X4MyHyDSRLHmzVs9pnwHM0ZLthKFNyvGfTvjiYokAWtNEuh74syt+m6Wietb6JvgibnnDj6uFKI3BbH4GUT9blsnMgug323bJ6bFvV4iESvz1fNnnUSokWQy5+fWzxPDohULgFzhDCpwov78Bp0E3t6DXSWnrUdNqpLbYKmXO1Hdbn+QH4B90p85UB1V5eSZgxPpUvZbIO4GPScil8K+dkDLdsFa1zypWNmlUN0Ns5H/iuzMuJql2QFYz+SnV1R1T+qywwqCNP9oswcLiAR3XnSacs52vd3PI9+0PZuoF6tVMWlvutsQ34IFZaAwIkdKigZcHumLBt/0KyFASBfN674n8FnHrHOQHU6oCeXkQA9kC8MtkvMb7fOLdzbTsD6SVojzZ64i9mDXxF+iLR9o52OxjIFzwLGRy/ivT/aAnHLZ3AsbnvslDjlQl2ADBFvf7xjmvFu0xlfK58TUpfVEkScFFapWJyKVybB4CRz1wKKz6n/a9581LpCVOWRsJa5p+j0zYcS2PfhmRf3RzwsDHeBjEVlIARbhxNKvmjdZyIidSdMMcsJHDRLE3bvo9kKfag0vRVKmuPLPc9FrACsz3vlkApcVQvzieHWoiP+foEvfj9+7Ti2tLfKdzVkMUmugZiZ46+7PKvIciiiuBPlyld0CCPTtTFHUOMO5dUfrUblX8K3awWiaNQFBS0J3iK08t1bgWfLhsKzsS32fRWugaqecwO9Rji9oHn+UuN8Nz9SgNxodroq9q7y/KHFxbqjCl62g25HN9zUa/s5wnIRwVAiWgTuOe3qGqjwp5m/GR8YVSSK/8mV9EL4AaF8d1uifdVA6wWSH1e/1UB8vcdU83P8ne3u1ho+Y/57WB7KnQaGaiD/108+wiAxNqMb2ex8on01VxdLKV1makXV3gzsvWaRevW8t/K11ZwYfo9g+guWADsA0JO0jWooiaupq1kNWrEheBdSRXBO7Jnb+56cTjPGwLpp7ZOHe/bSCJ4MGzPF3lK66LXhVo+rxvNjhoKVRjhGYxN4T8+AiRo3r+1KwdIGSrtODp3ri3JWAy6Eajp1Ukp9GaCbHSJFnYml84nKew7zLLe//ExQpjd4QAjMTvnbm+Ff6a1jf69QEVo0I33gI7/buwqgjiuvjeL6EYaMolKrKlHZHf/HwWbFbdID8T9aoyZJuCUd6YHaMPRAS6n5nvTwkRLlJ/f6wgyypUGZ22Bb1qGIb9SoPgSgIJkifUoewQW2EexqfoAsHXJVABLy+jp/SC4xzHZOSh42zU1k80HIgrnSOmu6T56F6gqy4Y2cZuZU8LXbO/01u8ifEz8yaXfEFSFdxE0TWl92OLKFtJZr9nNOBQQQr5FDGf6zB1/0CziG/5+PrUDgG3irzho6+7wXkc2CpxlBKOLWdjs3V/Lab6cURz1QZY4HYgUkJtm4U5OKUeO2+murlhC7SrnwyUtGrsD8NbCmI4SRHKPoeLBJQO/m3dRze5Ltr8N9IS7/ukPeOYe1O2agrmhH/JjYfz/l8Gmq8PGY+oavYp8I+2yKvGLD9kCxEgKcTeRh9AW/xPTLGsacrGKQCY+M76DfyLKxCZDiDY9xkBIKchxsMsn7FqZvRMMyJBHbqa3AKQyAN73NCSuFF5f1qDjARU/xqJFhOaKoR64c78oqh1GqOqEFbfNQIRw6WeFCGyW6v6p10uLdR7KXnR7+wub9aG992MpIBk0+gru74yO/WcA0vLdDEQIBwc+M0lmLB53ylsPtde3nliaC5ROHR1IS4LO8Q+3o0BHMr0my0bqFwwCAvZVXOFBHxXyUgrrmUTnZYVSQXNV6+MALBmmRU5yOzhhyHoEdj9YHZeyPpZkYc6DkJWCRYbFfmczNIs133KB9rlfug40w/hHa8pXyRyLaKQUMIUYEvt3Y4AQ==
@@ -0,0 +1 @@
ElIKBm9zX3VzYRIHQW1lcmljYRoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc3VzYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElMKB29zX2V1cm8SBkV1cm9wZRoKREVWX1BVQkxJQyI0aHR0cHM6Ly9vc2V1cm9kaXNwYXRjaC55dWFuc2hlbi5jb20vcXVlcnlfY3VyX3JlZ2lvbhJRCgdvc19hc2lhEgRBc2lhGgpERVZfUFVCTElDIjRodHRwczovL29zYXNpYWRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uElUKBm9zX2NodBIKVFcsIEhLLCBNTxoKREVWX1BVQkxJQyIzaHR0cHM6Ly9vc2NodGRpc3BhdGNoLnl1YW5zaGVuLmNvbS9xdWVyeV9jdXJfcmVnaW9uKpwQRWMyYhAAAABbrAvbhfIRHfaSCN24qQyVAAgAAMs68ZiMdPfEj41O2wBCYqGiC/WdovvJvaw4t3/m1zIYDrt3/ftK9GKFb7C+2E8FmaHqOnwjJYBg2wI1sXpGmuSxkeWw8Avr36wlNtQjhXNV9zoNKstuZYuheyLlpbPRbYZ3UA6/BzTVsjIhjR1lcqFrigQnpV6MgRR9KqxakCaffK6qIzMlodx4ZPKlqseQhCiyVAvLWQSRqCRcZipzotXsmgLQbpDFtRzhgukXPjfW5dAlzMwswPuu7ZQsf1AKipI34dVQLu6gtXthGgbjn89h/79VR5AokLCPGqIV7/2s+gHfykrjDtyp5rwCcmGQqwV3gHy5LGrHl8Zm12jNd7Qcng51ydqtX4xzet6J2iMF6Dw5nPd/hTyxn+i3Ttk6fop9rbCq3iNgEw3+0cSDal1I1ThYdVnMgPhZgQkZc5/SpTaR+8vfDzRIKbSSrrPSEgLnQvWZOOugXhNdyuiaBc8rJveno7vvktmnhDUF3xWi6osj75j2KghRrdHfDR3Zuh4COrGZDRBSKHft2AvfrxaMT9O8hPzzzYk0U2iicVCDlNP/8wqaT9Vqt1kHmruLxqh377iyp0mxKfNt0+SNRzLyRoyvOar/z3AT6TU9LRoCFrkcJpVsUN+2MVeT52PfMbv5O/Nw9sqsFDlofCJJ/EknY0wDc+tNarYOhDM67/ojn/p6W3ZPBJxb2wcF1TOh9dpAeZdCGJusqhMIj5lpoW8nENTFhkEgMUv2Lh5Z6WpeOAKAu9eDpBMhlRNCccDaNYUgo6TdVDtWxtPrS3NRYqtkvb2I2SEFP0apht954oKdG3ncxyOgHRUkwgtxbCMAngzWo9+VWV3H3OlqeEOv7DdO2o0y95EvlHYb/qtosXPI2jC+6FPa+yl4xmLqcENRTUrU23dsmX3SyBEmZvML4dNeyC53B+mh7DUFtPFJFndxj2tGO9mTSDgy8eCmKG90AiJOMoxaLB2HpnDXN1sTiIcd3WraiE6ZCt4E54hKXvXHPyN52CHkxq1y/TeXHEq4X4MyHyDSRLHmzVs9pnwHM0ZLthKFNyvGfTvjiYokAWtNEuh74syt+m6Wietb6JvgibnnDj6uFKI3BbH4GUT9blsnMgug323bJ6bFvV4iESvz1fNnnUSokWQy5+fWzxPDohULgFzhDCpwov78Bp0E3t6DXSWnrUdNqpLbYKmXO1Hdbn+QH4B90p85UB1V5eSZgxPpUvZbIO4GPScil8K+dkDLdsFa1zypWNmlUN0Ns5H/iuzMuJql2QFYz+SnV1R1T+qywwqCNP9oswcLiAR3XnSacs52vd3PI9+0PZuoF6tVMWlvutsQ34IFZaAwIkdKigZcHumLBt/0KyFASBfN674n8FnHrHOQHU6oCeXkQA9kC8MtkvMb7fOLdzbTsD6SVojzZ64i9mDXxF+iLR9o52OxjIFzwLGRy/ivT/aAnHLZ3AsbnvslDjlQl2ADBFvf7xjmvFu0xlfK58TUpfVEkScFFapWJyKVybB4CRz1wKKz6n/a9581LpCVOWRsJa5p+j0zYcS2PfhmRf3RzwsDHeBjEVlIARbhxNKvmjdZyIidSdMMcsJHDRLE3bvo9kKfag0vRVKmuPLPc9FrACsz3vlkApcVQvzieHWoiP+foEvfj9+7Ti2tLfKdzVkMUmugZiZ46+7PKvIciiiuBPlyld0CCPTtTFHUOMO5dUfrUblX8K3awWiaNQFBS0J3iK08t1bgWfLhsKzsS32fRWugaqecwO9Rji9oHn+UuN8Nz9SgNxodroq9q7y/KHFxbqjCl62g25HN9zUa/s5wnIRwVAiWgTuOe3qGqjwp5m/GR8YVSSK/8mV9EL4AaF8d1uifdVA6wWSH1e/1UB8vcdU83P8ne3u1ho+Y/57WB7KnQaGaiD/108+wiAxNqMb2ex8on01VxdLKV1makXV3gzsvWaRevW8t/K11ZwYfo9g+guWADsA0JO0jWooiaupq1kNWrEheBdSRXBO7Jnb+56cTjPGwLpp7ZOHe/bSCJ4MGzPF3lK66LXhVo+rxvNjhoKVRjhGYxN4T8+AiRo3r+1KwdIGSrtODp3ri3JWAy6Eajp1Ukp9GaCbHSJFnYml84nKew7zLLe//ExQpjd4QAjMTvnbm+Ff6a1jf69QEVo0I33gI7/buwqgjiuvjeL6EYaMolKrKlHZHf/HwWbFbdID8T9aoyZJuCUd6YHaMPRAS6n5nvTwkRLlJ/f6wgyypUGZ22Bb1qGIb9SoPgSgIJkifUoewQW2EexqfoAsHXJVABLy+jp/SC4xzHZOSh42zU1k80HIgrnSOmu6T56F6gqy4Y2cZuZU8LXbO/01u8ifEz8yaXfEFSFdxE0TWl92OLKFtJZr9nNOBQQQr5FDGf6zB1/0CziG/5+PrUDgG3irzho6+7wXkc2CpxlBKOLWdjs3V/Lab6cURz1QZY4HYgUkJtm4U5OKUeO2+murlhC7SrnwyUtGrsD8NbCmI4SRHKPoeLBJQO/m3dRze5Ltr8N9IS7/ukPeOYe1O2agrmhH/JjYfz/l8Gmq8PGY+oavYp8I+2yKvGLD9kCxEgKcTeRh9AW/xPTLGsacrGKQCY+M76DfyLKxCZDiDY9xkBIKchxsMsn7FqZvRMMyJBHbqa3AKQyAN73NCSuFF5f1qDjARU/xqJFhOaKoR64c78oqh1GqOqEFbfNQIRw6WeFCGyW6v6p10uLdR7KXnR7+wub9aG992MpIBk0+gru74yO/WcA0vLdDEQIBwc+M0lmLB53ylsPtde3nliaC5ROHR1IS4LO8Q+3o0BHMr0my0bqFwwCAvZVXOFBHxXyUgrrmUTnZYVSQXNV6+MALBmmRU5yOzhhyHoEdj9YHZeyPpZkYc6DkJWCRYbFfmczNIs133KB9rlfug40w/hHa8pXyRyLaKQUMIUYEvt3Y4AQ==
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAz/fyfozlDIDWG9e3Lb29+7j3c66wvUJBaBWP10rB9HTE6prj
fcGMqC9imr6zAdD9q+Gr1j7egvqgi3Da+VBAMFH92/5wD5PsD7dX8Z2f4o65Vk2n
VOY8Dl75Z/uRhg0Euwnfrved69z9LG6utmlyv6YUPAflXh/JFw7Dq6c4EGeR+Kej
FTwmVhEdzPGHjXhFmsVt9HdXRYSf4NxHPzOwj8tiSaOQA0jC4E4mM7rvGSH5GX6h
ma+7pJnl/5+rEVM0mSQvm0m1XefmuFy040bEZ/6O7ZenOGBsvvwuG3TT4FNDNzW8
Dw9ExH1l6NoRGaVkDdtrl/nFu5+a09Pm/E0ElwIDAQABAoIBAQCtH17Cck+KJQYX
j29xqG4qykNUDawbILiKCMkBE7553Wq/UcjmuuR4bVnML8ucS3mgR/BgHV3l8vUK
nxvqRx/oGZkWNazbiuwL+ThAblLWqrEmYuZVCoQcAnvkT8tIqDWz7fhDEuZnnkMz
ZcATIZzgZUSa5IfP3u3rP+MrVbyaCdzJEeI0Yrv1XT+M5ddkKQrYgqC5kRiYi/Lj
NcLJhqSVt8p37CdJx1PGHFjKKb4MZpANlNRgeTtWpGVfS0PJLzaiI1NyPSJv7xWZ
gVhbK9+wQxqSG6KmZ4vpEvRI1zKiov5BsAFN+GfuD5mpn1Xo9CpzTfj/sO13VpHH
+Mt80+yBAoGBAPYXVEcXug5zqkqXup4dp1S05saz1zWPhUhQm+CrbhgeTqpjngJJ
EB79qMrGmyki0P/cGtbTcrHf8+i7gDlIGW0OMb4/jn4f5ACVD00iyvkHSGPn0Aim
MoNOMbkGot7SkSnncwxXdawwDyTu2dofXuBr72+GYqgRAG52IuA0C0pRAoGBANhX
p/UyW/htB27frKch/rTKQKm12kBV20AkkRUQUibiiQyWueWKs+5bVaW5R5oDIhWx
qftJtnEFWUvWaTHpHsB/bpjS3CJ6WknqNbpa3QIScpV1uw8V+Etz/K2/ftjyZzFo
nqc+Jud5364xFdIlOsRj9gZnK83Wcui6EFxAer5nAoGBAJzTzzSjLUHqejqhKR98
nFeCFZPJpjuO5AxqunvaJAYgwlcZtueT8j8dvgTDvrvfYTu85CnFhNFQfFrzqspW
ZUW3hwHL9R3xatboJ2Er7Bf5iSuJ3my0pXpCSbO1Q/QmUrZWtl3GGsqJsg0CXjkA
RvFUN7ll9ddPRmwewykIYa2RAoGAcmKuWFNfE1O6WWIELH4p6LcDR3fyRI/gk+KB
nyx48zxVkAVllrsmdYFvIGd9Ny4u6F9+a3HG960HULS1/ACxFMCL3lumrsgYUvp1
m+mM7xqH4QRVeh14oZRa5hbY36YS76nMMMsI0Ny8aqJjUjADCXF81FfabkPTj79J
BS3GeEMCgYAXmFIT079QokHjJrWz/UaoEUbrNkXB/8vKiA4ZGSzMtl3jUPQdXrVf
e0ofeKiqCQs4f4S0dYEjCv7/OIijV5L24mj/Z1Q4q++S5OksKLPPAd3gX4AYbRcg
PS4rUKl1oDk/eYN0CNYC/DYV9sAv65lX8b35HYhvXISVYtwwQu/+Yg==
-----END RSA PRIVATE KEY-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA02M1I1V/YvxANOvLFX8R7D8At40IlT7HDWpAW3t+tAgQ7sqj
CeYOxiXqOaaw2kJhM3HT5nZll48UmykVq45Q05J57nhdSsGXLJshtLcTg9liMEoW
61BjVZi9EPPRSnE05tBJc57iqZw+aEcaSU0awfzBc8IkRd6+pJ5iIgEVfuTluani
zhHWvRli3EkAF4VNhaTfP3EkYfr4NE899aUeScbbdLFI6u1XQudlJCPTxaISx5Zc
wM+nP3v242ABcjgUcfCbz0AY547WazK4bWP3qicyxo4MoLOoe9WBq6EuG4CuZQrz
Knq8ltSxud/6chdg8Mqp/IasEQ2TpvY78tEXDQIDAQABAoIBAQC4uPsYk4AsSe75
0Au6Dz7kSfIgdDhJ44AisvTmfLauMFZLtfxfjBDhCwTxuD7XnCZAxHm97Ty+AqSp
Km/raQQsvtWalMhBqYanzjDYMRv2niJ1vGjm3WrQxBaEF+yOtvrZsK5fQTslqInI
qknIQH7fgjazJ7Z28D18sYNj37qfFWSSymgFo+SoS/BKEr200lpRA/oaGXiHcyIO
jJidP6b7UGes7uhMXUvLrfozmCsSqslxXO5Uk5XN/fWl4LxCGX7mpNfPZIT5YBSj
HliFkNlxIjyJg8ORLGi82M2cuyxp39r93F6uaCjLtb+rdwlGur7npgXUkKfWQJf9
WE7uar6BAoGBAPXIuIuYFFUhqNz5CKU014jZu6Ql0z5ZA08V84cTJcfLIK4e2rqC
8DFTldA0FtVfOGt0V08H/x2pRChGOvUwGG5nn9Dqqh6BjByUrW4z2hnXzT3ZuSDh
6eapiCB1jl9meJ0snhF2Ps/hqWGL2b3SkCCe90qVTzOVOeLO6YUCIOq9AoGBANws
fQkAq/0xw8neRGNTrnXimvbS+VXPIF38widljubNN7DY5cIFTQJrnTBWKbuz/t9a
J8QX6TFL0ci/9vhPJoThfL12vL2kWGYgWkWRPmqaBW3yz7Hs5rt+xuH3/7A5w5vm
kEg1NZJgnsJ0rMUTu1Q6PM5CBg6OpyHY4ThBb8qRAoGAML8ciuMgtTm1yg3CPzHZ
xZSZeJbf7K+uzlKmOBX+GkAZPS91ZiRuCvpu7hpGpQ77m6Q5ZL1LRdC6adpz+wkM
72ix87d3AhHjfg+mzgKOsS1x0WCLLRBhWZQqIXXvRNCH/3RH7WKsVoKFG4mnJ9TJ
LQ8aMLqoOKzSDD/JZM3lRWkCgYA8hn5Y2zZshCGufMuQApETFxhCgfzI+geLztAQ
xHpkOEX296kxjQN+htbPUuBmGTUXcVE9NtWEF7Oz3BGocRnFrbb83odEGsmySXKH
bUYbR/v2Ham638UOBevmcqZ3a2m6kcdYEkiH1MfP7QMRqjr1DI1qpfvERLLtOxGu
xU5WAQKBgQCaVavyY6Slj3ZRQ7iKk9fHkge/bFl+zhANxRfWVOYMC8mD2gHDsq9C
IdCp1Mg0tJpWLaGgyDM1kgChZYsff4jRxHC4czvAtoPSlxWXF2nL31qJ3vk2Zzzc
a4GSHAInodXBrKstav5SIKosWKT2YysxgHlA9Sm2f4n09GjFbslEHg==
-----END RSA PRIVATE KEY-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyaxqjPJP5+Innfv5IdfQqY/ftS++lnDRe3EczNkIjESWXhHS
OljEw9b9C+/BtF+fO9QZL7Z742y06eIdvsMPQKdGflB26+9OZ8AF4SpXDn3aVWGr
8+9qpB7BELRZI/Ph2FlFL4cobCzMHunncW8zTfMId48+fgHkAzCjRl5rC6XT0Yge
6+eKpXmF+hr0vGYWiTzqPzTABl44WZo3rw0yurZTzkrmRE4kR2VzkjY/rBnQAbFK
KFUKsUozjCXvSag4l461wDkhmmyivpNkK5cAxuDbsmC39iqagMt9438fajLVvYOv
pVs9ci5tiLcbBtfB4Rf/QVAkqtTm86Z0O3e7DwIDAQABAoIBAQCyma226vTW35LE
N5zXWuAg+hhcxk6bvofWMUMXKvGF/0vHPTMXlvuSkDeDNa4vBivneRthBNPMgb3q
DuTWxrogQMOOI8ZdhY3DFexfDvcQD2anDJuSqSmg9Nd36q+yxk3xIoXB5Ilo23dd
vTnJXHhsBNovv7zRLO134cAHFqDoKzt5EEHre0skUcn6HjHOek6c53jvpKr5LSrr
iwx5gMuY/7ZSIUDo9WGY70qbQFGY6bOlX9x8uNjcFF+7SztEVQ+vhJ/+7EvwqaJr
ysweo0l91TKM9WaMuwoucKeceVWuynEw6GGTw8UTLtltekLGe6bS8YxY8fVwnKkT
RwJYwAJRAoGBAP2rhcfOA+1Ja37hUHKebfp9rHsex4+pGyt3Kdu7WdqOn4sexmya
BuiHQcUchPDVla/ruQZ20+8LHgzBDo0m8sY7gpf715UV9NSVIRD0wu26SKRklOFz
J4HBOwU9hBGLSnRUJzyvVlt5O7E9hAv61SCrvWBEcow2YnKNQLwvjMVJAoGBAMuG
oSb3A/ulqtp2zpxVAclYe/bSItZZTOUWP6Vb4hOiHxIJ0n1H9ap6grOYkJ/Yn4gg
yYzKm/noF1wXP7Rj/xOahnvMkzhGdmOabvE9LH5HwQTWxBBWTkZzgBbYtbg+J5MT
cKqJaychSRjJj+xX+d90rtlSu/c27chlSRKAHXWXAoGAFTcIHDq9l1XBmL3tRXi8
h+uExlM/q2MgM5VmucrEbAPrke4D+Ec1drMBLCQDdkTWnPzg34qGlQJgA/8NYX61
ZSDK/j0AvaY1cKX8OvfNaaZftuf2j5ha4H4xmnGXnwQAORRkp62eUk4kUOFtLrdO
pcnXL7rpvZI6z4vCszpi0okCgYEAp3lZEl8g/+oK9UneKfYpSi1tlGTGFevVwozU
QpWhKta1CnraogycsnOtKWvZVi9C1xljwF7YioPY9QaMfTvroY3+K9DjM+OHd96U
fB4Chsc0pW60V10te/t+403f+oPqvLO6ehop+kEBjUwPCkQ6cQ3q8xmJYpvofoYZ
4wdZNnECgYBwG8Vrv7Z+kX9Zuh1FvcRoY57bYLU0cWW92SA3Nvi8pZOIEaLHrQyZ
pvvaLIicR1m9+KsOAmii7ru0zL7KsrGW+5migQsaDi4gzahKQpad/R7MLKi/L53r
Ymo0aZKARLHW82GbomQ0zxdRoo9vaqfGNpXkxyyt3k3GGDunmrskYw==
-----END RSA PRIVATE KEY-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAsJbFp3WcsiojjdQtVnTuvtawL2m4XxK93F6lCnFwcZqUP39t
xFGGlrogHMqreyawIUN7E5shtwGzigzjW8Ly5CryBJpXP3ehNTqJS7emb+9LlC19
Oxa1eQuUQnatgcsd16DPH7kJ5JzN3vXnhvUyk4Qficdmm0uk7FRaNYFi7EJs4xyq
FTrp3rDZ0dzBHumlIeK1om7FNt6Nyivgp+UybO7kl0NLFEeSlV4S+7ofitWQsO5x
YqKAzSzz+KIRQcxJidGBlZ1JN/g5DPDpx/ztvOWYUlM7TYk6xN3focZpU0kBzAw/
rn94yW9z8jpXfzk+MvWzVL/HAcPy4ySwkay0NwIDAQABAoIBADzKWpawbVYEHaM4
lLb7oCjAPXzE9zx7djLDvisfLCdfoINPedkoe52ty1o+BtRpWB7LXTY9pFic1FLE
5wvyy6zyf8hH3ZsysqNhWFxhh4FnLmx/UGokAir+anaK5mYVJ1vQtxzjlV1HAbQs
kRyrklKoHDdRFqiFXOwiib97oDNWhD+RxfyGwwJnynZZSXdLbLSiz/QHQNr/+Ufk
KRBaxt0CfU7mOLZxoy6fNAxHdBcBJPHCyh+aDvEbix7nSncSU8Ju/48YJ8DrglbZ
sXCYoA5Uz8NMDuaEMgoNWCFQVoEcRkEUoaH7BlWd3UUFRPnDZ1B4BmkrVoRE8a58
3OqSwakCgYEA19wQUISXtpnmCrEZfbyZ6IwOy8ZCVaVUtbTjVa8UyfNglzzJG3yz
cXU3X35v5/HNCHaXbG2qcbQLThnHBA+obW3RDo+Q49V84Zh1fUNH0ONHHuC09kB/
/gHqzn/4nLf1aJ2O0NrMyrZNsZ0ZKUKQuVCqWjBOmTNUitcc8RpXZ8sCgYEA0W09
POM/It7RoVGI+cfbbgSRmzFo9kzSp5lP7iZ81bnvUMabu2nv3OeGc3Pmdh1ZJFRw
6iDM6VVbG0uz8g+f8+JT32XdqM7MJAmgfcYfTVBMiVnh330WNkeRrGWqQzB2f2Wr
+0vJjU8CAAcOWDh0oNguJ1l1TSyKxqdL8FsA38UCgYEAudt1AJ7psgOYmqQZ+rUl
H6FYLAQsoWmVIk75XpE9KRUwmYdw8QXRy2LNpp9K4z7C9wKFJorWMsh+42Q2gzyo
HHBtjEf4zPLIb8XBg3UmpKjMV73Kkiy/B4nHDr4I5YdO+iCPEy0RH4kQJFnLjEcQ
LT9TLgxh4G7d4B2PgdjYYTkCgYEArdgiV2LETCvulBzcuYufqOn9/He9i4cl7p4j
bathQQFBmSnkqGQ+Cn/eagQxsKaYEsJNoOxtbNu/7x6eVzeFLawYt38Vy0UuzFN5
eC54WXNotTN5fk2VnKU4VYVnGrMmCobZhpbYzoZhQKiazby/g60wUtW9u7xXzqOd
M/428YkCgYBwbEOx1RboH8H+fP1CAiF+cqtq4Jrz9IRWPOgcDpt2Usk1rDweWrZx
bTRlwIaVc5csIEE2X02fut/TTXr1MoXHa6s2cQrnZYq44488NsO4TAC26hqs/x/H
bVOcX13gT26SYngAHHeh7xjWJr/KgIIwvcvgvoVs6lu7a8aLUvrOag==
-----END RSA PRIVATE KEY-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAxbbx2m1feHyrQ7jP+8mtDF/pyYLrJWKWAdEv3wZrOtjOZzeL
GPzsmkcgncgoRhX4dT+1itSMR9j9m0/OwsH2UoF6U32LxCOQWQD1AMgIZjAkJeJv
FTrtn8fMQ1701CkbaLTVIjRMlTw8kNXvNA/A9UatoiDmi4TFG6mrxTKZpIcTInvP
EpkK2A7Qsp1E4skFK8jmysy7uRhMaYHtPTsBvxP0zn3lhKB3W+HTqpneewXWHjCD
fL7Nbby91jbz5EKPZXWLuhXIvR1Cu4tiruorwXJxmXaP1HQZonytECNU/UOzP6GN
Ldq0eFDE4b04Wjp396551G99YiFP2nqHVJ5OMQIDAQABAoIBAQDEeYZhjyq+avUu
eSuFhOaIU4/ZhlXycsOqzpwJvzEz61tBSvrZPA5LSb9pzAvpic+7hDH94jX89+8d
NfO7qlADsVNEQJBxuv2o1MCjpCRkmBZz506IBGU60Kt1j5kwdCEergTW1q375z4w
l8f7LmSL2U6WvKcdojTVxohBkIUJ7shtmmukDi2YnMfe6T/2JuXDDL8rvIcnfr5E
MCgPQs+xLeLEGrIJdpUy1iIYZYrzvrpJwf9EJL3D0e7jkpbvAQZ8EF9YhEizJhOm
dzTqW4PgW2yUaHYd3q5QjiILy7AC+oOYoTZln3RfjPOxl+bYjeMOWlqkgtpPQkAE
4I64w8RZAoGBAPLR44pEkmTdfIIF8ZtzBiVfDZ29bT96J0CWXGVzp8x6bSu5J5jl
s7sP8DEcjGZ6vHsLGOvkcNxzcnR3l/5HOz6TIuvVuUm36b1jHltq1xZStjGeKZs1
ihhJSu2lIA+TrK8FCRnKARJ0ughXGNZFItgeM230Sgjp2RL4ISXJ724XAoGBANBy
S2RwNpUYvkCSZHSFnQM/jq1jldxw+0p4jAGpWLilEaA/8xWUnZrnCrPFF/t9llpb
dTR/dCI8ntIMAy2dH4IUHyYKUahyHSzCAUNKpS0s433kn5hy9tGvn7jyuOJ4dk9F
o1PIZM7qfzmkdCBbX3NF2TGpzOvbYGJHHC3ssVr3AoGBANHJDopN9iDYzpJTaktA
VEYDWnM2zmUyNylw/sDT7FwYRaup2xEZG2/5NC5qGM8NKTww+UYMZom/4FnJXXLd
vcyxOFGCpAORtoreUMLwioWJzkkN+apT1kxnPioVKJ7smhvYAOXcBZMZcAR2o0m0
D4eiiBJuJWyQBPCDmbfZQFffAoGBAKpcr4ewOrwS0/O8cgPV7CTqfjbyDFp1sLwF
2A/Hk66dotFBUvBRXZpruJCCxn4R/59r3lgAzy7oMrnjfXl7UHQk8+xIRMMSOQwK
p7OSv3szk96hy1pyo41vJ3CmWDsoTzGs7bcdMl72wvKemRaU92ckMEZpzAT8cEMC
cWKLb8yzAoGAMibG8IyHSo7CJz82+7UHm98jNOlg6s73CEjp0W/+FL45Ka7MF/lp
xtR3eSmxltvwvjQoti3V4Qboqtc2IPCt+EtapTM7Wo41wlLCWCNx4u25pZPH/c8g
1yQ+OvH+xOYG+SeO98Phw/8d3IRfR83aqisQHv5upo2Rozzo0Kh3OsE=
-----END RSA PRIVATE KEY-----
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
lt1L ܟ.\pXP"ƀ(a
+1
View File
@@ -0,0 +1 @@
OPrCJaJsJJJXI3$DQNduVQuB5s5jgf47mmABUJFZoKJUt5YcIt0wPCBcCCpuQ4dQz19rae5FeHYRpmb8WsjkIeEpAQi3uJlIgObMsL$Fmv1kPav7dDPBWKJuyFEOa2KpN1EGvWlJOqufR64SDYhIocM3HLT5VsLo$Bde3c2jsVPwygAGGcZU1rZCTaOo$2blfEMdpviC75gNDZzdGjAiOBahjs$!9fZKtNF2iI4UnGSE2aNXlIz9To8L9fSeA9mhJ2F0qTKa8UjxHiSurW0bSzloUUN83sT9UFBNz$c2IVIP!asMffifiTVmlb0MJ6jYUB!rMSR77SVmdNDlMRz1VnjR9xEENS1DnqUQSYq2JJAQJIZL6j4ABF!Dm5NisAf58vaRhY4Uz6zXEf$IZExmbToDsb49X6ATh7zZExCeddR8iDBFpdOAiveE0bO8pOgo!zYmt0$5sN8aJpDK10XbXkm21$Lvppj8gAV9SjUjT5j9luqok!PkTQS59zG1I$JSDzs1jkoaNTiO2LL0rJSHaTX2EMI4KUiMp1Tgpqsy$lSv89CVFg5AzODZX8TA5ne4lHT0aazfxQa354RHYRGe5OllgcuZVn88gWjmD3nUHtzhCpK0siGWqPvOlPX!OYUaJIFwaSy7v!K5Q2nX8qCjGAHeu1$hs0cdmn69r1pws0APbZ7BNm018poX7YxjiOBRV0tEKRcJbkKxad$t5dJIO9eJhKvk0l9xz8KjOF9u2NHhy5yQrHZWenJYDAIrmheIz2Q!vO7EePv2U6ACm5OJS8dDyWBMkFNuSoW4zv3RTIfdKw$!hd2bmSFQFic6jFNPeag53tpBPCiy3lNgIT0hgytmV9CBwZl!S$GzYOxv0buJ2P2Vvfah!bg4$nfAHKoHer4zTcYN4w3eSIBAOCKJW7uJ5Sk1uOcxVwBklzbN6l7m3Yhcyw44y76P7q$zeK4NuEpaK7izZTqVhhXL1piyu5A$lqWlkxCDbq0H3haa79hX0npzRphBlt7syl3UM2MNJS7ZgYYk76kNZSO!ZRaBz19phzewChIaE2Seub1h0U3JJI7PH1a5Ej6ozVaFUCSGvULtLeQeq!drnnvnqCfK4UZmbNYrHUPBMif2ttgHygUxSRQpMHx$tstSzESKn6XuQo4k!Fuiq5Du1ClQtPE$fgyYuzl4SDkw0RJYBY5dl227vyhKQzyxrSQQUQsKy08xM2LRZUymBp$oDiWBF4ZJv8iE5fDMuwbTRSfPX45uBKiDYSYcOXukcDeYYXturEpUD4VaC0RYF5KVBos1kTT2sQFq$cbkN9jiwmhBC7Yea6LEw64GOq67dlf$yvJmw5L8Sxb!Yd2u$PX2cQh4d19LpvkLTaqhofHmYP9agwBwxLQ4z4z93gkPmgKdGir40bV$nbDNcve6juK4Ct6R$Xid6nkdWM$9RWvQE7m7S7Rf5X8dWEHn86g6cLfi4saH5jOi1abRRYlBixuz3yiRQy!IOOvoH0RVqpamMy03xqGUscyuxjrTYhT1AMBmDaj$l9qgIMN3sS5EONMxMEFXvTo1!3vNCRhuahOt5bsN7WyLpiQiKb76onr8!j9l1QDwNm74OIXSQ2T1OgQeppxh3B5vkx4c8$t8$GfBCq4Q!dDZWu!PNdbRk2sWgGnD$OVXHJgvQZPkJPdQJSm7p85tlx3sh7vCDVBauxtbHz$iXqS5HIj8PX!ZmG20iYl5$H$ZA1e$nYfeD$rafvfJDYmKH9sgLPtP2N5LqNI6SIzrihs7U0ST7iTDzJrw9tMGLFrmtEZ!G8mlzMRBLUsTMFyTa5!r043dtVxSsNdcor2xkUhvJVjjnfuyNNmbd5GoxUxhoyqb0Xx5RuwGFevo!PEZ$FHiNjLzds0LXH20$6QXhWa2k9e4VOO6Far4iutjtXFf!qMQCpeBH2TcpGbbjjziBj$pKzCYhbLnUYL8lBN6sP381fn8MgT2M4WKAXm!N83VRULxtxx7spdeQKvM5Fx6CcMThAZXDOtcoqXMWXOaXt9sY6xSsnbq4ZvrPi8s4v17p95iCpNoJY2KZP4qiWgfcxir1iOYmWIU9$DCSb3jmJz6QzCVCdvy3XKLjropmAce89vi9U4W9v7abEwdKD1!4zdUBMlhRSLo3AX4uLpdDbc7!icy$tdFrrPhBV6VvZDoYXwYbCD91z6I$thUPyk50ct9MVx$dXRXEW8rpdVavkcuae1wOLgC36naRV$P3ciba8qoqFQqewxnhBdUi9rz33lo86lS6K6PPtOD!00D0SY9m48yyjUgqF9uYFq9tAXMoZZ6LruqS5WcVHcVEw6LocLJvyWZwy!8zKFtA5whK08PbAnz2BCTnM$$YPwssO!!$l5v1qz61VXerSg6V0$$eA1JyiZRF$0ErSFtwM51gnD7qXnlIMQ8krXmuUcWdbrvTKUOsY!6t8MfYvlPZDsJz$WaJDCT2TLHqjJRlGvr7CiBncg5S1!5hS6eQAhDw3fcw3ghO5fcIeDT!doP1E0W!MPbqSzWrrGXdYbtcfj5iqShrnDkizgD3E7q8zbBMa9kL3NaCBt2UP!jOULCeTIC3LxrSmhKCgHnhrsuyPaJg6e4jGr0n1Xj1K2gKybs4AIAOyAfEPqZEt$6WSrtp$3O
+24
View File
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID8zCCAtugAwIBAgIUYL/dZNSITnL3uvCDT62CVzoHeKMwDQYJKoZIhvcNAQEL
BQAwgYgxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwI
U2hhbmdoYWkxDzANBgNVBAoMBmZsc3dsZDEPMA0GA1UECwwGZmxzd2xkMQ8wDQYD
VQQDDAZmbHN3bGQxIDAeBgkqhkiG9w0BCQEWEWZsc3dsZEBmbHN3bGQuY29tMB4X
DTIyMDQyNjE3MjYwN1oXDTMyMDQyMzE3MjYwN1owgYgxCzAJBgNVBAYTAkNOMREw
DwYDVQQIDAhTaGFuZ2hhaTERMA8GA1UEBwwIU2hhbmdoYWkxDzANBgNVBAoMBmZs
c3dsZDEPMA0GA1UECwwGZmxzd2xkMQ8wDQYDVQQDDAZmbHN3bGQxIDAeBgkqhkiG
9w0BCQEWEWZsc3dsZEBmbHN3bGQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAp4bjl/JavfVApTELsgNhGxwlXY+Z4SoVz8a+KVogdZf0fdMt62ob
YMwg7mg9r5dUmfvfvHhtFjCjviR+sKkS6Dgfc+xwS2ap4DWNwk/7kpmDr/QYTqb9
XO8kUAQ+zPxU0rfPE8KbdCvTlTh4opCMWde8C3BvWExNcKBgb132pHlZNwJTDs7G
laa1T9aSoDz+I9qb91acKnWsDm9NumQ86L1pJgZ+DylzBkVAwTHbMgURLM6hGGmW
RHaYKOnHb/s91YxgO1pN56IL6M2tZQuydt8pMC1Tt3z8yZR2nqAPVvF3j0U1Xh2+
X9iKL9zAsCXgw+nQt/s9QWFP70Pr95hQ8wIDAQABo1MwUTAdBgNVHQ4EFgQU0HwJ
oRLZWEVg9itEpy1VI04MefkwHwYDVR0jBBgwFoAU0HwJoRLZWEVg9itEpy1VI04M
efkwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAK2vphvnVGONT
GBdb8khlPGVf+2caAy5+LrgxX77wQy4aRO//e8r040awzkhN4C8eC2ozGLifzTKy
zxNKWNp163c4v2i0PGx4gdAS7yfLMklUndntiV/uPdk4jZ7Vr4eTciN+Sf0FdUUR
K7tpq0HNHrI6q+29B8Eq1ClkBYAmJqnHllu1EmpNReXkPOOaadpv2pjoJDmIWumI
wCKs4uGZburgX9jBsFHuyRDYOkG+1OGL4zKZClMuEFNodOa3zm4WrBPzWM3WOBZ9
uHLCXkRnce192OopnaytxEEoxuKCQ3lQZzrcuoz1wRfczTBh2pUU4YR1L6oBNuG+
oC9MpDAcmw==
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAp4bjl/JavfVApTELsgNhGxwlXY+Z4SoVz8a+KVogdZf0fdMt
62obYMwg7mg9r5dUmfvfvHhtFjCjviR+sKkS6Dgfc+xwS2ap4DWNwk/7kpmDr/QY
Tqb9XO8kUAQ+zPxU0rfPE8KbdCvTlTh4opCMWde8C3BvWExNcKBgb132pHlZNwJT
Ds7Glaa1T9aSoDz+I9qb91acKnWsDm9NumQ86L1pJgZ+DylzBkVAwTHbMgURLM6h
GGmWRHaYKOnHb/s91YxgO1pN56IL6M2tZQuydt8pMC1Tt3z8yZR2nqAPVvF3j0U1
Xh2+X9iKL9zAsCXgw+nQt/s9QWFP70Pr95hQ8wIDAQABAoIBAD7rNHOO/HG3uO3R
+9iB2Gi8K3R2SI7+pW6B8E3LocFIrvTK6cYu9dVnFT81O2XFamri5Gb+u8nHvtfd
EQ/8kDNTUMzTEmHfTxH8Sx+dtpiau5DMFo0Dvsi2sGa1EXkflCQIEOgVARmilDbJ
HNXBgFUF54RMWCVLkxInydBJ9CZVKUVgN5xmUTnLOEINbcn3V8d2aLwSYjECW5Gm
22QzrA05dKvmc6Io776Sc2fNDnJHTkHMrxH+kQ+c2jnHLWvdMb3aCJh/4JDAF/Iq
CZ6EsXBBYJ7C3ZRMata3LEgGU44L2oOZpUcd+RNldcvaBN8T86hxiyHddC9DC+AE
+svJIgECgYEA3H2EBQeVDjJ6PGE/elHJwRyWWtb9xxd7hbJkDfqw+SMEX2pYKnf5
tftXKbPY+U09Sm78Mbdac7W+O7IfD1e00/EDIcjdmq4S2CMGNtQ4GQxfxsFzg2LH
ujtVtx/xuvCNQbgNOEXl+/127AQpSDkhPxD2Tm1WoH72UXS82Ydq9HMCgYEAwoHF
ucrscqPRsg9AVb0UbecbJjRjQD+BudGmOkm0d/QCsjANRJYpSoQ9jP39uqYWlh4f
JAe+H89UWdDTVtFWL14+//nLNqPeCAu70RYt+xajvGAWUCRYaZvECBQW5stzHnHv
p2LG6TSyRbs10vBHxSkxgDe1Ng5+7jjJe09KkYECgYASu5c71ikTy7YW6yw5eDlr
7sHXdeyZvaUA9ucJSQNAJ3l3odFbylWs4G3HXUBR7f4HFObYUnuc2RQQflGlPA5g
81kQxcAOJDv1oQQmJGGfvy1j9Yua3gmaCPB/XndrKoTV0I1O+qFPh3lTFAdt22y3
rvk+MIvrlt3WjdR9psOvgQKBgGOvhtq1uYD3nJ0ZW+uVQEcjTrLB3qwq4B2P6RWu
eKORl2AjaGliXD8ojzMXaVajkKfXQDaDEVnUNHLjp6yzFOyp7LfcGd4jFcQh31xF
dcNd0wTUahsgxX86qblKMoKOeq17z0uGQFN9AnDiha9aHi5Z8li4NFNEEqGc0QY1
mQ4BAoGALmY+k+sL3jdjZ7XwJTuCfPka/UQP32osWzZMCvalts5I73Osyr9+o3sC
gptDMTOmUIh6xxY2pshM6i6etKgyDA7r9utUe+w6pvMIrDG1uZva/2oj0jzQrRq5
X9F3C/MJVMcVSI6Uk+rvBb8QtltKovbN/JX+nlKrIp2FDNuj8zg=
-----END RSA PRIVATE KEY-----
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -0,0 +1,38 @@
package controller
import (
"flswld.com/logger"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
)
func (c *Controller) headDataVersions(context *gin.Context) {
context.Header("Content-Type", "application/octet-stream")
context.Header("Content-Length", "514")
context.Status(http.StatusOK)
}
func (c *Controller) getDataVersions(context *gin.Context) {
dataVersions, err := ioutil.ReadFile("static/data_versions")
if err != nil {
logger.LOG.Error("open data_versions error")
return
}
context.Data(http.StatusOK, "application/octet-stream", dataVersions)
}
func (c *Controller) headBlk(context *gin.Context) {
context.Header("Content-Type", "application/octet-stream")
context.Header("Content-Length", "14103")
context.Status(http.StatusOK)
}
func (c *Controller) getBlk(context *gin.Context) {
blk, err := ioutil.ReadFile("static/29342328.blk")
if err != nil {
logger.LOG.Error("open 29342328.blk error")
return
}
context.Data(http.StatusOK, "application/octet-stream", blk)
}
+147
View File
@@ -0,0 +1,147 @@
package controller
import (
"encoding/base64"
"flswld.com/common/config"
"flswld.com/light"
"flswld.com/logger"
"gate-hk4e/dao"
"gate-hk4e/region"
"github.com/gin-gonic/gin"
pb "google.golang.org/protobuf/proto"
"net/http"
"strconv"
)
type Controller struct {
dao *dao.Dao
rpcUserConsumer *light.Consumer
regionListBase64 string
regionCurrBase64 string
signRsaKey []byte
encRsaKeyMap map[string][]byte
pwdRsaKey []byte
}
func NewController(dao *dao.Dao, rpcUserConsumer *light.Consumer) (r *Controller) {
r = new(Controller)
r.dao = dao
r.rpcUserConsumer = rpcUserConsumer
r.regionListBase64 = ""
r.regionCurrBase64 = ""
regionCurr, regionList := region.InitRegion(config.CONF.Hk4e.KcpAddr, config.CONF.Hk4e.KcpPort)
r.signRsaKey, r.encRsaKeyMap, r.pwdRsaKey = region.LoadRsaKey()
regionCurrModify, err := pb.Marshal(regionCurr)
if err != nil {
logger.LOG.Error("Marshal QueryCurrRegionHttpRsp error")
return nil
}
r.regionCurrBase64 = base64.StdEncoding.EncodeToString(regionCurrModify)
regionListModify, err := pb.Marshal(regionList)
if err != nil {
logger.LOG.Error("Marshal QueryRegionListHttpRsp error")
return nil
}
r.regionListBase64 = base64.StdEncoding.EncodeToString(regionListModify)
r.runEngine()
return r
}
func (c *Controller) runEngine() {
if config.CONF.Logger.Level == "DEBUG" {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
go func() {
engine := c.registerRouter()
port := config.CONF.HttpPort
addr := ":" + strconv.FormatInt(int64(port), 10)
err := engine.Run(addr)
if err != nil {
logger.LOG.Error("gin run error: %v", err)
}
}()
}
func (c *Controller) registerRouter() *gin.Engine {
engine := gin.Default()
{
// 404
engine.NoRoute(func(context *gin.Context) {
logger.LOG.Info("no route find, fallback to fuck mhy, url: %v", context.Request.RequestURI)
context.Header("Content-type", "text/html; charset=UTF-8")
context.Status(http.StatusNotFound)
_, _ = context.Writer.WriteString("FUCK MHY")
})
}
{
// 调度
// dispatchosglobal.yuanshen.com
engine.GET("/query_security_file", c.query_security_file)
engine.GET("/query_region_list", c.query_region_list)
// osusadispatch.yuanshen.com
engine.GET("/query_cur_region", c.query_cur_region)
}
{
// 登录
// hk4e-sdk-os.hoyoverse.com
// 账号登录
engine.POST("/hk4e_global/mdk/shield/api/login", c.apiLogin)
// token登录
engine.POST("/hk4e_global/mdk/shield/api/verify", c.apiVerify)
// 获取combo token
engine.POST("/hk4e_global/combo/granter/login/v2/login", c.v2Login)
}
{
// BLK文件补丁下载
// autopatchhk.yuanshen.com
engine.HEAD("/client_design_data/2.6_live/output_6988297_84eeb1c18b/client_silence/General/AssetBundles/data_versions", c.headDataVersions)
engine.GET("/client_design_data/2.6_live/output_6988297_84eeb1c18b/client_silence/General/AssetBundles/data_versions", c.getDataVersions)
engine.HEAD("/client_design_data/2.6_live/output_6988297_84eeb1c18b/client_silence/General/AssetBundles/blocks/00/29342328.blk", c.headBlk)
engine.GET("/client_design_data/2.6_live/output_6988297_84eeb1c18b/client_silence/General/AssetBundles/blocks/00/29342328.blk", c.getBlk)
}
{
// 日志
engine.POST("/sdk/dataUpload", c.sdkDataUpload)
engine.GET("/perf/config/verify", c.perfConfigVerify)
engine.POST("/perf/dataUpload", c.perfDataUpload)
engine.POST("/log", c.log8888)
engine.POST("/crash/dataUpload", c.crashDataUpload)
}
{
// 返回固定数据
// Windows
engine.GET("/hk4e_global/mdk/agreement/api/getAgreementInfos", c.getAgreementInfos)
engine.POST("/hk4e_global/combo/granter/api/compareProtocolVersion", c.postCompareProtocolVersion)
engine.POST("/account/risky/api/check", c.check)
engine.GET("/combo/box/api/config/sdk/combo", c.combo)
engine.GET("/hk4e_global/combo/granter/api/getConfig", c.getConfig)
engine.GET("/hk4e_global/mdk/shield/api/loadConfig", c.loadConfig)
engine.POST("/data_abtest_api/config/experiment/list", c.list)
engine.GET("/admin/mi18n/plat_oversea/m2020030410/m2020030410-version.json", c.version10Json)
engine.GET("/admin/mi18n/plat_oversea/m2020030410/m2020030410-zh-cn.json", c.zhCN10Json)
engine.GET("/geetestV2.html", c.geetestV2)
// Android
engine.POST("/common/h5log/log/batch", c.batch)
engine.GET("/hk4e_global/combo/granter/api/getFont", c.getFont)
engine.GET("/admin/mi18n/plat_oversea/m202003049/m202003049-version.json", c.version9Json)
engine.GET("/admin/mi18n/plat_oversea/m202003049/m202003049-zh-cn.json", c.zhCN9Json)
engine.GET("/hk4e_global/combo/granter/api/compareProtocolVersion", c.getCompareProtocolVersion)
// Android geetest
engine.GET("/gettype.php", c.gettype)
engine.GET("/get.php", c.get)
engine.POST("/ajax.php", c.ajax)
engine.GET("/ajax.php", c.ajax)
engine.GET("/static/appweb/app3-index.html", c.app3Index)
engine.GET("/static/js/slide.7.8.6.js", c.slideJs)
engine.GET("/favicon.ico", c.faviconIco)
engine.GET("/static/js/gct.e7810b5b525994e2fb1f89135f8df14a.js", c.js)
engine.GET("/static/ant/style_https.1.2.6.css", c.css)
engine.GET("/pictures/gt/a330cf996/a330cf996.webp", c.webp)
engine.GET("/pictures/gt/a330cf996/bg/86f9db021.webp", c.bgWebp)
engine.GET("/pictures/gt/a330cf996/slice/86f9db021.png", c.slicePng)
engine.GET("/static/ant/sprite2x.1.2.6.png", c.sprite2xPng)
}
return engine
}
+144
View File
@@ -0,0 +1,144 @@
package controller
import (
"bytes"
"encoding/base64"
"flswld.com/common/utils/endec"
"flswld.com/logger"
"gate-hk4e/entity/api"
"github.com/gin-gonic/gin"
"io/ioutil"
"math"
"net/http"
"regexp"
"strconv"
)
func (c *Controller) query_security_file(context *gin.Context) {
file, err := ioutil.ReadFile("static/security_file")
if err != nil {
logger.LOG.Error("open security_file error")
return
}
context.Header("Content-type", "text/html; charset=UTF-8")
_, _ = context.Writer.WriteString(string(file))
}
func (c *Controller) query_region_list(context *gin.Context) {
context.Header("Content-type", "text/html; charset=UTF-8")
_, _ = context.Writer.WriteString(c.regionListBase64)
}
func (c *Controller) query_cur_region(context *gin.Context) {
versionName := context.Query("version")
response := "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="
if len(context.Request.URL.RawQuery) > 0 {
response = c.regionCurrBase64
}
reg, err := regexp.Compile("[0-9]+")
if err != nil {
logger.LOG.Error("compile regexp error: %v", err)
return
}
versionSlice := reg.FindAllString(versionName, -1)
version := 0
for index := 0; index < len(versionSlice); index++ {
v, err := strconv.Atoi(versionSlice[index])
if err != nil {
logger.LOG.Error("parse client version error: %v", err)
return
}
for i := 0; i < len(versionSlice)-1-index; i++ {
v *= 10
}
version += v
}
if version >= 1000 {
// 测试版本
version /= 10
}
if version >= 275 {
logger.LOG.Debug("do hk4e 2.8 rsa logic")
if context.Query("dispatchSeed") == "" {
rsp := &api.QueryCurRegionRspJson{
Content: response,
Sign: "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz",
}
context.JSON(http.StatusOK, rsp)
return
}
keyId := context.Query("key_id")
encPubPrivKey, exist := c.encRsaKeyMap[keyId]
if !exist {
logger.LOG.Error("can not found key id: %v", keyId)
return
}
regionInfo, err := base64.StdEncoding.DecodeString(response)
if err != nil {
logger.LOG.Error("decode region info error: %v", err)
return
}
chunkSize := 256 - 11
regionInfoLength := len(regionInfo)
numChunks := int(math.Ceil(float64(regionInfoLength) / float64(chunkSize)))
encryptedRegionInfo := make([]byte, 0)
for i := 0; i < numChunks; i++ {
from := i * chunkSize
to := int(math.Min(float64((i+1)*chunkSize), float64(regionInfoLength)))
chunk := regionInfo[from:to]
pubKey, err := endec.RsaParsePubKeyByPrivKey(encPubPrivKey)
if err != nil {
logger.LOG.Error("parse rsa pub key error: %v", err)
return
}
privKey, err := endec.RsaParsePrivKey(encPubPrivKey)
if err != nil {
logger.LOG.Error("parse rsa priv key error: %v", err)
return
}
encrypt, err := endec.RsaEncrypt(chunk, pubKey)
if err != nil {
logger.LOG.Error("rsa enc error: %v", err)
return
}
decrypt, err := endec.RsaDecrypt(encrypt, privKey)
if err != nil {
logger.LOG.Error("rsa dec error: %v", err)
return
}
if bytes.Compare(decrypt, chunk) != 0 {
logger.LOG.Error("rsa dec test fail")
return
}
encryptedRegionInfo = append(encryptedRegionInfo, encrypt...)
}
signPrivkey, err := endec.RsaParsePrivKey(c.signRsaKey)
if err != nil {
logger.LOG.Error("parse rsa priv key error: %v", err)
return
}
signData, err := endec.RsaSign(regionInfo, signPrivkey)
if err != nil {
logger.LOG.Error("rsa sign error: %v", err)
return
}
ok, err := endec.RsaVerify(regionInfo, signData, &signPrivkey.PublicKey)
if err != nil {
logger.LOG.Error("rsa verify error: %v", err)
return
}
if !ok {
logger.LOG.Error("rsa verify test fail")
return
}
rsp := &api.QueryCurRegionRspJson{
Content: base64.StdEncoding.EncodeToString(encryptedRegionInfo),
Sign: base64.StdEncoding.EncodeToString(signData),
}
context.JSON(http.StatusOK, rsp)
return
} else {
context.Header("Content-type", "text/html; charset=UTF-8")
_, _ = context.Writer.WriteString(response)
}
}
File diff suppressed because one or more lines are too long
+33
View File
@@ -0,0 +1,33 @@
package controller
import "github.com/gin-gonic/gin"
// POST https://log-upload-os.mihoyo.com/sdk/dataUpload HTTP/1.1
func (c *Controller) sdkDataUpload(context *gin.Context) {
context.Header("Content-type", "application/json")
_, _ = context.Writer.WriteString("{\"code\":0}")
}
// GET http://log-upload-os.hoyoverse.com/perf/config/verify?device_id=dd664c97f924af747b4576a297c132038be239291651474673768&platform=2&name=DESKTOP-EDUS2DL HTTP/1.1
func (c *Controller) perfConfigVerify(context *gin.Context) {
context.Header("Content-type", "application/json")
_, _ = context.Writer.WriteString("{\"code\":0}")
}
// POST http://log-upload-os.hoyoverse.com/perf/dataUpload HTTP/1.1
func (c *Controller) perfDataUpload(context *gin.Context) {
context.Header("Content-type", "application/json")
_, _ = context.Writer.WriteString("{\"code\":0}")
}
// POST http://overseauspider.yuanshen.com:8888/log HTTP/1.1
func (c *Controller) log8888(context *gin.Context) {
context.Header("Content-type", "application/json")
_, _ = context.Writer.WriteString("{\"code\":0}")
}
// POST http://log-upload-os.hoyoverse.com/crash/dataUpload HTTP/1.1
func (c *Controller) crashDataUpload(context *gin.Context) {
context.Header("Content-type", "application/json")
_, _ = context.Writer.WriteString("{\"code\":0}")
}
+249
View File
@@ -0,0 +1,249 @@
package controller
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json"
providerApiEntity "flswld.com/annie-user-api/entity"
"flswld.com/common/utils/endec"
"flswld.com/common/utils/random"
"flswld.com/logger"
"gate-hk4e/entity/api"
"gate-hk4e/entity/db"
"github.com/gin-gonic/gin"
"net/http"
"regexp"
"strconv"
"strings"
)
func (c *Controller) getMD5(src string) string {
ctx := md5.New()
ctx.Write([]byte(src))
return hex.EncodeToString(ctx.Sum(nil))
}
func (c *Controller) apiLogin(context *gin.Context) {
requestData := new(api.LoginAccountRequestJson)
err := context.ShouldBindJSON(requestData)
if err != nil {
logger.LOG.Error("parse LoginAccountRequestJson error: %v", err)
return
}
encPwdData, err := base64.StdEncoding.DecodeString(requestData.Password)
if err != nil {
logger.LOG.Error("decode password enc data error: %v", err)
return
}
pwdPrivKey, err := endec.RsaParsePrivKey(c.pwdRsaKey)
if err != nil {
logger.LOG.Error("parse rsa key error: %v", err)
return
}
pwdDecData, err := endec.RsaDecrypt(encPwdData, pwdPrivKey)
useAtAtMode := false
if err != nil {
logger.LOG.Debug("rsa dec error: %v", err)
logger.LOG.Debug("password rsa dec fail, fallback to @@ mode")
useAtAtMode = true
} else {
logger.LOG.Debug("password dec: %v", string(pwdDecData))
useAtAtMode = false
}
responseData := api.NewLoginResult()
var username string
var password string
if useAtAtMode {
// 账号格式检查 用户名6-20字符 密码8-20字符 用户名和密码公用account字段 第一次出现的@@视为分割标识 username@@password
if len(requestData.Account) > 20+20+2 {
responseData.Retcode = -201
responseData.Message = "用户名或密码长度超限"
context.JSON(http.StatusOK, responseData)
return
}
if !strings.Contains(requestData.Account, "@@") {
responseData.Retcode = -201
responseData.Message = "用户名同密码均填写到用户名输入框,填写格式为:用户名@@密码,密码输入框填写任意字符均可"
context.JSON(http.StatusOK, responseData)
return
}
atIndex := strings.Index(requestData.Account, "@@")
username = requestData.Account[:atIndex]
password = requestData.Account[atIndex+2:]
} else {
username = requestData.Account
password = string(pwdDecData)
}
if len(username) < 6 || len(username) > 20 {
responseData.Retcode = -201
responseData.Message = "用户名为6-20位字符"
context.JSON(http.StatusOK, responseData)
return
}
if len(password) < 8 || len(password) > 20 {
responseData.Retcode = -201
responseData.Message = "密码为8-20位字符"
context.JSON(http.StatusOK, responseData)
return
}
ok, err := regexp.MatchString("^[a-zA-Z0-9]{6,20}$", username)
if err != nil || !ok {
responseData.Retcode = -201
responseData.Message = "用户名只能包含大小写字母和数字"
context.JSON(http.StatusOK, responseData)
return
}
userList := make([]providerApiEntity.User, 0)
ok = c.rpcUserConsumer.CallFunction("RpcService", "RpcQueryUser", &providerApiEntity.User{Username: username}, &userList)
if !ok {
responseData.Retcode = -201
responseData.Message = "服务器内部错误:-1"
context.JSON(http.StatusOK, responseData)
return
}
if len(userList) != 1 {
responseData.Retcode = -201
responseData.Message = "用户名不存在"
context.JSON(http.StatusOK, responseData)
return
}
user := userList[0]
if c.getMD5(password) != user.Password {
responseData.Retcode = -201
responseData.Message = "用户名或密码错误"
context.JSON(http.StatusOK, responseData)
return
}
// 登录成功
account, err := c.dao.QueryAccountByField("username", username)
if err != nil {
logger.LOG.Error("query account from db error: %v", err)
return
}
if account == nil {
// 注册一个原神account
playerID, err := c.dao.GetNextYuanShenUid()
if err != nil {
responseData.Retcode = -201
responseData.Message = "服务器内部错误:-2"
context.JSON(http.StatusOK, responseData)
return
}
regAccount := &db.Account{
Uid: user.Uid,
Username: username,
PlayerID: playerID,
Token: base64.StdEncoding.EncodeToString(random.GetRandomByte(24)),
ComboToken: "",
}
_, err = c.dao.InsertAccount(regAccount)
if err != nil {
responseData.Retcode = -201
responseData.Message = "服务器内部错误:-3"
context.JSON(http.StatusOK, responseData)
return
}
responseData.Message = "OK"
responseData.Data.Account.Uid = strconv.FormatInt(int64(regAccount.Uid), 10)
responseData.Data.Account.Token = regAccount.Token
responseData.Data.Account.Email = regAccount.Username
} else {
// 生产新的token
account.Token = base64.StdEncoding.EncodeToString(random.GetRandomByte(24))
_, err := c.dao.UpdateAccountFieldByFieldName("uid", account.Uid, "token", account.Token)
if err != nil {
responseData.Retcode = -201
responseData.Message = "服务器内部错误:-4"
context.JSON(http.StatusOK, responseData)
return
}
responseData.Message = "OK"
responseData.Data.Account.Uid = strconv.FormatInt(int64(account.Uid), 10)
responseData.Data.Account.Token = account.Token
responseData.Data.Account.Email = account.Username
}
context.JSON(http.StatusOK, responseData)
}
func (c *Controller) apiVerify(context *gin.Context) {
requestData := new(api.LoginTokenRequest)
err := context.ShouldBindJSON(requestData)
if err != nil {
logger.LOG.Error("parse LoginTokenRequest error: %v", err)
return
}
uid, err := strconv.ParseInt(requestData.Uid, 10, 64)
if err != nil {
logger.LOG.Error("parse uid error: %v", err)
return
}
account, err := c.dao.QueryAccountByField("uid", uid)
if err != nil {
logger.LOG.Error("query account from db error: %v", err)
return
}
responseData := api.NewLoginResult()
if account == nil || account.Token != requestData.Token {
responseData.Retcode = -111
responseData.Message = "账号本地缓存信息错误"
context.JSON(http.StatusOK, responseData)
return
}
responseData.Message = "OK"
responseData.Data.Account.Uid = requestData.Uid
responseData.Data.Account.Token = requestData.Token
responseData.Data.Account.Email = account.Username
context.JSON(http.StatusOK, responseData)
}
func (c *Controller) v2Login(context *gin.Context) {
requestData := new(api.ComboTokenReq)
err := context.ShouldBindJSON(requestData)
if err != nil {
logger.LOG.Error("parse ComboTokenReq error: %v", err)
return
}
data := requestData.Data
if len(data) == 0 {
logger.LOG.Error("requestData.Data len == 0")
return
}
loginData := new(api.LoginTokenData)
err = json.Unmarshal([]byte(data), loginData)
if err != nil {
logger.LOG.Error("Unmarshal LoginTokenData error: %v", err)
return
}
uid, err := strconv.ParseInt(loginData.Uid, 10, 64)
if err != nil {
logger.LOG.Error("ParseInt uid error: %v", err)
return
}
responseData := api.NewComboTokenRes()
account, err := c.dao.QueryAccountByField("uid", uid)
if account == nil || account.Token != loginData.Token {
responseData.Retcode = -201
responseData.Message = "token错误"
context.JSON(http.StatusOK, responseData)
return
}
// 生成新的comboToken
account.ComboToken = random.GetRandomByteHexStr(20)
_, err = c.dao.UpdateAccountFieldByFieldName("uid", account.Uid, "comboToken", account.ComboToken)
if err != nil {
responseData.Retcode = -201
responseData.Message = "服务器内部错误:-1"
context.JSON(http.StatusOK, responseData)
return
}
responseData.Message = "OK"
responseData.Data.OpenID = loginData.Uid
responseData.Data.ComboID = "0"
responseData.Data.ComboToken = account.ComboToken
context.JSON(http.StatusOK, responseData)
}
+126
View File
@@ -0,0 +1,126 @@
package dao
import (
"context"
"flswld.com/logger"
dbEntity "gate-hk4e/entity/db"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
func (d *Dao) GetNextYuanShenUid() (uint64, error) {
db := d.db.Collection("player_id_counter")
find := db.FindOne(context.TODO(), bson.D{{"_id", "default"}})
item := new(dbEntity.PlayerIDCounter)
err := find.Decode(item)
if err != nil {
if err == mongo.ErrNoDocuments {
item := &dbEntity.PlayerIDCounter{
ID: "default",
PlayerID: 100000001,
}
_, err := db.InsertOne(context.TODO(), item)
if err != nil {
return 0, errors.New("insert new PlayerID error")
}
return item.PlayerID, nil
} else {
return 0, err
}
}
item.PlayerID++
_, err = db.UpdateOne(
context.TODO(),
bson.D{
{"_id", "default"},
},
bson.D{
{"$set", bson.D{
{"PlayerID", item.PlayerID},
}},
},
)
if err != nil {
return 0, err
}
return item.PlayerID, nil
}
func (d *Dao) InsertAccount(account *dbEntity.Account) (primitive.ObjectID, error) {
db := d.db.Collection("account")
id, err := db.InsertOne(context.TODO(), account)
if err != nil {
return primitive.ObjectID{}, err
} else {
_id, ok := id.InsertedID.(primitive.ObjectID)
if !ok {
logger.LOG.Error("get insert id error")
return primitive.ObjectID{}, nil
}
return _id, nil
}
}
func (d *Dao) DeleteAccountByUsername(username string) (int64, error) {
db := d.db.Collection("account")
deleteCount, err := db.DeleteOne(
context.TODO(),
bson.D{
{"username", username},
},
)
if err != nil {
return 0, err
} else {
return deleteCount.DeletedCount, nil
}
}
func (d *Dao) UpdateAccountFieldByFieldName(fieldName string, fieldValue any, fieldUpdateName string, fieldUpdateValue any) (int64, error) {
db := d.db.Collection("account")
updateCount, err := db.UpdateOne(
context.TODO(),
bson.D{
{fieldName, fieldValue},
},
bson.D{
{"$set", bson.D{
{fieldUpdateName, fieldUpdateValue},
}},
},
)
if err != nil {
return 0, err
} else {
return updateCount.ModifiedCount, nil
}
}
func (d *Dao) QueryAccountByField(fieldName string, fieldValue any) (*dbEntity.Account, error) {
db := d.db.Collection("account")
find, err := db.Find(
context.TODO(),
bson.D{
{fieldName, fieldValue},
},
)
if err != nil {
return nil, err
}
result := make([]*dbEntity.Account, 0)
for find.Next(context.TODO()) {
item := new(dbEntity.Account)
err := find.Decode(item)
if err != nil {
return nil, err
}
result = append(result, item)
}
if len(result) == 0 {
return nil, nil
} else {
return result[0], nil
}
}
+34
View File
@@ -0,0 +1,34 @@
package dao
import (
"context"
"flswld.com/common/config"
"flswld.com/logger"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Dao struct {
client *mongo.Client
db *mongo.Database
}
func NewDao() (r *Dao) {
r = new(Dao)
clientOptions := options.Client().ApplyURI(config.CONF.Database.Url)
client, err := mongo.Connect(context.TODO(), clientOptions)
if err != nil {
logger.LOG.Error("mongo connect error: %v", err)
return nil
}
r.client = client
r.db = client.Database("gate_hk4e")
return r
}
func (d *Dao) CloseDao() {
err := d.client.Disconnect(context.TODO())
if err != nil {
logger.LOG.Error("mongo close error: %v", err)
}
}
+15
View File
@@ -0,0 +1,15 @@
package api
type ComboTokenReq struct {
AppID int `json:"app_id"`
ChannelID int `json:"channel_id"`
Data string `json:"data"`
Device string `json:"device"`
Sign string `json:"sign"`
}
type LoginTokenData struct {
Uid string `json:"uid"`
Token string `json:"token"`
Guest bool `json:"guest"`
}
+34
View File
@@ -0,0 +1,34 @@
package api
type ComboTokenRes struct {
Message string `json:"message"`
Retcode int `json:"retcode"`
Data LoginData `json:"data"`
}
type LoginData struct {
AccountType int `json:"account_type"`
Heartbeat bool `json:"heartbeat"`
ComboID string `json:"combo_id"`
ComboToken string `json:"combo_token"`
OpenID string `json:"open_id"`
Data string `json:"data"`
FatigueRemind any `json:"fatigue_remind"`
}
func NewComboTokenRes() (r *ComboTokenRes) {
r = &ComboTokenRes{
Message: "",
Retcode: 0,
Data: LoginData{
AccountType: 1,
Heartbeat: false,
ComboID: "",
ComboToken: "",
OpenID: "",
Data: "{\"guest\":false}",
FatigueRemind: nil,
},
}
return r
}
@@ -0,0 +1,7 @@
package api
type LoginAccountRequestJson struct {
Account string `json:"account"`
Password string `json:"password"`
IsCrypto bool `json:"is_crypto"`
}
+74
View File
@@ -0,0 +1,74 @@
package api
type LoginResult struct {
Message string `json:"message"`
Retcode int `json:"retcode"`
Data VerifyData `json:"data"`
}
type VerifyData struct {
Account VerifyAccountData `json:"account"`
DeviceGrantRequired bool `json:"device_grant_required"`
RealnameOperation string `json:"realname_operation"`
RealpersonRequired bool `json:"realperson_required"`
SafeMobileRequired bool `json:"safe_mobile_required"`
}
type VerifyAccountData struct {
Uid string `json:"uid"`
Name string `json:"name"`
Email string `json:"email"`
Mobile string `json:"mobile"`
IsEmailVerify string `json:"is_email_verify"`
Realname string `json:"realname"`
IdentityCard string `json:"identity_card"`
Token string `json:"token"`
SafeMobile string `json:"safe_mobile"`
FacebookName string `json:"facebook_name"`
TwitterName string `json:"twitter_name"`
GameCenterName string `json:"game_center_name"`
GoogleName string `json:"google_name"`
AppleName string `json:"apple_name"`
SonyName string `json:"sony_name"`
TapName string `json:"tap_name"`
Country string `json:"country"`
ReactivateTicket string `json:"reactivate_ticket"`
AreaCode string `json:"area_code"`
DeviceGrantTicket string `json:"device_grant_ticket"`
}
func NewLoginResult() (r *LoginResult) {
r = &LoginResult{
Message: "",
Retcode: 0,
Data: VerifyData{
Account: VerifyAccountData{
Uid: "",
Name: "",
Email: "",
Mobile: "",
IsEmailVerify: "0",
Realname: "",
IdentityCard: "",
Token: "",
SafeMobile: "",
FacebookName: "",
TwitterName: "",
GameCenterName: "",
GoogleName: "",
AppleName: "",
SonyName: "",
TapName: "",
Country: "CN",
ReactivateTicket: "",
AreaCode: "**",
DeviceGrantTicket: "",
},
DeviceGrantRequired: false,
RealnameOperation: "None",
RealpersonRequired: false,
SafeMobileRequired: false,
},
}
return r
}
+6
View File
@@ -0,0 +1,6 @@
package api
type LoginTokenRequest struct {
Uid string `json:"uid"`
Token string `json:"token"`
}
+6
View File
@@ -0,0 +1,6 @@
package api
type QueryCurRegionRspJson struct {
Content string `json:"content"`
Sign string `json:"sign"`
}
+14
View File
@@ -0,0 +1,14 @@
package db
import "go.mongodb.org/mongo-driver/bson/primitive"
type Account struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Uid uint64 `bson:"uid"`
Username string `bson:"username"`
PlayerID uint64 `bson:"playerID"`
Token string `bson:"token"`
ComboToken string `bson:"comboToken"`
Forbid bool `bson:"forbid"`
ForbidEndTime uint64 `bson:"forbidEndTime"`
}
+6
View File
@@ -0,0 +1,6 @@
package db
type PlayerIDCounter struct {
ID string `bson:"_id"`
PlayerID uint64 `bson:"PlayerID"`
}
+619
View File
@@ -0,0 +1,619 @@
package forward
import (
"flswld.com/common/config"
"flswld.com/gate-hk4e-api/gm"
"flswld.com/gate-hk4e-api/proto"
"flswld.com/logger"
"gate-hk4e/dao"
"gate-hk4e/kcp"
"gate-hk4e/net"
"gate-hk4e/region"
"io/ioutil"
"runtime"
"sync"
"time"
)
const (
ConnWaitToken = iota
ConnWaitLogin
ConnAlive
ConnClose
)
type ClientHeadMeta struct {
seq uint32
}
type ForwardManager struct {
dao *dao.Dao
protoMsgInput chan *net.ProtoMsg
protoMsgOutput chan *net.ProtoMsg
netMsgInput chan *proto.NetMsg
netMsgOutput chan *proto.NetMsg
// 玩家登录相关
connStateMap map[uint64]uint8
connStateMapLock sync.RWMutex
// kcpConv -> userID
convUserIdMap map[uint64]uint32
convUserIdMapLock sync.RWMutex
// userID -> kcpConv
userIdConvMap map[uint32]uint64
userIdConvMapLock sync.RWMutex
// kcpConv -> ipAddr
convAddrMap map[uint64]string
convAddrMapLock sync.RWMutex
// kcpConv -> headMeta
convHeadMetaMap map[uint64]*ClientHeadMeta
convHeadMetaMapLock sync.RWMutex
secretKeyBuffer []byte
kcpEventInput chan *net.KcpEvent
kcpEventOutput chan *net.KcpEvent
regionCurr *proto.QueryCurrRegionHttpRsp
signRsaKey []byte
encRsaKeyMap map[string][]byte
}
func NewForwardManager(dao *dao.Dao,
protoMsgInput chan *net.ProtoMsg, protoMsgOutput chan *net.ProtoMsg,
kcpEventInput chan *net.KcpEvent, kcpEventOutput chan *net.KcpEvent,
netMsgInput chan *proto.NetMsg, netMsgOutput chan *proto.NetMsg) (r *ForwardManager) {
r = new(ForwardManager)
r.dao = dao
r.protoMsgInput = protoMsgInput
r.protoMsgOutput = protoMsgOutput
r.netMsgInput = netMsgInput
r.netMsgOutput = netMsgOutput
r.connStateMap = make(map[uint64]uint8)
r.convUserIdMap = make(map[uint64]uint32)
r.userIdConvMap = make(map[uint32]uint64)
r.convAddrMap = make(map[uint64]string)
r.convHeadMetaMap = make(map[uint64]*ClientHeadMeta)
r.kcpEventInput = kcpEventInput
r.kcpEventOutput = kcpEventOutput
return r
}
func (f *ForwardManager) getHeadMsg(clientSeq uint32) (headMsg *proto.PacketHead) {
headMsg = new(proto.PacketHead)
if clientSeq != 0 {
headMsg.ClientSequenceId = clientSeq
headMsg.SentMs = uint64(time.Now().UnixMilli())
}
return headMsg
}
func (f *ForwardManager) kcpEventHandle() {
for {
event := <-f.kcpEventOutput
logger.LOG.Info("rpc manager recv event, ConvId: %v, EventId: %v", event.ConvId, event.EventId)
switch event.EventId {
case net.KcpPacketSendNotify:
// 发包通知
// 关闭发包监听
f.kcpEventInput <- &net.KcpEvent{
ConvId: event.ConvId,
EventId: net.KcpPacketSendListen,
EventMessage: "Disable",
}
// 登录成功 通知GS初始化相关数据
userId, exist := f.getUserIdByConvId(event.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
headMeta, exist := f.getHeadMetaByConvId(event.ConvId)
if !exist {
logger.LOG.Error("can not find client head metadata by convId")
continue
}
netMsg := new(proto.NetMsg)
netMsg.UserId = userId
netMsg.EventId = proto.UserLoginNotify
netMsg.ClientSeq = headMeta.seq
f.netMsgInput <- netMsg
logger.LOG.Info("send to gs user login ok, ConvId: %v, UserId: %v", event.ConvId, netMsg.UserId)
case net.KcpConnCloseNotify:
// 连接断开通知
userId, exist := f.getUserIdByConvId(event.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
if f.getConnState(event.ConvId) == ConnAlive {
// 通知GS玩家下线
netMsg := new(proto.NetMsg)
netMsg.UserId = userId
netMsg.EventId = proto.UserOfflineNotify
f.netMsgInput <- netMsg
logger.LOG.Info("send to gs user offline, ConvId: %v, UserId: %v", event.ConvId, netMsg.UserId)
}
// 删除各种map数据
f.deleteConnState(event.ConvId)
f.deleteUserIdByConvId(event.ConvId)
currConvId, currExist := f.getConvIdByUserId(userId)
if currExist && currConvId == event.ConvId {
// 防止误删顶号的新连接数据
f.deleteConvIdByUserId(userId)
}
f.deleteAddrByConvId(event.ConvId)
f.deleteHeadMetaByConvId(event.ConvId)
case net.KcpConnEstNotify:
// 连接建立通知
addr, ok := event.EventMessage.(string)
if !ok {
logger.LOG.Error("event KcpConnEstNotify msg type error")
continue
}
f.setAddrByConvId(event.ConvId, addr)
case net.KcpConnRttNotify:
// 客户端往返时延通知
rtt, ok := event.EventMessage.(int32)
if !ok {
logger.LOG.Error("event KcpConnRttNotify msg type error")
continue
}
// 通知GS玩家客户端往返时延
userId, exist := f.getUserIdByConvId(event.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
netMsg := new(proto.NetMsg)
netMsg.UserId = userId
netMsg.EventId = proto.ClientRttNotify
netMsg.ClientRtt = uint32(rtt)
f.netMsgInput <- netMsg
case net.KcpConnAddrChangeNotify:
// 客户端网络地址改变通知
f.convAddrMapLock.Lock()
_, exist := f.convAddrMap[event.ConvId]
if !exist {
f.convAddrMapLock.Unlock()
logger.LOG.Error("conn addr change but conn can not be found")
continue
}
addr := event.EventMessage.(string)
f.convAddrMap[event.ConvId] = addr
f.convAddrMapLock.Unlock()
}
}
}
func (f *ForwardManager) Start() {
// 读取密钥相关文件
var err error = nil
f.secretKeyBuffer, err = ioutil.ReadFile("static/secretKeyBuffer.bin")
if err != nil {
logger.LOG.Error("open secretKeyBuffer.bin error")
return
}
f.signRsaKey, f.encRsaKeyMap, _ = region.LoadRsaKey()
// region
regionCurr, _ := region.InitRegion(config.CONF.Hk4e.KcpAddr, config.CONF.Hk4e.KcpPort)
f.regionCurr = regionCurr
// kcp事件监听
go f.kcpEventHandle()
go f.recvNetMsgFromGameServer()
// 接收客户端消息
cpuCoreNum := runtime.NumCPU()
for i := 0; i < cpuCoreNum*10; i++ {
go f.sendNetMsgToGameServer()
}
}
// 发送消息到GS
func (f *ForwardManager) sendNetMsgToGameServer() {
for {
protoMsg := <-f.protoMsgOutput
if protoMsg.HeadMessage == nil {
logger.LOG.Error("recv null head msg: %v", protoMsg)
}
f.setHeadMetaByConvId(protoMsg.ConvId, &ClientHeadMeta{
seq: protoMsg.HeadMessage.ClientSequenceId,
})
connState := f.getConnState(protoMsg.ConvId)
// gate本地处理的请求
switch protoMsg.ApiId {
case proto.ApiGetPlayerTokenReq:
// 获取玩家token请求
if connState != ConnWaitToken {
continue
}
getPlayerTokenReq := protoMsg.PayloadMessage.(*proto.GetPlayerTokenReq)
getPlayerTokenRsp := f.getPlayerToken(protoMsg.ConvId, getPlayerTokenReq)
if getPlayerTokenRsp == nil {
continue
}
// 改变解密密钥
f.kcpEventInput <- &net.KcpEvent{
ConvId: protoMsg.ConvId,
EventId: net.KcpXorKeyChange,
EventMessage: "DEC",
}
// 返回数据到客户端
resp := new(net.ProtoMsg)
resp.ConvId = protoMsg.ConvId
resp.ApiId = proto.ApiGetPlayerTokenRsp
resp.HeadMessage = f.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId)
resp.PayloadMessage = getPlayerTokenRsp
f.protoMsgInput <- resp
case proto.ApiPlayerLoginReq:
// 玩家登录请求
if connState != ConnWaitLogin {
continue
}
playerLoginReq := protoMsg.PayloadMessage.(*proto.PlayerLoginReq)
playerLoginRsp := f.playerLogin(protoMsg.ConvId, playerLoginReq)
if playerLoginRsp == nil {
continue
}
// 改变加密密钥
f.kcpEventInput <- &net.KcpEvent{
ConvId: protoMsg.ConvId,
EventId: net.KcpXorKeyChange,
EventMessage: "ENC",
}
// 开启发包监听
f.kcpEventInput <- &net.KcpEvent{
ConvId: protoMsg.ConvId,
EventId: net.KcpPacketSendListen,
EventMessage: "Enable",
}
go func() {
// 保证kcp事件已成功生效
time.Sleep(time.Millisecond * 50)
// 返回数据到客户端
resp := new(net.ProtoMsg)
resp.ConvId = protoMsg.ConvId
resp.ApiId = proto.ApiPlayerLoginRsp
resp.HeadMessage = f.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId)
resp.PayloadMessage = playerLoginRsp
f.protoMsgInput <- resp
}()
case proto.ApiSetPlayerBornDataReq:
// 玩家注册请求
if connState != ConnAlive {
continue
}
userId, exist := f.getUserIdByConvId(protoMsg.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
netMsg := new(proto.NetMsg)
netMsg.UserId = userId
netMsg.EventId = proto.UserRegNotify
netMsg.ApiId = proto.ApiSetPlayerBornDataReq
netMsg.ClientSeq = protoMsg.HeadMessage.ClientSequenceId
netMsg.PayloadMessage = protoMsg.PayloadMessage
f.netMsgInput <- netMsg
case proto.ApiPlayerForceExitRsp:
// 玩家退出游戏请求
if connState != ConnAlive {
continue
}
userId, exist := f.getUserIdByConvId(protoMsg.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
f.setConnState(protoMsg.ConvId, ConnClose)
info := new(gm.KickPlayerInfo)
info.UserId = userId
info.Reason = uint32(kcp.EnetServerKick)
f.KickPlayer(info)
case proto.ApiPingReq:
// ping请求
if connState != ConnAlive {
continue
}
pingReq := protoMsg.PayloadMessage.(*proto.PingReq)
logger.LOG.Debug("user ping req, data: %v", pingReq.String())
// 返回数据到客户端
// TODO 记录客户端最后一次ping时间做超时下线处理
pingRsp := new(proto.PingRsp)
pingRsp.ClientTime = pingReq.ClientTime
resp := new(net.ProtoMsg)
resp.ConvId = protoMsg.ConvId
resp.ApiId = proto.ApiPingRsp
resp.HeadMessage = f.getHeadMsg(protoMsg.HeadMessage.ClientSequenceId)
resp.PayloadMessage = pingRsp
f.protoMsgInput <- resp
// 通知GS玩家客户端的本地时钟
userId, exist := f.getUserIdByConvId(protoMsg.ConvId)
if !exist {
logger.LOG.Error("can not find userId by convId")
continue
}
netMsg := new(proto.NetMsg)
netMsg.UserId = userId
netMsg.EventId = proto.ClientTimeNotify
netMsg.ClientTime = pingReq.ClientTime
f.netMsgInput <- netMsg
default:
// 转发到GS
// 未登录禁止访问GS
if connState != ConnAlive {
continue
}
netMsg := new(proto.NetMsg)
userId, exist := f.getUserIdByConvId(protoMsg.ConvId)
if exist {
netMsg.UserId = userId
} else {
logger.LOG.Error("can not find userId by convId")
continue
}
netMsg.EventId = proto.NormalMsg
netMsg.ApiId = protoMsg.ApiId
netMsg.ClientSeq = protoMsg.HeadMessage.ClientSequenceId
netMsg.PayloadMessage = protoMsg.PayloadMessage
f.netMsgInput <- netMsg
}
}
}
// 从GS接收消息
func (f *ForwardManager) recvNetMsgFromGameServer() {
for {
netMsg := <-f.netMsgOutput
convId, exist := f.getConvIdByUserId(netMsg.UserId)
if !exist {
logger.LOG.Error("can not find convId by userId")
continue
}
if netMsg.EventId == proto.NormalMsg {
protoMsg := new(net.ProtoMsg)
protoMsg.ConvId = convId
protoMsg.ApiId = netMsg.ApiId
protoMsg.HeadMessage = f.getHeadMsg(netMsg.ClientSeq)
protoMsg.PayloadMessage = netMsg.PayloadMessage
f.protoMsgInput <- protoMsg
continue
} else {
logger.LOG.Error("recv unknown event from game server, event id: %v", netMsg.EventId)
continue
}
}
}
func (f *ForwardManager) getConnState(convId uint64) uint8 {
f.connStateMapLock.RLock()
connState, connStateExist := f.connStateMap[convId]
f.connStateMapLock.RUnlock()
if !connStateExist {
connState = ConnWaitToken
f.connStateMapLock.Lock()
f.connStateMap[convId] = ConnWaitToken
f.connStateMapLock.Unlock()
}
return connState
}
func (f *ForwardManager) setConnState(convId uint64, state uint8) {
f.connStateMapLock.Lock()
f.connStateMap[convId] = state
f.connStateMapLock.Unlock()
}
func (f *ForwardManager) deleteConnState(convId uint64) {
f.connStateMapLock.Lock()
delete(f.connStateMap, convId)
f.connStateMapLock.Unlock()
}
func (f *ForwardManager) getUserIdByConvId(convId uint64) (userId uint32, exist bool) {
f.convUserIdMapLock.RLock()
userId, exist = f.convUserIdMap[convId]
f.convUserIdMapLock.RUnlock()
return userId, exist
}
func (f *ForwardManager) setUserIdByConvId(convId uint64, userId uint32) {
f.convUserIdMapLock.Lock()
f.convUserIdMap[convId] = userId
f.convUserIdMapLock.Unlock()
}
func (f *ForwardManager) deleteUserIdByConvId(convId uint64) {
f.convUserIdMapLock.Lock()
delete(f.convUserIdMap, convId)
f.convUserIdMapLock.Unlock()
}
func (f *ForwardManager) getConvIdByUserId(userId uint32) (convId uint64, exist bool) {
f.userIdConvMapLock.RLock()
convId, exist = f.userIdConvMap[userId]
f.userIdConvMapLock.RUnlock()
return convId, exist
}
func (f *ForwardManager) setConvIdByUserId(userId uint32, convId uint64) {
f.userIdConvMapLock.Lock()
f.userIdConvMap[userId] = convId
f.userIdConvMapLock.Unlock()
}
func (f *ForwardManager) deleteConvIdByUserId(userId uint32) {
f.userIdConvMapLock.Lock()
delete(f.userIdConvMap, userId)
f.userIdConvMapLock.Unlock()
}
func (f *ForwardManager) getAddrByConvId(convId uint64) (addr string, exist bool) {
f.convAddrMapLock.RLock()
addr, exist = f.convAddrMap[convId]
f.convAddrMapLock.RUnlock()
return addr, exist
}
func (f *ForwardManager) setAddrByConvId(convId uint64, addr string) {
f.convAddrMapLock.Lock()
f.convAddrMap[convId] = addr
f.convAddrMapLock.Unlock()
}
func (f *ForwardManager) deleteAddrByConvId(convId uint64) {
f.convAddrMapLock.Lock()
delete(f.convAddrMap, convId)
f.convAddrMapLock.Unlock()
}
func (f *ForwardManager) getHeadMetaByConvId(convId uint64) (headMeta *ClientHeadMeta, exist bool) {
f.convHeadMetaMapLock.RLock()
headMeta, exist = f.convHeadMetaMap[convId]
f.convHeadMetaMapLock.RUnlock()
return headMeta, exist
}
func (f *ForwardManager) setHeadMetaByConvId(convId uint64, headMeta *ClientHeadMeta) {
f.convHeadMetaMapLock.Lock()
f.convHeadMetaMap[convId] = headMeta
f.convHeadMetaMapLock.Unlock()
}
func (f *ForwardManager) deleteHeadMetaByConvId(convId uint64) {
f.convHeadMetaMapLock.Lock()
delete(f.convHeadMetaMap, convId)
f.convHeadMetaMapLock.Unlock()
}
// 改变网关开放状态
func (f *ForwardManager) ChangeGateOpenState(isOpen bool) bool {
f.kcpEventInput <- &net.KcpEvent{
EventId: net.KcpGateOpenState,
EventMessage: isOpen,
}
logger.LOG.Info("change gate open state to: %v", isOpen)
return true
}
// 剔除玩家下线
func (f *ForwardManager) KickPlayer(info *gm.KickPlayerInfo) bool {
if info == nil {
return false
}
convId, exist := f.getConvIdByUserId(info.UserId)
if !exist {
return false
}
f.kcpEventInput <- &net.KcpEvent{
ConvId: convId,
EventId: net.KcpConnForceClose,
EventMessage: info.Reason,
}
return true
}
// 获取网关在线玩家信息
func (f *ForwardManager) GetOnlineUser(uid uint32) (list *gm.OnlineUserList) {
list = &gm.OnlineUserList{
UserList: make([]*gm.OnlineUserInfo, 0),
}
if uid == 0 {
// 获取全部玩家
f.convUserIdMapLock.RLock()
f.convAddrMapLock.RLock()
for convId, userId := range f.convUserIdMap {
addr := f.convAddrMap[convId]
info := &gm.OnlineUserInfo{
Uid: userId,
ConvId: convId,
Addr: addr,
}
list.UserList = append(list.UserList, info)
}
f.convAddrMapLock.RUnlock()
f.convUserIdMapLock.RUnlock()
} else {
// 获取指定uid玩家
convId, exist := f.getConvIdByUserId(uid)
if !exist {
return list
}
addr, exist := f.getAddrByConvId(convId)
if !exist {
return list
}
info := &gm.OnlineUserInfo{
Uid: uid,
ConvId: convId,
Addr: addr,
}
list.UserList = append(list.UserList, info)
}
return list
}
// 用户密码改变
func (f *ForwardManager) UserPasswordChange(uid uint32) bool {
// dispatch登录态失效
_, err := f.dao.UpdateAccountFieldByFieldName("uid", uid, "token", "")
if err != nil {
return false
}
// 游戏内登录态失效
account, err := f.dao.QueryAccountByField("uid", uid)
if err != nil {
return false
}
if account == nil {
return false
}
convId, exist := f.getConvIdByUserId(uint32(account.PlayerID))
if !exist {
return true
}
f.kcpEventInput <- &net.KcpEvent{
ConvId: convId,
EventId: net.KcpConnForceClose,
EventMessage: uint32(kcp.EnetAccountPasswordChange),
}
return true
}
// 封号
func (f *ForwardManager) ForbidUser(info *gm.ForbidUserInfo) bool {
if info == nil {
return false
}
// 写入账号封禁信息
_, err := f.dao.UpdateAccountFieldByFieldName("uid", info.UserId, "forbid", true)
if err != nil {
return false
}
_, err = f.dao.UpdateAccountFieldByFieldName("uid", info.UserId, "forbidEndTime", info.ForbidEndTime)
if err != nil {
return false
}
// 游戏强制下线
account, err := f.dao.QueryAccountByField("uid", info.UserId)
if err != nil {
return false
}
if account == nil {
return false
}
convId, exist := f.getConvIdByUserId(uint32(account.PlayerID))
if !exist {
return true
}
f.kcpEventInput <- &net.KcpEvent{
ConvId: convId,
EventId: net.KcpConnForceClose,
EventMessage: uint32(kcp.EnetServerKillClient),
}
return true
}
// 解封
func (f *ForwardManager) UnForbidUser(uid uint32) bool {
// 解除账号封禁
_, err := f.dao.UpdateAccountFieldByFieldName("uid", uid, "forbid", false)
if err != nil {
return false
}
return true
}
+204
View File
@@ -0,0 +1,204 @@
package forward
import (
"bytes"
"encoding/base64"
"encoding/binary"
"flswld.com/common/utils/endec"
"flswld.com/gate-hk4e-api/proto"
"flswld.com/logger"
"gate-hk4e/kcp"
"gate-hk4e/net"
"strconv"
"strings"
"time"
)
func (f *ForwardManager) getPlayerToken(convId uint64, req *proto.GetPlayerTokenReq) (rsp *proto.GetPlayerTokenRsp) {
uidStr := req.AccountUid
uid, err := strconv.ParseInt(uidStr, 10, 64)
if err != nil {
logger.LOG.Error("parse uid error: %v", err)
return nil
}
account, err := f.dao.QueryAccountByField("uid", uid)
if err != nil {
logger.LOG.Error("query account error: %v", err)
return nil
}
if account == nil {
logger.LOG.Error("account is nil")
return nil
}
if account.ComboToken != req.AccountToken {
logger.LOG.Error("token error")
return nil
}
// comboToken验证成功
if account.Forbid {
if account.ForbidEndTime > uint64(time.Now().Unix()) {
// 封号通知
rsp = new(proto.GetPlayerTokenRsp)
rsp.Uid = uint32(account.PlayerID)
rsp.IsProficientPlayer = true
rsp.Retcode = 21
rsp.Msg = "FORBID_CHEATING_PLUGINS"
//rsp.BlackUidEndTime = 2051193600 // 2035-01-01 00:00:00
rsp.BlackUidEndTime = uint32(account.ForbidEndTime)
rsp.RegPlatform = 3
rsp.CountryCode = "US"
addr, exist := f.getAddrByConvId(convId)
if !exist {
logger.LOG.Error("can not find addr by convId")
return nil
}
split := strings.Split(addr, ":")
rsp.ClientIpStr = split[0]
return rsp
} else {
account.Forbid = false
_, err := f.dao.UpdateAccountFieldByFieldName("uid", account.Uid, "forbid", false)
if err != nil {
logger.LOG.Error("update db error: %v", err)
return nil
}
}
}
oldConvId, oldExist := f.getConvIdByUserId(uint32(account.PlayerID))
if oldExist {
// 顶号
f.kcpEventInput <- &net.KcpEvent{
ConvId: oldConvId,
EventId: net.KcpConnForceClose,
EventMessage: uint32(kcp.EnetServerRelogin),
}
}
f.setUserIdByConvId(convId, uint32(account.PlayerID))
f.setConvIdByUserId(uint32(account.PlayerID), convId)
f.setConnState(convId, ConnWaitLogin)
// 返回响应
rsp = new(proto.GetPlayerTokenRsp)
rsp.Uid = uint32(account.PlayerID)
rsp.Token = account.ComboToken
rsp.AccountType = 1
// TODO 要确定一下新注册的号这个值该返回什么
rsp.IsProficientPlayer = true
rsp.SecretKeySeed = 11468049314633205968
rsp.SecurityCmdBuffer = f.secretKeyBuffer
rsp.PlatformType = 3
rsp.ChannelId = 1
rsp.CountryCode = "US"
rsp.ClientVersionRandomKey = "c25-314dd05b0b5f"
rsp.RegPlatform = 3
addr, exist := f.getAddrByConvId(convId)
if !exist {
logger.LOG.Error("can not find addr by convId")
return nil
}
split := strings.Split(addr, ":")
rsp.ClientIpStr = split[0]
if req.GetKeyId() != 0 {
// pre check
logger.LOG.Debug("do hk4e 2.8 rsa logic")
keyId := strconv.Itoa(int(req.GetKeyId()))
encPubPrivKey, exist := f.encRsaKeyMap[keyId]
if !exist {
logger.LOG.Error("can not found key id: %v", keyId)
return
}
pubKey, err := endec.RsaParsePubKeyByPrivKey(encPubPrivKey)
if err != nil {
logger.LOG.Error("parse rsa pub key error: %v", err)
return nil
}
signPrivkey, err := endec.RsaParsePrivKey(f.signRsaKey)
if err != nil {
logger.LOG.Error("parse rsa priv key error: %v", err)
return nil
}
clientSeedBase64 := req.GetClientSeed()
clientSeedEnc, err := base64.StdEncoding.DecodeString(clientSeedBase64)
if err != nil {
logger.LOG.Error("parse client seed base64 error: %v", err)
return nil
}
// create error rsp info
clientSeedEncCopy := make([]byte, len(clientSeedEnc))
copy(clientSeedEncCopy, clientSeedEnc)
endec.Xor(clientSeedEncCopy, []byte{0x9f, 0x26, 0xb2, 0x17, 0x61, 0x5f, 0xc8, 0x00})
rsp.EncryptedSeed = base64.StdEncoding.EncodeToString(clientSeedEncCopy)
rsp.SeedSignature = "bm90aGluZyBoZXJl"
// do
clientSeed, err := endec.RsaDecrypt(clientSeedEnc, signPrivkey)
if err != nil {
logger.LOG.Error("rsa dec error: %v", err)
return rsp
}
clientSeedUint64 := uint64(0)
err = binary.Read(bytes.NewReader(clientSeed), binary.BigEndian, &clientSeedUint64)
if err != nil {
logger.LOG.Error("parse client seed to uint64 error: %v", err)
return rsp
}
seedUint64 := uint64(11468049314633205968) ^ clientSeedUint64
seedBuf := new(bytes.Buffer)
err = binary.Write(seedBuf, binary.BigEndian, seedUint64)
if err != nil {
logger.LOG.Error("conv seed uint64 to bytes error: %v", err)
return rsp
}
seed := seedBuf.Bytes()
seedEnc, err := endec.RsaEncrypt(seed, pubKey)
if err != nil {
logger.LOG.Error("rsa enc error: %v", err)
return rsp
}
seedSign, err := endec.RsaSign(seed, signPrivkey)
if err != nil {
logger.LOG.Error("rsa sign error: %v", err)
return rsp
}
rsp.EncryptedSeed = base64.StdEncoding.EncodeToString(seedEnc)
rsp.SeedSignature = base64.StdEncoding.EncodeToString(seedSign)
}
return rsp
}
func (f *ForwardManager) playerLogin(convId uint64, req *proto.PlayerLoginReq) (rsp *proto.PlayerLoginRsp) {
userId, exist := f.getUserIdByConvId(convId)
if !exist {
logger.LOG.Error("can not find userId by convId")
return nil
}
account, err := f.dao.QueryAccountByField("playerID", userId)
if err != nil {
logger.LOG.Error("query account error: %v", err)
return nil
}
if account == nil {
logger.LOG.Error("account is nil")
return nil
}
if account.ComboToken != req.Token {
logger.LOG.Error("token error")
return nil
}
// comboToken验证成功
f.setConnState(convId, ConnAlive)
// 返回响应
rsp = new(proto.PlayerLoginRsp)
rsp.IsUseAbilityHash = true
rsp.AbilityHashCode = 1844674
rsp.GameBiz = "hk4e_global"
rsp.ClientDataVersion = f.regionCurr.RegionInfo.ClientDataVersion
rsp.ClientSilenceDataVersion = f.regionCurr.RegionInfo.ClientSilenceDataVersion
rsp.ClientMd5 = f.regionCurr.RegionInfo.ClientDataMd5
rsp.ClientSilenceMd5 = f.regionCurr.RegionInfo.ClientSilenceDataMd5
rsp.ResVersionConfig = f.regionCurr.RegionInfo.ResVersionConfig
rsp.ClientVersionSuffix = f.regionCurr.RegionInfo.ClientVersionSuffix
rsp.ClientSilenceVersionSuffix = f.regionCurr.RegionInfo.ClientSilenceVersionSuffix
rsp.IsScOpen = false
rsp.RegisterCps = "mihoyo"
rsp.CountryCode = "US"
return rsp
}
+94
View File
@@ -0,0 +1,94 @@
module gate-hk4e
go 1.19
// annie
require flswld.com/common v0.0.0-incompatible
replace flswld.com/common => ../common
require flswld.com/logger v0.0.0-incompatible
replace flswld.com/logger => ../logger
require flswld.com/air-api v0.0.0-incompatible // indirect
replace flswld.com/air-api => ../air-api
require flswld.com/light v0.0.0-incompatible
replace flswld.com/light => ../light
require flswld.com/gate-hk4e-api v0.0.0-incompatible
replace flswld.com/gate-hk4e-api => ../gate-hk4e-api
require flswld.com/annie-user-api v0.0.0-incompatible
replace flswld.com/annie-user-api => ../service/annie-user-api
// kcp
require (
github.com/klauspost/reedsolomon v1.9.14
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0
github.com/templexxx/xorsimd v0.4.1
github.com/tjfoc/gmsm v1.4.1
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9
)
// protobuf
require google.golang.org/protobuf v1.28.0
// gin
require github.com/gin-gonic/gin v1.6.3
// mongodb
require go.mongodb.org/mongo-driver v1.8.3
// nats
require github.com/nats-io/nats.go v1.16.0
// msgpack
require github.com/vmihailenco/msgpack/v5 v5.3.5
// statsviz
require github.com/arl/statsviz v0.5.1
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.2.0 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/klauspost/compress v1.14.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.6 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/nats-io/nats-server/v2 v2.8.4 // indirect
github.com/nats-io/nkeys v0.3.0 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/templexxx/cpu v0.0.1 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.0.2 // indirect
github.com/xdg-go/stringprep v1.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect
golang.org/x/text v0.3.6 // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
+203
View File
@@ -0,0 +1,203 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/arl/statsviz v0.5.1 h1:3HY0ZEB738JtguWsD1Tf1pFJZiCcWUmYRq/3OTYKaSI=
github.com/arl/statsviz v0.5.1/go.mod h1:zDnjgRblGm1Dyd7J5YlbH7gM1/+HRC+SfkhZhQb5AnM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.14.4 h1:eijASRJcobkVtSt81Olfh7JX43osYLwy5krOJo6YEu4=
github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/reedsolomon v1.9.14 h1:vkPCIhFMn2VdktLUcugqsU4vcLXN3dAhVd1uWA+TDD8=
github.com/klauspost/reedsolomon v1.9.14/go.mod h1:eqPAcE7xar5CIzcdfwydOEdcmchAKAP/qs14y4GCBOk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I=
github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4=
github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4=
github.com/nats-io/nats.go v1.16.0 h1:zvLE7fGBQYW6MWaFaRdsgm9qT39PJDQoju+DS8KsO1g=
github.com/nats-io/nats.go v1.16.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY=
github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
github.com/templexxx/xorsimd v0.4.1 h1:iUZcywbOYDRAZUasAs2eSCUW8eobuZDy0I9FJiORkVg=
github.com/templexxx/xorsimd v0.4.1/go.mod h1:W+ffZz8jJMH2SXwuKu9WhygqBMbFnp14G2fqEr8qaNo=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc=
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+64
View File
@@ -0,0 +1,64 @@
package kcp
const maxAutoTuneSamples = 258
// pulse represents a 0/1 signal with time sequence
type pulse struct {
bit bool // 0 or 1
seq uint32 // sequence of the signal
}
// autoTune object
type autoTune struct {
pulses [maxAutoTuneSamples]pulse
}
// Sample adds a signal sample to the pulse buffer
func (tune *autoTune) Sample(bit bool, seq uint32) {
tune.pulses[seq%maxAutoTuneSamples] = pulse{bit, seq}
}
// Find a period for a given signal
// returns -1 if not found
//
// --- ------
// | |
// |______________|
// Period
// Falling Edge Rising Edge
func (tune *autoTune) FindPeriod(bit bool) int {
// last pulse and initial index setup
lastPulse := tune.pulses[0]
idx := 1
// left edge
var leftEdge int
for ; idx < len(tune.pulses); idx++ {
if lastPulse.bit != bit && tune.pulses[idx].bit == bit { // edge found
if lastPulse.seq+1 == tune.pulses[idx].seq { // ensure edge continuity
leftEdge = idx
break
}
}
lastPulse = tune.pulses[idx]
}
// right edge
var rightEdge int
lastPulse = tune.pulses[leftEdge]
idx = leftEdge + 1
for ; idx < len(tune.pulses); idx++ {
if lastPulse.seq+1 == tune.pulses[idx].seq { // ensure pulses in this level monotonic
if lastPulse.bit == bit && tune.pulses[idx].bit != bit { // edge found
rightEdge = idx
break
}
} else {
return -1
}
lastPulse = tune.pulses[idx]
}
return rightEdge - leftEdge
}
+47
View File
@@ -0,0 +1,47 @@
package kcp
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAutoTune(t *testing.T) {
signals := []uint32{0, 0, 0, 0, 0, 0}
tune := autoTune{}
for i := 0; i < len(signals); i++ {
if signals[i] == 0 {
tune.Sample(false, uint32(i))
} else {
tune.Sample(true, uint32(i))
}
}
assert.Equal(t, -1, tune.FindPeriod(false))
assert.Equal(t, -1, tune.FindPeriod(true))
signals = []uint32{1, 0, 1, 0, 0, 1}
tune = autoTune{}
for i := 0; i < len(signals); i++ {
if signals[i] == 0 {
tune.Sample(false, uint32(i))
} else {
tune.Sample(true, uint32(i))
}
}
assert.Equal(t, 1, tune.FindPeriod(false))
assert.Equal(t, 1, tune.FindPeriod(true))
signals = []uint32{1, 0, 0, 0, 0, 1}
tune = autoTune{}
for i := 0; i < len(signals); i++ {
if signals[i] == 0 {
tune.Sample(false, uint32(i))
} else {
tune.Sample(true, uint32(i))
}
}
assert.Equal(t, -1, tune.FindPeriod(true))
assert.Equal(t, 4, tune.FindPeriod(false))
}
+12
View File
@@ -0,0 +1,12 @@
package kcp
import "golang.org/x/net/ipv4"
const (
batchSize = 16
)
type batchConn interface {
WriteBatch(ms []ipv4.Message, flags int) (int, error)
ReadBatch(ms []ipv4.Message, flags int) (int, error)
}
+618
View File
@@ -0,0 +1,618 @@
package kcp
import (
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/sha1"
"unsafe"
xor "github.com/templexxx/xorsimd"
"github.com/tjfoc/gmsm/sm4"
"golang.org/x/crypto/blowfish"
"golang.org/x/crypto/cast5"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/salsa20"
"golang.org/x/crypto/tea"
"golang.org/x/crypto/twofish"
"golang.org/x/crypto/xtea"
)
var (
initialVector = []byte{167, 115, 79, 156, 18, 172, 27, 1, 164, 21, 242, 193, 252, 120, 230, 107}
saltxor = `sH3CIVoF#rWLtJo6`
)
// BlockCrypt defines encryption/decryption methods for a given byte slice.
// Notes on implementing: the data to be encrypted contains a builtin
// nonce at the first 16 bytes
type BlockCrypt interface {
// Encrypt encrypts the whole block in src into dst.
// Dst and src may point at the same memory.
Encrypt(dst, src []byte)
// Decrypt decrypts the whole block in src into dst.
// Dst and src may point at the same memory.
Decrypt(dst, src []byte)
}
type salsa20BlockCrypt struct {
key [32]byte
}
// NewSalsa20BlockCrypt https://en.wikipedia.org/wiki/Salsa20
func NewSalsa20BlockCrypt(key []byte) (BlockCrypt, error) {
c := new(salsa20BlockCrypt)
copy(c.key[:], key)
return c, nil
}
func (c *salsa20BlockCrypt) Encrypt(dst, src []byte) {
salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
copy(dst[:8], src[:8])
}
func (c *salsa20BlockCrypt) Decrypt(dst, src []byte) {
salsa20.XORKeyStream(dst[8:], src[8:], src[:8], &c.key)
copy(dst[:8], src[:8])
}
type sm4BlockCrypt struct {
encbuf [sm4.BlockSize]byte // 64bit alignment enc/dec buffer
decbuf [2 * sm4.BlockSize]byte
block cipher.Block
}
// NewSM4BlockCrypt https://github.com/tjfoc/gmsm/tree/master/sm4
func NewSM4BlockCrypt(key []byte) (BlockCrypt, error) {
c := new(sm4BlockCrypt)
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type twofishBlockCrypt struct {
encbuf [twofish.BlockSize]byte
decbuf [2 * twofish.BlockSize]byte
block cipher.Block
}
// NewTwofishBlockCrypt https://en.wikipedia.org/wiki/Twofish
func NewTwofishBlockCrypt(key []byte) (BlockCrypt, error) {
c := new(twofishBlockCrypt)
block, err := twofish.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type tripleDESBlockCrypt struct {
encbuf [des.BlockSize]byte
decbuf [2 * des.BlockSize]byte
block cipher.Block
}
// NewTripleDESBlockCrypt https://en.wikipedia.org/wiki/Triple_DES
func NewTripleDESBlockCrypt(key []byte) (BlockCrypt, error) {
c := new(tripleDESBlockCrypt)
block, err := des.NewTripleDESCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type cast5BlockCrypt struct {
encbuf [cast5.BlockSize]byte
decbuf [2 * cast5.BlockSize]byte
block cipher.Block
}
// NewCast5BlockCrypt https://en.wikipedia.org/wiki/CAST-128
func NewCast5BlockCrypt(key []byte) (BlockCrypt, error) {
c := new(cast5BlockCrypt)
block, err := cast5.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type blowfishBlockCrypt struct {
encbuf [blowfish.BlockSize]byte
decbuf [2 * blowfish.BlockSize]byte
block cipher.Block
}
// NewBlowfishBlockCrypt https://en.wikipedia.org/wiki/Blowfish_(cipher)
func NewBlowfishBlockCrypt(key []byte) (BlockCrypt, error) {
c := new(blowfishBlockCrypt)
block, err := blowfish.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type aesBlockCrypt struct {
encbuf [aes.BlockSize]byte
decbuf [2 * aes.BlockSize]byte
block cipher.Block
}
// NewAESBlockCrypt https://en.wikipedia.org/wiki/Advanced_Encryption_Standard
func NewAESBlockCrypt(key []byte) (BlockCrypt, error) {
c := new(aesBlockCrypt)
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type teaBlockCrypt struct {
encbuf [tea.BlockSize]byte
decbuf [2 * tea.BlockSize]byte
block cipher.Block
}
// NewTEABlockCrypt https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm
func NewTEABlockCrypt(key []byte) (BlockCrypt, error) {
c := new(teaBlockCrypt)
block, err := tea.NewCipherWithRounds(key, 16)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type xteaBlockCrypt struct {
encbuf [xtea.BlockSize]byte
decbuf [2 * xtea.BlockSize]byte
block cipher.Block
}
// NewXTEABlockCrypt https://en.wikipedia.org/wiki/XTEA
func NewXTEABlockCrypt(key []byte) (BlockCrypt, error) {
c := new(xteaBlockCrypt)
block, err := xtea.NewCipher(key)
if err != nil {
return nil, err
}
c.block = block
return c, nil
}
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
type simpleXORBlockCrypt struct {
xortbl []byte
}
// NewSimpleXORBlockCrypt simple xor with key expanding
func NewSimpleXORBlockCrypt(key []byte) (BlockCrypt, error) {
c := new(simpleXORBlockCrypt)
c.xortbl = pbkdf2.Key(key, []byte(saltxor), 32, mtuLimit, sha1.New)
return c, nil
}
func (c *simpleXORBlockCrypt) Encrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
func (c *simpleXORBlockCrypt) Decrypt(dst, src []byte) { xor.Bytes(dst, src, c.xortbl) }
type noneBlockCrypt struct{}
// NewNoneBlockCrypt does nothing but copying
func NewNoneBlockCrypt(key []byte) (BlockCrypt, error) {
return new(noneBlockCrypt), nil
}
func (c *noneBlockCrypt) Encrypt(dst, src []byte) { copy(dst, src) }
func (c *noneBlockCrypt) Decrypt(dst, src []byte) { copy(dst, src) }
// packet encryption with local CFB mode
func encrypt(block cipher.Block, dst, src, buf []byte) {
switch block.BlockSize() {
case 8:
encrypt8(block, dst, src, buf)
case 16:
encrypt16(block, dst, src, buf)
default:
panic("unsupported cipher block size")
}
}
// optimized encryption for the ciphers which works in 8-bytes
func encrypt8(block cipher.Block, dst, src, buf []byte) {
tbl := buf[:8]
block.Encrypt(tbl, initialVector)
n := len(src) / 8
base := 0
repeat := n / 8
left := n % 8
ptr_tbl := (*uint64)(unsafe.Pointer(&tbl[0]))
for i := 0; i < repeat; i++ {
s := src[base:][0:64]
d := dst[base:][0:64]
// 1
*(*uint64)(unsafe.Pointer(&d[0])) = *(*uint64)(unsafe.Pointer(&s[0])) ^ *ptr_tbl
block.Encrypt(tbl, d[0:8])
// 2
*(*uint64)(unsafe.Pointer(&d[8])) = *(*uint64)(unsafe.Pointer(&s[8])) ^ *ptr_tbl
block.Encrypt(tbl, d[8:16])
// 3
*(*uint64)(unsafe.Pointer(&d[16])) = *(*uint64)(unsafe.Pointer(&s[16])) ^ *ptr_tbl
block.Encrypt(tbl, d[16:24])
// 4
*(*uint64)(unsafe.Pointer(&d[24])) = *(*uint64)(unsafe.Pointer(&s[24])) ^ *ptr_tbl
block.Encrypt(tbl, d[24:32])
// 5
*(*uint64)(unsafe.Pointer(&d[32])) = *(*uint64)(unsafe.Pointer(&s[32])) ^ *ptr_tbl
block.Encrypt(tbl, d[32:40])
// 6
*(*uint64)(unsafe.Pointer(&d[40])) = *(*uint64)(unsafe.Pointer(&s[40])) ^ *ptr_tbl
block.Encrypt(tbl, d[40:48])
// 7
*(*uint64)(unsafe.Pointer(&d[48])) = *(*uint64)(unsafe.Pointer(&s[48])) ^ *ptr_tbl
block.Encrypt(tbl, d[48:56])
// 8
*(*uint64)(unsafe.Pointer(&d[56])) = *(*uint64)(unsafe.Pointer(&s[56])) ^ *ptr_tbl
block.Encrypt(tbl, d[56:64])
base += 64
}
switch left {
case 7:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 6:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 5:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 4:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 3:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 2:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 1:
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *ptr_tbl
block.Encrypt(tbl, dst[base:])
base += 8
fallthrough
case 0:
xorBytes(dst[base:], src[base:], tbl)
}
}
// optimized encryption for the ciphers which works in 16-bytes
func encrypt16(block cipher.Block, dst, src, buf []byte) {
tbl := buf[:16]
block.Encrypt(tbl, initialVector)
n := len(src) / 16
base := 0
repeat := n / 8
left := n % 8
for i := 0; i < repeat; i++ {
s := src[base:][0:128]
d := dst[base:][0:128]
// 1
xor.Bytes16Align(d[0:16], s[0:16], tbl)
block.Encrypt(tbl, d[0:16])
// 2
xor.Bytes16Align(d[16:32], s[16:32], tbl)
block.Encrypt(tbl, d[16:32])
// 3
xor.Bytes16Align(d[32:48], s[32:48], tbl)
block.Encrypt(tbl, d[32:48])
// 4
xor.Bytes16Align(d[48:64], s[48:64], tbl)
block.Encrypt(tbl, d[48:64])
// 5
xor.Bytes16Align(d[64:80], s[64:80], tbl)
block.Encrypt(tbl, d[64:80])
// 6
xor.Bytes16Align(d[80:96], s[80:96], tbl)
block.Encrypt(tbl, d[80:96])
// 7
xor.Bytes16Align(d[96:112], s[96:112], tbl)
block.Encrypt(tbl, d[96:112])
// 8
xor.Bytes16Align(d[112:128], s[112:128], tbl)
block.Encrypt(tbl, d[112:128])
base += 128
}
switch left {
case 7:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 6:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 5:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 4:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 3:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 2:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 1:
xor.Bytes16Align(dst[base:], src[base:], tbl)
block.Encrypt(tbl, dst[base:])
base += 16
fallthrough
case 0:
xorBytes(dst[base:], src[base:], tbl)
}
}
// decryption
func decrypt(block cipher.Block, dst, src, buf []byte) {
switch block.BlockSize() {
case 8:
decrypt8(block, dst, src, buf)
case 16:
decrypt16(block, dst, src, buf)
default:
panic("unsupported cipher block size")
}
}
// decrypt 8 bytes block, all byte slices are supposed to be 64bit aligned
func decrypt8(block cipher.Block, dst, src, buf []byte) {
tbl := buf[0:8]
next := buf[8:16]
block.Encrypt(tbl, initialVector)
n := len(src) / 8
base := 0
repeat := n / 8
left := n % 8
ptr_tbl := (*uint64)(unsafe.Pointer(&tbl[0]))
ptr_next := (*uint64)(unsafe.Pointer(&next[0]))
for i := 0; i < repeat; i++ {
s := src[base:][0:64]
d := dst[base:][0:64]
// 1
block.Encrypt(next, s[0:8])
*(*uint64)(unsafe.Pointer(&d[0])) = *(*uint64)(unsafe.Pointer(&s[0])) ^ *ptr_tbl
// 2
block.Encrypt(tbl, s[8:16])
*(*uint64)(unsafe.Pointer(&d[8])) = *(*uint64)(unsafe.Pointer(&s[8])) ^ *ptr_next
// 3
block.Encrypt(next, s[16:24])
*(*uint64)(unsafe.Pointer(&d[16])) = *(*uint64)(unsafe.Pointer(&s[16])) ^ *ptr_tbl
// 4
block.Encrypt(tbl, s[24:32])
*(*uint64)(unsafe.Pointer(&d[24])) = *(*uint64)(unsafe.Pointer(&s[24])) ^ *ptr_next
// 5
block.Encrypt(next, s[32:40])
*(*uint64)(unsafe.Pointer(&d[32])) = *(*uint64)(unsafe.Pointer(&s[32])) ^ *ptr_tbl
// 6
block.Encrypt(tbl, s[40:48])
*(*uint64)(unsafe.Pointer(&d[40])) = *(*uint64)(unsafe.Pointer(&s[40])) ^ *ptr_next
// 7
block.Encrypt(next, s[48:56])
*(*uint64)(unsafe.Pointer(&d[48])) = *(*uint64)(unsafe.Pointer(&s[48])) ^ *ptr_tbl
// 8
block.Encrypt(tbl, s[56:64])
*(*uint64)(unsafe.Pointer(&d[56])) = *(*uint64)(unsafe.Pointer(&s[56])) ^ *ptr_next
base += 64
}
switch left {
case 7:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 6:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 5:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 4:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 3:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 2:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 1:
block.Encrypt(next, src[base:])
*(*uint64)(unsafe.Pointer(&dst[base])) = *(*uint64)(unsafe.Pointer(&src[base])) ^ *(*uint64)(unsafe.Pointer(&tbl[0]))
tbl, next = next, tbl
base += 8
fallthrough
case 0:
xorBytes(dst[base:], src[base:], tbl)
}
}
func decrypt16(block cipher.Block, dst, src, buf []byte) {
tbl := buf[0:16]
next := buf[16:32]
block.Encrypt(tbl, initialVector)
n := len(src) / 16
base := 0
repeat := n / 8
left := n % 8
for i := 0; i < repeat; i++ {
s := src[base:][0:128]
d := dst[base:][0:128]
// 1
block.Encrypt(next, s[0:16])
xor.Bytes16Align(d[0:16], s[0:16], tbl)
// 2
block.Encrypt(tbl, s[16:32])
xor.Bytes16Align(d[16:32], s[16:32], next)
// 3
block.Encrypt(next, s[32:48])
xor.Bytes16Align(d[32:48], s[32:48], tbl)
// 4
block.Encrypt(tbl, s[48:64])
xor.Bytes16Align(d[48:64], s[48:64], next)
// 5
block.Encrypt(next, s[64:80])
xor.Bytes16Align(d[64:80], s[64:80], tbl)
// 6
block.Encrypt(tbl, s[80:96])
xor.Bytes16Align(d[80:96], s[80:96], next)
// 7
block.Encrypt(next, s[96:112])
xor.Bytes16Align(d[96:112], s[96:112], tbl)
// 8
block.Encrypt(tbl, s[112:128])
xor.Bytes16Align(d[112:128], s[112:128], next)
base += 128
}
switch left {
case 7:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 6:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 5:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 4:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 3:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 2:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 1:
block.Encrypt(next, src[base:])
xor.Bytes16Align(dst[base:], src[base:], tbl)
tbl, next = next, tbl
base += 16
fallthrough
case 0:
xorBytes(dst[base:], src[base:], tbl)
}
}
// per bytes xors
func xorBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}
+289
View File
@@ -0,0 +1,289 @@
package kcp
import (
"bytes"
"crypto/aes"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"hash/crc32"
"io"
"testing"
)
func TestSM4(t *testing.T) {
bc, err := NewSM4BlockCrypt(pass[:16])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestAES(t *testing.T) {
bc, err := NewAESBlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestTEA(t *testing.T) {
bc, err := NewTEABlockCrypt(pass[:16])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestXOR(t *testing.T) {
bc, err := NewSimpleXORBlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestBlowfish(t *testing.T) {
bc, err := NewBlowfishBlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestNone(t *testing.T) {
bc, err := NewNoneBlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestCast5(t *testing.T) {
bc, err := NewCast5BlockCrypt(pass[:16])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func Test3DES(t *testing.T) {
bc, err := NewTripleDESBlockCrypt(pass[:24])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestTwofish(t *testing.T) {
bc, err := NewTwofishBlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestXTEA(t *testing.T) {
bc, err := NewXTEABlockCrypt(pass[:16])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func TestSalsa20(t *testing.T) {
bc, err := NewSalsa20BlockCrypt(pass[:32])
if err != nil {
t.Fatal(err)
}
cryptTest(t, bc)
}
func cryptTest(t *testing.T, bc BlockCrypt) {
data := make([]byte, mtuLimit)
io.ReadFull(rand.Reader, data)
dec := make([]byte, mtuLimit)
enc := make([]byte, mtuLimit)
bc.Encrypt(enc, data)
bc.Decrypt(dec, enc)
if !bytes.Equal(data, dec) {
t.Fail()
}
}
func BenchmarkSM4(b *testing.B) {
bc, err := NewSM4BlockCrypt(pass[:16])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkAES128(b *testing.B) {
bc, err := NewAESBlockCrypt(pass[:16])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkAES192(b *testing.B) {
bc, err := NewAESBlockCrypt(pass[:24])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkAES256(b *testing.B) {
bc, err := NewAESBlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkTEA(b *testing.B) {
bc, err := NewTEABlockCrypt(pass[:16])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkXOR(b *testing.B) {
bc, err := NewSimpleXORBlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkBlowfish(b *testing.B) {
bc, err := NewBlowfishBlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkNone(b *testing.B) {
bc, err := NewNoneBlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkCast5(b *testing.B) {
bc, err := NewCast5BlockCrypt(pass[:16])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func Benchmark3DES(b *testing.B) {
bc, err := NewTripleDESBlockCrypt(pass[:24])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkTwofish(b *testing.B) {
bc, err := NewTwofishBlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkXTEA(b *testing.B) {
bc, err := NewXTEABlockCrypt(pass[:16])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func BenchmarkSalsa20(b *testing.B) {
bc, err := NewSalsa20BlockCrypt(pass[:32])
if err != nil {
b.Fatal(err)
}
benchCrypt(b, bc)
}
func benchCrypt(b *testing.B, bc BlockCrypt) {
data := make([]byte, mtuLimit)
io.ReadFull(rand.Reader, data)
dec := make([]byte, mtuLimit)
enc := make([]byte, mtuLimit)
b.ReportAllocs()
b.SetBytes(int64(len(enc) * 2))
b.ResetTimer()
for i := 0; i < b.N; i++ {
bc.Encrypt(enc, data)
bc.Decrypt(dec, enc)
}
}
func BenchmarkCRC32(b *testing.B) {
content := make([]byte, 1024)
b.SetBytes(int64(len(content)))
for i := 0; i < b.N; i++ {
crc32.ChecksumIEEE(content)
}
}
func BenchmarkCsprngSystem(b *testing.B) {
data := make([]byte, md5.Size)
b.SetBytes(int64(len(data)))
for i := 0; i < b.N; i++ {
io.ReadFull(rand.Reader, data)
}
}
func BenchmarkCsprngMD5(b *testing.B) {
var data [md5.Size]byte
b.SetBytes(md5.Size)
for i := 0; i < b.N; i++ {
data = md5.Sum(data[:])
}
}
func BenchmarkCsprngSHA1(b *testing.B) {
var data [sha1.Size]byte
b.SetBytes(sha1.Size)
for i := 0; i < b.N; i++ {
data = sha1.Sum(data[:])
}
}
func BenchmarkCsprngNonceMD5(b *testing.B) {
var ng nonceMD5
ng.Init()
b.SetBytes(md5.Size)
data := make([]byte, md5.Size)
for i := 0; i < b.N; i++ {
ng.Fill(data)
}
}
func BenchmarkCsprngNonceAES128(b *testing.B) {
var ng nonceAES128
ng.Init()
b.SetBytes(aes.BlockSize)
data := make([]byte, aes.BlockSize)
for i := 0; i < b.N; i++ {
ng.Fill(data)
}
}
+52
View File
@@ -0,0 +1,52 @@
package kcp
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"io"
)
// Entropy defines a entropy source
type Entropy interface {
Init()
Fill(nonce []byte)
}
// nonceMD5 nonce generator for packet header
type nonceMD5 struct {
seed [md5.Size]byte
}
func (n *nonceMD5) Init() { /*nothing required*/ }
func (n *nonceMD5) Fill(nonce []byte) {
if n.seed[0] == 0 { // entropy update
io.ReadFull(rand.Reader, n.seed[:])
}
n.seed = md5.Sum(n.seed[:])
copy(nonce, n.seed[:])
}
// nonceAES128 nonce generator for packet headers
type nonceAES128 struct {
seed [aes.BlockSize]byte
block cipher.Block
}
func (n *nonceAES128) Init() {
var key [16]byte //aes-128
io.ReadFull(rand.Reader, key[:])
io.ReadFull(rand.Reader, n.seed[:])
block, _ := aes.NewCipher(key[:])
n.block = block
}
func (n *nonceAES128) Fill(nonce []byte) {
if n.seed[0] == 0 { // entropy update
io.ReadFull(rand.Reader, n.seed[:])
}
n.block.Encrypt(n.seed[:], n.seed[:])
copy(nonce, n.seed[:])
}
+381
View File
@@ -0,0 +1,381 @@
package kcp
import (
"encoding/binary"
"sync/atomic"
"github.com/klauspost/reedsolomon"
)
const (
fecHeaderSize = 6
fecHeaderSizePlus2 = fecHeaderSize + 2 // plus 2B data size
typeData = 0xf1
typeParity = 0xf2
fecExpire = 60000
rxFECMulti = 3 // FEC keeps rxFECMulti* (dataShard+parityShard) ordered packets in memory
)
// fecPacket is a decoded FEC packet
type fecPacket []byte
func (bts fecPacket) seqid() uint32 { return binary.LittleEndian.Uint32(bts) }
func (bts fecPacket) flag() uint16 { return binary.LittleEndian.Uint16(bts[4:]) }
func (bts fecPacket) data() []byte { return bts[6:] }
// fecElement has auxcilliary time field
type fecElement struct {
fecPacket
ts uint32
}
// fecDecoder for decoding incoming packets
type fecDecoder struct {
rxlimit int // queue size limit
dataShards int
parityShards int
shardSize int
rx []fecElement // ordered receive queue
// caches
decodeCache [][]byte
flagCache []bool
// zeros
zeros []byte
// RS decoder
codec reedsolomon.Encoder
// auto tune fec parameter
autoTune autoTune
}
func newFECDecoder(dataShards, parityShards int) *fecDecoder {
if dataShards <= 0 || parityShards <= 0 {
return nil
}
dec := new(fecDecoder)
dec.dataShards = dataShards
dec.parityShards = parityShards
dec.shardSize = dataShards + parityShards
dec.rxlimit = rxFECMulti * dec.shardSize
codec, err := reedsolomon.New(dataShards, parityShards)
if err != nil {
return nil
}
dec.codec = codec
dec.decodeCache = make([][]byte, dec.shardSize)
dec.flagCache = make([]bool, dec.shardSize)
dec.zeros = make([]byte, mtuLimit)
return dec
}
// decode a fec packet
func (dec *fecDecoder) decode(in fecPacket) (recovered [][]byte) {
// sample to auto FEC tuner
if in.flag() == typeData {
dec.autoTune.Sample(true, in.seqid())
} else {
dec.autoTune.Sample(false, in.seqid())
}
// check if FEC parameters is out of sync
var shouldTune bool
if int(in.seqid())%dec.shardSize < dec.dataShards {
if in.flag() != typeData { // expect typeData
shouldTune = true
}
} else {
if in.flag() != typeParity {
shouldTune = true
}
}
if shouldTune {
autoDS := dec.autoTune.FindPeriod(true)
autoPS := dec.autoTune.FindPeriod(false)
// edges found, we can tune parameters now
if autoDS > 0 && autoPS > 0 && autoDS < 256 && autoPS < 256 {
// and make sure it's different
if autoDS != dec.dataShards || autoPS != dec.parityShards {
dec.dataShards = autoDS
dec.parityShards = autoPS
dec.shardSize = autoDS + autoPS
dec.rxlimit = rxFECMulti * dec.shardSize
codec, err := reedsolomon.New(autoDS, autoPS)
if err != nil {
return nil
}
dec.codec = codec
dec.decodeCache = make([][]byte, dec.shardSize)
dec.flagCache = make([]bool, dec.shardSize)
//log.Println("autotune to :", dec.dataShards, dec.parityShards)
}
}
}
// insertion
n := len(dec.rx) - 1
insertIdx := 0
for i := n; i >= 0; i-- {
if in.seqid() == dec.rx[i].seqid() { // de-duplicate
return nil
} else if _itimediff(in.seqid(), dec.rx[i].seqid()) > 0 { // insertion
insertIdx = i + 1
break
}
}
// make a copy
pkt := fecPacket(xmitBuf.Get().([]byte)[:len(in)])
copy(pkt, in)
elem := fecElement{pkt, currentMs()}
// insert into ordered rx queue
if insertIdx == n+1 {
dec.rx = append(dec.rx, elem)
} else {
dec.rx = append(dec.rx, fecElement{})
copy(dec.rx[insertIdx+1:], dec.rx[insertIdx:]) // shift right
dec.rx[insertIdx] = elem
}
// shard range for current packet
shardBegin := pkt.seqid() - pkt.seqid()%uint32(dec.shardSize)
shardEnd := shardBegin + uint32(dec.shardSize) - 1
// max search range in ordered queue for current shard
searchBegin := insertIdx - int(pkt.seqid()%uint32(dec.shardSize))
if searchBegin < 0 {
searchBegin = 0
}
searchEnd := searchBegin + dec.shardSize - 1
if searchEnd >= len(dec.rx) {
searchEnd = len(dec.rx) - 1
}
// re-construct datashards
if searchEnd-searchBegin+1 >= dec.dataShards {
var numshard, numDataShard, first, maxlen int
// zero caches
shards := dec.decodeCache
shardsflag := dec.flagCache
for k := range dec.decodeCache {
shards[k] = nil
shardsflag[k] = false
}
// shard assembly
for i := searchBegin; i <= searchEnd; i++ {
seqid := dec.rx[i].seqid()
if _itimediff(seqid, shardEnd) > 0 {
break
} else if _itimediff(seqid, shardBegin) >= 0 {
shards[seqid%uint32(dec.shardSize)] = dec.rx[i].data()
shardsflag[seqid%uint32(dec.shardSize)] = true
numshard++
if dec.rx[i].flag() == typeData {
numDataShard++
}
if numshard == 1 {
first = i
}
if len(dec.rx[i].data()) > maxlen {
maxlen = len(dec.rx[i].data())
}
}
}
if numDataShard == dec.dataShards {
// case 1: no loss on data shards
dec.rx = dec.freeRange(first, numshard, dec.rx)
} else if numshard >= dec.dataShards {
// case 2: loss on data shards, but it's recoverable from parity shards
for k := range shards {
if shards[k] != nil {
dlen := len(shards[k])
shards[k] = shards[k][:maxlen]
copy(shards[k][dlen:], dec.zeros)
} else if k < dec.dataShards {
shards[k] = xmitBuf.Get().([]byte)[:0]
}
}
if err := dec.codec.ReconstructData(shards); err == nil {
for k := range shards[:dec.dataShards] {
if !shardsflag[k] {
// recovered data should be recycled
recovered = append(recovered, shards[k])
}
}
}
dec.rx = dec.freeRange(first, numshard, dec.rx)
}
}
// keep rxlimit
if len(dec.rx) > dec.rxlimit {
if dec.rx[0].flag() == typeData { // track the unrecoverable data
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
}
dec.rx = dec.freeRange(0, 1, dec.rx)
}
// timeout policy
current := currentMs()
numExpired := 0
for k := range dec.rx {
if _itimediff(current, dec.rx[k].ts) > fecExpire {
numExpired++
continue
}
break
}
if numExpired > 0 {
dec.rx = dec.freeRange(0, numExpired, dec.rx)
}
return
}
// free a range of fecPacket
func (dec *fecDecoder) freeRange(first, n int, q []fecElement) []fecElement {
for i := first; i < first+n; i++ { // recycle buffer
xmitBuf.Put([]byte(q[i].fecPacket))
}
if first == 0 && n < cap(q)/2 {
return q[n:]
}
copy(q[first:], q[first+n:])
return q[:len(q)-n]
}
// release all segments back to xmitBuf
func (dec *fecDecoder) release() {
if n := len(dec.rx); n > 0 {
dec.rx = dec.freeRange(0, n, dec.rx)
}
}
type (
// fecEncoder for encoding outgoing packets
fecEncoder struct {
dataShards int
parityShards int
shardSize int
paws uint32 // Protect Against Wrapped Sequence numbers
next uint32 // next seqid
shardCount int // count the number of datashards collected
maxSize int // track maximum data length in datashard
headerOffset int // FEC header offset
payloadOffset int // FEC payload offset
// caches
shardCache [][]byte
encodeCache [][]byte
// zeros
zeros []byte
// RS encoder
codec reedsolomon.Encoder
}
)
func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
if dataShards <= 0 || parityShards <= 0 {
return nil
}
enc := new(fecEncoder)
enc.dataShards = dataShards
enc.parityShards = parityShards
enc.shardSize = dataShards + parityShards
enc.paws = 0xffffffff / uint32(enc.shardSize) * uint32(enc.shardSize)
enc.headerOffset = offset
enc.payloadOffset = enc.headerOffset + fecHeaderSize
codec, err := reedsolomon.New(dataShards, parityShards)
if err != nil {
return nil
}
enc.codec = codec
// caches
enc.encodeCache = make([][]byte, enc.shardSize)
enc.shardCache = make([][]byte, enc.shardSize)
for k := range enc.shardCache {
enc.shardCache[k] = make([]byte, mtuLimit)
}
enc.zeros = make([]byte, mtuLimit)
return enc
}
// encodes the packet, outputs parity shards if we have collected quorum datashards
// notice: the contents of 'ps' will be re-written in successive calling
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
// The header format:
// | FEC SEQID(4B) | FEC TYPE(2B) | SIZE (2B) | PAYLOAD(SIZE-2) |
// |<-headerOffset |<-payloadOffset
enc.markData(b[enc.headerOffset:])
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
// copy data from payloadOffset to fec shard cache
sz := len(b)
enc.shardCache[enc.shardCount] = enc.shardCache[enc.shardCount][:sz]
copy(enc.shardCache[enc.shardCount][enc.payloadOffset:], b[enc.payloadOffset:])
enc.shardCount++
// track max datashard length
if sz > enc.maxSize {
enc.maxSize = sz
}
// Generation of Reed-Solomon Erasure Code
if enc.shardCount == enc.dataShards {
// fill '0' into the tail of each datashard
for i := 0; i < enc.dataShards; i++ {
shard := enc.shardCache[i]
slen := len(shard)
copy(shard[slen:enc.maxSize], enc.zeros)
}
// construct equal-sized slice with stripped header
cache := enc.encodeCache
for k := range cache {
cache[k] = enc.shardCache[k][enc.payloadOffset:enc.maxSize]
}
// encoding
if err := enc.codec.Encode(cache); err == nil {
ps = enc.shardCache[enc.dataShards:]
for k := range ps {
enc.markParity(ps[k][enc.headerOffset:])
ps[k] = ps[k][:enc.maxSize]
}
}
// counters resetting
enc.shardCount = 0
enc.maxSize = 0
}
return
}
func (enc *fecEncoder) markData(data []byte) {
binary.LittleEndian.PutUint32(data, enc.next)
binary.LittleEndian.PutUint16(data[4:], typeData)
enc.next++
}
func (enc *fecEncoder) markParity(data []byte) {
binary.LittleEndian.PutUint32(data, enc.next)
binary.LittleEndian.PutUint16(data[4:], typeParity)
// sequence wrap will only happen at parity shard
enc.next = (enc.next + 1) % enc.paws
}
+43
View File
@@ -0,0 +1,43 @@
package kcp
import (
"encoding/binary"
"math/rand"
"testing"
)
func BenchmarkFECDecode(b *testing.B) {
const dataSize = 10
const paritySize = 3
const payLoad = 1500
decoder := newFECDecoder(dataSize, paritySize)
b.ReportAllocs()
b.SetBytes(payLoad)
for i := 0; i < b.N; i++ {
if rand.Int()%(dataSize+paritySize) == 0 { // random loss
continue
}
pkt := make([]byte, payLoad)
binary.LittleEndian.PutUint32(pkt, uint32(i))
if i%(dataSize+paritySize) >= dataSize {
binary.LittleEndian.PutUint16(pkt[4:], typeParity)
} else {
binary.LittleEndian.PutUint16(pkt[4:], typeData)
}
decoder.decode(pkt)
}
}
func BenchmarkFECEncode(b *testing.B) {
const dataSize = 10
const paritySize = 3
const payLoad = 1500
b.ReportAllocs()
b.SetBytes(payLoad)
encoder := newFECEncoder(dataSize, paritySize, 0)
for i := 0; i < b.N; i++ {
data := make([]byte, payLoad)
encoder.encode(data)
}
}
+1094
View File
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
package kcp
import (
"io"
"net"
"sync"
"testing"
"time"
"github.com/xtaci/lossyconn"
)
const repeat = 16
func TestLossyConn1(t *testing.T) {
t.Log("testing loss rate 10%, rtt 200ms")
t.Log("testing link with nodelay parameters:1 10 2 1")
client, err := lossyconn.NewLossyConn(0.1, 100)
if err != nil {
t.Fatal(err)
}
server, err := lossyconn.NewLossyConn(0.1, 100)
if err != nil {
t.Fatal(err)
}
testlink(t, client, server, 1, 10, 2, 1)
}
func TestLossyConn2(t *testing.T) {
t.Log("testing loss rate 20%, rtt 200ms")
t.Log("testing link with nodelay parameters:1 10 2 1")
client, err := lossyconn.NewLossyConn(0.2, 100)
if err != nil {
t.Fatal(err)
}
server, err := lossyconn.NewLossyConn(0.2, 100)
if err != nil {
t.Fatal(err)
}
testlink(t, client, server, 1, 10, 2, 1)
}
func TestLossyConn3(t *testing.T) {
t.Log("testing loss rate 30%, rtt 200ms")
t.Log("testing link with nodelay parameters:1 10 2 1")
client, err := lossyconn.NewLossyConn(0.3, 100)
if err != nil {
t.Fatal(err)
}
server, err := lossyconn.NewLossyConn(0.3, 100)
if err != nil {
t.Fatal(err)
}
testlink(t, client, server, 1, 10, 2, 1)
}
func TestLossyConn4(t *testing.T) {
t.Log("testing loss rate 10%, rtt 200ms")
t.Log("testing link with nodelay parameters:1 10 2 0")
client, err := lossyconn.NewLossyConn(0.1, 100)
if err != nil {
t.Fatal(err)
}
server, err := lossyconn.NewLossyConn(0.1, 100)
if err != nil {
t.Fatal(err)
}
testlink(t, client, server, 1, 10, 2, 0)
}
func testlink(t *testing.T, client *lossyconn.LossyConn, server *lossyconn.LossyConn, nodelay, interval, resend, nc int) {
t.Log("testing with nodelay parameters:", nodelay, interval, resend, nc)
sess, _ := NewConn2(server.LocalAddr(), nil, 0, 0, client)
listener, _ := ServeConn(nil, 0, 0, server)
echoServer := func(l *Listener) {
for {
conn, err := l.AcceptKCP()
if err != nil {
return
}
go func() {
conn.SetNoDelay(nodelay, interval, resend, nc)
buf := make([]byte, 65536)
for {
n, err := conn.Read(buf)
if err != nil {
return
}
conn.Write(buf[:n])
}
}()
}
}
echoTester := func(s *UDPSession, raddr net.Addr) {
s.SetNoDelay(nodelay, interval, resend, nc)
buf := make([]byte, 64)
var rtt time.Duration
for i := 0; i < repeat; i++ {
start := time.Now()
s.Write(buf)
io.ReadFull(s, buf)
rtt += time.Since(start)
}
t.Log("client:", client)
t.Log("server:", server)
t.Log("avg rtt:", rtt/repeat)
t.Logf("total time: %v for %v round trip:", rtt, repeat)
}
go echoServer(listener)
echoTester(sess, server.LocalAddr())
}
func BenchmarkFlush(b *testing.B) {
kcp := NewKCP(1, func(buf []byte, size int) {})
kcp.snd_buf = make([]segment, 1024)
for k := range kcp.snd_buf {
kcp.snd_buf[k].xmit = 1
kcp.snd_buf[k].resendts = currentMs() + 10000
}
b.ResetTimer()
b.ReportAllocs()
var mu sync.Mutex
for i := 0; i < b.N; i++ {
mu.Lock()
kcp.flush(false)
mu.Unlock()
}
}
+126
View File
@@ -0,0 +1,126 @@
package kcp
import (
"bytes"
"encoding/binary"
"github.com/pkg/errors"
)
func (s *UDPSession) defaultReadLoop() {
buf := make([]byte, mtuLimit)
var src string
for {
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
udpPayload := buf[:n]
// make sure the packet is from the same source
if src == "" { // set source address
src = addr.String()
} else if addr.String() != src {
//atomic.AddUint64(&DefaultSnmp.InErrs, 1)
//continue
s.remote = addr
src = addr.String()
}
s.packetInput(udpPayload)
} else {
s.notifyReadError(errors.WithStack(err))
return
}
}
}
func (l *Listener) defaultMonitor() {
buf := make([]byte, mtuLimit)
for {
if n, from, err := l.conn.ReadFrom(buf); err == nil {
udpPayload := buf[:n]
var convId uint64 = 0
if n == 20 {
// 原神KCP的Enet协议
// 提取convId
convId += uint64(udpPayload[4]) << 24
convId += uint64(udpPayload[5]) << 16
convId += uint64(udpPayload[6]) << 8
convId += uint64(udpPayload[7]) << 0
convId += uint64(udpPayload[8]) << 56
convId += uint64(udpPayload[9]) << 48
convId += uint64(udpPayload[10]) << 40
convId += uint64(udpPayload[11]) << 32
// 提取Enet协议头部和尾部幻数
udpPayloadEnetHead := udpPayload[:4]
udpPayloadEnetTail := udpPayload[len(udpPayload)-4:]
// 提取Enet协议类型
enetTypeData := udpPayload[12:16]
enetTypeDataBuffer := bytes.NewBuffer(enetTypeData)
var enetType uint32
_ = binary.Read(enetTypeDataBuffer, binary.BigEndian, &enetType)
equalHead := bytes.Compare(udpPayloadEnetHead, MagicEnetSynHead)
equalTail := bytes.Compare(udpPayloadEnetTail, MagicEnetSynTail)
if equalHead == 0 && equalTail == 0 {
// 客户端前置握手获取conv
l.EnetNotify <- &Enet{
Addr: from.String(),
ConvId: convId,
ConnType: ConnEnetSyn,
EnetType: enetType,
}
continue
}
equalHead = bytes.Compare(udpPayloadEnetHead, MagicEnetEstHead)
equalTail = bytes.Compare(udpPayloadEnetTail, MagicEnetEstTail)
if equalHead == 0 && equalTail == 0 {
// 连接建立
l.EnetNotify <- &Enet{
Addr: from.String(),
ConvId: convId,
ConnType: ConnEnetEst,
EnetType: enetType,
}
continue
}
equalHead = bytes.Compare(udpPayloadEnetHead, MagicEnetFinHead)
equalTail = bytes.Compare(udpPayloadEnetTail, MagicEnetFinTail)
if equalHead == 0 && equalTail == 0 {
// 连接断开
l.EnetNotify <- &Enet{
Addr: from.String(),
ConvId: convId,
ConnType: ConnEnetFin,
EnetType: enetType,
}
continue
}
} else {
// 正常KCP包
convId += uint64(udpPayload[0]) << 0
convId += uint64(udpPayload[1]) << 8
convId += uint64(udpPayload[2]) << 16
convId += uint64(udpPayload[3]) << 24
convId += uint64(udpPayload[4]) << 32
convId += uint64(udpPayload[5]) << 40
convId += uint64(udpPayload[6]) << 48
convId += uint64(udpPayload[7]) << 56
}
l.sessionLock.RLock()
conn, exist := l.sessions[convId]
l.sessionLock.RUnlock()
if exist {
if conn.remote.String() != from.String() {
conn.remote = from
// 连接地址改变
l.EnetNotify <- &Enet{
Addr: conn.remote.String(),
ConvId: convId,
ConnType: ConnEnetAddrChange,
}
}
}
l.packetInput(udpPayload, from, convId)
} else {
l.notifyReadError(errors.WithStack(err))
return
}
}
}
+12
View File
@@ -0,0 +1,12 @@
//go:build !linux
// +build !linux
package kcp
func (s *UDPSession) readLoop() {
s.defaultReadLoop()
}
func (l *Listener) monitor() {
l.defaultMonitor()
}
+199
View File
@@ -0,0 +1,199 @@
//go:build linux
// +build linux
package kcp
import (
"bytes"
"encoding/binary"
"github.com/pkg/errors"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net"
"os"
)
// the read loop for a client session
func (s *UDPSession) readLoop() {
// default version
if s.xconn == nil {
s.defaultReadLoop()
return
}
// x/net version
var src string
msgs := make([]ipv4.Message, batchSize)
for k := range msgs {
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
}
for {
if count, err := s.xconn.ReadBatch(msgs, 0); err == nil {
for i := 0; i < count; i++ {
msg := &msgs[i]
// make sure the packet is from the same source
if src == "" { // set source address if nil
src = msg.Addr.String()
} else if msg.Addr.String() != src {
//atomic.AddUint64(&DefaultSnmp.InErrs, 1)
//continue
s.remote = msg.Addr
src = msg.Addr.String()
}
udpPayload := msg.Buffers[0][:msg.N]
// source and size has validated
s.packetInput(udpPayload)
}
} else {
// compatibility issue:
// for linux kernel<=2.6.32, support for sendmmsg is not available
// an error of type os.SyscallError will be returned
if operr, ok := err.(*net.OpError); ok {
if se, ok := operr.Err.(*os.SyscallError); ok {
if se.Syscall == "recvmmsg" {
s.defaultReadLoop()
return
}
}
}
s.notifyReadError(errors.WithStack(err))
return
}
}
}
// monitor incoming data for all connections of server
func (l *Listener) monitor() {
var xconn batchConn
if _, ok := l.conn.(*net.UDPConn); ok {
addr, err := net.ResolveUDPAddr("udp", l.conn.LocalAddr().String())
if err == nil {
if addr.IP.To4() != nil {
xconn = ipv4.NewPacketConn(l.conn)
} else {
xconn = ipv6.NewPacketConn(l.conn)
}
}
}
// default version
if xconn == nil {
l.defaultMonitor()
return
}
// x/net version
msgs := make([]ipv4.Message, batchSize)
for k := range msgs {
msgs[k].Buffers = [][]byte{make([]byte, mtuLimit)}
}
for {
if count, err := xconn.ReadBatch(msgs, 0); err == nil {
for i := 0; i < count; i++ {
msg := &msgs[i]
udpPayload := msg.Buffers[0][:msg.N]
var convId uint64 = 0
if msg.N == 20 {
// 原神KCP的Enet协议
// 提取convId
convId += uint64(udpPayload[4]) << 24
convId += uint64(udpPayload[5]) << 16
convId += uint64(udpPayload[6]) << 8
convId += uint64(udpPayload[7]) << 0
convId += uint64(udpPayload[8]) << 56
convId += uint64(udpPayload[9]) << 48
convId += uint64(udpPayload[10]) << 40
convId += uint64(udpPayload[11]) << 32
// 提取Enet协议头部和尾部幻数
udpPayloadEnetHead := udpPayload[:4]
udpPayloadEnetTail := udpPayload[len(udpPayload)-4:]
// 提取Enet协议类型
enetTypeData := udpPayload[12:16]
enetTypeDataBuffer := bytes.NewBuffer(enetTypeData)
var enetType uint32
_ = binary.Read(enetTypeDataBuffer, binary.BigEndian, &enetType)
equalHead := bytes.Compare(udpPayloadEnetHead, MagicEnetSynHead)
equalTail := bytes.Compare(udpPayloadEnetTail, MagicEnetSynTail)
if equalHead == 0 && equalTail == 0 {
// 客户端前置握手获取conv
l.EnetNotify <- &Enet{
Addr: msg.Addr.String(),
ConvId: convId,
ConnType: ConnEnetSyn,
EnetType: enetType,
}
continue
}
equalHead = bytes.Compare(udpPayloadEnetHead, MagicEnetEstHead)
equalTail = bytes.Compare(udpPayloadEnetTail, MagicEnetEstTail)
if equalHead == 0 && equalTail == 0 {
// 连接建立
l.EnetNotify <- &Enet{
Addr: msg.Addr.String(),
ConvId: convId,
ConnType: ConnEnetEst,
EnetType: enetType,
}
continue
}
equalHead = bytes.Compare(udpPayloadEnetHead, MagicEnetFinHead)
equalTail = bytes.Compare(udpPayloadEnetTail, MagicEnetFinTail)
if equalHead == 0 && equalTail == 0 {
// 连接断开
l.EnetNotify <- &Enet{
Addr: msg.Addr.String(),
ConvId: convId,
ConnType: ConnEnetFin,
EnetType: enetType,
}
continue
}
} else {
// 正常KCP包
convId += uint64(udpPayload[0]) << 0
convId += uint64(udpPayload[1]) << 8
convId += uint64(udpPayload[2]) << 16
convId += uint64(udpPayload[3]) << 24
convId += uint64(udpPayload[4]) << 32
convId += uint64(udpPayload[5]) << 40
convId += uint64(udpPayload[6]) << 48
convId += uint64(udpPayload[7]) << 56
}
l.sessionLock.RLock()
conn, exist := l.sessions[convId]
l.sessionLock.RUnlock()
if exist {
if conn.remote.String() != msg.Addr.String() {
conn.remote = msg.Addr
// 连接地址改变
l.EnetNotify <- &Enet{
Addr: conn.remote.String(),
ConvId: convId,
ConnType: ConnEnetAddrChange,
}
}
}
l.packetInput(udpPayload, msg.Addr, convId)
}
} else {
// compatibility issue:
// for linux kernel<=2.6.32, support for sendmmsg is not available
// an error of type os.SyscallError will be returned
if operr, ok := err.(*net.OpError); ok {
if se, ok := operr.Err.(*os.SyscallError); ok {
if se.Syscall == "recvmmsg" {
l.defaultMonitor()
return
}
}
}
l.notifyReadError(errors.WithStack(err))
return
}
}
}
File diff suppressed because it is too large Load Diff
+703
View File
@@ -0,0 +1,703 @@
package kcp
import (
"crypto/sha1"
"fmt"
"io"
"log"
"net"
"net/http"
_ "net/http/pprof"
"sync"
"sync/atomic"
"testing"
"time"
"golang.org/x/crypto/pbkdf2"
)
var baseport = uint32(10000)
var key = []byte("testkey")
var pass = pbkdf2.Key(key, []byte("testsalt"), 4096, 32, sha1.New)
func init() {
go func() {
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
log.Println("beginning tests, encryption:salsa20, fec:10/3")
}
func dialEcho(port int) (*UDPSession, error) {
//block, _ := NewNoneBlockCrypt(pass)
//block, _ := NewSimpleXORBlockCrypt(pass)
//block, _ := NewTEABlockCrypt(pass[:16])
//block, _ := NewAESBlockCrypt(pass)
block, _ := NewSalsa20BlockCrypt(pass)
sess, err := DialWithOptions(fmt.Sprintf("127.0.0.1:%v", port), block, 10, 3)
if err != nil {
panic(err)
}
sess.SetStreamMode(true)
sess.SetStreamMode(false)
sess.SetStreamMode(true)
sess.SetWindowSize(1024, 1024)
sess.SetReadBuffer(16 * 1024 * 1024)
sess.SetWriteBuffer(16 * 1024 * 1024)
sess.SetStreamMode(true)
sess.SetNoDelay(1, 10, 2, 1)
sess.SetMtu(1400)
sess.SetMtu(1600)
sess.SetMtu(1400)
sess.SetACKNoDelay(true)
sess.SetACKNoDelay(false)
sess.SetDeadline(time.Now().Add(time.Minute))
return sess, err
}
func dialSink(port int) (*UDPSession, error) {
sess, err := DialWithOptions(fmt.Sprintf("127.0.0.1:%v", port), nil, 0, 0)
if err != nil {
panic(err)
}
sess.SetStreamMode(true)
sess.SetWindowSize(1024, 1024)
sess.SetReadBuffer(16 * 1024 * 1024)
sess.SetWriteBuffer(16 * 1024 * 1024)
sess.SetStreamMode(true)
sess.SetNoDelay(1, 10, 2, 1)
sess.SetMtu(1400)
sess.SetACKNoDelay(false)
sess.SetDeadline(time.Now().Add(time.Minute))
return sess, err
}
func dialTinyBufferEcho(port int) (*UDPSession, error) {
//block, _ := NewNoneBlockCrypt(pass)
//block, _ := NewSimpleXORBlockCrypt(pass)
//block, _ := NewTEABlockCrypt(pass[:16])
//block, _ := NewAESBlockCrypt(pass)
block, _ := NewSalsa20BlockCrypt(pass)
sess, err := DialWithOptions(fmt.Sprintf("127.0.0.1:%v", port), block, 10, 3)
if err != nil {
panic(err)
}
return sess, err
}
// ////////////////////////
func listenEcho(port int) (net.Listener, error) {
//block, _ := NewNoneBlockCrypt(pass)
//block, _ := NewSimpleXORBlockCrypt(pass)
//block, _ := NewTEABlockCrypt(pass[:16])
//block, _ := NewAESBlockCrypt(pass)
block, _ := NewSalsa20BlockCrypt(pass)
return ListenWithOptions(fmt.Sprintf("127.0.0.1:%v", port), block, 10, 0)
}
func listenTinyBufferEcho(port int) (net.Listener, error) {
//block, _ := NewNoneBlockCrypt(pass)
//block, _ := NewSimpleXORBlockCrypt(pass)
//block, _ := NewTEABlockCrypt(pass[:16])
//block, _ := NewAESBlockCrypt(pass)
block, _ := NewSalsa20BlockCrypt(pass)
return ListenWithOptions(fmt.Sprintf("127.0.0.1:%v", port), block, 10, 3)
}
func listenSink(port int) (net.Listener, error) {
return ListenWithOptions(fmt.Sprintf("127.0.0.1:%v", port), nil, 0, 0)
}
func echoServer(port int) net.Listener {
l, err := listenEcho(port)
if err != nil {
panic(err)
}
go func() {
kcplistener := l.(*Listener)
kcplistener.SetReadBuffer(4 * 1024 * 1024)
kcplistener.SetWriteBuffer(4 * 1024 * 1024)
kcplistener.SetDSCP(46)
for {
s, err := l.Accept()
if err != nil {
return
}
// coverage test
s.(*UDPSession).SetReadBuffer(4 * 1024 * 1024)
s.(*UDPSession).SetWriteBuffer(4 * 1024 * 1024)
go handleEcho(s.(*UDPSession))
}
}()
return l
}
func sinkServer(port int) net.Listener {
l, err := listenSink(port)
if err != nil {
panic(err)
}
go func() {
kcplistener := l.(*Listener)
kcplistener.SetReadBuffer(4 * 1024 * 1024)
kcplistener.SetWriteBuffer(4 * 1024 * 1024)
kcplistener.SetDSCP(46)
for {
s, err := l.Accept()
if err != nil {
return
}
go handleSink(s.(*UDPSession))
}
}()
return l
}
func tinyBufferEchoServer(port int) net.Listener {
l, err := listenTinyBufferEcho(port)
if err != nil {
panic(err)
}
go func() {
for {
s, err := l.Accept()
if err != nil {
return
}
go handleTinyBufferEcho(s.(*UDPSession))
}
}()
return l
}
///////////////////////////
func handleEcho(conn *UDPSession) {
conn.SetStreamMode(true)
conn.SetWindowSize(4096, 4096)
conn.SetNoDelay(1, 10, 2, 1)
conn.SetDSCP(46)
conn.SetMtu(1400)
conn.SetACKNoDelay(false)
conn.SetReadDeadline(time.Now().Add(time.Hour))
conn.SetWriteDeadline(time.Now().Add(time.Hour))
buf := make([]byte, 65536)
for {
n, err := conn.Read(buf)
if err != nil {
return
}
conn.Write(buf[:n])
}
}
func handleSink(conn *UDPSession) {
conn.SetStreamMode(true)
conn.SetWindowSize(4096, 4096)
conn.SetNoDelay(1, 10, 2, 1)
conn.SetDSCP(46)
conn.SetMtu(1400)
conn.SetACKNoDelay(false)
conn.SetReadDeadline(time.Now().Add(time.Hour))
conn.SetWriteDeadline(time.Now().Add(time.Hour))
buf := make([]byte, 65536)
for {
_, err := conn.Read(buf)
if err != nil {
return
}
}
}
func handleTinyBufferEcho(conn *UDPSession) {
conn.SetStreamMode(true)
buf := make([]byte, 2)
for {
n, err := conn.Read(buf)
if err != nil {
return
}
conn.Write(buf[:n])
}
}
///////////////////////////
func TestTimeout(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
buf := make([]byte, 10)
//timeout
cli.SetDeadline(time.Now().Add(time.Second))
<-time.After(2 * time.Second)
n, err := cli.Read(buf)
if n != 0 || err == nil {
t.Fail()
}
cli.Close()
}
func TestSendRecv(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
cli.SetWriteDelay(true)
cli.SetDUP(1)
const N = 100
buf := make([]byte, 10)
for i := 0; i < N; i++ {
msg := fmt.Sprintf("hello%v", i)
cli.Write([]byte(msg))
if n, err := cli.Read(buf); err == nil {
if string(buf[:n]) != msg {
t.Fail()
}
} else {
panic(err)
}
}
cli.Close()
}
func TestSendVector(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
cli.SetWriteDelay(false)
const N = 100
buf := make([]byte, 20)
v := make([][]byte, 2)
for i := 0; i < N; i++ {
v[0] = []byte(fmt.Sprintf("hello%v", i))
v[1] = []byte(fmt.Sprintf("world%v", i))
msg := fmt.Sprintf("hello%vworld%v", i, i)
cli.WriteBuffers(v)
if n, err := cli.Read(buf); err == nil {
if string(buf[:n]) != msg {
t.Error(string(buf[:n]), msg)
}
} else {
panic(err)
}
}
cli.Close()
}
func TestTinyBufferReceiver(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l := tinyBufferEchoServer(port)
defer l.Close()
cli, err := dialTinyBufferEcho(port)
if err != nil {
panic(err)
}
const N = 100
snd := byte(0)
fillBuffer := func(buf []byte) {
for i := 0; i < len(buf); i++ {
buf[i] = snd
snd++
}
}
rcv := byte(0)
check := func(buf []byte) bool {
for i := 0; i < len(buf); i++ {
if buf[i] != rcv {
return false
}
rcv++
}
return true
}
sndbuf := make([]byte, 7)
rcvbuf := make([]byte, 7)
for i := 0; i < N; i++ {
fillBuffer(sndbuf)
cli.Write(sndbuf)
if n, err := io.ReadFull(cli, rcvbuf); err == nil {
if !check(rcvbuf[:n]) {
t.Fail()
}
} else {
panic(err)
}
}
cli.Close()
}
func TestClose(t *testing.T) {
var n int
var err error
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
// double close
cli.Close()
if cli.Close() == nil {
t.Fatal("double close misbehavior")
}
// write after close
buf := make([]byte, 10)
n, err = cli.Write(buf)
if n != 0 || err == nil {
t.Fatal("write after close misbehavior")
}
// write, close, read, read
cli, err = dialEcho(port)
if err != nil {
panic(err)
}
if n, err = cli.Write(buf); err != nil {
t.Fatal("write misbehavior")
}
// wait until data arrival
time.Sleep(2 * time.Second)
// drain
cli.Close()
n, err = io.ReadFull(cli, buf)
if err != nil {
t.Fatal("closed conn drain bytes failed", err, n)
}
// after drain, read should return error
n, err = cli.Read(buf)
if n != 0 || err == nil {
t.Fatal("write->close->drain->read misbehavior", err, n)
}
cli.Close()
}
func TestParallel1024CLIENT_64BMSG_64CNT(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
var wg sync.WaitGroup
wg.Add(1024)
for i := 0; i < 1024; i++ {
go parallel_client(&wg, port)
}
wg.Wait()
}
func parallel_client(wg *sync.WaitGroup, port int) (err error) {
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
err = echo_tester(cli, 64, 64)
cli.Close()
wg.Done()
return
}
func BenchmarkEchoSpeed4K(b *testing.B) {
speedclient(b, 4096)
}
func BenchmarkEchoSpeed64K(b *testing.B) {
speedclient(b, 65536)
}
func BenchmarkEchoSpeed512K(b *testing.B) {
speedclient(b, 524288)
}
func BenchmarkEchoSpeed1M(b *testing.B) {
speedclient(b, 1048576)
}
func speedclient(b *testing.B, nbytes int) {
port := int(atomic.AddUint32(&baseport, 1))
l := echoServer(port)
defer l.Close()
b.ReportAllocs()
cli, err := dialEcho(port)
if err != nil {
panic(err)
}
if err := echo_tester(cli, nbytes, b.N); err != nil {
b.Fail()
}
b.SetBytes(int64(nbytes))
cli.Close()
}
func BenchmarkSinkSpeed4K(b *testing.B) {
sinkclient(b, 4096)
}
func BenchmarkSinkSpeed64K(b *testing.B) {
sinkclient(b, 65536)
}
func BenchmarkSinkSpeed256K(b *testing.B) {
sinkclient(b, 524288)
}
func BenchmarkSinkSpeed1M(b *testing.B) {
sinkclient(b, 1048576)
}
func sinkclient(b *testing.B, nbytes int) {
port := int(atomic.AddUint32(&baseport, 1))
l := sinkServer(port)
defer l.Close()
b.ReportAllocs()
cli, err := dialSink(port)
if err != nil {
panic(err)
}
sink_tester(cli, nbytes, b.N)
b.SetBytes(int64(nbytes))
cli.Close()
}
func echo_tester(cli net.Conn, msglen, msgcount int) error {
buf := make([]byte, msglen)
for i := 0; i < msgcount; i++ {
// send packet
if _, err := cli.Write(buf); err != nil {
return err
}
// receive packet
nrecv := 0
for {
n, err := cli.Read(buf)
if err != nil {
return err
} else {
nrecv += n
if nrecv == msglen {
break
}
}
}
}
return nil
}
func sink_tester(cli *UDPSession, msglen, msgcount int) error {
// sender
buf := make([]byte, msglen)
for i := 0; i < msgcount; i++ {
if _, err := cli.Write(buf); err != nil {
return err
}
}
return nil
}
func TestSNMP(t *testing.T) {
t.Log(DefaultSnmp.Copy())
t.Log(DefaultSnmp.Header())
t.Log(DefaultSnmp.ToSlice())
DefaultSnmp.Reset()
t.Log(DefaultSnmp.ToSlice())
}
func TestListenerClose(t *testing.T) {
port := int(atomic.AddUint32(&baseport, 1))
l, err := ListenWithOptions(fmt.Sprintf("127.0.0.1:%v", port), nil, 10, 3)
if err != nil {
t.Fail()
}
l.SetReadDeadline(time.Now().Add(time.Second))
l.SetWriteDeadline(time.Now().Add(time.Second))
l.SetDeadline(time.Now().Add(time.Second))
time.Sleep(2 * time.Second)
if _, err := l.Accept(); err == nil {
t.Fail()
}
l.Close()
//fakeaddr, _ := net.ResolveUDPAddr("udp6", "127.0.0.1:1111")
fakeConvId := uint64(0)
if l.closeSession(fakeConvId) {
t.Fail()
}
}
// A wrapper for net.PacketConn that remembers when Close has been called.
type closedFlagPacketConn struct {
net.PacketConn
Closed bool
}
func (c *closedFlagPacketConn) Close() error {
c.Closed = true
return c.PacketConn.Close()
}
func newClosedFlagPacketConn(c net.PacketConn) *closedFlagPacketConn {
return &closedFlagPacketConn{c, false}
}
// Listener should close a net.PacketConn that it created.
// https://github.com/xtaci/kcp-go/issues/165
func TestListenerOwnedPacketConn(t *testing.T) {
// ListenWithOptions creates its own net.PacketConn.
l, err := ListenWithOptions("127.0.0.1:0", nil, 0, 0)
if err != nil {
panic(err)
}
defer l.Close()
// Replace the internal net.PacketConn with one that remembers when it
// has been closed.
pconn := newClosedFlagPacketConn(l.conn)
l.conn = pconn
if pconn.Closed {
t.Fatal("owned PacketConn closed before Listener.Close()")
}
err = l.Close()
if err != nil {
panic(err)
}
if !pconn.Closed {
t.Fatal("owned PacketConn not closed after Listener.Close()")
}
}
// Listener should not close a net.PacketConn that it did not create.
// https://github.com/xtaci/kcp-go/issues/165
func TestListenerNonOwnedPacketConn(t *testing.T) {
// Create a net.PacketConn not owned by the Listener.
c, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
panic(err)
}
defer c.Close()
// Make it remember when it has been closed.
pconn := newClosedFlagPacketConn(c)
l, err := ServeConn(nil, 0, 0, pconn)
if err != nil {
panic(err)
}
defer l.Close()
if pconn.Closed {
t.Fatal("non-owned PacketConn closed before Listener.Close()")
}
err = l.Close()
if err != nil {
panic(err)
}
if pconn.Closed {
t.Fatal("non-owned PacketConn closed after Listener.Close()")
}
}
// UDPSession should close a net.PacketConn that it created.
// https://github.com/xtaci/kcp-go/issues/165
func TestUDPSessionOwnedPacketConn(t *testing.T) {
l := sinkServer(0)
defer l.Close()
// DialWithOptions creates its own net.PacketConn.
client, err := DialWithOptions(l.Addr().String(), nil, 0, 0)
if err != nil {
panic(err)
}
defer client.Close()
// Replace the internal net.PacketConn with one that remembers when it
// has been closed.
pconn := newClosedFlagPacketConn(client.conn)
client.conn = pconn
if pconn.Closed {
t.Fatal("owned PacketConn closed before UDPSession.Close()")
}
err = client.Close()
if err != nil {
panic(err)
}
if !pconn.Closed {
t.Fatal("owned PacketConn not closed after UDPSession.Close()")
}
}
// UDPSession should not close a net.PacketConn that it did not create.
// https://github.com/xtaci/kcp-go/issues/165
func TestUDPSessionNonOwnedPacketConn(t *testing.T) {
l := sinkServer(0)
defer l.Close()
// Create a net.PacketConn not owned by the UDPSession.
c, err := net.ListenPacket("udp", "127.0.0.1:0")
if err != nil {
panic(err)
}
defer c.Close()
// Make it remember when it has been closed.
pconn := newClosedFlagPacketConn(c)
client, err := NewConn2(l.Addr(), nil, 0, 0, pconn)
if err != nil {
panic(err)
}
defer client.Close()
if pconn.Closed {
t.Fatal("non-owned PacketConn closed before UDPSession.Close()")
}
err = client.Close()
if err != nil {
panic(err)
}
if pconn.Closed {
t.Fatal("non-owned PacketConn closed after UDPSession.Close()")
}
}
+164
View File
@@ -0,0 +1,164 @@
package kcp
import (
"fmt"
"sync/atomic"
)
// Snmp defines network statistics indicator
type Snmp struct {
BytesSent uint64 // bytes sent from upper level
BytesReceived uint64 // bytes received to upper level
MaxConn uint64 // max number of connections ever reached
ActiveOpens uint64 // accumulated active open connections
PassiveOpens uint64 // accumulated passive open connections
CurrEstab uint64 // current number of established connections
InErrs uint64 // UDP read errors reported from net.PacketConn
InCsumErrors uint64 // checksum errors from CRC32
KCPInErrors uint64 // packet iput errors reported from KCP
InPkts uint64 // incoming packets count
OutPkts uint64 // outgoing packets count
InSegs uint64 // incoming KCP segments
OutSegs uint64 // outgoing KCP segments
InBytes uint64 // UDP bytes received
OutBytes uint64 // UDP bytes sent
RetransSegs uint64 // accmulated retransmited segments
FastRetransSegs uint64 // accmulated fast retransmitted segments
EarlyRetransSegs uint64 // accmulated early retransmitted segments
LostSegs uint64 // number of segs inferred as lost
RepeatSegs uint64 // number of segs duplicated
FECRecovered uint64 // correct packets recovered from FEC
FECErrs uint64 // incorrect packets recovered from FEC
FECParityShards uint64 // FEC segments received
FECShortShards uint64 // number of data shards that's not enough for recovery
}
func newSnmp() *Snmp {
return new(Snmp)
}
// Header returns all field names
func (s *Snmp) Header() []string {
return []string{
"BytesSent",
"BytesReceived",
"MaxConn",
"ActiveOpens",
"PassiveOpens",
"CurrEstab",
"InErrs",
"InCsumErrors",
"KCPInErrors",
"InPkts",
"OutPkts",
"InSegs",
"OutSegs",
"InBytes",
"OutBytes",
"RetransSegs",
"FastRetransSegs",
"EarlyRetransSegs",
"LostSegs",
"RepeatSegs",
"FECParityShards",
"FECErrs",
"FECRecovered",
"FECShortShards",
}
}
// ToSlice returns current snmp info as slice
func (s *Snmp) ToSlice() []string {
snmp := s.Copy()
return []string{
fmt.Sprint(snmp.BytesSent),
fmt.Sprint(snmp.BytesReceived),
fmt.Sprint(snmp.MaxConn),
fmt.Sprint(snmp.ActiveOpens),
fmt.Sprint(snmp.PassiveOpens),
fmt.Sprint(snmp.CurrEstab),
fmt.Sprint(snmp.InErrs),
fmt.Sprint(snmp.InCsumErrors),
fmt.Sprint(snmp.KCPInErrors),
fmt.Sprint(snmp.InPkts),
fmt.Sprint(snmp.OutPkts),
fmt.Sprint(snmp.InSegs),
fmt.Sprint(snmp.OutSegs),
fmt.Sprint(snmp.InBytes),
fmt.Sprint(snmp.OutBytes),
fmt.Sprint(snmp.RetransSegs),
fmt.Sprint(snmp.FastRetransSegs),
fmt.Sprint(snmp.EarlyRetransSegs),
fmt.Sprint(snmp.LostSegs),
fmt.Sprint(snmp.RepeatSegs),
fmt.Sprint(snmp.FECParityShards),
fmt.Sprint(snmp.FECErrs),
fmt.Sprint(snmp.FECRecovered),
fmt.Sprint(snmp.FECShortShards),
}
}
// Copy make a copy of current snmp snapshot
func (s *Snmp) Copy() *Snmp {
d := newSnmp()
d.BytesSent = atomic.LoadUint64(&s.BytesSent)
d.BytesReceived = atomic.LoadUint64(&s.BytesReceived)
d.MaxConn = atomic.LoadUint64(&s.MaxConn)
d.ActiveOpens = atomic.LoadUint64(&s.ActiveOpens)
d.PassiveOpens = atomic.LoadUint64(&s.PassiveOpens)
d.CurrEstab = atomic.LoadUint64(&s.CurrEstab)
d.InErrs = atomic.LoadUint64(&s.InErrs)
d.InCsumErrors = atomic.LoadUint64(&s.InCsumErrors)
d.KCPInErrors = atomic.LoadUint64(&s.KCPInErrors)
d.InPkts = atomic.LoadUint64(&s.InPkts)
d.OutPkts = atomic.LoadUint64(&s.OutPkts)
d.InSegs = atomic.LoadUint64(&s.InSegs)
d.OutSegs = atomic.LoadUint64(&s.OutSegs)
d.InBytes = atomic.LoadUint64(&s.InBytes)
d.OutBytes = atomic.LoadUint64(&s.OutBytes)
d.RetransSegs = atomic.LoadUint64(&s.RetransSegs)
d.FastRetransSegs = atomic.LoadUint64(&s.FastRetransSegs)
d.EarlyRetransSegs = atomic.LoadUint64(&s.EarlyRetransSegs)
d.LostSegs = atomic.LoadUint64(&s.LostSegs)
d.RepeatSegs = atomic.LoadUint64(&s.RepeatSegs)
d.FECParityShards = atomic.LoadUint64(&s.FECParityShards)
d.FECErrs = atomic.LoadUint64(&s.FECErrs)
d.FECRecovered = atomic.LoadUint64(&s.FECRecovered)
d.FECShortShards = atomic.LoadUint64(&s.FECShortShards)
return d
}
// Reset values to zero
func (s *Snmp) Reset() {
atomic.StoreUint64(&s.BytesSent, 0)
atomic.StoreUint64(&s.BytesReceived, 0)
atomic.StoreUint64(&s.MaxConn, 0)
atomic.StoreUint64(&s.ActiveOpens, 0)
atomic.StoreUint64(&s.PassiveOpens, 0)
atomic.StoreUint64(&s.CurrEstab, 0)
atomic.StoreUint64(&s.InErrs, 0)
atomic.StoreUint64(&s.InCsumErrors, 0)
atomic.StoreUint64(&s.KCPInErrors, 0)
atomic.StoreUint64(&s.InPkts, 0)
atomic.StoreUint64(&s.OutPkts, 0)
atomic.StoreUint64(&s.InSegs, 0)
atomic.StoreUint64(&s.OutSegs, 0)
atomic.StoreUint64(&s.InBytes, 0)
atomic.StoreUint64(&s.OutBytes, 0)
atomic.StoreUint64(&s.RetransSegs, 0)
atomic.StoreUint64(&s.FastRetransSegs, 0)
atomic.StoreUint64(&s.EarlyRetransSegs, 0)
atomic.StoreUint64(&s.LostSegs, 0)
atomic.StoreUint64(&s.RepeatSegs, 0)
atomic.StoreUint64(&s.FECParityShards, 0)
atomic.StoreUint64(&s.FECErrs, 0)
atomic.StoreUint64(&s.FECRecovered, 0)
atomic.StoreUint64(&s.FECShortShards, 0)
}
// DefaultSnmp is the global KCP connection statistics collector
var DefaultSnmp *Snmp
func init() {
DefaultSnmp = newSnmp()
}
+146
View File
@@ -0,0 +1,146 @@
package kcp
import (
"container/heap"
"runtime"
"sync"
"time"
)
// SystemTimedSched is the library level timed-scheduler
var SystemTimedSched = NewTimedSched(runtime.NumCPU())
type timedFunc struct {
execute func()
ts time.Time
}
// a heap for sorted timed function
type timedFuncHeap []timedFunc
func (h timedFuncHeap) Len() int { return len(h) }
func (h timedFuncHeap) Less(i, j int) bool { return h[i].ts.Before(h[j].ts) }
func (h timedFuncHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *timedFuncHeap) Push(x interface{}) { *h = append(*h, x.(timedFunc)) }
func (h *timedFuncHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
old[n-1].execute = nil // avoid memory leak
*h = old[0 : n-1]
return x
}
// TimedSched represents the control struct for timed parallel scheduler
type TimedSched struct {
// prepending tasks
prependTasks []timedFunc
prependLock sync.Mutex
chPrependNotify chan struct{}
// tasks will be distributed through chTask
chTask chan timedFunc
dieOnce sync.Once
die chan struct{}
}
// NewTimedSched creates a parallel-scheduler with given parallelization
func NewTimedSched(parallel int) *TimedSched {
ts := new(TimedSched)
ts.chTask = make(chan timedFunc)
ts.die = make(chan struct{})
ts.chPrependNotify = make(chan struct{}, 1)
for i := 0; i < parallel; i++ {
go ts.sched()
}
go ts.prepend()
return ts
}
func (ts *TimedSched) sched() {
var tasks timedFuncHeap
timer := time.NewTimer(0)
drained := false
for {
select {
case task := <-ts.chTask:
now := time.Now()
if now.After(task.ts) {
// already delayed! execute immediately
task.execute()
} else {
heap.Push(&tasks, task)
// properly reset timer to trigger based on the top element
stopped := timer.Stop()
if !stopped && !drained {
<-timer.C
}
timer.Reset(tasks[0].ts.Sub(now))
drained = false
}
case now := <-timer.C:
drained = true
for tasks.Len() > 0 {
if now.After(tasks[0].ts) {
heap.Pop(&tasks).(timedFunc).execute()
} else {
timer.Reset(tasks[0].ts.Sub(now))
drained = false
break
}
}
case <-ts.die:
return
}
}
}
func (ts *TimedSched) prepend() {
var tasks []timedFunc
for {
select {
case <-ts.chPrependNotify:
ts.prependLock.Lock()
// keep cap to reuse slice
if cap(tasks) < cap(ts.prependTasks) {
tasks = make([]timedFunc, 0, cap(ts.prependTasks))
}
tasks = tasks[:len(ts.prependTasks)]
copy(tasks, ts.prependTasks)
for k := range ts.prependTasks {
ts.prependTasks[k].execute = nil // avoid memory leak
}
ts.prependTasks = ts.prependTasks[:0]
ts.prependLock.Unlock()
for k := range tasks {
select {
case ts.chTask <- tasks[k]:
tasks[k].execute = nil // avoid memory leak
case <-ts.die:
return
}
}
tasks = tasks[:0]
case <-ts.die:
return
}
}
}
// Put a function 'f' awaiting to be executed at 'deadline'
func (ts *TimedSched) Put(f func(), deadline time.Time) {
ts.prependLock.Lock()
ts.prependTasks = append(ts.prependTasks, timedFunc{f, deadline})
ts.prependLock.Unlock()
select {
case ts.chPrependNotify <- struct{}{}:
default:
}
}
// Close terminates this scheduler
func (ts *TimedSched) Close() { ts.dieOnce.Do(func() { close(ts.die) }) }
+80
View File
@@ -0,0 +1,80 @@
package kcp
import (
"net"
"sync/atomic"
"github.com/pkg/errors"
"golang.org/x/net/ipv4"
)
func buildEnet(connType uint8, enetType uint32, conv uint64) []byte {
data := make([]byte, 20)
if connType == ConnEnetSyn {
copy(data[0:4], MagicEnetSynHead)
copy(data[16:20], MagicEnetSynTail)
} else if connType == ConnEnetEst {
copy(data[0:4], MagicEnetEstHead)
copy(data[16:20], MagicEnetEstTail)
} else if connType == ConnEnetFin {
copy(data[0:4], MagicEnetFinHead)
copy(data[16:20], MagicEnetFinTail)
} else {
return nil
}
// conv的高四个字节和低四个字节分开
// 例如 00 00 01 45 | LL LL LL LL | HH HH HH HH | 49 96 02 d2 | 14 51 45 45
data[4] = uint8(conv >> 24)
data[5] = uint8(conv >> 16)
data[6] = uint8(conv >> 8)
data[7] = uint8(conv >> 0)
data[8] = uint8(conv >> 56)
data[9] = uint8(conv >> 48)
data[10] = uint8(conv >> 40)
data[11] = uint8(conv >> 32)
// Enet
data[12] = uint8(enetType >> 24)
data[13] = uint8(enetType >> 16)
data[14] = uint8(enetType >> 8)
data[15] = uint8(enetType >> 0)
return data
}
func (l *Listener) defaultSendEnetNotifyToClient(enet *Enet) {
remoteAddr, err := net.ResolveUDPAddr("udp", enet.Addr)
if err != nil {
return
}
data := buildEnet(enet.ConnType, enet.EnetType, enet.ConvId)
if data == nil {
return
}
_, _ = l.conn.WriteTo(data, remoteAddr)
}
func (s *UDPSession) defaultSendEnetNotify(enet *Enet) {
data := buildEnet(enet.ConnType, enet.EnetType, s.GetConv())
if data == nil {
return
}
s.defaultTx([]ipv4.Message{{
Buffers: [][]byte{data},
Addr: s.remote,
}})
}
func (s *UDPSession) defaultTx(txqueue []ipv4.Message) {
nbytes := 0
npkts := 0
for k := range txqueue {
if n, err := s.conn.WriteTo(txqueue[k].Buffers[0], txqueue[k].Addr); err == nil {
nbytes += n
npkts++
} else {
s.notifyWriteError(errors.WithStack(err))
break
}
}
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
}
+20
View File
@@ -0,0 +1,20 @@
//go:build !linux
// +build !linux
package kcp
import (
"golang.org/x/net/ipv4"
)
func (l *Listener) SendEnetNotifyToClient(enet *Enet) {
l.defaultSendEnetNotifyToClient(enet)
}
func (s *UDPSession) SendEnetNotify(enet *Enet) {
s.defaultSendEnetNotify(enet)
}
func (s *UDPSession) tx(txqueue []ipv4.Message) {
s.defaultTx(txqueue)
}
+102
View File
@@ -0,0 +1,102 @@
//go:build linux
// +build linux
package kcp
import (
"golang.org/x/net/ipv6"
"net"
"os"
"sync/atomic"
"github.com/pkg/errors"
"golang.org/x/net/ipv4"
)
func (l *Listener) SendEnetNotifyToClient(enet *Enet) {
var xconn batchConn
_, ok := l.conn.(*net.UDPConn)
if !ok {
return
}
localAddr, err := net.ResolveUDPAddr("udp", l.conn.LocalAddr().String())
if err != nil {
return
}
if localAddr.IP.To4() != nil {
xconn = ipv4.NewPacketConn(l.conn)
} else {
xconn = ipv6.NewPacketConn(l.conn)
}
// default version
if xconn == nil {
l.defaultSendEnetNotifyToClient(enet)
return
}
remoteAddr, err := net.ResolveUDPAddr("udp", enet.Addr)
if err != nil {
return
}
data := buildEnet(enet.ConnType, enet.EnetType, enet.ConvId)
if data == nil {
return
}
_, _ = xconn.WriteBatch([]ipv4.Message{{
Buffers: [][]byte{data},
Addr: remoteAddr,
}}, 0)
}
func (s *UDPSession) SendEnetNotify(enet *Enet) {
data := buildEnet(enet.ConnType, enet.EnetType, s.GetConv())
if data == nil {
return
}
s.tx([]ipv4.Message{{
Buffers: [][]byte{data},
Addr: s.remote,
}})
}
func (s *UDPSession) tx(txqueue []ipv4.Message) {
// default version
if s.xconn == nil || s.xconnWriteError != nil {
s.defaultTx(txqueue)
return
}
// x/net version
nbytes := 0
npkts := 0
for len(txqueue) > 0 {
if n, err := s.xconn.WriteBatch(txqueue, 0); err == nil {
for k := range txqueue[:n] {
nbytes += len(txqueue[k].Buffers[0])
}
npkts += n
txqueue = txqueue[n:]
} else {
// compatibility issue:
// for linux kernel<=2.6.32, support for sendmmsg is not available
// an error of type os.SyscallError will be returned
if operr, ok := err.(*net.OpError); ok {
if se, ok := operr.Err.(*os.SyscallError); ok {
if se.Syscall == "sendmmsg" {
s.xconnWriteError = se
s.defaultTx(txqueue)
return
}
}
}
s.notifyWriteError(errors.WithStack(err))
break
}
}
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
atomic.AddUint64(&DefaultSnmp.OutBytes, uint64(nbytes))
}
+97
View File
@@ -0,0 +1,97 @@
package mq
import (
"flswld.com/common/config"
"flswld.com/gate-hk4e-api/proto"
"flswld.com/logger"
"github.com/nats-io/nats.go"
"github.com/vmihailenco/msgpack/v5"
pb "google.golang.org/protobuf/proto"
)
type MessageQueue struct {
natsConn *nats.Conn
natsMsgChan chan *nats.Msg
netMsgInput chan *proto.NetMsg
netMsgOutput chan *proto.NetMsg
apiProtoMap *proto.ApiProtoMap
}
func NewMessageQueue(netMsgInput chan *proto.NetMsg, netMsgOutput chan *proto.NetMsg) (r *MessageQueue) {
r = new(MessageQueue)
conn, err := nats.Connect(config.CONF.MQ.NatsUrl)
if err != nil {
logger.LOG.Error("connect nats error: %v", err)
return nil
}
r.natsConn = conn
r.natsMsgChan = make(chan *nats.Msg, 10000)
_, err = r.natsConn.ChanSubscribe("GATE_HK4E", r.natsMsgChan)
if err != nil {
logger.LOG.Error("nats subscribe error: %v", err)
return nil
}
r.netMsgInput = netMsgInput
r.netMsgOutput = netMsgOutput
r.apiProtoMap = proto.NewApiProtoMap()
return r
}
func (m *MessageQueue) Start() {
go m.startRecvHandler()
go m.startSendHandler()
}
func (m *MessageQueue) Close() {
m.natsConn.Close()
}
func (m *MessageQueue) startRecvHandler() {
for {
natsMsg := <-m.natsMsgChan
// msgpack NetMsg
netMsg := new(proto.NetMsg)
err := msgpack.Unmarshal(natsMsg.Data, netMsg)
if err != nil {
logger.LOG.Error("parse bin to net msg error: %v", err)
continue
}
if netMsg.EventId == proto.NormalMsg {
// protobuf PayloadMessage
payloadMessage := m.apiProtoMap.GetProtoObjByApiId(netMsg.ApiId)
err = pb.Unmarshal(netMsg.PayloadMessageData, payloadMessage)
if err != nil {
logger.LOG.Error("parse bin to payload msg error: %v", err)
continue
}
netMsg.PayloadMessage = payloadMessage
}
m.netMsgOutput <- netMsg
}
}
func (m *MessageQueue) startSendHandler() {
for {
netMsg := <-m.netMsgInput
// protobuf PayloadMessage
payloadMessageData, err := pb.Marshal(netMsg.PayloadMessage)
if err != nil {
logger.LOG.Error("parse payload msg to bin error: %v", err)
continue
}
netMsg.PayloadMessageData = payloadMessageData
// msgpack NetMsg
netMsgData, err := msgpack.Marshal(netMsg)
if err != nil {
logger.LOG.Error("parse net msg to bin error: %v", err)
continue
}
natsMsg := nats.NewMsg("GAME_HK4E")
natsMsg.Data = netMsgData
err = m.natsConn.PublishMsg(natsMsg)
if err != nil {
logger.LOG.Error("nats publish msg error: %v", err)
continue
}
}
}
+387
View File
@@ -0,0 +1,387 @@
package net
import (
"bytes"
"encoding/binary"
"flswld.com/common/config"
"flswld.com/common/utils/random"
"flswld.com/logger"
"gate-hk4e/kcp"
"io/ioutil"
"strconv"
"sync"
"time"
)
type KcpXorKey struct {
encKey []byte
decKey []byte
}
type KcpConnectManager struct {
openState bool
connMap map[uint64]*kcp.UDPSession
connMapLock sync.RWMutex
protoMsgInput chan *ProtoMsg
protoMsgOutput chan *ProtoMsg
kcpEventInput chan *KcpEvent
kcpEventOutput chan *KcpEvent
// 发送协程分发
kcpRawSendChanMap map[uint64]chan *ProtoMsg
kcpRawSendChanMapLock sync.RWMutex
// 收包发包监听标志
kcpRecvListenMap map[uint64]bool
kcpRecvListenMapLock sync.RWMutex
kcpSendListenMap map[uint64]bool
kcpSendListenMapLock sync.RWMutex
// key
dispatchKey []byte
secretKey []byte
kcpKeyMap map[uint64]*KcpXorKey
kcpKeyMapLock sync.RWMutex
// conv短时间内唯一生成
convGenMap map[uint64]int64
convGenMapLock sync.RWMutex
}
func NewKcpConnectManager(protoMsgInput chan *ProtoMsg, protoMsgOutput chan *ProtoMsg,
kcpEventInput chan *KcpEvent, kcpEventOutput chan *KcpEvent) (r *KcpConnectManager) {
r = new(KcpConnectManager)
r.openState = true
r.connMap = make(map[uint64]*kcp.UDPSession)
r.protoMsgInput = protoMsgInput
r.protoMsgOutput = protoMsgOutput
r.kcpEventInput = kcpEventInput
r.kcpEventOutput = kcpEventOutput
r.kcpRawSendChanMap = make(map[uint64]chan *ProtoMsg)
r.kcpRecvListenMap = make(map[uint64]bool)
r.kcpSendListenMap = make(map[uint64]bool)
r.kcpKeyMap = make(map[uint64]*KcpXorKey)
r.convGenMap = make(map[uint64]int64)
return r
}
func (k *KcpConnectManager) Start() {
go func() {
// key
var err error = nil
k.dispatchKey, err = ioutil.ReadFile("static/dispatchKey.bin")
if err != nil {
logger.LOG.Error("open dispatchKey.bin error")
return
}
k.secretKey, err = ioutil.ReadFile("static/secretKey.bin")
if err != nil {
logger.LOG.Error("open secretKey.bin error")
return
}
// kcp
port := strconv.FormatInt(int64(config.CONF.Hk4e.KcpPort), 10)
listener, err := kcp.ListenWithOptions("0.0.0.0:"+port, nil, 0, 0)
if err != nil {
logger.LOG.Error("listen kcp err: %v", err)
return
} else {
go k.enetHandle(listener)
go k.chanSendHandle()
go k.eventHandle()
for {
conn, err := listener.AcceptKCP()
if err != nil {
logger.LOG.Error("accept kcp err: %v", err)
return
}
if k.openState == false {
_ = conn.Close()
continue
}
conn.SetACKNoDelay(true)
conn.SetWriteDelay(false)
convId := conn.GetConv()
logger.LOG.Debug("client connect, convId: %v", convId)
// 连接建立成功通知
k.kcpEventOutput <- &KcpEvent{
ConvId: convId,
EventId: KcpConnEstNotify,
EventMessage: conn.RemoteAddr().String(),
}
k.connMapLock.Lock()
k.connMap[convId] = conn
k.connMapLock.Unlock()
k.kcpKeyMapLock.Lock()
k.kcpKeyMap[convId] = &KcpXorKey{
encKey: k.dispatchKey,
decKey: k.dispatchKey,
}
k.kcpKeyMapLock.Unlock()
go k.recvHandle(convId)
kcpRawSendChan := make(chan *ProtoMsg, 10000)
k.kcpRawSendChanMapLock.Lock()
k.kcpRawSendChanMap[convId] = kcpRawSendChan
k.kcpRawSendChanMapLock.Unlock()
go k.sendHandle(convId, kcpRawSendChan)
go k.rttMonitor(convId)
}
}
}()
go k.clearDeadConv()
}
func (k *KcpConnectManager) clearDeadConv() {
ticker := time.NewTicker(time.Minute)
for {
k.convGenMapLock.Lock()
now := time.Now().UnixNano()
oldConvList := make([]uint64, 0)
for conv, timestamp := range k.convGenMap {
if now-timestamp > int64(time.Hour) {
oldConvList = append(oldConvList, conv)
}
}
delConvList := make([]uint64, 0)
k.connMapLock.RLock()
for _, conv := range oldConvList {
_, exist := k.connMap[conv]
if !exist {
delConvList = append(delConvList, conv)
delete(k.convGenMap, conv)
}
}
k.connMapLock.RUnlock()
k.convGenMapLock.Unlock()
logger.LOG.Info("clean dead conv list: %v", delConvList)
<-ticker.C
}
}
func (k *KcpConnectManager) enetHandle(listener *kcp.Listener) {
for {
enetNotify := <-listener.EnetNotify
logger.LOG.Info("[Enet Notify], addr: %v, conv: %v, conn: %v, enet: %v", enetNotify.Addr, enetNotify.ConvId, enetNotify.ConnType, enetNotify.EnetType)
switch enetNotify.ConnType {
case kcp.ConnEnetSyn:
if enetNotify.EnetType == kcp.EnetClientConnectKey {
var conv uint64
k.convGenMapLock.Lock()
for {
convData := random.GetRandomByte(8)
convDataBuffer := bytes.NewBuffer(convData)
_ = binary.Read(convDataBuffer, binary.LittleEndian, &conv)
_, exist := k.convGenMap[conv]
if exist {
continue
} else {
k.convGenMap[conv] = time.Now().UnixNano()
break
}
}
k.convGenMapLock.Unlock()
listener.SendEnetNotifyToClient(&kcp.Enet{
Addr: enetNotify.Addr,
ConvId: conv,
ConnType: kcp.ConnEnetEst,
EnetType: enetNotify.EnetType,
})
}
case kcp.ConnEnetEst:
case kcp.ConnEnetFin:
k.closeKcpConn(enetNotify.ConvId, enetNotify.EnetType)
case kcp.ConnEnetAddrChange:
// 连接地址改变通知
k.kcpEventOutput <- &KcpEvent{
ConvId: enetNotify.ConvId,
EventId: KcpConnAddrChangeNotify,
EventMessage: enetNotify.Addr,
}
default:
}
}
}
func (k *KcpConnectManager) chanSendHandle() {
// 分发到每个连接具体的发送协程
for {
protoMsg := <-k.protoMsgInput
k.kcpRawSendChanMapLock.RLock()
kcpRawSendChan := k.kcpRawSendChanMap[protoMsg.ConvId]
k.kcpRawSendChanMapLock.RUnlock()
if kcpRawSendChan != nil {
select {
case kcpRawSendChan <- protoMsg:
default:
logger.LOG.Error("kcpRawSendChan is full, convId: %v", protoMsg.ConvId)
}
} else {
logger.LOG.Error("kcpRawSendChan is nil, convId: %v", protoMsg.ConvId)
}
}
}
func (k *KcpConnectManager) recvHandle(convId uint64) {
// 接收
k.connMapLock.RLock()
conn := k.connMap[convId]
k.connMapLock.RUnlock()
pktFreqLimitCounter := 0
pktFreqLimitTimer := time.Now().UnixNano()
protoEnDecode := NewProtoEnDecode()
recvBuf := make([]byte, conn.GetMaxPayloadLen())
for {
_ = conn.SetReadDeadline(time.Now().Add(time.Second * 30))
recvLen, err := conn.Read(recvBuf)
if err != nil {
logger.LOG.Error("exit recv loop, conn read err: %v, convId: %v", err, convId)
k.closeKcpConn(convId, kcp.EnetServerKick)
break
}
pktFreqLimitCounter++
now := time.Now().UnixNano()
if now-pktFreqLimitTimer > int64(time.Second) {
if pktFreqLimitCounter > 1000 {
logger.LOG.Error("exit recv loop, client packet send freq too high, convId: %v, pps: %v", convId, pktFreqLimitCounter)
k.closeKcpConn(convId, kcp.EnetPacketFreqTooHigh)
break
} else {
pktFreqLimitCounter = 0
}
pktFreqLimitTimer = now
}
recvData := recvBuf[:recvLen]
k.kcpRecvListenMapLock.RLock()
flag := k.kcpRecvListenMap[convId]
k.kcpRecvListenMapLock.RUnlock()
if flag {
// 收包通知
//recvMsg := make([]byte, len(recvData))
//copy(recvMsg, recvData)
k.kcpEventOutput <- &KcpEvent{
ConvId: convId,
EventId: KcpPacketRecvNotify,
EventMessage: recvData,
}
}
kcpMsgList := make([]*KcpMsg, 0)
k.decodeBinToPayload(recvData, convId, &kcpMsgList)
for _, v := range kcpMsgList {
protoMsgList := protoEnDecode.protoDecode(v)
for _, vv := range protoMsgList {
k.protoMsgOutput <- vv
}
}
}
}
func (k *KcpConnectManager) sendHandle(convId uint64, kcpRawSendChan chan *ProtoMsg) {
// 发送
k.connMapLock.RLock()
conn := k.connMap[convId]
k.connMapLock.RUnlock()
protoEnDecode := NewProtoEnDecode()
for {
protoMsg, ok := <-kcpRawSendChan
if !ok {
logger.LOG.Error("exit send loop, send chan close, convId: %v", convId)
k.closeKcpConn(convId, kcp.EnetServerKick)
break
}
kcpMsg := protoEnDecode.protoEncode(protoMsg)
if kcpMsg == nil {
logger.LOG.Error("decode kcp msg is nil, convId: %v", convId)
continue
}
bin := k.encodePayloadToBin(kcpMsg)
_ = conn.SetWriteDeadline(time.Now().Add(time.Second * 10))
_, err := conn.Write(bin)
if err != nil {
logger.LOG.Error("exit send loop, conn write err: %v, convId: %v", err, convId)
k.closeKcpConn(convId, kcp.EnetServerKick)
break
}
k.kcpSendListenMapLock.RLock()
flag := k.kcpSendListenMap[convId]
k.kcpSendListenMapLock.RUnlock()
if flag {
// 发包通知
k.kcpEventOutput <- &KcpEvent{
ConvId: convId,
EventId: KcpPacketSendNotify,
EventMessage: bin,
}
}
}
}
func (k *KcpConnectManager) rttMonitor(convId uint64) {
ticker := time.NewTicker(time.Second * 10)
for {
select {
case <-ticker.C:
k.connMapLock.RLock()
conn := k.connMap[convId]
k.connMapLock.RUnlock()
if conn == nil {
break
}
logger.LOG.Debug("convId: %v, RTO: %v, SRTT: %v, RTTVar: %v", convId, conn.GetRTO(), conn.GetSRTT(), conn.GetSRTTVar())
k.kcpEventOutput <- &KcpEvent{
ConvId: convId,
EventId: KcpConnRttNotify,
EventMessage: conn.GetSRTT(),
}
}
}
}
func (k *KcpConnectManager) closeKcpConn(convId uint64, enetType uint32) {
k.connMapLock.RLock()
conn, exist := k.connMap[convId]
k.connMapLock.RUnlock()
if !exist {
return
}
// 获取待关闭的发送管道
k.kcpRawSendChanMapLock.RLock()
kcpRawSendChan := k.kcpRawSendChanMap[convId]
k.kcpRawSendChanMapLock.RUnlock()
// 清理数据
k.connMapLock.Lock()
delete(k.connMap, convId)
k.connMapLock.Unlock()
k.kcpRawSendChanMapLock.Lock()
delete(k.kcpRawSendChanMap, convId)
k.kcpRawSendChanMapLock.Unlock()
k.kcpRecvListenMapLock.Lock()
delete(k.kcpRecvListenMap, convId)
k.kcpRecvListenMapLock.Unlock()
k.kcpSendListenMapLock.Lock()
delete(k.kcpSendListenMap, convId)
k.kcpSendListenMapLock.Unlock()
k.kcpKeyMapLock.Lock()
delete(k.kcpKeyMap, convId)
k.kcpKeyMapLock.Unlock()
// 关闭连接
conn.SendEnetNotify(&kcp.Enet{
ConnType: kcp.ConnEnetFin,
EnetType: enetType,
})
_ = conn.Close()
// 关闭发送管道
close(kcpRawSendChan)
// 连接关闭通知
k.kcpEventOutput <- &KcpEvent{
ConvId: convId,
EventId: KcpConnCloseNotify,
}
}
func (k *KcpConnectManager) closeAllKcpConn() {
closeConnList := make([]*kcp.UDPSession, 0)
k.connMapLock.RLock()
for _, v := range k.connMap {
closeConnList = append(closeConnList, v)
}
k.connMapLock.RUnlock()
for _, v := range closeConnList {
k.closeKcpConn(v.GetConv(), kcp.EnetServerShutdown)
}
}
+187
View File
@@ -0,0 +1,187 @@
package net
import (
"bytes"
"encoding/binary"
"flswld.com/common/utils/endec"
"flswld.com/logger"
)
/*
原神KCP协议(*为xor加密数据)
0 1 2 4 8(字节)
+---------------------------------------------------------------------------------------+
| conv |
+---------------------------------------------------------------------------------------+
| cmd | frg | wnd | ts |
+---------------------------------------------------------------------------------------+
| sn | una |
+---------------------------------------------------------------------------------------+
| len | 0X4567* | apiId* |
+---------------------------------------------------------------------------------------+
| headLen* | payloadLen* | head* |
+---------------------------------------------------------------------------------------+
| payload* | 0X89AB* |
+---------------------------------------------------------------------------------------+
*/
type KcpMsg struct {
ConvId uint64
ApiId uint16
HeadData []byte
ProtoData []byte
}
func (k *KcpConnectManager) decodeBinToPayload(data []byte, convId uint64, kcpMsgList *[]*KcpMsg) {
// xor解密
k.kcpKeyMapLock.RLock()
xorKey, exist := k.kcpKeyMap[convId]
k.kcpKeyMapLock.RUnlock()
if !exist {
logger.LOG.Error("kcp xor key not exist, convId: %v", convId)
return
}
endec.Xor(data, xorKey.decKey)
k.decodeRecur(data, convId, kcpMsgList)
}
func (k *KcpConnectManager) decodeRecur(data []byte, convId uint64, kcpMsgList *[]*KcpMsg) {
// 长度太短
if len(data) < 12 {
logger.LOG.Debug("packet len less 12 byte")
return
}
// 头部标志错误
if data[0] != 0x45 || data[1] != 0x67 {
logger.LOG.Error("packet head magic 0x4567 error")
return
}
// 协议号
apiIdByteSlice := make([]byte, 8)
apiIdByteSlice[6] = data[2]
apiIdByteSlice[7] = data[3]
apiIdBuffer := bytes.NewBuffer(apiIdByteSlice)
var apiId int64
err := binary.Read(apiIdBuffer, binary.BigEndian, &apiId)
if err != nil {
logger.LOG.Error("packet api id parse fail: %v", err)
return
}
// 头部长度
headLenByteSlice := make([]byte, 8)
headLenByteSlice[6] = data[4]
headLenByteSlice[7] = data[5]
headLenBuffer := bytes.NewBuffer(headLenByteSlice)
var headLen int64
err = binary.Read(headLenBuffer, binary.BigEndian, &headLen)
if err != nil {
logger.LOG.Error("packet head len parse fail: %v", err)
return
}
// proto长度
protoLenByteSlice := make([]byte, 8)
protoLenByteSlice[4] = data[6]
protoLenByteSlice[5] = data[7]
protoLenByteSlice[6] = data[8]
protoLenByteSlice[7] = data[9]
protoLenBuffer := bytes.NewBuffer(protoLenByteSlice)
var protoLen int64
err = binary.Read(protoLenBuffer, binary.BigEndian, &protoLen)
if err != nil {
logger.LOG.Error("packet proto len parse fail: %v", err)
return
}
// 检查最小长度
if len(data) < int(headLen+protoLen)+12 {
logger.LOG.Error("packet len error")
return
}
// 尾部标志错误
if data[headLen+protoLen+10] != 0x89 || data[headLen+protoLen+11] != 0xAB {
logger.LOG.Error("packet tail magic 0x89AB error")
return
}
// 判断是否有不止一个包
haveMoreData := false
if len(data) > int(headLen+protoLen)+12 {
haveMoreData = true
}
// 头部数据
headData := data[10 : 10+headLen]
// proto数据
protoData := data[10+headLen : 10+headLen+protoLen]
// 返回数据
kcpMsg := new(KcpMsg)
kcpMsg.ConvId = convId
kcpMsg.ApiId = uint16(apiId)
//kcpMsg.HeadData = make([]byte, len(headData))
//copy(kcpMsg.HeadData, headData)
//kcpMsg.ProtoData = make([]byte, len(protoData))
//copy(kcpMsg.ProtoData, protoData)
kcpMsg.HeadData = headData
kcpMsg.ProtoData = protoData
*kcpMsgList = append(*kcpMsgList, kcpMsg)
// 递归解析
if haveMoreData {
k.decodeRecur(data[int(headLen+protoLen)+12:], convId, kcpMsgList)
}
}
func (k *KcpConnectManager) encodePayloadToBin(kcpMsg *KcpMsg) (bin []byte) {
if kcpMsg.HeadData == nil {
kcpMsg.HeadData = make([]byte, 0)
}
if kcpMsg.ProtoData == nil {
kcpMsg.ProtoData = make([]byte, 0)
}
bin = make([]byte, len(kcpMsg.HeadData)+len(kcpMsg.ProtoData)+12)
// 头部标志
bin[0] = 0x45
bin[1] = 0x67
// 协议号
apiIdBuffer := bytes.NewBuffer([]byte{})
err := binary.Write(apiIdBuffer, binary.BigEndian, kcpMsg.ApiId)
if err != nil {
logger.LOG.Error("api id encode err: %v", err)
return nil
}
bin[2] = (apiIdBuffer.Bytes())[0]
bin[3] = (apiIdBuffer.Bytes())[1]
// 头部长度
headLenBuffer := bytes.NewBuffer([]byte{})
err = binary.Write(headLenBuffer, binary.BigEndian, uint16(len(kcpMsg.HeadData)))
if err != nil {
logger.LOG.Error("head len encode err: %v", err)
return nil
}
bin[4] = (headLenBuffer.Bytes())[0]
bin[5] = (headLenBuffer.Bytes())[1]
// proto长度
protoLenBuffer := bytes.NewBuffer([]byte{})
err = binary.Write(protoLenBuffer, binary.BigEndian, uint32(len(kcpMsg.ProtoData)))
if err != nil {
logger.LOG.Error("proto len encode err: %v", err)
return nil
}
bin[6] = (protoLenBuffer.Bytes())[0]
bin[7] = (protoLenBuffer.Bytes())[1]
bin[8] = (protoLenBuffer.Bytes())[2]
bin[9] = (protoLenBuffer.Bytes())[3]
// 头部数据
copy(bin[10:], kcpMsg.HeadData)
// proto数据
copy(bin[10+len(kcpMsg.HeadData):], kcpMsg.ProtoData)
// 尾部标志
bin[len(bin)-2] = 0x89
bin[len(bin)-1] = 0xAB
// xor加密
k.kcpKeyMapLock.RLock()
xorKey, exist := k.kcpKeyMap[kcpMsg.ConvId]
k.kcpKeyMapLock.RUnlock()
if !exist {
logger.LOG.Error("kcp xor key not exist, convId: %v", kcpMsg.ConvId)
return
}
endec.Xor(bin, xorKey.encKey)
return bin
}
+134
View File
@@ -0,0 +1,134 @@
package net
import "flswld.com/logger"
const (
KcpXorKeyChange = iota
KcpPacketRecvListen
KcpPacketSendListen
KcpConnForceClose
KcpAllConnForceClose
KcpGateOpenState
KcpPacketRecvNotify
KcpPacketSendNotify
KcpConnCloseNotify
KcpConnEstNotify
KcpConnRttNotify
KcpConnAddrChangeNotify
)
type KcpEvent struct {
ConvId uint64
EventId int
EventMessage any
}
func (k *KcpConnectManager) eventHandle() {
// 事件处理
for {
event := <-k.kcpEventInput
logger.LOG.Info("kcp manager recv event, ConvId: %v, EventId: %v, EventMessage: %v", event.ConvId, event.EventId, event.EventMessage)
switch event.EventId {
case KcpXorKeyChange:
// XOR密钥切换
k.connMapLock.RLock()
_, exist := k.connMap[event.ConvId]
k.connMapLock.RUnlock()
if !exist {
logger.LOG.Error("conn not exist, convId: %v", event.ConvId)
continue
}
flag, ok := event.EventMessage.(string)
if !ok {
logger.LOG.Error("event KcpXorKeyChange msg type error")
continue
}
if flag == "ENC" {
k.kcpKeyMapLock.Lock()
k.kcpKeyMap[event.ConvId].encKey = k.secretKey
k.kcpKeyMapLock.Unlock()
} else if flag == "DEC" {
k.kcpKeyMapLock.Lock()
k.kcpKeyMap[event.ConvId].decKey = k.secretKey
k.kcpKeyMapLock.Unlock()
}
case KcpPacketRecvListen:
// 收包监听
k.connMapLock.RLock()
_, exist := k.connMap[event.ConvId]
k.connMapLock.RUnlock()
if !exist {
logger.LOG.Error("conn not exist, convId: %v", event.ConvId)
continue
}
flag, ok := event.EventMessage.(string)
if !ok {
logger.LOG.Error("event KcpXorKeyChange msg type error")
continue
}
if flag == "Enable" {
k.kcpRecvListenMapLock.Lock()
k.kcpRecvListenMap[event.ConvId] = true
k.kcpRecvListenMapLock.Unlock()
} else if flag == "Disable" {
k.kcpRecvListenMapLock.Lock()
k.kcpRecvListenMap[event.ConvId] = false
k.kcpRecvListenMapLock.Unlock()
}
case KcpPacketSendListen:
// 发包监听
k.connMapLock.RLock()
_, exist := k.connMap[event.ConvId]
k.connMapLock.RUnlock()
if !exist {
logger.LOG.Error("conn not exist, convId: %v", event.ConvId)
continue
}
flag, ok := event.EventMessage.(string)
if !ok {
logger.LOG.Error("event KcpXorKeyChange msg type error")
continue
}
if flag == "Enable" {
k.kcpSendListenMapLock.Lock()
k.kcpSendListenMap[event.ConvId] = true
k.kcpSendListenMapLock.Unlock()
} else if flag == "Disable" {
k.kcpSendListenMapLock.Lock()
k.kcpSendListenMap[event.ConvId] = false
k.kcpSendListenMapLock.Unlock()
}
case KcpConnForceClose:
// 强制关闭某个连接
k.connMapLock.RLock()
_, exist := k.connMap[event.ConvId]
k.connMapLock.RUnlock()
if !exist {
logger.LOG.Error("conn not exist, convId: %v", event.ConvId)
continue
}
reason, ok := event.EventMessage.(uint32)
if !ok {
logger.LOG.Error("event KcpConnForceClose msg type error")
continue
}
k.closeKcpConn(event.ConvId, reason)
logger.LOG.Info("conn has been force close, convId: %v", event.ConvId)
case KcpAllConnForceClose:
// 强制关闭所有连接
k.closeAllKcpConn()
logger.LOG.Info("all conn has been force close")
case KcpGateOpenState:
// 改变网关开放状态
openState, ok := event.EventMessage.(bool)
if !ok {
logger.LOG.Error("event KcpGateOpenState msg type error")
continue
}
k.openState = openState
if openState == false {
k.closeAllKcpConn()
}
}
}
}
+160
View File
@@ -0,0 +1,160 @@
package net
import (
"flswld.com/gate-hk4e-api/proto"
"flswld.com/logger"
pb "google.golang.org/protobuf/proto"
)
type ProtoEnDecode struct {
apiProtoMap *proto.ApiProtoMap
}
func NewProtoEnDecode() (r *ProtoEnDecode) {
r = new(ProtoEnDecode)
r.apiProtoMap = proto.NewApiProtoMap()
return r
}
type ProtoMsg struct {
ConvId uint64
ApiId uint16
HeadMessage *proto.PacketHead
PayloadMessage pb.Message
}
type ProtoMessage struct {
apiId uint16
message pb.Message
}
func (p *ProtoEnDecode) protoDecode(kcpMsg *KcpMsg) (protoMsgList []*ProtoMsg) {
protoMsgList = make([]*ProtoMsg, 0)
protoMsg := new(ProtoMsg)
protoMsg.ConvId = kcpMsg.ConvId
protoMsg.ApiId = kcpMsg.ApiId
// head msg
if kcpMsg.HeadData != nil && len(kcpMsg.HeadData) != 0 {
headMsg := new(proto.PacketHead)
err := pb.Unmarshal(kcpMsg.HeadData, headMsg)
if err != nil {
logger.LOG.Error("unmarshal head data err: %v", err)
return protoMsgList
}
protoMsg.HeadMessage = headMsg
} else {
protoMsg.HeadMessage = nil
}
// payload msg
protoMessageList := make([]*ProtoMessage, 0)
p.protoDecodePayloadCore(kcpMsg.ApiId, kcpMsg.ProtoData, &protoMessageList)
if len(protoMessageList) == 0 {
logger.LOG.Error("decode proto object is nil")
return protoMsgList
}
if kcpMsg.ApiId == proto.ApiUnionCmdNotify {
for _, protoMessage := range protoMessageList {
msg := new(ProtoMsg)
msg.ConvId = kcpMsg.ConvId
msg.ApiId = protoMessage.apiId
msg.HeadMessage = protoMsg.HeadMessage
msg.PayloadMessage = protoMessage.message
//logger.LOG.Debug("[recv] union proto msg, convId: %v, apiId: %v", msg.ConvId, msg.ApiId)
if protoMessage.apiId == proto.ApiUnionCmdNotify {
// 聚合消息自身不再往后发送
continue
}
//logger.LOG.Debug("[recv] proto msg, convId: %v, apiId: %v, headMsg: %v", protoMsg.ConvId, protoMsg.ApiId, protoMsg.HeadMessage)
protoMsgList = append(protoMsgList, msg)
}
// 聚合消息自身不再往后发送
return protoMsgList
} else {
protoMsg.PayloadMessage = protoMessageList[0].message
}
//logger.LOG.Debug("[recv] proto msg, convId: %v, apiId: %v, headMsg: %v", protoMsg.ConvId, protoMsg.ApiId, protoMsg.HeadMessage)
protoMsgList = append(protoMsgList, protoMsg)
return protoMsgList
}
func (p *ProtoEnDecode) protoDecodePayloadCore(apiId uint16, protoData []byte, protoMessageList *[]*ProtoMessage) {
protoObj := p.decodePayloadToProto(apiId, protoData)
if protoObj == nil {
logger.LOG.Error("decode proto object is nil")
return
}
if apiId == proto.ApiUnionCmdNotify {
// 处理聚合消息
unionCmdNotify, ok := protoObj.(*proto.UnionCmdNotify)
if !ok {
logger.LOG.Error("parse union cmd error")
return
}
for _, cmd := range unionCmdNotify.GetCmdList() {
p.protoDecodePayloadCore(uint16(cmd.MessageId), cmd.Body, protoMessageList)
}
}
*protoMessageList = append(*protoMessageList, &ProtoMessage{
apiId: apiId,
message: protoObj,
})
}
func (p *ProtoEnDecode) protoEncode(protoMsg *ProtoMsg) (kcpMsg *KcpMsg) {
//logger.LOG.Debug("[send] proto msg, convId: %v, apiId: %v, headMsg: %v", protoMsg.ConvId, protoMsg.ApiId, protoMsg.HeadMessage)
kcpMsg = new(KcpMsg)
kcpMsg.ConvId = protoMsg.ConvId
kcpMsg.ApiId = protoMsg.ApiId
// head msg
if protoMsg.HeadMessage != nil {
headData, err := pb.Marshal(protoMsg.HeadMessage)
if err != nil {
logger.LOG.Error("marshal head data err: %v", err)
return nil
}
kcpMsg.HeadData = headData
} else {
kcpMsg.HeadData = nil
}
// payload msg
if protoMsg.PayloadMessage != nil {
apiId, protoData := p.encodeProtoToPayload(protoMsg.PayloadMessage)
if apiId == 0 || protoData == nil {
logger.LOG.Error("encode proto data is nil")
return nil
}
if apiId != 65535 && apiId != protoMsg.ApiId {
logger.LOG.Error("api id is not match with proto obj, src api id: %v, found api id: %v", protoMsg.ApiId, apiId)
return nil
}
kcpMsg.ProtoData = protoData
} else {
kcpMsg.ProtoData = nil
}
return kcpMsg
}
func (p *ProtoEnDecode) decodePayloadToProto(apiId uint16, protoData []byte) (protoObj pb.Message) {
protoObj = p.apiProtoMap.GetProtoObjByApiId(apiId)
if protoObj == nil {
logger.LOG.Error("get new proto object is nil")
return nil
}
err := pb.Unmarshal(protoData, protoObj)
if err != nil {
logger.LOG.Error("unmarshal proto data err: %v", err)
return nil
}
return protoObj
}
func (p *ProtoEnDecode) encodeProtoToPayload(protoObj pb.Message) (apiId uint16, protoData []byte) {
apiId = p.apiProtoMap.GetApiIdByProtoObj(protoObj)
var err error = nil
protoData, err = pb.Marshal(protoObj)
if err != nil {
logger.LOG.Error("marshal proto object err: %v", err)
return 0, nil
}
return apiId, protoData
}
+175
View File
@@ -0,0 +1,175 @@
package region
import (
"encoding/base64"
"flswld.com/common/utils/endec"
"flswld.com/gate-hk4e-api/proto"
"flswld.com/logger"
pb "google.golang.org/protobuf/proto"
"io/ioutil"
)
func LoadRsaKey() (signRsaKey []byte, encRsaKeyMap map[string][]byte, pwdRsaKey []byte) {
var err error = nil
encRsaKeyMap = make(map[string][]byte)
signRsaKey, err = ioutil.ReadFile("static/region_sign_key.pem")
if err != nil {
logger.LOG.Error("open region_sign_key.pem error: %v", err)
return nil, nil, nil
}
encKeyIdList := []string{"2", "3", "4", "5"}
for _, v := range encKeyIdList {
encRsaKeyMap[v], err = ioutil.ReadFile("static/region_enc_key_" + v + ".pem")
if err != nil {
logger.LOG.Error("open region_enc_key_3.pem error: %v", err)
return nil, nil, nil
}
}
pwdRsaKey, err = ioutil.ReadFile("static/account_password_key.pem")
if err != nil {
logger.LOG.Error("open account_password_key.pem error: %v", err)
return nil, nil, nil
}
return signRsaKey, encRsaKeyMap, pwdRsaKey
}
func InitRegion(kcpAddr string, kcpPort int) (*proto.QueryCurrRegionHttpRsp, *proto.QueryRegionListHttpRsp) {
dispatchKey, err := ioutil.ReadFile("static/dispatchKey.bin")
if err != nil {
logger.LOG.Error("open dispatchKey.bin error: %v", err)
return nil, nil
}
dispatchSeed, err := ioutil.ReadFile("static/dispatchSeed.bin")
if err != nil {
logger.LOG.Error("open dispatchSeed.bin error: %v", err)
return nil, nil
}
// RegionCurr
regionCurr := new(proto.QueryCurrRegionHttpRsp)
regionCurr.RegionInfo = &proto.RegionInfo{
GateserverIp: kcpAddr,
GateserverPort: uint32(kcpPort),
SecretKey: dispatchSeed,
}
// RegionList
customConfigStr := `
{
"sdkenv": "2",
"checkdevice": "false",
"loadPatch": "false",
"showexception": "false",
"regionConfig": "pm|fk|add",
"downloadMode": "0",
}
`
customConfig := []byte(customConfigStr)
endec.Xor(customConfig, dispatchKey)
serverList := make([]*proto.RegionSimpleInfo, 0)
server := &proto.RegionSimpleInfo{
Name: "os_usa",
Title: "America",
Type: "DEV_PUBLIC",
DispatchUrl: "https://osusadispatch.yuanshen.com/query_cur_region",
}
serverList = append(serverList, server)
regionList := new(proto.QueryRegionListHttpRsp)
regionList.RegionList = serverList
regionList.ClientSecretKey = dispatchSeed
regionList.ClientCustomConfigEncrypted = customConfig
regionList.EnableLoginPc = true
return regionCurr, regionList
}
// 新的region构建方式已经不需要读取query_region_list和query_cur_region文件了 并跳过了版本校验的blk补丁下载
func _InitRegion(log *logger.Logger, kcpAddr string, kcpPort int) (*proto.QueryCurrRegionHttpRsp, *proto.QueryRegionListHttpRsp) {
// TODO 总有一天要把这些烦人的数据全部自己构造别再他妈的读文件了
// 加载文件
regionListKeyFile, err := ioutil.ReadFile("static/query_region_list_key")
if err != nil {
log.Error("open query_region_list_key error")
return nil, nil
}
regionCurrKeyFile, err := ioutil.ReadFile("static/query_cur_region_key")
if err != nil {
log.Error("open query_cur_region_key error")
return nil, nil
}
regionListFile, err := ioutil.ReadFile("static/query_region_list")
if err != nil {
log.Error("open query_region_list error")
return nil, nil
}
regionCurrFile, err := ioutil.ReadFile("static/query_cur_region")
if err != nil {
log.Error("open query_cur_region error")
return nil, nil
}
regionListKeyBin, err := base64.StdEncoding.DecodeString(string(regionListKeyFile))
if err != nil {
log.Error("decode query_region_list_key error")
return nil, nil
}
regionCurrKeyBin, err := base64.StdEncoding.DecodeString(string(regionCurrKeyFile))
if err != nil {
log.Error("decode query_cur_region_key error")
return nil, nil
}
regionListBin, err := base64.StdEncoding.DecodeString(string(regionListFile))
if err != nil {
log.Error("decode query_region_list error")
return nil, nil
}
regionCurrBin, err := base64.StdEncoding.DecodeString(string(regionCurrFile))
if err != nil {
log.Error("decode query_cur_region error")
return nil, nil
}
regionListKey := new(proto.QueryRegionListHttpRsp)
err = pb.Unmarshal(regionListKeyBin, regionListKey)
if err != nil {
log.Error("Unmarshal QueryRegionListHttpRsp error")
return nil, nil
}
regionCurrKey := new(proto.QueryCurrRegionHttpRsp)
err = pb.Unmarshal(regionCurrKeyBin, regionCurrKey)
if err != nil {
log.Error("Unmarshal QueryCurrRegionHttpRsp error")
return nil, nil
}
regionList := new(proto.QueryRegionListHttpRsp)
err = pb.Unmarshal(regionListBin, regionList)
if err != nil {
log.Error("Unmarshal QueryRegionListHttpRsp error")
return nil, nil
}
regionCurr := new(proto.QueryCurrRegionHttpRsp)
err = pb.Unmarshal(regionCurrBin, regionCurr)
if err != nil {
log.Error("Unmarshal QueryCurrRegionHttpRsp error")
return nil, nil
}
secretKey, err := ioutil.ReadFile("static/dispatchSeed.bin")
if err != nil {
log.Error("open dispatchSeed.bin error")
return nil, nil
}
// RegionCurr
regionInfo := regionCurr.GetRegionInfo()
regionInfo.GateserverIp = kcpAddr
regionInfo.GateserverPort = uint32(kcpPort)
regionInfo.SecretKey = secretKey
regionCurr.RegionInfo = regionInfo
// RegionList
serverList := make([]*proto.RegionSimpleInfo, 0)
server := &proto.RegionSimpleInfo{
Name: "os_usa",
Title: "America",
Type: "DEV_PUBLIC",
DispatchUrl: "https://osusadispatch.yuanshen.com/query_cur_region",
}
serverList = append(serverList, server)
regionList.RegionList = serverList
regionList.ClientSecretKey = regionListKey.ClientSecretKey
regionList.ClientCustomConfigEncrypted = regionListKey.ClientCustomConfigEncrypted
return regionCurr, regionList
}
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
package rpc
import "gate-hk4e/forward"
type RpcManager struct {
forwardManager *forward.ForwardManager
}
func NewRpcManager(forwardManager *forward.ForwardManager) (r *RpcManager) {
r = new(RpcManager)
r.forwardManager = forwardManager
return r
}
+62
View File
@@ -0,0 +1,62 @@
package rpc
import (
"flswld.com/gate-hk4e-api/gm"
"github.com/pkg/errors"
)
// rpc interface
// 改变网关开放状态
func (r *RpcManager) ChangeGateOpenState(isOpen *bool, result *bool) error {
if isOpen == nil || result == nil {
return errors.New("param is nil")
}
*result = r.forwardManager.ChangeGateOpenState(*isOpen)
return nil
}
// 剔除玩家下线
func (r *RpcManager) KickPlayer(info *gm.KickPlayerInfo, result *bool) error {
if info == nil || result == nil {
return errors.New("param is nil")
}
*result = r.forwardManager.KickPlayer(info)
return nil
}
// 获取网关在线玩家信息
func (r *RpcManager) GetOnlineUser(uid *uint32, list *gm.OnlineUserList) error {
if uid == nil || list == nil {
return errors.New("param is nil")
}
list = r.forwardManager.GetOnlineUser(*uid)
return nil
}
// 用户密码改变
func (r *RpcManager) UserPasswordChange(uid *uint32, result *bool) error {
if uid == nil || result == nil {
return errors.New("param is nil")
}
*result = r.forwardManager.UserPasswordChange(*uid)
return nil
}
// 封号
func (r *RpcManager) ForbidUser(info *gm.ForbidUserInfo, result *bool) error {
if info == nil || result == nil {
return errors.New("param is nil")
}
*result = r.forwardManager.ForbidUser(info)
return nil
}
// 解封
func (r *RpcManager) UnForbidUser(uid *uint32, result *bool) error {
if uid == nil || result == nil {
return errors.New("param is nil")
}
*result = r.forwardManager.UnForbidUser(*uid)
return nil
}