blob: b32ce601ec5cb4ba43f75a77bfd5f7c11c7dc79e [file] [log] [blame]
/**
* 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
}