From 5ab729b166b48d645e0655495b4b37892dd8eaa4 Mon Sep 17 00:00:00 2001 From: ginuerzh Date: Sun, 15 Oct 2023 23:55:52 +0800 Subject: [PATCH] fix serial --- dialer/serial/dialer.go | 9 +- handler/relay/connect.go | 5 +- handler/serial/handler.go | 9 +- internal/util/serial/serial.go | 203 +++++++++++---- internal/util/serial/serial_linux.go | 165 +++++++++++++ internal/util/serial/serial_other.go | 28 +++ internal/util/serial/serial_windows.go | 328 +++++++++++++++++++++++++ internal/util/serial/util.go | 66 +++++ listener/serial/listener.go | 13 +- 9 files changed, 755 insertions(+), 71 deletions(-) create mode 100644 internal/util/serial/serial_linux.go create mode 100644 internal/util/serial/serial_other.go create mode 100644 internal/util/serial/serial_windows.go create mode 100644 internal/util/serial/util.go diff --git a/dialer/serial/dialer.go b/dialer/serial/dialer.go index ba36941..6d329ea 100644 --- a/dialer/serial/dialer.go +++ b/dialer/serial/dialer.go @@ -7,9 +7,8 @@ import ( "github.com/go-gost/core/dialer" "github.com/go-gost/core/logger" md "github.com/go-gost/core/metadata" - serial_util "github.com/go-gost/x/internal/util/serial" + serial "github.com/go-gost/x/internal/util/serial" "github.com/go-gost/x/registry" - goserial "github.com/tarm/serial" ) func init() { @@ -42,11 +41,11 @@ func (d *serialDialer) Dial(ctx context.Context, addr string, opts ...dialer.Dia opt(&options) } - cfg := serial_util.ParseConfigFromAddr(addr) - port, err := goserial.OpenPort(cfg) + cfg := serial.ParseConfigFromAddr(addr) + port, err := serial.OpenPort(cfg) if err != nil { return nil, err } - return serial_util.NewConn(port, &serial_util.Addr{Port: cfg.Name}, nil), nil + return serial.NewConn(port, &serial.Addr{Port: cfg.Name}, nil), nil } diff --git a/handler/relay/connect.go b/handler/relay/connect.go index 0936dac..8735399 100644 --- a/handler/relay/connect.go +++ b/handler/relay/connect.go @@ -13,8 +13,7 @@ import ( "github.com/go-gost/relay" xnet "github.com/go-gost/x/internal/net" sx "github.com/go-gost/x/internal/util/selector" - serial_util "github.com/go-gost/x/internal/util/serial" - goserial "github.com/tarm/serial" + serial "github.com/go-gost/x/internal/util/serial" ) func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network, address string, log logger.Logger) (err error) { @@ -62,7 +61,7 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network case "unix": cc, err = (&net.Dialer{}).DialContext(ctx, "unix", address) case "serial": - cc, err = goserial.OpenPort(serial_util.ParseConfigFromAddr(address)) + cc, err = serial.OpenPort(serial.ParseConfigFromAddr(address)) default: cc, err = h.router.Dial(ctx, network, address) } diff --git a/handler/serial/handler.go b/handler/serial/handler.go index 5d60fb1..59853b4 100644 --- a/handler/serial/handler.go +++ b/handler/serial/handler.go @@ -14,10 +14,9 @@ import ( md "github.com/go-gost/core/metadata" "github.com/go-gost/core/recorder" xnet "github.com/go-gost/x/internal/net" - serial_util "github.com/go-gost/x/internal/util/serial" + serial "github.com/go-gost/x/internal/util/serial" xrecorder "github.com/go-gost/x/recorder" "github.com/go-gost/x/registry" - goserial "github.com/tarm/serial" ) func init() { @@ -119,14 +118,14 @@ func (h *serialHandler) forwardSerial(ctx context.Context, conn net.Conn, target log.Debugf("%s >> %s", conn.LocalAddr(), target.Addr) var port io.ReadWriteCloser - cfg := serial_util.ParseConfigFromAddr(conn.LocalAddr().String()) + cfg := serial.ParseConfigFromAddr(conn.LocalAddr().String()) cfg.Name = target.Addr if opts := h.router.Options(); opts != nil && opts.Chain != nil { - port, err = h.router.Dial(ctx, "serial", serial_util.AddrFromConfig(cfg)) + port, err = h.router.Dial(ctx, "serial", serial.AddrFromConfig(cfg)) } else { cfg.ReadTimeout = h.md.timeout - port, err = goserial.OpenPort(cfg) + port, err = serial.OpenPort(cfg) } if err != nil { log.Error(err) diff --git a/internal/util/serial/serial.go b/internal/util/serial/serial.go index 975295a..a86d53f 100644 --- a/internal/util/serial/serial.go +++ b/internal/util/serial/serial.go @@ -1,68 +1,169 @@ +/* +Goserial is a simple go package to allow you to read and write from +the serial port as a stream of bytes. + +It aims to have the same API on all platforms, including windows. As +an added bonus, the windows package does not use cgo, so you can cross +compile for windows from another platform. Unfortunately goinstall +does not currently let you cross compile so you will have to do it +manually: + + GOOS=windows make clean install + +Currently there is very little in the way of configurability. You can +set the baud rate. Then you can Read(), Write(), or Close() the +connection. Read() will block until at least one byte is returned. +Write is the same. There is currently no exposed way to set the +timeouts, though patches are welcome. + +Currently all ports are opened with 8 data bits, 1 stop bit, no +parity, no hardware flow control, and no software flow control. This +works fine for many real devices and many faux serial devices +including usb-to-serial converters and bluetooth serial ports. + +You may Read() and Write() simulantiously on the same connection (from +different goroutines). + +Example usage: + + package main + + import ( + "github.com/tarm/serial" + "log" + ) + + func main() { + c := &serial.Config{Name: "COM5", Baud: 115200} + s, err := serial.OpenPort(c) + if err != nil { + log.Fatal(err) + } + + n, err := s.Write([]byte("test")) + if err != nil { + log.Fatal(err) + } + + buf := make([]byte, 128) + n, err = s.Read(buf) + if err != nil { + log.Fatal(err) + } + log.Print("%q", buf[:n]) + } +*/ package serial import ( - "strconv" - "strings" + "errors" + "io" + "time" +) - goserial "github.com/tarm/serial" +const DefaultSize = 8 // Default value for Config.Size + +type StopBits byte +type Parity byte + +const ( + Stop1 StopBits = 1 + Stop1Half StopBits = 15 + Stop2 StopBits = 2 ) const ( - DefaultPort = "COM1" - DefaultBaudRate = 9600 + ParityNone Parity = 'N' + ParityOdd Parity = 'O' + ParityEven Parity = 'E' + ParityMark Parity = 'M' // parity bit is always 1 + ParitySpace Parity = 'S' // parity bit is always 0 ) -// COM1,9600,odd -func ParseConfigFromAddr(addr string) *goserial.Config { - cfg := &goserial.Config{ - Name: DefaultPort, - Baud: DefaultBaudRate, - } - ss := strings.Split(addr, ",") - switch len(ss) { - case 1: - cfg.Name = ss[0] - case 2: - cfg.Name = ss[0] - cfg.Baud, _ = strconv.Atoi(ss[1]) - case 3: - cfg.Name = ss[0] - cfg.Baud, _ = strconv.Atoi(ss[1]) - cfg.Parity = parseParity(ss[2]) - } - return cfg +// Config contains the information needed to open a serial port. +// +// Currently few options are implemented, but more may be added in the +// future (patches welcome), so it is recommended that you create a +// new config addressing the fields by name rather than by order. +// +// For example: +// +// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500} +// +// or +// +// c1 := new(serial.Config) +// c1.Name = "/dev/tty.usbserial" +// c1.Baud = 115200 +// c1.ReadTimeout = time.Millisecond * 500 +type Config struct { + Name string + Baud int + ReadTimeout time.Duration // Total timeout + + // Size is the number of data bits. If 0, DefaultSize is used. + Size byte + + // Parity is the bit to use and defaults to ParityNone (no parity bit). + Parity Parity + + // Number of stop bits to use. Default is 1 (1 stop bit). + StopBits StopBits + + // RTSFlowControl bool + // DTRFlowControl bool + // XONFlowControl bool + + // CRLFTranslate bool } -func AddrFromConfig(cfg *goserial.Config) string { - ss := []string{ - cfg.Name, - strconv.Itoa(cfg.Baud), - } +// ErrBadSize is returned if Size is not supported. +var ErrBadSize error = errors.New("unsupported serial data size") - switch cfg.Parity { - case goserial.ParityEven: - ss = append(ss, "even") - case goserial.ParityOdd: - ss = append(ss, "odd") - case goserial.ParityMark: - ss = append(ss, "mark") - case goserial.ParitySpace: - ss = append(ss, "space") +// ErrBadStopBits is returned if the specified StopBits setting not supported. +var ErrBadStopBits error = errors.New("unsupported stop bit setting") + +// ErrBadParity is returned if the parity is not supported. +var ErrBadParity error = errors.New("unsupported parity setting") + +// OpenPort opens a serial port with the specified configuration +func OpenPort(c *Config) (io.ReadWriteCloser, error) { + size, par, stop := c.Size, c.Parity, c.StopBits + if size == 0 { + size = DefaultSize } - return strings.Join(ss, ",") + if par == 0 { + par = ParityNone + } + if stop == 0 { + stop = Stop1 + } + return openPort(c.Name, c.Baud, size, par, stop, c.ReadTimeout) } -func parseParity(s string) goserial.Parity { - switch strings.ToLower(s) { - case "o", "odd": - return goserial.ParityOdd - case "e", "even": - return goserial.ParityEven - case "m", "mark": - return goserial.ParityMark - case "s", "space": - return goserial.ParitySpace - default: - return goserial.ParityNone +// Converts the timeout values for Linux / POSIX systems +func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) { + const MAXUINT8 = 1<<8 - 1 // 255 + // set blocking / non-blocking read + var minBytesToRead uint8 = 1 + var readTimeoutInDeci int64 + if readTimeout > 0 { + // EOF on zero read + minBytesToRead = 0 + // convert timeout to deciseconds as expected by VTIME + readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100) + // capping the timeout + if readTimeoutInDeci < 1 { + // min possible timeout 1 Deciseconds (0.1s) + readTimeoutInDeci = 1 + } else if readTimeoutInDeci > MAXUINT8 { + // max possible timeout is 255 deciseconds (25.5s) + readTimeoutInDeci = MAXUINT8 + } } + return minBytesToRead, uint8(readTimeoutInDeci) } + +// func SendBreak() + +// func RegisterBreakHandler(func()) diff --git a/internal/util/serial/serial_linux.go b/internal/util/serial/serial_linux.go new file mode 100644 index 0000000..138f571 --- /dev/null +++ b/internal/util/serial/serial_linux.go @@ -0,0 +1,165 @@ +//go:build linux +// +build linux + +package serial + +import ( + "fmt" + "os" + "time" + "unsafe" + + "golang.org/x/sys/unix" +) + +func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) { + var bauds = map[int]uint32{ + 50: unix.B50, + 75: unix.B75, + 110: unix.B110, + 134: unix.B134, + 150: unix.B150, + 200: unix.B200, + 300: unix.B300, + 600: unix.B600, + 1200: unix.B1200, + 1800: unix.B1800, + 2400: unix.B2400, + 4800: unix.B4800, + 9600: unix.B9600, + 19200: unix.B19200, + 38400: unix.B38400, + 57600: unix.B57600, + 115200: unix.B115200, + 230400: unix.B230400, + 460800: unix.B460800, + 500000: unix.B500000, + 576000: unix.B576000, + 921600: unix.B921600, + 1000000: unix.B1000000, + 1152000: unix.B1152000, + 1500000: unix.B1500000, + 2000000: unix.B2000000, + 2500000: unix.B2500000, + 3000000: unix.B3000000, + 3500000: unix.B3500000, + 4000000: unix.B4000000, + } + + rate, ok := bauds[baud] + + if !ok { + return nil, fmt.Errorf("Unrecognized baud rate") + } + + f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666) + if err != nil { + return nil, err + } + + defer func() { + if err != nil && f != nil { + f.Close() + } + }() + + // Base settings + cflagToUse := unix.CREAD | unix.CLOCAL | rate + switch databits { + case 5: + cflagToUse |= unix.CS5 + case 6: + cflagToUse |= unix.CS6 + case 7: + cflagToUse |= unix.CS7 + case 8: + cflagToUse |= unix.CS8 + default: + return nil, ErrBadSize + } + // Stop bits settings + switch stopbits { + case Stop1: + // default is 1 stop bit + case Stop2: + cflagToUse |= unix.CSTOPB + default: + // Don't know how to set 1.5 + return nil, ErrBadStopBits + } + // Parity settings + switch parity { + case ParityNone: + // default is no parity + case ParityOdd: + cflagToUse |= unix.PARENB + cflagToUse |= unix.PARODD + case ParityEven: + cflagToUse |= unix.PARENB + default: + return nil, ErrBadParity + } + fd := f.Fd() + vmin, vtime := posixTimeoutValues(readTimeout) + t := unix.Termios{ + Iflag: unix.IGNPAR, + Cflag: cflagToUse, + Ispeed: rate, + Ospeed: rate, + } + t.Cc[unix.VMIN] = vmin + t.Cc[unix.VTIME] = vtime + + if _, _, errno := unix.Syscall6( + unix.SYS_IOCTL, + uintptr(fd), + uintptr(unix.TCSETS), + uintptr(unsafe.Pointer(&t)), + 0, + 0, + 0, + ); errno != 0 { + return nil, errno + } + + if err = unix.SetNonblock(int(fd), false); err != nil { + return + } + + return &Port{f: f}, nil +} + +type Port struct { + // We intentionly do not use an "embedded" struct so that we + // don't export File + f *os.File +} + +func (p *Port) Read(b []byte) (n int, err error) { + return p.f.Read(b) +} + +func (p *Port) Write(b []byte) (n int, err error) { + return p.f.Write(b) +} + +// Discards data written to the port but not transmitted, +// or data received but not read +func (p *Port) Flush() error { + const TCFLSH = 0x540B + _, _, errno := unix.Syscall( + unix.SYS_IOCTL, + uintptr(p.f.Fd()), + uintptr(TCFLSH), + uintptr(unix.TCIOFLUSH), + ) + + if errno == 0 { + return nil + } + return errno +} + +func (p *Port) Close() (err error) { + return p.f.Close() +} diff --git a/internal/util/serial/serial_other.go b/internal/util/serial/serial_other.go new file mode 100644 index 0000000..4c1c1cc --- /dev/null +++ b/internal/util/serial/serial_other.go @@ -0,0 +1,28 @@ +//go:build !windows && !linux +// +build !windows,!linux + +package serial + +import ( + "errors" + "time" +) + +func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) { + return nil, errors.New("unsupported platform") +} + +type Port struct { +} + +func (p *Port) Read(b []byte) (n int, err error) { + return +} + +func (p *Port) Write(b []byte) (n int, err error) { + return +} + +func (p *Port) Close() (err error) { + return +} diff --git a/internal/util/serial/serial_windows.go b/internal/util/serial/serial_windows.go new file mode 100644 index 0000000..82eca6e --- /dev/null +++ b/internal/util/serial/serial_windows.go @@ -0,0 +1,328 @@ +//go:build windows +// +build windows + +package serial + +import ( + "fmt" + "os" + "sync" + "syscall" + "time" + "unsafe" +) + +type Port struct { + f *os.File + fd syscall.Handle + rl sync.Mutex + wl sync.Mutex + ro *syscall.Overlapped + wo *syscall.Overlapped +} + +type structDCB struct { + DCBlength, BaudRate uint32 + flags [4]byte + wReserved, XonLim, XoffLim uint16 + ByteSize, Parity, StopBits byte + XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte + wReserved1 uint16 +} + +type structTimeouts struct { + ReadIntervalTimeout uint32 + ReadTotalTimeoutMultiplier uint32 + ReadTotalTimeoutConstant uint32 + WriteTotalTimeoutMultiplier uint32 + WriteTotalTimeoutConstant uint32 +} + +func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) { + if len(name) > 0 && name[0] != '\\' { + name = "\\\\.\\" + name + } + + h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name), + syscall.GENERIC_READ|syscall.GENERIC_WRITE, + 0, + nil, + syscall.OPEN_EXISTING, + syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED, + 0) + if err != nil { + return nil, err + } + f := os.NewFile(uintptr(h), name) + defer func() { + if err != nil { + f.Close() + } + }() + + if err = setCommState(h, baud, databits, parity, stopbits); err != nil { + return nil, err + } + if err = setupComm(h, 64, 64); err != nil { + return nil, err + } + if err = setCommTimeouts(h, readTimeout); err != nil { + return nil, err + } + if err = setCommMask(h); err != nil { + return nil, err + } + + ro, err := newOverlapped() + if err != nil { + return nil, err + } + wo, err := newOverlapped() + if err != nil { + return nil, err + } + port := new(Port) + port.f = f + port.fd = h + port.ro = ro + port.wo = wo + + return port, nil +} + +func (p *Port) Close() error { + return p.f.Close() +} + +func (p *Port) Write(buf []byte) (int, error) { + p.wl.Lock() + defer p.wl.Unlock() + + if err := resetEvent(p.wo.HEvent); err != nil { + return 0, err + } + var n uint32 + err := syscall.WriteFile(p.fd, buf, &n, p.wo) + if err != nil && err != syscall.ERROR_IO_PENDING { + return int(n), err + } + return getOverlappedResult(p.fd, p.wo) +} + +func (p *Port) Read(buf []byte) (int, error) { + if p == nil || p.f == nil { + return 0, fmt.Errorf("Invalid port on read") + } + + p.rl.Lock() + defer p.rl.Unlock() + + if err := resetEvent(p.ro.HEvent); err != nil { + return 0, err + } + var done uint32 + err := syscall.ReadFile(p.fd, buf, &done, p.ro) + if err != nil && err != syscall.ERROR_IO_PENDING { + return int(done), err + } + return getOverlappedResult(p.fd, p.ro) +} + +// Discards data written to the port but not transmitted, +// or data received but not read +func (p *Port) Flush() error { + return purgeComm(p.fd) +} + +var ( + nSetCommState, + nSetCommTimeouts, + nSetCommMask, + nSetupComm, + nGetOverlappedResult, + nCreateEvent, + nResetEvent, + nPurgeComm, + nFlushFileBuffers uintptr +) + +func init() { + k32, err := syscall.LoadLibrary("kernel32.dll") + if err != nil { + panic("LoadLibrary " + err.Error()) + } + defer syscall.FreeLibrary(k32) + + nSetCommState = getProcAddr(k32, "SetCommState") + nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts") + nSetCommMask = getProcAddr(k32, "SetCommMask") + nSetupComm = getProcAddr(k32, "SetupComm") + nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult") + nCreateEvent = getProcAddr(k32, "CreateEventW") + nResetEvent = getProcAddr(k32, "ResetEvent") + nPurgeComm = getProcAddr(k32, "PurgeComm") + nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers") +} + +func getProcAddr(lib syscall.Handle, name string) uintptr { + addr, err := syscall.GetProcAddress(lib, name) + if err != nil { + panic(name + " " + err.Error()) + } + return addr +} + +func setCommState(h syscall.Handle, baud int, databits byte, parity Parity, stopbits StopBits) error { + var params structDCB + params.DCBlength = uint32(unsafe.Sizeof(params)) + + params.flags[0] = 0x01 // fBinary + params.flags[0] |= 0x10 // Assert DSR + + params.BaudRate = uint32(baud) + + params.ByteSize = databits + + switch parity { + case ParityNone: + params.Parity = 0 + case ParityOdd: + params.Parity = 1 + case ParityEven: + params.Parity = 2 + case ParityMark: + params.Parity = 3 + case ParitySpace: + params.Parity = 4 + default: + return ErrBadParity + } + + switch stopbits { + case Stop1: + params.StopBits = 0 + case Stop1Half: + params.StopBits = 1 + case Stop2: + params.StopBits = 2 + default: + return ErrBadStopBits + } + + r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(¶ms)), 0) + if r == 0 { + return err + } + return nil +} + +func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error { + var timeouts structTimeouts + const MAXDWORD = 1<<32 - 1 + + // blocking read by default + var timeoutMs int64 = MAXDWORD - 1 + + if readTimeout > 0 { + // non-blocking read + timeoutMs = readTimeout.Nanoseconds() / 1e6 + if timeoutMs < 1 { + timeoutMs = 1 + } else if timeoutMs > MAXDWORD-1 { + timeoutMs = MAXDWORD - 1 + } + } + + /* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx + + For blocking I/O see below: + + Remarks: + + If an application sets ReadIntervalTimeout and + ReadTotalTimeoutMultiplier to MAXDWORD and sets + ReadTotalTimeoutConstant to a value greater than zero and + less than MAXDWORD, one of the following occurs when the + ReadFile function is called: + + If there are any bytes in the input buffer, ReadFile returns + immediately with the bytes in the buffer. + + If there are no bytes in the input buffer, ReadFile waits + until a byte arrives and then returns immediately. + + If no bytes arrive within the time specified by + ReadTotalTimeoutConstant, ReadFile times out. + */ + + timeouts.ReadIntervalTimeout = MAXDWORD + timeouts.ReadTotalTimeoutMultiplier = MAXDWORD + timeouts.ReadTotalTimeoutConstant = uint32(timeoutMs) + + r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0) + if r == 0 { + return err + } + return nil +} + +func setupComm(h syscall.Handle, in, out int) error { + r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out)) + if r == 0 { + return err + } + return nil +} + +func setCommMask(h syscall.Handle) error { + const EV_RXCHAR = 0x0001 + r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0) + if r == 0 { + return err + } + return nil +} + +func resetEvent(h syscall.Handle) error { + r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0) + if r == 0 { + return err + } + return nil +} + +func purgeComm(h syscall.Handle) error { + const PURGE_TXABORT = 0x0001 + const PURGE_RXABORT = 0x0002 + const PURGE_TXCLEAR = 0x0004 + const PURGE_RXCLEAR = 0x0008 + r, _, err := syscall.Syscall(nPurgeComm, 2, uintptr(h), + PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR, 0) + if r == 0 { + return err + } + return nil +} + +func newOverlapped() (*syscall.Overlapped, error) { + var overlapped syscall.Overlapped + r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0) + if r == 0 { + return nil, err + } + overlapped.HEvent = syscall.Handle(r) + return &overlapped, nil +} + +func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) { + var n int + r, _, err := syscall.Syscall6(nGetOverlappedResult, 4, + uintptr(h), + uintptr(unsafe.Pointer(overlapped)), + uintptr(unsafe.Pointer(&n)), 1, 0, 0) + if r == 0 { + return n, err + } + + return n, nil +} diff --git a/internal/util/serial/util.go b/internal/util/serial/util.go new file mode 100644 index 0000000..743cc34 --- /dev/null +++ b/internal/util/serial/util.go @@ -0,0 +1,66 @@ +package serial + +import ( + "strconv" + "strings" +) + +const ( + DefaultPort = "COM1" + DefaultBaudRate = 9600 +) + +// COM1,9600,odd +func ParseConfigFromAddr(addr string) *Config { + cfg := &Config{ + Name: DefaultPort, + Baud: DefaultBaudRate, + } + ss := strings.Split(addr, ",") + switch len(ss) { + case 1: + cfg.Name = ss[0] + case 2: + cfg.Name = ss[0] + cfg.Baud, _ = strconv.Atoi(ss[1]) + case 3: + cfg.Name = ss[0] + cfg.Baud, _ = strconv.Atoi(ss[1]) + cfg.Parity = parseParity(ss[2]) + } + return cfg +} + +func AddrFromConfig(cfg *Config) string { + ss := []string{ + cfg.Name, + strconv.Itoa(cfg.Baud), + } + + switch cfg.Parity { + case ParityEven: + ss = append(ss, "even") + case ParityOdd: + ss = append(ss, "odd") + case ParityMark: + ss = append(ss, "mark") + case ParitySpace: + ss = append(ss, "space") + } + return strings.Join(ss, ",") +} + +func parseParity(s string) Parity { + switch strings.ToLower(s) { + case "o", "odd": + return ParityOdd + case "e", "even": + return ParityEven + case "m", "mark": + return ParityMark + case "s", "space": + return ParitySpace + default: + return ParityNone + } +} diff --git a/listener/serial/listener.go b/listener/serial/listener.go index a378bc2..b4b5540 100644 --- a/listener/serial/listener.go +++ b/listener/serial/listener.go @@ -8,11 +8,10 @@ import ( "github.com/go-gost/core/listener" "github.com/go-gost/core/logger" md "github.com/go-gost/core/metadata" - serial_util "github.com/go-gost/x/internal/util/serial" + serial "github.com/go-gost/x/internal/util/serial" limiter "github.com/go-gost/x/limiter/traffic/wrapper" metrics "github.com/go-gost/x/metrics/wrapper" "github.com/go-gost/x/registry" - goserial "github.com/tarm/serial" ) func init() { @@ -35,7 +34,7 @@ func NewListener(opts ...listener.Option) listener.Listener { } if options.Addr == "" { - options.Addr = serial_util.DefaultPort + options.Addr = serial.DefaultPort } return &serialListener{ @@ -49,7 +48,7 @@ func (l *serialListener) Init(md md.Metadata) (err error) { return } - l.addr = &serial_util.Addr{Port: l.options.Addr} + l.addr = &serial.Addr{Port: l.options.Addr} l.cqueue = make(chan net.Conn) l.closed = make(chan struct{}) @@ -87,14 +86,14 @@ func (l *serialListener) listenLoop() { for { ctx, cancel := context.WithCancel(context.Background()) err := func() error { - cfg := serial_util.ParseConfigFromAddr(l.options.Addr) + cfg := serial.ParseConfigFromAddr(l.options.Addr) cfg.ReadTimeout = l.md.timeout - port, err := goserial.OpenPort(cfg) + port, err := serial.OpenPort(cfg) if err != nil { return err } - c := serial_util.NewConn(port, l.addr, cancel) + c := serial.NewConn(port, l.addr, cancel) c = metrics.WrapConn(l.options.Service, c) c = limiter.WrapConn(l.options.TrafficLimiter, c)