├── .buildkite └── pipeline.yaml ├── .gitignore ├── .goreleaser.yaml ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── api.go ├── ascii_over_tcp_client.go ├── asciiclient.go ├── asciiclient_test.go ├── client.go ├── cmd └── modbus-cli │ ├── log.go │ ├── main.go │ └── main_test.go ├── crc.go ├── crc_test.go ├── go.mod ├── go.sum ├── lrc.go ├── lrc_test.go ├── modbus.go ├── rtu_over_tcp_client.go ├── rtu_over_udp_client.go ├── rtuclient.go ├── rtuclient_prop_test.go ├── rtuclient_test.go ├── serial.go ├── serial_test.go ├── tcpclient.go ├── tcpclient_prop_test.go ├── tcpclient_test.go ├── test ├── README.md ├── asciiclient_test.go ├── client.go ├── common.go ├── commw32 │ ├── commw32.c │ └── commw32.go ├── rtu_over_tcp_client_test.go ├── rtuclient_test.go └── tcpclient_test.go └── vendor ├── github.com ├── google │ └── go-cmp │ │ ├── LICENSE │ │ └── cmp │ │ ├── compare.go │ │ ├── export.go │ │ ├── internal │ │ ├── diff │ │ │ ├── debug_disable.go │ │ │ ├── debug_enable.go │ │ │ └── diff.go │ │ ├── flags │ │ │ └── flags.go │ │ ├── function │ │ │ └── func.go │ │ └── value │ │ │ ├── name.go │ │ │ ├── pointer.go │ │ │ └── sort.go │ │ ├── options.go │ │ ├── path.go │ │ ├── report.go │ │ ├── report_compare.go │ │ ├── report_references.go │ │ ├── report_reflect.go │ │ ├── report_slices.go │ │ ├── report_text.go │ │ └── report_value.go └── grid-x │ └── serial │ ├── .gitattributes │ ├── .travis.yml │ ├── LICENSE │ ├── README.md │ ├── generate.bat │ ├── serial.go │ ├── serial_bsd.go │ ├── serial_darwin.go │ ├── serial_linux.go │ ├── serial_openbsd.go │ ├── serial_posix.go │ ├── serial_windows.go │ ├── termios_bsd.go │ ├── termios_darwin.go │ ├── termios_linux.go │ ├── termios_mipsx.go │ ├── termios_openbsd.go │ ├── zsyscall_windows.go │ └── ztypes_windows.go ├── modules.txt └── pgregory.net └── rapid ├── .editorconfig ├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── collections.go ├── combinators.go ├── data.go ├── doc.go ├── engine.go ├── floats.go ├── generator.go ├── integers.go ├── make.go ├── persist.go ├── shrink.go ├── statemachine.go ├── strings.go ├── utils.go └── vis.go /.buildkite/pipeline.yaml: -------------------------------------------------------------------------------- 1 | steps: 2 | - command: 3 | - "make ci_test" 4 | label: ":llama: Tests" 5 | agents: 6 | - "queue=default" 7 | - command: 8 | - "make ci_build" 9 | label: ":golang: Build" 10 | agents: 11 | - "queue=default" 12 | - command: 13 | - "make ci_lint" 14 | label: ":llama: Linting" 15 | agents: 16 | - "queue=default" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/** 2 | 3 | dist/ 4 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | builds: 3 | - main: ./cmd/modbus-cli 4 | env: 5 | - CGO_ENABLED=0 6 | goos: 7 | - linux 8 | - windows 9 | - darwin 10 | goarch: 11 | - amd64 12 | - arm64 13 | - arm 14 | goarm: 15 | - 7 16 | # Ignore ARM32 build for both macOS and Windows 17 | ignore: 18 | - goos: darwin 19 | goarch: arm 20 | - goos: windows 21 | goarch: arm 22 | archives: 23 | - id: modbus-cli 24 | name_template: >- 25 | modbus-cli_ 26 | {{- if eq .Os "darwin" }}mac 27 | {{- else }}{{ .Os }}{{ end }}_{{ .Arch }} 28 | format_overrides: 29 | - goos: windows 30 | formats: [ 'zip' ] 31 | checksum: 32 | name_template: 'checksums.txt' 33 | snapshot: 34 | version_template: "{{ incpatch .Version }}-next" 35 | changelog: 36 | sort: asc 37 | filters: 38 | exclude: 39 | - '^docs:' 40 | - '^test:' 41 | 42 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - tip 8 | 9 | script: 10 | - go test -v -bench . -benchmem 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Quoc-Viet Nguyen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the names of the copyright holders nor the names of any 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO_FILES := $(shell find . -type f -name "*.go") 2 | GO_BUILD := CGO_ENABLED=0 go build -ldflags "-w -s" 3 | GO_TOOLS := public.ecr.aws/gridx/base-images:modbus-dev-1.21.latest 4 | DOCKER_RUN := docker run --init --rm -v $$PWD:/go/src/github.com/grid-x/modbus -w /go/src/github.com/grid-x/modbus 5 | GO_RUN := ${DOCKER_RUN} ${GO_TOOLS} bash -c 6 | 7 | BRANCH := $(shell echo ${BUILDKITE_BRANCH} | sed 's/\//_/g') 8 | 9 | all: bin/ 10 | 11 | .PHONY: test 12 | test: 13 | diagslave -m tcp -p 5020 & diagslave -m enc -p 5021 & go test -run TCP -v $(shell glide nv) 14 | socat -d -d pty,raw,echo=0 pty,raw,echo=0 & diagslave -m rtu /dev/pts/1 & go test -run RTU -v $(shell glide nv) 15 | socat -d -d pty,raw,echo=0 pty,raw,echo=0 & diagslave -m ascii /dev/pts/3 & go test -run ASCII -v $(shell glide nv) 16 | go test -v -count=1 github.com/grid-x/modbus/cmd/modbus-cli 17 | 18 | .PHONY: lint 19 | lint: 20 | golint -set_exit_status 21 | 22 | .PHONY: build 23 | build: 24 | go build 25 | 26 | release: 27 | goreleaser release --skip-publish --skip-validate --clean 28 | 29 | ci_test: 30 | ${GO_RUN} "make test" 31 | 32 | ci_lint: 33 | ${GO_RUN} "make lint" 34 | 35 | ci_build: 36 | ${GO_RUN} "make build" 37 | 38 | ci_release: 39 | ${GO_RUN} "goreleaser release --skip-publish --skip-validate --rm-dist" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go modbus 2 | Fault-tolerant, fail-fast implementation of Modbus protocol in Go. 3 | 4 | # Supported functions 5 | 6 | Bit access: 7 | - Read Discrete Inputs 8 | - Read Coils 9 | - Write Single Coil 10 | - Write Multiple Coils 11 | 12 | 16-bit access: 13 | - Read Input Registers 14 | - Read Holding Registers 15 | - Write Single Register 16 | - Write Multiple Registers 17 | - Read/Write Multiple Registers 18 | - Mask Write Register 19 | - Read FIFO Queue 20 | 21 | # Supported formats 22 | - TCP 23 | - Serial (RTU, ASCII) 24 | - UDP 25 | 26 | # Usage 27 | Basic usage: 28 | ```go 29 | // Modbus TCP 30 | client := modbus.TCPClient("localhost:502") 31 | // Read input register 9 32 | results, err := client.ReadInputRegisters(context.Background(), 8, 1) 33 | 34 | // Modbus RTU/ASCII 35 | // Default configuration is 19200, 8, 1, even 36 | client = modbus.RTUClient("/dev/ttyS0") 37 | results, err = client.ReadCoils(context.Background(), 2, 1) 38 | ``` 39 | 40 | Advanced usage: 41 | ```go 42 | // Modbus TCP 43 | handler := modbus.NewTCPClientHandler("localhost:502") 44 | handler.Timeout = 10 * time.Second 45 | handler.SlaveID = 0xFF 46 | handler.Logger = log.New(os.Stdout, "test: ", log.LstdFlags) 47 | ctx := context.Background() 48 | // Connect manually so that multiple requests are handled in one connection session 49 | err := handler.Connect(ctx) 50 | defer handler.Close() 51 | 52 | client := modbus.NewClient(handler) 53 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 54 | results, err = client.WriteMultipleRegisters(ctx, 1, 2, []byte{0, 3, 0, 4}) 55 | results, err = client.WriteMultipleCoils(ctx, 5, 10, []byte{4, 3}) 56 | ``` 57 | 58 | ```go 59 | // Modbus RTU/ASCII 60 | handler := modbus.NewRTUClientHandler("/dev/ttyUSB0") 61 | handler.BaudRate = 115200 62 | handler.DataBits = 8 63 | handler.Parity = "N" 64 | handler.StopBits = 1 65 | handler.SlaveID = 1 66 | handler.Timeout = 5 * time.Second 67 | 68 | ctx := context.Background() 69 | err := handler.Connect(ctx) 70 | defer handler.Close() 71 | 72 | client := modbus.NewClient(handler) 73 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 74 | ``` 75 | 76 | # Modbus-CLI 77 | 78 | We offer a CLI tool to read/write registers. 79 | 80 | ## Usage 81 | 82 | For simplicity, the following examples are all using Modbus TCP. 83 | For Modbus RTU, replace the address field and use the `rtu-` arguments in order to use different baudrates, databits, etc. 84 | ```sh 85 | ./modbus-cli -address=rtu:///dev/ttyUSB0 -rtu-baudrate=57600 -rtu-stopbits=2 -rtu-parity=N -rtu-databits=8 ... 86 | ``` 87 | 88 | For Modbus UDP, replace the address field with a UDP address. 89 | ```sh 90 | ./modbus-cli --address=udp://127.0.0.1:502 ... 91 | ``` 92 | 93 | ### Reading Registers 94 | 95 | Read 1 register and get raw result 96 | ```sh 97 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=1 -type-parse=raw -register=42 98 | ``` 99 | 100 | Read 1 register and decode result as uint16 101 | ```sh 102 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=1 -type-parse=uint16 -register=42 103 | ``` 104 | 105 | Read 1 register and get all possible decoded results 106 | ```sh 107 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=1 -type-parse=all -register=42 108 | ``` 109 | 110 | Read 2 registers and decode result as uint32 111 | ```sh 112 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=2 -type-parse=uint32 -register=42 113 | ``` 114 | 115 | Read 2 registers and get all possible decoded results 116 | ```sh 117 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=2 -type-parse=all -register=42 118 | ``` 119 | 120 | Reading multiple registers is only possible in the raw format 121 | ```sh 122 | ./modbus-cli -address=tcp://127.0.0.1:502 -quantity=16 -type-parse=raw -register=42 123 | ``` 124 | 125 | ### Writing Registers 126 | 127 | Write 1 register 128 | ```sh 129 | ./modbus-cli -address=tcp://127.0.0.1:502 -fn-code=0x06 -type-exec=uint16 -register=42 -write-value=7 130 | ``` 131 | 132 | Write 2 registers 133 | ```sh 134 | ./modbus-cli -address=tcp://127.0.0.1:502 -fn-code=0x10 -type-exec=uint32 -register=42 -write-value=7 135 | ``` 136 | 137 | ## Release 138 | 139 | To release the Modbus-CLI tool, run either `make release` if you have installed `goreleaser` or `make ci_release`. 140 | The generated files can be found in the directory in the `dist` directory. 141 | 142 | Take the `.tar.gz` and `.zip` files and create a new GitHub release. 143 | 144 | # References 145 | - [Modbus Specifications and Implementation Guides](http://www.modbus.org/specs.php) 146 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import "context" 8 | 9 | // Client declares the functionality of a Modbus client regardless of the underlying transport stream. 10 | type Client interface { 11 | // Bit access 12 | 13 | // ReadCoils reads from 1 to 2000 contiguous status of coils in a 14 | // remote device and returns coil status. 15 | ReadCoils(ctx context.Context, address, quantity uint16) (results []byte, err error) 16 | // ReadDiscreteInputs reads from 1 to 2000 contiguous status of 17 | // discrete inputs in a remote device and returns input status. 18 | ReadDiscreteInputs(ctx context.Context, address, quantity uint16) (results []byte, err error) 19 | // WriteSingleCoil write a single output to either ON or OFF in a 20 | // remote device and returns output value. 21 | WriteSingleCoil(ctx context.Context, address, value uint16) (results []byte, err error) 22 | // WriteMultipleCoils forces each coil in a sequence of coils to either 23 | // ON or OFF in a remote device and returns quantity of outputs. 24 | WriteMultipleCoils(ctx context.Context, address, quantity uint16, value []byte) (results []byte, err error) 25 | 26 | // 16-bit access 27 | 28 | // ReadInputRegisters reads from 1 to 125 contiguous input registers in 29 | // a remote device and returns input registers. 30 | ReadInputRegisters(ctx context.Context, address, quantity uint16) (results []byte, err error) 31 | // ReadHoldingRegisters reads the contents of a contiguous block of 32 | // holding registers in a remote device and returns register value. 33 | ReadHoldingRegisters(ctx context.Context, address, quantity uint16) (results []byte, err error) 34 | // WriteSingleRegister writes a single holding register in a remote 35 | // device and returns register value. 36 | WriteSingleRegister(ctx context.Context, address, value uint16) (results []byte, err error) 37 | // WriteMultipleRegisters writes a block of contiguous registers 38 | // (1 to 123 registers) in a remote device and returns quantity of 39 | // registers. 40 | WriteMultipleRegisters(ctx context.Context, address, quantity uint16, value []byte) (results []byte, err error) 41 | // ReadWriteMultipleRegisters performs a combination of one read 42 | // operation and one write operation. It returns read registers value. 43 | ReadWriteMultipleRegisters(ctx context.Context, readAddress, readQuantity, writeAddress, writeQuantity uint16, value []byte) (results []byte, err error) 44 | // MaskWriteRegister modify the contents of a specified holding 45 | // register using a combination of an AND mask, an OR mask, and the 46 | // register's current contents. The function returns 47 | // AND-mask and OR-mask. 48 | MaskWriteRegister(ctx context.Context, address, andMask, orMask uint16) (results []byte, err error) 49 | //ReadFIFOQueue reads the contents of a First-In-First-Out (FIFO) queue 50 | // of register in a remote device and returns FIFO value register. 51 | ReadFIFOQueue(ctx context.Context, address uint16) (results []byte, err error) 52 | } 53 | -------------------------------------------------------------------------------- /ascii_over_tcp_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 xft. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "context" 9 | "time" 10 | ) 11 | 12 | // ASCIIOverTCPClientHandler implements Packager and Transporter interface. 13 | type ASCIIOverTCPClientHandler struct { 14 | asciiPackager 15 | asciiTCPTransporter 16 | } 17 | 18 | // NewASCIIOverTCPClientHandler allocates and initializes a ASCIIOverTCPClientHandler. 19 | func NewASCIIOverTCPClientHandler(address string) *ASCIIOverTCPClientHandler { 20 | handler := &ASCIIOverTCPClientHandler{} 21 | handler.Address = address 22 | handler.Timeout = tcpTimeout 23 | handler.IdleTimeout = tcpIdleTimeout 24 | handler.Dial = defaultDialFunc(handler.Timeout) 25 | return handler 26 | } 27 | 28 | // ASCIIOverTCPClient creates ASCII over TCP client with default handler and given connect string. 29 | func ASCIIOverTCPClient(address string) Client { 30 | handler := NewASCIIOverTCPClientHandler(address) 31 | return NewClient(handler) 32 | } 33 | 34 | // asciiTCPTransporter implements Transporter interface. 35 | type asciiTCPTransporter struct { 36 | tcpTransporter 37 | } 38 | 39 | func (mb *asciiTCPTransporter) Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) { 40 | mb.mu.Lock() 41 | defer mb.mu.Unlock() 42 | 43 | // Make sure port is connected 44 | if err = mb.connect(ctx); err != nil { 45 | return 46 | } 47 | // Start the timer to close when idle 48 | mb.lastActivity = time.Now() 49 | mb.startCloseTimer() 50 | 51 | // Set write and read timeout 52 | if mb.Timeout > 0 { 53 | if err = mb.conn.SetDeadline(mb.lastActivity.Add(mb.Timeout)); err != nil { 54 | return 55 | } 56 | } 57 | 58 | // Send the request 59 | mb.logf("modbus: send %q\n", aduRequest) 60 | if _, err = mb.conn.Write(aduRequest); err != nil { 61 | return 62 | } 63 | // Get the response 64 | var n, length int 65 | var data [asciiMaxSize]byte 66 | for { 67 | if n, err = mb.conn.Read(data[length:]); err != nil { 68 | return 69 | } 70 | length += n 71 | if length >= asciiMaxSize || n == 0 { 72 | break 73 | } 74 | // Expect end of frame in the data received 75 | if length > asciiMinSize { 76 | if string(data[length-len(asciiEnd):length]) == asciiEnd { 77 | break 78 | } 79 | } 80 | } 81 | aduResponse = data[:length] 82 | mb.logf("modbus: recv %q\n", aduResponse) 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /asciiclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/hex" 11 | "fmt" 12 | "time" 13 | ) 14 | 15 | const ( 16 | asciiEnd = "\r\n" 17 | asciiMinSize = 3 18 | asciiMaxSize = 513 19 | 20 | hexTable = "0123456789ABCDEF" 21 | ) 22 | 23 | // Modbus ASCII defines ':' but in the field often '>' is seen. 24 | var asciiStart = []string{":", ">"} 25 | 26 | // ASCIIClientHandler implements Packager and Transporter interface. 27 | type ASCIIClientHandler struct { 28 | asciiPackager 29 | asciiSerialTransporter 30 | } 31 | 32 | // NewASCIIClientHandler allocates and initializes a ASCIIClientHandler. 33 | func NewASCIIClientHandler(address string) *ASCIIClientHandler { 34 | handler := &ASCIIClientHandler{} 35 | handler.Address = address 36 | handler.Timeout = serialTimeout 37 | handler.IdleTimeout = serialIdleTimeout 38 | return handler 39 | } 40 | 41 | // ASCIIClient creates ASCII client with default handler and given connect string. 42 | func ASCIIClient(address string) Client { 43 | handler := NewASCIIClientHandler(address) 44 | return NewClient(handler) 45 | } 46 | 47 | // asciiPackager implements Packager interface. 48 | type asciiPackager struct { 49 | SlaveID byte 50 | } 51 | 52 | // SetSlave sets modbus slave id for the next client operations 53 | func (mb *asciiPackager) SetSlave(slaveID byte) { 54 | mb.SlaveID = slaveID 55 | } 56 | 57 | // Encode encodes PDU in an ASCII frame: 58 | // 59 | // Start : 1 char 60 | // Address : 2 chars 61 | // Function : 2 chars 62 | // Data : 0 up to 2x252 chars 63 | // LRC : 2 chars 64 | // End : 2 chars 65 | func (mb *asciiPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { 66 | var buf bytes.Buffer 67 | 68 | if _, err = buf.WriteString(asciiStart[0]); err != nil { 69 | return 70 | } 71 | if err = writeHex(&buf, []byte{mb.SlaveID, pdu.FunctionCode}); err != nil { 72 | return 73 | } 74 | if err = writeHex(&buf, pdu.Data); err != nil { 75 | return 76 | } 77 | // Exclude the beginning colon and terminating CRLF pair characters 78 | var lrc lrc 79 | lrc.reset() 80 | lrc.pushByte(mb.SlaveID).pushByte(pdu.FunctionCode).pushBytes(pdu.Data) 81 | if err = writeHex(&buf, []byte{lrc.value()}); err != nil { 82 | return 83 | } 84 | if _, err = buf.WriteString(asciiEnd); err != nil { 85 | return 86 | } 87 | adu = buf.Bytes() 88 | return 89 | } 90 | 91 | // Verify verifies response length, frame boundary and slave id. 92 | func (mb *asciiPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) { 93 | length := len(aduResponse) 94 | // Minimum size (including address, function and LRC) 95 | if length < asciiMinSize+6 { 96 | err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, 9) 97 | return 98 | } 99 | // Length excluding colon must be an even number 100 | if length%2 != 1 { 101 | err = fmt.Errorf("modbus: response length '%v' is not an even number", length-1) 102 | return 103 | } 104 | // First char must be a colon 105 | str := string(aduResponse[0:len(asciiStart[0])]) 106 | if !isStartCharacter(str) { 107 | err = fmt.Errorf("modbus: response frame '%v'... is not started with '%v'", str, asciiStart) 108 | return 109 | } 110 | // 2 last chars must be \r\n 111 | str = string(aduResponse[len(aduResponse)-len(asciiEnd):]) 112 | if str != asciiEnd { 113 | err = fmt.Errorf("modbus: response frame ...'%v' is not ended with '%v'", str, asciiEnd) 114 | return 115 | } 116 | // Slave id 117 | responseVal, err := readHex(aduResponse[1:]) 118 | if err != nil { 119 | return 120 | } 121 | requestVal, err := readHex(aduRequest[1:]) 122 | if err != nil { 123 | return 124 | } 125 | if responseVal != requestVal { 126 | err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", responseVal, requestVal) 127 | return 128 | } 129 | return 130 | } 131 | 132 | // Decode extracts PDU from ASCII frame and verify LRC. 133 | func (mb *asciiPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) { 134 | pdu = &ProtocolDataUnit{} 135 | // Slave address 136 | address, err := readHex(adu[1:]) 137 | if err != nil { 138 | return 139 | } 140 | // Function code 141 | if pdu.FunctionCode, err = readHex(adu[3:]); err != nil { 142 | return 143 | } 144 | // Data 145 | dataEnd := len(adu) - 4 146 | data := adu[5:dataEnd] 147 | pdu.Data = make([]byte, hex.DecodedLen(len(data))) 148 | if _, err = hex.Decode(pdu.Data, data); err != nil { 149 | return 150 | } 151 | // LRC 152 | lrcVal, err := readHex(adu[dataEnd:]) 153 | if err != nil { 154 | return 155 | } 156 | // Calculate checksum 157 | var lrc lrc 158 | lrc.reset() 159 | lrc.pushByte(address).pushByte(pdu.FunctionCode).pushBytes(pdu.Data) 160 | if lrcVal != lrc.value() { 161 | err = fmt.Errorf("modbus: response lrc '%v' does not match expected '%v'", lrcVal, lrc.value()) 162 | return 163 | } 164 | return 165 | } 166 | 167 | // asciiSerialTransporter implements Transporter interface. 168 | type asciiSerialTransporter struct { 169 | serialPort 170 | } 171 | 172 | func (mb *asciiSerialTransporter) Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) { 173 | mb.mu.Lock() 174 | defer mb.mu.Unlock() 175 | 176 | // Make sure port is connected 177 | if err = mb.connect(ctx); err != nil { 178 | return 179 | } 180 | // Start the timer to close when idle 181 | mb.lastActivity = time.Now() 182 | mb.startCloseTimer() 183 | 184 | // Send the request 185 | mb.logf("modbus: send % x\n", aduRequest) 186 | if _, err = mb.port.Write(aduRequest); err != nil { 187 | return 188 | } 189 | // Get the response 190 | var n, length int 191 | var data [asciiMaxSize]byte 192 | for { 193 | if n, err = mb.port.Read(data[length:]); err != nil { 194 | return 195 | } 196 | length += n 197 | if length >= asciiMaxSize || n == 0 { 198 | break 199 | } 200 | // Expect end of frame in the data received 201 | if length > asciiMinSize { 202 | if string(data[length-len(asciiEnd):length]) == asciiEnd { 203 | break 204 | } 205 | } 206 | } 207 | aduResponse = data[:length] 208 | mb.logf("modbus: recv % x\n", aduResponse) 209 | return 210 | } 211 | 212 | // writeHex encodes byte to string in hexadecimal, e.g. 0xA5 => "A5" 213 | // (encoding/hex only supports lowercase string). 214 | func writeHex(buf *bytes.Buffer, value []byte) (err error) { 215 | var str [2]byte 216 | for _, v := range value { 217 | str[0] = hexTable[v>>4] 218 | str[1] = hexTable[v&0x0F] 219 | 220 | if _, err = buf.Write(str[:]); err != nil { 221 | return 222 | } 223 | } 224 | return 225 | } 226 | 227 | // readHex decodes hex string to byte, e.g. "8C" => 0x8C. 228 | func readHex(data []byte) (value byte, err error) { 229 | var dst [1]byte 230 | if _, err = hex.Decode(dst[:], data[0:2]); err != nil { 231 | return 232 | } 233 | value = dst[0] 234 | return 235 | } 236 | 237 | // isStartCharacter confirms that the given character is a Modbus ASCII start character. 238 | func isStartCharacter(str string) bool { 239 | for i := range asciiStart { 240 | if str == asciiStart[i] { 241 | return true 242 | } 243 | } 244 | return false 245 | } 246 | -------------------------------------------------------------------------------- /asciiclient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "bytes" 9 | "testing" 10 | ) 11 | 12 | func TestASCIIEncoding(t *testing.T) { 13 | encoder := asciiPackager{} 14 | encoder.SlaveID = 17 15 | 16 | pdu := ProtocolDataUnit{} 17 | pdu.FunctionCode = 3 18 | pdu.Data = []byte{0, 107, 0, 3} 19 | 20 | adu, err := encoder.Encode(&pdu) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | expected := []byte(":1103006B00037E\r\n") 25 | if !bytes.Equal(expected, adu) { 26 | t.Fatalf("adu actual: %v, expected %v", adu, expected) 27 | } 28 | } 29 | 30 | func TestASCIIDecoding(t *testing.T) { 31 | decoder := asciiPackager{} 32 | decoder.SlaveID = 247 33 | adu := []byte(":F7031389000A60\r\n") 34 | 35 | pdu, err := decoder.Decode(adu) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | if pdu.FunctionCode != 3 { 41 | t.Fatalf("Function code: expected %v, actual %v", 15, pdu.FunctionCode) 42 | } 43 | expected := []byte{0x13, 0x89, 0, 0x0A} 44 | if !bytes.Equal(expected, pdu.Data) { 45 | t.Fatalf("Data: expected %v, actual %v", expected, pdu.Data) 46 | } 47 | } 48 | 49 | func TestASCIIDecodeStartCharacter(t *testing.T) { 50 | decoder := asciiPackager{} 51 | aduReq := []byte(":010300010002F9\r\n") 52 | aduRespGreaterThan := []byte(">010304010F1509CA\r\n") 53 | aduRespColon := []byte(":010304010F1509CA\r\n") 54 | aduRespFail := []byte("!010304010F1509CA\r\n") 55 | 56 | // Modbus ASCII conform. 57 | if err := decoder.Verify(aduReq, aduRespColon); err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | // Not Modbus ASCII conform but common in the field. 62 | if err := decoder.Verify(aduReq, aduRespGreaterThan); err != nil { 63 | t.Fatal(err) 64 | } 65 | 66 | if err := decoder.Verify(aduReq, aduRespFail); err == nil { 67 | t.Fatalf("expected '%s' to fail but does not", aduRespFail) 68 | } 69 | } 70 | 71 | func BenchmarkASCIIEncoder(b *testing.B) { 72 | encoder := asciiPackager{ 73 | SlaveID: 10, 74 | } 75 | pdu := ProtocolDataUnit{ 76 | FunctionCode: 1, 77 | Data: []byte{2, 3, 4, 5, 6, 7, 8, 9}, 78 | } 79 | for i := 0; i < b.N; i++ { 80 | _, err := encoder.Encode(&pdu) 81 | if err != nil { 82 | b.Fatal(err) 83 | } 84 | } 85 | } 86 | 87 | func BenchmarkASCIIDecoder(b *testing.B) { 88 | decoder := asciiPackager{ 89 | SlaveID: 10, 90 | } 91 | adu := []byte(":F7031389000A60\r\n") 92 | for i := 0; i < b.N; i++ { 93 | _, err := decoder.Decode(adu) 94 | if err != nil { 95 | b.Fatal(err) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /cmd/modbus-cli/log.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "log/slog" 4 | 5 | type debugAdapter struct { 6 | *slog.Logger 7 | } 8 | 9 | func (log *debugAdapter) Printf(msg string, args ...any) { 10 | log.Logger.Debug(msg, args...) 11 | } 12 | -------------------------------------------------------------------------------- /crc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | // Table of CRC values for high–order byte 8 | var crcHighBytes = []byte{ 9 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 10 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 11 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 12 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 13 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 14 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 15 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 16 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 17 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 18 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 19 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 20 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 21 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 22 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 23 | 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 24 | 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 25 | } 26 | 27 | // Table of CRC values for low-order byte 28 | var crcLowBytes = []byte{ 29 | 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 30 | 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 31 | 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 32 | 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 33 | 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 34 | 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 35 | 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 36 | 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 37 | 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 38 | 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 39 | 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 40 | 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 41 | 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 42 | 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 43 | 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 44 | 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40, 45 | } 46 | 47 | // Cyclical Redundancy Checking 48 | type crc struct { 49 | high byte 50 | low byte 51 | } 52 | 53 | func (crc *crc) reset() *crc { 54 | crc.high = 0xFF 55 | crc.low = 0xFF 56 | return crc 57 | } 58 | 59 | func (crc *crc) pushBytes(bs []byte) *crc { 60 | var idx, b byte 61 | 62 | for _, b = range bs { 63 | idx = crc.low ^ b 64 | crc.low = crc.high ^ crcHighBytes[idx] 65 | crc.high = crcLowBytes[idx] 66 | } 67 | return crc 68 | } 69 | 70 | func (crc *crc) value() uint16 { 71 | return uint16(crc.high)<<8 | uint16(crc.low) 72 | } 73 | -------------------------------------------------------------------------------- /crc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestCRC(t *testing.T) { 12 | var crc crc 13 | crc.reset() 14 | crc.pushBytes([]byte{0x02, 0x07}) 15 | 16 | if crc.value() != 0x1241 { 17 | t.Fatalf("crc expected %v, actual %v", 0x1241, crc.value()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/grid-x/modbus 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/google/go-cmp v0.6.0 7 | github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa 8 | pgregory.net/rapid v1.1.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzhkE4vQr5Gbx2LvtEMv4BJOK4LyU= 4 | github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk= 5 | pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= 6 | pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= 7 | -------------------------------------------------------------------------------- /lrc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | // Longitudinal Redundancy Checking 8 | type lrc struct { 9 | sum uint8 10 | } 11 | 12 | func (lrc *lrc) reset() *lrc { 13 | lrc.sum = 0 14 | return lrc 15 | } 16 | 17 | func (lrc *lrc) pushByte(b byte) *lrc { 18 | lrc.sum += b 19 | return lrc 20 | } 21 | 22 | func (lrc *lrc) pushBytes(data []byte) *lrc { 23 | var b byte 24 | for _, b = range data { 25 | lrc.sum += b 26 | } 27 | return lrc 28 | } 29 | 30 | func (lrc *lrc) value() byte { 31 | // Return twos complement 32 | return uint8(-int8(lrc.sum)) 33 | } 34 | -------------------------------------------------------------------------------- /lrc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "testing" 9 | ) 10 | 11 | func TestLRC(t *testing.T) { 12 | var lrc lrc 13 | lrc.reset().pushByte(0x01).pushByte(0x03) 14 | lrc.pushBytes([]byte{0x01, 0x0A}) 15 | 16 | if lrc.value() != 0xF1 { 17 | t.Fatalf("lrc expected %v, actual %v", 0xF1, lrc.value()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modbus.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | /* 6 | Package modbus provides a client for MODBUS TCP and RTU/ASCII. 7 | */ 8 | package modbus 9 | 10 | import ( 11 | "context" 12 | "fmt" 13 | ) 14 | 15 | const ( 16 | // FuncCodeReadDiscreteInputs for bit wise access 17 | FuncCodeReadDiscreteInputs = 2 18 | // FuncCodeReadCoils for bit wise access 19 | FuncCodeReadCoils = 1 20 | // FuncCodeWriteSingleCoil for bit wise access 21 | FuncCodeWriteSingleCoil = 5 22 | // FuncCodeWriteMultipleCoils for bit wise access 23 | FuncCodeWriteMultipleCoils = 15 24 | 25 | // FuncCodeReadInputRegisters 16-bit wise access 26 | FuncCodeReadInputRegisters = 4 27 | // FuncCodeReadHoldingRegisters 16-bit wise access 28 | FuncCodeReadHoldingRegisters = 3 29 | // FuncCodeWriteSingleRegister 16-bit wise access 30 | FuncCodeWriteSingleRegister = 6 31 | // FuncCodeWriteMultipleRegisters 16-bit wise access 32 | FuncCodeWriteMultipleRegisters = 16 33 | // FuncCodeReadWriteMultipleRegisters 16-bit wise access 34 | FuncCodeReadWriteMultipleRegisters = 23 35 | // FuncCodeMaskWriteRegister 16-bit wise access 36 | FuncCodeMaskWriteRegister = 22 37 | // FuncCodeReadFIFOQueue 16-bit wise access 38 | FuncCodeReadFIFOQueue = 24 39 | ) 40 | 41 | const ( 42 | // ExceptionCodeIllegalFunction error code 43 | ExceptionCodeIllegalFunction = 1 44 | // ExceptionCodeIllegalDataAddress error code 45 | ExceptionCodeIllegalDataAddress = 2 46 | // ExceptionCodeIllegalDataValue error code 47 | ExceptionCodeIllegalDataValue = 3 48 | // ExceptionCodeServerDeviceFailure error code 49 | ExceptionCodeServerDeviceFailure = 4 50 | // ExceptionCodeAcknowledge error code 51 | ExceptionCodeAcknowledge = 5 52 | // ExceptionCodeServerDeviceBusy error code 53 | ExceptionCodeServerDeviceBusy = 6 54 | // ExceptionCodeMemoryParityError error code 55 | ExceptionCodeMemoryParityError = 8 56 | // ExceptionCodeGatewayPathUnavailable error code 57 | ExceptionCodeGatewayPathUnavailable = 10 58 | // ExceptionCodeGatewayTargetDeviceFailedToRespond error code 59 | ExceptionCodeGatewayTargetDeviceFailedToRespond = 11 60 | ) 61 | 62 | // Error implements error interface. 63 | type Error struct { 64 | FunctionCode byte 65 | ExceptionCode byte 66 | } 67 | 68 | // Error converts known modbus exception code to error message. 69 | func (e *Error) Error() string { 70 | var name string 71 | switch e.ExceptionCode { 72 | case ExceptionCodeIllegalFunction: 73 | name = "illegal function" 74 | case ExceptionCodeIllegalDataAddress: 75 | name = "illegal data address" 76 | case ExceptionCodeIllegalDataValue: 77 | name = "illegal data value" 78 | case ExceptionCodeServerDeviceFailure: 79 | name = "server device failure" 80 | case ExceptionCodeAcknowledge: 81 | name = "acknowledge" 82 | case ExceptionCodeServerDeviceBusy: 83 | name = "server device busy" 84 | case ExceptionCodeMemoryParityError: 85 | name = "memory parity error" 86 | case ExceptionCodeGatewayPathUnavailable: 87 | name = "gateway path unavailable" 88 | case ExceptionCodeGatewayTargetDeviceFailedToRespond: 89 | name = "gateway target device failed to respond" 90 | default: 91 | name = "unknown" 92 | } 93 | return fmt.Sprintf("modbus: exception '%v' (%s), function '%v'", e.ExceptionCode, name, e.FunctionCode&0x7F) 94 | } 95 | 96 | // ProtocolDataUnit (PDU) is independent of underlying communication layers. 97 | type ProtocolDataUnit struct { 98 | FunctionCode byte 99 | Data []byte 100 | } 101 | 102 | // Packager specifies the communication layer. 103 | type Packager interface { 104 | SetSlave(slaveID byte) 105 | Encode(pdu *ProtocolDataUnit) (adu []byte, err error) 106 | Decode(adu []byte) (pdu *ProtocolDataUnit, err error) 107 | Verify(aduRequest []byte, aduResponse []byte) (err error) 108 | } 109 | 110 | // Transporter specifies the transport layer. 111 | type Transporter interface { 112 | Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) 113 | } 114 | 115 | // Connector exposes the underlying handler capability for open/connect and close the transport channel. 116 | type Connector interface { 117 | Connect(ctx context.Context) error 118 | Close() error 119 | } 120 | -------------------------------------------------------------------------------- /rtu_over_tcp_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 xft. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "context" 9 | "io" 10 | "time" 11 | ) 12 | 13 | // RTUOverTCPClientHandler implements Packager and Transporter interface. 14 | type RTUOverTCPClientHandler struct { 15 | rtuPackager 16 | rtuTCPTransporter 17 | } 18 | 19 | // NewRTUOverTCPClientHandler allocates and initializes a RTUOverTCPClientHandler. 20 | func NewRTUOverTCPClientHandler(address string) *RTUOverTCPClientHandler { 21 | handler := &RTUOverTCPClientHandler{} 22 | handler.Address = address 23 | handler.Timeout = tcpTimeout 24 | handler.IdleTimeout = tcpIdleTimeout 25 | handler.Dial = defaultDialFunc(handler.Timeout) 26 | return handler 27 | } 28 | 29 | // RTUOverTCPClient creates RTU over TCP client with default handler and given connect string. 30 | func RTUOverTCPClient(address string) Client { 31 | handler := NewRTUOverTCPClientHandler(address) 32 | return NewClient(handler) 33 | } 34 | 35 | // rtuTCPTransporter implements Transporter interface. 36 | type rtuTCPTransporter struct { 37 | tcpTransporter 38 | } 39 | 40 | // Send sends data to server and ensures adequate response for request type 41 | func (mb *rtuTCPTransporter) Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) { 42 | mb.mu.Lock() 43 | defer mb.mu.Unlock() 44 | 45 | // Establish a new connection if not connected 46 | if err = mb.connect(ctx); err != nil { 47 | return 48 | } 49 | // Set timer to close when idle 50 | mb.lastActivity = time.Now() 51 | mb.startCloseTimer() 52 | 53 | // Set write and read timeout 54 | if mb.Timeout > 0 { 55 | if err = mb.conn.SetDeadline(mb.lastActivity.Add(mb.Timeout)); err != nil { 56 | return 57 | } 58 | } 59 | 60 | // Send the request 61 | mb.logf("modbus: send % x\n", aduRequest) 62 | if _, err = mb.conn.Write(aduRequest); err != nil { 63 | return 64 | } 65 | function := aduRequest[1] 66 | functionFail := aduRequest[1] & 0x80 67 | bytesToRead := calculateResponseLength(aduRequest) 68 | 69 | var n, n1 int 70 | var data [rtuMaxSize]byte 71 | // We first read the minimum length and then read either the full package 72 | // or the error package, depending on the error status (byte 2 of the response) 73 | n, err = io.ReadAtLeast(mb.conn, data[:], rtuMinSize) 74 | if err != nil { 75 | return 76 | } 77 | // if the function is correct 78 | if data[1] == function { 79 | // we read the rest of the bytes 80 | if n < bytesToRead { 81 | if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize { 82 | n1, err = io.ReadFull(mb.conn, data[n:bytesToRead]) 83 | n += n1 84 | } 85 | } 86 | } else if data[1] == functionFail { 87 | // for error we need to read 5 bytes 88 | if n < rtuExceptionSize { 89 | n1, err = io.ReadFull(mb.conn, data[n:rtuExceptionSize]) 90 | } 91 | n += n1 92 | } 93 | 94 | if err != nil { 95 | return 96 | } 97 | aduResponse = data[:n] 98 | mb.logf("modbus: recv % x\n", aduResponse) 99 | return 100 | } 101 | -------------------------------------------------------------------------------- /rtu_over_udp_client.go: -------------------------------------------------------------------------------- 1 | package modbus 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "net" 8 | "sync" 9 | ) 10 | 11 | // ErrADURequestLength informs about a wrong ADU request length. 12 | type ErrADURequestLength int 13 | 14 | func (length ErrADURequestLength) Error() string { 15 | return fmt.Sprintf("modbus: ADU request length '%d' must not be less than 2", length) 16 | } 17 | 18 | // ErrADUResponseLength informs about a wrong ADU request length. 19 | type ErrADUResponseLength int 20 | 21 | func (length ErrADUResponseLength) Error() string { 22 | return fmt.Sprintf("modbus: ADU response length '%d' must not be less than 2", length) 23 | } 24 | 25 | // RTUOverUDPClientHandler implements Packager and Transporter interface. 26 | type RTUOverUDPClientHandler struct { 27 | rtuPackager 28 | rtuUDPTransporter 29 | } 30 | 31 | // NewRTUOverUDPClientHandler allocates and initializes a RTUOverUDPClientHandler. 32 | func NewRTUOverUDPClientHandler(address string) *RTUOverUDPClientHandler { 33 | handler := &RTUOverUDPClientHandler{} 34 | handler.Address = address 35 | return handler 36 | } 37 | 38 | // RTUOverUDPClient creates RTU over UDP client with default handler and given connect string. 39 | func RTUOverUDPClient(address string) Client { 40 | handler := NewRTUOverUDPClientHandler(address) 41 | return NewClient(handler) 42 | } 43 | 44 | // rtuUDPTransporter implements Transporter interface. 45 | type rtuUDPTransporter struct { 46 | // Connect string 47 | Address string 48 | // Transmission logger 49 | Logger Logger 50 | 51 | // UDP connection 52 | mu sync.Mutex 53 | conn net.Conn 54 | } 55 | 56 | // Send sends data to server and ensures adequate response for request type 57 | func (mb *rtuUDPTransporter) Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) { 58 | mb.mu.Lock() 59 | defer mb.mu.Unlock() 60 | 61 | // Check ADU request length 62 | if len(aduRequest) < 2 { 63 | err = ErrADURequestLength(len(aduRequest)) 64 | return 65 | } 66 | 67 | // Establish a new connection if not connected 68 | if err = mb.connect(ctx); err != nil { 69 | return 70 | } 71 | 72 | // Send the request 73 | mb.logf("modbus: send % x\n", aduRequest) 74 | if _, err = mb.conn.Write(aduRequest); err != nil { 75 | return 76 | } 77 | function := aduRequest[1] 78 | functionFail := aduRequest[1] & 0x80 79 | bytesToRead := calculateResponseLength(aduRequest) 80 | 81 | var n, n1 int 82 | var data [rtuMaxSize]byte 83 | // We first read the minimum length and then read either the full package 84 | // or the error package, depending on the error status (byte 2 of the response) 85 | n, err = io.ReadAtLeast(mb.conn, data[:], rtuMinSize) 86 | if err != nil { 87 | return 88 | } 89 | 90 | // Check ADU response length 91 | if len(data) < 2 { 92 | err = ErrADUResponseLength(len(data)) 93 | return 94 | } 95 | 96 | // if the function is correct 97 | if data[1] == function { 98 | // we read the rest of the bytes 99 | if n < bytesToRead { 100 | if bytesToRead > rtuMinSize && bytesToRead <= rtuMaxSize { 101 | n1, err = io.ReadFull(mb.conn, data[n:bytesToRead]) 102 | n += n1 103 | } 104 | } 105 | } else if data[1] == functionFail { 106 | // for error we need to read 5 bytes 107 | if n < rtuExceptionSize { 108 | n1, err = io.ReadFull(mb.conn, data[n:rtuExceptionSize]) 109 | } 110 | n += n1 111 | } 112 | 113 | if err != nil { 114 | return 115 | } 116 | aduResponse = data[:n] 117 | mb.logf("modbus: recv % x\n", aduResponse) 118 | return 119 | } 120 | 121 | func (mb *rtuUDPTransporter) logf(format string, v ...interface{}) { 122 | if mb.Logger != nil { 123 | mb.Logger.Printf(format, v...) 124 | } 125 | } 126 | 127 | // Connect establishes a new connection to the address in Address. 128 | func (mb *rtuUDPTransporter) Connect(ctx context.Context) error { 129 | mb.mu.Lock() 130 | defer mb.mu.Unlock() 131 | 132 | return mb.connect(ctx) 133 | } 134 | 135 | // connect establishes a new connection to the address in Address. Caller must hold the mutex before calling this method. 136 | // Since UDP is connectionless this does little more than setting up the connection object. 137 | func (mb *rtuUDPTransporter) connect(ctx context.Context) error { 138 | select { 139 | case <-ctx.Done(): 140 | return ctx.Err() 141 | default: 142 | } 143 | if mb.conn == nil { 144 | dialer := net.Dialer{} 145 | conn, err := dialer.DialContext(ctx, "udp", mb.Address) 146 | if err != nil { 147 | return err 148 | } 149 | mb.conn = conn 150 | } 151 | return nil 152 | } 153 | 154 | // Close closes current connection. 155 | func (mb *rtuUDPTransporter) Close() error { 156 | mb.mu.Lock() 157 | defer mb.mu.Unlock() 158 | 159 | return mb.close() 160 | } 161 | 162 | // close closes current connection. Caller must hold the mutex before calling this method. 163 | // Since UDP is connectionless this does little more than freeing up the connection object. 164 | func (mb *rtuUDPTransporter) close() (err error) { 165 | if mb.conn != nil { 166 | err = mb.conn.Close() 167 | mb.conn = nil 168 | } 169 | return 170 | } 171 | -------------------------------------------------------------------------------- /rtuclient.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "context" 9 | "encoding/binary" 10 | "errors" 11 | "fmt" 12 | "io" 13 | "time" 14 | ) 15 | 16 | const ( 17 | rtuMinSize = 4 18 | rtuMaxSize = 256 19 | 20 | rtuExceptionSize = 5 21 | ) 22 | 23 | const ( 24 | stateSlaveID = 1 << iota 25 | stateFunctionCode 26 | stateReadLength 27 | stateReadPayload 28 | stateCRC 29 | ) 30 | 31 | const ( 32 | readCoilsFunctionCode = 0x01 33 | readDiscreteInputsFunctionCode = 0x02 34 | readHoldingRegisterFunctionCode = 0x03 35 | readInputRegisterFunctionCode = 0x04 36 | 37 | writeSingleCoilFunctionCode = 0x05 38 | writeSingleRegisterFunctionCode = 0x06 39 | writeMultipleRegisterFunctionCode = 0x10 40 | writeMultipleCoilsFunctionCode = 0x0F 41 | maskWriteRegisterFunctionCode = 0x16 42 | 43 | readWriteMultipleRegisterFunctionCode = 0x17 44 | readFifoQueueFunctionCode = 0x18 45 | ) 46 | 47 | // RTUClientHandler implements Packager and Transporter interface. 48 | type RTUClientHandler struct { 49 | rtuPackager 50 | rtuSerialTransporter 51 | } 52 | 53 | // NewRTUClientHandler allocates and initializes a RTUClientHandler. 54 | func NewRTUClientHandler(address string) *RTUClientHandler { 55 | handler := &RTUClientHandler{} 56 | handler.Address = address 57 | handler.Timeout = serialTimeout 58 | handler.IdleTimeout = serialIdleTimeout 59 | return handler 60 | } 61 | 62 | // RTUClient creates RTU client with default handler and given connect string. 63 | func RTUClient(address string) Client { 64 | handler := NewRTUClientHandler(address) 65 | return NewClient(handler) 66 | } 67 | 68 | // rtuPackager implements Packager interface. 69 | type rtuPackager struct { 70 | SlaveID byte 71 | } 72 | 73 | // SetSlave sets modbus slave id for the next client operations 74 | func (mb *rtuPackager) SetSlave(slaveID byte) { 75 | mb.SlaveID = slaveID 76 | } 77 | 78 | // Encode encodes PDU in an RTU frame: 79 | // 80 | // Slave Address : 1 byte 81 | // Function : 1 byte 82 | // Data : 0 up to 252 bytes 83 | // CRC : 2 byte 84 | func (mb *rtuPackager) Encode(pdu *ProtocolDataUnit) (adu []byte, err error) { 85 | length := len(pdu.Data) + 4 86 | if length > rtuMaxSize { 87 | err = fmt.Errorf("modbus: length of data '%v' must not be bigger than '%v'", length, rtuMaxSize) 88 | return 89 | } 90 | adu = make([]byte, length) 91 | 92 | adu[0] = mb.SlaveID 93 | adu[1] = pdu.FunctionCode 94 | copy(adu[2:], pdu.Data) 95 | 96 | // Append crc 97 | var crc crc 98 | crc.reset().pushBytes(adu[0 : length-2]) 99 | checksum := crc.value() 100 | 101 | adu[length-1] = byte(checksum >> 8) 102 | adu[length-2] = byte(checksum) 103 | return 104 | } 105 | 106 | // Verify verifies response length and slave id. 107 | func (mb *rtuPackager) Verify(aduRequest []byte, aduResponse []byte) (err error) { 108 | length := len(aduResponse) 109 | // Minimum size (including address, function and CRC) 110 | if length < rtuMinSize { 111 | err = fmt.Errorf("modbus: response length '%v' does not meet minimum '%v'", length, rtuMinSize) 112 | return 113 | } 114 | // Slave address must match 115 | if aduResponse[0] != aduRequest[0] { 116 | err = fmt.Errorf("modbus: response slave id '%v' does not match request '%v'", aduResponse[0], aduRequest[0]) 117 | return 118 | } 119 | return 120 | } 121 | 122 | // Decode extracts PDU from RTU frame and verify CRC. 123 | func (mb *rtuPackager) Decode(adu []byte) (pdu *ProtocolDataUnit, err error) { 124 | length := len(adu) 125 | // Calculate checksum 126 | var crc crc 127 | crc.reset().pushBytes(adu[0 : length-2]) 128 | checksum := uint16(adu[length-1])<<8 | uint16(adu[length-2]) 129 | if checksum != crc.value() { 130 | err = fmt.Errorf("modbus: response crc '%v' does not match expected '%v'", checksum, crc.value()) 131 | return 132 | } 133 | // Function code & data 134 | pdu = &ProtocolDataUnit{} 135 | pdu.FunctionCode = adu[1] 136 | pdu.Data = adu[2 : length-2] 137 | return 138 | } 139 | 140 | // rtuSerialTransporter implements Transporter interface. 141 | type rtuSerialTransporter struct { 142 | serialPort 143 | } 144 | 145 | // InvalidLengthError is returned by readIncrementally when the modbus response would overflow buffer 146 | // implemented to simplify testing 147 | type InvalidLengthError struct { 148 | length byte // length received which triggered the error 149 | } 150 | 151 | // Error implements the error interface 152 | func (e *InvalidLengthError) Error() string { 153 | return fmt.Sprintf("invalid length received: %d", e.length) 154 | } 155 | 156 | // readIncrementally reads incrementally 157 | func readIncrementally(slaveID, functionCode byte, r io.Reader, deadline time.Time) ([]byte, error) { 158 | if r == nil { 159 | return nil, fmt.Errorf("reader is nil") 160 | } 161 | 162 | buf := make([]byte, 1) 163 | data := make([]byte, rtuMaxSize) 164 | 165 | state := stateSlaveID 166 | var length, toRead byte 167 | var n, crcCount int 168 | 169 | for { 170 | if time.Now().After(deadline) { // Possible that serialport may spew data 171 | return nil, errors.New("failed to read from serial port within deadline") 172 | } 173 | 174 | if _, err := io.ReadAtLeast(r, buf, 1); err != nil { 175 | return nil, err 176 | } 177 | 178 | switch state { 179 | // expecting slaveID 180 | case stateSlaveID: 181 | // read slaveID 182 | if buf[0] == slaveID { 183 | state = stateFunctionCode 184 | data[n] = buf[0] 185 | n++ 186 | continue 187 | } 188 | case stateFunctionCode: 189 | // read function code 190 | if buf[0] == functionCode { 191 | switch functionCode { 192 | case readDiscreteInputsFunctionCode, 193 | readCoilsFunctionCode, 194 | readHoldingRegisterFunctionCode, 195 | readInputRegisterFunctionCode, 196 | readWriteMultipleRegisterFunctionCode, 197 | readFifoQueueFunctionCode: 198 | 199 | state = stateReadLength 200 | case writeSingleCoilFunctionCode, 201 | writeSingleRegisterFunctionCode, 202 | writeMultipleRegisterFunctionCode, 203 | writeMultipleCoilsFunctionCode: 204 | 205 | state = stateReadPayload 206 | toRead = 4 207 | case maskWriteRegisterFunctionCode: 208 | state = stateReadPayload 209 | toRead = 6 210 | default: 211 | return nil, fmt.Errorf("functioncode not handled: %d", functionCode) 212 | } 213 | data[n] = buf[0] 214 | n++ 215 | continue 216 | } else if buf[0] == functionCode+0x80 { 217 | state = stateReadPayload 218 | data[n] = buf[0] 219 | n++ 220 | // only exception code left to read 221 | toRead = 1 222 | } 223 | case stateReadLength: 224 | // read length byte 225 | length = buf[0] 226 | // max length = rtuMaxSize - SlaveID(1) - FunctionCode(1) - length(1) - CRC(2) 227 | if length > rtuMaxSize-5 || length == 0 { 228 | return nil, &InvalidLengthError{length: length} 229 | } 230 | 231 | toRead = length 232 | data[n] = length 233 | n++ 234 | state = stateReadPayload 235 | case stateReadPayload: 236 | // read payload 237 | data[n] = buf[0] 238 | toRead-- 239 | n++ 240 | if toRead == 0 { 241 | state = stateCRC 242 | } 243 | case stateCRC: 244 | // read crc 245 | data[n] = buf[0] 246 | crcCount++ 247 | n++ 248 | if crcCount == 2 { 249 | return data[:n], nil 250 | } 251 | } 252 | } 253 | } 254 | 255 | func (mb *rtuSerialTransporter) Send(ctx context.Context, aduRequest []byte) (aduResponse []byte, err error) { 256 | mb.mu.Lock() 257 | defer mb.mu.Unlock() 258 | 259 | // Make sure port is connected 260 | if err = mb.connect(ctx); err != nil { 261 | return 262 | } 263 | // Start the timer to close when idle 264 | mb.lastActivity = time.Now() 265 | mb.startCloseTimer() 266 | 267 | // Send the request 268 | mb.logf("modbus: send % x\n", aduRequest) 269 | if _, err = mb.port.Write(aduRequest); err != nil { 270 | return 271 | } 272 | // function := aduRequest[1] 273 | // functionFail := aduRequest[1] & 0x80 274 | bytesToRead := calculateResponseLength(aduRequest) 275 | select { 276 | case <-ctx.Done(): 277 | return nil, ctx.Err() 278 | case <-time.After(mb.calculateDelay(len(aduRequest) + bytesToRead)): 279 | } 280 | 281 | data, err := readIncrementally(aduRequest[0], aduRequest[1], mb.port, time.Now().Add(mb.Config.Timeout)) 282 | mb.logf("modbus: recv % x\n", data[:]) 283 | aduResponse = data 284 | return 285 | } 286 | 287 | // calculateDelay roughly calculates time needed for the next frame. 288 | // See MODBUS over Serial Line - Specification and Implementation Guide (page 13). 289 | func (mb *rtuSerialTransporter) calculateDelay(chars int) time.Duration { 290 | var characterDelay, frameDelay int // us 291 | 292 | if mb.BaudRate <= 0 || mb.BaudRate > 19200 { 293 | characterDelay = 750 294 | frameDelay = 1750 295 | } else { 296 | characterDelay = 15000000 / mb.BaudRate 297 | frameDelay = 35000000 / mb.BaudRate 298 | } 299 | return time.Duration(characterDelay*chars+frameDelay) * time.Microsecond 300 | } 301 | 302 | func calculateResponseLength(adu []byte) int { 303 | length := rtuMinSize 304 | switch adu[1] { 305 | case FuncCodeReadDiscreteInputs, 306 | FuncCodeReadCoils: 307 | count := int(binary.BigEndian.Uint16(adu[4:])) 308 | length += 1 + count/8 309 | if count%8 != 0 { 310 | length++ 311 | } 312 | case FuncCodeReadInputRegisters, 313 | FuncCodeReadHoldingRegisters, 314 | FuncCodeReadWriteMultipleRegisters: 315 | count := int(binary.BigEndian.Uint16(adu[4:])) 316 | length += 1 + count*2 317 | case FuncCodeWriteSingleCoil, 318 | FuncCodeWriteMultipleCoils, 319 | FuncCodeWriteSingleRegister, 320 | FuncCodeWriteMultipleRegisters: 321 | length += 4 322 | case FuncCodeMaskWriteRegister: 323 | length += 6 324 | case FuncCodeReadFIFOQueue: 325 | // undetermined 326 | default: 327 | } 328 | return length 329 | } 330 | -------------------------------------------------------------------------------- /rtuclient_prop_test.go: -------------------------------------------------------------------------------- 1 | package modbus 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "pgregory.net/rapid" 8 | ) 9 | 10 | func TestRTUEncodeDecode(t *testing.T) { 11 | rapid.Check(t, func(t *rapid.T) { 12 | packager := &rtuPackager{ 13 | SlaveID: rapid.Byte().Draw(t, "SlaveID"), 14 | } 15 | 16 | pdu := &ProtocolDataUnit{ 17 | FunctionCode: rapid.Byte().Draw(t, "FunctionCode"), 18 | Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data"), 19 | } 20 | 21 | raw, err := packager.Encode(pdu) 22 | if err != nil { 23 | t.Fatalf("error while encoding: %+v", err) 24 | } 25 | 26 | dpdu, err := packager.Decode(raw) 27 | if err != nil { 28 | t.Fatalf("error while decoding: %+v", err) 29 | } 30 | 31 | if !cmp.Equal(pdu, dpdu) { 32 | t.Errorf("invalid pdu: %s", cmp.Diff(pdu, dpdu)) 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /serial.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package modbus 6 | 7 | import ( 8 | "context" 9 | "fmt" 10 | "io" 11 | "sync" 12 | "time" 13 | 14 | "github.com/grid-x/serial" 15 | ) 16 | 17 | const ( 18 | // Default timeout 19 | serialTimeout = 5 * time.Second 20 | serialIdleTimeout = 60 * time.Second 21 | ) 22 | 23 | // serialPort has configuration and I/O controller. 24 | type serialPort struct { 25 | // Serial port configuration. 26 | serial.Config 27 | 28 | Logger Logger 29 | IdleTimeout time.Duration 30 | 31 | mu sync.Mutex 32 | // port is platform-dependent data structure for serial port. 33 | port io.ReadWriteCloser 34 | lastActivity time.Time 35 | closeTimer *time.Timer 36 | } 37 | 38 | func (mb *serialPort) Connect(ctx context.Context) (err error) { 39 | mb.mu.Lock() 40 | defer mb.mu.Unlock() 41 | 42 | return mb.connect(ctx) 43 | } 44 | 45 | // connect connects to the serial port if it is not connected. Caller must hold the mutex. 46 | func (mb *serialPort) connect(ctx context.Context) error { 47 | select { 48 | case <-ctx.Done(): 49 | return ctx.Err() 50 | default: 51 | } 52 | if mb.port == nil { 53 | port, err := serial.Open(&mb.Config) 54 | if err != nil { 55 | return fmt.Errorf("could not open %s: %w", mb.Config.Address, err) 56 | } 57 | mb.port = port 58 | } 59 | return nil 60 | } 61 | 62 | func (mb *serialPort) Close() (err error) { 63 | mb.mu.Lock() 64 | defer mb.mu.Unlock() 65 | 66 | return mb.close() 67 | } 68 | 69 | // close closes the serial port if it is connected. Caller must hold the mutex. 70 | func (mb *serialPort) close() (err error) { 71 | if mb.port != nil { 72 | err = mb.port.Close() 73 | mb.port = nil 74 | } 75 | return 76 | } 77 | 78 | func (mb *serialPort) logf(format string, v ...interface{}) { 79 | if mb.Logger != nil { 80 | mb.Logger.Printf(format, v...) 81 | } 82 | } 83 | 84 | func (mb *serialPort) startCloseTimer() { 85 | if mb.IdleTimeout <= 0 { 86 | return 87 | } 88 | if mb.closeTimer == nil { 89 | mb.closeTimer = time.AfterFunc(mb.IdleTimeout, mb.closeIdle) 90 | } else { 91 | mb.closeTimer.Reset(mb.IdleTimeout) 92 | } 93 | } 94 | 95 | // closeIdle closes the connection if last activity is passed behind IdleTimeout. 96 | func (mb *serialPort) closeIdle() { 97 | mb.mu.Lock() 98 | defer mb.mu.Unlock() 99 | 100 | if mb.IdleTimeout <= 0 { 101 | return 102 | } 103 | 104 | if idle := time.Since(mb.lastActivity); idle >= mb.IdleTimeout { 105 | mb.logf("modbus: closing connection due to idle timeout: %v", idle) 106 | mb.close() 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /serial_test.go: -------------------------------------------------------------------------------- 1 | package modbus 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "sync/atomic" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | type nopCloser struct { 12 | io.ReadWriter 13 | 14 | closed atomic.Bool 15 | } 16 | 17 | func (n *nopCloser) Close() error { 18 | n.closed.Store(true) 19 | return nil 20 | } 21 | 22 | func TestSerialCloseIdle(t *testing.T) { 23 | port := &nopCloser{ 24 | ReadWriter: &bytes.Buffer{}, 25 | } 26 | s := serialPort{ 27 | port: port, 28 | IdleTimeout: 100 * time.Millisecond, 29 | } 30 | s.lastActivity = time.Now() 31 | s.startCloseTimer() 32 | 33 | time.Sleep(150 * time.Millisecond) 34 | s.mu.Lock() 35 | defer s.mu.Unlock() 36 | if !port.closed.Load() || s.port != nil { 37 | t.Fatalf("serial port is not closed when inactivity: %+v", port) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tcpclient_prop_test.go: -------------------------------------------------------------------------------- 1 | package modbus 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/google/go-cmp/cmp" 7 | "pgregory.net/rapid" 8 | ) 9 | 10 | func TestTCPEncodeDecode(t *testing.T) { 11 | rapid.Check(t, func(t *rapid.T) { 12 | packager := &tcpPackager{ 13 | transactionID: rapid.Uint32().Draw(t, "transactionID"), 14 | SlaveID: rapid.Byte().Draw(t, "SlaveID"), 15 | } 16 | 17 | pdu := &ProtocolDataUnit{ 18 | FunctionCode: rapid.Byte().Draw(t, "FunctionCode"), 19 | Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data"), 20 | } 21 | 22 | raw, err := packager.Encode(pdu) 23 | if err != nil { 24 | t.Fatalf("error while encoding: %+v", err) 25 | } 26 | 27 | dpdu, err := packager.Decode(raw) 28 | if err != nil { 29 | t.Fatalf("error while decoding: %+v", err) 30 | } 31 | 32 | if !cmp.Equal(pdu, dpdu) { 33 | t.Errorf("invalid pdu: %s", cmp.Diff(pdu, dpdu)) 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | System testing for [modbus library](https://github.com/grid-x/modbus) 2 | 3 | Modbus simulator 4 | ---------------- 5 | * [Diagslave](http://www.modbusdriver.com/diagslave.html) 6 | * [socat](http://www.dest-unreach.org/socat/) 7 | 8 | ```bash 9 | # TCP 10 | $ diagslave -m tcp -p 5020 11 | 12 | 13 | # RTU/ASCII 14 | $ socat -d -d pty,raw,echo=0 pty,raw,echo=0 15 | 2015/04/03 12:34:56 socat[2342] N PTY is /dev/pts/6 16 | 2015/04/03 12:34:56 socat[2342] N PTY is /dev/pts/7 17 | $ diagslave -m ascii /dev/pts/7 18 | 19 | # Or 20 | $ diagslave -m rtu /dev/pts/7 21 | 22 | 23 | # RTU/ASCII Over TCP 24 | $ socat -d -d pty,raw,echo=0 tcp-listen:5020,reuseaddr 25 | 2018/12/25 15:57:52 socat[30337] N PTY is /dev/pts/6 26 | 2018/12/25 15:57:52 socat[30337] N listening on AF=2 0.0.0.0:5020 27 | $ diagslave -m ascii /dev/pts/6 28 | 29 | # Or 30 | $ diagslave -m rtu /dev/pts/6 31 | 32 | 33 | $ go test -v -run TCP 34 | $ go test -v -run RTU 35 | $ go test -v -run ASCII 36 | $ go test -v -run RTUOverTCP 37 | $ go test -v -run ASCIIOverTCP 38 | ``` 39 | -------------------------------------------------------------------------------- /test/asciiclient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "context" 9 | "log" 10 | "testing" 11 | 12 | "github.com/grid-x/modbus" 13 | ) 14 | 15 | const ( 16 | asciiDevice = "/dev/pts/2" 17 | ) 18 | 19 | func TestASCIIClient(t *testing.T) { 20 | // Diagslave does not support broadcast id. 21 | handler := modbus.NewASCIIClientHandler(asciiDevice) 22 | handler.SlaveID = 17 23 | ClientTestAll(t, modbus.NewClient(handler)) 24 | } 25 | 26 | func TestASCIIClientAdvancedUsage(t *testing.T) { 27 | handler := modbus.NewASCIIClientHandler(asciiDevice) 28 | handler.BaudRate = 19200 29 | handler.DataBits = 8 30 | handler.Parity = "E" 31 | handler.StopBits = 1 32 | handler.SlaveID = 12 33 | handler.Logger = log.Default() 34 | ctx := context.Background() 35 | err := handler.Connect(ctx) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer handler.Close() 40 | 41 | client := modbus.NewClient(handler) 42 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 43 | if err != nil || results == nil { 44 | t.Fatal(err, results) 45 | } 46 | results, err = client.ReadWriteMultipleRegisters(ctx, 0, 2, 2, 2, []byte{1, 2, 3, 4}) 47 | if err != nil || results == nil { 48 | t.Fatal(err, results) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | 11 | "github.com/grid-x/modbus" 12 | ) 13 | 14 | func ClientTestReadCoils(t *testing.T, client modbus.Client) { 15 | // Read discrete outputs 20-38: 16 | address := uint16(0x0013) 17 | quantity := uint16(0x0013) 18 | results, err := client.ReadCoils(context.Background(), address, quantity) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | AssertEquals(t, 3, len(results)) 23 | } 24 | 25 | func ClientTestReadDiscreteInputs(t *testing.T, client modbus.Client) { 26 | // Read discrete inputs 197-218 27 | address := uint16(0x00C4) 28 | quantity := uint16(0x0016) 29 | results, err := client.ReadDiscreteInputs(context.Background(), address, quantity) 30 | if err != nil { 31 | t.Fatal(err) 32 | } 33 | AssertEquals(t, 3, len(results)) 34 | } 35 | 36 | func ClientTestReadHoldingRegisters(t *testing.T, client modbus.Client) { 37 | // Read registers 108-110 38 | address := uint16(0x006B) 39 | quantity := uint16(0x0003) 40 | results, err := client.ReadHoldingRegisters(context.Background(), address, quantity) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | AssertEquals(t, 6, len(results)) 45 | } 46 | 47 | func ClientTestReadInputRegisters(t *testing.T, client modbus.Client) { 48 | // Read input register 9 49 | address := uint16(0x0008) 50 | quantity := uint16(0x0001) 51 | results, err := client.ReadInputRegisters(context.Background(), address, quantity) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | AssertEquals(t, 2, len(results)) 56 | } 57 | 58 | func ClientTestWriteSingleCoil(t *testing.T, client modbus.Client) { 59 | // Write coil 173 ON 60 | address := uint16(0x00AC) 61 | value := uint16(0xFF00) 62 | results, err := client.WriteSingleCoil(context.Background(), address, value) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | AssertEquals(t, 2, len(results)) 67 | } 68 | 69 | func ClientTestWriteSingleRegister(t *testing.T, client modbus.Client) { 70 | // Write register 2 to 00 03 hex 71 | address := uint16(0x0001) 72 | value := uint16(0x0003) 73 | results, err := client.WriteSingleRegister(context.Background(), address, value) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | AssertEquals(t, 2, len(results)) 78 | } 79 | 80 | func ClientTestWriteMultipleCoils(t *testing.T, client modbus.Client) { 81 | // Write a series of 10 coils starting at coil 20 82 | address := uint16(0x0013) 83 | quantity := uint16(0x000A) 84 | values := []byte{0xCD, 0x01} 85 | results, err := client.WriteMultipleCoils(context.Background(), address, quantity, values) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | AssertEquals(t, 2, len(results)) 90 | } 91 | 92 | func ClientTestWriteMultipleRegisters(t *testing.T, client modbus.Client) { 93 | // Write two registers starting at 2 to 00 0A and 01 02 hex 94 | address := uint16(0x0001) 95 | quantity := uint16(0x0002) 96 | values := []byte{0x00, 0x0A, 0x01, 0x02} 97 | results, err := client.WriteMultipleRegisters(context.Background(), address, quantity, values) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | AssertEquals(t, 2, len(results)) 102 | } 103 | 104 | func ClientTestMaskWriteRegisters(t *testing.T, client modbus.Client) { 105 | // Mask write to register 5 106 | address := uint16(0x0004) 107 | andMask := uint16(0x00F2) 108 | orMask := uint16(0x0025) 109 | results, err := client.MaskWriteRegister(context.Background(), address, andMask, orMask) 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | AssertEquals(t, 4, len(results)) 114 | } 115 | 116 | func ClientTestReadWriteMultipleRegisters(t *testing.T, client modbus.Client) { 117 | // read six registers starting at register 4, and to write three registers starting at register 15 118 | address := uint16(0x0003) 119 | quantity := uint16(0x0006) 120 | writeAddress := uint16(0x000E) 121 | writeQuantity := uint16(0x0003) 122 | values := []byte{0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF} 123 | results, err := client.ReadWriteMultipleRegisters(context.Background(), address, quantity, writeAddress, writeQuantity, values) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | AssertEquals(t, 12, len(results)) 128 | } 129 | 130 | func ClientTestReadFIFOQueue(t *testing.T, client modbus.Client) { 131 | // Read queue starting at the pointer register 1246 132 | address := uint16(0x04DE) 133 | results, err := client.ReadFIFOQueue(context.Background(), address) 134 | // Server not implemented 135 | if err != nil { 136 | AssertEquals(t, "modbus: exception '1' (illegal function), function '24'", err.Error()) 137 | } else { 138 | AssertEquals(t, 0, len(results)) 139 | } 140 | } 141 | 142 | func ClientTestAll(t *testing.T, client modbus.Client) { 143 | t.Logf("testing ReadCoils") 144 | ClientTestReadCoils(t, client) 145 | 146 | t.Logf("testing ReadDiscreteInputs") 147 | ClientTestReadDiscreteInputs(t, client) 148 | 149 | t.Logf("testing ReadHoldingRegisters") 150 | ClientTestReadHoldingRegisters(t, client) 151 | 152 | t.Logf("testing ReadInputRegisters") 153 | ClientTestReadInputRegisters(t, client) 154 | 155 | t.Logf("testing WriteSingleCoil") 156 | ClientTestWriteSingleCoil(t, client) 157 | 158 | t.Logf("testing WriteSingleRegister") 159 | ClientTestWriteSingleRegister(t, client) 160 | 161 | t.Logf("testing WriteMultipleCoils") 162 | ClientTestWriteMultipleCoils(t, client) 163 | 164 | t.Logf("testing WriteMultipleRegisters") 165 | ClientTestWriteMultipleRegisters(t, client) 166 | 167 | t.Logf("testing MaskWriteRegisters") 168 | ClientTestMaskWriteRegisters(t, client) 169 | 170 | t.Logf("testing ReadWriteMultipleRegisters") 171 | ClientTestReadWriteMultipleRegisters(t, client) 172 | 173 | t.Logf("testing ReadFifoQueue") 174 | ClientTestReadFIFOQueue(t, client) 175 | } 176 | -------------------------------------------------------------------------------- /test/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "runtime" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | func AssertEquals(t *testing.T, expected, actual interface{}) { 14 | _, file, line, ok := runtime.Caller(1) 15 | if !ok { 16 | file = "???" 17 | line = 0 18 | } else { 19 | // Get file name only 20 | idx := strings.LastIndex(file, "/") 21 | if idx >= 0 { 22 | file = file[idx+1:] 23 | } 24 | } 25 | 26 | if expected != actual { 27 | t.Logf("%s:%d: Expected: %+v (%T), actual: %+v (%T)", file, line, 28 | expected, expected, actual, actual) 29 | t.FailNow() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/commw32/commw32.c: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | // Test serial communication in Win32 6 | // gcc commw32.c 7 | #include 8 | #include 9 | #include 10 | 11 | static const char* port = "COM4"; 12 | 13 | static printLastError() { 14 | char lpBuffer[256] = "?"; 15 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 16 | NULL, 17 | GetLastError(), 18 | MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), 19 | lpBuffer, 20 | sizeof(lpBuffer)-1, 21 | NULL); 22 | printf("%s\n", lpBuffer); 23 | } 24 | 25 | int main(int argc, char** argv) { 26 | HANDLE handle; 27 | DCB dcb = {0}; 28 | COMMTIMEOUTS timeouts; 29 | DWORD n = 0; 30 | char data[512]; 31 | int i = 0; 32 | 33 | handle = CreateFile(port, 34 | GENERIC_READ | GENERIC_WRITE, 35 | 0, 36 | 0, 37 | OPEN_EXISTING, 38 | 0, 39 | 0); 40 | if (handle == INVALID_HANDLE_VALUE) { 41 | printf("invalid handle %d\n", GetLastError()); 42 | printLastError(); 43 | return 1; 44 | } 45 | printf("handle created %d\n", handle); 46 | 47 | dcb.BaudRate = CBR_9600; 48 | dcb.ByteSize = 8; 49 | dcb.StopBits = ONESTOPBIT; 50 | dcb.Parity = NOPARITY; 51 | // No software handshaking 52 | dcb.fTXContinueOnXoff = 1; 53 | dcb.fOutX = 0; 54 | dcb.fInX = 0; 55 | // Binary mode 56 | dcb.fBinary = 1; 57 | // No blocking on errors 58 | dcb.fAbortOnError = 0; 59 | 60 | if (!SetCommState(handle, &dcb)) { 61 | printf("set comm state error %d\n", GetLastError()); 62 | printLastError(); 63 | CloseHandle(handle); 64 | return 1; 65 | } 66 | printf("set comm state succeed\n"); 67 | 68 | // time-out between charactor for receiving (ms) 69 | timeouts.ReadIntervalTimeout = 1000; 70 | timeouts.ReadTotalTimeoutMultiplier = 0; 71 | timeouts.ReadTotalTimeoutConstant = 1000; 72 | timeouts.WriteTotalTimeoutMultiplier = 0; 73 | timeouts.WriteTotalTimeoutConstant = 1000; 74 | if (!SetCommTimeouts(handle, &timeouts)) { 75 | printf("set comm timeouts error %d\n", GetLastError()); 76 | printLastError(); 77 | CloseHandle(handle); 78 | return 1; 79 | } 80 | printf("set comm timeouts succeed\n"); 81 | 82 | if (!WriteFile(handle, "abc", 3, &n, NULL)) { 83 | printf("write file error %d\n", GetLastError()); 84 | printLastError(); 85 | CloseHandle(handle); 86 | return 1; 87 | } 88 | printf("write file succeed\n"); 89 | printf("Press Enter when ready for reading..."); 90 | getchar(); 91 | 92 | if (!ReadFile(handle, data, sizeof(data), &n, NULL)) { 93 | printf("read file error %d\n", GetLastError()); 94 | printLastError(); 95 | CloseHandle(handle); 96 | return 1; 97 | } 98 | printf("received data %d:\n", n); 99 | for (i = 0; i < n; ++i) { 100 | printf("%02x", data[i]); 101 | } 102 | printf("\n"); 103 | 104 | CloseHandle(handle); 105 | printf("closed\n"); 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /test/commw32/commw32.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | // +build windows,cgo 5 | 6 | // Port of commw32.c 7 | // To generate go types: go tool cgo commw32.go 8 | package main 9 | 10 | // #include 11 | import "C" 12 | 13 | import ( 14 | "bufio" 15 | "fmt" 16 | "os" 17 | "syscall" 18 | ) 19 | 20 | const port = "COM4" 21 | 22 | func main() { 23 | handle, err := syscall.CreateFile(syscall.StringToUTF16Ptr(port), 24 | syscall.GENERIC_READ|syscall.GENERIC_WRITE, 25 | 0, // mode 26 | nil, // security 27 | syscall.OPEN_EXISTING, // no creating new 28 | 0, 29 | 0) 30 | if err != nil { 31 | fmt.Print(err) 32 | return 33 | } 34 | fmt.Printf("handle created %d\n", handle) 35 | defer syscall.CloseHandle(handle) 36 | 37 | var dcb C.DCB 38 | dcb.BaudRate = 9600 39 | dcb.ByteSize = 8 40 | dcb.StopBits = C.ONESTOPBIT 41 | dcb.Parity = C.NOPARITY 42 | if C.SetCommState(C.HANDLE(handle), &dcb) == 0 { 43 | fmt.Printf("set comm state error %v\n", syscall.GetLastError()) 44 | return 45 | } 46 | fmt.Printf("set comm state succeed\n") 47 | 48 | var timeouts C.COMMTIMEOUTS 49 | // time-out between charactor for receiving (ms) 50 | timeouts.ReadIntervalTimeout = 1000 51 | timeouts.ReadTotalTimeoutMultiplier = 0 52 | timeouts.ReadTotalTimeoutConstant = 1000 53 | timeouts.WriteTotalTimeoutMultiplier = 0 54 | timeouts.WriteTotalTimeoutConstant = 1000 55 | if C.SetCommTimeouts(C.HANDLE(handle), &timeouts) == 0 { 56 | fmt.Printf("set comm timeouts error %v\n", syscall.GetLastError()) 57 | return 58 | } 59 | fmt.Printf("set comm timeouts succeed\n") 60 | 61 | var n uint32 62 | data := []byte("abc") 63 | err = syscall.WriteFile(handle, data, &n, nil) 64 | if err != nil { 65 | fmt.Println(err) 66 | return 67 | } 68 | fmt.Printf("write file succeed\n") 69 | fmt.Printf("Press Enter when ready for reading...") 70 | reader := bufio.NewReader(os.Stdin) 71 | _, _ = reader.ReadString('\n') 72 | 73 | data = make([]byte, 512) 74 | err = syscall.ReadFile(handle, data, &n, nil) 75 | if err != nil { 76 | fmt.Println(err) 77 | return 78 | } 79 | fmt.Printf("received data %v:\n", n) 80 | fmt.Printf("%x\n", data[:n]) 81 | fmt.Printf("closed\n") 82 | } 83 | -------------------------------------------------------------------------------- /test/rtu_over_tcp_client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 xft. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "context" 9 | "log" 10 | "testing" 11 | "time" 12 | 13 | "github.com/grid-x/modbus" 14 | ) 15 | 16 | const ( 17 | rtuOverTCPDevice = "localhost:5021" 18 | ) 19 | 20 | func TestRTUOverTCPClient(t *testing.T) { 21 | // Diagslave does not support broadcast id. 22 | handler := modbus.NewRTUOverTCPClientHandler(rtuOverTCPDevice) 23 | handler.SlaveID = 17 24 | ClientTestAll(t, modbus.NewClient(handler)) 25 | } 26 | 27 | func TestRTUOverTCPClientAdvancedUsage(t *testing.T) { 28 | handler := modbus.NewRTUOverTCPClientHandler(rtuOverTCPDevice) 29 | handler.Timeout = 5 * time.Second 30 | handler.SlaveID = 1 31 | handler.Logger = log.Default() 32 | ctx := context.Background() 33 | handler.Connect(ctx) 34 | defer handler.Close() 35 | 36 | client := modbus.NewClient(handler) 37 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 38 | if err != nil || results == nil { 39 | t.Fatal(err, results) 40 | } 41 | results, err = client.WriteMultipleRegisters(ctx, 1, 2, []byte{0, 3, 0, 4}) 42 | if err != nil || results == nil { 43 | t.Fatal(err, results) 44 | } 45 | results, err = client.WriteMultipleCoils(ctx, 5, 10, []byte{4, 3}) 46 | if err != nil || results == nil { 47 | t.Fatal(err, results) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/rtuclient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "context" 9 | "log" 10 | "testing" 11 | 12 | "github.com/grid-x/modbus" 13 | ) 14 | 15 | const ( 16 | rtuDevice = "/dev/pts/0" 17 | ) 18 | 19 | func TestRTUClient(t *testing.T) { 20 | // Diagslave does not support broadcast id. 21 | handler := modbus.NewRTUClientHandler(rtuDevice) 22 | handler.SlaveID = 17 23 | ClientTestAll(t, modbus.NewClient(handler)) 24 | } 25 | 26 | func TestRTUClientAdvancedUsage(t *testing.T) { 27 | handler := modbus.NewRTUClientHandler(rtuDevice) 28 | handler.BaudRate = 19200 29 | handler.DataBits = 8 30 | handler.Parity = "E" 31 | handler.StopBits = 1 32 | handler.SlaveID = 11 33 | handler.Logger = log.Default() 34 | ctx := context.Background() 35 | err := handler.Connect(ctx) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer handler.Close() 40 | 41 | client := modbus.NewClient(handler) 42 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 43 | if err != nil || results == nil { 44 | t.Fatal(err, results) 45 | } 46 | results, err = client.ReadWriteMultipleRegisters(ctx, 0, 2, 2, 2, []byte{1, 2, 3, 4}) 47 | if err != nil || results == nil { 48 | t.Fatal(err, results) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/tcpclient_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Quoc-Viet Nguyen. All rights reserved. 2 | // This software may be modified and distributed under the terms 3 | // of the BSD license. See the LICENSE file for details. 4 | 5 | package test 6 | 7 | import ( 8 | "context" 9 | "log" 10 | "testing" 11 | "time" 12 | 13 | "github.com/grid-x/modbus" 14 | ) 15 | 16 | const ( 17 | tcpDevice = "localhost:5020" 18 | ) 19 | 20 | func TestTCPClient(t *testing.T) { 21 | client := modbus.TCPClient(tcpDevice) 22 | ClientTestAll(t, client) 23 | } 24 | 25 | func TestTCPClientAdvancedUsage(t *testing.T) { 26 | handler := modbus.NewTCPClientHandler(tcpDevice) 27 | handler.Timeout = 5 * time.Second 28 | handler.SlaveID = 1 29 | handler.Logger = log.Default() 30 | ctx := context.Background() 31 | handler.Connect(ctx) 32 | defer handler.Close() 33 | 34 | client := modbus.NewClient(handler) 35 | results, err := client.ReadDiscreteInputs(ctx, 15, 2) 36 | if err != nil || results == nil { 37 | t.Fatal(err, results) 38 | } 39 | results, err = client.WriteMultipleRegisters(ctx, 1, 2, []byte{0, 3, 0, 4}) 40 | if err != nil || results == nil { 41 | t.Fatal(err, results) 42 | } 43 | results, err = client.WriteMultipleCoils(ctx, 5, 10, []byte{4, 3}) 44 | if err != nil || results == nil { 45 | t.Fatal(err, results) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/export.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cmp 6 | 7 | import ( 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | // retrieveUnexportedField uses unsafe to forcibly retrieve any field from 13 | // a struct such that the value has read-write permissions. 14 | // 15 | // The parent struct, v, must be addressable, while f must be a StructField 16 | // describing the field to retrieve. If addr is false, 17 | // then the returned value will be shallowed copied to be non-addressable. 18 | func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value { 19 | ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem() 20 | if !addr { 21 | // A field is addressable if and only if the struct is addressable. 22 | // If the original parent value was not addressable, shallow copy the 23 | // value to make it non-addressable to avoid leaking an implementation 24 | // detail of how forcibly exporting a field works. 25 | if ve.Kind() == reflect.Interface && ve.IsNil() { 26 | return reflect.Zero(f.Type) 27 | } 28 | return reflect.ValueOf(ve.Interface()).Convert(f.Type) 29 | } 30 | return ve 31 | } 32 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build !cmp_debug 6 | // +build !cmp_debug 7 | 8 | package diff 9 | 10 | var debug debugger 11 | 12 | type debugger struct{} 13 | 14 | func (debugger) Begin(_, _ int, f EqualFunc, _, _ *EditScript) EqualFunc { 15 | return f 16 | } 17 | func (debugger) Update() {} 18 | func (debugger) Finish() {} 19 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build cmp_debug 6 | // +build cmp_debug 7 | 8 | package diff 9 | 10 | import ( 11 | "fmt" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | // The algorithm can be seen running in real-time by enabling debugging: 18 | // go test -tags=cmp_debug -v 19 | // 20 | // Example output: 21 | // === RUN TestDifference/#34 22 | // ┌───────────────────────────────┐ 23 | // │ \ · · · · · · · · · · · · · · │ 24 | // │ · # · · · · · · · · · · · · · │ 25 | // │ · \ · · · · · · · · · · · · · │ 26 | // │ · · \ · · · · · · · · · · · · │ 27 | // │ · · · X # · · · · · · · · · · │ 28 | // │ · · · # \ · · · · · · · · · · │ 29 | // │ · · · · · # # · · · · · · · · │ 30 | // │ · · · · · # \ · · · · · · · · │ 31 | // │ · · · · · · · \ · · · · · · · │ 32 | // │ · · · · · · · · \ · · · · · · │ 33 | // │ · · · · · · · · · \ · · · · · │ 34 | // │ · · · · · · · · · · \ · · # · │ 35 | // │ · · · · · · · · · · · \ # # · │ 36 | // │ · · · · · · · · · · · # # # · │ 37 | // │ · · · · · · · · · · # # # # · │ 38 | // │ · · · · · · · · · # # # # # · │ 39 | // │ · · · · · · · · · · · · · · \ │ 40 | // └───────────────────────────────┘ 41 | // [.Y..M.XY......YXYXY.|] 42 | // 43 | // The grid represents the edit-graph where the horizontal axis represents 44 | // list X and the vertical axis represents list Y. The start of the two lists 45 | // is the top-left, while the ends are the bottom-right. The '·' represents 46 | // an unexplored node in the graph. The '\' indicates that the two symbols 47 | // from list X and Y are equal. The 'X' indicates that two symbols are similar 48 | // (but not exactly equal) to each other. The '#' indicates that the two symbols 49 | // are different (and not similar). The algorithm traverses this graph trying to 50 | // make the paths starting in the top-left and the bottom-right connect. 51 | // 52 | // The series of '.', 'X', 'Y', and 'M' characters at the bottom represents 53 | // the currently established path from the forward and reverse searches, 54 | // separated by a '|' character. 55 | 56 | const ( 57 | updateDelay = 100 * time.Millisecond 58 | finishDelay = 500 * time.Millisecond 59 | ansiTerminal = true // ANSI escape codes used to move terminal cursor 60 | ) 61 | 62 | var debug debugger 63 | 64 | type debugger struct { 65 | sync.Mutex 66 | p1, p2 EditScript 67 | fwdPath, revPath *EditScript 68 | grid []byte 69 | lines int 70 | } 71 | 72 | func (dbg *debugger) Begin(nx, ny int, f EqualFunc, p1, p2 *EditScript) EqualFunc { 73 | dbg.Lock() 74 | dbg.fwdPath, dbg.revPath = p1, p2 75 | top := "┌─" + strings.Repeat("──", nx) + "┐\n" 76 | row := "│ " + strings.Repeat("· ", nx) + "│\n" 77 | btm := "└─" + strings.Repeat("──", nx) + "┘\n" 78 | dbg.grid = []byte(top + strings.Repeat(row, ny) + btm) 79 | dbg.lines = strings.Count(dbg.String(), "\n") 80 | fmt.Print(dbg) 81 | 82 | // Wrap the EqualFunc so that we can intercept each result. 83 | return func(ix, iy int) (r Result) { 84 | cell := dbg.grid[len(top)+iy*len(row):][len("│ ")+len("· ")*ix:][:len("·")] 85 | for i := range cell { 86 | cell[i] = 0 // Zero out the multiple bytes of UTF-8 middle-dot 87 | } 88 | switch r = f(ix, iy); { 89 | case r.Equal(): 90 | cell[0] = '\\' 91 | case r.Similar(): 92 | cell[0] = 'X' 93 | default: 94 | cell[0] = '#' 95 | } 96 | return 97 | } 98 | } 99 | 100 | func (dbg *debugger) Update() { 101 | dbg.print(updateDelay) 102 | } 103 | 104 | func (dbg *debugger) Finish() { 105 | dbg.print(finishDelay) 106 | dbg.Unlock() 107 | } 108 | 109 | func (dbg *debugger) String() string { 110 | dbg.p1, dbg.p2 = *dbg.fwdPath, dbg.p2[:0] 111 | for i := len(*dbg.revPath) - 1; i >= 0; i-- { 112 | dbg.p2 = append(dbg.p2, (*dbg.revPath)[i]) 113 | } 114 | return fmt.Sprintf("%s[%v|%v]\n\n", dbg.grid, dbg.p1, dbg.p2) 115 | } 116 | 117 | func (dbg *debugger) print(d time.Duration) { 118 | if ansiTerminal { 119 | fmt.Printf("\x1b[%dA", dbg.lines) // Reset terminal cursor 120 | } 121 | fmt.Print(dbg) 122 | time.Sleep(d) 123 | } 124 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/flags/flags.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package flags 6 | 7 | // Deterministic controls whether the output of Diff should be deterministic. 8 | // This is only used for testing. 9 | var Deterministic bool 10 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/function/func.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package function provides functionality for identifying function types. 6 | package function 7 | 8 | import ( 9 | "reflect" 10 | "regexp" 11 | "runtime" 12 | "strings" 13 | ) 14 | 15 | type funcType int 16 | 17 | const ( 18 | _ funcType = iota 19 | 20 | tbFunc // func(T) bool 21 | ttbFunc // func(T, T) bool 22 | trbFunc // func(T, R) bool 23 | tibFunc // func(T, I) bool 24 | trFunc // func(T) R 25 | 26 | Equal = ttbFunc // func(T, T) bool 27 | EqualAssignable = tibFunc // func(T, I) bool; encapsulates func(T, T) bool 28 | Transformer = trFunc // func(T) R 29 | ValueFilter = ttbFunc // func(T, T) bool 30 | Less = ttbFunc // func(T, T) bool 31 | ValuePredicate = tbFunc // func(T) bool 32 | KeyValuePredicate = trbFunc // func(T, R) bool 33 | ) 34 | 35 | var boolType = reflect.TypeOf(true) 36 | 37 | // IsType reports whether the reflect.Type is of the specified function type. 38 | func IsType(t reflect.Type, ft funcType) bool { 39 | if t == nil || t.Kind() != reflect.Func || t.IsVariadic() { 40 | return false 41 | } 42 | ni, no := t.NumIn(), t.NumOut() 43 | switch ft { 44 | case tbFunc: // func(T) bool 45 | if ni == 1 && no == 1 && t.Out(0) == boolType { 46 | return true 47 | } 48 | case ttbFunc: // func(T, T) bool 49 | if ni == 2 && no == 1 && t.In(0) == t.In(1) && t.Out(0) == boolType { 50 | return true 51 | } 52 | case trbFunc: // func(T, R) bool 53 | if ni == 2 && no == 1 && t.Out(0) == boolType { 54 | return true 55 | } 56 | case tibFunc: // func(T, I) bool 57 | if ni == 2 && no == 1 && t.In(0).AssignableTo(t.In(1)) && t.Out(0) == boolType { 58 | return true 59 | } 60 | case trFunc: // func(T) R 61 | if ni == 1 && no == 1 { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | var lastIdentRx = regexp.MustCompile(`[_\p{L}][_\p{L}\p{N}]*$`) 69 | 70 | // NameOf returns the name of the function value. 71 | func NameOf(v reflect.Value) string { 72 | fnc := runtime.FuncForPC(v.Pointer()) 73 | if fnc == nil { 74 | return "" 75 | } 76 | fullName := fnc.Name() // e.g., "long/path/name/mypkg.(*MyType).(long/path/name/mypkg.myMethod)-fm" 77 | 78 | // Method closures have a "-fm" suffix. 79 | fullName = strings.TrimSuffix(fullName, "-fm") 80 | 81 | var name string 82 | for len(fullName) > 0 { 83 | inParen := strings.HasSuffix(fullName, ")") 84 | fullName = strings.TrimSuffix(fullName, ")") 85 | 86 | s := lastIdentRx.FindString(fullName) 87 | if s == "" { 88 | break 89 | } 90 | name = s + "." + name 91 | fullName = strings.TrimSuffix(fullName, s) 92 | 93 | if i := strings.LastIndexByte(fullName, '('); inParen && i >= 0 { 94 | fullName = fullName[:i] 95 | } 96 | fullName = strings.TrimSuffix(fullName, ".") 97 | } 98 | return strings.TrimSuffix(name, ".") 99 | } 100 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/value/name.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package value 6 | 7 | import ( 8 | "reflect" 9 | "strconv" 10 | ) 11 | 12 | var anyType = reflect.TypeOf((*interface{})(nil)).Elem() 13 | 14 | // TypeString is nearly identical to reflect.Type.String, 15 | // but has an additional option to specify that full type names be used. 16 | func TypeString(t reflect.Type, qualified bool) string { 17 | return string(appendTypeName(nil, t, qualified, false)) 18 | } 19 | 20 | func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte { 21 | // BUG: Go reflection provides no way to disambiguate two named types 22 | // of the same name and within the same package, 23 | // but declared within the namespace of different functions. 24 | 25 | // Use the "any" alias instead of "interface{}" for better readability. 26 | if t == anyType { 27 | return append(b, "any"...) 28 | } 29 | 30 | // Named type. 31 | if t.Name() != "" { 32 | if qualified && t.PkgPath() != "" { 33 | b = append(b, '"') 34 | b = append(b, t.PkgPath()...) 35 | b = append(b, '"') 36 | b = append(b, '.') 37 | b = append(b, t.Name()...) 38 | } else { 39 | b = append(b, t.String()...) 40 | } 41 | return b 42 | } 43 | 44 | // Unnamed type. 45 | switch k := t.Kind(); k { 46 | case reflect.Bool, reflect.String, reflect.UnsafePointer, 47 | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 48 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 49 | reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 50 | b = append(b, k.String()...) 51 | case reflect.Chan: 52 | if t.ChanDir() == reflect.RecvDir { 53 | b = append(b, "<-"...) 54 | } 55 | b = append(b, "chan"...) 56 | if t.ChanDir() == reflect.SendDir { 57 | b = append(b, "<-"...) 58 | } 59 | b = append(b, ' ') 60 | b = appendTypeName(b, t.Elem(), qualified, false) 61 | case reflect.Func: 62 | if !elideFunc { 63 | b = append(b, "func"...) 64 | } 65 | b = append(b, '(') 66 | for i := 0; i < t.NumIn(); i++ { 67 | if i > 0 { 68 | b = append(b, ", "...) 69 | } 70 | if i == t.NumIn()-1 && t.IsVariadic() { 71 | b = append(b, "..."...) 72 | b = appendTypeName(b, t.In(i).Elem(), qualified, false) 73 | } else { 74 | b = appendTypeName(b, t.In(i), qualified, false) 75 | } 76 | } 77 | b = append(b, ')') 78 | switch t.NumOut() { 79 | case 0: 80 | // Do nothing 81 | case 1: 82 | b = append(b, ' ') 83 | b = appendTypeName(b, t.Out(0), qualified, false) 84 | default: 85 | b = append(b, " ("...) 86 | for i := 0; i < t.NumOut(); i++ { 87 | if i > 0 { 88 | b = append(b, ", "...) 89 | } 90 | b = appendTypeName(b, t.Out(i), qualified, false) 91 | } 92 | b = append(b, ')') 93 | } 94 | case reflect.Struct: 95 | b = append(b, "struct{ "...) 96 | for i := 0; i < t.NumField(); i++ { 97 | if i > 0 { 98 | b = append(b, "; "...) 99 | } 100 | sf := t.Field(i) 101 | if !sf.Anonymous { 102 | if qualified && sf.PkgPath != "" { 103 | b = append(b, '"') 104 | b = append(b, sf.PkgPath...) 105 | b = append(b, '"') 106 | b = append(b, '.') 107 | } 108 | b = append(b, sf.Name...) 109 | b = append(b, ' ') 110 | } 111 | b = appendTypeName(b, sf.Type, qualified, false) 112 | if sf.Tag != "" { 113 | b = append(b, ' ') 114 | b = strconv.AppendQuote(b, string(sf.Tag)) 115 | } 116 | } 117 | if b[len(b)-1] == ' ' { 118 | b = b[:len(b)-1] 119 | } else { 120 | b = append(b, ' ') 121 | } 122 | b = append(b, '}') 123 | case reflect.Slice, reflect.Array: 124 | b = append(b, '[') 125 | if k == reflect.Array { 126 | b = strconv.AppendUint(b, uint64(t.Len()), 10) 127 | } 128 | b = append(b, ']') 129 | b = appendTypeName(b, t.Elem(), qualified, false) 130 | case reflect.Map: 131 | b = append(b, "map["...) 132 | b = appendTypeName(b, t.Key(), qualified, false) 133 | b = append(b, ']') 134 | b = appendTypeName(b, t.Elem(), qualified, false) 135 | case reflect.Ptr: 136 | b = append(b, '*') 137 | b = appendTypeName(b, t.Elem(), qualified, false) 138 | case reflect.Interface: 139 | b = append(b, "interface{ "...) 140 | for i := 0; i < t.NumMethod(); i++ { 141 | if i > 0 { 142 | b = append(b, "; "...) 143 | } 144 | m := t.Method(i) 145 | if qualified && m.PkgPath != "" { 146 | b = append(b, '"') 147 | b = append(b, m.PkgPath...) 148 | b = append(b, '"') 149 | b = append(b, '.') 150 | } 151 | b = append(b, m.Name...) 152 | b = appendTypeName(b, m.Type, qualified, true) 153 | } 154 | if b[len(b)-1] == ' ' { 155 | b = b[:len(b)-1] 156 | } else { 157 | b = append(b, ' ') 158 | } 159 | b = append(b, '}') 160 | default: 161 | panic("invalid kind: " + k.String()) 162 | } 163 | return b 164 | } 165 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package value 6 | 7 | import ( 8 | "reflect" 9 | "unsafe" 10 | ) 11 | 12 | // Pointer is an opaque typed pointer and is guaranteed to be comparable. 13 | type Pointer struct { 14 | p unsafe.Pointer 15 | t reflect.Type 16 | } 17 | 18 | // PointerOf returns a Pointer from v, which must be a 19 | // reflect.Ptr, reflect.Slice, or reflect.Map. 20 | func PointerOf(v reflect.Value) Pointer { 21 | // The proper representation of a pointer is unsafe.Pointer, 22 | // which is necessary if the GC ever uses a moving collector. 23 | return Pointer{unsafe.Pointer(v.Pointer()), v.Type()} 24 | } 25 | 26 | // IsNil reports whether the pointer is nil. 27 | func (p Pointer) IsNil() bool { 28 | return p.p == nil 29 | } 30 | 31 | // Uintptr returns the pointer as a uintptr. 32 | func (p Pointer) Uintptr() uintptr { 33 | return uintptr(p.p) 34 | } 35 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/internal/value/sort.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package value 6 | 7 | import ( 8 | "fmt" 9 | "math" 10 | "reflect" 11 | "sort" 12 | ) 13 | 14 | // SortKeys sorts a list of map keys, deduplicating keys if necessary. 15 | // The type of each value must be comparable. 16 | func SortKeys(vs []reflect.Value) []reflect.Value { 17 | if len(vs) == 0 { 18 | return vs 19 | } 20 | 21 | // Sort the map keys. 22 | sort.SliceStable(vs, func(i, j int) bool { return isLess(vs[i], vs[j]) }) 23 | 24 | // Deduplicate keys (fails for NaNs). 25 | vs2 := vs[:1] 26 | for _, v := range vs[1:] { 27 | if isLess(vs2[len(vs2)-1], v) { 28 | vs2 = append(vs2, v) 29 | } 30 | } 31 | return vs2 32 | } 33 | 34 | // isLess is a generic function for sorting arbitrary map keys. 35 | // The inputs must be of the same type and must be comparable. 36 | func isLess(x, y reflect.Value) bool { 37 | switch x.Type().Kind() { 38 | case reflect.Bool: 39 | return !x.Bool() && y.Bool() 40 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 41 | return x.Int() < y.Int() 42 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 43 | return x.Uint() < y.Uint() 44 | case reflect.Float32, reflect.Float64: 45 | // NOTE: This does not sort -0 as less than +0 46 | // since Go maps treat -0 and +0 as equal keys. 47 | fx, fy := x.Float(), y.Float() 48 | return fx < fy || math.IsNaN(fx) && !math.IsNaN(fy) 49 | case reflect.Complex64, reflect.Complex128: 50 | cx, cy := x.Complex(), y.Complex() 51 | rx, ix, ry, iy := real(cx), imag(cx), real(cy), imag(cy) 52 | if rx == ry || (math.IsNaN(rx) && math.IsNaN(ry)) { 53 | return ix < iy || math.IsNaN(ix) && !math.IsNaN(iy) 54 | } 55 | return rx < ry || math.IsNaN(rx) && !math.IsNaN(ry) 56 | case reflect.Ptr, reflect.UnsafePointer, reflect.Chan: 57 | return x.Pointer() < y.Pointer() 58 | case reflect.String: 59 | return x.String() < y.String() 60 | case reflect.Array: 61 | for i := 0; i < x.Len(); i++ { 62 | if isLess(x.Index(i), y.Index(i)) { 63 | return true 64 | } 65 | if isLess(y.Index(i), x.Index(i)) { 66 | return false 67 | } 68 | } 69 | return false 70 | case reflect.Struct: 71 | for i := 0; i < x.NumField(); i++ { 72 | if isLess(x.Field(i), y.Field(i)) { 73 | return true 74 | } 75 | if isLess(y.Field(i), x.Field(i)) { 76 | return false 77 | } 78 | } 79 | return false 80 | case reflect.Interface: 81 | vx, vy := x.Elem(), y.Elem() 82 | if !vx.IsValid() || !vy.IsValid() { 83 | return !vx.IsValid() && vy.IsValid() 84 | } 85 | tx, ty := vx.Type(), vy.Type() 86 | if tx == ty { 87 | return isLess(x.Elem(), y.Elem()) 88 | } 89 | if tx.Kind() != ty.Kind() { 90 | return vx.Kind() < vy.Kind() 91 | } 92 | if tx.String() != ty.String() { 93 | return tx.String() < ty.String() 94 | } 95 | if tx.PkgPath() != ty.PkgPath() { 96 | return tx.PkgPath() < ty.PkgPath() 97 | } 98 | // This can happen in rare situations, so we fallback to just comparing 99 | // the unique pointer for a reflect.Type. This guarantees deterministic 100 | // ordering within a program, but it is obviously not stable. 101 | return reflect.ValueOf(vx.Type()).Pointer() < reflect.ValueOf(vy.Type()).Pointer() 102 | default: 103 | // Must be Func, Map, or Slice; which are not comparable. 104 | panic(fmt.Sprintf("%T is not comparable", x.Type())) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/report.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cmp 6 | 7 | // defaultReporter implements the reporter interface. 8 | // 9 | // As Equal serially calls the PushStep, Report, and PopStep methods, the 10 | // defaultReporter constructs a tree-based representation of the compared value 11 | // and the result of each comparison (see valueNode). 12 | // 13 | // When the String method is called, the FormatDiff method transforms the 14 | // valueNode tree into a textNode tree, which is a tree-based representation 15 | // of the textual output (see textNode). 16 | // 17 | // Lastly, the textNode.String method produces the final report as a string. 18 | type defaultReporter struct { 19 | root *valueNode 20 | curr *valueNode 21 | } 22 | 23 | func (r *defaultReporter) PushStep(ps PathStep) { 24 | r.curr = r.curr.PushStep(ps) 25 | if r.root == nil { 26 | r.root = r.curr 27 | } 28 | } 29 | func (r *defaultReporter) Report(rs Result) { 30 | r.curr.Report(rs) 31 | } 32 | func (r *defaultReporter) PopStep() { 33 | r.curr = r.curr.PopStep() 34 | } 35 | 36 | // String provides a full report of the differences detected as a structured 37 | // literal in pseudo-Go syntax. String may only be called after the entire tree 38 | // has been traversed. 39 | func (r *defaultReporter) String() string { 40 | assert(r.root != nil && r.curr == nil) 41 | if r.root.NumDiff == 0 { 42 | return "" 43 | } 44 | ptrs := new(pointerReferences) 45 | text := formatOptions{}.FormatDiff(r.root, ptrs) 46 | resolveReferences(text) 47 | return text.String() 48 | } 49 | 50 | func assert(ok bool) { 51 | if !ok { 52 | panic("assertion failure") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/report_references.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cmp 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | 12 | "github.com/google/go-cmp/cmp/internal/flags" 13 | "github.com/google/go-cmp/cmp/internal/value" 14 | ) 15 | 16 | const ( 17 | pointerDelimPrefix = "⟪" 18 | pointerDelimSuffix = "⟫" 19 | ) 20 | 21 | // formatPointer prints the address of the pointer. 22 | func formatPointer(p value.Pointer, withDelims bool) string { 23 | v := p.Uintptr() 24 | if flags.Deterministic { 25 | v = 0xdeadf00f // Only used for stable testing purposes 26 | } 27 | if withDelims { 28 | return pointerDelimPrefix + formatHex(uint64(v)) + pointerDelimSuffix 29 | } 30 | return formatHex(uint64(v)) 31 | } 32 | 33 | // pointerReferences is a stack of pointers visited so far. 34 | type pointerReferences [][2]value.Pointer 35 | 36 | func (ps *pointerReferences) PushPair(vx, vy reflect.Value, d diffMode, deref bool) (pp [2]value.Pointer) { 37 | if deref && vx.IsValid() { 38 | vx = vx.Addr() 39 | } 40 | if deref && vy.IsValid() { 41 | vy = vy.Addr() 42 | } 43 | switch d { 44 | case diffUnknown, diffIdentical: 45 | pp = [2]value.Pointer{value.PointerOf(vx), value.PointerOf(vy)} 46 | case diffRemoved: 47 | pp = [2]value.Pointer{value.PointerOf(vx), value.Pointer{}} 48 | case diffInserted: 49 | pp = [2]value.Pointer{value.Pointer{}, value.PointerOf(vy)} 50 | } 51 | *ps = append(*ps, pp) 52 | return pp 53 | } 54 | 55 | func (ps *pointerReferences) Push(v reflect.Value) (p value.Pointer, seen bool) { 56 | p = value.PointerOf(v) 57 | for _, pp := range *ps { 58 | if p == pp[0] || p == pp[1] { 59 | return p, true 60 | } 61 | } 62 | *ps = append(*ps, [2]value.Pointer{p, p}) 63 | return p, false 64 | } 65 | 66 | func (ps *pointerReferences) Pop() { 67 | *ps = (*ps)[:len(*ps)-1] 68 | } 69 | 70 | // trunkReferences is metadata for a textNode indicating that the sub-tree 71 | // represents the value for either pointer in a pair of references. 72 | type trunkReferences struct{ pp [2]value.Pointer } 73 | 74 | // trunkReference is metadata for a textNode indicating that the sub-tree 75 | // represents the value for the given pointer reference. 76 | type trunkReference struct{ p value.Pointer } 77 | 78 | // leafReference is metadata for a textNode indicating that the value is 79 | // truncated as it refers to another part of the tree (i.e., a trunk). 80 | type leafReference struct{ p value.Pointer } 81 | 82 | func wrapTrunkReferences(pp [2]value.Pointer, s textNode) textNode { 83 | switch { 84 | case pp[0].IsNil(): 85 | return &textWrap{Value: s, Metadata: trunkReference{pp[1]}} 86 | case pp[1].IsNil(): 87 | return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} 88 | case pp[0] == pp[1]: 89 | return &textWrap{Value: s, Metadata: trunkReference{pp[0]}} 90 | default: 91 | return &textWrap{Value: s, Metadata: trunkReferences{pp}} 92 | } 93 | } 94 | func wrapTrunkReference(p value.Pointer, printAddress bool, s textNode) textNode { 95 | var prefix string 96 | if printAddress { 97 | prefix = formatPointer(p, true) 98 | } 99 | return &textWrap{Prefix: prefix, Value: s, Metadata: trunkReference{p}} 100 | } 101 | func makeLeafReference(p value.Pointer, printAddress bool) textNode { 102 | out := &textWrap{Prefix: "(", Value: textEllipsis, Suffix: ")"} 103 | var prefix string 104 | if printAddress { 105 | prefix = formatPointer(p, true) 106 | } 107 | return &textWrap{Prefix: prefix, Value: out, Metadata: leafReference{p}} 108 | } 109 | 110 | // resolveReferences walks the textNode tree searching for any leaf reference 111 | // metadata and resolves each against the corresponding trunk references. 112 | // Since pointer addresses in memory are not particularly readable to the user, 113 | // it replaces each pointer value with an arbitrary and unique reference ID. 114 | func resolveReferences(s textNode) { 115 | var walkNodes func(textNode, func(textNode)) 116 | walkNodes = func(s textNode, f func(textNode)) { 117 | f(s) 118 | switch s := s.(type) { 119 | case *textWrap: 120 | walkNodes(s.Value, f) 121 | case textList: 122 | for _, r := range s { 123 | walkNodes(r.Value, f) 124 | } 125 | } 126 | } 127 | 128 | // Collect all trunks and leaves with reference metadata. 129 | var trunks, leaves []*textWrap 130 | walkNodes(s, func(s textNode) { 131 | if s, ok := s.(*textWrap); ok { 132 | switch s.Metadata.(type) { 133 | case leafReference: 134 | leaves = append(leaves, s) 135 | case trunkReference, trunkReferences: 136 | trunks = append(trunks, s) 137 | } 138 | } 139 | }) 140 | 141 | // No leaf references to resolve. 142 | if len(leaves) == 0 { 143 | return 144 | } 145 | 146 | // Collect the set of all leaf references to resolve. 147 | leafPtrs := make(map[value.Pointer]bool) 148 | for _, leaf := range leaves { 149 | leafPtrs[leaf.Metadata.(leafReference).p] = true 150 | } 151 | 152 | // Collect the set of trunk pointers that are always paired together. 153 | // This allows us to assign a single ID to both pointers for brevity. 154 | // If a pointer in a pair ever occurs by itself or as a different pair, 155 | // then the pair is broken. 156 | pairedTrunkPtrs := make(map[value.Pointer]value.Pointer) 157 | unpair := func(p value.Pointer) { 158 | if !pairedTrunkPtrs[p].IsNil() { 159 | pairedTrunkPtrs[pairedTrunkPtrs[p]] = value.Pointer{} // invalidate other half 160 | } 161 | pairedTrunkPtrs[p] = value.Pointer{} // invalidate this half 162 | } 163 | for _, trunk := range trunks { 164 | switch p := trunk.Metadata.(type) { 165 | case trunkReference: 166 | unpair(p.p) // standalone pointer cannot be part of a pair 167 | case trunkReferences: 168 | p0, ok0 := pairedTrunkPtrs[p.pp[0]] 169 | p1, ok1 := pairedTrunkPtrs[p.pp[1]] 170 | switch { 171 | case !ok0 && !ok1: 172 | // Register the newly seen pair. 173 | pairedTrunkPtrs[p.pp[0]] = p.pp[1] 174 | pairedTrunkPtrs[p.pp[1]] = p.pp[0] 175 | case ok0 && ok1 && p0 == p.pp[1] && p1 == p.pp[0]: 176 | // Exact pair already seen; do nothing. 177 | default: 178 | // Pair conflicts with some other pair; break all pairs. 179 | unpair(p.pp[0]) 180 | unpair(p.pp[1]) 181 | } 182 | } 183 | } 184 | 185 | // Correlate each pointer referenced by leaves to a unique identifier, 186 | // and print the IDs for each trunk that matches those pointers. 187 | var nextID uint 188 | ptrIDs := make(map[value.Pointer]uint) 189 | newID := func() uint { 190 | id := nextID 191 | nextID++ 192 | return id 193 | } 194 | for _, trunk := range trunks { 195 | switch p := trunk.Metadata.(type) { 196 | case trunkReference: 197 | if print := leafPtrs[p.p]; print { 198 | id, ok := ptrIDs[p.p] 199 | if !ok { 200 | id = newID() 201 | ptrIDs[p.p] = id 202 | } 203 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) 204 | } 205 | case trunkReferences: 206 | print0 := leafPtrs[p.pp[0]] 207 | print1 := leafPtrs[p.pp[1]] 208 | if print0 || print1 { 209 | id0, ok0 := ptrIDs[p.pp[0]] 210 | id1, ok1 := ptrIDs[p.pp[1]] 211 | isPair := pairedTrunkPtrs[p.pp[0]] == p.pp[1] && pairedTrunkPtrs[p.pp[1]] == p.pp[0] 212 | if isPair { 213 | var id uint 214 | assert(ok0 == ok1) // must be seen together or not at all 215 | if ok0 { 216 | assert(id0 == id1) // must have the same ID 217 | id = id0 218 | } else { 219 | id = newID() 220 | ptrIDs[p.pp[0]] = id 221 | ptrIDs[p.pp[1]] = id 222 | } 223 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id)) 224 | } else { 225 | if print0 && !ok0 { 226 | id0 = newID() 227 | ptrIDs[p.pp[0]] = id0 228 | } 229 | if print1 && !ok1 { 230 | id1 = newID() 231 | ptrIDs[p.pp[1]] = id1 232 | } 233 | switch { 234 | case print0 && print1: 235 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)+","+formatReference(id1)) 236 | case print0: 237 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id0)) 238 | case print1: 239 | trunk.Prefix = updateReferencePrefix(trunk.Prefix, formatReference(id1)) 240 | } 241 | } 242 | } 243 | } 244 | } 245 | 246 | // Update all leaf references with the unique identifier. 247 | for _, leaf := range leaves { 248 | if id, ok := ptrIDs[leaf.Metadata.(leafReference).p]; ok { 249 | leaf.Prefix = updateReferencePrefix(leaf.Prefix, formatReference(id)) 250 | } 251 | } 252 | } 253 | 254 | func formatReference(id uint) string { 255 | return fmt.Sprintf("ref#%d", id) 256 | } 257 | 258 | func updateReferencePrefix(prefix, ref string) string { 259 | if prefix == "" { 260 | return pointerDelimPrefix + ref + pointerDelimSuffix 261 | } 262 | suffix := strings.TrimPrefix(prefix, pointerDelimPrefix) 263 | return pointerDelimPrefix + ref + ": " + suffix 264 | } 265 | -------------------------------------------------------------------------------- /vendor/github.com/google/go-cmp/cmp/report_value.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019, The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package cmp 6 | 7 | import "reflect" 8 | 9 | // valueNode represents a single node within a report, which is a 10 | // structured representation of the value tree, containing information 11 | // regarding which nodes are equal or not. 12 | type valueNode struct { 13 | parent *valueNode 14 | 15 | Type reflect.Type 16 | ValueX reflect.Value 17 | ValueY reflect.Value 18 | 19 | // NumSame is the number of leaf nodes that are equal. 20 | // All descendants are equal only if NumDiff is 0. 21 | NumSame int 22 | // NumDiff is the number of leaf nodes that are not equal. 23 | NumDiff int 24 | // NumIgnored is the number of leaf nodes that are ignored. 25 | NumIgnored int 26 | // NumCompared is the number of leaf nodes that were compared 27 | // using an Equal method or Comparer function. 28 | NumCompared int 29 | // NumTransformed is the number of non-leaf nodes that were transformed. 30 | NumTransformed int 31 | // NumChildren is the number of transitive descendants of this node. 32 | // This counts from zero; thus, leaf nodes have no descendants. 33 | NumChildren int 34 | // MaxDepth is the maximum depth of the tree. This counts from zero; 35 | // thus, leaf nodes have a depth of zero. 36 | MaxDepth int 37 | 38 | // Records is a list of struct fields, slice elements, or map entries. 39 | Records []reportRecord // If populated, implies Value is not populated 40 | 41 | // Value is the result of a transformation, pointer indirect, of 42 | // type assertion. 43 | Value *valueNode // If populated, implies Records is not populated 44 | 45 | // TransformerName is the name of the transformer. 46 | TransformerName string // If non-empty, implies Value is populated 47 | } 48 | type reportRecord struct { 49 | Key reflect.Value // Invalid for slice element 50 | Value *valueNode 51 | } 52 | 53 | func (parent *valueNode) PushStep(ps PathStep) (child *valueNode) { 54 | vx, vy := ps.Values() 55 | child = &valueNode{parent: parent, Type: ps.Type(), ValueX: vx, ValueY: vy} 56 | switch s := ps.(type) { 57 | case StructField: 58 | assert(parent.Value == nil) 59 | parent.Records = append(parent.Records, reportRecord{Key: reflect.ValueOf(s.Name()), Value: child}) 60 | case SliceIndex: 61 | assert(parent.Value == nil) 62 | parent.Records = append(parent.Records, reportRecord{Value: child}) 63 | case MapIndex: 64 | assert(parent.Value == nil) 65 | parent.Records = append(parent.Records, reportRecord{Key: s.Key(), Value: child}) 66 | case Indirect: 67 | assert(parent.Value == nil && parent.Records == nil) 68 | parent.Value = child 69 | case TypeAssertion: 70 | assert(parent.Value == nil && parent.Records == nil) 71 | parent.Value = child 72 | case Transform: 73 | assert(parent.Value == nil && parent.Records == nil) 74 | parent.Value = child 75 | parent.TransformerName = s.Name() 76 | parent.NumTransformed++ 77 | default: 78 | assert(parent == nil) // Must be the root step 79 | } 80 | return child 81 | } 82 | 83 | func (r *valueNode) Report(rs Result) { 84 | assert(r.MaxDepth == 0) // May only be called on leaf nodes 85 | 86 | if rs.ByIgnore() { 87 | r.NumIgnored++ 88 | } else { 89 | if rs.Equal() { 90 | r.NumSame++ 91 | } else { 92 | r.NumDiff++ 93 | } 94 | } 95 | assert(r.NumSame+r.NumDiff+r.NumIgnored == 1) 96 | 97 | if rs.ByMethod() { 98 | r.NumCompared++ 99 | } 100 | if rs.ByFunc() { 101 | r.NumCompared++ 102 | } 103 | assert(r.NumCompared <= 1) 104 | } 105 | 106 | func (child *valueNode) PopStep() (parent *valueNode) { 107 | if child.parent == nil { 108 | return nil 109 | } 110 | parent = child.parent 111 | parent.NumSame += child.NumSame 112 | parent.NumDiff += child.NumDiff 113 | parent.NumIgnored += child.NumIgnored 114 | parent.NumCompared += child.NumCompared 115 | parent.NumTransformed += child.NumTransformed 116 | parent.NumChildren += child.NumChildren + 1 117 | if parent.MaxDepth < child.MaxDepth+1 { 118 | parent.MaxDepth = child.MaxDepth + 1 119 | } 120 | return parent 121 | } 122 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.go text 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | - 1.8 6 | - tip 7 | 8 | script: 9 | - go test -v ./... 10 | - GOOS=linux go build 11 | - GOOS=darwin go build 12 | - GOOS=freebsd go build 13 | - GOOS=windows go build 14 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Quoc-Viet Nguyen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/README.md: -------------------------------------------------------------------------------- 1 | # serial [![Build Status](https://travis-ci.org/goburrow/serial.svg?branch=master)](https://travis-ci.org/goburrow/serial) [![GoDoc](https://godoc.org/github.com/goburrow/serial?status.svg)](https://godoc.org/github.com/goburrow/serial) 2 | ## Example 3 | ```go 4 | package main 5 | 6 | import ( 7 | "log" 8 | 9 | "github.com/goburrow/serial" 10 | ) 11 | 12 | func main() { 13 | port, err := serial.Open(&serial.Config{Address: "/dev/ttyUSB0"}) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | defer port.Close() 18 | 19 | _, err = port.Write([]byte("serial")) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | } 24 | ``` 25 | ## Testing 26 | 27 | ### Linux and Mac OS 28 | - `socat -d -d pty,raw,echo=0 pty,raw,echo=0` 29 | - on Mac OS, the socat command can be installed using homebrew: 30 | ````brew install socat```` 31 | 32 | ### Windows 33 | - [Null-modem emulator](http://com0com.sourceforge.net/) 34 | - [Terminal](https://sites.google.com/site/terminalbpp/) 35 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/generate.bat: -------------------------------------------------------------------------------- 1 | go tool cgo -godefs types_windows.go | gofmt > ztypes_windows.go 2 | go generate syscall_windows.go 3 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/serial.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package serial provides a cross-platform serial reader and writer. 3 | */ 4 | package serial 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | "time" 10 | ) 11 | 12 | var ( 13 | // ErrTimeout is occurred when timing out. 14 | ErrTimeout = errors.New("serial: timeout") 15 | ) 16 | 17 | // Config is common configuration for serial port. 18 | type Config struct { 19 | // Device path (/dev/ttyS0) 20 | Address string 21 | // Baud rate (default 19200) 22 | BaudRate int 23 | // Data bits: 5, 6, 7 or 8 (default 8) 24 | DataBits int 25 | // Stop bits: 1 or 2 (default 1) 26 | StopBits int 27 | // Parity: N - None, E - Even, O - Odd (default E) 28 | // (The use of no parity requires 2 stop bits.) 29 | Parity string 30 | // Read (Write) timeout. 31 | Timeout time.Duration 32 | // Configuration related to RS485 33 | RS485 RS485Config 34 | } 35 | 36 | // platform independent RS485 config. Thie structure is ignored unless Enable is true. 37 | type RS485Config struct { 38 | // Enable RS485 support 39 | Enabled bool 40 | // Delay RTS prior to send 41 | DelayRtsBeforeSend time.Duration 42 | // Delay RTS after send 43 | DelayRtsAfterSend time.Duration 44 | // Set RTS high during send 45 | RtsHighDuringSend bool 46 | // Set RTS high after send 47 | RtsHighAfterSend bool 48 | // Rx during Tx 49 | RxDuringTx bool 50 | } 51 | 52 | // Port is the interface for controlling serial port. 53 | type Port io.ReadWriteCloser 54 | 55 | // Open opens a serial port. 56 | func Open(c *Config) (Port, error) { 57 | return open(c) 58 | } 59 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/serial_bsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd netbsd 2 | 3 | package serial 4 | 5 | import ( 6 | "fmt" 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var baudRates = map[int]uint32{ 12 | 50: syscall.B50, 13 | 75: syscall.B75, 14 | 110: syscall.B110, 15 | 134: syscall.B134, 16 | 150: syscall.B150, 17 | 200: syscall.B200, 18 | 300: syscall.B300, 19 | 600: syscall.B600, 20 | 1200: syscall.B1200, 21 | 1800: syscall.B1800, 22 | 2400: syscall.B2400, 23 | 4800: syscall.B4800, 24 | 9600: syscall.B9600, 25 | 19200: syscall.B19200, 26 | 38400: syscall.B38400, 27 | 57600: syscall.B57600, 28 | 115200: syscall.B115200, 29 | 230400: syscall.B230400, 30 | 460800: syscall.B460800, 31 | } 32 | 33 | var charSizes = map[int]uint32{ 34 | 5: syscall.CS5, 35 | 6: syscall.CS6, 36 | 7: syscall.CS7, 37 | 8: syscall.CS8, 38 | } 39 | 40 | // syscallSelect is a wapper for syscall.Select that only returns error. 41 | func syscallSelect(n int, r *syscall.FdSet, w *syscall.FdSet, e *syscall.FdSet, tv *syscall.Timeval) error { 42 | return syscall.Select(n, r, w, e, tv) 43 | } 44 | 45 | // tcsetattr sets terminal file descriptor parameters. 46 | // See man tcsetattr(3). 47 | func tcsetattr(fd int, termios *syscall.Termios) (err error) { 48 | r, _, errno := syscall.Syscall(uintptr(syscall.SYS_IOCTL), 49 | uintptr(fd), uintptr(syscall.TIOCSETA), uintptr(unsafe.Pointer(termios))) 50 | if errno != 0 { 51 | err = errno 52 | return 53 | } 54 | if r != 0 { 55 | err = fmt.Errorf("tcsetattr failed %v", r) 56 | } 57 | return 58 | } 59 | 60 | // tcgetattr gets terminal file descriptor parameters. 61 | // See man tcgetattr(3). 62 | func tcgetattr(fd int, termios *syscall.Termios) (err error) { 63 | r, _, errno := syscall.Syscall(uintptr(syscall.SYS_IOCTL), 64 | uintptr(fd), uintptr(syscall.TIOCGETA), uintptr(unsafe.Pointer(termios))) 65 | if errno != 0 { 66 | err = errno 67 | return 68 | } 69 | if r != 0 { 70 | err = fmt.Errorf("tcgetattr failed %v", r) 71 | return 72 | } 73 | return 74 | } 75 | 76 | // fdget returns index and offset of fd in fds. 77 | func fdget(fd int, fds *syscall.FdSet) (index, offset int) { 78 | index = fd / (syscall.FD_SETSIZE / len(fds.X__fds_bits)) % len(fds.X__fds_bits) 79 | offset = fd % (syscall.FD_SETSIZE / len(fds.X__fds_bits)) 80 | return 81 | } 82 | 83 | // fdset implements FD_SET macro. 84 | func fdset(fd int, fds *syscall.FdSet) { 85 | idx, pos := fdget(fd, fds) 86 | fds.X__fds_bits[idx] = 1 << uint(pos) 87 | } 88 | 89 | // fdisset implements FD_ISSET macro. 90 | func fdisset(fd int, fds *syscall.FdSet) bool { 91 | idx, pos := fdget(fd, fds) 92 | return fds.X__fds_bits[idx]&(1< 0 { 97 | timeout := syscall.NsecToTimeval(p.timeout.Nanoseconds()) 98 | tv = &timeout 99 | } 100 | for { 101 | // If syscall.Select() returns EINTR (Interrupted system call), retry it 102 | if err = syscallSelect(fd+1, &rfds, nil, nil, tv); err == nil { 103 | break 104 | } 105 | if err != syscall.EINTR { 106 | err = fmt.Errorf("serial: could not select: %v", err) 107 | return 108 | } 109 | } 110 | if !fdisset(fd, &rfds) { 111 | // Timeout 112 | err = ErrTimeout 113 | return 114 | } 115 | n, err = syscall.Read(fd, b) 116 | if n == 0 && err == nil { 117 | err = io.ErrUnexpectedEOF 118 | } 119 | return 120 | } 121 | 122 | // Write writes data to the serial port. 123 | func (p *port) Write(b []byte) (n int, err error) { 124 | n, err = syscall.Write(p.fd, b) 125 | return 126 | } 127 | 128 | func (p *port) setTermios(termios *syscall.Termios) (err error) { 129 | if err = tcsetattr(p.fd, termios); err != nil { 130 | err = fmt.Errorf("serial: could not set setting: %v", err) 131 | } 132 | return 133 | } 134 | 135 | // backupTermios saves current termios setting. 136 | // Make sure that device file has been opened before calling this function. 137 | func (p *port) backupTermios() { 138 | oldTermios := &syscall.Termios{} 139 | if err := tcgetattr(p.fd, oldTermios); err != nil { 140 | // Warning only. 141 | log.Printf("serial: could not get setting: %v\n", err) 142 | return 143 | } 144 | // Will be reloaded when closing. 145 | p.oldTermios = oldTermios 146 | } 147 | 148 | // restoreTermios restores backed up termios setting. 149 | // Make sure that device file has been opened before calling this function. 150 | func (p *port) restoreTermios() { 151 | if p.oldTermios == nil { 152 | return 153 | } 154 | if err := tcsetattr(p.fd, p.oldTermios); err != nil { 155 | // Warning only. 156 | log.Printf("serial: could not restore setting: %v\n", err) 157 | return 158 | } 159 | p.oldTermios = nil 160 | } 161 | 162 | // Helpers for termios 163 | 164 | func newTermios(c *Config) (termios *syscall.Termios, err error) { 165 | termios = &syscall.Termios{} 166 | flag := termios.Cflag 167 | // Baud rate 168 | if c.BaudRate == 0 { 169 | // 19200 is the required default. 170 | flag = syscall.B19200 171 | } else { 172 | var ok bool 173 | flag, ok = baudRates[c.BaudRate] 174 | if !ok { 175 | err = fmt.Errorf("serial: unsupported baud rate %v", c.BaudRate) 176 | return 177 | } 178 | } 179 | termios.Cflag |= flag 180 | // Input baud. 181 | cfSetIspeed(termios, flag) 182 | // Output baud. 183 | cfSetOspeed(termios, flag) 184 | // Character size. 185 | if c.DataBits == 0 { 186 | flag = syscall.CS8 187 | } else { 188 | var ok bool 189 | flag, ok = charSizes[c.DataBits] 190 | if !ok { 191 | err = fmt.Errorf("serial: unsupported character size %v", c.DataBits) 192 | return 193 | } 194 | } 195 | termios.Cflag |= flag 196 | // Stop bits 197 | switch c.StopBits { 198 | case 0, 1: 199 | // Default is one stop bit. 200 | // noop 201 | case 2: 202 | // CSTOPB: Set two stop bits. 203 | termios.Cflag |= syscall.CSTOPB 204 | default: 205 | err = fmt.Errorf("serial: unsupported stop bits %v", c.StopBits) 206 | return 207 | } 208 | switch c.Parity { 209 | case "N": 210 | // noop 211 | case "O": 212 | // PARODD: Parity is odd. 213 | termios.Cflag |= syscall.PARODD 214 | fallthrough 215 | case "", "E": 216 | // As mentioned in the modbus spec, the default parity mode must be Even parity 217 | // PARENB: Enable parity generation on output. 218 | termios.Cflag |= syscall.PARENB 219 | // INPCK: Enable input parity checking. 220 | termios.Iflag |= syscall.INPCK 221 | default: 222 | err = fmt.Errorf("serial: unsupported parity %v", c.Parity) 223 | return 224 | } 225 | // Control modes. 226 | // CREAD: Enable receiver. 227 | // CLOCAL: Ignore control lines. 228 | termios.Cflag |= syscall.CREAD | syscall.CLOCAL 229 | // Special characters. 230 | // VMIN: Minimum number of characters for noncanonical read. 231 | // VTIME: Time in deciseconds for noncanonical read. 232 | // Both are unused as NDELAY is we utilized when opening device. 233 | return 234 | } 235 | 236 | // enableRS485 enables RS485 functionality of driver via an ioctl if the config says so 237 | func enableRS485(fd int, config *RS485Config) error { 238 | if !config.Enabled { 239 | return nil 240 | } 241 | rs485 := rs485_ioctl_opts{ 242 | rs485Enabled, 243 | uint32(config.DelayRtsBeforeSend / time.Millisecond), 244 | uint32(config.DelayRtsAfterSend / time.Millisecond), 245 | [5]uint32{0, 0, 0, 0, 0}, 246 | } 247 | 248 | if config.RtsHighDuringSend { 249 | rs485.flags |= rs485RTSOnSend 250 | } 251 | if config.RtsHighAfterSend { 252 | rs485.flags |= rs485RTSAfterSend 253 | } 254 | if config.RxDuringTx { 255 | rs485.flags |= rs485RXDuringTX 256 | } 257 | 258 | r, _, errno := syscall.Syscall( 259 | syscall.SYS_IOCTL, 260 | uintptr(fd), 261 | uintptr(rs485Tiocs), 262 | uintptr(unsafe.Pointer(&rs485))) 263 | if errno != 0 { 264 | return os.NewSyscallError("SYS_IOCTL (RS485)", errno) 265 | } 266 | if r != 0 { 267 | return errors.New("serial: unknown error from SYS_IOCTL (RS485)") 268 | } 269 | return nil 270 | } 271 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/serial_windows.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "fmt" 5 | "syscall" 6 | ) 7 | 8 | type port struct { 9 | handle syscall.Handle 10 | 11 | oldDCB c_DCB 12 | oldTimeouts c_COMMTIMEOUTS 13 | } 14 | 15 | // New allocates and returns a new serial port controller. 16 | func newPort() *port { 17 | return &port{ 18 | handle: syscall.InvalidHandle, 19 | } 20 | } 21 | 22 | // Open connects to the given serial port. 23 | func open(c *Config) (Port, error) { 24 | p := newPort() 25 | h, err := newHandle(c) 26 | if err != nil { 27 | return nil, err 28 | } 29 | p.handle = h 30 | defer func() { 31 | if err != nil { 32 | syscall.CloseHandle(p.handle) 33 | p.handle = syscall.InvalidHandle 34 | } 35 | }() 36 | 37 | if err = p.setSerialConfig(c); err != nil { 38 | return nil, err 39 | } 40 | return p, p.setTimeouts(c) 41 | } 42 | 43 | func (p *port) Close() (err error) { 44 | if p.handle == syscall.InvalidHandle { 45 | return 46 | } 47 | err1 := SetCommTimeouts(p.handle, &p.oldTimeouts) 48 | err2 := SetCommState(p.handle, &p.oldDCB) 49 | err = syscall.CloseHandle(p.handle) 50 | if err == nil { 51 | if err1 == nil { 52 | err = err2 53 | } else { 54 | err = err1 55 | } 56 | } 57 | p.handle = syscall.InvalidHandle 58 | return 59 | } 60 | 61 | // Read reads from serial port. 62 | // It is blocked until data received or timeout after p.timeout. 63 | func (p *port) Read(b []byte) (n int, err error) { 64 | var done uint32 65 | if err = syscall.ReadFile(p.handle, b, &done, nil); err != nil { 66 | return 67 | } 68 | if done == 0 { 69 | err = ErrTimeout 70 | return 71 | } 72 | n = int(done) 73 | return 74 | } 75 | 76 | // Write writes data to the serial port. 77 | func (p *port) Write(b []byte) (n int, err error) { 78 | var done uint32 79 | if err = syscall.WriteFile(p.handle, b, &done, nil); err != nil { 80 | return 81 | } 82 | n = int(done) 83 | return 84 | } 85 | 86 | func (p *port) setTimeouts(c *Config) error { 87 | var timeouts c_COMMTIMEOUTS 88 | // Read and write timeout 89 | if c.Timeout > 0 { 90 | timeout := toDWORD(int(c.Timeout.Nanoseconds() / 1e6)) 91 | // wait until a byte arrived or time out 92 | timeouts.ReadIntervalTimeout = c_MAXDWORD 93 | timeouts.ReadTotalTimeoutMultiplier = c_MAXDWORD 94 | timeouts.ReadTotalTimeoutConstant = timeout 95 | timeouts.WriteTotalTimeoutConstant = timeout 96 | } 97 | err := GetCommTimeouts(p.handle, &p.oldTimeouts) 98 | if err != nil { 99 | return err 100 | } 101 | err = SetCommTimeouts(p.handle, &timeouts) 102 | if err != nil { 103 | // reset 104 | SetCommTimeouts(p.handle, &p.oldTimeouts) 105 | } 106 | return err 107 | } 108 | 109 | func (p *port) setSerialConfig(c *Config) error { 110 | var dcb c_DCB 111 | if c.BaudRate == 0 { 112 | dcb.BaudRate = 19200 113 | } else { 114 | dcb.BaudRate = toDWORD(c.BaudRate) 115 | } 116 | // Data bits 117 | if c.DataBits == 0 { 118 | dcb.ByteSize = 8 119 | } else { 120 | dcb.ByteSize = toBYTE(c.DataBits) 121 | } 122 | // Stop bits 123 | switch c.StopBits { 124 | case 0, 1: 125 | // Default is one stop bit. 126 | dcb.StopBits = c_ONESTOPBIT 127 | case 2: 128 | dcb.StopBits = c_TWOSTOPBITS 129 | default: 130 | return fmt.Errorf("serial: unsupported stop bits %v", c.StopBits) 131 | } 132 | // Parity 133 | switch c.Parity { 134 | case "", "E": 135 | // Default parity mode is Even. 136 | dcb.Parity = c_EVENPARITY 137 | dcb.Pad_cgo_0[0] |= 0x02 // fParity 138 | case "O": 139 | dcb.Parity = c_ODDPARITY 140 | dcb.Pad_cgo_0[0] |= 0x02 // fParity 141 | case "N": 142 | dcb.Parity = c_NOPARITY 143 | default: 144 | return fmt.Errorf("serial: unsupported parity %v", c.Parity) 145 | } 146 | dcb.Pad_cgo_0[0] |= 0x01 // fBinary 147 | 148 | err := GetCommState(p.handle, &p.oldDCB) 149 | if err != nil { 150 | return err 151 | } 152 | err = SetCommState(p.handle, &dcb) 153 | if err != nil { 154 | SetCommState(p.handle, &p.oldDCB) 155 | } 156 | return err 157 | } 158 | 159 | func newHandle(c *Config) (handle syscall.Handle, err error) { 160 | handle, err = syscall.CreateFile( 161 | syscall.StringToUTF16Ptr(c.Address), 162 | syscall.GENERIC_READ|syscall.GENERIC_WRITE, 163 | 0, // mode 164 | nil, // security 165 | syscall.OPEN_EXISTING, // create mode 166 | 0, // attributes 167 | 0) // templates 168 | return 169 | } 170 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/termios_bsd.go: -------------------------------------------------------------------------------- 1 | // +build freebsd netbsd 2 | 3 | package serial 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func cfSetIspeed(termios *syscall.Termios, speed uint32) { 10 | termios.Ispeed = speed 11 | } 12 | 13 | func cfSetOspeed(termios *syscall.Termios, speed uint32) { 14 | termios.Ospeed = speed 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/termios_darwin.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | func cfSetIspeed(termios *syscall.Termios, speed uint64) { 8 | termios.Ispeed = speed 9 | } 10 | 11 | func cfSetOspeed(termios *syscall.Termios, speed uint64) { 12 | termios.Ospeed = speed 13 | } 14 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/termios_linux.go: -------------------------------------------------------------------------------- 1 | // +build !mips,!mipsle,!mips64,!mips64le 2 | 3 | package serial 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | func cfSetIspeed(termios *syscall.Termios, speed uint32) { 10 | termios.Ispeed = speed 11 | } 12 | 13 | func cfSetOspeed(termios *syscall.Termios, speed uint32) { 14 | termios.Ospeed = speed 15 | } 16 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/termios_mipsx.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // +build mips mipsle mips64 mips64le 3 | 4 | package serial 5 | 6 | import ( 7 | "syscall" 8 | ) 9 | 10 | func cfSetIspeed(termios *syscall.Termios, speed uint32) { 11 | // MIPS has no Ispeed field in termios. 12 | } 13 | 14 | func cfSetOspeed(termios *syscall.Termios, speed uint32) { 15 | // MIPS has no Ospeed field in termios. 16 | } 17 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/termios_openbsd.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import ( 4 | "syscall" 5 | ) 6 | 7 | func cfSetIspeed(termios *syscall.Termios, speed uint32) { 8 | termios.Ispeed = int32(speed) 9 | } 10 | 11 | func cfSetOspeed(termios *syscall.Termios, speed uint32) { 12 | termios.Ospeed = int32(speed) 13 | } 14 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/zsyscall_windows.go: -------------------------------------------------------------------------------- 1 | // MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT 2 | 3 | package serial 4 | 5 | import ( 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | var _ unsafe.Pointer 11 | 12 | var ( 13 | modkernel32 = syscall.NewLazyDLL("kernel32.dll") 14 | 15 | procGetCommState = modkernel32.NewProc("GetCommState") 16 | procSetCommState = modkernel32.NewProc("SetCommState") 17 | procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts") 18 | procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") 19 | ) 20 | 21 | func GetCommState(handle syscall.Handle, dcb *c_DCB) (err error) { 22 | r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) 23 | if r1 == 0 { 24 | if e1 != 0 { 25 | err = error(e1) 26 | } else { 27 | err = syscall.EINVAL 28 | } 29 | } 30 | return 31 | } 32 | 33 | func SetCommState(handle syscall.Handle, dcb *c_DCB) (err error) { 34 | r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(dcb)), 0) 35 | if r1 == 0 { 36 | if e1 != 0 { 37 | err = error(e1) 38 | } else { 39 | err = syscall.EINVAL 40 | } 41 | } 42 | return 43 | } 44 | 45 | func GetCommTimeouts(handle syscall.Handle, timeouts *c_COMMTIMEOUTS) (err error) { 46 | r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) 47 | if r1 == 0 { 48 | if e1 != 0 { 49 | err = error(e1) 50 | } else { 51 | err = syscall.EINVAL 52 | } 53 | } 54 | return 55 | } 56 | 57 | func SetCommTimeouts(handle syscall.Handle, timeouts *c_COMMTIMEOUTS) (err error) { 58 | r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) 59 | if r1 == 0 { 60 | if e1 != 0 { 61 | err = error(e1) 62 | } else { 63 | err = syscall.EINVAL 64 | } 65 | } 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /vendor/github.com/grid-x/serial/ztypes_windows.go: -------------------------------------------------------------------------------- 1 | // Created by cgo -godefs - DO NOT EDIT 2 | // cgo -godefs types_windows.go 3 | 4 | package serial 5 | 6 | const ( 7 | c_MAXDWORD = 0xffffffff 8 | c_ONESTOPBIT = 0x0 9 | c_TWOSTOPBITS = 0x2 10 | c_EVENPARITY = 0x2 11 | c_ODDPARITY = 0x1 12 | c_NOPARITY = 0x0 13 | ) 14 | 15 | type c_COMMTIMEOUTS struct { 16 | ReadIntervalTimeout uint32 17 | ReadTotalTimeoutMultiplier uint32 18 | ReadTotalTimeoutConstant uint32 19 | WriteTotalTimeoutMultiplier uint32 20 | WriteTotalTimeoutConstant uint32 21 | } 22 | 23 | type c_DCB struct { 24 | DCBlength uint32 25 | BaudRate uint32 26 | Pad_cgo_0 [4]byte 27 | WReserved uint16 28 | XonLim uint16 29 | XoffLim uint16 30 | ByteSize uint8 31 | Parity uint8 32 | StopBits uint8 33 | XonChar int8 34 | XoffChar int8 35 | ErrorChar int8 36 | EofChar int8 37 | EvtChar int8 38 | WReserved1 uint16 39 | } 40 | 41 | func toDWORD(val int) uint32 { 42 | return uint32(val) 43 | } 44 | 45 | func toBYTE(val int) uint8 { 46 | return uint8(val) 47 | } 48 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/google/go-cmp v0.6.0 2 | ## explicit; go 1.13 3 | github.com/google/go-cmp/cmp 4 | github.com/google/go-cmp/cmp/internal/diff 5 | github.com/google/go-cmp/cmp/internal/flags 6 | github.com/google/go-cmp/cmp/internal/function 7 | github.com/google/go-cmp/cmp/internal/value 8 | # github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa 9 | ## explicit; go 1.13 10 | github.com/grid-x/serial 11 | # pgregory.net/rapid v1.1.0 12 | ## explicit; go 1.18 13 | pgregory.net/rapid 14 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/.editorconfig: -------------------------------------------------------------------------------- 1 | root = True 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | [*.go] 9 | indent_style = tab 10 | indent_size = 8 11 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/.gitattributes: -------------------------------------------------------------------------------- 1 | *.go eol=lf 2 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.vscode/ 3 | *.swp 4 | 5 | /rapid.test 6 | /rapid.test.exe 7 | /cpu.prof 8 | /mem.prof 9 | /profile* 10 | /testdata/ 11 | 12 | /doc.html 13 | /vis-*.html 14 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 3 | Any issues reported are greatly appreciated. Please consider opening an issue 4 | not only in case you have encountered a bug, but if *anything* (be it API, 5 | functionality, workflow, docs, ...) looks like it can be improved. 6 | 7 | # Pull requests 8 | 9 | Please avoid "improve the code style" kind of pull requests; in particular 10 | `if block ends with a return statement, so drop this else and outdent its block` 11 | suggestion of `golint` should be ignored. 12 | 13 | If you intend to work on anything non-trivial, please open an issue first, 14 | to discuss the design and implementation before writing any code. 15 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/README.md: -------------------------------------------------------------------------------- 1 | # rapid [![PkgGoDev][godev-img]][godev] [![CI][ci-img]][ci] 2 | 3 | Rapid is a Go library for property-based testing. 4 | 5 | Rapid checks that properties you define hold for a large number 6 | of automatically generated test cases. If a failure is found, rapid 7 | automatically minimizes the failing test case before presenting it. 8 | 9 | ## Features 10 | 11 | - Imperative Go API with type-safe data generation using generics 12 | - Data generation biased to explore "small" values and edge cases more thoroughly 13 | - Fully automatic minimization of failing test cases 14 | - Persistence and automatic re-running of minimized failing test cases 15 | - Support for state machine ("stateful" or "model-based") testing 16 | - No dependencies outside the Go standard library 17 | 18 | ## Examples 19 | 20 | Here is what a trivial test using rapid looks like ([playground](https://go.dev/play/p/QJhOzo_BByz)): 21 | 22 | ```go 23 | package rapid_test 24 | 25 | import ( 26 | "sort" 27 | "testing" 28 | 29 | "pgregory.net/rapid" 30 | ) 31 | 32 | func TestSortStrings(t *testing.T) { 33 | rapid.Check(t, func(t *rapid.T) { 34 | s := rapid.SliceOf(rapid.String()).Draw(t, "s") 35 | sort.Strings(s) 36 | if !sort.StringsAreSorted(s) { 37 | t.Fatalf("unsorted after sort: %v", s) 38 | } 39 | }) 40 | } 41 | ``` 42 | 43 | More complete examples: 44 | 45 | - `ParseDate` function test: 46 | [source code](./example_function_test.go), [playground](https://go.dev/play/p/tZFU8zv8AUl) 47 | - `Queue` state machine test: 48 | [source code](./example_statemachine_test.go), [playground](https://go.dev/play/p/cxEh4deG-4n) 49 | 50 | ## Comparison 51 | 52 | Rapid aims to bring to Go the power and convenience 53 | [Hypothesis](https://github.com/HypothesisWorks/hypothesis) brings to Python. 54 | 55 | Compared to [testing.F.Fuzz](https://pkg.go.dev/testing#F.Fuzz), rapid shines 56 | in generating complex structured data, including state machine tests, but lacks 57 | coverage-guided feedback and mutations. Note that with 58 | [`MakeFuzz`](https://pkg.go.dev/pgregory.net/rapid#MakeFuzz), any rapid test 59 | can be used as a fuzz target for the standard fuzzer. 60 | 61 | Compared to [gopter](https://pkg.go.dev/github.com/leanovate/gopter), rapid 62 | provides a much simpler API (queue test in [rapid](./example_statemachine_test.go) vs 63 | [gopter](https://github.com/leanovate/gopter/blob/90cc76d7f1b21637b4b912a7c19dea3efe145bb2/commands/example_circularqueue_test.go)), 64 | is much smarter about data generation and is able to minimize failing test cases 65 | fully automatically, without any user code. 66 | 67 | As for [testing/quick](https://pkg.go.dev/testing/quick), it lacks both 68 | convenient data generation facilities and any form of test case minimization, which 69 | are two main things to look for in a property-based testing library. 70 | 71 | ## FAQ 72 | 73 | ### What is property-based testing? 74 | 75 | Suppose we've written arithmetic functions `add`, `subtract` and `multiply` 76 | and want to test them. Traditional testing approach is example-based — 77 | we come up with example inputs and outputs, and verify that the system behavior 78 | matches the examples: 79 | 80 | ```go 81 | func TestArithmetic_Example(t *testing.T) { 82 | t.Run("add", func(t *testing.T) { 83 | examples := [][3]int{ 84 | {0, 0, 0}, 85 | {0, 1, 1}, 86 | {2, 2, 4}, 87 | // ... 88 | } 89 | for _, e := range examples { 90 | if add(e[0], e[1]) != e[2] { 91 | t.Fatalf("add(%v, %v) != %v", e[0], e[1], e[2]) 92 | } 93 | } 94 | }) 95 | t.Run("subtract", func(t *testing.T) { /* ... */ }) 96 | t.Run("multiply", func(t *testing.T) { /* ... */ }) 97 | } 98 | ``` 99 | 100 | In comparison, with property-based testing we define higher-level properties 101 | that should hold for arbitrary input. Each time we run a property-based test, 102 | properties are checked on a new set of pseudo-random data: 103 | 104 | ```go 105 | func TestArithmetic_Property(t *testing.T) { 106 | rapid.Check(t, func(t *rapid.T) { 107 | var ( 108 | a = rapid.Int().Draw(t, "a") 109 | b = rapid.Int().Draw(t, "b") 110 | c = rapid.Int().Draw(t, "c") 111 | ) 112 | if add(a, 0) != a { 113 | t.Fatalf("add() does not have 0 as identity") 114 | } 115 | if add(a, b) != add(b, a) { 116 | t.Fatalf("add() is not commutative") 117 | } 118 | if add(a, add(b, c)) != add(add(a, b), c) { 119 | t.Fatalf("add() is not associative") 120 | } 121 | if multiply(a, add(b, c)) != add(multiply(a, b), multiply(a, c)) { 122 | t.Fatalf("multiply() is not distributive over add()") 123 | } 124 | // ... 125 | }) 126 | } 127 | ``` 128 | 129 | Property-based tests are more powerful and concise than example-based ones — 130 | and are also much more fun to write. As an additional benefit, coming up with 131 | general properties of the system often improves the design of the system itself. 132 | 133 | ### What properties should I test? 134 | 135 | As you've seen from the examples above, it depends on the system you are testing. 136 | Usually a good place to start is to put yourself in the shoes of your user 137 | and ask what are the properties the user will rely on (often unknowingly or 138 | implicitly) when building on top of your system. That said, here are some 139 | broadly applicable and often encountered properties to keep in mind: 140 | 141 | - function does not panic on valid input data 142 | - behavior of two algorithms or data structures is identical 143 | - all variants of the `decode(encode(x)) == x` roundtrip 144 | 145 | ### How does rapid work? 146 | 147 | At its core, rapid does a fairly simple thing: generates pseudo-random data 148 | based on the specification you provide, and check properties that you define 149 | on the generated data. 150 | 151 | Checking is easy: you simply write `if` statements and call something like 152 | `t.Fatalf` when things look wrong. 153 | 154 | Generating is a bit more involved. When you construct a `Generator`, nothing 155 | happens: `Generator` is just a specification of how to `Draw` the data you 156 | want. When you call `Draw`, rapid will take some bytes from its internal 157 | random bitstream, use them to construct the value based on the `Generator` 158 | specification, and track how the random bytes used correspond to the value 159 | (and its subparts). This knowledge about the structure of the values being 160 | generated, as well as their relationship with the parts of the bitstream 161 | allows rapid to intelligently and automatically minify any failure found. 162 | 163 | ### What about fuzzing? 164 | 165 | Property-based testing focuses on quick feedback loop: checking the properties 166 | on a small but diverse set of pseudo-random inputs in a fractions of a second. 167 | 168 | In comparison, fuzzing focuses on slow, often multi-day, brute force input 169 | generation that maximizes the coverage. 170 | 171 | Both approaches are useful. Property-based tests are used alongside regular 172 | example-based tests during development, and fuzzing is used to search for edge 173 | cases and security vulnerabilities. With 174 | [`MakeFuzz`](https://pkg.go.dev/pgregory.net/rapid#MakeFuzz), any rapid test 175 | can be used as a fuzz target. 176 | 177 | ## Usage 178 | 179 | Just run `go test` as usual, it will pick up also all `rapid` tests. 180 | 181 | There are a number of optional flags to influence rapid behavior, run 182 | `go test -args -h` and look at the flags with the `-rapid.` prefix. You can 183 | then pass such flags as usual. For example: 184 | 185 | ```sh 186 | go test -rapid.checks=10_000 187 | ``` 188 | 189 | ## Status 190 | 191 | Rapid is stable: tests using rapid should continue to work with all future 192 | rapid releases with the same major version. Possible exceptions to this rule 193 | are API changes that replace the concrete type of parameter with an interface 194 | type, or other similar mostly non-breaking changes. 195 | 196 | ## License 197 | 198 | Rapid is licensed under the [Mozilla Public License Version 2.0](./LICENSE). 199 | 200 | [godev-img]: https://pkg.go.dev/badge/pgregory.net/rapid 201 | [godev]: https://pkg.go.dev/pgregory.net/rapid 202 | [ci-img]: https://github.com/flyingmutant/rapid/workflows/CI/badge.svg 203 | [ci]: https://github.com/flyingmutant/rapid/actions 204 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## Generators 4 | 5 | - times, durations, locations 6 | - complex numbers 7 | - big numbers 8 | - ip addresses & masks 9 | - subset-of-slice 10 | - runes with rune/range blacklist 11 | - recursive (base + extend) 12 | 13 | ## Shrinking 14 | 15 | - make it OK to pivot to a different error when shrinking 16 | - right now, we require for traceback to remain the same to continue shrinking, which is probably limiting 17 | - floats: maybe shrink towards lower *biased* exponent? 18 | - just like we have lower+delete pass to deal with situations like generation/sampling, we need to have a pass for choice 19 | - idea: lower (the "choice" block) + fill some region with random data 20 | - to try to reproduce with a simpler choice 21 | - this should work both OneOf and floats (where exponent is kind of a OneOf key) 22 | - questions: 23 | - how to deal with misalignment? 24 | - how to determine the group to randomize? 25 | - e.g. right now for floats it is not an explicit group but rather a bunch of nearby blocks 26 | - use fewer bits for genFloat01 to make shrinking a bit faster 27 | - shrink duplicates together 28 | - generalize to arbitrary "offsets" for pairs 29 | - better caching 30 | - detect when we are generating already generated values and abort early 31 | - not all value groups are standalone! 32 | - standalone might be too coarse, maybe should be replaced with a bunch of other traits 33 | - we are doing too much prop evaluations 34 | - partial sort does not swap e.g. int and int32 35 | - when shrinking, if we try to lower the wanted bits of some uint64, we have a high chance to draw very low value 36 | - because high bits will be masked out 37 | - this can prevent shrinking, when we first lower block A (which e.g. selects the generator), then 38 | we draw next block B (which the lowered generator wants fewer bits of). Instead of getting a bit value for B 39 | and doing proper search, we end up getting a small one, and abandoning the generator shrink 40 | - for order-based passes, try alternating orders? 41 | - what order is a better default? 42 | - "prefix search" shrinking 43 | - when shrinking, why do we leave the tail the same? 44 | - we have "misalignment" problems and all that 45 | - generate random data instead! 46 | - generate random tails all the time 47 | - minimize bitstream mis-alignment during shrinking (try to make the shape as constant as possible) 48 | - better, make minimization not care about mis-alignment 49 | - sticky bitstream? 50 | - differentiate groups with structure vs groups without one for smarter shrinking 51 | - non-greedy shrink 52 | - allow to increase the data size *between shrink passes*, if the net result is good 53 | - e.g. allow sort to do arbitrary? swaps 54 | - rejection sampling during shrinking leads to data misalignment, is this a problem? 55 | - can we detect overruns early and re-roll only the last part of the bitstream? 56 | - maybe overwrite bitstream instead of prune? 57 | - to never have an un-pruned version 58 | - to guarantee? that we can draw values successfully while shrinking (working with bufBitStream) 59 | 60 | ## Misc 61 | 62 | - ability to run tests without shrinking (e.g. for running non-deterministic tests) 63 | - bitStream -> blockStream? 64 | - do not play with filter games for the state machine, just find all valid actions 65 | - when generating numbers in range, try to bias based on the min number, 66 | just like we bias repeat based on the min number? 67 | - because min number defines the "magnitude" of the whole thing, kind of? 68 | - so when we are generating numbers in [1000000; +inf) we do not stick with 1000000 too hard 69 | - more powerful assume/filter (look at what hypothesis is doing) 70 | - incorporate special case checking (bounds esp.) 71 | 72 | ## Wild ideas 73 | 74 | - global path-based generation (kind of like simplex method), which makes most of the generators hit corner cases simultaneously 75 | - recurrence-based generation, because it is hard to stumble upon interesting stuff purely by random 76 | - start generating already generated stuff, overriding random for some number of draws 77 | - zip the sequence with itself 78 | - random jumps of rng, back/forward 79 | - recurrence-based generation may actually be better than usual fuzzing! 80 | - because we operate on 64 bits at once, which in most cases correspond to "full value", 81 | we have e.g. a much better chance to reproduce a multi-byte sequence (exact or slightly altered) somewhere else 82 | - this is kind-of related to go-fuzz versifier in some way 83 | - we also can (and should) reuse whole chunks which can correspond to strings/lists/etc. 84 | - random markov chain which switches states like 85 | - generate new data 86 | - reuse existing data starting from 87 | - reuse existing data altering it like X 88 | - should transition probabilities be universal or depend on generators? 89 | - should they also determine where to jump to, so that we jump to "compatible" stuff only? 90 | - can tag words with compatibility classes 91 | - can just jump to previous starts of the generated blocks? 92 | - can explore/exploit trade-off help us decide when to generate random data, and when to reuse existing? 93 | - probably can do thompson sampling when we have online coverage information 94 | - arbiter-based distributed system tester 95 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/collections.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import "fmt" 10 | 11 | // ID returns its argument as is. ID is a helper for use with [SliceOfDistinct] and similar functions. 12 | func ID[V any](v V) V { 13 | return v 14 | } 15 | 16 | // SliceOf is a shorthand for [SliceOfN](elem, -1, -1). 17 | func SliceOf[E any](elem *Generator[E]) *Generator[[]E] { 18 | return SliceOfN(elem, -1, -1) 19 | } 20 | 21 | // SliceOfN creates a []E generator. If minLen >= 0, generated slices have minimum length of minLen. 22 | // If maxLen >= 0, generated slices have maximum length of maxLen. SliceOfN panics if maxLen >= 0 23 | // and minLen > maxLen. 24 | func SliceOfN[E any](elem *Generator[E], minLen int, maxLen int) *Generator[[]E] { 25 | assertValidRange(minLen, maxLen) 26 | 27 | return newGenerator[[]E](&sliceGen[E, struct{}]{ 28 | minLen: minLen, 29 | maxLen: maxLen, 30 | elem: elem, 31 | }) 32 | } 33 | 34 | // SliceOfDistinct is a shorthand for [SliceOfNDistinct](elem, -1, -1, keyFn). 35 | func SliceOfDistinct[E any, K comparable](elem *Generator[E], keyFn func(E) K) *Generator[[]E] { 36 | return SliceOfNDistinct(elem, -1, -1, keyFn) 37 | } 38 | 39 | // SliceOfNDistinct creates a []E generator. Elements of each generated slice are distinct according to keyFn. 40 | // If minLen >= 0, generated slices have minimum length of minLen. If maxLen >= 0, generated slices 41 | // have maximum length of maxLen. SliceOfNDistinct panics if maxLen >= 0 and minLen > maxLen. 42 | // [ID] helper can be used as keyFn to generate slices of distinct comparable elements. 43 | func SliceOfNDistinct[E any, K comparable](elem *Generator[E], minLen int, maxLen int, keyFn func(E) K) *Generator[[]E] { 44 | assertValidRange(minLen, maxLen) 45 | 46 | return newGenerator[[]E](&sliceGen[E, K]{ 47 | minLen: minLen, 48 | maxLen: maxLen, 49 | elem: elem, 50 | keyFn: keyFn, 51 | }) 52 | } 53 | 54 | type sliceGen[E any, K comparable] struct { 55 | minLen int 56 | maxLen int 57 | elem *Generator[E] 58 | keyFn func(E) K 59 | } 60 | 61 | func (g *sliceGen[E, K]) String() string { 62 | if g.keyFn == nil { 63 | if g.minLen < 0 && g.maxLen < 0 { 64 | return fmt.Sprintf("SliceOf(%v)", g.elem) 65 | } else { 66 | return fmt.Sprintf("SliceOfN(%v, minLen=%v, maxLen=%v)", g.elem, g.minLen, g.maxLen) 67 | } 68 | } else { 69 | if g.minLen < 0 && g.maxLen < 0 { 70 | return fmt.Sprintf("SliceOfDistinct(%v, key=%T)", g.elem, g.keyFn) 71 | } else { 72 | return fmt.Sprintf("SliceOfNDistinct(%v, minLen=%v, maxLen=%v, key=%T)", g.elem, g.minLen, g.maxLen, g.keyFn) 73 | } 74 | } 75 | } 76 | 77 | func (g *sliceGen[E, K]) value(t *T) []E { 78 | repeat := newRepeat(g.minLen, g.maxLen, -1, g.elem.String()) 79 | 80 | var seen map[K]struct{} 81 | if g.keyFn != nil { 82 | seen = make(map[K]struct{}, repeat.avg()) 83 | } 84 | 85 | sl := make([]E, 0, repeat.avg()) 86 | for repeat.more(t.s) { 87 | e := g.elem.value(t) 88 | if g.keyFn == nil { 89 | sl = append(sl, e) 90 | } else { 91 | k := g.keyFn(e) 92 | if _, ok := seen[k]; ok { 93 | repeat.reject() 94 | } else { 95 | seen[k] = struct{}{} 96 | sl = append(sl, e) 97 | } 98 | } 99 | } 100 | 101 | return sl 102 | } 103 | 104 | // MapOf is a shorthand for [MapOfN](key, val, -1, -1). 105 | func MapOf[K comparable, V any](key *Generator[K], val *Generator[V]) *Generator[map[K]V] { 106 | return MapOfN(key, val, -1, -1) 107 | } 108 | 109 | // MapOfN creates a map[K]V generator. If minLen >= 0, generated maps have minimum length of minLen. 110 | // If maxLen >= 0, generated maps have maximum length of maxLen. MapOfN panics if maxLen >= 0 111 | // and minLen > maxLen. 112 | func MapOfN[K comparable, V any](key *Generator[K], val *Generator[V], minLen int, maxLen int) *Generator[map[K]V] { 113 | assertValidRange(minLen, maxLen) 114 | 115 | return newGenerator[map[K]V](&mapGen[K, V]{ 116 | minLen: minLen, 117 | maxLen: maxLen, 118 | key: key, 119 | val: val, 120 | }) 121 | } 122 | 123 | // MapOfValues is a shorthand for [MapOfNValues](val, -1, -1, keyFn). 124 | func MapOfValues[K comparable, V any](val *Generator[V], keyFn func(V) K) *Generator[map[K]V] { 125 | return MapOfNValues(val, -1, -1, keyFn) 126 | } 127 | 128 | // MapOfNValues creates a map[K]V generator, where keys are generated by applying keyFn to values. 129 | // If minLen >= 0, generated maps have minimum length of minLen. If maxLen >= 0, generated maps 130 | // have maximum length of maxLen. MapOfNValues panics if maxLen >= 0 and minLen > maxLen. 131 | func MapOfNValues[K comparable, V any](val *Generator[V], minLen int, maxLen int, keyFn func(V) K) *Generator[map[K]V] { 132 | assertValidRange(minLen, maxLen) 133 | 134 | return newGenerator[map[K]V](&mapGen[K, V]{ 135 | minLen: minLen, 136 | maxLen: maxLen, 137 | val: val, 138 | keyFn: keyFn, 139 | }) 140 | } 141 | 142 | type mapGen[K comparable, V any] struct { 143 | minLen int 144 | maxLen int 145 | key *Generator[K] 146 | val *Generator[V] 147 | keyFn func(V) K 148 | } 149 | 150 | func (g *mapGen[K, V]) String() string { 151 | if g.key != nil { 152 | if g.minLen < 0 && g.maxLen < 0 { 153 | return fmt.Sprintf("MapOf(%v, %v)", g.key, g.val) 154 | } else { 155 | return fmt.Sprintf("MapOfN(%v, %v, minLen=%v, maxLen=%v)", g.key, g.val, g.minLen, g.maxLen) 156 | } 157 | } else { 158 | if g.minLen < 0 && g.maxLen < 0 { 159 | return fmt.Sprintf("MapOfValues(%v, key=%T)", g.val, g.keyFn) 160 | } else { 161 | return fmt.Sprintf("MapOfNValues(%v, minLen=%v, maxLen=%v, key=%T)", g.val, g.minLen, g.maxLen, g.keyFn) 162 | } 163 | } 164 | } 165 | 166 | func (g *mapGen[K, V]) value(t *T) map[K]V { 167 | label := g.val.String() 168 | if g.key != nil { 169 | label = g.key.String() + "," + label 170 | } 171 | 172 | repeat := newRepeat(g.minLen, g.maxLen, -1, label) 173 | 174 | m := make(map[K]V, repeat.avg()) 175 | for repeat.more(t.s) { 176 | var k K 177 | var v V 178 | if g.key != nil { 179 | k = g.key.value(t) 180 | v = g.val.value(t) 181 | } else { 182 | v = g.val.value(t) 183 | k = g.keyFn(v) 184 | } 185 | 186 | if _, ok := m[k]; ok { 187 | repeat.reject() 188 | } else { 189 | m[k] = v 190 | } 191 | } 192 | 193 | return m 194 | } 195 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/combinators.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "fmt" 11 | "math" 12 | "strings" 13 | ) 14 | 15 | const tryLabel = "try" 16 | 17 | // Custom creates a generator which produces results of calling fn. In fn, values should be generated 18 | // by calling other generators; it is invalid to return a value from fn without using any other generator. 19 | // Custom is a primary way of creating user-defined generators. 20 | func Custom[V any](fn func(*T) V) *Generator[V] { 21 | return newGenerator[V](&customGen[V]{ 22 | fn: fn, 23 | }) 24 | } 25 | 26 | type customGen[V any] struct { 27 | fn func(*T) V 28 | } 29 | 30 | func (g *customGen[V]) String() string { 31 | var v V 32 | return fmt.Sprintf("Custom(%T)", v) 33 | } 34 | 35 | func (g *customGen[V]) value(t *T) V { 36 | return find(g.maybeValue, t, small) 37 | } 38 | 39 | func (g *customGen[V]) maybeValue(t *T) (V, bool) { 40 | t = newT(t.tb, t.s, flags.debug, nil) 41 | 42 | defer func() { 43 | if r := recover(); r != nil { 44 | if _, ok := r.(invalidData); !ok { 45 | panic(r) 46 | } 47 | } 48 | }() 49 | 50 | return g.fn(t), true 51 | } 52 | 53 | // Deferred creates a generator which defers calling fn until attempting to produce a value. This allows 54 | // to define recursive generators. 55 | func Deferred[V any](fn func() *Generator[V]) *Generator[V] { 56 | return newGenerator[V](&deferredGen[V]{ 57 | fn: fn, 58 | }) 59 | } 60 | 61 | type deferredGen[V any] struct { 62 | g *Generator[V] 63 | fn func() *Generator[V] 64 | } 65 | 66 | func (g *deferredGen[V]) String() string { 67 | var v V 68 | return fmt.Sprintf("Deferred(%T)", v) 69 | } 70 | 71 | func (g *deferredGen[V]) value(t *T) V { 72 | if g.g == nil { 73 | g.g = g.fn() 74 | } 75 | return g.g.value(t) 76 | } 77 | 78 | func filter[V any](g *Generator[V], fn func(V) bool) *Generator[V] { 79 | return newGenerator[V](&filteredGen[V]{ 80 | g: g, 81 | fn: fn, 82 | }) 83 | } 84 | 85 | type filteredGen[V any] struct { 86 | g *Generator[V] 87 | fn func(V) bool 88 | } 89 | 90 | func (g *filteredGen[V]) String() string { 91 | return fmt.Sprintf("%v.Filter(...)", g.g) 92 | } 93 | 94 | func (g *filteredGen[V]) value(t *T) V { 95 | return find(g.maybeValue, t, small) 96 | } 97 | 98 | func (g *filteredGen[V]) maybeValue(t *T) (V, bool) { 99 | v := g.g.value(t) 100 | if g.fn(v) { 101 | return v, true 102 | } else { 103 | var zero V 104 | return zero, false 105 | } 106 | } 107 | 108 | func find[V any](gen func(*T) (V, bool), t *T, tries int) V { 109 | for n := 0; n < tries; n++ { 110 | i := t.s.beginGroup(tryLabel, false) 111 | v, ok := gen(t) 112 | t.s.endGroup(i, !ok) 113 | if ok { 114 | return v 115 | } 116 | } 117 | 118 | panic(invalidData(fmt.Sprintf("failed to find suitable value in %d tries", tries))) 119 | } 120 | 121 | // Map creates a generator producing fn(u) for each u produced by g. 122 | func Map[U any, V any](g *Generator[U], fn func(U) V) *Generator[V] { 123 | return newGenerator[V](&mappedGen[U, V]{ 124 | g: g, 125 | fn: fn, 126 | }) 127 | } 128 | 129 | type mappedGen[U any, V any] struct { 130 | g *Generator[U] 131 | fn func(U) V 132 | } 133 | 134 | func (g *mappedGen[U, V]) String() string { 135 | return fmt.Sprintf("Map(%v, %T)", g.g, g.fn) 136 | } 137 | 138 | func (g *mappedGen[U, V]) value(t *T) V { 139 | return g.fn(g.g.value(t)) 140 | } 141 | 142 | // Just creates a generator which always produces the given value. 143 | // Just(val) is a shorthand for [SampledFrom]([]V{val}). 144 | func Just[V any](val V) *Generator[V] { 145 | return SampledFrom([]V{val}) 146 | } 147 | 148 | // SampledFrom creates a generator which produces values from the given slice. 149 | // SampledFrom panics if slice is empty. 150 | func SampledFrom[S ~[]E, E any](slice S) *Generator[E] { 151 | assertf(len(slice) > 0, "slice should not be empty") 152 | 153 | return newGenerator[E](&sampledGen[E]{ 154 | slice: slice, 155 | }) 156 | } 157 | 158 | type sampledGen[E any] struct { 159 | slice []E 160 | } 161 | 162 | func (g *sampledGen[E]) String() string { 163 | if len(g.slice) == 1 { 164 | return fmt.Sprintf("Just(%v)", g.slice[0]) 165 | } else { 166 | return fmt.Sprintf("SampledFrom(%v %T)", len(g.slice), g.slice[0]) 167 | } 168 | } 169 | 170 | func (g *sampledGen[E]) value(t *T) E { 171 | i := genIndex(t.s, len(g.slice), true) 172 | 173 | return g.slice[i] 174 | } 175 | 176 | // Permutation creates a generator which produces permutations of the given slice. 177 | func Permutation[S ~[]E, E any](slice S) *Generator[S] { 178 | return newGenerator[S](&permGen[S, E]{ 179 | slice: slice, 180 | }) 181 | } 182 | 183 | type permGen[S ~[]E, E any] struct { 184 | slice S 185 | } 186 | 187 | func (g *permGen[S, E]) String() string { 188 | var zero E 189 | return fmt.Sprintf("Permutation(%v %T)", len(g.slice), zero) 190 | } 191 | 192 | func (g *permGen[S, E]) value(t *T) S { 193 | s := append(S(nil), g.slice...) 194 | n := len(s) 195 | m := n - 1 196 | if m < 0 { 197 | m = 0 198 | } 199 | 200 | // shrink-friendly variant of Fisher–Yates shuffle: shrinks to lower number of smaller distance swaps 201 | repeat := newRepeat(0, m, math.MaxInt, "permute") 202 | for i := 0; repeat.more(t.s); i++ { 203 | j, _, _ := genUintRange(t.s, uint64(i), uint64(n-1), false) 204 | s[i], s[j] = s[j], s[i] 205 | } 206 | 207 | return s 208 | } 209 | 210 | // OneOf creates a generator which produces each value by selecting one of gens and producing a value from it. 211 | // OneOf panics if gens is empty. 212 | func OneOf[V any](gens ...*Generator[V]) *Generator[V] { 213 | assertf(len(gens) > 0, "at least one generator should be specified") 214 | 215 | return newGenerator[V](&oneOfGen[V]{ 216 | gens: gens, 217 | }) 218 | } 219 | 220 | type oneOfGen[V any] struct { 221 | gens []*Generator[V] 222 | } 223 | 224 | func (g *oneOfGen[V]) String() string { 225 | strs := make([]string, len(g.gens)) 226 | for i, g := range g.gens { 227 | strs[i] = g.String() 228 | } 229 | 230 | return fmt.Sprintf("OneOf(%v)", strings.Join(strs, ", ")) 231 | } 232 | 233 | func (g *oneOfGen[V]) value(t *T) V { 234 | i := genIndex(t.s, len(g.gens), true) 235 | 236 | return g.gens[i].value(t) 237 | } 238 | 239 | // Ptr creates a *E generator. If allowNil is true, Ptr can return nil pointers. 240 | func Ptr[E any](elem *Generator[E], allowNil bool) *Generator[*E] { 241 | return newGenerator[*E](&ptrGen[E]{ 242 | elem: elem, 243 | allowNil: allowNil, 244 | }) 245 | } 246 | 247 | type ptrGen[E any] struct { 248 | elem *Generator[E] 249 | allowNil bool 250 | } 251 | 252 | func (g *ptrGen[E]) String() string { 253 | return fmt.Sprintf("Ptr(%v, allowNil=%v)", g.elem, g.allowNil) 254 | } 255 | 256 | func (g *ptrGen[E]) value(t *T) *E { 257 | pNonNil := float64(1) 258 | if g.allowNil { 259 | pNonNil = 0.5 260 | } 261 | 262 | if flipBiasedCoin(t.s, pNonNil) { 263 | e := g.elem.value(t) 264 | return &e 265 | } else { 266 | return nil 267 | } 268 | } 269 | 270 | func asAny[V any](g *Generator[V]) *Generator[any] { 271 | return newGenerator[any](&asAnyGen[V]{ 272 | gen: g, 273 | }) 274 | } 275 | 276 | type asAnyGen[V any] struct { 277 | gen *Generator[V] 278 | } 279 | 280 | func (g *asAnyGen[V]) String() string { 281 | return fmt.Sprintf("%v.AsAny()", g.gen) 282 | } 283 | 284 | func (g *asAnyGen[V]) value(t *T) any { 285 | return g.gen.value(t) 286 | } 287 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/data.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "hash/maphash" 11 | "math" 12 | "math/bits" 13 | ) 14 | 15 | type bitStream interface { 16 | drawBits(n int) uint64 17 | beginGroup(label string, standalone bool) int 18 | endGroup(i int, discard bool) 19 | } 20 | 21 | func baseSeed() uint64 { 22 | if flags.seed != 0 { 23 | return flags.seed 24 | } 25 | 26 | return new(maphash.Hash).Sum64() 27 | } 28 | 29 | type randomBitStream struct { 30 | ctx jsf64ctx 31 | recordedBits 32 | } 33 | 34 | func newRandomBitStream(seed uint64, persist bool) *randomBitStream { 35 | s := &randomBitStream{} 36 | s.init(seed) 37 | s.persist = persist 38 | return s 39 | } 40 | 41 | func (s *randomBitStream) init(seed uint64) { 42 | s.ctx.init(seed) 43 | } 44 | 45 | func (s *randomBitStream) drawBits(n int) uint64 { 46 | assert(n >= 0) 47 | 48 | var u uint64 49 | if n <= 64 { 50 | u = s.ctx.rand() & bitmask64(uint(n)) 51 | } else { 52 | u = math.MaxUint64 53 | } 54 | s.record(u) 55 | 56 | return u 57 | } 58 | 59 | type bufBitStream struct { 60 | buf []uint64 61 | recordedBits 62 | } 63 | 64 | func newBufBitStream(buf []uint64, persist bool) *bufBitStream { 65 | s := &bufBitStream{ 66 | buf: buf, 67 | } 68 | s.persist = persist 69 | return s 70 | } 71 | 72 | func (s *bufBitStream) drawBits(n int) uint64 { 73 | assert(n >= 0) 74 | 75 | if len(s.buf) == 0 { 76 | panic(invalidData("overrun")) 77 | } 78 | 79 | u := s.buf[0] & bitmask64(uint(n)) 80 | s.record(u) 81 | s.buf = s.buf[1:] 82 | 83 | return u 84 | } 85 | 86 | type groupInfo struct { 87 | begin int 88 | end int 89 | label string 90 | standalone bool 91 | discard bool 92 | } 93 | 94 | type recordedBits struct { 95 | data []uint64 96 | groups []groupInfo 97 | dataLen int 98 | persist bool 99 | } 100 | 101 | func (rec *recordedBits) record(u uint64) { 102 | if rec.persist { 103 | rec.data = append(rec.data, u) 104 | } else { 105 | rec.dataLen++ 106 | } 107 | } 108 | 109 | func (rec *recordedBits) beginGroup(label string, standalone bool) int { 110 | if !rec.persist { 111 | return rec.dataLen 112 | } 113 | 114 | rec.groups = append(rec.groups, groupInfo{ 115 | begin: len(rec.data), 116 | end: -1, 117 | label: label, 118 | standalone: standalone, 119 | }) 120 | 121 | return len(rec.groups) - 1 122 | } 123 | 124 | func (rec *recordedBits) endGroup(i int, discard bool) { 125 | assertf(discard || (!rec.persist && rec.dataLen > i) || (rec.persist && len(rec.data) > rec.groups[i].begin), 126 | "group did not use any data from bitstream; this is likely a result of Custom generator not calling any of the built-in generators") 127 | 128 | if !rec.persist { 129 | return 130 | } 131 | 132 | rec.groups[i].end = len(rec.data) 133 | rec.groups[i].discard = discard 134 | } 135 | 136 | func (rec *recordedBits) prune() { 137 | assert(rec.persist) 138 | 139 | for i := 0; i < len(rec.groups); { 140 | if rec.groups[i].discard { 141 | rec.removeGroup(i) // O(n^2) 142 | } else { 143 | i++ 144 | } 145 | } 146 | 147 | for _, g := range rec.groups { 148 | assert(g.begin != g.end) 149 | } 150 | } 151 | 152 | func (rec *recordedBits) removeGroup(i int) { 153 | g := rec.groups[i] 154 | assert(g.end >= 0) 155 | 156 | j := i + 1 157 | for j < len(rec.groups) && rec.groups[j].end <= g.end { 158 | j++ 159 | } 160 | 161 | rec.data = append(rec.data[:g.begin], rec.data[g.end:]...) 162 | rec.groups = append(rec.groups[:i], rec.groups[j:]...) 163 | 164 | n := g.end - g.begin 165 | for j := range rec.groups { 166 | if rec.groups[j].begin >= g.end { 167 | rec.groups[j].begin -= n 168 | } 169 | if rec.groups[j].end >= g.end { 170 | rec.groups[j].end -= n 171 | } 172 | } 173 | } 174 | 175 | // "A Small Noncryptographic PRNG" by Bob Jenkins 176 | // See http://www.pcg-random.org/posts/bob-jenkins-small-prng-passes-practrand.html for some recent analysis. 177 | type jsf64ctx struct { 178 | a uint64 179 | b uint64 180 | c uint64 181 | d uint64 182 | } 183 | 184 | func (x *jsf64ctx) init(seed uint64) { 185 | x.a = 0xf1ea5eed 186 | x.b = seed 187 | x.c = seed 188 | x.d = seed 189 | 190 | for i := 0; i < 20; i++ { 191 | x.rand() 192 | } 193 | } 194 | 195 | func (x *jsf64ctx) rand() uint64 { 196 | e := x.a - bits.RotateLeft64(x.b, 7) 197 | x.a = x.b ^ bits.RotateLeft64(x.c, 13) 198 | x.b = x.c + bits.RotateLeft64(x.d, 37) 199 | x.c = x.d + e 200 | x.d = e + x.a 201 | return x.d 202 | } 203 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | /* 8 | Package rapid implements utilities for property-based testing. 9 | 10 | [Check] verifies that properties you define hold for a large number 11 | of automatically generated test cases. If a failure is found, rapid 12 | fails the current test and presents an automatically minimized 13 | version of the failing test case. 14 | 15 | [T.Repeat] is used to construct state machine (sometimes called "stateful" 16 | or "model-based") tests. 17 | 18 | # Generators 19 | 20 | Primitives: 21 | - [Bool] 22 | - [Rune], [RuneFrom] 23 | - [Byte], [ByteMin], [ByteMax], [ByteRange] 24 | - [Int], [IntMin], [IntMax], [IntRange] 25 | - [Int8], [Int8Min], [Int8Max], [Int8Range] 26 | - [Int16], [Int16Min], [Int16Max], [Int16Range] 27 | - [Int32], [Int32Min], [Int32Max], [Int32Range] 28 | - [Int64], [Int64Min], [Int64Max], [Int64Range] 29 | - [Uint], [UintMin], [UintMax], [UintRange] 30 | - [Uint8], [Uint8Min], [Uint8Max], [Uint8Range] 31 | - [Uint16], [Uint16Min], [Uint16Max], [Uint16Range] 32 | - [Uint32], [Uint32Min], [Uint32Max], [Uint32Range] 33 | - [Uint64], [Uint64Min], [Uint64Max], [Uint64Range] 34 | - [Uintptr], [UintptrMin], [UintptrMax], [UintptrRange] 35 | - [Float32], [Float32Min], [Float32Max], [Float32Range] 36 | - [Float64], [Float64Min], [Float64Max], [Float64Range] 37 | 38 | Collections: 39 | - [String], [StringMatching], [StringOf], [StringOfN], [StringN] 40 | - [SliceOfBytesMatching] 41 | - [SliceOf], [SliceOfN], [SliceOfDistinct], [SliceOfNDistinct] 42 | - [Permutation] 43 | - [MapOf], [MapOfN], [MapOfValues], [MapOfNValues] 44 | 45 | User-defined types: 46 | - [Custom] 47 | - [Make] 48 | 49 | Other: 50 | - [Map], 51 | - [Generator.Filter] 52 | - [SampledFrom], [Just] 53 | - [OneOf] 54 | - [Deferred] 55 | - [Ptr] 56 | */ 57 | package rapid 58 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/floats.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "fmt" 11 | "math" 12 | "math/bits" 13 | ) 14 | 15 | const ( 16 | float32ExpBits = 8 17 | float32SignifBits = 23 18 | 19 | float64ExpBits = 11 20 | float64SignifBits = 52 21 | 22 | floatExpLabel = "floatexp" 23 | floatSignifLabel = "floatsignif" 24 | ) 25 | 26 | // Float32 is a shorthand for [Float32Range](-[math.MaxFloat32], [math.MaxFloat32]). 27 | func Float32() *Generator[float32] { 28 | return Float32Range(-math.MaxFloat32, math.MaxFloat32) 29 | } 30 | 31 | // Float32Min is a shorthand for [Float32Range](min, [math.MaxFloat32]). 32 | func Float32Min(min float32) *Generator[float32] { 33 | return Float32Range(min, math.MaxFloat32) 34 | } 35 | 36 | // Float32Max is a shorthand for [Float32Range](-[math.MaxFloat32], max). 37 | func Float32Max(max float32) *Generator[float32] { 38 | return Float32Range(-math.MaxFloat32, max) 39 | } 40 | 41 | // Float32Range creates a generator of 32-bit floating-point numbers in range [min, max]. 42 | // Both min and max can be infinite. 43 | func Float32Range(min float32, max float32) *Generator[float32] { 44 | assertf(min == min, "min should not be a NaN") 45 | assertf(max == max, "max should not be a NaN") 46 | assertf(min <= max, "invalid range [%v, %v]", min, max) 47 | 48 | return newGenerator[float32](&float32Gen{ 49 | floatGen{ 50 | min: float64(min), 51 | max: float64(max), 52 | minVal: -math.MaxFloat32, 53 | maxVal: math.MaxFloat32, 54 | }, 55 | }) 56 | } 57 | 58 | // Float64 is a shorthand for [Float64Range](-[math.MaxFloat64], [math.MaxFloat64]). 59 | func Float64() *Generator[float64] { 60 | return Float64Range(-math.MaxFloat64, math.MaxFloat64) 61 | } 62 | 63 | // Float64Min is a shorthand for [Float64Range](min, [math.MaxFloat64]). 64 | func Float64Min(min float64) *Generator[float64] { 65 | return Float64Range(min, math.MaxFloat64) 66 | } 67 | 68 | // Float64Max is a shorthand for [Float64Range](-[math.MaxFloat64], max). 69 | func Float64Max(max float64) *Generator[float64] { 70 | return Float64Range(-math.MaxFloat64, max) 71 | } 72 | 73 | // Float64Range creates a generator of 64-bit floating-point numbers in range [min, max]. 74 | // Both min and max can be infinite. 75 | func Float64Range(min float64, max float64) *Generator[float64] { 76 | assertf(min == min, "min should not be a NaN") 77 | assertf(max == max, "max should not be a NaN") 78 | assertf(min <= max, "invalid range [%v, %v]", min, max) 79 | 80 | return newGenerator[float64](&float64Gen{ 81 | floatGen{ 82 | min: min, 83 | max: max, 84 | minVal: -math.MaxFloat64, 85 | maxVal: math.MaxFloat64, 86 | }, 87 | }) 88 | } 89 | 90 | type floatGen struct { 91 | min float64 92 | max float64 93 | minVal float64 94 | maxVal float64 95 | } 96 | type float32Gen struct{ floatGen } 97 | type float64Gen struct{ floatGen } 98 | 99 | func (g *floatGen) stringImpl(kind string) string { 100 | if g.min != g.minVal && g.max != g.maxVal { 101 | return fmt.Sprintf("%sRange(%g, %g)", kind, g.min, g.max) 102 | } else if g.min != g.minVal { 103 | return fmt.Sprintf("%sMin(%g)", kind, g.min) 104 | } else if g.max != g.maxVal { 105 | return fmt.Sprintf("%sMax(%g)", kind, g.max) 106 | } 107 | 108 | return fmt.Sprintf("%s()", kind) 109 | } 110 | func (g *float32Gen) String() string { 111 | return g.stringImpl("Float32") 112 | } 113 | func (g *float64Gen) String() string { 114 | return g.stringImpl("Float64") 115 | } 116 | 117 | func (g *float32Gen) value(t *T) float32 { 118 | return float32FromParts(genFloatRange(t.s, g.min, g.max, float32SignifBits)) 119 | } 120 | func (g *float64Gen) value(t *T) float64 { 121 | return float64FromParts(genFloatRange(t.s, g.min, g.max, float64SignifBits)) 122 | } 123 | 124 | func ufloatFracBits(e int32, signifBits uint) uint { 125 | if e <= 0 { 126 | return signifBits 127 | } else if uint(e) < signifBits { 128 | return signifBits - uint(e) 129 | } else { 130 | return 0 131 | } 132 | } 133 | 134 | func ufloat32Parts(f float32) (int32, uint64, uint64) { 135 | u := math.Float32bits(f) & math.MaxInt32 136 | 137 | e := int32(u>>float32SignifBits) - int32(bitmask64(float32ExpBits-1)) 138 | s := uint64(u) & bitmask64(float32SignifBits) 139 | n := ufloatFracBits(e, float32SignifBits) 140 | 141 | return e, s >> n, s & bitmask64(n) 142 | } 143 | 144 | func ufloat64Parts(f float64) (int32, uint64, uint64) { 145 | u := math.Float64bits(f) & math.MaxInt64 146 | 147 | e := int32(u>>float64SignifBits) - int32(bitmask64(float64ExpBits-1)) 148 | s := u & bitmask64(float64SignifBits) 149 | n := ufloatFracBits(e, float64SignifBits) 150 | 151 | return e, s >> n, s & bitmask64(n) 152 | } 153 | 154 | func ufloat32FromParts(e int32, si uint64, sf uint64) float32 { 155 | e_ := (uint32(e) + uint32(bitmask64(float32ExpBits-1))) << float32SignifBits 156 | s_ := (uint32(si) << ufloatFracBits(e, float32SignifBits)) | uint32(sf) 157 | 158 | return math.Float32frombits(e_ | s_) 159 | } 160 | 161 | func ufloat64FromParts(e int32, si uint64, sf uint64) float64 { 162 | e_ := (uint64(e) + bitmask64(float64ExpBits-1)) << float64SignifBits 163 | s_ := (si << ufloatFracBits(e, float64SignifBits)) | sf 164 | 165 | return math.Float64frombits(e_ | s_) 166 | } 167 | 168 | func float32FromParts(sign bool, e int32, si uint64, sf uint64) float32 { 169 | f := ufloat32FromParts(e, si, sf) 170 | if sign { 171 | return -f 172 | } else { 173 | return f 174 | } 175 | } 176 | 177 | func float64FromParts(sign bool, e int32, si uint64, sf uint64) float64 { 178 | f := ufloat64FromParts(e, si, sf) 179 | if sign { 180 | return -f 181 | } else { 182 | return f 183 | } 184 | } 185 | 186 | func genUfloatRange(s bitStream, min float64, max float64, signifBits uint) (int32, uint64, uint64) { 187 | assert(min >= 0 && min <= max) 188 | 189 | var ( 190 | minExp, maxExp int32 191 | minSignifI, maxSignifI, minSignifF, maxSignifF uint64 192 | ) 193 | if signifBits == float32SignifBits { 194 | minExp, minSignifI, minSignifF = ufloat32Parts(float32(min)) 195 | maxExp, maxSignifI, maxSignifF = ufloat32Parts(float32(max)) 196 | } else { 197 | minExp, minSignifI, minSignifF = ufloat64Parts(min) 198 | maxExp, maxSignifI, maxSignifF = ufloat64Parts(max) 199 | } 200 | 201 | i := s.beginGroup(floatExpLabel, false) 202 | e, lOverflow, rOverflow := genIntRange(s, int64(minExp), int64(maxExp), true) 203 | s.endGroup(i, false) 204 | 205 | fracBits := ufloatFracBits(int32(e), signifBits) 206 | 207 | j := s.beginGroup(floatSignifLabel, false) 208 | var siMin, siMax uint64 209 | switch { 210 | case lOverflow: 211 | siMin, siMax = minSignifI, minSignifI 212 | case rOverflow: 213 | siMin, siMax = maxSignifI, maxSignifI 214 | case minExp == maxExp: 215 | siMin, siMax = minSignifI, maxSignifI 216 | case int32(e) == minExp: 217 | siMin, siMax = minSignifI, bitmask64(signifBits-fracBits) 218 | case int32(e) == maxExp: 219 | siMin, siMax = 0, maxSignifI 220 | default: 221 | siMin, siMax = 0, bitmask64(signifBits-fracBits) 222 | } 223 | si, _, _ := genUintRange(s, siMin, siMax, false) 224 | var sfMin, sfMax uint64 225 | switch { 226 | case lOverflow: 227 | sfMin, sfMax = minSignifF, minSignifF 228 | case rOverflow: 229 | sfMin, sfMax = maxSignifF, maxSignifF 230 | case minExp == maxExp && minSignifI == maxSignifI: 231 | sfMin, sfMax = minSignifF, maxSignifF 232 | case int32(e) == minExp && si == minSignifI: 233 | sfMin, sfMax = minSignifF, bitmask64(fracBits) 234 | case int32(e) == maxExp && si == maxSignifI: 235 | sfMin, sfMax = 0, maxSignifF 236 | default: 237 | sfMin, sfMax = 0, bitmask64(fracBits) 238 | } 239 | maxR := bits.Len64(sfMax - sfMin) 240 | r := genUintNNoReject(s, uint64(maxR)) 241 | sf, _, _ := genUintRange(s, sfMin, sfMax, false) 242 | s.endGroup(j, false) 243 | 244 | for i := uint(0); i < uint(maxR)-uint(r); i++ { 245 | mask := ^(uint64(1) << i) 246 | if sf&mask < sfMin { 247 | break 248 | } 249 | sf &= mask 250 | } 251 | 252 | return int32(e), si, sf 253 | } 254 | 255 | func genFloatRange(s bitStream, min float64, max float64, signifBits uint) (bool, int32, uint64, uint64) { 256 | var posMin, negMin, pNeg float64 257 | if min >= 0 { 258 | posMin = min 259 | pNeg = 0 260 | } else if max <= 0 { 261 | negMin = -max 262 | pNeg = 1 263 | } else { 264 | pNeg = 0.5 265 | } 266 | 267 | if flipBiasedCoin(s, pNeg) { 268 | e, si, sf := genUfloatRange(s, negMin, -min, signifBits) 269 | return true, e, si, sf 270 | } else { 271 | e, si, sf := genUfloatRange(s, posMin, max, signifBits) 272 | return false, e, si, sf 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/generator.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "fmt" 11 | "reflect" 12 | "sync" 13 | ) 14 | 15 | type generatorImpl[V any] interface { 16 | String() string 17 | value(t *T) V 18 | } 19 | 20 | // Generator describes a generator of values of type V. 21 | type Generator[V any] struct { 22 | impl generatorImpl[V] 23 | strOnce sync.Once 24 | str string 25 | } 26 | 27 | func newGenerator[V any](impl generatorImpl[V]) *Generator[V] { 28 | return &Generator[V]{ 29 | impl: impl, 30 | } 31 | } 32 | 33 | func (g *Generator[V]) String() string { 34 | g.strOnce.Do(func() { 35 | g.str = g.impl.String() 36 | }) 37 | 38 | return g.str 39 | } 40 | 41 | // Draw produces a value from the generator. 42 | func (g *Generator[V]) Draw(t *T, label string) V { 43 | if t.tbLog { 44 | t.tb.Helper() 45 | } 46 | 47 | v := g.value(t) 48 | 49 | if len(t.refDraws) > 0 { 50 | ref := t.refDraws[t.draws] 51 | if !reflect.DeepEqual(v, ref) { 52 | t.tb.Fatalf("draw %v differs: %#v vs expected %#v", t.draws, v, ref) 53 | } 54 | } 55 | 56 | if t.tbLog || t.rawLog != nil { 57 | if label == "" { 58 | label = fmt.Sprintf("#%v", t.draws) 59 | } 60 | 61 | if t.tbLog { 62 | t.tb.Helper() 63 | } 64 | t.Logf("[rapid] draw %v: %#v", label, v) 65 | } 66 | 67 | t.draws++ 68 | 69 | return v 70 | } 71 | 72 | func (g *Generator[V]) value(t *T) V { 73 | i := t.s.beginGroup(g.str, true) 74 | v := g.impl.value(t) 75 | t.s.endGroup(i, false) 76 | return v 77 | } 78 | 79 | // Example produces an example value from the generator. If seed is provided, value is produced deterministically 80 | // based on seed. Example should only be used for examples; always use *Generator.Draw in property-based tests. 81 | func (g *Generator[V]) Example(seed ...int) V { 82 | s := baseSeed() 83 | if len(seed) > 0 { 84 | s = uint64(seed[0]) 85 | } 86 | 87 | v, n, err := example(g, newT(nil, newRandomBitStream(s, false), false, nil)) 88 | assertf(err == nil, "%v failed to generate an example in %v tries: %v", g, n, err) 89 | 90 | return v 91 | } 92 | 93 | // Filter creates a generator producing only values from g for which fn returns true. 94 | func (g *Generator[V]) Filter(fn func(V) bool) *Generator[V] { 95 | return filter(g, fn) 96 | } 97 | 98 | // AsAny creates a generator producing values from g converted to any. 99 | func (g *Generator[V]) AsAny() *Generator[any] { 100 | return asAny(g) 101 | } 102 | 103 | func example[V any](g *Generator[V], t *T) (V, int, error) { 104 | for i := 1; ; i++ { 105 | r, err := recoverValue(g, t) 106 | if err == nil { 107 | return r, i, nil 108 | } else if i == exampleMaxTries { 109 | var zero V 110 | return zero, i, err 111 | } 112 | } 113 | } 114 | 115 | func recoverValue[V any](g *Generator[V], t *T) (v V, err *testError) { 116 | defer func() { err = panicToError(recover(), 3) }() 117 | 118 | return g.value(t), nil 119 | } 120 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/make.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "fmt" 11 | "reflect" 12 | ) 13 | 14 | // Make creates a generator of values of type V, using reflection to infer the required structure. 15 | func Make[V any]() *Generator[V] { 16 | var zero V 17 | gen := newMakeGen(reflect.TypeOf(zero)) 18 | return newGenerator[V](&makeGen[V]{ 19 | gen: gen, 20 | }) 21 | } 22 | 23 | type makeGen[V any] struct { 24 | gen *Generator[any] 25 | } 26 | 27 | func (g *makeGen[V]) String() string { 28 | var zero V 29 | return fmt.Sprintf("Make[%T]()", zero) 30 | } 31 | 32 | func (g *makeGen[V]) value(t *T) V { 33 | return g.gen.value(t).(V) 34 | } 35 | 36 | func newMakeGen(typ reflect.Type) *Generator[any] { 37 | gen, mayNeedCast := newMakeKindGen(typ) 38 | if !mayNeedCast || typ.String() == typ.Kind().String() { 39 | return gen // fast path with less reflect 40 | } 41 | return newGenerator[any](&castGen{gen, typ}) 42 | } 43 | 44 | type castGen struct { 45 | gen *Generator[any] 46 | typ reflect.Type 47 | } 48 | 49 | func (g *castGen) String() string { 50 | return fmt.Sprintf("cast(%v, %v)", g.gen, g.typ.Name()) 51 | } 52 | 53 | func (g *castGen) value(t *T) any { 54 | v := g.gen.value(t) 55 | return reflect.ValueOf(v).Convert(g.typ).Interface() 56 | } 57 | 58 | func newMakeKindGen(typ reflect.Type) (gen *Generator[any], mayNeedCast bool) { 59 | switch typ.Kind() { 60 | case reflect.Bool: 61 | return Bool().AsAny(), true 62 | case reflect.Int: 63 | return Int().AsAny(), true 64 | case reflect.Int8: 65 | return Int8().AsAny(), true 66 | case reflect.Int16: 67 | return Int16().AsAny(), true 68 | case reflect.Int32: 69 | return Int32().AsAny(), true 70 | case reflect.Int64: 71 | return Int64().AsAny(), true 72 | case reflect.Uint: 73 | return Uint().AsAny(), true 74 | case reflect.Uint8: 75 | return Uint8().AsAny(), true 76 | case reflect.Uint16: 77 | return Uint16().AsAny(), true 78 | case reflect.Uint32: 79 | return Uint32().AsAny(), true 80 | case reflect.Uint64: 81 | return Uint64().AsAny(), true 82 | case reflect.Uintptr: 83 | return Uintptr().AsAny(), true 84 | case reflect.Float32: 85 | return Float32().AsAny(), true 86 | case reflect.Float64: 87 | return Float64().AsAny(), true 88 | case reflect.Array: 89 | return genAnyArray(typ), false 90 | case reflect.Map: 91 | return genAnyMap(typ), false 92 | case reflect.Pointer: 93 | return Deferred(func() *Generator[any] { return genAnyPointer(typ) }), false 94 | case reflect.Slice: 95 | return genAnySlice(typ), false 96 | case reflect.String: 97 | return String().AsAny(), true 98 | case reflect.Struct: 99 | return genAnyStruct(typ), false 100 | default: 101 | panic(fmt.Sprintf("unsupported type kind for Make: %v", typ.Kind())) 102 | } 103 | } 104 | 105 | func genAnyPointer(typ reflect.Type) *Generator[any] { 106 | elem := typ.Elem() 107 | elemGen := newMakeGen(elem) 108 | const pNonNil = 0.5 109 | 110 | return Custom[any](func(t *T) any { 111 | if flipBiasedCoin(t.s, pNonNil) { 112 | val := elemGen.value(t) 113 | ptr := reflect.New(elem) 114 | ptr.Elem().Set(reflect.ValueOf(val)) 115 | return ptr.Interface() 116 | } else { 117 | return reflect.Zero(typ).Interface() 118 | } 119 | }) 120 | } 121 | 122 | func genAnyArray(typ reflect.Type) *Generator[any] { 123 | count := typ.Len() 124 | elemGen := newMakeGen(typ.Elem()) 125 | 126 | return Custom[any](func(t *T) any { 127 | a := reflect.Indirect(reflect.New(typ)) 128 | if count == 0 { 129 | t.s.drawBits(0) 130 | } else { 131 | for i := 0; i < count; i++ { 132 | e := reflect.ValueOf(elemGen.value(t)) 133 | a.Index(i).Set(e) 134 | } 135 | } 136 | return a.Interface() 137 | }) 138 | } 139 | 140 | func genAnySlice(typ reflect.Type) *Generator[any] { 141 | elemGen := newMakeGen(typ.Elem()) 142 | 143 | return Custom[any](func(t *T) any { 144 | repeat := newRepeat(-1, -1, -1, elemGen.String()) 145 | sl := reflect.MakeSlice(typ, 0, repeat.avg()) 146 | for repeat.more(t.s) { 147 | e := reflect.ValueOf(elemGen.value(t)) 148 | sl = reflect.Append(sl, e) 149 | } 150 | return sl.Interface() 151 | }) 152 | } 153 | 154 | func genAnyMap(typ reflect.Type) *Generator[any] { 155 | keyGen := newMakeGen(typ.Key()) 156 | valGen := newMakeGen(typ.Elem()) 157 | 158 | return Custom[any](func(t *T) any { 159 | label := keyGen.String() + "," + valGen.String() 160 | repeat := newRepeat(-1, -1, -1, label) 161 | m := reflect.MakeMapWithSize(typ, repeat.avg()) 162 | for repeat.more(t.s) { 163 | k := reflect.ValueOf(keyGen.value(t)) 164 | v := reflect.ValueOf(valGen.value(t)) 165 | if m.MapIndex(k).IsValid() { 166 | repeat.reject() 167 | } else { 168 | m.SetMapIndex(k, v) 169 | } 170 | } 171 | return m.Interface() 172 | }) 173 | } 174 | 175 | func genAnyStruct(typ reflect.Type) *Generator[any] { 176 | numFields := typ.NumField() 177 | fieldGens := make([]*Generator[any], numFields) 178 | for i := 0; i < numFields; i++ { 179 | fieldGens[i] = newMakeGen(typ.Field(i).Type) 180 | } 181 | 182 | return Custom[any](func(t *T) any { 183 | s := reflect.Indirect(reflect.New(typ)) 184 | if numFields == 0 { 185 | t.s.drawBits(0) 186 | } else { 187 | for i := 0; i < numFields; i++ { 188 | f := reflect.ValueOf(fieldGens[i].value(t)) 189 | s.Field(i).Set(f) 190 | } 191 | } 192 | return s.Interface() 193 | }) 194 | } 195 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/persist.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "bufio" 11 | "fmt" 12 | "os" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "time" 17 | "unicode" 18 | ) 19 | 20 | const ( 21 | rapidVersion = "v0.4.8" 22 | 23 | persistDirMode = 0775 24 | failfileTmpPattern = ".rapid-failfile-tmp-*" 25 | ) 26 | 27 | func kindaSafeFilename(f string) string { 28 | var s strings.Builder 29 | for _, r := range f { 30 | if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' || r == '_' { 31 | s.WriteRune(r) 32 | } else { 33 | s.WriteRune('_') 34 | } 35 | } 36 | return s.String() 37 | } 38 | 39 | func failFileName(testName string) (string, string) { 40 | ts := time.Now().Format("20060102150405") 41 | fileName := fmt.Sprintf("%s-%s-%d.fail", kindaSafeFilename(testName), ts, os.Getpid()) 42 | dirName := filepath.Join("testdata", "rapid", kindaSafeFilename(testName)) 43 | return dirName, filepath.Join(dirName, fileName) 44 | } 45 | 46 | func failFilePattern(testName string) string { 47 | fileName := fmt.Sprintf("%s-*.fail", kindaSafeFilename(testName)) 48 | dirName := filepath.Join("testdata", "rapid", kindaSafeFilename(testName)) 49 | return filepath.Join(dirName, fileName) 50 | } 51 | 52 | func saveFailFile(filename string, version string, output []byte, seed uint64, buf []uint64) error { 53 | dir := filepath.Dir(filename) 54 | err := os.MkdirAll(dir, persistDirMode) 55 | if err != nil { 56 | return fmt.Errorf("failed to create directory for fail file %q: %w", filename, err) 57 | } 58 | 59 | f, err := os.CreateTemp(dir, failfileTmpPattern) 60 | if err != nil { 61 | return fmt.Errorf("failed to create temporary file for fail file %q: %w", filename, err) 62 | } 63 | defer func() { _ = os.Remove(f.Name()) }() 64 | defer func() { _ = f.Close() }() 65 | 66 | out := strings.Split(string(output), "\n") 67 | for _, s := range out { 68 | _, err := f.WriteString("# " + s + "\n") 69 | if err != nil { 70 | return fmt.Errorf("failed to write data to fail file %q: %w", filename, err) 71 | } 72 | } 73 | 74 | bs := []string{fmt.Sprintf("%v#%v", version, seed)} 75 | for _, u := range buf { 76 | bs = append(bs, fmt.Sprintf("0x%x", u)) 77 | } 78 | 79 | _, err = f.WriteString(strings.Join(bs, "\n")) 80 | if err != nil { 81 | return fmt.Errorf("failed to write data to fail file %q: %w", filename, err) 82 | } 83 | 84 | _ = f.Close() // early close, otherwise os.Rename will fail on Windows 85 | err = os.Rename(f.Name(), filename) 86 | if err != nil { 87 | return fmt.Errorf("failed to save fail file %q: %w", filename, err) 88 | } 89 | 90 | return nil 91 | } 92 | 93 | func loadFailFile(filename string) (string, uint64, []uint64, error) { 94 | f, err := os.Open(filename) 95 | if err != nil { 96 | return "", 0, nil, fmt.Errorf("failed to open fail file: %w", err) 97 | } 98 | defer func() { _ = f.Close() }() 99 | 100 | var data []string 101 | scanner := bufio.NewScanner(f) 102 | for scanner.Scan() { 103 | s := strings.TrimSpace(scanner.Text()) 104 | if strings.HasPrefix(s, "#") || s == "" { 105 | continue 106 | } 107 | data = append(data, s) 108 | } 109 | if err := scanner.Err(); err != nil { 110 | return "", 0, nil, fmt.Errorf("failed to load fail file %q: %w", filename, err) 111 | } 112 | 113 | if len(data) == 0 { 114 | return "", 0, nil, fmt.Errorf("no data in fail file %q", filename) 115 | } 116 | 117 | split := strings.Split(data[0], "#") 118 | if len(split) != 2 { 119 | return "", 0, nil, fmt.Errorf("invalid version/seed field %q in %q", data[0], filename) 120 | } 121 | seed, err := strconv.ParseUint(split[1], 10, 64) 122 | if err != nil { 123 | return "", 0, nil, fmt.Errorf("invalid seed %q in %q", split[1], filename) 124 | } 125 | 126 | var buf []uint64 127 | for _, b := range data[1:] { 128 | u, err := strconv.ParseUint(b, 0, 64) 129 | if err != nil { 130 | return "", 0, nil, fmt.Errorf("failed to load fail file %q: %w", filename, err) 131 | } 132 | buf = append(buf, u) 133 | } 134 | 135 | return split[0], seed, buf, nil 136 | } 137 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/statemachine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "reflect" 11 | "sort" 12 | "testing" 13 | ) 14 | 15 | const ( 16 | actionLabel = "action" 17 | validActionTries = 100 // hack, but probably good enough for now 18 | checkMethodName = "Check" 19 | noValidActionsMsg = "can't find a valid (non-skipped) action" 20 | ) 21 | 22 | // Repeat executes a random sequence of actions (often called a "state machine" test). 23 | // actions[""], if set, is executed before/after every other action invocation 24 | // and should only contain invariant checking code. 25 | // 26 | // For complex state machines, it can be more convenient to specify actions as 27 | // methods of a special state machine type. In this case, [StateMachineActions] 28 | // can be used to create an actions map from state machine methods using reflection. 29 | func (t *T) Repeat(actions map[string]func(*T)) { 30 | t.Helper() 31 | 32 | check := func(*T) {} 33 | actionKeys := make([]string, 0, len(actions)) 34 | for key, action := range actions { 35 | if key != "" { 36 | actionKeys = append(actionKeys, key) 37 | } else { 38 | check = action 39 | } 40 | } 41 | if len(actionKeys) == 0 { 42 | return 43 | } 44 | sort.Strings(actionKeys) 45 | 46 | steps := flags.steps 47 | if testing.Short() { 48 | steps /= 2 49 | } 50 | 51 | repeat := newRepeat(-1, -1, float64(steps), "Repeat") 52 | sm := stateMachine{ 53 | check: check, 54 | actionKeys: SampledFrom(actionKeys), 55 | actions: actions, 56 | } 57 | 58 | sm.check(t) 59 | t.failOnError() 60 | for repeat.more(t.s) { 61 | ok := sm.executeAction(t) 62 | if ok { 63 | sm.check(t) 64 | t.failOnError() 65 | } else { 66 | repeat.reject() 67 | } 68 | } 69 | } 70 | 71 | type StateMachine interface { 72 | // Check is ran after every action and should contain invariant checks. 73 | // 74 | // All other public methods should have a form ActionName(t *rapid.T) 75 | // and are used as possible actions. At least one action has to be specified. 76 | Check(*T) 77 | } 78 | 79 | // StateMachineActions creates an actions map for [*T.Repeat] 80 | // from methods of a [StateMachine] type instance using reflection. 81 | func StateMachineActions(sm StateMachine) map[string]func(*T) { 82 | var ( 83 | v = reflect.ValueOf(sm) 84 | t = v.Type() 85 | n = t.NumMethod() 86 | ) 87 | 88 | actions := make(map[string]func(*T), n) 89 | for i := 0; i < n; i++ { 90 | name := t.Method(i).Name 91 | m, ok := v.Method(i).Interface().(func(*T)) 92 | if ok && name != checkMethodName { 93 | actions[name] = m 94 | } 95 | } 96 | 97 | assertf(len(actions) > 0, "state machine of type %v has no actions specified", t) 98 | actions[""] = sm.Check 99 | 100 | return actions 101 | } 102 | 103 | type stateMachine struct { 104 | check func(*T) 105 | actionKeys *Generator[string] 106 | actions map[string]func(*T) 107 | } 108 | 109 | func (sm *stateMachine) executeAction(t *T) bool { 110 | t.Helper() 111 | 112 | for n := 0; n < validActionTries; n++ { 113 | i := t.s.beginGroup(actionLabel, false) 114 | action := sm.actions[sm.actionKeys.Draw(t, "action")] 115 | invalid, skipped := runAction(t, action) 116 | t.s.endGroup(i, false) 117 | 118 | if skipped { 119 | continue 120 | } else { 121 | return !invalid 122 | } 123 | } 124 | 125 | panic(stopTest(noValidActionsMsg)) 126 | } 127 | 128 | func runAction(t *T, action func(*T)) (invalid bool, skipped bool) { 129 | defer func(draws int) { 130 | if r := recover(); r != nil { 131 | if _, ok := r.(invalidData); ok { 132 | invalid = true 133 | skipped = t.draws == draws 134 | } else { 135 | panic(r) 136 | } 137 | } 138 | }(t.draws) 139 | 140 | action(t) 141 | t.failOnError() 142 | 143 | return false, false 144 | } 145 | -------------------------------------------------------------------------------- /vendor/pgregory.net/rapid/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Gregory Petrosyan 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this 5 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 6 | 7 | package rapid 8 | 9 | import ( 10 | "math" 11 | "math/bits" 12 | ) 13 | 14 | const ( 15 | biasLabel = "bias" 16 | intBitsLabel = "intbits" 17 | coinFlipLabel = "coinflip" 18 | dieRollLabel = "dieroll" 19 | repeatLabel = "@repeat" 20 | ) 21 | 22 | func bitmask64(n uint) uint64 { 23 | return uint64(1)< 0 && p <= 1) 32 | 33 | f := genFloat01(s) 34 | n := math.Log1p(-f) / math.Log1p(-p) 35 | 36 | return uint64(n) 37 | } 38 | 39 | func genUintNNoReject(s bitStream, max uint64) uint64 { 40 | bitlen := bits.Len64(max) 41 | i := s.beginGroup(intBitsLabel, false) 42 | u := s.drawBits(bitlen) 43 | s.endGroup(i, false) 44 | if u > max { 45 | u = max 46 | } 47 | return u 48 | } 49 | 50 | func genUintNUnbiased(s bitStream, max uint64) uint64 { 51 | bitlen := bits.Len64(max) 52 | 53 | for { 54 | i := s.beginGroup(intBitsLabel, false) 55 | u := s.drawBits(bitlen) 56 | ok := u <= max 57 | s.endGroup(i, !ok) 58 | if ok { 59 | return u 60 | } 61 | } 62 | } 63 | 64 | func genUintNBiased(s bitStream, max uint64) (uint64, bool, bool) { 65 | bitlen := bits.Len64(max) 66 | i := s.beginGroup(biasLabel, false) 67 | m := math.Max(8, (float64(bitlen)+48)/7) 68 | n := genGeom(s, 1/(m+1)) + 1 69 | s.endGroup(i, false) 70 | 71 | if int(n) < bitlen { 72 | bitlen = int(n) 73 | } else if int(n) >= 64-(16-int(m))*4 { 74 | bitlen = 65 75 | } 76 | 77 | for { 78 | i := s.beginGroup(intBitsLabel, false) 79 | u := s.drawBits(bitlen) 80 | ok := bitlen > 64 || u <= max 81 | s.endGroup(i, !ok) 82 | if bitlen > 64 { 83 | u = max 84 | } 85 | if u <= max { 86 | return u, u == 0 && n == 1, u == max && bitlen >= int(n) 87 | } 88 | } 89 | } 90 | 91 | func genUintN(s bitStream, max uint64, bias bool) (uint64, bool, bool) { 92 | if bias { 93 | return genUintNBiased(s, max) 94 | } else { 95 | return genUintNUnbiased(s, max), false, false 96 | } 97 | } 98 | 99 | func genUintRange(s bitStream, min uint64, max uint64, bias bool) (uint64, bool, bool) { 100 | if min > max { 101 | assertf(false, "invalid range [%v, %v]", min, max) // avoid allocations in the fast path 102 | } 103 | 104 | u, lOverflow, rOverflow := genUintN(s, max-min, bias) 105 | 106 | return min + u, lOverflow, rOverflow 107 | } 108 | 109 | func genIntRange(s bitStream, min int64, max int64, bias bool) (int64, bool, bool) { 110 | if min > max { 111 | assertf(false, "invalid range [%v, %v]", min, max) // avoid allocations in the fast path 112 | } 113 | 114 | var posMin, negMin uint64 115 | var pNeg float64 116 | if min >= 0 { 117 | posMin = uint64(min) 118 | pNeg = 0 119 | } else if max <= 0 { 120 | negMin = uint64(-max) 121 | pNeg = 1 122 | } else { 123 | posMin = 0 124 | negMin = 1 125 | pos := uint64(max) + 1 126 | neg := uint64(-min) 127 | pNeg = float64(neg) / (float64(neg) + float64(pos)) 128 | if bias { 129 | pNeg = 0.5 130 | } 131 | } 132 | 133 | if flipBiasedCoin(s, pNeg) { 134 | u, lOverflow, rOverflow := genUintRange(s, negMin, uint64(-min), bias) 135 | return -int64(u), rOverflow, lOverflow && max <= 0 136 | } else { 137 | u, lOverflow, rOverflow := genUintRange(s, posMin, uint64(max), bias) 138 | return int64(u), lOverflow && min >= 0, rOverflow 139 | } 140 | } 141 | 142 | func genIndex(s bitStream, n int, bias bool) int { 143 | assert(n > 0) 144 | 145 | u, _, _ := genUintN(s, uint64(n-1), bias) 146 | 147 | return int(u) 148 | } 149 | 150 | func flipBiasedCoin(s bitStream, p float64) bool { 151 | assert(p >= 0 && p <= 1) 152 | 153 | i := s.beginGroup(coinFlipLabel, false) 154 | f := genFloat01(s) 155 | s.endGroup(i, false) 156 | 157 | return f >= 1-p 158 | } 159 | 160 | type loadedDie struct { 161 | table []int 162 | } 163 | 164 | func newLoadedDie(weights []int) *loadedDie { 165 | assert(len(weights) > 0) 166 | 167 | if len(weights) == 1 { 168 | return &loadedDie{ 169 | table: []int{0}, 170 | } 171 | } 172 | 173 | total := 0 174 | for _, w := range weights { 175 | assert(w > 0 && w < 100) 176 | total += w 177 | } 178 | 179 | table := make([]int, total) 180 | i := 0 181 | for n, w := range weights { 182 | for j := i; i < j+w; i++ { 183 | table[i] = n 184 | } 185 | } 186 | 187 | return &loadedDie{ 188 | table: table, 189 | } 190 | } 191 | 192 | func (d *loadedDie) roll(s bitStream) int { 193 | i := s.beginGroup(dieRollLabel, false) 194 | ix := genIndex(s, len(d.table), false) 195 | s.endGroup(i, false) 196 | 197 | return d.table[ix] 198 | } 199 | 200 | type repeat struct { 201 | minCount int 202 | maxCount int 203 | avgCount float64 204 | pContinue float64 205 | count int 206 | group int 207 | rejected bool 208 | rejections int 209 | forceStop bool 210 | label string 211 | } 212 | 213 | func newRepeat(minCount int, maxCount int, avgCount float64, label string) *repeat { 214 | if minCount < 0 { 215 | minCount = 0 216 | } 217 | if maxCount < 0 { 218 | maxCount = math.MaxInt 219 | } 220 | if avgCount < 0 { 221 | avgCount = float64(minCount) + math.Min(math.Max(float64(minCount), small), (float64(maxCount)-float64(minCount))/2) 222 | } 223 | 224 | return &repeat{ 225 | minCount: minCount, 226 | maxCount: maxCount, 227 | avgCount: avgCount, 228 | pContinue: 1 - 1/(1+avgCount-float64(minCount)), // TODO was no -minCount intentional? 229 | group: -1, 230 | label: label + repeatLabel, 231 | } 232 | } 233 | 234 | func (r *repeat) avg() int { 235 | return int(math.Ceil(r.avgCount)) 236 | } 237 | 238 | func (r *repeat) more(s bitStream) bool { 239 | if r.group >= 0 { 240 | s.endGroup(r.group, r.rejected) 241 | } 242 | 243 | r.group = s.beginGroup(r.label, true) 244 | r.rejected = false 245 | 246 | pCont := r.pContinue 247 | if r.count < r.minCount { 248 | pCont = 1 249 | } else if r.forceStop || r.count >= r.maxCount { 250 | pCont = 0 251 | } 252 | 253 | cont := flipBiasedCoin(s, pCont) 254 | if cont { 255 | r.count++ 256 | } else { 257 | s.endGroup(r.group, false) 258 | } 259 | 260 | return cont 261 | } 262 | 263 | func (r *repeat) reject() { 264 | assert(r.count > 0) 265 | r.count-- 266 | r.rejected = true 267 | r.rejections++ 268 | 269 | if r.rejections > r.count*2 { 270 | if r.count >= r.minCount { 271 | r.forceStop = true 272 | } else { 273 | panic(invalidData("too many rejections in repeat")) 274 | } 275 | } 276 | } 277 | --------------------------------------------------------------------------------