first commit

This commit is contained in:
ircop 2018-08-07 18:54:08 +03:00
commit 68a5f1bdf0
18 changed files with 1313 additions and 0 deletions

17
Gopkg.lock generated Normal file
View File

@ -0,0 +1,17 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = ["github.com/pkg/errors"]
solver-name = "gps-cdcl"
solver-version = 1

34
Gopkg.toml Normal file
View File

@ -0,0 +1,34 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[prune]
go-tests = true
unused-packages = true

1
README.md Normal file
View File

@ -0,0 +1 @@
# tclient

12
cmd.go Normal file
View File

@ -0,0 +1,12 @@
package tclient
// Cmd sends command and returns output
func (c *TelnetClient) Cmd(cmd string) (string, error) {
err := c.Write([]byte(cmd))
if err != nil {
return "", err
}
return c.ReadUntil(c.Prompt)
}

116
constants.go Normal file
View File

@ -0,0 +1,116 @@
package tclient
const (
// version constant
VERSION = 0.1
// TELNET_IAC interpret as command
TELNET_IAC = 255
// TELNET_DONT don't use following option
TELNET_DONT = 254
// TELNET_DO use following option
TELNET_DO = 253
// TELNET_WONT i will not use this option
TELNET_WONT = 252
// TELNET_WILL i will use this option
TELNET_WILL = 251
// TELNET_SB interpret as subnegotiation
TELNET_SB = 250
// TELNET_GA you may reverse this line
TELNET_GA = 249
// TELNET_EL erase current line
TELNET_EL = 248
// TELNET_EC erase current character
TELNET_EC = 247
// TELNET_AYT are you there
TELNET_AYT = 246
// TELNET_AO abort output but let prog finish
TELNET_AO = 245
// TELNET_IP interrupt process permanently
TELNET_IP = 244
// TELNET_BREAK break
TELNET_BREAK = 243
// TELNET_DM data mark - for connect
TELNET_DM = 242
// TELNET_NOP nop
TELNET_NOP = 241
// TELNET_SE subnegotiation end
TELNET_SE = 240
// TELNET_EOR end of record
TELNET_EOR = 239
// TELNET_ABORT abort process
TELNET_ABORT = 238
// TELNET_SUSP suspend process
TELNET_SUSP = 237
// TELNET_EOF end of file
TELNET_EOF = 236
// TELNET_SYNC for telfunc calls
TELNET_SYNCH = 242
// TELOPT_BINARY binary transmission
TELOPT_BINARY = 0
// TELOPT_ECHO echo
TELOPT_ECHO = 1
// TELOPT_RCP reconnetion
TELOPT_RCP = 2
// TELOPT_SGA suppres go ahead
TELOPT_SGA = 3
// TELOPT_NAMS approx message size negotiation
TELOPT_NAMS = 4
// TELOPT_STATUS status
TELOPT_STATUS = 5
// TELOPT_TM timing mark
TELOPT_TM = 6
// TELOPT_RCTE remote controlled trans and echo
TELOPT_RCTE = 7
// TELOPT_NAOL output line width
TELOPT_NAOL = 8
// TELOPT_NAOP output page size
TELOPT_NAOP = 9
// TELOPT_NAOCRD output carriage-return disposition
TELOPT_NAOCRD = 10
// TELOPT_NAOHTS output horizontal tab stops
TELOPT_NAOHTS = 11
// TELOPT_NAOHTD output horizontal tab disposition
TELOPT_NAOHTD = 12
// TELOPT_NAOFFD output formfeed disposition
TELOPT_NAOFFD = 13
// TELOPT_NAOVTS output vertical tabstops
TELOPT_NAOVTS = 14
// TELOPT_NAOVTD output vertical tab disposition
TELOPT_NAOVTD = 15
// TELOPT_NAOLFD output linefeed disposition
TELOPT_NAOLFD = 16
// TELOPT_XASCII extended ascii
TELOPT_XASCII = 17
// TELOPT_LOGOUT logout
TELOPT_LOGOUT = 18
// TELOPT_BM byte macro
TELOPT_BM = 19
// TELOPT_DET data entry terminal
TELOPT_DET = 20
// TELOPT_SUPDUP SUPDUP
TELOPT_SUPDUP = 21
// TELOPT_SUPDUPOUTPUT SUPDUP output
TELOPT_SUPDUPOUTPUT = 22
// TELOPT_SNDLOC Send location
TELOPT_SNDLOC = 23
// TELOPT_TTYPE terminal type
TELOPT_TTYPE = 24
// TELOPT_EOR end of record
TELOPT_EOR = 25
// TELOPT_TUID TACACS user identification
TELOPT_TUID = 26
// TELOPT_OUTMARK output marking
TELOPT_OUTMARK = 27
// TELOPT_TTYLOC terminal location number
TELOPT_TTYLOC = 28
// TELOPT_NAWS - Negotiate About Window Size
TELOPT_NAWS = 31
// TELOPT_SB_SEND SEND subneg
TELOPT_SB_SEND = 1
// TELOPT_SB_IS IS subneg
TELOPT_SB_IS = 0
// TELOPT_SB_NEV_ENVIRON
TELOPT_SB_NEV_ENVIRON = 39
)

