image/parse.go (191 lines of code) (raw):

/** * 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) }