Fix double swap on interrupted revert

This fixes #480.

When mcuboot rewrites image trailers during a swap, some information is
lost.  If a reset occurs before the swap completes, mcuboot may not be
able to determine what which swap type to resume upon startup.
Specifically, if a "revert" swap gets interupted, mcuboot will perform
an extraneous swap on the subsequent boot.  See
https://github.com/JuulLabs-OSS/mcuboot/issues/480 for details.

This commit adds an additional field to the image trailer: `swap-type`.
The new trailer structure is illustrated below:

```
     0                   1                   2                   3
     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ~                                                               ~
    ~    Swap status (BOOT_MAX_IMG_SECTORS * min-write-size * 3)    ~
    ~                                                               ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ~                 Encryption key 0 (16 octets) [*]              ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ~                 Encryption key 1 (16 octets) [*]              ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |           Swap size           |    0xff padding (4 octets)    |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Swap type   |           0xff padding (7 octets)             ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Copy done   |           0xff padding (7 octets)             ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |   Image OK    |           0xff padding (7 octets)             ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ~                       MAGIC (16 octets)                       ~
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

The `swap-type` field contains one of the `BOOT_SWAP_TYPE_[...]` constants.
Every time a trailer is written, this field is written along with it.
When resuming an interrupted swap, mcuboot uses this field alone to
determine the type of swap being resumed. For new swap operations
(non-resume case), this field is not read at all; instead, mcuboot
consults the `boot_swap_tables` array to determine the swap operation to
perform (as it did prior to this commit).

Some additional changes were necessary to make all the simulated unit
tests pass:

* Before initiating a new swap operation, always write the image trailer
to the scratch area.  This step allows mcuboot to persist the
`swap-type` field somewhere before erasing the trailer in the primary
slot.  If a reset occurs immediately after the erase, mcuboot recovers
by using the trailer in the scratch area.

* Related to the above: if the scratch area is being used to hold status
bytes (because there are no spare sectors in the primary slot), erase
the scratch area immediately after the trailer gets written to the
primary slot.  This eliminates ambiguity regarding the location of the
current trailer in case a reset occurs shortly afterwards.

Signed-off-by: Christopher Collins <ccollins@apache.org>
diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c
index 0debf18..8cab4c4 100644
--- a/boot/bootutil/src/bootutil_misc.c
+++ b/boot/bootutil/src/bootutil_misc.c
@@ -119,6 +119,32 @@
     return BOOT_FLAG_SET;
 }
 
+/**
+ * Determines if a status source table is satisfied by the specified magic
+ * code.
+ *
+ * @param tbl_val               A magic field from a status source table.
+ * @param val                   The magic value in a trailer, encoded as a
+ *                                  BOOT_MAGIC_[...].
+ *
+ * @return                      1 if the two values are compatible;
+ *                              0 otherwise.
+ */
+int
+boot_magic_compatible_check(uint8_t tbl_val, uint8_t val)
+{
+    switch (tbl_val) {
+    case BOOT_MAGIC_ANY:
+        return 1;
+
+    case BOOT_MAGIC_NOTGOOD:
+        return val != BOOT_MAGIC_GOOD;
+
+    default:
+        return tbl_val == val;
+    }
+}
+
 uint32_t
 boot_trailer_sz(uint8_t min_write_sz)
 {
@@ -136,7 +162,6 @@
 static uint32_t
 boot_magic_off(const struct flash_area *fap)
 {
-    assert(offsetof(struct image_trailer, magic) == 16);
     return fap->fa_size - BOOT_MAGIC_SZ;
 }
 
@@ -168,17 +193,21 @@
     return fap->fa_size - off_from_end;
 }
 
+uint32_t
+boot_swap_type_off(const struct flash_area *fap)
+{
+    return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 3;
+}
+
 static uint32_t
 boot_copy_done_off(const struct flash_area *fap)
 {
-    assert(offsetof(struct image_trailer, copy_done) == 0);
     return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN * 2;
 }
 
 static uint32_t
 boot_image_ok_off(const struct flash_area *fap)
 {
-    assert(offsetof(struct image_trailer, image_ok) == 8);
     return fap->fa_size - BOOT_MAGIC_SZ - BOOT_MAX_ALIGN;
 }
 
@@ -216,6 +245,16 @@
         state->magic = boot_magic_decode(magic);
     }
 
+    off = boot_swap_type_off(fap);
+    rc = flash_area_read_is_empty(fap, off, &state->swap_type,
+            sizeof state->swap_type);
+    if (rc < 0) {
+        return BOOT_EFLASH;
+    }
+    if (rc == 1 || state->swap_type > BOOT_SWAP_TYPE_REVERT) {
+        state->swap_type = BOOT_SWAP_TYPE_NONE;
+    }
+
     off = boot_copy_done_off(fap);
     rc = flash_area_read_is_empty(fap, off, &state->copy_done,
             sizeof state->copy_done);
@@ -405,34 +444,19 @@
 }
 
 static int
-boot_write_flag(int flag, const struct flash_area *fap)
+boot_write_trailer_byte(const struct flash_area *fap, uint32_t off,
+                        uint8_t val)
 {
-    uint32_t off;
-    int rc;
     uint8_t buf[BOOT_MAX_ALIGN];
     uint8_t align;
     uint8_t erased_val;
-
-    switch (flag) {
-    case BOOT_FLAG_COPY_DONE:
-        off = boot_copy_done_off(fap);
-        BOOT_LOG_DBG("writing copy_done; fa_id=%d off=0x%x (0x%x)",
-                     fap->fa_id, off, fap->fa_off + off);
-        break;
-    case BOOT_FLAG_IMAGE_OK:
-        off = boot_image_ok_off(fap);
-        BOOT_LOG_DBG("writing image_ok; fa_id=%d off=0x%x (0x%x)",
-                     fap->fa_id, off, fap->fa_off + off);
-        break;
-    default:
-        return BOOT_EBADARGS;
-    }
+    int rc;
 
     align = flash_area_align(fap);
     assert(align <= BOOT_MAX_ALIGN);
     erased_val = flash_area_erased_val(fap);
     memset(buf, erased_val, BOOT_MAX_ALIGN);
-    buf[0] = BOOT_FLAG_SET;
+    buf[0] = val;
 
     rc = flash_area_write(fap, off, buf, align);
     if (rc != 0) {
@@ -445,13 +469,39 @@
 int
 boot_write_copy_done(const struct flash_area *fap)
 {
-    return boot_write_flag(BOOT_FLAG_COPY_DONE, fap);
+    uint32_t off;
+
+    off = boot_copy_done_off(fap);
+    BOOT_LOG_DBG("writing copy_done; fa_id=%d off=0x%x (0x%x)",
+                 fap->fa_id, off, fap->fa_off + off);
+    return boot_write_trailer_byte(fap, off, BOOT_FLAG_SET);
 }
 
 int
 boot_write_image_ok(const struct flash_area *fap)
 {
-    return boot_write_flag(BOOT_FLAG_IMAGE_OK, fap);
+    uint32_t off;
+
+    off = boot_image_ok_off(fap);
+    BOOT_LOG_DBG("writing image_ok; fa_id=%d off=0x%x (0x%x)",
+                 fap->fa_id, off, fap->fa_off + off);
+    return boot_write_trailer_byte(fap, off, BOOT_FLAG_SET);
+}
+
+/**
+ * Writes the specified value to the `swap-type` field of an image trailer.
+ * This value is persisted so that the boot loader knows what swap operation to
+ * resume in case of an unexpected reset.
+ */
+int
+boot_write_swap_type(const struct flash_area *fap, uint8_t swap_type)
+{
+    uint32_t off;
+
+    off = boot_swap_type_off(fap);
+    BOOT_LOG_DBG("writing swap_type; fa_id=%d off=0x%x (0x%x), swap_type=0x%x",
+                 fap->fa_id, off, fap->fa_off + off, swap_type);
+    return boot_write_trailer_byte(fap, off, swap_type);
 }
 
 int
@@ -524,10 +574,10 @@
     for (i = 0; i < BOOT_SWAP_TABLES_COUNT; i++) {
         table = boot_swap_tables + i;
 
-        if ((table->magic_primary_slot == BOOT_MAGIC_ANY     ||
-                table->magic_primary_slot == primary_slot.magic) &&
-            (table->magic_secondary_slot == BOOT_MAGIC_ANY   ||
-                table->magic_secondary_slot == secondary_slot.magic) &&
+        if (boot_magic_compatible_check(table->magic_primary_slot,
+                                        primary_slot.magic) &&
+            boot_magic_compatible_check(table->magic_secondary_slot,
+                                        secondary_slot.magic) &&
             (table->image_ok_primary_slot == BOOT_FLAG_ANY   ||
                 table->image_ok_primary_slot == primary_slot.image_ok) &&
             (table->image_ok_secondary_slot == BOOT_FLAG_ANY ||
@@ -566,6 +616,7 @@
 {
     const struct flash_area *fap;
     struct boot_swap_state state_secondary_slot;
+    uint8_t swap_type;
     int rc;
 
     rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY,
@@ -591,6 +642,15 @@
             rc = boot_write_image_ok(fap);
         }
 
+        if (rc == 0) {
+            if (permanent) {
+                swap_type = BOOT_SWAP_TYPE_PERM;
+            } else {
+                swap_type = BOOT_SWAP_TYPE_TEST;
+            }
+            rc = boot_write_swap_type(fap, swap_type);
+        }
+
         flash_area_close(fap);
         return rc;