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