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