blob: f1019a81fec2b24f42f2694ce211cd4a7c6c07e0 [file] [log] [blame]
package image
import (
"bytes"
"encoding/binary"
"io"
"io/ioutil"
"strconv"
"strings"
"github.com/apache/mynewt-artifact/errors"
)
// ParseVersion parses an image version string (e.g., "1.2.3.4")
func ParseVersion(versStr string) (ImageVersion, error) {
var err error
var major uint64
var minor uint64
var rev uint64
var buildNum uint64
var ver ImageVersion
components := strings.SplitN(versStr, ".", 4)
major, err = strconv.ParseUint(components[0], 10, 8)
if err != nil {
return ver, errors.Errorf("invalid version string %s", versStr)
}
if len(components) > 1 {
minor, err = strconv.ParseUint(components[1], 10, 8)
if err != nil {
return ver, errors.Errorf("invalid version string %s", versStr)
}
}
if len(components) > 2 {
rev, err = strconv.ParseUint(components[2], 10, 16)
if err != nil {
return ver, errors.Errorf("invalid version string %s", versStr)
}
}
if len(components) > 3 {
buildNum, err = strconv.ParseUint(components[3], 10, 32)
if err != nil {
return ver, errors.Errorf("invalid version string %s", versStr)
}
}
ver.Major = uint8(major)
ver.Minor = uint8(minor)
ver.Rev = uint16(rev)
ver.BuildNum = uint32(buildNum)
return ver, nil
}
func parseRawHeader(imgData []byte, offset int) (ImageHdr, int, error) {
var hdr ImageHdr
r := bytes.NewReader(imgData)
r.Seek(int64(offset), io.SeekStart)
if err := binary.Read(r, binary.LittleEndian, &hdr); err != nil {
return hdr, 0, errors.Wrapf(err, "error reading image header")
}
if hdr.Magic != IMAGE_MAGIC {
return hdr, 0, errors.Errorf(
"image magic incorrect; expected 0x%08x, got 0x%08x",
uint32(IMAGE_MAGIC), hdr.Magic)
}
remLen := len(imgData) - offset
if remLen < int(hdr.HdrSz) {
return hdr, 0, errors.Errorf(
"image header incomplete; expected %d bytes, got %d bytes",
hdr.HdrSz, remLen)
}
return hdr, int(hdr.HdrSz), nil
}
func parseRawBody(imgData []byte, hdr ImageHdr,
offset int) ([]byte, int, error) {
imgSz := int(hdr.ImgSz)
remLen := len(imgData) - offset
if remLen < imgSz {
return nil, 0, errors.Errorf(
"image body incomplete; expected %d bytes, got %d bytes",
imgSz, remLen)
}
return imgData[offset : offset+imgSz], imgSz, nil
}
func parseRawTrailer(imgData []byte, offset int) (ImageTrailer, int, error) {
var trailer ImageTrailer
r := bytes.NewReader(imgData)
r.Seek(int64(offset), io.SeekStart)
if err := binary.Read(r, binary.LittleEndian, &trailer); err != nil {
return trailer, 0, errors.Wrapf(err,
"image contains invalid trailer at offset %d", offset)
}
return trailer, IMAGE_TRAILER_SIZE, nil
}
func parseRawTlv(imgData []byte, offset int) (ImageTlv, int, error) {
tlv := ImageTlv{}
r := bytes.NewReader(imgData)
r.Seek(int64(offset), io.SeekStart)
if err := binary.Read(r, binary.LittleEndian, &tlv.Header); err != nil {
return tlv, 0, errors.Wrapf(err,
"image contains invalid TLV at offset %d", offset)
}
tlv.Data = make([]byte, tlv.Header.Len)
if _, err := r.Read(tlv.Data); err != nil {
return tlv, 0, errors.Wrapf(err,
"image contains invalid TLV at offset %d", offset)
}
return tlv, IMAGE_TLV_SIZE + int(tlv.Header.Len), nil
}
func ParseImage(imgData []byte) (Image, error) {
img := Image{}
offset := 0
hdr, size, err := parseRawHeader(imgData, offset)
if err != nil {
return img, err
}
offset += size
body, size, err := parseRawBody(imgData, hdr, offset)
if err != nil {
return img, err
}
offset += size
trailer, size, err := parseRawTrailer(imgData, offset)
if err != nil {
return img, err
}
offset += size
totalLen := IMAGE_HEADER_SIZE + len(body) + int(trailer.TlvTotLen)
if len(imgData) < totalLen {
return img, errors.Errorf("image data truncated: have=%d want=%d",
len(imgData), totalLen)
}
// Trim excess data following image trailer.
imgData = imgData[:totalLen]
var tlvs []ImageTlv
tlvLen := IMAGE_TRAILER_SIZE
for offset < len(imgData) {
tlv, size, err := parseRawTlv(imgData, offset)
if err != nil {
return img, err
}
tlvs = append(tlvs, tlv)
offset += size
if offset > len(imgData) {
return img, errors.Errorf("TLVs extend beyond end of image")
}
tlvLen += size
}
if int(trailer.TlvTotLen) != tlvLen {
return img, errors.Errorf(
"invalid image: trailer indicates TLV-length=%d; actual=%d",
trailer.TlvTotLen, tlvLen)
}
img.Header = hdr
img.Body = body
img.Tlvs = tlvs
return img, nil
}
func ReadImage(filename string) (Image, error) {
ri := Image{}
imgData, err := ioutil.ReadFile(filename)
if err != nil {
return ri, errors.Wrapf(err, "failed to read image from file")
}
return ParseImage(imgData)
}