Skip to content

Commit 0c18ab6

Browse files
committed
fix: review
Signed-off-by: llogen <christoph.lange@blindspot.software>
1 parent 87a4da9 commit 0c18ab6

2 files changed

Lines changed: 50 additions & 31 deletions

File tree

cmds/dutctl/rpc.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ func (app *application) detailsRPC(device, command, keyword string) error {
9595
// setRawInput puts the terminal into raw input mode: disables local echo and
9696
// canonical (line-buffered) mode so each keystroke is available immediately.
9797
// It returns a restore function, or nil if the fd is not a terminal.
98-
func setRawInput(fd int) func() {
99-
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS)
98+
func setRawInput(fileDescriptor int) func() {
99+
termios, err := unix.IoctlGetTermios(fileDescriptor, unix.TCGETS)
100100
if err != nil {
101101
return nil
102102
}
@@ -108,12 +108,13 @@ func setRawInput(fd int) func() {
108108
termios.Cc[unix.VMIN] = 1
109109
termios.Cc[unix.VTIME] = 0
110110

111-
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil {
111+
err = unix.IoctlSetTermios(fileDescriptor, unix.TCSETS, termios)
112+
if err != nil {
112113
return nil
113114
}
114115

115116
return func() {
116-
_ = unix.IoctlSetTermios(fd, unix.TCSETS, &old)
117+
_ = unix.IoctlSetTermios(fileDescriptor, unix.TCSETS, &old)
117118
}
118119
}
119120

@@ -264,7 +265,9 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
264265
go func() {
265266
defer cancel()
266267

267-
buf := make([]byte, 256)
268+
const stdinBufSize = 256
269+
270+
buf := make([]byte, stdinBufSize)
268271

269272
for {
270273
select {
@@ -275,7 +278,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
275278
default:
276279
}
277280

278-
n, err := app.stdin.Read(buf)
281+
nRead, err := app.stdin.Read(buf)
279282
if err != nil {
280283
if !errors.Is(err, io.EOF) {
281284
errChan <- fmt.Errorf("reading stdin: %w", err)
@@ -288,7 +291,7 @@ func (app *application) runRPC(device, command string, cmdArgs []string) error {
288291
Msg: &pb.RunRequest_Console{
289292
Console: &pb.Console{
290293
Data: &pb.Console_Stdin{
291-
Stdin: buf[:n],
294+
Stdin: buf[:nRead],
292295
},
293296
},
294297
},

pkg/module/serial/serial.go

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ Input from the client is forwarded to the serial port, and output from the seria
5757
If a regex is provided, the module will wait for the regex to match on the serial output,
5858
then exit with success. If no expect string is provided, the module will read from the serial port
5959
until it is terminated by a signal (e.g. Ctrl-C).
60-
The expect string supports regular expressions according to [1].
60+
The expect string supports regular expressions according to [1].
6161
The optional -t flag specifies the maximum time to wait for the regex to match.
6262
Quote the expect string if it contains spaces or special characters. E.g.: "(?i)hello\s+world!? dutctl"
6363
@@ -100,7 +100,7 @@ func (s *Serial) Deinit() error {
100100
return nil
101101
}
102102

103-
//nolint:cyclop,funlen,gocognit
103+
//nolint:cyclop,funlen,gocognit,gocyclo,maintidx
104104
func (s *Serial) Run(ctx context.Context, session module.Session, args ...string) error {
105105
log.Println("serial module: Run called")
106106

@@ -141,11 +141,13 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
141141
// Strip ANSI CSI sequences from stdin, since the remote system may send terminal
142142
// queries (e.g. DSR) that cause the local terminal to inject responses (e.g. CPR)
143143
// into stdin, which would corrupt the serial input.
144+
const stdinBufSize = 256
145+
144146
go func() {
145-
buf := make([]byte, 256)
147+
buf := make([]byte, stdinBufSize)
146148

147149
for {
148-
n, err := stdin.Read(buf)
150+
nRead, err := stdin.Read(buf)
149151
if err != nil {
150152
select {
151153
case <-done:
@@ -162,7 +164,7 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
162164
return
163165
}
164166

165-
data := stripCSI(buf[:n])
167+
data := stripCSI(buf[:nRead])
166168

167169
if len(data) == 0 {
168170
continue
@@ -174,12 +176,13 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
174176
default:
175177
}
176178

177-
if _, err := port.Write(data); err != nil {
179+
_, writeErr := port.Write(data)
180+
if writeErr != nil {
178181
select {
179182
case <-done:
180183
return // port closed on Run() exit — not an error.
181184
default:
182-
log.Printf("serial module: error writing to serial port: %v", err)
185+
log.Printf("serial module: error writing to serial port: %v", writeErr)
183186
}
184187

185188
return
@@ -192,17 +195,17 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
192195
readBuffer := make([]byte, bufferSize)
193196
lineBuffer := &bytes.Buffer{}
194197

195-
// mu protects lineBuffer which is accessed from the main loop
198+
// mutex protects lineBuffer which is accessed from the main loop
196199
// and the flush timer goroutine.
197-
var mu sync.Mutex
200+
var mutex sync.Mutex
198201

199202
flushBuffer := func() {
200203
if lineBuffer.Len() == 0 {
201204
return
202205
}
203206

204207
line := lineBuffer.String()
205-
stdout.Write([]byte(line)) //nolint:errcheck
208+
_, _ = stdout.Write([]byte(line)) // ChanWriter.Write always returns nil
206209

207210
if s.expect != nil && s.expect.MatchString(line) {
208211
log.Printf("serial module: pattern matched in flush")
@@ -233,12 +236,12 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
233236

234237
return nil
235238
case <-ctx.Done():
236-
mu.Lock()
239+
mutex.Lock()
237240

238241
// Flush any remaining data before closing.
239242
flushBuffer()
240243

241-
mu.Unlock()
244+
mutex.Unlock()
242245

243246
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
244247
fmt.Fprintln(stdout, "\n--- Timeout reached, no match found ---")
@@ -272,26 +275,31 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
272275
continue
273276
}
274277

275-
mu.Lock()
278+
mutex.Lock()
276279

277280
// Stop pending flush timer since we have new data.
278281
if flushTimer != nil {
279282
flushTimer.Stop()
280283
}
281284

282285
// Process the data read character by character
283-
for i := range len(outData) {
284-
b := outData[i]
286+
for _, b := range outData {
285287
lineBuffer.WriteByte(b)
286288

287289
// If we reach a newline or a buffer limit, process the line
288290
if b == '\n' || lineBuffer.Len() >= 1024 {
289291
line := lineBuffer.String()
290-
stdout.Write([]byte(line)) //nolint:errcheck
292+
293+
_, writeErr := stdout.Write([]byte(line))
294+
if writeErr != nil {
295+
mutex.Unlock()
296+
297+
return fmt.Errorf("error writing to stdout: %w", writeErr)
298+
}
291299

292300
// Check for regex match if we have one
293301
if s.expect != nil && s.expect.MatchString(line) {
294-
mu.Unlock()
302+
mutex.Unlock()
295303

296304
fmt.Fprintln(stdout, "\n--- Pattern matched, connection closed ---")
297305

@@ -306,13 +314,13 @@ func (s *Serial) Run(ctx context.Context, session module.Session, args ...string
306314
// schedule a flush so prompts like "login: " appear promptly.
307315
if lineBuffer.Len() > 0 {
308316
flushTimer = time.AfterFunc(flushTimeout, func() {
309-
mu.Lock()
317+
mutex.Lock()
310318
flushBuffer()
311-
mu.Unlock()
319+
mutex.Unlock()
312320
})
313321
}
314322

315-
mu.Unlock()
323+
mutex.Unlock()
316324
}
317325
}
318326
}
@@ -363,17 +371,22 @@ func (s *Serial) openPort() (*serial.Port, error) {
363371
return port, nil
364372
}
365373

374+
// csiPrefixLen is the length of the CSI prefix "ESC[".
375+
const csiPrefixLen = 2
376+
366377
// filterOutputCSI removes CSI sequences from serial output data, except for
367378
// SGR (Select Graphic Rendition, final byte 'm') which handles colors and styles.
368379
// This prevents cursor positioning, screen clearing, and terminal query sequences
369380
// from affecting the client terminal, while preserving colored output.
381+
//
382+
//nolint:cyclop,varnamelen
370383
func filterOutputCSI(data []byte) []byte {
371384
result := make([]byte, 0, len(data))
372385

373386
for i := 0; i < len(data); i++ {
374387
if data[i] == 0x1b && i+1 < len(data) && data[i+1] == '[' {
375388
// Find the extent of this CSI sequence.
376-
j := i + 2
389+
j := i + csiPrefixLen
377390

378391
for j < len(data) && data[j] >= 0x30 && data[j] <= 0x3f {
379392
j++ // parameter bytes
@@ -410,6 +423,8 @@ func filterOutputCSI(data []byte) []byte {
410423
// intermediate bytes (0x20-0x2f), and a final byte (0x40-0x7e).
411424
// This filters out terminal responses (like cursor position reports) that the local
412425
// terminal injects into stdin when the remote system sends queries.
426+
//
427+
//nolint:varnamelen
413428
func stripCSI(data []byte) []byte {
414429
result := make([]byte, 0, len(data))
415430

@@ -418,12 +433,13 @@ func stripCSI(data []byte) []byte {
418433
// Skip ESC[
419434
i += 2
420435

421-
// Skip parameter and intermediate bytes, stop at final byte.
422-
for i < len(data) && data[i] < 0x40 {
436+
// Skip parameter bytes (0x30-0x3f) and intermediate bytes (0x20-0x2f).
437+
for i < len(data) && data[i] >= 0x20 && data[i] < 0x40 {
423438
i++
424439
}
425-
// i now points at the final byte (0x40-0x7e); skip it too.
426440

441+
// i now points at the final byte (0x40-0x7e); the outer loop's
442+
// i++ will advance past it on the next iteration.
427443
continue
428444
}
429445

0 commit comments

Comments
 (0)