blob: 690a1ef100f58572f5fb63c47fc2a30fe18facdb [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can
// found in the LICENSE file.
package serial
import (
"context"
"fmt"
"io"
"net"
"os"
"syscall"
"go.fuchsia.dev/fuchsia/tools/lib/ring"
)
// Server proxies all i/o to/from a serial port via another io.ReadWriter.
// Start and Stop may be pairwise called any number of times.
type Server struct {
serial io.ReadWriter
opts ServerOptions
done chan struct{}
writeBufferSize int
}
// ServerOptions provide options that parametrize the server's behavior.
type ServerOptions struct {
// WriteBufferSize is the size of the write buffer of the socket connection
// that is to be established.
WriteBufferSize int
// AuxiliaryOutput is an optional serial output sink.
AuxiliaryOutput io.Writer
}
// NewServer returns a new server that lives atop the given 'serial' port.
func NewServer(serial io.ReadWriter, opts ServerOptions) *Server {
if opts.WriteBufferSize <= 0 {
panic(fmt.Sprintf("serial server: needs a positive write buffer to initialize: %d <= 0", opts.WriteBufferSize))
}
return &Server{
serial: serial,
opts: opts,
done: make(chan struct{}, 1),
}
}
// Run begins the server and blocks until the context signals done or an error
// is encountered. While running, all serial i/o is forwarded to and from the
// first connection accepted by the listener.
func (s *Server) Run(ctx context.Context, listener net.Listener) error {
errs := make(chan error)
// The data below flows as
//
// serial -> ring buffer -> conn
// -> aux output
//
// and back as
//
// conn -> serial
//
// We use an intermediate ring buffer so as not to block on writes to
// the connection (as would happen in the case of a socket, for example):
// this which would prevent timely writes to the auxiliary output.
// serial -> (ring buffer, aux output)
rb := ring.NewBuffer(s.opts.WriteBufferSize)
go func() {
var teedSerial io.Reader = s.serial
if s.opts.AuxiliaryOutput != nil {
teedSerial = io.TeeReader(s.serial, s.opts.AuxiliaryOutput)
}
for {
if _, err := io.Copy(rb, teedSerial); err != nil {
errs <- fmt.Errorf("failed to read from serial: %v", err)
}
}
}()
go func() {
// We allow consecutive connections, but only one at a given time.
// When a client connection is closed, copying to/from the socket
// will hit syscall errors (which will be swallowed) and we begin
// a new iteration.
for {
conn, err := listener.Accept()
if err != nil {
errs <- err
return
}
defer conn.Close()
// net.Conn does not feature a SetWriteBuffer method, but all of its
// main net implementations do.
wbs, ok := conn.(writeBufferSetter)
if ok {
if err := wbs.SetWriteBuffer(s.opts.WriteBufferSize); err != nil {
errs <- err
return
}
}
// conn -> serial
go func() {
for {
if _, err := io.Copy(s.serial, conn); sanitizeError(ctx, err) != nil {
errs <- fmt.Errorf("failed to read from the connection: %v", err)
}
}
}()
// ring buffer -> conn
for {
if _, err := io.Copy(conn, rb); sanitizeError(ctx, err) != nil {
errs <- fmt.Errorf("failed to copy data to the connection: %v", err)
}
}
}
}()
select {
case <-ctx.Done():
return nil
case err := <-errs:
return err
}
}
func sanitizeError(ctx context.Context, err error) error {
// If the context has been canceled, no need to report any error as we
// would already be tearing down the server as a result.
if err == nil || ctx.Err() != nil {
return nil
}
oe, ok := err.(*net.OpError)
if !ok {
return err
}
se, ok := oe.Err.(*os.SyscallError)
if !ok {
return err
}
// Errors resulting from a closed socket connection are fine and would be
// spurious to report. Assuming that this is to be used on a
// POSIX-compliant host, we ignore read and write errors that result from
// such a case.
//
// POSIX documentation:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/recv.html
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/send.html
if (se.Syscall == "read" || se.Syscall == "write") && (se.Err == syscall.ECONNRESET || se.Err == syscall.EPIPE) {
return nil
}
return err
}
type writeBufferSetter interface {
SetWriteBuffer(int) error
}