blob: 85f87e9ef972a82b91f84a11d2d677898235ee5f [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 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 parseRawTlvs(imgData []byte, offset int, size int) ([]ImageTlv, error) {
var tlvs []ImageTlv
end := offset + size
for offset < end {
tlv, tlvSize, err := parseRawTlv(imgData, offset)
if err != nil {
return nil, err
}
tlvs = append(tlvs, tlv)
offset += tlvSize
if offset > end {
return nil, errors.Errorf("TLVs extend beyond end of image")
}
}
return tlvs, 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
var protTrailer *ImageTrailer
var protTlvs []ImageTlv
if hdr.ProtSz > 0 {
pt, size, err := parseRawTrailer(imgData, offset)
if err != nil {
return img, err
}
protTrailer = &pt
offset += size
tlvsLen := int(hdr.ProtSz) - IMAGE_TRAILER_SIZE
pts, err := parseRawTlvs(imgData, offset, tlvsLen)
if err != nil {
return img, err
}
protTlvs = pts
offset += tlvsLen
}
trailer, size, err := parseRawTrailer(imgData, offset)
if err != nil {
return img, err
}
offset += size
totalLen := int(hdr.HdrSz) + len(body) + int(trailer.TlvTotLen)
if protTrailer != nil {
totalLen += int(protTrailer.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]
remLen := len(imgData) - offset
tlvs, err := parseRawTlvs(imgData, offset, remLen)
if err != nil {
return img, err
}
tlvLen := IMAGE_TRAILER_SIZE
if int(trailer.TlvTotLen) != IMAGE_TRAILER_SIZE+remLen {
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
img.ProtTlvs = protTlvs
extra := img.Header.HdrSz - IMAGE_HEADER_SIZE
if extra > 0 {
img.Pad = make([]byte, extra)
}
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)
}