aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2016-11-29 15:08:22 +0100
committerAleksander Morgado <aleksander@aleksander.es>2017-01-16 11:24:12 +0100
commitf535ed61f62efb17c977a812ef4688db622712b8 (patch)
treed1ad9b5f26705167a20aca8fa74677982dddc096
parent48e069c50a7e7cd7241c6bc412538e627ecf2ce1 (diff)
downloadexternal_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.zip
external_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.tar.gz
external_libqmi-f535ed61f62efb17c977a812ef4688db622712b8.tar.bz2
qmi-firmware-update: new QfuImage and QfuImageCwe objects
Implement objects to handle the firmware images.
-rw-r--r--.gitignore2
-rw-r--r--src/qmi-firmware-update/Makefile.am32
-rw-r--r--src/qmi-firmware-update/qfu-image-cwe.c246
-rw-r--r--src/qmi-firmware-update/qfu-image-cwe.h64
-rw-r--r--src/qmi-firmware-update/qfu-image-factory.c73
-rw-r--r--src/qmi-firmware-update/qfu-image-factory.h40
-rw-r--r--src/qmi-firmware-update/qfu-image.c387
-rw-r--r--src/qmi-firmware-update/qfu-image.h110
8 files changed, 954 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index cd94d7f..79216de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -77,3 +77,5 @@ Makefile.in
/utils/qmi-network
/utils/swi-update
+
+/src/qmi-firmware-update/qfu-enum-types.*
diff --git a/src/qmi-firmware-update/Makefile.am b/src/qmi-firmware-update/Makefile.am
index acbca21..1bbca27 100644
--- a/src/qmi-firmware-update/Makefile.am
+++ b/src/qmi-firmware-update/Makefile.am
@@ -11,11 +11,39 @@ qmi_firmware_update_CPPFLAGS = \
-I$(top_builddir)/src/libqmi-glib/generated \
$(NULL)
+ENUMS = \
+ qfu-image.h \
+ $(NULL)
+
+ENUMS_GENERATED = \
+ qfu-enum-types.h qfu-enum-types.c \
+ $(NULL)
+
+qfu-enum-types.h: Makefile.am $(ENUMS) $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h
+ $(AM_V_GEN) $(GLIB_MKENUMS) \
+ --fhead "#ifndef QFU_ENUM_TYPES_H\n#define QFU_ENUM_TYPES_H\n#include \"qfu-image.h\"\n" \
+ --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.h \
+ --ftail "#endif /* __QFUENUM_TYPES_H__ */\n" \
+ $(ENUMS) > $@
+
+qfu-enum-types.c: $(ENUMS) qfu-enum-types.h $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c
+ $(AM_V_GEN) $(GLIB_MKENUMS) \
+ --fhead "#include \"qfu-enum-types.h\"\n" \
+ --template $(top_srcdir)/build-aux/templates/qmi-enum-types-template.c \
+ $(ENUMS) > $@
+
+nodist_qmi_firmware_update_SOURCES = \
+ $(ENUMS_GENERATED) \
+ $(NULL)
+
qmi_firmware_update_SOURCES = \
qfu-main.c \
qfu-updater.h qfu-updater.c \
qfu-udev-helpers.h qfu-udev-helpers.c \
qfu-download-helpers.h qfu-download-helpers.c \
+ qfu-image.h qfu-image.c \
+ qfu-image-cwe.h qfu-image-cwe.c \
+ qfu-image-factory.h qfu-image-factory.c \
$(NULL)
qmi_firmware_update_LDADD = \
@@ -23,3 +51,7 @@ qmi_firmware_update_LDADD = \
$(GLIB_LIBS) \
$(top_builddir)/src/libqmi-glib/libqmi-glib.la \
$(NULL)
+
+# Request to build enum types before anything else
+BUILT_SOURCES = $(ENUMS_GENERATED)
+CLEANFILES = $(ENUMS_GENERATED)
diff --git a/src/qmi-firmware-update/qfu-image-cwe.c b/src/qmi-firmware-update/qfu-image-cwe.c
new file mode 100644
index 0000000..5f61f05
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image-cwe.c
@@ -0,0 +1,246 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include "qfu-image-cwe.h"
+
+static GInitableIface *iface_initable_parent;
+static void initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (QfuImageCwe, qfu_image_cwe, QFU_TYPE_IMAGE, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
+
+/* Sierra Wireless CWE file header
+ * Note: 32bit numbers are big endian
+ */
+typedef struct _QfuCweFileHeader QfuCweFileHeader;
+struct _QfuCweFileHeader {
+ gchar reserved1[256];
+ guint32 crc; /* 32bit CRC of "reserved1" field */
+ guint32 rev; /* header revision */
+ guint32 val; /* CRC validity indicator */
+ gchar type[4]; /* ASCII - not null terminated */
+ gchar product[4]; /* ASCII - not null terminated */
+ guint32 imgsize; /* image size */
+ guint32 imgcrc; /* 32bit CRC of the image */
+ gchar version[84]; /* ASCII - null terminated */
+ gchar date[8]; /* ASCII - null terminated */
+ guint32 compat; /* backward compatibility */
+ gchar reserved2[20];
+} __attribute__ ((packed));
+
+struct _QfuImageCwePrivate {
+ QfuCweFileHeader hdr;
+ gchar *type;
+ gchar *product;
+};
+
+/******************************************************************************/
+
+const gchar *
+qfu_image_cwe_header_get_type (QfuImageCwe *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL);
+
+ return self->priv->type;
+}
+
+const gchar *
+qfu_image_cwe_header_get_product (QfuImageCwe *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL);
+
+ return self->priv->product;
+}
+
+const gchar *
+qfu_image_cwe_header_get_version (QfuImageCwe *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL);
+
+ return self->priv->hdr.version;
+}
+
+const gchar *
+qfu_image_cwe_header_get_date (QfuImageCwe *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE_CWE (self), NULL);
+
+ return self->priv->hdr.date;
+}
+
+/******************************************************************************/
+
+static goffset
+get_header_size (QfuImage *self)
+{
+ return (goffset) sizeof (QfuCweFileHeader);
+}
+
+static goffset
+get_data_size (QfuImage *self)
+{
+ return qfu_image_get_size (self) - sizeof (QfuCweFileHeader);
+}
+
+static gssize
+read_header (QfuImage *_self,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ QfuImageCwe *self = QFU_IMAGE_CWE (_self);
+
+ if (out_buffer_size < sizeof (QfuCweFileHeader)) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "buffer too small to read header");
+ return -1;
+ }
+
+ memcpy (out_buffer, &self->priv->hdr, sizeof (QfuCweFileHeader));
+ return sizeof (QfuCweFileHeader);
+}
+
+/******************************************************************************/
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ QfuImageCwe *self;
+ GInputStream *input_stream = NULL;
+ gboolean result = FALSE;
+ gssize n_read;
+
+ self = QFU_IMAGE_CWE (initable);
+
+ /* Run parent initable */
+ if (!iface_initable_parent->init (initable, cancellable, error))
+ return FALSE;
+
+ g_object_get (self, "input-stream", &input_stream, NULL);
+ g_assert (G_IS_FILE_INPUT_STREAM (input_stream));
+
+ /* Preload file header */
+
+ g_debug ("[qfu-image-cwe] reading file header");
+
+ if (!g_seekable_seek (G_SEEKABLE (input_stream), 0, G_SEEK_SET, cancellable, error)) {
+ g_prefix_error (error, "couldn't seek input stream: ");
+ goto out;
+ }
+
+ n_read = g_input_stream_read (input_stream,
+ &self->priv->hdr,
+ sizeof (QfuCweFileHeader),
+ cancellable,
+ error);
+ if (n_read < 0) {
+ g_prefix_error (error, "couldn't read file header: ");
+ goto out;
+ }
+
+ if (n_read != sizeof (QfuCweFileHeader)) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "CWE firmware image file is too short: full header not available");
+ goto out;
+ }
+
+ /* Preload non-NUL terminated strings */
+ self->priv->type = g_strndup (self->priv->hdr.type, sizeof (self->priv->hdr.type));
+ self->priv->product = g_strndup (self->priv->hdr.product, sizeof (self->priv->hdr.product));
+
+ g_debug ("[qfu-image-cwe] validating data size...");
+
+ /* Validate image size as reported in the header */
+ if (qfu_image_get_data_size (QFU_IMAGE (self)) != GUINT32_FROM_BE (self->priv->hdr.imgsize)) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "CWE firmware image file is too short: full data not available");
+ goto out;
+ }
+
+ /* Success! */
+ result = TRUE;
+
+out:
+ g_object_unref (input_stream);
+ return result;
+}
+
+/******************************************************************************/
+
+QfuImage *
+qfu_image_cwe_new (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ return QFU_IMAGE (g_initable_new (QFU_TYPE_IMAGE_CWE,
+ cancellable,
+ error,
+ "file", file,
+ "image-type", QFU_IMAGE_TYPE_CWE,
+ NULL));
+}
+
+static void
+qfu_image_cwe_init (QfuImageCwe *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_IMAGE_CWE, QfuImageCwePrivate);
+}
+
+static void
+finalize (GObject *object)
+{
+ QfuImageCwe *self = QFU_IMAGE_CWE (object);
+
+ g_free (self->priv->type);
+ g_free (self->priv->product);
+
+ G_OBJECT_CLASS (qfu_image_cwe_parent_class)->finalize (object);
+}
+
+static void
+initable_iface_init (GInitableIface *iface)
+{
+ iface_initable_parent = g_type_interface_peek_parent (iface);
+ iface->init = initable_init;
+}
+
+static void
+qfu_image_cwe_class_init (QfuImageCweClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ QfuImageClass *image_class = QFU_IMAGE_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (QfuImageCwePrivate));
+
+ object_class->finalize = finalize;
+
+ image_class->get_header_size = get_header_size;
+ image_class->get_data_size = get_data_size;
+ image_class->read_header = read_header;
+
+}
diff --git a/src/qmi-firmware-update/qfu-image-cwe.h b/src/qmi-firmware-update/qfu-image-cwe.h
new file mode 100644
index 0000000..06bd424
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image-cwe.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef QFU_IMAGE_CWE_H
+#define QFU_IMAGE_CWE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "qfu-image.h"
+
+G_BEGIN_DECLS
+
+#define QFU_TYPE_IMAGE_CWE (qfu_image_cwe_get_type ())
+#define QFU_IMAGE_CWE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QFU_TYPE_IMAGE_CWE, QfuImageCwe))
+#define QFU_IMAGE_CWE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QFU_TYPE_IMAGE_CWE, QfuImageCweClass))
+#define QFU_IS_IMAGE_CWE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QFU_TYPE_IMAGE_CWE))
+#define QFU_IS_IMAGE_CWE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QFU_TYPE_IMAGE_CWE))
+#define QFU_IMAGE_CWE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QFU_TYPE_IMAGE_CWE, QfuImageCweClass))
+
+typedef struct _QfuImageCwe QfuImageCwe;
+typedef struct _QfuImageCweClass QfuImageCweClass;
+typedef struct _QfuImageCwePrivate QfuImageCwePrivate;
+
+struct _QfuImageCwe {
+ QfuImage parent;
+ QfuImageCwePrivate *priv;
+};
+
+struct _QfuImageCweClass {
+ QfuImageClass parent;
+};
+
+GType qfu_image_cwe_get_type (void);
+QfuImage *qfu_image_cwe_new (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+const gchar *qfu_image_cwe_header_get_type (QfuImageCwe *self);
+const gchar *qfu_image_cwe_header_get_product (QfuImageCwe *self);
+const gchar *qfu_image_cwe_header_get_version (QfuImageCwe *self);
+const gchar *qfu_image_cwe_header_get_date (QfuImageCwe *self);
+
+G_END_DECLS
+
+#endif /* QFU_IMAGE_CWE_H */
diff --git a/src/qmi-firmware-update/qfu-image-factory.c b/src/qmi-firmware-update/qfu-image-factory.c
new file mode 100644
index 0000000..1ee7b6d
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image-factory.c
@@ -0,0 +1,73 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "qfu-image-factory.h"
+#include "qfu-image.h"
+#include "qfu-image-cwe.h"
+
+QfuImage *
+qfu_image_factory_build (GFile *file,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gchar *basename;
+ QfuImageType image_type;
+ QfuImage *image = NULL;
+
+ g_assert (G_IS_FILE (file));
+ basename = g_file_get_basename (file);
+
+ /* guessing image type based on the well known Gobi 1k and 2k
+ * filenames, and assumes anything else is a CWE image
+ *
+ * This is based on the types in gobi-loader's snooped magic strings:
+ * 0x05 => "amss.mbn"
+ * 0x06 => "apps.mbn"
+ * 0x0d => "uqcn.mbn" (Gobi 2000 only)
+ */
+ if (g_ascii_strcasecmp (basename, "amss.mbn") == 0)
+ image_type = QFU_IMAGE_TYPE_AMSS_MODEM;
+ else if (g_ascii_strcasecmp (basename, "apps.mbn") == 0)
+ image_type = QFU_IMAGE_TYPE_AMSS_APPLICATION;
+ else if (g_ascii_strcasecmp (basename, "uqcn.mbn") == 0)
+ image_type = QFU_IMAGE_TYPE_AMSS_UQCN;
+ else
+ /* Note: should check validity of the image as this is the one used as
+ * default fallback */
+ image_type = QFU_IMAGE_TYPE_CWE;
+
+ switch (image_type) {
+ case QFU_IMAGE_TYPE_AMSS_MODEM:
+ case QFU_IMAGE_TYPE_AMSS_APPLICATION:
+ case QFU_IMAGE_TYPE_AMSS_UQCN:
+ image = qfu_image_new (file, image_type, cancellable, error);
+ break;
+ case QFU_IMAGE_TYPE_CWE:
+ image = qfu_image_cwe_new (file, cancellable, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_free (basename);
+ return image;
+}
diff --git a/src/qmi-firmware-update/qfu-image-factory.h b/src/qmi-firmware-update/qfu-image-factory.h
new file mode 100644
index 0000000..6ca1cd8
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image-factory.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef QFU_IMAGE_FACTORY_H
+#define QFU_IMAGE_FACTORY_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "qfu-image.h"
+#include "qfu-image-cwe.h"
+
+G_BEGIN_DECLS
+
+QfuImage *qfu_image_factory_build (GFile *file,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* QFU_IMAGE_H */
diff --git a/src/qmi-firmware-update/qfu-image.c b/src/qmi-firmware-update/qfu-image.c
new file mode 100644
index 0000000..830c945
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image.c
@@ -0,0 +1,387 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "qfu-image.h"
+#include "qfu-enum-types.h"
+
+static void initable_iface_init (GInitableIface *iface);
+
+G_DEFINE_TYPE_EXTENDED (QfuImage, qfu_image, G_TYPE_OBJECT, 0,
+ G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
+
+enum {
+ PROP_0,
+ PROP_FILE,
+ PROP_IMAGE_TYPE,
+ PROP_INPUT_STREAM,
+ PROP_LAST
+};
+
+static GParamSpec *properties[PROP_LAST];
+
+struct _QfuImagePrivate {
+ QfuImageType image_type;
+ GFile *file;
+ GFileInfo *info;
+ GInputStream *input_stream;
+};
+
+/******************************************************************************/
+
+gsize
+qfu_image_get_data_chunk_size (QfuImage *self,
+ guint16 chunk_i)
+{
+ gssize chunk_size = QFU_IMAGE_CHUNK_SIZE;
+ guint n_chunks;
+
+ n_chunks = qfu_image_get_n_data_chunks (self);
+ if (chunk_i == (n_chunks - 1)) {
+ chunk_size = qfu_image_get_data_size (self) - (chunk_i * QFU_IMAGE_CHUNK_SIZE);
+ g_assert (chunk_size > 0);
+ } else
+ chunk_size = QFU_IMAGE_CHUNK_SIZE;
+
+ return chunk_size;
+}
+
+gssize
+qfu_image_read_data_chunk (QfuImage *self,
+ guint16 chunk_i,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gssize chunk_size;
+ guint n_chunks;
+ goffset chunk_offset;
+ gssize n_read;
+
+ g_return_val_if_fail (QFU_IS_IMAGE (self), -1);
+
+ g_debug ("[qfu-image] reading chunk #%u", chunk_i);
+
+ n_chunks = qfu_image_get_n_data_chunks (self);
+ if (chunk_i >= n_chunks) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "invalid chunk index %u", chunk_i);
+ return -1;
+ }
+
+ /* Last chunk may be shorter than the default */
+ chunk_size = qfu_image_get_data_chunk_size (self, chunk_i);
+ g_debug ("[qfu-image] chunk #%u size: %" G_GSSIZE_FORMAT " bytes", chunk_i, chunk_size);
+
+ /* Make sure there's enough room */
+ if (out_buffer_size < chunk_size) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "buffer too small to fit chunk size: %" G_GSIZE_FORMAT, chunk_size);
+ return -1;
+ }
+
+ /* Compute chunk offset */
+ chunk_offset = qfu_image_get_header_size (self) + (chunk_i * QFU_IMAGE_CHUNK_SIZE);
+ g_debug ("[qfu-image] chunk #%u offset: %" G_GOFFSET_FORMAT " bytes", chunk_i, chunk_offset);
+
+ /* Seek to the correct place: note that this is likely a noop if already in that offset */
+ if (!g_seekable_seek (G_SEEKABLE (self->priv->input_stream), chunk_offset, G_SEEK_SET, cancellable, error)) {
+ g_prefix_error (error, "couldn't seek input stream: ");
+ return -1;
+ }
+
+ /* Read full chunk */
+ n_read = g_input_stream_read (self->priv->input_stream,
+ out_buffer,
+ chunk_size,
+ cancellable,
+ error);
+ if (n_read < 0) {
+ g_prefix_error (error, "couldn't read chunk %u", chunk_i);
+ return -1;
+ }
+
+ if (n_read != chunk_size) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "couldn't read expected chunk %u size: %" G_GSSIZE_FORMAT " (%" G_GSSIZE_FORMAT " bytes read)",
+ chunk_i, chunk_size, n_read);
+ return -1;
+ }
+
+ g_debug ("[qfu-image] chunk #%u successfully read", chunk_i);
+
+ return chunk_size;
+}
+
+/******************************************************************************/
+
+QfuImageType
+qfu_image_get_image_type (QfuImage *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), QFU_IMAGE_TYPE_UNKNOWN);
+
+ return self->priv->image_type;
+}
+
+const gchar *
+qfu_image_get_display_name (QfuImage *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), NULL);
+
+ return g_file_info_get_display_name (self->priv->info);
+}
+
+goffset
+qfu_image_get_size (QfuImage *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
+
+ return g_file_info_get_size (self->priv->info);
+}
+
+goffset
+qfu_image_get_header_size (QfuImage *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
+
+ return QFU_IMAGE_GET_CLASS (self)->get_header_size (self);
+}
+
+gssize
+qfu_image_read_header (QfuImage *self,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
+
+ return (QFU_IMAGE_GET_CLASS (self)->read_header ?
+ QFU_IMAGE_GET_CLASS (self)->read_header (self, out_buffer, out_buffer_size, cancellable, error) :
+ 0);
+}
+
+goffset
+qfu_image_get_data_size (QfuImage *self)
+{
+ g_return_val_if_fail (QFU_IS_IMAGE (self), 0);
+
+ return QFU_IMAGE_GET_CLASS (self)->get_data_size (self);
+}
+
+guint16
+qfu_image_get_n_data_chunks (QfuImage *self)
+{
+ goffset data_size;
+
+ data_size = qfu_image_get_data_size (self);
+ g_assert (data_size <= ((goffset) G_MAXUINT16 * (goffset) QFU_IMAGE_CHUNK_SIZE));
+
+ return (guint16) (data_size / QFU_IMAGE_CHUNK_SIZE) + !!(data_size % QFU_IMAGE_CHUNK_SIZE);
+}
+
+/******************************************************************************/
+
+static goffset
+get_header_size (QfuImage *self)
+{
+ return 0;
+}
+
+static goffset
+get_data_size (QfuImage *self)
+{
+ goffset file_size;
+
+ file_size = qfu_image_get_size (self);
+
+ /* some image types contain trailing garbage - from gobi-loader */
+ if (self->priv->image_type == QFU_IMAGE_TYPE_AMSS_MODEM)
+ file_size -= 8;
+
+ return file_size;
+}
+
+/******************************************************************************/
+
+static gboolean
+initable_init (GInitable *initable,
+ GCancellable *cancellable,
+ GError **error)
+{
+ QfuImage *self;
+
+ self = QFU_IMAGE (initable);
+ g_assert (self->priv->file);
+
+ /* Load file info */
+ g_debug ("[qfu-image] loading file info...");
+ self->priv->info = g_file_query_info (self->priv->file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NONE,
+ cancellable,
+ error);
+ if (!self->priv->info)
+ return FALSE;
+
+ /* Check minimum file size */
+ if (qfu_image_get_size (self) < qfu_image_get_header_size (self)) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "image is too short");
+ return FALSE;
+ }
+
+ /* Open file for reading. Kept open while the input stream reference is valid. */
+ g_debug ("[qfu-image] opening file for reading...");
+ self->priv->input_stream = G_INPUT_STREAM (g_file_read (self->priv->file, cancellable, error));
+ if (!self->priv->input_stream)
+ return FALSE;
+
+ return TRUE;
+}
+
+/******************************************************************************/
+
+QfuImage *
+qfu_image_new (GFile *file,
+ QfuImageType image_type,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+ return QFU_IMAGE (g_initable_new (QFU_TYPE_IMAGE,
+ cancellable,
+ error,
+ "file", file,
+ "image-type", image_type,
+ NULL));
+}
+
+static void
+qfu_image_init (QfuImage *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, QFU_TYPE_IMAGE, QfuImagePrivate);
+ self->priv->image_type = QFU_IMAGE_TYPE_UNKNOWN;
+}
+
+static void
+set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ QfuImage *self = QFU_IMAGE (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ self->priv->file = g_value_dup_object (value);
+ break;
+ case PROP_IMAGE_TYPE:
+ self->priv->image_type = g_value_get_enum (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ QfuImage *self = QFU_IMAGE (object);
+
+ switch (prop_id) {
+ case PROP_FILE:
+ g_value_set_object (value, self->priv->file);
+ break;
+ case PROP_IMAGE_TYPE:
+ g_value_set_enum (value, self->priv->image_type);
+ break;
+ case PROP_INPUT_STREAM:
+ g_value_set_object (value, self->priv->input_stream);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+dispose (GObject *object)
+{
+ QfuImage *self = QFU_IMAGE (object);
+
+ g_clear_object (&self->priv->input_stream);
+ g_clear_object (&self->priv->info);
+ g_clear_object (&self->priv->file);
+
+ G_OBJECT_CLASS (qfu_image_parent_class)->dispose (object);
+}
+
+static void
+initable_iface_init (GInitableIface *iface)
+{
+ iface->init = initable_init;
+}
+
+static void
+qfu_image_class_init (QfuImageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (object_class, sizeof (QfuImagePrivate));
+
+ object_class->dispose = dispose;
+ object_class->get_property = get_property;
+ object_class->set_property = set_property;
+
+ klass->get_header_size = get_header_size;
+ klass->get_data_size = get_data_size;
+
+ properties[PROP_FILE] =
+ g_param_spec_object ("file",
+ "File",
+ "File object",
+ G_TYPE_FILE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_FILE, properties[PROP_FILE]);
+
+ properties[PROP_IMAGE_TYPE] =
+ g_param_spec_enum ("image-type",
+ "Image type",
+ "Type of firmware image",
+ QFU_TYPE_IMAGE_TYPE,
+ QFU_IMAGE_TYPE_UNKNOWN,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_IMAGE_TYPE, properties[PROP_IMAGE_TYPE]);
+
+ properties[PROP_INPUT_STREAM] =
+ g_param_spec_object ("input-stream",
+ "Input stream",
+ "Input stream object",
+ G_TYPE_FILE_INPUT_STREAM,
+ G_PARAM_READABLE);
+ g_object_class_install_property (object_class, PROP_INPUT_STREAM, properties[PROP_INPUT_STREAM]);
+}
diff --git a/src/qmi-firmware-update/qfu-image.h b/src/qmi-firmware-update/qfu-image.h
new file mode 100644
index 0000000..8bd0304
--- /dev/null
+++ b/src/qmi-firmware-update/qfu-image.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * qmi-firmware-update -- Command line tool to update firmware in QMI devices
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Copyright (C) 2016 Bjørn Mork <bjorn@mork.no>
+ * Copyright (C) 2016 Zodiac Inflight Innovations
+ * Copyright (C) 2016 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef QFU_IMAGE_H
+#define QFU_IMAGE_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* Most of these origin from GobiAPI_1.0.40/Core/QDLEnum.h
+ *
+ * The gobi-loader's snooped magic strings use types
+ * 0x05 => "amss.mbn"
+ * 0x06 => "apps.mbn"
+ * 0x0d => "uqcn.mbn" (Gobi 2000 only)
+ * with no file header data
+ *
+ * The 0x80 type is snooped from the Sierra Wireless firmware
+ * uploaders, using 400 bytes file header data
+ */
+typedef enum {
+ QFU_IMAGE_TYPE_UNKNOWN = 0x00,
+ QFU_IMAGE_TYPE_AMSS_MODEM = 0x05,
+ QFU_IMAGE_TYPE_AMSS_APPLICATION = 0x06,
+ QFU_IMAGE_TYPE_AMSS_UQCN = 0x0d,
+ QFU_IMAGE_TYPE_DBL = 0x0f,
+ QFU_IMAGE_TYPE_OSBL = 0x10,
+ QFU_IMAGE_TYPE_CWE = 0x80,
+} QfuImageType;
+
+/* Default chunk size */
+#define QFU_IMAGE_CHUNK_SIZE (1024 * 1024)
+
+#define QFU_TYPE_IMAGE (qfu_image_get_type ())
+#define QFU_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), QFU_TYPE_IMAGE, QfuImage))
+#define QFU_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), QFU_TYPE_IMAGE, QfuImageClass))
+#define QFU_IS_IMAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), QFU_TYPE_IMAGE))
+#define QFU_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), QFU_TYPE_IMAGE))
+#define QFU_IMAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), QFU_TYPE_IMAGE, QfuImageClass))
+
+typedef struct _QfuImage QfuImage;
+typedef struct _QfuImageClass QfuImageClass;
+typedef struct _QfuImagePrivate QfuImagePrivate;
+
+struct _QfuImage {
+ GObject parent;
+ QfuImagePrivate *priv;
+};
+
+struct _QfuImageClass {
+ GObjectClass parent;
+
+ goffset (* get_header_size) (QfuImage *self);
+ gssize (* read_header) (QfuImage *self,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error);
+ goffset (* get_data_size) (QfuImage *self);
+};
+
+GType qfu_image_get_type (void);
+QfuImage *qfu_image_new (GFile *file,
+ QfuImageType image_type,
+ GCancellable *cancellable,
+ GError **error);
+QfuImageType qfu_image_get_image_type (QfuImage *self);
+const gchar *qfu_image_get_display_name (QfuImage *self);
+goffset qfu_image_get_size (QfuImage *self);
+goffset qfu_image_get_header_size (QfuImage *self);
+gssize qfu_image_read_header (QfuImage *self,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error);
+goffset qfu_image_get_data_size (QfuImage *self);
+guint16 qfu_image_get_n_data_chunks (QfuImage *self);
+gsize qfu_image_get_data_chunk_size (QfuImage *self,
+ guint16 chunk_i);
+gssize qfu_image_read_data_chunk (QfuImage *self,
+ guint16 chunk_i,
+ guint8 *out_buffer,
+ gsize out_buffer_size,
+ GCancellable *cancellable,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* QFU_IMAGE_H */