39
login.go Normal file
View File

@ -0,0 +1,39 @@
package tclient
// Login is a simple wrapper for login/password auth
func (c *TelnetClient) Login(login string, password string) (string, error) {
// wait for login
result := ""
out, err := c.ReadUntil(c.loginPrompt)
result = out
if err != nil {
return result, err
}
err = c.Write([]byte(login))
if err != nil {
return result, err
}
// and for password
out, err = c.ReadUntil(c.passwordPrompt)
result += out
if err != nil {
return result, err
}
err = c.Write([]byte(password))
if err != nil {
return result, err
}
// and wait for prompt
out, err = c.ReadUntil(c.Prompt)
result += out
if err != nil {
return result, err
}
return result, nil
}

58
negotiation.go Normal file
View File

@ -0,0 +1,58 @@
package tclient
func (c *TelnetClient) negotiate(sequence []byte) error {
if len(sequence) < 3 {
// do nothing
return nil
}
var err error
// DO sequence
if sequence[1] == TELNET_DO && len(sequence) == 3 {
switch sequence[2] {
case TELOPT_TTYPE:
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_WILL, TELOPT_TTYPE})
break
case TELOPT_SB_NEV_ENVIRON:
// iac do newinv - iac will newinv
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_WILL, TELOPT_SB_NEV_ENVIRON})
break
case TELOPT_NAWS:
// wont naws
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_WONT, TELOPT_NAWS})
break
default:
// accept any other 'do'
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_WILL, sequence[2]})
break
}
}
// subseq SEND request
if len(sequence) == 6 && sequence[1] == TELNET_SB && sequence[3] == TELOPT_SB_SEND {
// what to send?
switch(sequence[2]) {
case TELOPT_TTYPE:
// set terminal to xterm
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_SB, TELOPT_TTYPE, TELOPT_SB_IS, 'X', 'T', 'E', 'R', 'M', TELNET_IAC, TELNET_SE})
break
case TELOPT_SB_NEV_ENVIRON:
// send new-env -> is new env
err = c.WriteRaw([]byte{TELNET_IAC, TELNET_SB, TELOPT_SB_NEV_ENVIRON, TELOPT_SB_IS, TELNET_IAC, TELNET_SE})
break
default:
break
}
}
// subseq IS request
if len(sequence) == 6 && sequence[1] == TELNET_SB && sequence[3] == TELOPT_SB_IS {
//
}
if err != nil {
return err
}
return nil
}

156
reader.go Normal file
View File

