From 36ec968da9373cdf5271e2c6d4a26959a4d70a8b Mon Sep 17 00:00:00 2001 From: Deomid Ryabkov Date: Fri, 21 Dec 2018 16:49:47 +0000 Subject: [PATCH] mos licensing implementation CL: mos licensing implementation PUBLISHED_FROM=60db4b16af57d8938a8922fd78b435d5b34d3995 --- mos/call.go | 5 +- mos/console.go | 23 ++-- mos/devutil.go | 127 ------------------- mos/devutil/devutil.go | 81 ++++++++++++ mos/{ => devutil}/serial.go | 13 +- mos/devutil/serial_darwin.go | 21 +++ mos/devutil/serial_linux.go | 15 +++ mos/{os_posix.go => devutil/serial_posix.go} | 4 +- mos/devutil/serial_windows.go | 67 ++++++++++ mos/esp32_efuse.go | 3 +- mos/flags/flags.go | 85 ++++++++++--- mos/flash.go | 12 +- mos/flash_read.go | 3 +- mos/license_cmd/license.go | 85 ++++++++++++- mos/main.go | 24 ++-- mos/os_darwin.go | 1 - mos/os_linux.go | 11 -- mos/os_windows.go | 59 --------- mos/ui.go | 20 +-- 19 files changed, 393 insertions(+), 266 deletions(-) delete mode 100644 mos/devutil.go create mode 100644 mos/devutil/devutil.go rename mos/{ => devutil}/serial.go (55%) create mode 100644 mos/devutil/serial_darwin.go create mode 100644 mos/devutil/serial_linux.go rename mos/{os_posix.go => devutil/serial_posix.go} (68%) create mode 100644 mos/devutil/serial_windows.go diff --git a/mos/call.go b/mos/call.go index b294da3..c65f1bf 100644 --- a/mos/call.go +++ b/mos/call.go @@ -6,6 +6,7 @@ import ( "fmt" "cesanta.com/mos/dev" + "cesanta.com/mos/flags" "github.com/cesanta/errors" flag "github.com/spf13/pflag" @@ -46,9 +47,9 @@ func call(ctx context.Context, devConn *dev.DevConn) error { params = args[1] } - if *timeout > 0 { + if *flags.Timeout > 0 { var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, *timeout) + ctx, cancel = context.WithTimeout(ctx, *flags.Timeout) defer cancel() } diff --git a/mos/console.go b/mos/console.go index a43b352..196d006 100644 --- a/mos/console.go +++ b/mos/console.go @@ -20,6 +20,8 @@ import ( "cesanta.com/common/go/ourjson" "cesanta.com/mos/debug_core_dump" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" + "cesanta.com/mos/flags" "cesanta.com/mos/timestamp" "github.com/cesanta/errors" @@ -42,11 +44,8 @@ var ( ) func init() { - flag.UintVar(&baudRateFlag, "baud-rate", 115200, "Serial port speed") flag.BoolVar(&noInput, "no-input", false, "Do not read from stdin, only print device's output to stdout") - flag.BoolVar(&hwFCFlag, "hw-flow-control", false, "Enable hardware flow control (CTS/RTS)") - flag.BoolVar(&setControlLines, "set-control-lines", true, "Set RTS and DTR explicitly when in console/RPC mode") flag.BoolVar(&catchCoreDumps, "catch-core-dumps", true, "Catch and save core dumps") flag.StringVar(&tsfSpec, "timestamp", "StampMilli", @@ -149,15 +148,15 @@ func console(ctx context.Context, devConn *dev.DevConn) error { var r io.Reader var w io.Writer - purl, err := url.Parse(*portFlag) + purl, err := url.Parse(*flags.Port) switch { case err == nil && (purl.Scheme == "mqtt" || purl.Scheme == "mqtts"): chr := &chanReader{rch: make(chan []byte)} - opts, topic, err := codec.MQTTClientOptsFromURL(*portFlag, "", "", "") + opts, topic, err := codec.MQTTClientOptsFromURL(*flags.Port, "", "", "") if err != nil { return errors.Errorf("invalid MQTT port URL format") } - tlsConfig, err := tlsConfigFromFlags() + tlsConfig, err := flags.TLSConfigFromFlags() if err != nil { return errors.Annotatef(err, "inavlid TLS config") } @@ -212,7 +211,7 @@ func console(ctx context.Context, devConn *dev.DevConn) error { case err == nil && (purl.Scheme == "ws" || purl.Scheme == "wss"): // Connect to mDash and activate event forwarding. chr := &chanReader{rch: make(chan []byte)} - devConn, err = createDevConn(ctx) + devConn, err = devutil.CreateDevConnFromFlags(ctx) if err != nil { return errors.Trace(err) } @@ -247,15 +246,15 @@ func console(ctx context.Context, devConn *dev.DevConn) error { default: // Everything else is treated as a serial port. - port, err := getPort() + port, err := devutil.GetPort() if err != nil { return errors.Trace(err) } sp, err := serial.Open(serial.OpenOptions{ PortName: port, - BaudRate: baudRateFlag, - HardwareFlowControl: hwFCFlag, + BaudRate: uint(*flags.BaudRate), + HardwareFlowControl: *flags.HWFC, DataBits: 8, ParityMode: serial.PARITY_NONE, StopBits: 1, @@ -264,8 +263,8 @@ func console(ctx context.Context, devConn *dev.DevConn) error { if err != nil { return errors.Annotatef(err, "failed to open %s", port) } - if setControlLines || *invertedControlLines { - bFalse := *invertedControlLines + if *flags.SetControlLines || *flags.InvertedControlLines { + bFalse := *flags.InvertedControlLines sp.SetDTR(bFalse) sp.SetRTS(bFalse) } diff --git a/mos/devutil.go b/mos/devutil.go deleted file mode 100644 index 094fb67..0000000 --- a/mos/devutil.go +++ /dev/null @@ -1,127 +0,0 @@ -package main - -import ( - "crypto/tls" - "crypto/x509" - "flag" - "io/ioutil" - "runtime" - "strings" - "time" - - "context" - - "cesanta.com/common/go/mgrpc/codec" - "cesanta.com/mos/dev" - "cesanta.com/mos/watson" - "github.com/cesanta/errors" -) - -var ( - azureConnectionString = "" - certFile = "" - keyFile = "" - caFile = "" - rpcUARTNoDelayFlag = false -) - -func init() { - flag.StringVar(&azureConnectionString, "azure-connection-string", "", "Azure connection string") - flag.StringVar(&certFile, "cert-file", "", "Certificate file name") - flag.StringVar(&keyFile, "key-file", "", "Key file name") - flag.StringVar(&caFile, "ca-cert-file", "", "CA cert for TLS server verification") - flag.BoolVar(&rpcUARTNoDelayFlag, "rpc-uart-no-delay", false, "Do not introduce delay into UART over RPC") - hiddenFlags = append(hiddenFlags, "ca-cert-file") -} - -func createDevConn(ctx context.Context) (*dev.DevConn, error) { - return createDevConnWithJunkHandler(ctx, func(junk []byte) {}) -} - -func tlsConfigFromFlags() (*tls.Config, error) { - tlsConfig := &tls.Config{ - // TODO(rojer): Ship default CA bundle with mos. - InsecureSkipVerify: caFile == "", - } - - // Load client cert / key if specified - if certFile != "" && keyFile == "" { - return nil, errors.Errorf("Please specify --key-file") - } - if certFile != "" { - cert, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - return nil, errors.Trace(err) - } - tlsConfig.Certificates = []tls.Certificate{cert} - } - - // Load CA cert if specified - if caFile != "" { - caCert, err := ioutil.ReadFile(caFile) - if err != nil { - return nil, errors.Trace(err) - } - tlsConfig.RootCAs = x509.NewCertPool() - tlsConfig.RootCAs.AppendCertsFromPEM(caCert) - } - - return tlsConfig, nil -} - -func createDevConnWithJunkHandler(ctx context.Context, junkHandler func(junk []byte)) (*dev.DevConn, error) { - port, err := getPort() - if err != nil { - return nil, errors.Trace(err) - } - c := dev.Client{Port: port, Timeout: *timeout, Reconnect: *reconnect} - prefix := "serial://" - if strings.Index(port, "://") > 0 { - prefix = "" - } - addr := prefix + port - - // Init and pass TLS config if --cert-file and --key-file are specified - var tlsConfig *tls.Config - if certFile != "" || strings.HasPrefix(port, "wss") || strings.HasPrefix(port, "https") || strings.HasPrefix(port, "mqtts") { - - tlsConfig, err = tlsConfigFromFlags() - if err != nil { - return nil, errors.Trace(err) - } - } - - codecOpts := &codec.Options{ - AzureDM: codec.AzureDMCodecOptions{ - ConnectionString: azureConnectionString, - }, - MQTT: codec.MQTTCodecOptions{}, - Serial: codec.SerialCodecOptions{ - BaudRate: baudRateFlag, - HardwareFlowControl: hwFCFlag, - JunkHandler: junkHandler, - SetControlLines: setControlLines, - InvertedControlLines: *invertedControlLines, - }, - Watson: codec.WatsonCodecOptions{ - APIKey: watson.WatsonAPIKeyFlag, - APIAuthToken: watson.WatsonAPIAuthTokenFlag, - }, - } - // Due to lack of flow control, we send data in chunks and wait after each. - // At non-default baud rate we assume user knows what they are doing. - if !codecOpts.Serial.HardwareFlowControl && codecOpts.Serial.BaudRate == 115200 && !rpcUARTNoDelayFlag { - codecOpts.Serial.SendChunkSize = 16 - codecOpts.Serial.SendChunkDelay = 5 * time.Millisecond - // So, this is weird. ST-Link serial device seems to have issues on Mac OS X if we write too fast. - // Yes, even 16 bytes at 5 ms interval is too fast, and 8 bytes too. It looks like this: - // processes trying to access /dev/cu.usbmodemX get stuck and the only re-plugging - // (or re-enumerating) gets it out of this state. - // Hence, the following kludge. - if runtime.GOOS == "darwin" && strings.Contains(port, "cu.usbmodem") { - codecOpts.Serial.SendChunkSize = 6 - } - } - devConn, err := c.CreateDevConnWithOpts(ctx, addr, *reconnect, tlsConfig, codecOpts) - return devConn, errors.Trace(err) -} diff --git a/mos/devutil/devutil.go b/mos/devutil/devutil.go new file mode 100644 index 0000000..c7f1188 --- /dev/null +++ b/mos/devutil/devutil.go @@ -0,0 +1,81 @@ +package devutil + +import ( + "context" + "crypto/tls" + "runtime" + "strings" + "time" + + "cesanta.com/common/go/mgrpc/codec" + "cesanta.com/mos/dev" + "cesanta.com/mos/flags" + "cesanta.com/mos/watson" + "github.com/cesanta/errors" +) + +func createDevConnWithJunkHandler(ctx context.Context, junkHandler func(junk []byte)) (*dev.DevConn, error) { + port, err := GetPort() + if err != nil { + return nil, errors.Trace(err) + } + c := dev.Client{Port: port, Timeout: *flags.Timeout, Reconnect: *flags.Reconnect} + prefix := "serial://" + if strings.Index(port, "://") > 0 { + prefix = "" + } + addr := prefix + port + + // Init and pass TLS config if --cert-file and --key-file are specified + var tlsConfig *tls.Config + if *flags.CertFile != "" || + strings.HasPrefix(port, "wss") || + strings.HasPrefix(port, "https") || + strings.HasPrefix(port, "mqtts") { + + tlsConfig, err = flags.TLSConfigFromFlags() + if err != nil { + return nil, errors.Trace(err) + } + } + + codecOpts := &codec.Options{ + AzureDM: codec.AzureDMCodecOptions{ + ConnectionString: *flags.AzureConnectionString, + }, + MQTT: codec.MQTTCodecOptions{}, + Serial: codec.SerialCodecOptions{ + BaudRate: uint(*flags.BaudRate), + HardwareFlowControl: *flags.HWFC, + JunkHandler: junkHandler, + SetControlLines: *flags.SetControlLines, + InvertedControlLines: *flags.InvertedControlLines, + }, + Watson: codec.WatsonCodecOptions{ + APIKey: watson.WatsonAPIKeyFlag, + APIAuthToken: watson.WatsonAPIAuthTokenFlag, + }, + } + // Due to lack of flow control, we send data in chunks and wait after each. + // At non-default baud rate we assume user knows what they are doing. + if !codecOpts.Serial.HardwareFlowControl && + codecOpts.Serial.BaudRate == 115200 && + !*flags.RPCUARTNoDelay { + codecOpts.Serial.SendChunkSize = 16 + codecOpts.Serial.SendChunkDelay = 5 * time.Millisecond + // So, this is weird. ST-Link serial device seems to have issues on Mac OS X if we write too fast. + // Yes, even 16 bytes at 5 ms interval is too fast, and 8 bytes too. It looks like this: + // processes trying to access /dev/cu.usbmodemX get stuck and the only re-plugging + // (or re-enumerating) gets it out of this state. + // Hence, the following kludge. + if runtime.GOOS == "darwin" && strings.Contains(port, "cu.usbmodem") { + codecOpts.Serial.SendChunkSize = 6 + } + } + devConn, err := c.CreateDevConnWithOpts(ctx, addr, *flags.Reconnect, tlsConfig, codecOpts) + return devConn, errors.Trace(err) +} + +func CreateDevConnFromFlags(ctx context.Context) (*dev.DevConn, error) { + return createDevConnWithJunkHandler(ctx, func(junk []byte) {}) +} diff --git a/mos/serial.go b/mos/devutil/serial.go similarity index 55% rename from mos/serial.go rename to mos/devutil/serial.go index b9e4f82..f085f53 100644 --- a/mos/serial.go +++ b/mos/devutil/serial.go @@ -1,21 +1,24 @@ -package main +package devutil import ( "github.com/cesanta/errors" + + "cesanta.com/common/go/ourutil" + "cesanta.com/mos/flags" ) var defaultPort string -func getPort() (string, error) { - if *portFlag != "auto" { - return *portFlag, nil +func GetPort() (string, error) { + if *flags.Port != "auto" { + return *flags.Port, nil } if defaultPort == "" { defaultPort = getDefaultPort() if defaultPort == "" { return "", errors.Errorf("--port not specified and none were found") } - reportf("Using port %s", defaultPort) + ourutil.Reportf("Using port %s", defaultPort) } return defaultPort, nil } diff --git a/mos/devutil/serial_darwin.go b/mos/devutil/serial_darwin.go new file mode 100644 index 0000000..aa10934 --- /dev/null +++ b/mos/devutil/serial_darwin.go @@ -0,0 +1,21 @@ +package devutil + +import ( + "path/filepath" + "sort" + "strings" +) + +func EnumerateSerialPorts() []string { + list, _ := filepath.Glob("/dev/cu.*") + var filteredList []string + for _, s := range list { + if !strings.Contains(s, "Bluetooth-") && + !strings.Contains(s, "-SPPDev") && + !strings.Contains(s, "-WirelessiAP") { + filteredList = append(filteredList, s) + } + } + sort.Strings(filteredList) + return filteredList +} diff --git a/mos/devutil/serial_linux.go b/mos/devutil/serial_linux.go new file mode 100644 index 0000000..3140ad4 --- /dev/null +++ b/mos/devutil/serial_linux.go @@ -0,0 +1,15 @@ +package devutil + +import ( + "path/filepath" + "sort" +) + +func EnumerateSerialPorts() []string { + // Note: Prefer ttyUSB* to ttyACM*. + list1, _ := filepath.Glob("/dev/ttyUSB*") + sort.Strings(list1) + list2, _ := filepath.Glob("/dev/ttyACM*") + sort.Strings(list2) + return append(list1, list2...) +} diff --git a/mos/os_posix.go b/mos/devutil/serial_posix.go similarity index 68% rename from mos/os_posix.go rename to mos/devutil/serial_posix.go index d3d3d14..ba977c0 100644 --- a/mos/os_posix.go +++ b/mos/devutil/serial_posix.go @@ -1,9 +1,9 @@ // +build !windows -package main +package devutil func getDefaultPort() string { - ports := enumerateSerialPorts() + ports := EnumerateSerialPorts() if len(ports) == 0 { return "" } diff --git a/mos/devutil/serial_windows.go b/mos/devutil/serial_windows.go new file mode 100644 index 0000000..7495427 --- /dev/null +++ b/mos/devutil/serial_windows.go @@ -0,0 +1,67 @@ +package devutil + +import ( + "sort" + "strings" + + "golang.org/x/sys/windows/registry" +) + +func EnumerateSerialPorts() []string { + emptyList := []string{} + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `HARDWARE\DEVICEMAP\SERIALCOMM\`, registry.QUERY_VALUE) + if err != nil { + return emptyList + } + defer k.Close() + list, err := k.ReadValueNames(0) + if err != nil { + return emptyList + } + vsm := make([]string, len(list)) + for i, v := range list { + val, _, _ := k.GetStringValue(v) + vsm[i] = val + } + sort.Strings(vsm) + return vsm +} + +func getCOMNumber(port string) int { + if !strings.HasPrefix(port, "COM") { + return -1 + } + cn, err := strconv.Atoi(port[3:]) + if err != nil { + return -1 + } + return cn +} + +type byCOMNumber []string + +func (a byCOMNumber) Len() int { return len(a) } +func (a byCOMNumber) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byCOMNumber) Less(i, j int) bool { + cni := getCOMNumber(a[i]) + cnj := getCOMNumber(a[j]) + if cni < 0 || cnj < 0 { + return a[i] < a[j] + } + return cni < cnj +} + +func getDefaultPort() string { + ports := enumerateSerialPorts() + var filteredPorts []string + for _, p := range ports { + // COM1 and COM2 are commonly mapped to on-board serial ports which are usually not a good guess. + if p != "COM1" && p != "COM2" { + filteredPorts = append(filteredPorts, p) + } + } + if len(filteredPorts) == 0 { + return "" + } + return filteredPorts[0] +} diff --git a/mos/esp32_efuse.go b/mos/esp32_efuse.go index 3ba3a28..ecd23f9 100644 --- a/mos/esp32_efuse.go +++ b/mos/esp32_efuse.go @@ -9,6 +9,7 @@ import ( "strings" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" "cesanta.com/mos/flash/esp" "cesanta.com/mos/flash/esp/rom_client" "cesanta.com/mos/flash/esp32" @@ -29,7 +30,7 @@ func getRRW() (esp.RegReaderWriter, error) { return esp32.NewFakeFuseController(), nil } - port, err := getPort() + port, err := devutil.GetPort() if err != nil { return nil, errors.Trace(err) } diff --git a/mos/flags/flags.go b/mos/flags/flags.go index ea3fe91..032ca43 100644 --- a/mos/flags/flags.go +++ b/mos/flags/flags.go @@ -1,27 +1,51 @@ package flags import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "time" + "cesanta.com/common/go/ourutil" + "github.com/cesanta/errors" flag "github.com/spf13/pflag" ) var ( // --arch was deprecated at 2017/08/15 and should eventually be removed. - archOld = flag.String("arch", "", "Deprecated, please use --platform instead") - Board = flag.String("board", "", "Board name.") - BuildInfo = flag.String("build-info", "", "") - Checksums = flag.StringSlice("checksums", []string{"sha1"}, "") - Description = flag.String("description", "", "") - Input = flag.StringP("input", "i", "", "") - Manifest = flag.String("manifest", "", "") - Name = flag.String("name", "", "") - Output = flag.StringP("output", "o", "", "") - platform = flag.String("platform", "", "Hardware platform") - SrcDir = flag.String("src-dir", "", "") - Compress = flag.Bool("compress", false, "") - GHToken = flag.String("gh-token", "", "") - ChunkSize = flag.Int("chunk-size", 512, "Chunk size for operations") - FsOpAttempts = flag.Int("fs-op-attempts", 3, "Chunk size for operations") + archOld = flag.String("arch", "", "Deprecated, please use --platform instead") + Port = flag.String("port", "auto", "Serial port where the device is connected. "+ + "If set to 'auto', ports on the system will be enumerated and the first will be used.") + BaudRate = flag.Int("baud-rate", 115200, "Serial port speed") + Board = flag.String("board", "", "Board name.") + BuildInfo = flag.String("build-info", "", "") + Checksums = flag.StringSlice("checksums", []string{"sha1"}, "") + Description = flag.String("description", "", "") + Input = flag.StringP("input", "i", "", "") + Manifest = flag.String("manifest", "", "") + Name = flag.String("name", "", "") + Output = flag.StringP("output", "o", "", "") + platform = flag.String("platform", "", "Hardware platform") + SrcDir = flag.String("src-dir", "", "") + Compress = flag.Bool("compress", false, "") + GHToken = flag.String("gh-token", "", "") + ChunkSize = flag.Int("chunk-size", 512, "Chunk size for operations") + FsOpAttempts = flag.Int("fs-op-attempts", 3, "Chunk size for operations") + PID = flag.String("pid", "mos", "") + UID = flag.String("uid", "", "") + CertFile = flag.String("cert-file", "", "Certificate file name") + KeyFile = flag.String("key-file", "", "Key file name") + CAFile = flag.String("ca-cert-file", "", "CA cert for TLS server verification") + RPCUARTNoDelay = flag.Bool("rpc-uart-no-delay", false, "Do not introduce delay into UART over RPC") + Timeout = flag.Duration("timeout", 20*time.Second, "Timeout for the device connection and call operation") + Reconnect = flag.Bool("reconnect", false, "Enable reconnection") + HWFC = flag.Bool("hw-flow-control", false, "Enable hardware flow control (CTS/RTS)") + LicenseServer = flag.String("license-server", "https://license.mongoose-os.com", "License server address") + + InvertedControlLines = flag.Bool("inverted-control-lines", false, "DTR and RTS control lines use inverted polarity") + SetControlLines = flag.Bool("set-control-lines", true, "Set RTS and DTR explicitly when in console/RPC mode") + + AzureConnectionString = flag.String("azure-connection-string", "", "Azure connection string") ) func Platform() string { @@ -33,3 +57,34 @@ func Platform() string { } return *archOld } + +func TLSConfigFromFlags() (*tls.Config, error) { + tlsConfig := &tls.Config{ + // TODO(rojer): Ship default CA bundle with mos. + InsecureSkipVerify: *CAFile == "", + } + + // Load client cert / key if specified + if *CertFile != "" && *KeyFile == "" { + return nil, errors.Errorf("Please specify --key-file") + } + if *CertFile != "" { + cert, err := tls.LoadX509KeyPair(*CertFile, *KeyFile) + if err != nil { + return nil, errors.Trace(err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + // Load CA cert if specified + if *CAFile != "" { + caCert, err := ioutil.ReadFile(*CAFile) + if err != nil { + return nil, errors.Trace(err) + } + tlsConfig.RootCAs = x509.NewCertPool() + tlsConfig.RootCAs.AppendCertsFromPEM(caCert) + } + + return tlsConfig, nil +} diff --git a/mos/flash.go b/mos/flash.go index df14928..9f94bbd 100644 --- a/mos/flash.go +++ b/mos/flash.go @@ -14,6 +14,8 @@ import ( "cesanta.com/common/go/fwbundle" "cesanta.com/common/go/ourutil" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" + "cesanta.com/mos/flags" "cesanta.com/mos/flash/cc3200" "cesanta.com/mos/flash/cc3220" "cesanta.com/mos/flash/esp" @@ -137,13 +139,13 @@ func flash(ctx context.Context, devConn *dev.DevConn) error { port := "" if fw.Platform != "stm32" { - port, err = getPort() + port, err = devutil.GetPort() if err != nil { return errors.Trace(err) } } - espFlashOpts.InvertedControlLines = *invertedControlLines + espFlashOpts.InvertedControlLines = *flags.InvertedControlLines switch strings.ToLower(fw.Platform) { case "cc3200": @@ -161,11 +163,11 @@ func flash(ctx context.Context, devConn *dev.DevConn) error { case "stm32": // Ideally we'd like to find mounted directory corresponding to the selected port. // But for now, we'll just find mountpoints that sort of look like STLink... - port = *portFlag + port = *flags.Port if port == "auto" || (strings.HasPrefix(port, "/dev/") || strings.HasPrefix(port, "COM")) { port, err = stm32.GetSTLinkMountForPort(port) if err != nil { - glog.Infof("Did not find port corresponding to %s: %s", *portFlag, err) + glog.Infof("Did not find port corresponding to %s: %s", *flags.Port, err) mm, err := stm32.GetSTLinkMounts() if err != nil { return errors.Trace(err) @@ -175,7 +177,7 @@ func flash(ctx context.Context, devConn *dev.DevConn) error { } port = mm[0] } else { - glog.Infof("%s -> %s", *portFlag, port) + glog.Infof("%s -> %s", *flags.Port, port) } } stm32FlashOpts.ShareName = port diff --git a/mos/flash_read.go b/mos/flash_read.go index 4467c36..ef9b588 100644 --- a/mos/flash_read.go +++ b/mos/flash_read.go @@ -10,6 +10,7 @@ import ( "context" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" "cesanta.com/mos/flags" "cesanta.com/mos/flash/esp" espFlasher "cesanta.com/mos/flash/esp/flasher" @@ -46,7 +47,7 @@ func flashRead(ctx context.Context, devConn *dev.DevConn) error { return errors.Errorf("invalid arguments") } - port, err := getPort() + port, err := devutil.GetPort() if err != nil { return errors.Trace(err) } diff --git a/mos/license_cmd/license.go b/mos/license_cmd/license.go index 5b5e9fc..247594d 100644 --- a/mos/license_cmd/license.go +++ b/mos/license_cmd/license.go @@ -2,14 +2,20 @@ package license import ( "bufio" + "bytes" "context" "encoding/json" "fmt" "io/ioutil" + "net/http" "os" + "cesanta.com/common/go/ourutil" "cesanta.com/mos/common/paths" + "cesanta.com/mos/config" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" + "cesanta.com/mos/flags" "github.com/cesanta/errors" ) @@ -25,7 +31,7 @@ func readToken() string { } func prompt() string { - fmt.Println(" 1. Please login to https://license.mongoose-os.com") + fmt.Printf(" 1. Please login to %s\n", *flags.LicenseServer) fmt.Println(" 2. Click on 'Key' in the top menu") fmt.Println(" 3. Copy the access key to the clipboard") fmt.Println(" 4. Paste the access key below and press enter") @@ -42,7 +48,19 @@ func saveToken() { ioutil.WriteFile(paths.AuthFilepath, data, 0600) } +type licenseRequest struct { + Type string `json:"type"` + DeviceID string `json:"device_id"` + App string `json:"app,omitempty"` +} + +type licenseResponse struct { + Text string `json:"text"` + Message string `json:"message"` +} + func License(ctx context.Context, devConn *dev.DevConn) error { + var err error token := readToken() if token == "" { saveToken() @@ -51,5 +69,68 @@ func License(ctx context.Context, devConn *dev.DevConn) error { return errors.New("Cannot save access token") } } - return errors.New("Are you looking at me? Not implemented yet!") + lreq := licenseRequest{ + Type: *flags.PID, + DeviceID: *flags.UID, + App: "", + } + if lreq.DeviceID == "" { + ourutil.Reportf("Connecting to the device...") + devConn, err = devutil.CreateDevConnFromFlags(ctx) + if err != nil { + return errors.Annotatef(err, "error connecting to device") + } + ourutil.Reportf("Querying device UID...") + rs, err := dev.CallDeviceService(ctx, devConn, "Sys.GetUID", "") + if err != nil { + return errors.Annotatef(err, "error querying device UID") + } + var res struct { + PID string `json:"pid"` + UID string `json:"uid"` + App string `json:"app"` + } + if err := json.Unmarshal([]byte(rs), &res); err != nil || res.UID == "" || res.PID == "" { + return errors.Annotatef(err, "invalid device reply %s", res) + } + ourutil.Reportf("PID: %s UID: %s", res.PID, res.UID) + lreq.Type = res.PID + lreq.DeviceID = res.UID + lreq.App = res.App + } + + ourutil.Reportf("Requesting license from %s...", *flags.LicenseServer) + + postData, _ := json.Marshal(&lreq) + url := fmt.Sprintf("%s/api/v1/license", *flags.LicenseServer) + client := &http.Client{} + req, err := http.NewRequest("POST", url, bytes.NewBuffer(postData)) + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) + resp, err := client.Do(req) + if err != nil { + return errors.Annotatef(err, "HTTP request failed %s", url) + } + defer resp.Body.Close() + rs, _ := ioutil.ReadAll(resp.Body) + var lresp licenseResponse + json.Unmarshal(rs, &lresp) + if lresp.Text == "" { + return errors.Errorf("Error obtaining license: %s", lresp.Message) + } + ourutil.Reportf("License: %s", lresp.Text) + if devConn == nil { + return nil + } + devConf, err := devConn.GetConfig(ctx) + if err != nil { + return errors.Annotatef(err, "failed to get config") + } + settings := map[string]string{ + "device.license": lresp.Text, + } + if err := config.ApplyDiff(devConf, settings); err != nil { + return errors.Trace(err) + } + return config.SetAndSave(ctx, devConn, devConf) } diff --git a/mos/main.go b/mos/main.go index 0389893..a047447 100644 --- a/mos/main.go +++ b/mos/main.go @@ -26,6 +26,7 @@ import ( "cesanta.com/mos/create_fw_bundle" "cesanta.com/mos/debug_core_dump" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" "cesanta.com/mos/fs" "cesanta.com/mos/gcp" license "cesanta.com/mos/license_cmd" @@ -55,17 +56,12 @@ var ( devicePass = flag.String("device-pass", "", "Device pass/key") dryRun = flag.Bool("dry-run", true, "Do not apply changes, print what would be done") firmware = flag.String("firmware", moscommon.GetFirmwareZipFilePath(moscommon.GetBuildDir("")), "Firmware .zip file location (file of HTTP URL)") - portFlag = flag.String("port", "auto", "Serial port where the device is connected. "+ - "If set to 'auto', ports on the system will be enumerated and the first will be used.") - timeout = flag.Duration("timeout", 20*time.Second, "Timeout for the device connection and call operation") - reconnect = flag.Bool("reconnect", false, "Enable reconnection") - force = flag.Bool("force", false, "Use the force") - verbose = flag.Bool("verbose", false, "Verbose output") - chdir = flag.StringP("chdir", "C", "", "Change into this directory first") - xFlag = flag.BoolP("enable-extended", "X", false, "Deprecated. Enable extended commands") - - invertedControlLines = flag.Bool("inverted-control-lines", false, "DTR and RTS control lines use inverted polarity") - helpFull = flag.Bool("full", false, "Show full help, including advanced flags") + force = flag.Bool("force", false, "Use the force") + verbose = flag.Bool("verbose", false, "Verbose output") + chdir = flag.StringP("chdir", "C", "", "Change into this directory first") + xFlag = flag.BoolP("enable-extended", "X", false, "Deprecated. Enable extended commands") + + helpFull = flag.Bool("full", false, "Show full help, including advanced flags") isUI = false ) @@ -118,7 +114,7 @@ func init() { {"gcp-iot-setup", gcp.GCPIoTSetup, `Provision the device for Google IoT Core`, nil, []string{"atca-slot", "gcp-region", "port", "use-atca", "registry"}, true, false}, {"watson-iot-setup", watson.WatsonIoTSetup, `Provision the device for IBM Watson IoT Platform`, nil, []string{}, true, false}, {"update", update.Update, `Self-update mos tool; optionally update channel can be given (e.g. "latest", "release", or some exact version)`, nil, nil, false, false}, - {"license", license.License, `License device`, nil, []string{"port"}, true, false}, + {"license", license.License, `License device`, nil, []string{"port"}, false, false}, {"wifi", wifi, `Setup WiFi - shortcut to config-set wifi...`, nil, nil, true, false}, {"help", showHelp, `Show help. Add --full to show advanced commands`, nil, nil, false, false}, {"version", showVersion, `Show version`, nil, nil, false, false}, @@ -155,7 +151,7 @@ func showVersion(ctx context.Context, devConn *dev.DevConn) error { } func showPorts(ctx context.Context, devConn *dev.DevConn) error { - fmt.Printf("%s\n", strings.Join(enumerateSerialPorts(), "\n")) + fmt.Printf("%s\n", strings.Join(devutil.EnumerateSerialPorts(), "\n")) return nil } @@ -257,7 +253,7 @@ func main() { } if cmd != nil && cmd.needDevConn { var err error - devConn, err = createDevConn(ctx) + devConn, err = devutil.CreateDevConnFromFlags(ctx) if err != nil { fmt.Println(errors.Trace(err)) os.Exit(1) diff --git a/mos/os_darwin.go b/mos/os_darwin.go index 79c8a94..75a5c30 100644 --- a/mos/os_darwin.go +++ b/mos/os_darwin.go @@ -5,7 +5,6 @@ import ( "os" "os/exec" "path/filepath" - "sort" "strings" zwebview "github.com/zserge/webview" diff --git a/mos/os_linux.go b/mos/os_linux.go index 824bf61..6ccbaae 100644 --- a/mos/os_linux.go +++ b/mos/os_linux.go @@ -2,19 +2,8 @@ package main import ( "fmt" - "path/filepath" - "sort" ) -func enumerateSerialPorts() []string { - // Note: Prefer ttyUSB* to ttyACM*. - list1, _ := filepath.Glob("/dev/ttyUSB*") - sort.Strings(list1) - list2, _ := filepath.Glob("/dev/ttyACM*") - sort.Strings(list2) - return append(list1, list2...) -} - func osSpecificInit() { } diff --git a/mos/os_windows.go b/mos/os_windows.go index fc88aa9..8e0ff5f 100644 --- a/mos/os_windows.go +++ b/mos/os_windows.go @@ -25,65 +25,6 @@ import ( zwebview "github.com/zserge/webview" ) -func enumerateSerialPorts() []string { - emptyList := []string{} - k, err := registry.OpenKey(registry.LOCAL_MACHINE, `HARDWARE\DEVICEMAP\SERIALCOMM\`, registry.QUERY_VALUE) - if err != nil { - return emptyList - } - defer k.Close() - list, err := k.ReadValueNames(0) - if err != nil { - return emptyList - } - vsm := make([]string, len(list)) - for i, v := range list { - val, _, _ := k.GetStringValue(v) - vsm[i] = val - } - sort.Strings(vsm) - return vsm -} - -func getCOMNumber(port string) int { - if !strings.HasPrefix(port, "COM") { - return -1 - } - cn, err := strconv.Atoi(port[3:]) - if err != nil { - return -1 - } - return cn -} - -type byCOMNumber []string - -func (a byCOMNumber) Len() int { return len(a) } -func (a byCOMNumber) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byCOMNumber) Less(i, j int) bool { - cni := getCOMNumber(a[i]) - cnj := getCOMNumber(a[j]) - if cni < 0 || cnj < 0 { - return a[i] < a[j] - } - return cni < cnj -} - -func getDefaultPort() string { - ports := enumerateSerialPorts() - var filteredPorts []string - for _, p := range ports { - // COM1 and COM2 are commonly mapped to on-board serial ports which are usually not a good guess. - if p != "COM1" && p != "COM2" { - filteredPorts = append(filteredPorts, p) - } - } - if len(filteredPorts) == 0 { - return "" - } - return filteredPorts[0] -} - func osSpecificInit() { if startWebview && len(os.Args) == 1 { C.hideWindow(C.CString(os.Args[0])) diff --git a/mos/ui.go b/mos/ui.go index 371d200..023accd 100644 --- a/mos/ui.go +++ b/mos/ui.go @@ -19,6 +19,8 @@ import ( "cesanta.com/common/go/ourutil" "cesanta.com/mos/dev" + "cesanta.com/mos/devutil" + "cesanta.com/mos/flags" "cesanta.com/mos/version" "github.com/cesanta/errors" "github.com/elazarl/go-bindata-assetfs" @@ -170,12 +172,12 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { numActiveMosCommands-- } if numActiveMosCommands == 0 { - if *portFlag == "" { + if *flags.Port == "" { cancel = nil } else { var ctx2 context.Context ctx2, cancel = context.WithCancel(ctx) - cmd := exec.CommandContext(ctx2, fullMosPath, "console", "--port", *portFlag) + cmd := exec.CommandContext(ctx2, fullMosPath, "console", "--port", *flags.Port) w := wsWriter{} cmd.Stdout = &w cmd.Stderr = &w @@ -186,7 +188,7 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { } }() - if *portFlag != "" { + if *flags.Port != "" { lockChan <- 0 // Start mos console if port is set } @@ -217,7 +219,7 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { PortFlag string Ports []string } - reply := GetPortsResult{*portFlag, enumerateSerialPorts()} + reply := GetPortsResult{*flags.Port, devutil.EnumerateSerialPorts()} httpReply(w, reply, nil) }) @@ -230,8 +232,8 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { http.HandleFunc("/serial", func(w http.ResponseWriter, r *http.Request) { port := r.FormValue("port") - if port != *portFlag { - *portFlag = port + if port != *flags.Port { + *flags.Port = port lockChan <- 0 // let console goroutine know the port has changed } httpReply(w, true, nil) @@ -262,12 +264,12 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { // Release port if mos command wants to grab it if cmd.name == "flash" || cmd.needDevConn { - if *portFlag == "" { + if *flags.Port == "" { httpReply(w, true, fmt.Errorf("Port not chosen")) return } args = append(args, "--port") - args = append(args, *portFlag) + args = append(args, *flags.Port) lockChan <- 1 defer func() { lockChan <- -1 @@ -300,7 +302,7 @@ func startUI(ctx context.Context, devConn *dev.DevConn) error { initialised := false prevList := "" for { - ports := enumerateSerialPorts() + ports := devutil.EnumerateSerialPorts() sort.Strings(ports) list := strings.Join(ports, ",") if initialised && list != prevList {