fix: adjustment directory structure
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -86,3 +86,4 @@ third-lib
|
||||
|
||||
# binary files
|
||||
bin
|
||||
public
|
||||
|
||||
4
Makefile
4
Makefile
@@ -10,11 +10,11 @@ cproxy_clean:
|
||||
$(MAKE) -C proxy_c clean
|
||||
|
||||
goproxy_clean:
|
||||
$(MAKE) -C proxy_go clean
|
||||
$(MAKE) -C proxy clean
|
||||
|
||||
cproxy:
|
||||
$(MAKE) -C proxy_c
|
||||
|
||||
goproxy:
|
||||
$(MAKE) -C proxy_go build
|
||||
$(MAKE) -C proxy build
|
||||
|
||||
|
||||
0
proxy_go/.idea/.gitignore → proxy/.idea/.gitignore
generated
vendored
0
proxy_go/.idea/.gitignore → proxy/.idea/.gitignore
generated
vendored
@@ -57,7 +57,7 @@ sudo apt install libhiredis-dev
|
||||
log:
|
||||
output: file
|
||||
level: info #log¼¶±ð
|
||||
path: /var/log/proxy_go.log
|
||||
path: /var/log/proxy.log
|
||||
maxAge: 120
|
||||
rotationTime: 3
|
||||
mysqlDb:
|
||||
194
proxy/canal/canal.go
Normal file
194
proxy/canal/canal.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package canal
|
||||
|
||||
import (
|
||||
"github.com/go-mysql-org/go-mysql/canal"
|
||||
"github.com/go-mysql-org/go-mysql/mysql"
|
||||
"github.com/go-mysql-org/go-mysql/replication"
|
||||
mdb "proxy/Nmysql"
|
||||
rds "proxy/Nredis"
|
||||
"proxy/config"
|
||||
|
||||
//"github.com/siddontang/go-log/log"
|
||||
//"strconv"
|
||||
|
||||
"proxy/logger"
|
||||
)
|
||||
|
||||
type MyEventHandler struct {
|
||||
canal.DummyEventHandler
|
||||
}
|
||||
|
||||
// 监听数据记录
|
||||
func (h *MyEventHandler) OnRow(e *canal.RowsEvent) error {
|
||||
logger.CanalLog.Infof("OnRow: %s.%s %s %v", e.Table.Schema, e.Table.Name, e.Action, e.Rows)
|
||||
|
||||
/*if !config.Config.CanalServer.Standalone && rds.CheckIfRdbMaster() == false {
|
||||
logger.CanalLog.Warnf("not stand alone, and change to Slave!")
|
||||
cnl.Close()
|
||||
return nil
|
||||
}*/
|
||||
c := ParseAndFilterBinLog(e)
|
||||
updateRedisTable(c)
|
||||
for columnIndex, curColumn := range e.Table.Columns {
|
||||
logger.CanalLog.Debugf("row info: %v %v %v", curColumn.Name, columnIndex, e.Rows[len(e.Rows)-1][columnIndex])
|
||||
}
|
||||
//cnl.Close()// if slave
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建,更改,重命名或者删除表时触发,通常会需要清除与表相关的数据,如缓存。 It will be called before OnDDL.
|
||||
func (h *MyEventHandler) OnTableChanged(header *replication.EventHeader, schema string, table string) error {
|
||||
logger.CanalLog.Infof("OnTableChanged: %s %s", schema, table)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 监听binlog日志的变化文件与记录的位置
|
||||
func (h *MyEventHandler) OnPosSynced(header *replication.EventHeader, pos mysql.Position, set mysql.GTIDSet, force bool) error {
|
||||
// if force == true, 立即同步位置
|
||||
rds.RdbSetBinLogPos(pos.Name, pos.Pos)
|
||||
logger.CanalLog.Infof("OnPosSynced: Name[%v] Pos[%v], force[%t]", pos.Name, pos.Pos, force)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 当产生新的binlog日志后触发(在达到内存的使用限制后(默认为1GB),会开启另一个文件,每个新文件的名称后都会有一个增量.)
|
||||
func (h *MyEventHandler) OnRotate(header *replication.EventHeader, r *replication.RotateEvent) error {
|
||||
// record := fmt.Sprintf("On Rotate: %v \n", &mysql.Position{Name: string(r.NextLogName), Pos: uint32(r.Position)})
|
||||
// binlog的记录位置,新binlog的文件名
|
||||
logger.CanalLog.Infof("On Rotate: Pos[%v] NextLogName[%v] \n", r.Position, r.NextLogName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create alter drop truncate(删除当前表再新建一个一模一样的表结构)
|
||||
func (h *MyEventHandler) OnDDL(header *replication.EventHeader, nextPos mysql.Position, queryEvent *replication.QueryEvent) error {
|
||||
// binlog日志的变化文件与记录的位置
|
||||
logger.CanalLog.Infof("OnDDL: Name[%v] Pos[%v]\n", nextPos.Name, nextPos.Pos)
|
||||
logger.CanalLog.Infof("%v\n %v\n %v\n %v\n %v\n",
|
||||
queryEvent.ExecutionTime, // 猜是执行时间,但测试显示0
|
||||
string(queryEvent.Schema), // 库名
|
||||
string(queryEvent.Query), // 变更的sql语句
|
||||
string(queryEvent.StatusVars[:]), //测试显示乱码
|
||||
queryEvent.SlaveProxyID) // 从库代理ID?
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *MyEventHandler) String() string {
|
||||
return "MyEventHandler"
|
||||
}
|
||||
|
||||
var cnl *canal.Canal
|
||||
func StartMyCanal(addr string, username string, password string) {
|
||||
cfg := canal.NewDefaultConfig()
|
||||
cfg.Addr = addr //"192.168.1.211:3306"
|
||||
cfg.User = username
|
||||
cfg.Password = password
|
||||
// We only care table canal_test in test db
|
||||
cfg.Dump.TableDB = "boss"
|
||||
cfg.Dump.Tables = []string{"tb_prd_ofr_detail_inst_551",
|
||||
"tb_bil_tariff", "tb_prd_ofr", "tb_bil_evt_pricing_strategy", "config_area",
|
||||
"tb_bil_pricing_area", "ratable_history", "tb_bil_holiday_rel", "tb_bil_holiday"}//, "tb_prd_prd_inst_551"}
|
||||
cfg.IncludeTableRegex = make([]string, 0, 1)
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_prd_prd_inst_551")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_prd_ofr_detail_inst_551")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_bil_tariff")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_prd_ofr")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_bil_evt_pricing_strategy")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.config_area")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_bil_pricing_area")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.ratable_history")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_bil_holiday_rel")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_bil_holiday")
|
||||
if config.Config.CronCfg.Enabled && config.Config.CronCfg.NtfSms != "" {
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_sms_info")
|
||||
}
|
||||
if config.Config.Rest.Enabled {
|
||||
cfg.Dump.Tables = append(cfg.Dump.Tables, "tb_sync_mobile")
|
||||
cfg.IncludeTableRegex = append(cfg.IncludeTableRegex, "boss\\.tb_sync_mobile")
|
||||
}
|
||||
|
||||
c, err := canal.NewCanal(cfg)
|
||||
if err != nil {
|
||||
logger.CanalLog.Errorf("NewCanal, err: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Register a handler to handle RowsEvent
|
||||
c.SetEventHandler(&MyEventHandler{})
|
||||
|
||||
var pos *mysql.Position
|
||||
if config.Config.CanalServer.Reinit {
|
||||
pos = QueryBinLogPos(c)
|
||||
if pos == nil {
|
||||
logger.CanalLog.Errorln("fail to get binlog position.")
|
||||
return
|
||||
}
|
||||
|
||||
logger.CanalLog.Infoln("start to sync mysql DB.")
|
||||
CurState = "loading"
|
||||
if config.Config.CanalServer.FlushBeforeInit {
|
||||
rds.ClrTariffAndBundle()
|
||||
}
|
||||
_ = mdb.LoadAcctTblFromMysql()
|
||||
_ = mdb.LoadAlertSmsFromMysql()
|
||||
//_ = mdb.LoadCreateAcctFromMysql()
|
||||
config.Config.CanalServer.Reinit = false
|
||||
config.SavePcfCfg()
|
||||
logger.CanalLog.Infoln("finish sync mysql DB...")
|
||||
CurState = "synchronize"
|
||||
} else {
|
||||
ret := rds.RdbGetBinLogPos()
|
||||
if ret == nil {
|
||||
logger.CanalLog.Warnln("fail to get redis binlog position, query current position from mysql.")
|
||||
pos = QueryBinLogPos(c)
|
||||
if pos == nil {
|
||||
logger.CanalLog.Errorln("fail to get binlog position.")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
pos = &mysql.Position{Name: ret.File, Pos: ret.Position}
|
||||
}
|
||||
}
|
||||
|
||||
// Start canal
|
||||
cnl = c
|
||||
logger.CanalLog.Infof("Go run")
|
||||
err = c.RunFrom(*pos)
|
||||
if err != nil {
|
||||
logger.CanalLog.Errorf("RunFrom err: %v", err)
|
||||
} else {
|
||||
logger.CanalLog.Warnf("Canal exit!!!!!!")
|
||||
c.Close()
|
||||
}
|
||||
|
||||
/* 从头开始监听
|
||||
err = c.Run()
|
||||
if err != nil {
|
||||
logger.CanalLog.Infof("Run err: %v", err)
|
||||
}*/
|
||||
|
||||
// mysql-bin.000004, 1027
|
||||
// startPos := mysql.Position{Name: "mysql-bin.000004", Pos: 1027}
|
||||
// c.RunFrom(startPos)
|
||||
}
|
||||
|
||||
func QueryBinLogPos(c *canal.Canal) *mysql.Position {
|
||||
var pos mysql.Position
|
||||
ret, err := c.Execute("SHOW MASTER STATUS;")
|
||||
if err != nil {
|
||||
logger.CanalLog.Errorf("SHOW MASTER STATUS err: %v", err)
|
||||
return nil
|
||||
}
|
||||
pos.Name, err = ret.Resultset.GetStringByName(0, "File")
|
||||
if err != nil {
|
||||
logger.CanalLog.Errorf("Get binlog file name err: %v", err)
|
||||
return nil
|
||||
}
|
||||
posTmp, err := ret.Resultset.GetUintByName(0, "Position")
|
||||
if err != nil {
|
||||
logger.CanalLog.Errorf("Get binlog position err: %v", err)
|
||||
return nil
|
||||
}
|
||||
pos.Pos = uint32(posTmp)
|
||||
logger.CanalLog.Infof("Current Position: %v", pos)
|
||||
rds.RdbSetBinLogPos(pos.Name, pos.Pos)
|
||||
return &pos
|
||||
}
|
||||
176
proxy/go.sum
Normal file
176
proxy/go.sum
Normal file
@@ -0,0 +1,176 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-mysql-org/go-mysql v1.11.0 h1:Y0ooXu2UtbjsgpfjFBXZEvidEl1q8n0ESxej0zZ78Zc=
|
||||
github.com/go-mysql-org/go-mysql v1.11.0/go.mod h1:y/7aggbs+Io8rPVerIjTe1+nMgt8q5tBIxIc+qQnE0k=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
|
||||
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg=
|
||||
github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ircop/tclient v0.0.0-20180908074649-f30684f28b0f h1:p7j1sjVHXwjJpr8fYjzInUGTmK9rYXgcQPiXjpawl/c=
|
||||
github.com/ircop/tclient v0.0.0-20180908074649-f30684f28b0f/go.mod h1:d111ZaRuwWWPIw3+gL48/SD8Pj9P6qBS5scMGWFt6/I=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb h1:3pSi4EDG6hg0orE1ndHkXvX6Qdq2cZn8gAPir8ymKZk=
|
||||
github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
|
||||
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 h1:tdMsjOqUR7YXHoBitzdebTvOjs/swniBTOLy5XiMtuE=
|
||||
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86/go.mod h1:exzhVYca3WRtd6gclGNErRWb1qEgff3LYta0LvRmON4=
|
||||
github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 h1:2SOzvGvE8beiC1Y4g9Onkvu6UmuBBOeWRGQEjJaT/JY=
|
||||
github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=
|
||||
github.com/pingcap/tidb/pkg/parser v0.0.0-20241118164214-4f047be191be h1:t5EkCmZpxLCig5GQA0AZG47aqsuL5GTsJeeUD+Qfies=
|
||||
github.com/pingcap/tidb/pkg/parser v0.0.0-20241118164214-4f047be191be/go.mod h1:Hju1TEWZvrctQKbztTRwXH7rd41Yq0Pgmq4PrEKcq7o=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
|
||||
github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=
|
||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 h1:oI+RNwuC9jF2g2lP0u0cVEEZrc/AYBCuFdvwrLWM/6Q=
|
||||
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/withlin/canal-go v1.1.2 h1:ugAjPPXB1g6sePQLei/l7eaZ57ioJSo87/+z4++H2hY=
|
||||
github.com/withlin/canal-go v1.1.2/go.mod h1:VlapEcKekyXd7luA6QzflxbisrYzSLAjpRqzLcgr+zk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
248
proxy_go/go.sum
248
proxy_go/go.sum
@@ -1,248 +0,0 @@
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.0 h1:8zixYquU1Odk+vzAaAQPAdRh1ZjmUXNQ1T+dUBvlhVo=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.0/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cznic/golex v0.0.0-20181122101858-9c343928389c/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc=
|
||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
|
||||
github.com/cznic/parser v0.0.0-20160622100904-31edd927e5b1/go.mod h1:2B43mz36vGZNZEwkWi8ayRSSUXLfjL8OkbzwW4NcPMM=
|
||||
github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
|
||||
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
|
||||
github.com/cznic/y v0.0.0-20170802143616-045f81c6662a/go.mod h1:1rk5VM7oSnA4vjp+hrLQ3HWHa+Y4yPCa3/CsJrcNnvs=
|
||||
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
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-mysql-org/go-mysql v1.6.0 h1:19B5fojzZcri/1wj9G/1+ws8RJ3N6rJs2X5c/+kBLuQ=
|
||||
github.com/go-mysql-org/go-mysql v1.6.0/go.mod h1:GX0clmylJLdZEYAojPCDTCvwZxbTBrke93dV55715u0=
|
||||
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/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-redis/redis/v8 v8.11.0 h1:O1Td0mQ8UFChQ3N9zFQqo6kTU2cJ+/it88gDB+zg0wo=
|
||||
github.com/go-redis/redis/v8 v8.11.0/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
|
||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/protobuf v1.2.0/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/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ircop/tclient v0.0.0-20180908074649-f30684f28b0f h1:p7j1sjVHXwjJpr8fYjzInUGTmK9rYXgcQPiXjpawl/c=
|
||||
github.com/ircop/tclient v0.0.0-20180908074649-f30684f28b0f/go.mod h1:d111ZaRuwWWPIw3+gL48/SD8Pj9P6qBS5scMGWFt6/I=
|
||||
github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk=
|
||||
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
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/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
|
||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
|
||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8 h1:USx2/E1bX46VG32FIw034Au6seQ2fY9NEILmNh/UlQg=
|
||||
github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=
|
||||
github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pingcap/errors v0.11.5-0.20201029093017-5a7df2af2ac7/go.mod h1:G7x87le1poQzLB/TqvTJI2ILrSgobnq4Ut7luOwvfvI=
|
||||
github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 h1:LllgC9eGfqzkfubMgjKIDyZYaa609nNWAyNZtpy2B3M=
|
||||
github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3/go.mod h1:G7x87le1poQzLB/TqvTJI2ILrSgobnq4Ut7luOwvfvI=
|
||||
github.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=
|
||||
github.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4 h1:ERrF0fTuIOnwfGbt71Ji3DKbOEaP189tjym50u8gpC8=
|
||||
github.com/pingcap/log v0.0.0-20210317133921-96f4fcab92a4/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=
|
||||
github.com/pingcap/parser v0.0.0-20210415081931-48e7f467fd74 h1:FkVEC3Fck3fD16hMObMl/IWs72jR9FmqPn0Bdf728Sk=
|
||||
github.com/pingcap/parser v0.0.0-20210415081931-48e7f467fd74/go.mod h1:xZC8I7bug4GJ5KtHhgAikjTfU4kBv1Sbo3Pf1MZ6lVw=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM=
|
||||
github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI=
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e h1:quuzZLi72kkJjl+f5AQ93FMcadG19WkS7MO6TXFOSas=
|
||||
github.com/reiver/go-telnet v0.0.0-20180421082511-9ff0b2ab096e/go.mod h1:+5vNVvEWwEIx86DB9Ke/+a5wBI464eDRo3eF0LcfpWg=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
|
||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
|
||||
github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed h1:KMgQoLJGCq1IoZpLZE3AIffh9veYWoVlsvA4ib55TMM=
|
||||
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go v1.2.1 h1:dz+JxTe7GZQdErTo7SREc1jQj/hFP1k7jyIAwODoW+k=
|
||||
github.com/ugorji/go v1.2.1/go.mod h1:cSVypSfTLm2o9fKxXvQgn3rMmkPXovcWor6Qn5tbFmI=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.1 h1:/TRfW3XKkvWvmAYyCUaQlhoCDGjcvNR8xVVA/l5p/jQ=
|
||||
github.com/ugorji/go/codec v1.2.1/go.mod h1:s/WxCRi46t8rA+fowL40EnmD7ec0XhR7ZypxeBNdzsM=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/withlin/canal-go v1.1.1 h1:eEcX/184K9tIny6krZi2NGXN111cyINzTPRNTfr83Fg=
|
||||
github.com/withlin/canal-go v1.1.1/go.mod h1:dIyy0yorJ7CfPnVh8sYqkBItyqTQNTxPftE3fBJTkmY=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604=
|
||||
golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb h1:eBmm0M9fYhWpKZLjQUUKka/LtIxf46G4fxeEz5KJr9U=
|
||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091 h1:DMyOG0U+gKfu8JZzg2UQe9MeaC1X+xQWlAKcRnjxjCw=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
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.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2016 Charles Iliya Krempeaux <charles@reptile.ca> :: http://changelog.ca/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,64 +0,0 @@
|
||||
# go-oi
|
||||
|
||||
Package **oi** provides useful tools to be used with the Go programming language's standard "io" package.
|
||||
|
||||
For example, did you know that when you call the `Write` method on something that fits the `io.Writer`
|
||||
interface, that it is possible that not everything was be written?!
|
||||
|
||||
I.e., that a _**short write**_ happened.
|
||||
|
||||
That just doing the following is (in general) **not** enough:
|
||||
```
|
||||
n, err := writer.Write(p)
|
||||
```
|
||||
|
||||
That, for example, you should be checking if `err == io.ErrShortWrite`, and then maybe calling the `Write`
|
||||
method again but only with what didn't get written.
|
||||
|
||||
For a simple example of this (that actually is **not** sufficient to solve this problem, but illustrates
|
||||
the direction you would need to go to solve this problem is):
|
||||
```
|
||||
n, err := w.Write(p)
|
||||
|
||||
if io.ErrShortWrite == err {
|
||||
n2, err2 := w.Write(p[n:])
|
||||
}
|
||||
```
|
||||
|
||||
Note that the second call to the `Write` method passed `p[n:]` (instead of just `p`), to account for the `n` bytes
|
||||
already being written (with the first call to the `Write` method).
|
||||
|
||||
A more "production quality" version of this would likely be in a loop, but such that that the loop had "guards"
|
||||
against looping forever, and also possibly looping for "too long".
|
||||
|
||||
Well package **oi** provides tools that helps you deal with this and other problems. For example, you
|
||||
can handle a _**short write**_ with the following **oi** func:
|
||||
```
|
||||
n, err := oi.LongWrite(writer, p)
|
||||
```
|
||||
|
||||
|
||||
## Documention
|
||||
|
||||
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-oi
|
||||
|
||||
[](https://godoc.org/github.com/reiver/go-oi)
|
||||
|
||||
|
||||
## Example
|
||||
```
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
)
|
||||
|
||||
// ...
|
||||
|
||||
p := []byte("It is important that this message be written!!!")
|
||||
|
||||
n, err := oi.LongWrite(writer, p)
|
||||
if nil != err {
|
||||
//@TODO: Handle error.
|
||||
return
|
||||
}
|
||||
|
||||
```
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
Package oi provides useful tools to be used with Go's standard "io" package.
|
||||
|
||||
For example, did you know that when you call the Write method on something that fits the io.Writer
|
||||
interface, that it is possible that not everything was be written?!
|
||||
|
||||
I.e., that a 'short write' happened.
|
||||
|
||||
That just doing the following is (in general) not enough:
|
||||
|
||||
n, err := writer.Write(p)
|
||||
|
||||
That, for example, you should be checking if "err == io.ErrShortWrite", and then maybe calling the Write
|
||||
method again but only with what didn't get written.
|
||||
|
||||
For a simple example of this (that actually is not sufficient to solve this problem, but illustrates
|
||||
the direction you would need to go to solve this problem is):
|
||||
|
||||
n, err := w.Write(p)
|
||||
|
||||
if io.ErrShortWrite == err {
|
||||
n2, err2 := w.Write(p[n:])
|
||||
}
|
||||
|
||||
Note that the second call to the Write method passed "p[n:]" (instead of just "p"), to account for the "n" bytes
|
||||
already being written (with the first call to the `Write` method).
|
||||
|
||||
A more "production quality" version of this would likely be in a loop, but such that that the loop had "guards"
|
||||
against looping forever, and also possibly looping for "too long".
|
||||
|
||||
|
||||
Well package oi provides tools that helps you deal with this and other problems. For example, you
|
||||
can handle a 'short write' with the following oi func:
|
||||
```
|
||||
n, err := oi.LongWrite(writer, p)
|
||||
```
|
||||
|
||||
*/
|
||||
package oi
|
||||
@@ -1,39 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// LongWrite tries to write the bytes from 'p' to the writer 'w', such that it deals
|
||||
// with "short writes" where w.Write would return an error of io.ErrShortWrite and
|
||||
// n < len(p).
|
||||
//
|
||||
// Note that LongWrite still could return the error io.ErrShortWrite; but this
|
||||
// would only be after trying to handle the io.ErrShortWrite a number of times, and
|
||||
// then eventually giving up.
|
||||
func LongWrite(w io.Writer, p []byte) (int64, error) {
|
||||
|
||||
numWritten := int64(0)
|
||||
for {
|
||||
//@TODO: Should check to make sure this doesn't get stuck in an infinite loop writting nothing!
|
||||
n, err := w.Write(p)
|
||||
numWritten += int64(n)
|
||||
if nil != err && io.ErrShortWrite != err {
|
||||
return numWritten, err
|
||||
}
|
||||
|
||||
if !(n < len(p)) {
|
||||
break
|
||||
}
|
||||
|
||||
p = p[n:]
|
||||
|
||||
if len(p) < 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return numWritten, nil
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi/test"
|
||||
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestLongWrite(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
String string
|
||||
}{
|
||||
{
|
||||
String: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "apple",
|
||||
},
|
||||
{
|
||||
String: "banana",
|
||||
},
|
||||
{
|
||||
String: "cherry",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Hello world!",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "😁😂😃😄😅😆😉😊😋😌😍😏😒😓😔😖😘😚😜😝😞😠😡😢😣😤😥😨😩😪😫😭😰😱😲😳😵😷",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "0123456789",
|
||||
},
|
||||
{
|
||||
String: "٠١٢٣٤٥٦٧٨٩", // Arabic-Indic Digits
|
||||
},
|
||||
{
|
||||
String: "۰۱۲۳۴۵۶۷۸۹", // Extended Arabic-Indic Digits
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ Ⅼ Ⅽ Ⅾ Ⅿ",
|
||||
},
|
||||
{
|
||||
String: "ⅰ ⅱ ⅲ ⅳ ⅴ ⅵ ⅶ ⅷ ⅸ ⅹ ⅺ ⅻ ⅼ ⅽ ⅾ ⅿ",
|
||||
},
|
||||
{
|
||||
String: "ↀ ↁ ↂ Ↄ ↄ ↅ ↆ ↇ ↈ",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
p := []byte(test.String)
|
||||
|
||||
var writer oitest.ShortWriter
|
||||
n, err := LongWrite(&writer, p)
|
||||
if nil != err {
|
||||
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q; for %q.", testNumber, err, err.Error(), test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := int64(len([]byte(test.String))), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected %d, but actually got %d; for %q.", testNumber, expected, actual, test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := test.String, writer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestLongWriteExpectError(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
String string
|
||||
Expected string
|
||||
Writes []int
|
||||
Err error
|
||||
}{
|
||||
{
|
||||
String: "apple",
|
||||
Expected: "appl",
|
||||
Writes: []int{2,2},
|
||||
Err: errors.New("Crabapple!"),
|
||||
},
|
||||
{
|
||||
String: "apple",
|
||||
Expected: "appl",
|
||||
Writes: []int{2,2,0},
|
||||
Err: errors.New("Crabapple!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "banana",
|
||||
Expected: "banan",
|
||||
Writes: []int{2,3},
|
||||
Err: errors.New("bananananananana!"),
|
||||
},
|
||||
{
|
||||
String: "banana",
|
||||
Expected: "banan",
|
||||
Writes: []int{2,3,0},
|
||||
Err: errors.New("bananananananananananananana!!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "cherry",
|
||||
Expected: "cher",
|
||||
Writes: []int{1,1,1,1},
|
||||
Err: errors.New("C.H.E.R.R.Y."),
|
||||
},
|
||||
{
|
||||
String: "cherry",
|
||||
Expected: "cher",
|
||||
Writes: []int{1,1,1,1,0},
|
||||
Err: errors.New("C_H_E_R_R_Y"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Hello world!",
|
||||
Expected: "Hello world",
|
||||
Writes: []int{1,2,3,5},
|
||||
Err: errors.New("Welcome!"),
|
||||
},
|
||||
{
|
||||
String: "Hello world!",
|
||||
Expected: "Hello world",
|
||||
Writes: []int{1,2,3,5,0},
|
||||
Err: errors.New("WeLcOmE!!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: " ",
|
||||
Expected: " ",
|
||||
Writes: []int{1,2,3,5,8,13},
|
||||
Err: errors.New("Space, the final frontier"),
|
||||
},
|
||||
{
|
||||
String: " ",
|
||||
Expected: " ",
|
||||
Writes: []int{1,2,3,5,8,13,0},
|
||||
Err: errors.New("Space, the final frontier"),
|
||||
},
|
||||
}
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
p := []byte(test.String)
|
||||
|
||||
writer := oitest.NewWritesThenErrorWriter(test.Err, test.Writes...)
|
||||
n, err := LongWrite(writer, p)
|
||||
if nil == err {
|
||||
t.Errorf("For test #%d, expected to get an error, but actually did not get one: %v; for %q.", testNumber, err, test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := test.Err, err; expected != actual {
|
||||
t.Errorf("For test #%d, expected to get error (%T) %q, but actually got (%T) %q; for %q.", testNumber, expected, expected.Error(), actual, actual.Error(), test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := int64(len(test.Expected)), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected number of bytes written to be %d = len(%q), but actually was %d = len(%q); for %q.", testNumber, expected, test.Expected, actual, writer.String(), test.String)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// LongWriteByte trys to write the byte from 'b' to the writer 'w', such that it deals
|
||||
// with "short writes" where w.Write would return an error of io.ErrShortWrite and
|
||||
// n < 1.
|
||||
//
|
||||
// Note that LongWriteByte still could return the error io.ErrShortWrite; but this
|
||||
// would only be after trying to handle the io.ErrShortWrite a number of times, and
|
||||
// then eventually giving up.
|
||||
func LongWriteByte(w io.Writer, b byte) error {
|
||||
var buffer [1]byte
|
||||
p := buffer[:]
|
||||
|
||||
buffer[0] = b
|
||||
|
||||
numWritten, err := LongWrite(w, p)
|
||||
if 1 != numWritten {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi/test"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestLongWriteByte(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Byte byte
|
||||
}{}
|
||||
|
||||
for b := byte(' '); b <= byte('/'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte('0'); b <= byte('9'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte(':'); b <= byte('@'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte('A'); b <= byte('Z'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte('['); b <= byte('`'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte('a'); b <= byte('z'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
for b := byte('{'); b <= byte('~'); b++ {
|
||||
test := struct {
|
||||
Byte byte
|
||||
}{
|
||||
Byte:b,
|
||||
}
|
||||
|
||||
tests = append(tests, test)
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
var writer oitest.ShortWriter
|
||||
err := LongWriteByte(&writer, test.Byte)
|
||||
if nil != err {
|
||||
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q; for %d (%q).", testNumber, err, err.Error(), test.Byte, string(test.Byte))
|
||||
continue
|
||||
}
|
||||
if expected, actual := string(test.Byte), writer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// LongWriteString tries to write the bytes from 's' to the writer 'w', such that it deals
|
||||
// with "short writes" where w.Write (or w.WriteString) would return an error of io.ErrShortWrite
|
||||
// and n < len(s).
|
||||
//
|
||||
// Note that LongWriteString still could return the error io.ErrShortWrite; but this
|
||||
// would only be after trying to handle the io.ErrShortWrite a number of times, and
|
||||
// then eventually giving up.
|
||||
func LongWriteString(w io.Writer, s string) (int64, error) {
|
||||
|
||||
numWritten := int64(0)
|
||||
for {
|
||||
//@TODO: Should check to make sure this doesn't get stuck in an infinite loop writting nothing!
|
||||
n, err := io.WriteString(w, s)
|
||||
numWritten += int64(n)
|
||||
if nil != err && io.ErrShortWrite != err {
|
||||
return numWritten, err
|
||||
}
|
||||
|
||||
if !(n < len(s)) {
|
||||
break
|
||||
}
|
||||
|
||||
s = s[n:]
|
||||
|
||||
if len(s) < 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return numWritten, nil
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi/test"
|
||||
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestLongWriteString(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
String string
|
||||
}{
|
||||
{
|
||||
String: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "apple",
|
||||
},
|
||||
{
|
||||
String: "banana",
|
||||
},
|
||||
{
|
||||
String: "cherry",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Hello world!",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "😁😂😃😄😅😆😉😊😋😌😍😏😒😓😔😖😘😚😜😝😞😠😡😢😣😤😥😨😩😪😫😭😰😱😲😳😵😷",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "0123456789",
|
||||
},
|
||||
{
|
||||
String: "٠١٢٣٤٥٦٧٨٩", // Arabic-Indic Digits
|
||||
},
|
||||
{
|
||||
String: "۰۱۲۳۴۵۶۷۸۹", // Extended Arabic-Indic Digits
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Ⅰ Ⅱ Ⅲ Ⅳ Ⅴ Ⅵ Ⅶ Ⅷ Ⅸ Ⅹ Ⅺ Ⅻ Ⅼ Ⅽ Ⅾ Ⅿ",
|
||||
},
|
||||
{
|
||||
String: "ⅰ ⅱ ⅲ ⅳ ⅴ ⅵ ⅶ ⅷ ⅸ ⅹ ⅺ ⅻ ⅼ ⅽ ⅾ ⅿ",
|
||||
},
|
||||
{
|
||||
String: "ↀ ↁ ↂ Ↄ ↄ ↅ ↆ ↇ ↈ",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
var writer oitest.ShortWriter
|
||||
n, err := LongWriteString(&writer, test.String)
|
||||
if nil != err {
|
||||
t.Errorf("For test #%d, did not expect an error, but actually got one: (%T) %q; for %q.", testNumber, err, err.Error(), test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := int64(len([]byte(test.String))), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected %d, but actually got %d; for %q.", testNumber, expected, actual, test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := test.String, writer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func TestLongWriteStringExpectError(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
String string
|
||||
Expected string
|
||||
Writes []int
|
||||
Err error
|
||||
}{
|
||||
{
|
||||
String: "apple",
|
||||
Expected: "appl",
|
||||
Writes: []int{2,2},
|
||||
Err: errors.New("Crabapple!"),
|
||||
},
|
||||
{
|
||||
String: "apple",
|
||||
Expected: "appl",
|
||||
Writes: []int{2,2,0},
|
||||
Err: errors.New("Crabapple!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "banana",
|
||||
Expected: "banan",
|
||||
Writes: []int{2,3},
|
||||
Err: errors.New("bananananananana!"),
|
||||
},
|
||||
{
|
||||
String: "banana",
|
||||
Expected: "banan",
|
||||
Writes: []int{2,3,0},
|
||||
Err: errors.New("bananananananananananananana!!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "cherry",
|
||||
Expected: "cher",
|
||||
Writes: []int{1,1,1,1},
|
||||
Err: errors.New("C.H.E.R.R.Y."),
|
||||
},
|
||||
{
|
||||
String: "cherry",
|
||||
Expected: "cher",
|
||||
Writes: []int{1,1,1,1,0},
|
||||
Err: errors.New("C_H_E_R_R_Y"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: "Hello world!",
|
||||
Expected: "Hello world",
|
||||
Writes: []int{1,2,3,5},
|
||||
Err: errors.New("Welcome!"),
|
||||
},
|
||||
{
|
||||
String: "Hello world!",
|
||||
Expected: "Hello world",
|
||||
Writes: []int{1,2,3,5,0},
|
||||
Err: errors.New("WeLcOmE!!!"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
String: " ",
|
||||
Expected: " ",
|
||||
Writes: []int{1,2,3,5,8,13},
|
||||
Err: errors.New("Space, the final frontier"),
|
||||
},
|
||||
{
|
||||
String: " ",
|
||||
Expected: " ",
|
||||
Writes: []int{1,2,3,5,8,13,0},
|
||||
Err: errors.New("Space, the final frontier"),
|
||||
},
|
||||
}
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
writer := oitest.NewWritesThenErrorWriter(test.Err, test.Writes...)
|
||||
n, err := LongWriteString(writer, test.String)
|
||||
if nil == err {
|
||||
t.Errorf("For test #%d, expected to get an error, but actually did not get one: %v; for %q.", testNumber, err, test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := test.Err, err; expected != actual {
|
||||
t.Errorf("For test #%d, expected to get error (%T) %q, but actually got (%T) %q; for %q.", testNumber, expected, expected.Error(), actual, actual.Error(), test.String)
|
||||
continue
|
||||
}
|
||||
if expected, actual := int64(len(test.Expected)), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected number of bytes written to be %d = len(%q), but actually was %d = len(%q); for %q.", testNumber, expected, test.Expected, actual, writer.String(), test.String)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/*
|
||||
Package oitest provides useful tools to be used for testing implementations of interfaces in Go's standard "io" package.
|
||||
|
||||
*/
|
||||
package oitest
|
||||
@@ -1,12 +0,0 @@
|
||||
package oitest
|
||||
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
randomness = rand.New(rand.NewSource( time.Now().UTC().UnixNano() ))
|
||||
)
|
||||
@@ -1,80 +0,0 @@
|
||||
package oitest
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// ShortWriter is useful for testing things that makes use of
|
||||
// or accept an io.Writer.
|
||||
//
|
||||
// In particular, it is useful to see if that thing can handle
|
||||
// an io.Writer that does a "short write".
|
||||
//
|
||||
// A "short write" is the case when a call like:
|
||||
//
|
||||
// n, err := w.Write(p)
|
||||
//
|
||||
// Returns an n < len(p) and err == io.ErrShortWrite.
|
||||
//
|
||||
// A thing that can "handle" this situation might try
|
||||
// writing again, but only what didn't get written.
|
||||
//
|
||||
// For a simple example of this:
|
||||
//
|
||||
// n, err := w.Write(p)
|
||||
//
|
||||
// if io.ErrShortWrite == err {
|
||||
// n2, err2 := w.Write(p[n:])
|
||||
// }
|
||||
//
|
||||
// Note that the second call to the Write method passed
|
||||
// 'p[n:]' (instead of just 'p'), to account for 'n' bytes
|
||||
// already written (with the first call to the Write
|
||||
// method).
|
||||
//
|
||||
// A more "production quality" version of this would likely
|
||||
// be in a loop, but such that that loop had "guards" against
|
||||
// looping forever, and also possibly looping for "too long".
|
||||
type ShortWriter struct {
|
||||
buffer bytes.Buffer
|
||||
}
|
||||
|
||||
|
||||
// Write makes it so ShortWriter fits the io.Writer interface.
|
||||
//
|
||||
// ShortWriter's version of Write will "short write" if len(p) >= 2,
|
||||
// else it will
|
||||
func (w *ShortWriter) Write(p []byte) (int, error) {
|
||||
if len(p) < 1 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
m := 1
|
||||
if limit := len(p)-1; limit > 1 {
|
||||
m += randomness.Intn(len(p)-1)
|
||||
}
|
||||
|
||||
n, err := w.buffer.Write(p[:m])
|
||||
|
||||
err = nil
|
||||
if n < len(p) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
||||
// Returns what was written to the ShortWriter as a []byte.
|
||||
func (w ShortWriter) Bytes() []byte {
|
||||
return w.buffer.Bytes()
|
||||
}
|
||||
|
||||
|
||||
// Returns what was written to the ShortWriter as a string.
|
||||
func (w ShortWriter) String() string {
|
||||
return w.buffer.String()
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package oitest
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// WritesThenErrorWriter is useful for testing things that makes
|
||||
// use of or accept an io.Writer.
|
||||
//
|
||||
// In particular, it is useful to see if that thing can handle
|
||||
// an io.Writer that does a "short write".
|
||||
//
|
||||
// A "short write" is the case when a call like:
|
||||
//
|
||||
// n, err := w.Write(p)
|
||||
//
|
||||
// Returns an n < len(p) and err == io.ErrShortWrite.
|
||||
//
|
||||
// A thing that can "handle" this situation might try
|
||||
// writing again, but only what didn't get written.
|
||||
//
|
||||
// For a simple example of this:
|
||||
//
|
||||
// n, err := w.Write(p)
|
||||
//
|
||||
// if io.ErrShortWrite == err {
|
||||
// n2, err2 := w.Write(p[n:])
|
||||
// }
|
||||
//
|
||||
// Note that the second call to the Write method passed
|
||||
// 'p[n:]' (instead of just 'p'), to account for 'n' bytes
|
||||
// already written (with the first call to the Write
|
||||
// method).
|
||||
//
|
||||
// A more "production quality" version of this would likely
|
||||
// be in a loop, but such that that loop had "guards" against
|
||||
// looping forever, and also possibly looping for "too long".
|
||||
type WritesThenErrorWriter struct {
|
||||
buffer bytes.Buffer
|
||||
err error
|
||||
numbersWritten []int
|
||||
writeNumber int
|
||||
}
|
||||
|
||||
|
||||
// NewWritesThenErrorWriter returns a new *WritesThenErrorWriter.
|
||||
func NewWritesThenErrorWriter(err error, numbersWritten ...int) *WritesThenErrorWriter {
|
||||
|
||||
slice := make([]int, len(numbersWritten))
|
||||
copy(slice, numbersWritten)
|
||||
|
||||
writer := WritesThenErrorWriter{
|
||||
err:err,
|
||||
numbersWritten:slice,
|
||||
writeNumber:0,
|
||||
}
|
||||
|
||||
return &writer
|
||||
}
|
||||
|
||||
|
||||
// Write makes it so *WritesThenErrorWriter fits the io.Writer interface.
|
||||
//
|
||||
// *WritesThenErrorWriter's version of Write will "short write" all but
|
||||
// the last call to write, where it will return the specified error (which
|
||||
// could, of course, be nil, if that was specified).
|
||||
func (writer *WritesThenErrorWriter) Write(p []byte) (int, error) {
|
||||
|
||||
m := writer.numbersWritten[writer.writeNumber]
|
||||
|
||||
writer.buffer.Write(p[:m])
|
||||
|
||||
writer.writeNumber++
|
||||
|
||||
if len(writer.numbersWritten) == writer.writeNumber {
|
||||
return m, writer.err
|
||||
}
|
||||
|
||||
return m, io.ErrShortWrite
|
||||
}
|
||||
|
||||
|
||||
// Returns what was written to the ShortWriter as a []byte.
|
||||
func (w WritesThenErrorWriter) Bytes() []byte {
|
||||
return w.buffer.Bytes()
|
||||
}
|
||||
|
||||
|
||||
// Returns what was written to the ShortWriter as a string.
|
||||
func (w WritesThenErrorWriter) String() string {
|
||||
return w.buffer.String()
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package oi
|
||||
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// WriteNopCloser takes an io.Writer and returns an io.WriteCloser where
|
||||
// calling the Write method on the returned io.WriterCloser calls the
|
||||
// Write method on the io.Writer it received, but whre calling the Close
|
||||
// method on the returned io.WriterCloser does "nothing" (i.e., is a "nop").
|
||||
//
|
||||
// This is useful in cases where an io.WriteCloser is expected, but you
|
||||
// only have an io.Writer (where closing doesn't make sense) and you
|
||||
// need to make your io.Writer fit. (I.e., you need an adaptor.)
|
||||
func WriteNopCloser(w io.Writer) io.WriteCloser {
|
||||
wc := internalWriteNopCloser{
|
||||
writer:w,
|
||||
}
|
||||
|
||||
return &wc
|
||||
}
|
||||
|
||||
|
||||
type internalWriteNopCloser struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
|
||||
func (wc * internalWriteNopCloser) Write(p []byte) (n int, err error) {
|
||||
return wc.writer.Write(p)
|
||||
}
|
||||
|
||||
|
||||
func (wc * internalWriteNopCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2016 Charles Iliya Krempeaux <charles@reptile.ca> :: http://changelog.ca/
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
@@ -1,252 +0,0 @@
|
||||
# go-telnet
|
||||
|
||||
Package **telnet** provides TELNET and TELNETS client and server implementations, for the Go programming language.
|
||||
|
||||
|
||||
The **telnet** package provides an API in a style similar to the "net/http" library that is part of the Go standard library, including support for "middleware".
|
||||
|
||||
|
||||
(TELNETS is *secure TELNET*, with the TELNET protocol over a secured TLS (or SSL) connection.)
|
||||
|
||||
|
||||
## Documention
|
||||
|
||||
Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-telnet
|
||||
|
||||
[](https://godoc.org/github.com/reiver/go-telnet)
|
||||
|
||||
|
||||
## Very Simple TELNET Server Example
|
||||
|
||||
A very very simple TELNET server is shown in the following code.
|
||||
|
||||
This particular TELNET server just echos back to the user anything they "submit" to the server.
|
||||
|
||||
(By default, a TELNET client does *not* send anything to the server until the [Enter] key is pressed.
|
||||
"Submit" means typing something and then pressing the [Enter] key.)
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var handler telnet.Handler = telnet.EchoHandler
|
||||
|
||||
err := telnet.ListenAndServe(":5555", handler)
|
||||
if nil != err {
|
||||
//@TODO: Handle this error better.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If you wanted to test out this very very simple TELNET server, if you were on the same computer it was
|
||||
running, you could connect to it using the bash command:
|
||||
```
|
||||
telnet localhost 5555
|
||||
```
|
||||
(Note that we use the same TCP port number -- "5555" -- as we had in our code. That is important, as the
|
||||
value used by your TELNET server and the value used by your TELNET client **must** match.)
|
||||
|
||||
|
||||
## Very Simple (Secure) TELNETS Server Example
|
||||
|
||||
TELNETS is the secure version of TELNET.
|
||||
|
||||
The code to make a TELNETS server is very similar to the code to make a TELNET server.
|
||||
(The difference between we use the `telnet.ListenAndServeTLS` func instead of the
|
||||
`telnet.ListenAndServe` func.)
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
var handler telnet.Handler = telnet.EchoHandler
|
||||
|
||||
err := telnet.ListenAndServeTLS(":5555", "cert.pem", "key.pem", handler)
|
||||
if nil != err {
|
||||
//@TODO: Handle this error better.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If you wanted to test out this very very simple TELNETS server, get the `telnets` client program from here:
|
||||
https://github.com/reiver/telnets
|
||||
|
||||
|
||||
## TELNET Client Example:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:5555" with address you want to connect to.
|
||||
telnet.DialToAndCall("example.net:5555", caller)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## TELNETS Client Example:
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//@TODO: Configure the TLS connection here, if you need to.
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:5555" with address you want to connect to.
|
||||
telnet.DialToAndCallTLS("example.net:5555", caller, tlsConfig)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## TELNET Shell Server Example
|
||||
|
||||
A more useful TELNET servers can be made using the `"github.com/reiver/go-telnet/telsh"` sub-package.
|
||||
|
||||
For example:
|
||||
```
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
"github.com/reiver/go-telnet/telsh"
|
||||
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
|
||||
func fiveHandler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
oi.LongWriteString(stdout, "The number FIVE looks like this: 5\r\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fiveProducer(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
return telsh.PromoteHandlerFunc(fiveHandler)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func danceHandler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
for i:=0; i<20; i++ {
|
||||
oi.LongWriteString(stdout, "\r⠋")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠙")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠹")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠸")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠼")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠴")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠦")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠧")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠇")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠏")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
}
|
||||
oi.LongWriteString(stdout, "\r \r\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func danceProducer(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
|
||||
return telsh.PromoteHandlerFunc(danceHandler)
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
|
||||
shellHandler := telsh.NewShellHandler()
|
||||
|
||||
shellHandler.WelcomeMessage = `
|
||||
__ __ ______ _ _____ ____ __ __ ______
|
||||
\ \ / /| ____|| | / ____| / __ \ | \/ || ____|
|
||||
\ \ /\ / / | |__ | | | | | | | || \ / || |__
|
||||
\ \/ \/ / | __| | | | | | | | || |\/| || __|
|
||||
\ /\ / | |____ | |____ | |____ | |__| || | | || |____
|
||||
\/ \/ |______||______| \_____| \____/ |_| |_||______|
|
||||
|
||||
`
|
||||
|
||||
|
||||
// Register the "five" command.
|
||||
commandName := "five"
|
||||
commandProducer := telsh.ProducerFunc(fiveProducer)
|
||||
|
||||
shellHandler.Register(commandName, commandProducer)
|
||||
|
||||
|
||||
|
||||
// Register the "dance" command.
|
||||
commandName = "dance"
|
||||
commandProducer = telsh.ProducerFunc(danceProducer)
|
||||
|
||||
shellHandler.Register(commandName, commandProducer)
|
||||
|
||||
|
||||
|
||||
shellHandler.Register("dance", telsh.ProducerFunc(danceProducer))
|
||||
|
||||
addr := ":5555"
|
||||
if err := telnet.ListenAndServe(addr, shellHandler); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
TELNET servers made using the `"github.com/reiver/go-telnet/telsh"` sub-package will often be more useful
|
||||
as it makes it easier for you to create a *shell* interface.
|
||||
|
||||
|
||||
# More Information
|
||||
|
||||
There is a lot more information about documentation on all this here: http://godoc.org/github.com/reiver/go-telnet
|
||||
|
||||
(You should really read those.)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
// A Caller represents the client end of a TELNET (or TELNETS) connection.
|
||||
//
|
||||
// Writing data to the Writer passed as an argument to the CallTELNET method
|
||||
// will send data to the TELNET (or TELNETS) server.
|
||||
//
|
||||
// Reading data from the Reader passed as an argument to the CallTELNET method
|
||||
// will receive data from the TELNET server.
|
||||
//
|
||||
// The Writer's Write method sends "escaped" TELNET (and TELNETS) data.
|
||||
//
|
||||
// The Reader's Read method "un-escapes" TELNET (and TELNETS) data, and filters
|
||||
// out TELNET (and TELNETS) command sequences.
|
||||
type Caller interface {
|
||||
CallTELNET(Context, Writer, Reader)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
|
||||
func DialAndCall(caller Caller) error {
|
||||
conn, err := Dial()
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &Client{Caller:caller}
|
||||
|
||||
return client.Call(conn)
|
||||
}
|
||||
|
||||
|
||||
func DialToAndCall(srvAddr string, caller Caller) error {
|
||||
conn, err := DialTo(srvAddr)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &Client{Caller:caller}
|
||||
|
||||
return client.Call(conn)
|
||||
}
|
||||
|
||||
|
||||
func DialAndCallTLS(caller Caller, tlsConfig *tls.Config) error {
|
||||
conn, err := DialTLS(tlsConfig)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &Client{Caller:caller}
|
||||
|
||||
return client.Call(conn)
|
||||
}
|
||||
|
||||
func DialToAndCallTLS(srvAddr string, caller Caller, tlsConfig *tls.Config) error {
|
||||
conn, err := DialToTLS(srvAddr, tlsConfig)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &Client{Caller:caller}
|
||||
|
||||
return client.Call(conn)
|
||||
}
|
||||
|
||||
|
||||
type Client struct {
|
||||
Caller Caller
|
||||
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
|
||||
func (client *Client) Call(conn *Conn) error {
|
||||
|
||||
logger := client.logger()
|
||||
|
||||
|
||||
caller := client.Caller
|
||||
if nil == caller {
|
||||
logger.Debug("Defaulted caller to StandardCaller.")
|
||||
caller = StandardCaller
|
||||
}
|
||||
|
||||
|
||||
var ctx Context = NewContext().InjectLogger(logger)
|
||||
|
||||
var w Writer = conn
|
||||
var r Reader = conn
|
||||
|
||||
caller.CallTELNET(ctx, w, r)
|
||||
conn.Close()
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (client *Client) logger() Logger {
|
||||
logger := client.Logger
|
||||
if nil == logger {
|
||||
logger = internalDiscardLogger{}
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
|
||||
func (client *Client) SetAuth(username string) {
|
||||
//@TODO: #################################################
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
|
||||
type Conn struct {
|
||||
conn interface {
|
||||
Read(b []byte) (n int, err error)
|
||||
Write(b []byte) (n int, err error)
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
dataReader *internalDataReader
|
||||
dataWriter *internalDataWriter
|
||||
}
|
||||
|
||||
|
||||
// Dial makes a (un-secure) TELNET client connection to the system's 'loopback address'
|
||||
// (also known as "localhost" or 127.0.0.1).
|
||||
//
|
||||
// If a secure connection is desired, use `DialTLS` instead.
|
||||
func Dial() (*Conn, error) {
|
||||
return DialTo("")
|
||||
}
|
||||
|
||||
// DialTo makes a (un-secure) TELNET client connection to the the address specified by
|
||||
// 'addr'.
|
||||
//
|
||||
// If a secure connection is desired, use `DialToTLS` instead.
|
||||
func DialTo(addr string) (*Conn, error) {
|
||||
|
||||
const network = "tcp"
|
||||
|
||||
if "" == addr {
|
||||
addr = "127.0.0.1:telnet"
|
||||
}
|
||||
|
||||
conn, err := net.Dial(network, addr)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataReader := newDataReader(conn)
|
||||
dataWriter := newDataWriter(conn)
|
||||
|
||||
clientConn := Conn{
|
||||
conn:conn,
|
||||
dataReader:dataReader,
|
||||
dataWriter:dataWriter,
|
||||
}
|
||||
|
||||
return &clientConn, nil
|
||||
}
|
||||
|
||||
|
||||
// DialTLS makes a (secure) TELNETS client connection to the system's 'loopback address'
|
||||
// (also known as "localhost" or 127.0.0.1).
|
||||
func DialTLS(tlsConfig *tls.Config) (*Conn, error) {
|
||||
return DialToTLS("", tlsConfig)
|
||||
}
|
||||
|
||||
// DialToTLS makes a (secure) TELNETS client connection to the the address specified by
|
||||
// 'addr'.
|
||||
func DialToTLS(addr string, tlsConfig *tls.Config) (*Conn, error) {
|
||||
|
||||
const network = "tcp"
|
||||
|
||||
if "" == addr {
|
||||
addr = "127.0.0.1:telnets"
|
||||
}
|
||||
|
||||
conn, err := tls.Dial(network, addr, tlsConfig)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dataReader := newDataReader(conn)
|
||||
dataWriter := newDataWriter(conn)
|
||||
|
||||
clientConn := Conn{
|
||||
conn:conn,
|
||||
dataReader:dataReader,
|
||||
dataWriter:dataWriter,
|
||||
}
|
||||
|
||||
return &clientConn, nil
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Close closes the client connection.
|
||||
//
|
||||
// Typical usage might look like:
|
||||
//
|
||||
// telnetsClient, err = telnet.DialToTLS(addr, tlsConfig)
|
||||
// if nil != err {
|
||||
// //@TODO: Handle error.
|
||||
// return err
|
||||
// }
|
||||
// defer telnetsClient.Close()
|
||||
func (clientConn *Conn) Close() error {
|
||||
return clientConn.conn.Close()
|
||||
}
|
||||
|
||||
|
||||
// Read receives `n` bytes sent from the server to the client,
|
||||
// and "returns" into `p`.
|
||||
//
|
||||
// Note that Read can only be used for receiving TELNET (and TELNETS) data from the server.
|
||||
//
|
||||
// TELNET (and TELNETS) command codes cannot be received using this method, as Read deals
|
||||
// with TELNET (and TELNETS) "unescaping", and (when appropriate) filters out TELNET (and TELNETS)
|
||||
// command codes.
|
||||
//
|
||||
// Read makes Client fit the io.Reader interface.
|
||||
func (clientConn *Conn) Read(p []byte) (n int, err error) {
|
||||
return clientConn.dataReader.Read(p)
|
||||
}
|
||||
|
||||
|
||||
// Write sends `n` bytes from 'p' to the server.
|
||||
//
|
||||
// Note that Write can only be used for sending TELNET (and TELNETS) data to the server.
|
||||
//
|
||||
// TELNET (and TELNETS) command codes cannot be sent using this method, as Write deals with
|
||||
// TELNET (and TELNETS) "escaping", and will properly "escape" anything written with it.
|
||||
//
|
||||
// Write makes Conn fit the io.Writer interface.
|
||||
func (clientConn *Conn) Write(p []byte) (n int, err error) {
|
||||
return clientConn.dataWriter.Write(p)
|
||||
}
|
||||
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (clientConn *Conn) LocalAddr() net.Addr {
|
||||
return clientConn.conn.LocalAddr()
|
||||
}
|
||||
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (clientConn *Conn) RemoteAddr() net.Addr {
|
||||
return clientConn.conn.RemoteAddr()
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
type Context interface {
|
||||
Logger() Logger
|
||||
|
||||
InjectLogger(Logger) Context
|
||||
}
|
||||
|
||||
|
||||
type internalContext struct {
|
||||
logger Logger
|
||||
}
|
||||
|
||||
|
||||
func NewContext() Context {
|
||||
ctx := internalContext{}
|
||||
|
||||
return &ctx
|
||||
}
|
||||
|
||||
|
||||
func (ctx *internalContext) Logger() Logger {
|
||||
return ctx.logger
|
||||
}
|
||||
|
||||
func (ctx *internalContext) InjectLogger(logger Logger) Context {
|
||||
ctx.logger = logger
|
||||
|
||||
return ctx
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
errCorrupted = errors.New("Corrupted")
|
||||
)
|
||||
|
||||
|
||||
// An internalDataReader deals with "un-escaping" according to the TELNET protocol.
|
||||
//
|
||||
// In the TELNET protocol byte value 255 is special.
|
||||
//
|
||||
// The TELNET protocol calls byte value 255: "IAC". Which is short for "interpret as command".
|
||||
//
|
||||
// The TELNET protocol also has a distinction between 'data' and 'commands'.
|
||||
//
|
||||
//(DataReader is targetted toward TELNET 'data', not TELNET 'commands'.)
|
||||
//
|
||||
// If a byte with value 255 (=IAC) appears in the data, then it must be escaped.
|
||||
//
|
||||
// Escaping byte value 255 (=IAC) in the data is done by putting 2 of them in a row.
|
||||
//
|
||||
// So, for example:
|
||||
//
|
||||
// []byte{255} -> []byte{255, 255}
|
||||
//
|
||||
// Or, for a more complete example, if we started with the following:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20}
|
||||
//
|
||||
// ... TELNET escaping would produce the following:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20}
|
||||
//
|
||||
// (Notice that each "255" in the original byte array became 2 "255"s in a row.)
|
||||
//
|
||||
// DataReader deals with "un-escaping". In other words, it un-does what was shown
|
||||
// in the examples.
|
||||
//
|
||||
// So, for example, it does this:
|
||||
//
|
||||
// []byte{255, 255} -> []byte{255}
|
||||
//
|
||||
// And, for example, goes from this:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20}
|
||||
//
|
||||
// ... to this:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20}
|
||||
type internalDataReader struct {
|
||||
wrapped io.Reader
|
||||
buffered *bufio.Reader
|
||||
}
|
||||
|
||||
|
||||
// newDataReader creates a new DataReader reading from 'r'.
|
||||
func newDataReader(r io.Reader) *internalDataReader {
|
||||
buffered := bufio.NewReader(r)
|
||||
|
||||
reader := internalDataReader{
|
||||
wrapped:r,
|
||||
buffered:buffered,
|
||||
}
|
||||
|
||||
return &reader
|
||||
}
|
||||
|
||||
|
||||
// Read reads the TELNET escaped data from the wrapped io.Reader, and "un-escapes" it into 'data'.
|
||||
func (r *internalDataReader) Read(data []byte) (n int, err error) {
|
||||
|
||||
const IAC = 255
|
||||
|
||||
const SB = 250
|
||||
const SE = 240
|
||||
|
||||
const WILL = 251
|
||||
const WONT = 252
|
||||
const DO = 253
|
||||
const DONT = 254
|
||||
|
||||
p := data
|
||||
|
||||
for len(p) > 0 {
|
||||
var b byte
|
||||
|
||||
b, err = r.buffered.ReadByte()
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if IAC == b {
|
||||
var peeked []byte
|
||||
|
||||
peeked, err = r.buffered.Peek(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
|
||||
switch peeked[0] {
|
||||
case WILL, WONT, DO, DONT:
|
||||
_, err = r.buffered.Discard(2)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
case IAC:
|
||||
p[0] = IAC
|
||||
n++
|
||||
p = p[1:]
|
||||
|
||||
_, err = r.buffered.Discard(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
case SB:
|
||||
for {
|
||||
var b2 byte
|
||||
b2, err = r.buffered.ReadByte()
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if IAC == b2 {
|
||||
peeked, err = r.buffered.Peek(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
|
||||
if IAC == peeked[0] {
|
||||
_, err = r.buffered.Discard(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
if SE == peeked[0] {
|
||||
_, err = r.buffered.Discard(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
case SE:
|
||||
_, err = r.buffered.Discard(1)
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
default:
|
||||
// If we get in here, this is not following the TELNET protocol.
|
||||
//@TODO: Make a better error.
|
||||
err = errCorrupted
|
||||
return n, err
|
||||
}
|
||||
} else {
|
||||
|
||||
p[0] = b
|
||||
n++
|
||||
p = p[1:]
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestDataReader(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Bytes []byte
|
||||
Expected []byte
|
||||
}{
|
||||
{
|
||||
Bytes: []byte{},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a"),
|
||||
Expected: []byte("a"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("b"),
|
||||
Expected: []byte("b"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("c"),
|
||||
Expected: []byte("c"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple"),
|
||||
Expected: []byte("apple"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana"),
|
||||
Expected: []byte("banana"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry"),
|
||||
Expected: []byte("cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry"),
|
||||
Expected: []byte("apple banana cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,255},
|
||||
Expected: []byte{255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255},
|
||||
Expected: []byte{255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255,255,255},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
Expected: []byte("apple\xffbanana\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
Expected: []byte("\xffapple\xffbanana\xffcherry\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
Expected: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
//@TODO: Add random tests.
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
subReader := bytes.NewReader(test.Bytes)
|
||||
|
||||
reader := newDataReader(subReader)
|
||||
|
||||
buffer := make([]byte, 2*len(test.Bytes))
|
||||
n, err := reader.Read(buffer)
|
||||
if nil != err && io.EOF != err {
|
||||
t.Errorf("For test #%d, did not expected an error, but actually got one: (%T) %v; for %q -> %q.", testNumber, err, err, string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := len(test.Expected), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected %d, but actually got %d (and %q); for %q -> %q.", testNumber, expected, actual, string(buffer[:n]), string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := string(test.Expected), string(buffer[:n]); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
var iaciac []byte = []byte{255, 255}
|
||||
|
||||
var errOverflow = errors.New("Overflow")
|
||||
var errPartialIACIACWrite = errors.New("Partial IAC IAC write.")
|
||||
|
||||
|
||||
// An internalDataWriter deals with "escaping" according to the TELNET (and TELNETS) protocol.
|
||||
//
|
||||
// In the TELNET (and TELNETS) protocol byte value 255 is special.
|
||||
//
|
||||
// The TELNET (and TELNETS) protocol calls byte value 255: "IAC". Which is short for "interpret as command".
|
||||
//
|
||||
// The TELNET (and TELNETS) protocol also has a distinction between 'data' and 'commands'.
|
||||
//
|
||||
//(DataWriter is targetted toward TELNET (and TELNETS) 'data', not TELNET (and TELNETS) 'commands'.)
|
||||
//
|
||||
// If a byte with value 255 (=IAC) appears in the data, then it must be escaped.
|
||||
//
|
||||
// Escaping byte value 255 (=IAC) in the data is done by putting 2 of them in a row.
|
||||
//
|
||||
// So, for example:
|
||||
//
|
||||
// []byte{255} -> []byte{255, 255}
|
||||
//
|
||||
// Or, for a more complete example, if we started with the following:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20}
|
||||
//
|
||||
// ... TELNET escaping would produce the following:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20}
|
||||
//
|
||||
// (Notice that each "255" in the original byte array became 2 "255"s in a row.)
|
||||
//
|
||||
// internalDataWriter takes care of all this for you, so you do not have to do it.
|
||||
type internalDataWriter struct {
|
||||
wrapped io.Writer
|
||||
}
|
||||
|
||||
|
||||
// newDataWriter creates a new internalDataWriter writing to 'w'.
|
||||
//
|
||||
// 'w' receives what is written to the *internalDataWriter but escaped according to
|
||||
// the TELNET (and TELNETS) protocol.
|
||||
//
|
||||
// I.e., byte 255 (= IAC) gets encoded as 255, 255.
|
||||
//
|
||||
// For example, if the following it written to the *internalDataWriter's Write method:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20}
|
||||
//
|
||||
// ... then (conceptually) the following is written to 'w's Write method:
|
||||
//
|
||||
// []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20}
|
||||
//
|
||||
// (Notice that each "255" in the original byte array became 2 "255"s in a row.)
|
||||
//
|
||||
// *internalDataWriter takes care of all this for you, so you do not have to do it.
|
||||
func newDataWriter(w io.Writer) *internalDataWriter {
|
||||
writer := internalDataWriter{
|
||||
wrapped:w,
|
||||
}
|
||||
|
||||
return &writer
|
||||
}
|
||||
|
||||
|
||||
// Write writes the TELNET (and TELNETS) escaped data for of the data in 'data' to the wrapped io.Writer.
|
||||
func (w *internalDataWriter) Write(data []byte) (n int, err error) {
|
||||
var n64 int64
|
||||
|
||||
n64, err = w.write64(data)
|
||||
n = int(n64)
|
||||
if int64(n) != n64 {
|
||||
panic(errOverflow)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
|
||||
func (w *internalDataWriter) write64(data []byte) (n int64, err error) {
|
||||
|
||||
if len(data) <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
const IAC = 255
|
||||
|
||||
var buffer bytes.Buffer
|
||||
for _, datum := range data {
|
||||
|
||||
if IAC == datum {
|
||||
|
||||
if buffer.Len() > 0 {
|
||||
var numWritten int64
|
||||
|
||||
numWritten, err = oi.LongWrite(w.wrapped, buffer.Bytes())
|
||||
n += numWritten
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
buffer.Reset()
|
||||
}
|
||||
|
||||
|
||||
var numWritten int64
|
||||
//@TODO: Should we worry about "iaciac" potentially being modified by the .Write()?
|
||||
numWritten, err = oi.LongWrite(w.wrapped, iaciac)
|
||||
if int64(len(iaciac)) != numWritten {
|
||||
//@TODO: Do we really want to panic() here?
|
||||
panic(errPartialIACIACWrite)
|
||||
}
|
||||
n += 1
|
||||
if nil != err {
|
||||
return n, err
|
||||
}
|
||||
} else {
|
||||
buffer.WriteByte(datum) // The returned error is always nil, so we ignore it.
|
||||
}
|
||||
}
|
||||
|
||||
if buffer.Len() > 0 {
|
||||
var numWritten int64
|
||||
numWritten, err = oi.LongWrite(w.wrapped, buffer.Bytes())
|
||||
n += numWritten
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestDataWriter(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Bytes []byte
|
||||
Expected []byte
|
||||
}{
|
||||
{
|
||||
Bytes: []byte{},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple"),
|
||||
Expected: []byte("apple"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana"),
|
||||
Expected: []byte("banana"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry"),
|
||||
Expected: []byte("cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry"),
|
||||
Expected: []byte("apple banana cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255},
|
||||
Expected: []byte{255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255},
|
||||
Expected: []byte{255,255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255},
|
||||
Expected: []byte{255,255,255,255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255},
|
||||
Expected: []byte{255,255,255,255,255,255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255},
|
||||
Expected: []byte{255,255,255,255,255,255,255,255,255,255},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xffbanana\xffcherry"),
|
||||
Expected: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xffapple\xffbanana\xffcherry\xff"),
|
||||
Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
},
|
||||
}
|
||||
|
||||
//@TODO: Add random tests.
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
subWriter := new(bytes.Buffer)
|
||||
|
||||
writer := newDataWriter(subWriter)
|
||||
|
||||
n, err := writer.Write(test.Bytes)
|
||||
if nil != err {
|
||||
t.Errorf("For test #%d, did not expected an error, but actually got one: (%T) %v; for %q -> %q.", testNumber, err, err, string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := len(test.Bytes), n; expected != actual {
|
||||
t.Errorf("For test #%d, expected %d, but actually got %d; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := string(test.Expected), subWriter.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
type internalDiscardLogger struct{}
|
||||
|
||||
func (internalDiscardLogger) Debug(...interface{}) {}
|
||||
func (internalDiscardLogger) Debugf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Debugln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Error(...interface{}) {}
|
||||
func (internalDiscardLogger) Errorf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Errorln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Trace(...interface{}) {}
|
||||
func (internalDiscardLogger) Tracef(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Traceln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Warn(...interface{}) {}
|
||||
func (internalDiscardLogger) Warnf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Warnln(...interface{}) {}
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
/*
|
||||
Package telnet provides TELNET and TELNETS client and server implementations
|
||||
in a style similar to the "net/http" library that is part of the Go standard library,
|
||||
including support for "middleware"; TELNETS is secure TELNET, with the TELNET protocol
|
||||
over a secured TLS (or SSL) connection.
|
||||
|
||||
|
||||
Example TELNET Server
|
||||
|
||||
ListenAndServe starts a (un-secure) TELNET server with a given address and handler.
|
||||
|
||||
handler := telnet.EchoHandler
|
||||
|
||||
err := telnet.ListenAndServe(":23", handler)
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
Example TELNETS Server
|
||||
|
||||
ListenAndServeTLS starts a (secure) TELNETS server with a given address and handler,
|
||||
using the specified "cert.pem" and "key.pem" files.
|
||||
|
||||
handler := telnet.EchoHandler
|
||||
|
||||
err := telnet.ListenAndServeTLS(":992", "cert.pem", "key.pem", handler)
|
||||
if nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
Example TELNET Client:
|
||||
|
||||
DialToAndCall creates a (un-secure) TELNET client, which connects to a given address using the specified caller.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:23" with address you want to connect to.
|
||||
telnet.DialToAndCall("example.net:23", caller)
|
||||
}
|
||||
|
||||
|
||||
Example TELNETS Client:
|
||||
|
||||
DialToAndCallTLS creates a (secure) TELNETS client, which connects to a given address using the specified caller.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
func main() {
|
||||
//@TODO: Configure the TLS connection here, if you need to.
|
||||
tlsConfig := &tls.Config{}
|
||||
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:992" with address you want to connect to.
|
||||
telnet.DialToAndCallTLS("example.net:992", caller, tlsConfig)
|
||||
}
|
||||
|
||||
|
||||
TELNET vs TELNETS
|
||||
|
||||
If you are communicating over the open Internet, you should be using (the secure) TELNETS protocol and ListenAndServeTLS.
|
||||
|
||||
If you are communicating just on localhost, then using just (the un-secure) TELNET protocol and telnet.ListenAndServe may be OK.
|
||||
|
||||
If you are not sure which to use, use TELNETS and ListenAndServeTLS.
|
||||
|
||||
|
||||
Example TELNET Shell Server
|
||||
|
||||
The previous 2 exaple servers were very very simple. Specifically, they just echoed back whatever
|
||||
you submitted to it.
|
||||
|
||||
If you typed:
|
||||
|
||||
Apple Banana Cherry\r\n
|
||||
|
||||
... it would send back:
|
||||
|
||||
Apple Banana Cherry\r\n
|
||||
|
||||
(Exactly the same data you sent it.)
|
||||
|
||||
A more useful TELNET server can be made using the "github.com/reiver/go-telnet/telsh" sub-package.
|
||||
|
||||
The `telsh` sub-package provides "middleware" that enables you to create a "shell" interface (also
|
||||
called a "command line interface" or "CLI") which most people would expect when using TELNET OR TELNETS.
|
||||
|
||||
For example:
|
||||
|
||||
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
"github.com/reiver/go-telnet/telsh"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
|
||||
shellHandler := telsh.NewShellHandler()
|
||||
|
||||
commandName := "date"
|
||||
shellHandler.Register(commandName, danceProducer)
|
||||
|
||||
commandName = "animate"
|
||||
shellHandler.Register(commandName, animateProducer)
|
||||
|
||||
addr := ":23"
|
||||
if err := telnet.ListenAndServe(addr, shellHandler); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
Note that in the example, so far, we have registered 2 commands: `date` and `animate`.
|
||||
|
||||
For this to actually work, we need to have code for the `date` and `animate` commands.
|
||||
|
||||
The actual implemenation for the `date` command could be done like the following:
|
||||
|
||||
func dateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
const layout = "Mon Jan 2 15:04:05 -0700 MST 2006"
|
||||
s := time.Now().Format(layout)
|
||||
|
||||
if _, err := oi.LongWriteString(stdout, s); nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func dateProducerFunc(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
return telsh.PromoteHandlerFunc(dateHandler)
|
||||
}
|
||||
|
||||
|
||||
var dateProducer = ProducerFunc(dateProducerFunc)
|
||||
|
||||
Note that your "real" work is in the `dateHandlerFunc` func.
|
||||
|
||||
And the actual implementation for the `animate` command could be done as follows:
|
||||
|
||||
func animateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
|
||||
for i:=0; i<20; i++ {
|
||||
oi.LongWriteString(stdout, "\r⠋")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠙")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠹")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠸")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠼")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠴")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠦")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠧")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠇")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠏")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
}
|
||||
oi.LongWriteString(stdout, "\r \r\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func animateProducerFunc(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
return telsh.PromoteHandlerFunc(animateHandler)
|
||||
}
|
||||
|
||||
|
||||
var animateProducer = ProducerFunc(animateProducerFunc)
|
||||
|
||||
Again, note that your "real" work is in the `animateHandlerFunc` func.
|
||||
|
||||
Generating PEM Files
|
||||
|
||||
If you are using the telnet.ListenAndServeTLS func or the telnet.Server.ListenAndServeTLS method, you will need to
|
||||
supply "cert.pem" and "key.pem" files.
|
||||
|
||||
If you do not already have these files, the Go soure code contains a tool for generating these files for you.
|
||||
|
||||
It can be found at:
|
||||
|
||||
$GOROOT/src/crypto/tls/generate_cert.go
|
||||
|
||||
So, for example, if your `$GOROOT` is the "/usr/local/go" directory, then it would be at:
|
||||
|
||||
/usr/local/go/src/crypto/tls/generate_cert.go
|
||||
|
||||
If you run the command:
|
||||
|
||||
go run $GOROOT/src/crypto/tls/generate_cert.go --help
|
||||
|
||||
... then you get the help information for "generate_cert.go".
|
||||
|
||||
Of course, you would replace or set `$GOROOT` with whatever your path actually is. Again, for example,
|
||||
if your `$GOROOT` is the "/usr/local/go" directory, then it would be:
|
||||
|
||||
go run /usr/local/go/src/crypto/tls/generate_cert.go --help
|
||||
|
||||
To demonstrate the usage of "generate_cert.go", you might run the following to generate certificates
|
||||
that were bound to the hosts `127.0.0.1` and `localhost`:
|
||||
|
||||
go run /usr/local/go/src/crypto/tls/generate_cert.go --ca --host='127.0.0.1,localhost'
|
||||
|
||||
|
||||
If you are not sure where "generate_cert.go" is on your computer, on Linux and Unix based systems, you might
|
||||
be able to find the file with the command:
|
||||
|
||||
locate /src/crypto/tls/generate_cert.go
|
||||
|
||||
(If it finds it, it should output the full path to this file.)
|
||||
|
||||
|
||||
Example TELNET Client
|
||||
|
||||
You can make a simple (un-secure) TELNET client with code like the following:
|
||||
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:5555" with address you want to connect to.
|
||||
telnet.DialToAndCall("example.net:5555", caller)
|
||||
}
|
||||
|
||||
|
||||
Example TELNETS Client
|
||||
|
||||
You can make a simple (secure) TELNETS client with code like the following:
|
||||
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
var caller telnet.Caller = telnet.StandardCaller
|
||||
|
||||
//@TOOD: replace "example.net:5555" with address you want to connect to.
|
||||
telnet.DialToAndCallTLS("example.net:5555", caller)
|
||||
}
|
||||
|
||||
|
||||
TELNET Story
|
||||
|
||||
The TELNET protocol is best known for providing a means of connecting to a remote computer, using a (text-based) shell interface, and being able to interact with it, (more or less) as if you were sitting at that computer.
|
||||
|
||||
(Shells are also known as command-line interfaces or CLIs.)
|
||||
|
||||
Although this was the original usage of the TELNET protocol, it can be (and is) used for other purposes as well.
|
||||
|
||||
|
||||
The Era
|
||||
|
||||
The TELNET protocol came from an era in computing when text-based shell interface where the common way of interacting with computers.
|
||||
|
||||
The common interface for computers during this era was a keyboard and a monochromatic (i.e., single color) text-based monitors called "video terminals".
|
||||
|
||||
(The word "video" in that era of computing did not refer to things such as movies. But instead was meant to contrast it with paper. In particular, the teletype machines, which were typewriter like devices that had a keyboard, but instead of having a monitor had paper that was printed onto.)
|
||||
|
||||
|
||||
Early Office Computers
|
||||
|
||||
In that era, in the early days of office computers, it was rare that an individual would have a computer at their desk. (A single computer was much too expensive.)
|
||||
|
||||
Instead, there would be a single central computer that everyone would share. The style of computer used (for the single central shared computer) was called a "mainframe".
|
||||
|
||||
What individuals would have at their desks, instead of their own compuer, would be some type of video terminal.
|
||||
|
||||
The different types of video terminals had named such as:
|
||||
|
||||
• VT52
|
||||
|
||||
• VT100
|
||||
|
||||
• VT220
|
||||
|
||||
• VT240
|
||||
|
||||
("VT" in those named was short for "video terminal".)
|
||||
|
||||
|
||||
Teletype
|
||||
|
||||
To understand this era, we need to go back a bit in time to what came before it: teletypes.
|
||||
|
||||
|
||||
Terminal Codes
|
||||
|
||||
Terminal codes (also sometimes called 'terminal control codes') are used to issue various kinds of commands
|
||||
to the terminal.
|
||||
|
||||
(Note that 'terminal control codes' are a completely separate concept for 'TELNET commands',
|
||||
and the two should NOT be conflated or confused.)
|
||||
|
||||
The most common types of 'terminal codes' are the 'ANSI escape codes'. (Although there are other types too.)
|
||||
|
||||
|
||||
ANSI Escape Codes
|
||||
|
||||
ANSI escape codes (also sometimes called 'ANSI escape sequences') are a common type of 'terminal code' used
|
||||
to do things such as:
|
||||
|
||||
• moving the cursor,
|
||||
|
||||
• erasing the display,
|
||||
|
||||
• erasing the line,
|
||||
|
||||
• setting the graphics mode,
|
||||
|
||||
• setting the foregroup color,
|
||||
|
||||
• setting the background color,
|
||||
|
||||
• setting the screen resolution, and
|
||||
|
||||
• setting keyboard strings.
|
||||
|
||||
|
||||
Setting The Foreground Color With ANSI Escape Codes
|
||||
|
||||
One of the abilities of ANSI escape codes is to set the foreground color.
|
||||
|
||||
Here is a table showing codes for this:
|
||||
|
||||
| ANSI Color | Go string | Go []byte |
|
||||
| ------------ | ---------- | ----------------------------- |
|
||||
| Black | "\x1b[30m" | []byte{27, '[', '3','0', 'm'} |
|
||||
| Red | "\x1b[31m" | []byte{27, '[', '3','1', 'm'} |
|
||||
| Green | "\x1b[32m" | []byte{27, '[', '3','2', 'm'} |
|
||||
| Brown/Yellow | "\x1b[33m" | []byte{27, '[', '3','3', 'm'} |
|
||||
| Blue | "\x1b[34m" | []byte{27, '[', '3','4', 'm'} |
|
||||
| Magenta | "\x1b[35m" | []byte{27, '[', '3','5', 'm'} |
|
||||
| Cyan | "\x1b[36m" | []byte{27, '[', '3','6', 'm'} |
|
||||
| Gray/White | "\x1b[37m" | []byte{27, '[', '3','7', 'm'} |
|
||||
|
||||
(Note that in the `[]byte` that the first `byte` is the number `27` (which
|
||||
is the "escape" character) where the third and fouth characters are the
|
||||
**not** number literals, but instead character literals `'3'` and whatever.)
|
||||
|
||||
|
||||
Setting The Background Color With ANSI Escape Codes
|
||||
|
||||
Another of the abilities of ANSI escape codes is to set the background color.
|
||||
|
||||
| ANSI Color | Go string | Go []byte |
|
||||
| ------------ | ---------- | ----------------------------- |
|
||||
| Black | "\x1b[40m" | []byte{27, '[', '4','0', 'm'} |
|
||||
| Red | "\x1b[41m" | []byte{27, '[', '4','1', 'm'} |
|
||||
| Green | "\x1b[42m" | []byte{27, '[', '4','2', 'm'} |
|
||||
| Brown/Yellow | "\x1b[43m" | []byte{27, '[', '4','3', 'm'} |
|
||||
| Blue | "\x1b[44m" | []byte{27, '[', '4','4', 'm'} |
|
||||
| Magenta | "\x1b[45m" | []byte{27, '[', '4','5', 'm'} |
|
||||
| Cyan | "\x1b[46m" | []byte{27, '[', '4','6', 'm'} |
|
||||
| Gray/White | "\x1b[47m" | []byte{27, '[', '4','7', 'm'} |
|
||||
|
||||
(Note that in the `[]byte` that the first `byte` is the number `27` (which
|
||||
is the "escape" character) where the third and fouth characters are the
|
||||
**not** number literals, but instead character literals `'4'` and whatever.)
|
||||
|
||||
Using ANSI Escape Codes
|
||||
|
||||
In Go code, if I wanted to use an ANSI escape code to use a blue background,
|
||||
a white foreground, and bold, I could do that with the ANSI escape code:
|
||||
|
||||
"\x1b[44;37;1m"
|
||||
|
||||
Note that that start with byte value 27, which we have encoded as hexadecimal
|
||||
as \x1b. Followed by the '[' character.
|
||||
|
||||
Coming after that is the sub-string "44", which is the code that sets our background color to blue.
|
||||
|
||||
We follow that with the ';' character (which separates codes).
|
||||
|
||||
And the after that comes the sub-string "37", which is the code that set our foreground color to white.
|
||||
|
||||
After that, we follow with another ";" character (which, again, separates codes).
|
||||
|
||||
And then we follow it the sub-string "1", which is the code that makes things bold.
|
||||
|
||||
And finally, the ANSI escape sequence is finished off with the 'm' character.
|
||||
|
||||
To show this in a more complete example, our `dateHandlerFunc` from before could incorporate ANSI escape sequences as follows:
|
||||
|
||||
func dateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
const layout = "Mon Jan 2 15:04:05 -0700 MST 2006"
|
||||
s := "\x1b[44;37;1m" + time.Now().Format(layout) + "\x1b[0m"
|
||||
|
||||
if _, err := oi.LongWriteString(stdout, s); nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Note that in that example, in addition to using the ANSI escape sequence "\x1b[44;37;1m"
|
||||
to set the background color to blue, set the foreground color to white, and make it bold,
|
||||
we also used the ANSI escape sequence "\x1b[0m" to reset the background and foreground colors
|
||||
and boldness back to "normal".
|
||||
|
||||
*/
|
||||
package telnet
|
||||
@@ -1,33 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
)
|
||||
|
||||
|
||||
// EchoHandler is a simple TELNET server which "echos" back to the client any (non-command)
|
||||
// data back to the TELNET client, it received from the TELNET client.
|
||||
var EchoHandler Handler = internalEchoHandler{}
|
||||
|
||||
|
||||
type internalEchoHandler struct{}
|
||||
|
||||
|
||||
func (handler internalEchoHandler) ServeTELNET(ctx Context, w Writer, r Reader) {
|
||||
|
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:]
|
||||
|
||||
for {
|
||||
n, err := r.Read(p)
|
||||
|
||||
if n > 0 {
|
||||
oi.LongWrite(w, p[:n])
|
||||
}
|
||||
|
||||
if nil != err {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,339 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestEchoHandler(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Bytes []byte
|
||||
Expected []byte
|
||||
}{
|
||||
{
|
||||
Bytes: []byte{},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a"),
|
||||
Expected: []byte("a"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("b"),
|
||||
Expected: []byte("b"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("c"),
|
||||
Expected: []byte("c"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple"),
|
||||
Expected: []byte("apple"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana"),
|
||||
Expected: []byte("banana"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry"),
|
||||
Expected: []byte("cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry"),
|
||||
Expected: []byte("apple banana cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 255},
|
||||
Expected: []byte{255, 255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255},
|
||||
Expected: []byte{255, 255, 255, 255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
Expected: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
var ctx Context = nil
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
writer := newDataWriter(&buffer)
|
||||
reader := newDataReader( bytes.NewReader(test.Bytes) )
|
||||
|
||||
EchoHandler.ServeTELNET(ctx, writer, reader)
|
||||
|
||||
if expected, actual := string(test.Expected), buffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
// A Handler serves a TELNET (or TELNETS) connection.
|
||||
//
|
||||
// Writing data to the Writer passed as an argument to the ServeTELNET method
|
||||
// will send data to the TELNET (or TELNETS) client.
|
||||
//
|
||||
// Reading data from the Reader passed as an argument to the ServeTELNET method
|
||||
// will receive data from the TELNET client.
|
||||
//
|
||||
// The Writer's Write method sends "escaped" TELNET (and TELNETS) data.
|
||||
//
|
||||
// The Reader's Read method "un-escapes" TELNET (and TELNETS) data, and filters
|
||||
// out TELNET (and TELNETS) command sequences.
|
||||
type Handler interface {
|
||||
ServeTELNET(Context, Writer, Reader)
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
type Logger interface{
|
||||
Debug(...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
|
||||
Error(...interface{})
|
||||
Errorf(string, ...interface{})
|
||||
|
||||
Trace(...interface{})
|
||||
Tracef(string, ...interface{})
|
||||
|
||||
Warn(...interface{})
|
||||
Warnf(string, ...interface{})
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
type Reader interface {
|
||||
Read([]byte) (int, error)
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
)
|
||||
|
||||
|
||||
// ListenAndServe listens on the TCP network address `addr` and then spawns a call to the ServeTELNET
|
||||
// method on the `handler` to serve each incoming connection.
|
||||
//
|
||||
// For a very simple example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "github.com/reiver/go-telnet"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
//
|
||||
// //@TODO: In your code, you would probably want to use a different handler.
|
||||
// var handler telnet.Handler = telnet.EchoHandler
|
||||
//
|
||||
// err := telnet.ListenAndServe(":5555", handler)
|
||||
// if nil != err {
|
||||
// //@TODO: Handle this error better.
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
func ListenAndServe(addr string, handler Handler) error {
|
||||
server := &Server{Addr: addr, Handler: handler}
|
||||
return server.ListenAndServe()
|
||||
}
|
||||
|
||||
|
||||
// Serve accepts an incoming TELNET or TELNETS client connection on the net.Listener `listener`.
|
||||
func Serve(listener net.Listener, handler Handler) error {
|
||||
|
||||
server := &Server{Handler: handler}
|
||||
return server.Serve(listener)
|
||||
}
|
||||
|
||||
|
||||
// A Server defines parameters of a running TELNET server.
|
||||
//
|
||||
// For a simple example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "github.com/reiver/go-telnet"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
//
|
||||
// var handler telnet.Handler = telnet.EchoHandler
|
||||
//
|
||||
// server := &telnet.Server{
|
||||
// Addr:":5555",
|
||||
// Handler:handler,
|
||||
// }
|
||||
//
|
||||
// err := server.ListenAndServe()
|
||||
// if nil != err {
|
||||
// //@TODO: Handle this error better.
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
type Server struct {
|
||||
Addr string // TCP address to listen on; ":telnet" or ":telnets" if empty (when used with ListenAndServe or ListenAndServeTLS respectively).
|
||||
Handler Handler // handler to invoke; telnet.EchoServer if nil
|
||||
|
||||
TLSConfig *tls.Config // optional TLS configuration; used by ListenAndServeTLS.
|
||||
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
// ListenAndServe listens on the TCP network address 'server.Addr' and then spawns a call to the ServeTELNET
|
||||
// method on the 'server.Handler' to serve each incoming connection.
|
||||
//
|
||||
// For a simple example:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "github.com/reiver/go-telnet"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
//
|
||||
// var handler telnet.Handler = telnet.EchoHandler
|
||||
//
|
||||
// server := &telnet.Server{
|
||||
// Addr:":5555",
|
||||
// Handler:handler,
|
||||
// }
|
||||
//
|
||||
// err := server.ListenAndServe()
|
||||
// if nil != err {
|
||||
// //@TODO: Handle this error better.
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
func (server *Server) ListenAndServe() error {
|
||||
|
||||
addr := server.Addr
|
||||
if "" == addr {
|
||||
addr = ":telnet"
|
||||
}
|
||||
|
||||
|
||||
listener, err := net.Listen("tcp", addr)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return server.Serve(listener)
|
||||
}
|
||||
|
||||
|
||||
// Serve accepts an incoming TELNET client connection on the net.Listener `listener`.
|
||||
func (server *Server) Serve(listener net.Listener) error {
|
||||
|
||||
defer listener.Close()
|
||||
|
||||
|
||||
logger := server.logger()
|
||||
|
||||
|
||||
handler := server.Handler
|
||||
if nil == handler {
|
||||
//@TODO: Should this be a "ShellHandler" instead, that gives a shell-like experience by default
|
||||
// If this is changd, then need to change the comment in the "type Server struct" definition.
|
||||
logger.Debug("Defaulted handler to EchoHandler.")
|
||||
handler = EchoHandler
|
||||
}
|
||||
|
||||
|
||||
for {
|
||||
// Wait for a new TELNET client connection.
|
||||
logger.Debugf("Listening at %q.", listener.Addr())
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
//@TODO: Could try to recover from certain kinds of errors. Maybe waiting a while before trying again.
|
||||
return err
|
||||
}
|
||||
logger.Debugf("Received new connection from %q.", conn.RemoteAddr())
|
||||
|
||||
// Handle the new TELNET client connection by spawning
|
||||
// a new goroutine.
|
||||
go server.handle(conn, handler)
|
||||
logger.Debugf("Spawned handler to handle connection from %q.", conn.RemoteAddr())
|
||||
}
|
||||
}
|
||||
|
||||
func (server *Server) handle(c net.Conn, handler Handler) {
|
||||
defer c.Close()
|
||||
|
||||
logger := server.logger()
|
||||
|
||||
|
||||
defer func(){
|
||||
if r := recover(); nil != r {
|
||||
if nil != logger {
|
||||
logger.Errorf("Recovered from: (%T) %v", r, r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var ctx Context = NewContext().InjectLogger(logger)
|
||||
|
||||
var w Writer = newDataWriter(c)
|
||||
var r Reader = newDataReader(c)
|
||||
|
||||
handler.ServeTELNET(ctx, w, r)
|
||||
c.Close()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func (server *Server) logger() Logger {
|
||||
logger := server.Logger
|
||||
if nil == logger {
|
||||
logger = internalDiscardLogger{}
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
// StandardCaller is a simple TELNET client which sends to the server any data it gets from os.Stdin
|
||||
// as TELNET (and TELNETS) data, and writes any TELNET (or TELNETS) data it receives from
|
||||
// the server to os.Stdout, and writes any error it has to os.Stderr.
|
||||
var StandardCaller Caller = internalStandardCaller{}
|
||||
|
||||
|
||||
type internalStandardCaller struct{}
|
||||
|
||||
|
||||
func (caller internalStandardCaller) CallTELNET(ctx Context, w Writer, r Reader) {
|
||||
standardCallerCallTELNET(os.Stdin, os.Stdout, os.Stderr, ctx, w, r)
|
||||
}
|
||||
|
||||
|
||||
func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, ctx Context, w Writer, r Reader) {
|
||||
|
||||
go func(writer io.Writer, reader io.Reader) {
|
||||
|
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:]
|
||||
|
||||
for {
|
||||
// Read 1 byte.
|
||||
n, err := reader.Read(p)
|
||||
if n <= 0 && nil == err {
|
||||
continue
|
||||
} else if n <= 0 && nil != err {
|
||||
break
|
||||
}
|
||||
|
||||
oi.LongWrite(writer, p)
|
||||
}
|
||||
}(stdout, r)
|
||||
|
||||
|
||||
|
||||
var buffer bytes.Buffer
|
||||
var p []byte
|
||||
|
||||
var crlfBuffer [2]byte = [2]byte{'\r','\n'}
|
||||
crlf := crlfBuffer[:]
|
||||
|
||||
scanner := bufio.NewScanner(stdin)
|
||||
scanner.Split(scannerSplitFunc)
|
||||
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
buffer.Write(crlf)
|
||||
|
||||
p = buffer.Bytes()
|
||||
|
||||
n, err := oi.LongWrite(w, p)
|
||||
if nil != err {
|
||||
break
|
||||
}
|
||||
if expected, actual := int64(len(p)), n; expected != actual {
|
||||
err := fmt.Errorf("Transmission problem: tried sending %d bytes, but actually only sent %d bytes.", expected, actual)
|
||||
fmt.Fprint(stderr, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
buffer.Reset()
|
||||
}
|
||||
|
||||
// Wait a bit to receive data from the server (that we would send to io.Stdout).
|
||||
time.Sleep(3 * time.Millisecond)
|
||||
}
|
||||
|
||||
|
||||
func scannerSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
if atEOF {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
return bufio.ScanLines(data, atEOF)
|
||||
}
|
||||
@@ -1,974 +0,0 @@
|
||||
package telnet
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestStandardCallerFromClientToServer(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Bytes []byte
|
||||
Expected []byte
|
||||
}{
|
||||
{
|
||||
Bytes: []byte{},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("b"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("c"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a\n"),
|
||||
Expected: []byte("a\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("b\n"),
|
||||
Expected: []byte("b\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("c\n"),
|
||||
Expected: []byte("c\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a\nb\nc"),
|
||||
Expected: []byte("a\r\nb\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a\nb\nc\n"),
|
||||
Expected: []byte("a\r\nb\r\nc\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\n"),
|
||||
Expected: []byte("apple\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana\n"),
|
||||
Expected: []byte("banana\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry\n"),
|
||||
Expected: []byte("cherry\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\nbanana\ncherry"),
|
||||
Expected: []byte("apple\r\nbanana\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\nbanana\ncherry\n"),
|
||||
Expected: []byte("apple\r\nbanana\r\ncherry\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry\n"),
|
||||
Expected: []byte("apple banana cherry\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, '\n'},
|
||||
Expected: []byte{255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\n'},
|
||||
Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xffbanana\xffcherry\n"),
|
||||
Expected: []byte("apple\xff\xffbanana\xff\xffcherry\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xffapple\xffbanana\xffcherry\xff\n"),
|
||||
Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
Expected: []byte(""),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry\n"),
|
||||
Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\r\n"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff\n"),
|
||||
Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff\r\n"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255,251,24, '\n'}, // IAC WILL TERMINAL-TYPE '\n'
|
||||
Expected: []byte{255,255,251,24,'\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,252,24, '\n'}, // IAC WON'T TERMINAL-TYPE '\n'
|
||||
Expected: []byte{255,255,252,24,'\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,253,24, '\n'}, // IAC DO TERMINAL-TYPE '\n'
|
||||
Expected: []byte{255,255,253,24,'\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,254,24, '\n'}, // IAC DON'T TERMINAL-TYPE '\n'
|
||||
Expected: []byte{255,255,254,24,'\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, '\n'}, // 'C' IAC WILL TERMINAL-TYPE '\n'
|
||||
Expected: []byte{67, 255,255,251,24, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, '\n'}, // 'C' IAC WON'T TERMINAL-TYPE '\n'
|
||||
Expected: []byte{67, 255,255,252,24, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, '\n'}, // 'C' IAC DO TERMINAL-TYPE '\n'
|
||||
Expected: []byte{67, 255,255,253,24, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, '\n'}, // 'C' IAC DON'T TERMINAL-TYPE '\n'
|
||||
Expected: []byte{67, 255,255,254,24, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255,251,24, 68, '\n'}, // IAC WILL TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{255,255,251,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,252,24, 68, '\n'}, // IAC WON'T TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{255,255,252,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,253,24, 68, '\n'}, // IAC DO TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{255,255,253,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,254,24, 68, '\n'}, // IAC DON'T TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{255,255,254,24, 68, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, 68, '\n'}, // 'C' IAC WILL TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{67, 255,255,251,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, 68, '\n'}, // 'C' IAC WON'T TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{67, 255,255,252,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, 68, '\n'}, // 'C' IAC DO TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{67, 255,255,253,24, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, 68, '\n'}, // 'C' IAC DON'T TERMINAL-TYPE 'D' '\n'
|
||||
Expected: []byte{67, 255,255,254,24, 68, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255, 250, 24, 1, 255, 240, '\n'}, // IAC SB TERMINAL-TYPE SEND IAC SE '\n'
|
||||
Expected: []byte{255, 255, 250, 24, 1, 255, 255, 240, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, '\n'}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE '\n'
|
||||
Expected: []byte{255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, '\n'}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE '\n'
|
||||
Expected: []byte{67, 255, 255, 250, 24, 1, 255, 255, 240, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, '\n'}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE '\n'
|
||||
Expected: []byte{67, 255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255, 250, 24, 1, 255, 240, 68, '\n'}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' '\n'
|
||||
Expected: []byte{255, 255, 250, 24, 1, 255, 255, 240, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, 68, '\n'}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' '\n'
|
||||
Expected: []byte{255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, 68, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68, '\n'}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' '\n'
|
||||
Expected: []byte{67, 255, 255, 250, 24, 1, 255, 255, 240, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, 68, '\n'}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' '\n'
|
||||
Expected: []byte{67, 255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, 68, '\r','\n'},
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10, 11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, '\n'}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE '\n'
|
||||
Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12, 13, 255,240, '\n'}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE '\n'
|
||||
Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, 68, '\n'}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' '\n'
|
||||
Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, 68, '\r','\n'},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, 68, '\n'}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' '\n'
|
||||
Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, 68, '\r','\n'},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
var stdinBuffer bytes.Buffer
|
||||
var stdoutBuffer bytes.Buffer
|
||||
var stderrBuffer bytes.Buffer
|
||||
|
||||
stdinBuffer.Write(test.Bytes) // <----------------- The important difference between the 2 loops.
|
||||
|
||||
stdin := ioutil.NopCloser(&stdinBuffer)
|
||||
stdout := oi.WriteNopCloser(&stdoutBuffer)
|
||||
stderr := oi.WriteNopCloser(&stderrBuffer)
|
||||
|
||||
var ctx Context = nil
|
||||
|
||||
var dataWriterBuffer bytes.Buffer
|
||||
dataWriter := newDataWriter(&dataWriterBuffer)
|
||||
|
||||
dataReader := newDataReader( bytes.NewReader([]byte{}) ) // <----------------- The important difference between the 2 loops.
|
||||
|
||||
standardCallerCallTELNET(stdin, stdout, stderr, ctx, dataWriter, dataReader)
|
||||
|
||||
|
||||
if expected, actual := string(test.Expected), dataWriterBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q; for %q.", testNumber, expected, actual, test.Bytes)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := "", stdoutBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := "", stderrBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
func TestStandardCallerFromServerToClient(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
Bytes []byte
|
||||
Expected []byte
|
||||
}{
|
||||
{
|
||||
Bytes: []byte{},
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("a"),
|
||||
Expected: []byte("a"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("b"),
|
||||
Expected: []byte("b"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("c"),
|
||||
Expected: []byte("c"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple"),
|
||||
Expected: []byte("apple"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("banana"),
|
||||
Expected: []byte("banana"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("cherry"),
|
||||
Expected: []byte("cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple banana cherry"),
|
||||
Expected: []byte("apple banana cherry"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,255},
|
||||
Expected: []byte{255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255},
|
||||
Expected: []byte{255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255,255},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,255,255,255,255,255,255,255,255,255},
|
||||
Expected: []byte{255,255,255,255,255},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
Expected: []byte("apple\xffbanana\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
Expected: []byte("\xffapple\xffbanana\xffcherry\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"),
|
||||
Expected: []byte("apple\xff\xffbanana\xff\xffcherry"),
|
||||
},
|
||||
{
|
||||
Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"),
|
||||
Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"),
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D'
|
||||
Expected: []byte{67, 68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
|
||||
//@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply?
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE
|
||||
Expected: []byte{67},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{68},
|
||||
},
|
||||
{
|
||||
Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D'
|
||||
Expected: []byte{67,68},
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
var stdinBuffer bytes.Buffer
|
||||
var stdoutBuffer bytes.Buffer
|
||||
var stderrBuffer bytes.Buffer
|
||||
|
||||
stdin := ioutil.NopCloser(&stdinBuffer)
|
||||
stdout := oi.WriteNopCloser(&stdoutBuffer)
|
||||
stderr := oi.WriteNopCloser(&stderrBuffer)
|
||||
|
||||
var ctx Context = nil
|
||||
|
||||
var dataWriterBuffer bytes.Buffer
|
||||
dataWriter := newDataWriter(&dataWriterBuffer)
|
||||
|
||||
dataReader := newDataReader( bytes.NewReader(test.Bytes) ) // <----------------- The important difference between the 2 loops.
|
||||
|
||||
standardCallerCallTELNET(stdin, stdout, stderr, ctx, dataWriter, dataReader)
|
||||
|
||||
|
||||
if expected, actual := "", dataWriterBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q; for %q.", testNumber, expected, actual, test.Bytes)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := string(test.Expected), stdoutBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
|
||||
if expected, actual := "", stderrBuffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user