@ -0,0 +1,156 @@
package tclient
import (
"time"
"fmt"
"bytes"
"github.com/pkg/errors"
"regexp"
)
func (c *TelnetClient) ReadUntil(waitfor string) (string, error) {
var err error
var b byte
var prev byte
var buf bytes.Buffer
var lastLine bytes.Buffer
if waitfor == "" {
return buf.String(), fmt.Errorf(`Empty "waitfor" string given`)
}
rePrompt, err := regexp.Compile(waitfor)
if err != nil {
return buf.String(), fmt.Errorf(`Cannot compile "waitfor" regexp`)
}
// run reading cycle
globalTout := time.After(time.Second * time.Duration(c.TimeoutGlobal))
for {
select {
//case <- c.ctx.Done():
// return c.cutEscapes(buf), nil
case <- globalTout:
return c.cutEscapes(buf), fmt.Errorf("Operation timeout reached during read")
default:
prev = b
b, err = c.readByte()
if err != nil {
return c.cutEscapes(buf), errors.Wrap(err, "Error during read")
}
// catch escape sequences
if b == TELNET_IAC {
seq := []byte{b}
b2, err := c.readByte()
if err != nil {
return c.cutEscapes(buf), errors.Wrap(err, "Error while reading escape sequence")
}
seq = append(seq, b2)
if b2 == TELNET_SB { // subnegotiation
// read all until subneg. end.
for {
bn, err := c.readByte()
if err != nil {
return c.cutEscapes(buf), errors.Wrap(err, "Error while reading escape subnegotiation sequence")
}
seq = append(seq, bn)
if bn == TELNET_SE {
break
}
}
} else {
// not subsequence.
bn, err := c.readByte()
if err != nil {
return c.cutEscapes(buf), errors.Wrap(err, "Error while reading IAC sequence")
}
seq = append(seq, bn)
}
// Sequence finished, do something with it:
err = c.negotiate(seq)
if err != nil {
return c.cutEscapes(buf), errors.Wrap(err, "Failed to negotiate connection")
//c.errChan <- fmt.Sprintf("Failed to negotiate connection: %s", err.Error())
}
}
// this is not escape sequence, so write this byte to buffer
buf.Write([]byte{b})
//fmt.Printf("%v\t|\t%s\n",b,string(b))
// check for CRLF.
// We need last line to compare with prompt.
if b == '\n' && prev == '\r' {
lastLine.Reset()
} else {
lastLine.Write([]byte{b})
}
// After reading, we should check for regexp every time.
// Unfortunately, we cant wait only CRLF, because prompt usually comes without CRLF.
if rePrompt.Match(lastLine.Bytes()) {
// we've catched required prompt.
// now remove all escape sequences and return result
return c.cutEscapes(buf), nil
}
}
}
}
func (c *TelnetClient) cutEscapes(buffer bytes.Buffer) string {
bts := buffer.Bytes()
result := ""
inSequence := false
for i := range bts {
if bts[i] == 27 {
inSequence = true
continue
}
if inSequence {
// 2) 0-?, @-~, ' ' - / === 48-63, 32-47, finish with 64-126
if bts[i] == 91 {
continue
}
if bts[i] >= 32 && bts[i] <= 63 {
// just skip it
continue
}
if bts[i] >= 64 && bts[i] <= 126 {
// finish sequence
inSequence = false
continue
}
}
result += string(bts[i])
}
return result
}
// read one byte from tcp stream
func (c *TelnetClient) readByte() (byte, error) {
var err error
var buffer [1]byte
p := buffer[:]
err = c.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(c.Timeout)))
if err != nil {
return p[0], err
}
_, err = c.conn.Read(p)
// error during read
if err != nil {
return p[0], errors.Wrap(err, "Error during readByte")
}
return p[0], nil
}

101
telnet.go Normal file
View File

@ -0,0 +1,101 @@
package tclient
import (
"fmt"
"net"
"time"
)
type TelnetClient struct {
// Timeout of read/write operations
Timeout int
// TimeoutGlobal is global operation timeout; i.e. like stucked in DLink-like refreshing pagination
TimeoutGlobal int
Prompt string
conn net.Conn
closed bool
Options []int
loginPrompt string
passwordPrompt string
reader bool
writer bool
}
func New(tout int, prompt string) *TelnetClient {
if tout < 1 {
tout = 1
}
c := TelnetClient{
Timeout: tout,
Prompt: `(?msi:[\$%#>]$)`,
loginPrompt: `[Uu]ser[Nn]ame\:$`,
passwordPrompt: `[Pp]ass[Ww]ord\:$`,
closed:false,
Options: make([]int,0),
}
if prompt != "" {
c.Prompt = prompt
}
// Global timeout defaults to 3 * rw timeout
c.TimeoutGlobal = c.Timeout * 2
// set default options
// we will accept an offer from remote sidie for it to echo and suppress goaheads
c.SetOpts([]int{TELOPT_ECHO, TELOPT_SGA})
return &c
}
func (c *TelnetClient) GlobalTimeout(t int) {
c.TimeoutGlobal = t
}
// SetLoginPrompt sets login prompt
func (c *TelnetClient) SetLoginPrompt(s string) {
c.loginPrompt = s
}
func (c *TelnetClient) SetPasswordPrompt(s string) {
c.passwordPrompt = s
}
func (c *TelnetClient) Close() {
c.conn.Close()
}
// FlushOpts flushes default options set
func (c *TelnetClient) FlushOpts() {
c.Options = make([]int, 0)
}
// SetOpts prepares default options set
func (c *TelnetClient) SetOpts(opts []int) error {
for _, opt := range opts {
if opt > 255 {
return fmt.Errorf("Bad telnet option %d: option > 255", opt)
}
c.Options = append(c.Options, opt)
}
return nil
}
// Open tcp connection
func (c *TelnetClient) Open(host string, port int) error {
addr := fmt.Sprintf("%s:%d", host, port)
var err error
c.conn, err = net.DialTimeout("tcp", addr, time.Second * time.Duration(c.Timeout))
if err != nil {
c.closed = true
return err
}
return nil
}

