package serial import ( "errors" "fmt" "strings" ) type LineConfig struct { Device string BaudRate int DataBits int Parity string StopBits int StopBitsMode string FlowControl string RTSControl string DTRControl string IgnoreJammed bool } var ( ErrUnsupportedPlatform = errors.New("serial termios is unsupported on this platform") ErrInvalidLineConfig = errors.New("invalid serial line config") ) func NormalizeLineConfig(config LineConfig) (LineConfig, error) { normalized := LineConfig{ Device: strings.TrimSpace(config.Device), BaudRate: config.BaudRate, DataBits: config.DataBits, Parity: strings.ToLower(strings.TrimSpace(config.Parity)), StopBits: config.StopBits, StopBitsMode: strings.TrimSpace(config.StopBitsMode), FlowControl: strings.ToLower(strings.TrimSpace(config.FlowControl)), RTSControl: strings.ToLower(strings.TrimSpace(config.RTSControl)), DTRControl: strings.ToLower(strings.TrimSpace(config.DTRControl)), IgnoreJammed: config.IgnoreJammed, } if normalized.BaudRate == 0 { normalized.BaudRate = 9600 } if normalized.DataBits == 0 { normalized.DataBits = 8 } if normalized.Parity == "" { normalized.Parity = "none" } if normalized.StopBits == 0 { normalized.StopBits = 1 } if normalized.StopBitsMode == "" { normalized.StopBitsMode = fmt.Sprintf("%d", normalized.StopBits) } if normalized.FlowControl == "" { normalized.FlowControl = "none" } if normalized.RTSControl == "" { normalized.RTSControl = "auto" } if normalized.DTRControl == "" { normalized.DTRControl = "auto" } if normalized.Device == "" { return LineConfig{}, invalidLineConfig("device must not be empty") } if !isAllowedSerialDevice(normalized.Device) { return LineConfig{}, invalidLineConfig("device must be under /dev/tty* or /dev/serial/by-path") } if !isAllowedBaudRate(normalized.BaudRate) { return LineConfig{}, invalidLineConfig("baud rate is not supported") } if normalized.DataBits < 5 || normalized.DataBits > 8 { return LineConfig{}, invalidLineConfig("data bits must be 5 through 8") } switch normalized.Parity { case "none", "even", "odd", "mark", "space": default: return LineConfig{}, invalidLineConfig("parity must be none, even, odd, mark, or space") } if normalized.StopBitsMode != "1" && normalized.StopBitsMode != "1.5" && normalized.StopBitsMode != "2" { return LineConfig{}, invalidLineConfig("stop bits must be 1, 1.5, or 2") } if normalized.StopBitsMode == "1.5" && normalized.DataBits != 5 { return LineConfig{}, invalidLineConfig("1.5 stop bits requires 5 data bits") } if normalized.StopBitsMode == "2" { normalized.StopBits = 2 } else { normalized.StopBits = 1 } switch normalized.FlowControl { case "none", "rtscts", "xonxoff": default: return LineConfig{}, invalidLineConfig("flow control must be none, rtscts, or xonxoff") } if normalized.FlowControl == "rtscts" && normalized.RTSControl != "auto" { return LineConfig{}, invalidLineConfig("manual RTS control requires flow control other than rtscts") } for name, value := range map[string]string{"rts control": normalized.RTSControl, "dtr control": normalized.DTRControl} { if value != "auto" && value != "on" && value != "off" { return LineConfig{}, invalidLineConfig(name + " must be auto, on, or off") } } return normalized, nil } func invalidLineConfig(message string) error { return fmt.Errorf("%w: %s", ErrInvalidLineConfig, message) } func isAllowedSerialDevice(device string) bool { return strings.HasPrefix(device, "/dev/tty") || strings.HasPrefix(device, "/dev/serial/by-path/") } func isAllowedBaudRate(baudRate int) bool { switch baudRate { case 110, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800: return true default: return false } }