Add wbmp.decoder.skip_frame
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 4104385..51cc52a 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -4245,6 +4245,7 @@
     uint32_t p_decode_image_config[1];
     uint32_t p_decode_frame_config[1];
     uint32_t p_decode_frame[1];
+    uint32_t p_skip_frame[1];
   } private_impl;
 
   struct {
@@ -4258,6 +4259,9 @@
       uint8_t v_src[1];
       uint8_t v_c;
     } s_decode_frame[1];
+    struct {
+      uint64_t scratch;
+    } s_skip_frame[1];
   } private_data;
 
 #ifdef __cplusplus
@@ -13009,6 +13013,10 @@
 
 // ---------------- Private Function Prototypes
 
+static wuffs_base__status  //
+wuffs_wbmp__decoder__skip_frame(wuffs_wbmp__decoder* self,
+                                wuffs_base__io_buffer* a_src);
+
 // ---------------- VTables
 
 const wuffs_base__image_decoder__func_ptrs
@@ -13288,6 +13296,11 @@
       }
     } else if (self->private_impl.f_call_sequence == 1) {
     } else if (self->private_impl.f_call_sequence == 2) {
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
+      status = wuffs_wbmp__decoder__skip_frame(self, a_src);
+      if (status.repr) {
+        goto suspend;
+      }
       status = wuffs_base__make_status(wuffs_base__note__end_of_data);
       goto ok;
     } else {
@@ -13489,6 +13502,71 @@
   return status;
 }
 
+// -------- func wbmp.decoder.skip_frame
+
+static wuffs_base__status  //
+wuffs_wbmp__decoder__skip_frame(wuffs_wbmp__decoder* self,
+                                wuffs_base__io_buffer* a_src) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
+  uint64_t v_bytes_per_row = 0;
+  uint64_t v_total_bytes = 0;
+
+  uint8_t* iop_a_src = NULL;
+  uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_skip_frame[0];
+  if (coro_susp_point) {
+  }
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    v_bytes_per_row = ((((uint64_t)(self->private_impl.f_width)) + 7) / 8);
+    v_total_bytes =
+        (v_bytes_per_row * ((uint64_t)(self->private_impl.f_height)));
+    self->private_data.s_skip_frame[0].scratch =
+        ((uint32_t)((v_total_bytes & 4294967295)));
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
+    if (self->private_data.s_skip_frame[0].scratch >
+        ((uint64_t)(io2_a_src - iop_a_src))) {
+      self->private_data.s_skip_frame[0].scratch -=
+          ((uint64_t)(io2_a_src - iop_a_src));
+      iop_a_src = io2_a_src;
+      status = wuffs_base__make_status(wuffs_base__suspension__short_read);
+      goto suspend;
+    }
+    iop_a_src += self->private_data.s_skip_frame[0].scratch;
+    self->private_impl.f_seen_frame = true;
+    self->private_impl.f_call_sequence = 2;
+
+    goto ok;
+  ok:
+    self->private_impl.p_skip_frame[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+suspend:
+  self->private_impl.p_skip_frame[0] =
+      wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+
+  goto exit;
+exit:
+  if (a_src) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  return status;
+}
+
 // -------- func wbmp.decoder.ack_metadata_chunk
 
 WUFFS_BASE__MAYBE_STATIC wuffs_base__status  //
diff --git a/std/wbmp/decode_wbmp.wuffs b/std/wbmp/decode_wbmp.wuffs
index 7671c21..442b3bd 100644
--- a/std/wbmp/decode_wbmp.wuffs
+++ b/std/wbmp/decode_wbmp.wuffs
@@ -101,7 +101,7 @@
 	} else if this.call_sequence == 1 {
 		// No-op.
 	} else if this.call_sequence == 2 {
-		// TODO: skip frame.
+		this.skip_frame?(src: args.src)
 		return base."@end of data"
 	} else {
 		return base."@end of data"
@@ -200,6 +200,20 @@
 	this.call_sequence = 3
 }
 
+pri func decoder.skip_frame?(src: base.io_reader) {
+	var bytes_per_row : base.u64[..= 0x20000000]
+	var total_bytes   : base.u64
+
+	bytes_per_row = ((this.width as base.u64) + 7) / 8
+	total_bytes = bytes_per_row * (this.height as base.u64)
+
+	// TODO: total_bytes may be larger than UINT32_MAX.
+	args.src.skip?(n: (total_bytes & 0xFFFFFFFF) as base.u32)
+
+	this.seen_frame = true
+	this.call_sequence = 2
+}
+
 pub func decoder.ack_metadata_chunk?(src: base.io_reader) {
 	// TODO: this final line shouldn't be necessary, here and in some other
 	// std/*/*.wuffs functions.
diff --git a/test/c/std/wbmp.c b/test/c/std/wbmp.c
index 0fc128d..fe492da 100644
--- a/test/c/std/wbmp.c
+++ b/test/c/std/wbmp.c
@@ -81,6 +81,35 @@
       "test/data/muybridge-frame-000.wbmp", 0, SIZE_MAX, 30, 20, 0xFFFFFFFF);
 }
 
+const char* test_wuffs_wbmp_decode_frame_config() {
+  CHECK_FOCUS(__func__);
+  wuffs_wbmp__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_wbmp__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+  wuffs_base__io_buffer src = ((wuffs_base__io_buffer){
+      .data = global_src_slice,
+  });
+  CHECK_STRING(read_file(&src, "test/data/hat.wbmp"));
+  CHECK_STATUS("decode_frame_config #0",
+               wuffs_wbmp__decoder__decode_frame_config(&dec, &fc, &src));
+
+  wuffs_base__status status =
+      wuffs_wbmp__decoder__decode_frame_config(&dec, &fc, &src);
+  if (status.repr != wuffs_base__note__end_of_data) {
+    RETURN_FAIL("decode_frame_config #1: got \"%s\", want \"%s\"", status.repr,
+                wuffs_base__note__end_of_data);
+  }
+  if (src.meta.ri != src.meta.wi) {
+    RETURN_FAIL("at end of data: ri (%zu) doesn't equal wi (%zu)", src.meta.ri,
+                src.meta.wi);
+  }
+  return NULL;
+}
+
 const char* test_wuffs_wbmp_decode_image_config() {
   CHECK_FOCUS(__func__);
   wuffs_wbmp__decoder dec;
@@ -136,6 +165,7 @@
 // The empty comments forces clang-format to place one element per line.
 proc tests[] = {
 
+    test_wuffs_wbmp_decode_frame_config,  //
     test_wuffs_wbmp_decode_image_config,  //
     test_wuffs_wbmp_decode_interface,     //