| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package cli |
| |
| import ( |
| "encoding/hex" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "strings" |
| "time" |
| |
| "mynewt.apache.org/newt/newtmgr/config" |
| "mynewt.apache.org/newt/newtmgr/core" |
| "mynewt.apache.org/newt/newtmgr/protocol" |
| "mynewt.apache.org/newt/newtmgr/transport" |
| "mynewt.apache.org/newt/util" |
| |
| "github.com/spf13/cobra" |
| ) |
| |
| var ( |
| coreElfify bool |
| coreOffset uint32 |
| coreNumBytes uint32 |
| ) |
| |
| func imageFlagsStr(image protocol.ImageStateEntry) string { |
| strs := []string{} |
| |
| if image.Active { |
| strs = append(strs, "active") |
| } |
| if image.Confirmed { |
| strs = append(strs, "confirmed") |
| } |
| if image.Pending { |
| strs = append(strs, "pending") |
| } |
| if image.Permanent { |
| strs = append(strs, "permanent") |
| } |
| |
| return strings.Join(strs, " ") |
| } |
| |
| func imageStatePrintRsp(rsp *protocol.ImageStateRsp) error { |
| if rsp.ReturnCode != 0 { |
| return util.FmtNewtError("rc=%d\n", rsp.ReturnCode) |
| } |
| fmt.Println("Images:") |
| for _, img := range rsp.Images { |
| fmt.Printf(" slot=%d\n", img.Slot) |
| fmt.Printf(" version: %s\n", img.Version) |
| fmt.Printf(" bootable: %v\n", img.Bootable) |
| fmt.Printf(" flags: %s\n", imageFlagsStr(img)) |
| if len(img.Hash) == 0 { |
| fmt.Printf(" hash: Unavailable\n") |
| } else { |
| fmt.Printf(" hash: %x\n", img.Hash) |
| } |
| } |
| |
| fmt.Printf("Split status: %s\n", rsp.SplitStatus.String()) |
| return nil |
| } |
| |
| func imageStateListCmd(cmd *cobra.Command, args []string) { |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| defer runner.Conn.Close() |
| |
| var nmr *protocol.NmgrReq |
| |
| req, err := protocol.NewImageStateReadReq() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| nmr, err = req.Encode() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| if err := runner.WriteReq(nmr); err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rawRsp, err := runner.ReadResp() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rsp, err := protocol.DecodeImageStateResponse(rawRsp.Data) |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| if err := imageStatePrintRsp(rsp); err != nil { |
| nmUsage(nil, err) |
| } |
| } |
| |
| func imageStateTestCmd(cmd *cobra.Command, args []string) { |
| if len(args) < 1 { |
| nmUsage(cmd, nil) |
| } |
| |
| hexBytes, _ := hex.DecodeString(args[0]) |
| |
| req, err := protocol.NewImageStateWriteReq() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| req.Hash = hexBytes |
| req.Confirm = false |
| |
| nmr, err := req.Encode() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| defer runner.Conn.Close() |
| |
| if err := runner.WriteReq(nmr); err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rawRsp, err := runner.ReadResp() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rsp, err := protocol.DecodeImageStateResponse(rawRsp.Data) |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| if err := imageStatePrintRsp(rsp); err != nil { |
| nmUsage(nil, err) |
| } |
| } |
| |
| func imageStateConfirmCmd(cmd *cobra.Command, args []string) { |
| req, err := protocol.NewImageStateWriteReq() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| if len(args) >= 1 { |
| hexBytes, _ := hex.DecodeString(args[0]) |
| req.Hash = hexBytes |
| } |
| req.Confirm = true |
| |
| nmr, err := req.Encode() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| defer runner.Conn.Close() |
| |
| if err := runner.WriteReq(nmr); err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rawRsp, err := runner.ReadResp() |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| |
| rsp, err := protocol.DecodeImageStateResponse(rawRsp.Data) |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| if err := imageStatePrintRsp(rsp); err != nil { |
| nmUsage(nil, err) |
| } |
| } |
| |
| func echoOnNmUsage( |
| runner *protocol.CmdRunner, cmderr error, cmd *cobra.Command) { |
| |
| echoCtrl(runner, "1") |
| nmUsage(cmd, cmderr) |
| } |
| |
| func imageUploadCmd(cmd *cobra.Command, args []string) { |
| if len(args) < 1 { |
| nmUsage(cmd, util.NewNewtError("Need to specify image to upload")) |
| } |
| |
| imageFile, err := ioutil.ReadFile(args[0]) |
| if err != nil { |
| nmUsage(cmd, util.NewNewtError(err.Error())) |
| } |
| |
| cpm, err := config.NewConnProfileMgr() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| profile, err := cpm.GetConnProfile(ConnProfileName) |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| conn, err := transport.NewConnWithTimeout(profile, time.Second*16) |
| if err != nil { |
| nmUsage(nil, err) |
| } |
| defer conn.Close() |
| |
| runner, err := protocol.NewCmdRunner(conn) |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| if profile.Type() == "serial" { |
| err = echoCtrl(runner, "0") |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| defer echoCtrl(runner, "1") |
| } |
| |
| var currOff uint32 = 0 |
| var mtu uint32 = 0 |
| imageSz := uint32(len(imageFile)) |
| rexmits := 0 |
| |
| if profile.Type() == "ble" { |
| mtu = uint32((transport.BleMTU - 64) * 3 / 4) |
| } else { |
| /* since this possibly gets base 64 encoded, we want |
| * to ensure that the payload leaving this layer is 91 |
| * bytes or less (91 bytes plus 2 byte crc will encode |
| * to 124 with 4 bytes of header |
| * left over */ |
| |
| /* 00000000 02 00 00 4f 00 01 00 01 a2 64 64 61 74 61 58 40 |...O.....ddataX@| |
| * 00000010 00 f0 5a f8 0e 4b 1c 70 0e 4b 5a 88 12 05 10 0f |..Z..K.p.KZ.....| |
| * 00000020 59 88 0d 4a 0a 40 5a 80 59 88 0c 4a 0a 40 5a 80 |Y..J.@Z.Y..J.@Z.| |
| * 00000030 19 1c 80 22 d2 01 4b 88 13 42 fc d1 05 49 02 02 |..."..K..B...I..| |
| * 00000040 48 88 05 4b 03 40 13 43 4b 80 00 f0 5d f8 10 bd |H..K.@.CK...]...| |
| * 00000050 63 6f 66 66 1a 00 01 5d b8 |coff..x| |
| */ |
| |
| /* from this dump we can see the following |
| * 1) newtmgr hdr 8 bytes |
| * 2) cbor wrapper up to data (and length) 8 bytes |
| * 3) cbor data 64 bytes |
| * 4) offset tag 4 bytes |
| * 5) offsert value 3 (safely say 5 bytes since it could be bigger |
| * than uint16_t |
| * That makes 25 bytes plus the data needs to fit in 91 bytes |
| */ |
| |
| /* however, something is not calcualated properly as we |
| * can only do 66 bytes here. Use 64 for power of 2 */ |
| mtu = 64 |
| } |
| |
| for currOff < imageSz { |
| imageUpload, err := protocol.NewImageUpload() |
| if err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| |
| blockSz := imageSz - currOff |
| if blockSz > mtu { |
| blockSz = mtu |
| } |
| if currOff == 0 { |
| /* we need extra space to encode the image size */ |
| if blockSz > (mtu - 8) { |
| /* |
| * to encode the image size, we write clen=val in CBOR. |
| * From below (for up to 2G images, you can see that it |
| * will take up to 9 bytes. (starts at 63.. ends at e8) |
| * 00000040 7d c4 00 00 7d c4 00 00 63 6c 65 6e 1a 00 01 5d |}...}...clen...]| |
| * 00000050 e8 63 6f 66 66 00 |.coff.| |
| * However, since the offset is zero, we will use less |
| * bytes (we budgeted for 5 bytes but will only use 1 |
| */ |
| |
| /* to make these powers of 2, just go with 8 bytes */ |
| blockSz = mtu - 8 |
| } |
| } |
| |
| imageUpload.Offset = currOff |
| imageUpload.Size = imageSz |
| imageUpload.Data = imageFile[currOff : currOff+blockSz] |
| |
| nmr, err := imageUpload.EncodeWriteRequest() |
| if err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| |
| var rsp *protocol.NmgrReq |
| var i int |
| for i = 0; i < 5; i++ { |
| if err := runner.WriteReq(nmr); err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| |
| rsp, err = runner.ReadResp() |
| if err == nil { |
| break |
| } |
| |
| /* |
| * Failed. Reopening tty. |
| */ |
| conn, err = transport.NewConnWithTimeout(profile, time.Second) |
| if err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| |
| runner, err = protocol.NewCmdRunner(conn) |
| if err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| } |
| rexmits += i |
| if i == 5 { |
| err = util.NewNewtError("Maximum number of TX retries reached") |
| } |
| if err != nil { |
| echoOnNmUsage(runner, err, cmd) |
| } |
| |
| ersp, err := protocol.DecodeImageUploadResponse(rsp.Data) |
| if err != nil { |
| echoOnNmUsage(runner, err, nil) |
| } |
| currOff = ersp.Offset |
| |
| fmt.Println(currOff) |
| } |
| |
| if rexmits != 0 { |
| fmt.Printf(" %d retransmits\n", rexmits) |
| } |
| fmt.Println("Done") |
| } |
| |
| func coreConvertCmd(cmd *cobra.Command, args []string) { |
| if len(args) < 2 { |
| nmUsage(cmd, nil) |
| return |
| } |
| |
| coreConvert, err := core.ConvertFilenames(args[0], args[1]) |
| if err != nil { |
| nmUsage(cmd, err) |
| return |
| } |
| |
| fmt.Printf("Corefile created for\n %x\n", coreConvert.ImageHash) |
| } |
| |
| func coreDownloadCmd(cmd *cobra.Command, args []string) { |
| if len(args) < 1 { |
| nmUsage(cmd, util.NewNewtError("Need to specify filename for core")) |
| return |
| } |
| |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| defer runner.Conn.Close() |
| |
| tmpName := args[0] + ".tmp" |
| file, err := os.OpenFile(tmpName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660) |
| if err != nil { |
| nmUsage(cmd, util.NewNewtError(fmt.Sprintf( |
| "Cannot open file %s - %s", tmpName, err.Error()))) |
| } |
| |
| coreDownload, err := protocol.NewCoreDownload() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| coreDownload.Runner = runner |
| coreDownload.File = file |
| |
| err = coreDownload.Download(coreOffset, coreNumBytes) |
| file.Close() |
| if err != nil { |
| fmt.Println(err) |
| return |
| } |
| |
| fmt.Println("Coredump download completed") |
| |
| if !coreElfify { |
| os.Rename(tmpName, args[0]) |
| return |
| } |
| |
| /* |
| * Download finished. Now convert to ELF corefile format. |
| */ |
| coreConvert, err := core.ConvertFilenames(tmpName, args[0]) |
| if err != nil { |
| nmUsage(cmd, err) |
| return |
| } |
| |
| if err != nil { |
| fmt.Println(err) |
| return |
| } |
| |
| os.Remove(tmpName) |
| fmt.Printf("Corefile created for\n %x\n", coreConvert.ImageHash) |
| } |
| |
| func coreListCmd(cmd *cobra.Command, args []string) { |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| defer runner.Conn.Close() |
| |
| coreList, err := protocol.NewCoreList() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| nmr, err := coreList.EncodeWriteRequest() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| if err := runner.WriteReq(nmr); err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| rsp, err := runner.ReadResp() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| clRsp, err := protocol.DecodeCoreListResponse(rsp.Data) |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| if clRsp.ErrCode == protocol.NMGR_ERR_OK { |
| fmt.Printf("Corefile present\n") |
| } else if clRsp.ErrCode == protocol.NMGR_ERR_ENOENT { |
| fmt.Printf("No corefiles\n") |
| } else { |
| fmt.Printf("List failed: %d\n", clRsp.ErrCode) |
| } |
| } |
| |
| func coreEraseCmd(cmd *cobra.Command, args []string) { |
| runner, err := getTargetCmdRunner() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| defer runner.Conn.Close() |
| |
| coreErase, err := protocol.NewCoreErase() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| nmr, err := coreErase.EncodeWriteRequest() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| if err := runner.WriteReq(nmr); err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| rsp, err := runner.ReadResp() |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| |
| ceRsp, err := protocol.DecodeCoreEraseResponse(rsp.Data) |
| if err != nil { |
| nmUsage(cmd, err) |
| } |
| if ceRsp.ErrCode != 0 { |
| fmt.Printf("Erase failed: %d\n", ceRsp.ErrCode) |
| } else { |
| fmt.Printf("Done\n") |
| } |
| } |
| |
| func imageCmd() *cobra.Command { |
| imageCmd := &cobra.Command{ |
| Use: "image", |
| Short: "Manage images on a device", |
| Run: func(cmd *cobra.Command, args []string) { |
| cmd.HelpFunc()(cmd, args) |
| }, |
| } |
| |
| listCmd := &cobra.Command{ |
| Use: "list", |
| Short: "Show images on a device", |
| Run: imageStateListCmd, |
| } |
| imageCmd.AddCommand(listCmd) |
| |
| testCmd := &cobra.Command{ |
| Use: "test <hex-image-hash>", |
| Short: "Test an image on next reboot", |
| Run: imageStateTestCmd, |
| } |
| imageCmd.AddCommand(testCmd) |
| |
| confirmCmd := &cobra.Command{ |
| Use: "confirm [hex-image-hash] -c <conn_profile>", |
| Short: "Permanently run image", |
| Long: "If a hash is specified, permanently switch to the " + |
| "corresponding image. If no hash is specified, the current " + |
| "image setup is made permanent.", |
| Run: imageStateConfirmCmd, |
| } |
| imageCmd.AddCommand(confirmCmd) |
| |
| uploadEx := " newtmgr -c olimex image upload bin/slinky_zero/apps/slinky.img\n" |
| |
| uploadCmd := &cobra.Command{ |
| Use: "upload <image-file> -c <conn_profile>", |
| Short: "Upload image to a device", |
| Example: uploadEx, |
| Run: imageUploadCmd, |
| } |
| imageCmd.AddCommand(uploadCmd) |
| |
| coreListEx := " newtmgr -c olimex image corelist\n" |
| |
| coreListCmd := &cobra.Command{ |
| Use: "corelist -c <conn_profile>", |
| Short: "List core(s) on a device", |
| Example: coreListEx, |
| Run: coreListCmd, |
| } |
| imageCmd.AddCommand(coreListCmd) |
| |
| coreEx := " newtmgr -c olimex image coredownload -e core\n" |
| coreEx += " newtmgr -c olimex image coredownload --offset 10 -n 10 core\n" |
| |
| coreDownloadCmd := &cobra.Command{ |
| Use: "coredownload <core-filename> -c <conn_profile>", |
| Short: "Download core from a device", |
| Example: coreEx, |
| Run: coreDownloadCmd, |
| } |
| coreDownloadCmd.Flags().BoolVarP(&coreElfify, "elfify", "e", false, "Create an ELF file") |
| coreDownloadCmd.Flags().Uint32Var(&coreOffset, "offset", 0, "Start offset") |
| coreDownloadCmd.Flags().Uint32VarP(&coreNumBytes, "bytes", "n", 0, "Number of bytes of the core to download") |
| imageCmd.AddCommand(coreDownloadCmd) |
| |
| coreConvertCmd := &cobra.Command{ |
| Use: "coreconvert <core-filename> <elf-filename>", |
| Short: "Convert core to ELF", |
| Run: coreConvertCmd, |
| } |
| imageCmd.AddCommand(coreConvertCmd) |
| |
| coreEraseEx := " newtmgr -c olimex image coreerase\n" |
| |
| coreEraseCmd := &cobra.Command{ |
| Use: "coreerase -c <conn_profile>", |
| Short: "Erase core on a device", |
| Example: coreEraseEx, |
| Run: coreEraseCmd, |
| } |
| imageCmd.AddCommand(coreEraseCmd) |
| |
| return imageCmd |
| } |