156
telnet_test.go Normal file
View File

@ -0,0 +1,156 @@
package tclient
import (
"testing"
"net"
"github.com/pkg/errors"
"time"
)
func TestNew(t *testing.T) {
c := New(-1, "")
if c.Timeout != 1 {
t.Fatal("Negative timeout should be modified to 1")
}
}
func TestDialFailed(t *testing.T) {
c := New(0,"")
err := c.Open("127.0.0.1", 12345)
if err == nil {
t.Fatal("127.0.0.1:12345 should fails")
}
}
func TestDialOk(t *testing.T) {
l, err := net.Listen("tcp4", ":33333")
if err != nil {
t.Fatal(err)
}
defer l.Close()
c := New(0, "")
err = c.Open("127.0.0.1", 33333)
if err != nil {
t.Fatal(errors.Wrap(err, "Failed to dial localhost"))
}
}
func TestReadUntilSimple(t *testing.T) {
s, c := net.Pipe()
defer s.Close()
defer c.Close()
client := New(0, "")
client.conn = c
contents := `asd qwe zxc 123 >`
go func() {
_, e := s.Write([]byte(contents))
if e != nil {
t.Fatal(e, "Failed to write to pipe")
}
}()
out, err := client.ReadUntil(`>$`)
if err != nil {
t.Fatal(errors.Wrap(err, "Error during pipe read"))
}
if out != contents {
t.Fatal("Recorded and read data doesn't match!")
}
}
func TestREadUntilTimeout(t *testing.T) {
s, c := net.Pipe()
defer s.Close()
defer c.Close()
client := New(0, "")
client.conn = c
_, err := client.ReadUntil(`>$`)
if err == nil {
t.Fatal("Should timeout")
}
}
func TestComplexLogin(t *testing.T) {
login := "testLogin"
pw := "testPw"
s, c := net.Pipe()
defer s.Close()
defer c.Close()
client := New(0, "")
client.conn = c
preLogin := `
DES-3028 Fast Ethernet Switch Command Line Interface
Firmware: Build 2.70.B06
Copyright(C) 2008 D-Link Corporation. All rights reserved.
UserName:`
prePw := `PassWord:`
prePrompt := `
DES-3028:5#`
// server should also read messages from client...
go func() {
for {
s.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(999)))
var buffer [1]byte
p := buffer[:]
_, err := s.Read(p)
if err != nil {
return
}
}
}()
go func () {
_, e := s.Write([]byte(preLogin))
if e != nil {
t.Fatalf("Error writing preLogin: %s", e.Error())
}
}()
_, err := client.ReadUntil(client.loginPrompt)
if err != nil {
t.Fatalf("Error reading login prompt: %s", err.Error())
}
err = client.Write([]byte(login))
if err != nil {
t.Fatalf("error writing login: %s", err.Error())
}
go func () {
_, e := s.Write([]byte(prePw))
if e != nil {
t.Fatalf("Error writing preLogin: %s", e.Error())
}
}()
err = client.Write([]byte(pw))
if err != nil {
t.Fatalf("error writing password: %s", err.Error())
}
_, err = client.ReadUntil(client.passwordPrompt)
if err != nil {
t.Fatalf("Error reading password prompt: %s", err.Error())
}
go func () {
_, e := s.Write([]byte(prePrompt))
if e != nil {
t.Fatalf("Error writing prompt: %s", e.Error())
}
}()
out, err := client.ReadUntil(client.Prompt)
if err != nil {
t.Fatalf("Error reading prompt: %s\nlast: %s", err.Error(), out)
}
}

24
vendor/github.com/pkg/errors/.gitignore generated vendored Normal file
View File

@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof

11
vendor/github.com/pkg/errors/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,11 @@
language: go
go_import_path: github.com/pkg/errors
go:
- 1.4.3
- 1.5.4
- 1.6.2
- 1.7.1
- tip
script:
- go test -v ./...

23
vendor/github.com/pkg/errors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

52
vendor/github.com/pkg/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors)
Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
if err != nil {
return err
}
```
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
## Adding context to an error
The errors.Wrap function returns a new error that adds context to the original error. For example
```go
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```
## Retrieving the cause of an error
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
```go
type causer interface {
Cause() error
}
```
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
```go
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
```
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
Before proposing a change, please discuss your change by raising an issue.
## Licence
BSD-2-Clause

32
vendor/github.com/pkg/errors/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,32 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\pkg\errors
shallow_clone: true # for startup speed
environment:
GOPATH: C:\gopath
platform:
- x64
# http://www.appveyor.com/docs/installed-software
install:
# some helpful output for debugging builds
- go version
- go env
# pre-installed MinGW at C:\MinGW is 32bit only
# but MSYS2 at C:\msys64 has mingw64
- set PATH=C:\msys64\mingw64\bin;%PATH%
- gcc --version
- g++ --version
build_script:
- go install -v ./...
test_script:
- set PATH=C:\gopath\bin;%PATH%
- go test -v ./...
#artifacts:
# - path: '%GOPATH%\bin\*.exe'
deploy: off

269
vendor/github.com/pkg/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,269 @@
// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
// if err != nil {
// return err
// }
//
// which applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// and the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required the errors.WithStack and errors.WithMessage
// functions destructure errors.Wrap into its component operations of annotating
// an error with a stack trace and an a message, respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
// type causer interface {
// Cause() error
// }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error which does not implement causer, which is assumed to be
// the original cause. For example:
//
// switch err := errors.Cause(err).(type) {
// case *MyError:
// // handle specifically
// default:
// // unknown error
// }
//
// causer interface is not exported by this package, but is considered a part
// of stable public API.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported
//
// %s print the error. If the error has a Cause it will be
// printed recursively
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface.
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// Where errors.StackTrace is defined as
//
// type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// stackTracer interface is not exported by this package, but is considered a part
// of stable public API.
//
// See the documentation for Frame.Format for more details.
package errors
import (
"fmt"
"io"
)
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type withStack struct {
error
*stack
}
func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is call, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
}
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

178
vendor/github.com/pkg/errors/stack.go generated vendored Normal file
View File

@ -0,0 +1,178 @@
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strings"
)
// Frame represents a program counter inside a stack frame.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s path of source file relative to the compile time GOPATH
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}
func trimGOPATH(name, file string) string {
// Here we want to get the source file path relative to the compile time
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
// GOPATH at runtime, but we can infer the number of path segments in the
// GOPATH. We note that fn.Name() returns the function name qualified by
// the import path, which does not include the GOPATH. Thus we can trim
// segments from the beginning of the file path until the number of path
// separators remaining is one more than the number of path separators in
// the function name. For example, given:
//
// GOPATH /home/user
// file /home/user/src/pkg/sub/file.go
// fn.Name() pkg/sub.Type.Method
//
// We want to produce:
//
// pkg/sub/file.go
//
// From this we can easily see that fn.Name() has one less path separator
// than our desired output. We count separators from the end of the file
// path until it finds two more than in the function name and then move
// one character forward to preserve the initial path segment without a
// leading separator.
const sep = "/"
goal := strings.Count(name, sep) + 2
i := len(file)
for n := 0; n < goal; n++ {
i = strings.LastIndex(file[:i], sep)
if i == -1 {
// not enough separators found, set i so that the slice expression
// below leaves file unmodified
i = -len(sep)
break
}
}
// get back to 0 or trim the leading separator
file = file[i+len(sep):]
return file
}

34
writer.go Normal file
View File

@ -0,0 +1,34 @@
package tclient
import (
"time"
"io"
"github.com/pkg/errors"
)
// Write is the same as WriteRaw, but adds CRLF to given string
func (c *TelnetClient) Write(bytes []byte) error {
bytes = append(bytes, '\r', '\n')
return c.WriteRaw(bytes)
}
// WriteRaw writes raw bytes to tcp connection
func (c *TelnetClient) WriteRaw(bytes []byte) error {
var wrote int
var err error
err = c.conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(c.Timeout)))
if err != nil {
return err
}
n, err := c.conn.Write(bytes)
wrote += n
if err != nil && err != io.ErrShortWrite {
return errors.Wrap(err, "Failed to WriteRaw()")
}
return nil
}