1/*
2 *  Copyright (c) 2010 The WebM project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include <stdlib.h>
12#include <string.h>
13
14#include "./vpx_version.h"
15
16#include "vpx/internal/vpx_codec_internal.h"
17#include "vpx/vp8dx.h"
18#include "vpx/vpx_decoder.h"
19
20#include "vp9/common/vp9_frame_buffers.h"
21
22#include "vp9/decoder/vp9_decoder.h"
23#include "vp9/decoder/vp9_read_bit_buffer.h"
24
25#include "vp9/vp9_iface_common.h"
26
27#define VP9_CAP_POSTPROC (CONFIG_VP9_POSTPROC ? VPX_CODEC_CAP_POSTPROC : 0)
28
29typedef vpx_codec_stream_info_t vp9_stream_info_t;
30
31struct vpx_codec_alg_priv {
32  vpx_codec_priv_t        base;
33  vpx_codec_dec_cfg_t     cfg;
34  vp9_stream_info_t       si;
35  int                     decoder_init;
36  struct VP9Decoder *pbi;
37  int                     postproc_cfg_set;
38  vp8_postproc_cfg_t      postproc_cfg;
39#if CONFIG_POSTPROC_VISUALIZER
40  unsigned int            dbg_postproc_flag;
41  int                     dbg_color_ref_frame_flag;
42  int                     dbg_color_mb_modes_flag;
43  int                     dbg_color_b_modes_flag;
44  int                     dbg_display_mv_flag;
45#endif
46  vpx_image_t             img;
47  int                     img_setup;
48  int                     img_avail;
49  int                     invert_tile_order;
50
51  // External frame buffer info to save for VP9 common.
52  void *ext_priv;  // Private data associated with the external frame buffers.
53  vpx_get_frame_buffer_cb_fn_t get_ext_fb_cb;
54  vpx_release_frame_buffer_cb_fn_t release_ext_fb_cb;
55};
56
57static vpx_codec_err_t decoder_init(vpx_codec_ctx_t *ctx,
58                            vpx_codec_priv_enc_mr_cfg_t *data) {
59  // This function only allocates space for the vpx_codec_alg_priv_t
60  // structure. More memory may be required at the time the stream
61  // information becomes known.
62  if (!ctx->priv) {
63    vpx_codec_alg_priv_t *alg_priv = vpx_memalign(32, sizeof(*alg_priv));
64    if (alg_priv == NULL)
65      return VPX_CODEC_MEM_ERROR;
66
67    vp9_zero(*alg_priv);
68
69    ctx->priv = (vpx_codec_priv_t *)alg_priv;
70    ctx->priv->sz = sizeof(*ctx->priv);
71    ctx->priv->iface = ctx->iface;
72    ctx->priv->alg_priv = alg_priv;
73    ctx->priv->alg_priv->si.sz = sizeof(ctx->priv->alg_priv->si);
74    ctx->priv->init_flags = ctx->init_flags;
75
76    if (ctx->config.dec) {
77      // Update the reference to the config structure to an internal copy.
78      ctx->priv->alg_priv->cfg = *ctx->config.dec;
79      ctx->config.dec = &ctx->priv->alg_priv->cfg;
80    }
81  }
82
83  return VPX_CODEC_OK;
84}
85
86static vpx_codec_err_t decoder_destroy(vpx_codec_alg_priv_t *ctx) {
87  if (ctx->pbi) {
88    vp9_decoder_remove(ctx->pbi);
89    ctx->pbi = NULL;
90  }
91
92  vpx_free(ctx);
93
94  return VPX_CODEC_OK;
95}
96
97static vpx_codec_err_t decoder_peek_si(const uint8_t *data,
98                                       unsigned int data_sz,
99                                       vpx_codec_stream_info_t *si) {
100  if (data_sz <= 8)
101    return VPX_CODEC_UNSUP_BITSTREAM;
102
103  if (data + data_sz <= data)
104    return VPX_CODEC_INVALID_PARAM;
105
106  si->is_kf = 0;
107  si->w = si->h = 0;
108
109  {
110    struct vp9_read_bit_buffer rb = { data, data + data_sz, 0, NULL, NULL };
111    const int frame_marker = vp9_rb_read_literal(&rb, 2);
112    const int version = vp9_rb_read_bit(&rb);
113    (void) vp9_rb_read_bit(&rb);  // unused version bit
114
115    if (frame_marker != VP9_FRAME_MARKER)
116      return VPX_CODEC_UNSUP_BITSTREAM;
117    if (version > 1) return VPX_CODEC_UNSUP_BITSTREAM;
118
119    if (vp9_rb_read_bit(&rb)) {  // show an existing frame
120      return VPX_CODEC_OK;
121    }
122
123    si->is_kf = !vp9_rb_read_bit(&rb);
124    if (si->is_kf) {
125      const int sRGB = 7;
126      int colorspace;
127
128      rb.bit_offset += 1;  // show frame
129      rb.bit_offset += 1;  // error resilient
130
131      if (vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_0 ||
132          vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_1 ||
133          vp9_rb_read_literal(&rb, 8) != VP9_SYNC_CODE_2) {
134        return VPX_CODEC_UNSUP_BITSTREAM;
135      }
136
137      colorspace = vp9_rb_read_literal(&rb, 3);
138      if (colorspace != sRGB) {
139        rb.bit_offset += 1;  // [16,235] (including xvycc) vs [0,255] range
140        if (version == 1) {
141          rb.bit_offset += 2;  // subsampling x/y
142          rb.bit_offset += 1;  // has extra plane
143        }
144      } else {
145        if (version == 1) {
146          rb.bit_offset += 1;  // has extra plane
147        } else {
148          // RGB is only available in version 1
149          return VPX_CODEC_UNSUP_BITSTREAM;
150        }
151      }
152
153      // TODO(jzern): these are available on non-keyframes in intra only mode.
154      si->w = vp9_rb_read_literal(&rb, 16) + 1;
155      si->h = vp9_rb_read_literal(&rb, 16) + 1;
156    }
157  }
158
159  return VPX_CODEC_OK;
160}
161
162static vpx_codec_err_t decoder_get_si(vpx_codec_alg_priv_t *ctx,
163                                      vpx_codec_stream_info_t *si) {
164  const size_t sz = (si->sz >= sizeof(vp9_stream_info_t))
165                       ? sizeof(vp9_stream_info_t)
166                       : sizeof(vpx_codec_stream_info_t);
167  memcpy(si, &ctx->si, sz);
168  si->sz = (unsigned int)sz;
169
170  return VPX_CODEC_OK;
171}
172
173static vpx_codec_err_t update_error_state(vpx_codec_alg_priv_t *ctx,
174                           const struct vpx_internal_error_info *error) {
175  if (error->error_code)
176    ctx->base.err_detail = error->has_detail ? error->detail : NULL;
177
178  return error->error_code;
179}
180
181static void init_buffer_callbacks(vpx_codec_alg_priv_t *ctx) {
182  VP9_COMMON *const cm = &ctx->pbi->common;
183
184  cm->new_fb_idx = -1;
185
186  if (ctx->get_ext_fb_cb != NULL && ctx->release_ext_fb_cb != NULL) {
187    cm->get_fb_cb = ctx->get_ext_fb_cb;
188    cm->release_fb_cb = ctx->release_ext_fb_cb;
189    cm->cb_priv = ctx->ext_priv;
190  } else {
191    cm->get_fb_cb = vp9_get_frame_buffer;
192    cm->release_fb_cb = vp9_release_frame_buffer;
193
194    if (vp9_alloc_internal_frame_buffers(&cm->int_frame_buffers))
195      vpx_internal_error(&cm->error, VPX_CODEC_MEM_ERROR,
196                         "Failed to initialize internal frame buffers");
197
198    cm->cb_priv = &cm->int_frame_buffers;
199  }
200}
201
202static void set_default_ppflags(vp8_postproc_cfg_t *cfg) {
203  cfg->post_proc_flag = VP8_DEBLOCK | VP8_DEMACROBLOCK;
204  cfg->deblocking_level = 4;
205  cfg->noise_level = 0;
206}
207
208static void set_ppflags(const vpx_codec_alg_priv_t *ctx,
209                        vp9_ppflags_t *flags) {
210  flags->post_proc_flag =
211#if CONFIG_POSTPROC_VISUALIZER
212      (ctx->dbg_color_ref_frame_flag ? VP9D_DEBUG_CLR_FRM_REF_BLKS : 0) |
213      (ctx->dbg_color_mb_modes_flag ? VP9D_DEBUG_CLR_BLK_MODES : 0) |
214      (ctx->dbg_color_b_modes_flag ? VP9D_DEBUG_CLR_BLK_MODES : 0) |
215      (ctx->dbg_display_mv_flag ? VP9D_DEBUG_DRAW_MV : 0) |
216#endif
217      ctx->postproc_cfg.post_proc_flag;
218
219  flags->deblocking_level = ctx->postproc_cfg.deblocking_level;
220  flags->noise_level = ctx->postproc_cfg.noise_level;
221#if CONFIG_POSTPROC_VISUALIZER
222  flags->display_ref_frame_flag = ctx->dbg_color_ref_frame_flag;
223  flags->display_mb_modes_flag = ctx->dbg_color_mb_modes_flag;
224  flags->display_b_modes_flag = ctx->dbg_color_b_modes_flag;
225  flags->display_mv_flag = ctx->dbg_display_mv_flag;
226#endif
227}
228
229static void init_decoder(vpx_codec_alg_priv_t *ctx) {
230  VP9D_CONFIG oxcf;
231  oxcf.width = ctx->si.w;
232  oxcf.height = ctx->si.h;
233  oxcf.version = 9;
234  oxcf.max_threads = ctx->cfg.threads;
235  oxcf.inv_tile_order = ctx->invert_tile_order;
236
237  ctx->pbi = vp9_decoder_create(&oxcf);
238  if (ctx->pbi == NULL)
239    return;
240
241  vp9_initialize_dec();
242
243  // If postprocessing was enabled by the application and a
244  // configuration has not been provided, default it.
245  if (!ctx->postproc_cfg_set &&
246      (ctx->base.init_flags & VPX_CODEC_USE_POSTPROC))
247    set_default_ppflags(&ctx->postproc_cfg);
248
249  init_buffer_callbacks(ctx);
250}
251
252static vpx_codec_err_t decode_one(vpx_codec_alg_priv_t *ctx,
253                                  const uint8_t **data, unsigned int data_sz,
254                                  void *user_priv, int64_t deadline) {
255  YV12_BUFFER_CONFIG sd = { 0 };
256  int64_t time_stamp = 0, time_end_stamp = 0;
257  vp9_ppflags_t flags = {0};
258  VP9_COMMON *cm = NULL;
259
260  ctx->img_avail = 0;
261
262  // Determine the stream parameters. Note that we rely on peek_si to
263  // validate that we have a buffer that does not wrap around the top
264  // of the heap.
265  if (!ctx->si.h) {
266    const vpx_codec_err_t res =
267        ctx->base.iface->dec.peek_si(*data, data_sz, &ctx->si);
268    if (res != VPX_CODEC_OK)
269      return res;
270  }
271
272  // Initialize the decoder instance on the first frame
273  if (!ctx->decoder_init) {
274    init_decoder(ctx);
275    if (ctx->pbi == NULL)
276      return VPX_CODEC_ERROR;
277
278    ctx->decoder_init = 1;
279  }
280
281  cm = &ctx->pbi->common;
282
283  if (vp9_receive_compressed_data(ctx->pbi, data_sz, data, deadline))
284    return update_error_state(ctx, &cm->error);
285
286  if (ctx->base.init_flags & VPX_CODEC_USE_POSTPROC)
287    set_ppflags(ctx, &flags);
288
289  if (vp9_get_raw_frame(ctx->pbi, &sd, &time_stamp, &time_end_stamp, &flags))
290    return update_error_state(ctx, &cm->error);
291
292  yuvconfig2image(&ctx->img, &sd, user_priv);
293  ctx->img.fb_priv = cm->frame_bufs[cm->new_fb_idx].raw_frame_buffer.priv;
294  ctx->img_avail = 1;
295
296  return VPX_CODEC_OK;
297}
298
299static void parse_superframe_index(const uint8_t *data, size_t data_sz,
300                                   uint32_t sizes[8], int *count) {
301  uint8_t marker;
302
303  assert(data_sz);
304  marker = data[data_sz - 1];
305  *count = 0;
306
307  if ((marker & 0xe0) == 0xc0) {
308    const uint32_t frames = (marker & 0x7) + 1;
309    const uint32_t mag = ((marker >> 3) & 0x3) + 1;
310    const size_t index_sz = 2 + mag * frames;
311
312    if (data_sz >= index_sz && data[data_sz - index_sz] == marker) {
313      // found a valid superframe index
314      uint32_t i, j;
315      const uint8_t *x = &data[data_sz - index_sz + 1];
316
317      for (i = 0; i < frames; i++) {
318        uint32_t this_sz = 0;
319
320        for (j = 0; j < mag; j++)
321          this_sz |= (*x++) << (j * 8);
322        sizes[i] = this_sz;
323      }
324
325      *count = frames;
326    }
327  }
328}
329
330static vpx_codec_err_t decoder_decode(vpx_codec_alg_priv_t *ctx,
331                                      const uint8_t *data, unsigned int data_sz,
332                                      void *user_priv, long deadline) {
333  const uint8_t *data_start = data;
334  const uint8_t *data_end = data + data_sz;
335  vpx_codec_err_t res = VPX_CODEC_OK;
336  uint32_t sizes[8];
337  int frames_this_pts, frame_count = 0;
338
339  if (data == NULL || data_sz == 0)
340    return VPX_CODEC_INVALID_PARAM;
341
342  parse_superframe_index(data, data_sz, sizes, &frames_this_pts);
343
344  do {
345    // Skip over the superframe index, if present
346    if (data_sz && (*data_start & 0xe0) == 0xc0) {
347      const uint8_t marker = *data_start;
348      const uint32_t frames = (marker & 0x7) + 1;
349      const uint32_t mag = ((marker >> 3) & 0x3) + 1;
350      const uint32_t index_sz = 2 + mag * frames;
351
352      if (data_sz >= index_sz && data_start[index_sz - 1] == marker) {
353        data_start += index_sz;
354        data_sz -= index_sz;
355        if (data_start < data_end)
356          continue;
357        else
358          break;
359      }
360    }
361
362    // Use the correct size for this frame, if an index is present.
363    if (frames_this_pts) {
364      uint32_t this_sz = sizes[frame_count];
365
366      if (data_sz < this_sz) {
367        ctx->base.err_detail = "Invalid frame size in index";
368        return VPX_CODEC_CORRUPT_FRAME;
369      }
370
371      data_sz = this_sz;
372      frame_count++;
373    }
374
375    res = decode_one(ctx, &data_start, data_sz, user_priv, deadline);
376    assert(data_start >= data);
377    assert(data_start <= data_end);
378
379    // Early exit if there was a decode error
380    if (res)
381      break;
382
383    // Account for suboptimal termination by the encoder.
384    while (data_start < data_end && *data_start == 0)
385      data_start++;
386
387    data_sz = (unsigned int)(data_end - data_start);
388  } while (data_start < data_end);
389
390  return res;
391}
392
393static vpx_image_t *decoder_get_frame(vpx_codec_alg_priv_t *ctx,
394                                      vpx_codec_iter_t *iter) {
395  vpx_image_t *img = NULL;
396
397  if (ctx->img_avail) {
398    // iter acts as a flip flop, so an image is only returned on the first
399    // call to get_frame.
400    if (!(*iter)) {
401      img = &ctx->img;
402      *iter = img;
403    }
404  }
405  ctx->img_avail = 0;
406
407  return img;
408}
409
410static vpx_codec_err_t decoder_set_fb_fn(
411    vpx_codec_alg_priv_t *ctx,
412    vpx_get_frame_buffer_cb_fn_t cb_get,
413    vpx_release_frame_buffer_cb_fn_t cb_release, void *cb_priv) {
414  if (cb_get == NULL || cb_release == NULL) {
415    return VPX_CODEC_INVALID_PARAM;
416  } else if (ctx->pbi == NULL) {
417    // If the decoder has already been initialized, do not accept changes to
418    // the frame buffer functions.
419    ctx->get_ext_fb_cb = cb_get;
420    ctx->release_ext_fb_cb = cb_release;
421    ctx->ext_priv = cb_priv;
422    return VPX_CODEC_OK;
423  }
424
425  return VPX_CODEC_ERROR;
426}
427
428static vpx_codec_err_t ctrl_set_reference(vpx_codec_alg_priv_t *ctx,
429                                          int ctr_id, va_list args) {
430  vpx_ref_frame_t *const data = va_arg(args, vpx_ref_frame_t *);
431
432  if (data) {
433    vpx_ref_frame_t *const frame = (vpx_ref_frame_t *)data;
434    YV12_BUFFER_CONFIG sd;
435
436    image2yuvconfig(&frame->img, &sd);
437    return vp9_set_reference_dec(&ctx->pbi->common,
438                                 (VP9_REFFRAME)frame->frame_type, &sd);
439  } else {
440    return VPX_CODEC_INVALID_PARAM;
441  }
442}
443
444static vpx_codec_err_t ctrl_copy_reference(vpx_codec_alg_priv_t *ctx,
445                                           int ctr_id, va_list args) {
446  vpx_ref_frame_t *data = va_arg(args, vpx_ref_frame_t *);
447
448  if (data) {
449    vpx_ref_frame_t *frame = (vpx_ref_frame_t *)data;
450    YV12_BUFFER_CONFIG sd;
451
452    image2yuvconfig(&frame->img, &sd);
453
454    return vp9_copy_reference_dec(ctx->pbi,
455                                  (VP9_REFFRAME)frame->frame_type, &sd);
456  } else {
457    return VPX_CODEC_INVALID_PARAM;
458  }
459}
460
461static vpx_codec_err_t ctrl_get_reference(vpx_codec_alg_priv_t *ctx,
462                                          int ctr_id, va_list args) {
463  vp9_ref_frame_t *data = va_arg(args, vp9_ref_frame_t *);
464
465  if (data) {
466    YV12_BUFFER_CONFIG* fb;
467
468    vp9_get_reference_dec(ctx->pbi, data->idx, &fb);
469    yuvconfig2image(&data->img, fb, NULL);
470    return VPX_CODEC_OK;
471  } else {
472    return VPX_CODEC_INVALID_PARAM;
473  }
474}
475
476static vpx_codec_err_t ctrl_set_postproc(vpx_codec_alg_priv_t *ctx,
477                                         int ctr_id, va_list args) {
478#if CONFIG_VP9_POSTPROC
479  vp8_postproc_cfg_t *data = va_arg(args, vp8_postproc_cfg_t *);
480
481  if (data) {
482    ctx->postproc_cfg_set = 1;
483    ctx->postproc_cfg = *((vp8_postproc_cfg_t *)data);
484    return VPX_CODEC_OK;
485  } else {
486    return VPX_CODEC_INVALID_PARAM;
487  }
488#else
489  return VPX_CODEC_INCAPABLE;
490#endif
491}
492
493static vpx_codec_err_t ctrl_set_dbg_options(vpx_codec_alg_priv_t *ctx,
494                                            int ctrl_id, va_list args) {
495#if CONFIG_POSTPROC_VISUALIZER && CONFIG_POSTPROC
496  int data = va_arg(args, int);
497
498#define MAP(id, var) case id: var = data; break;
499
500  switch (ctrl_id) {
501      MAP(VP8_SET_DBG_COLOR_REF_FRAME,   ctx->dbg_color_ref_frame_flag);
502      MAP(VP8_SET_DBG_COLOR_MB_MODES,    ctx->dbg_color_mb_modes_flag);
503      MAP(VP8_SET_DBG_COLOR_B_MODES,     ctx->dbg_color_b_modes_flag);
504      MAP(VP8_SET_DBG_DISPLAY_MV,        ctx->dbg_display_mv_flag);
505  }
506
507  return VPX_CODEC_OK;
508#else
509  return VPX_CODEC_INCAPABLE;
510#endif
511}
512
513static vpx_codec_err_t ctrl_get_last_ref_updates(vpx_codec_alg_priv_t *ctx,
514                                                 int ctrl_id, va_list args) {
515  int *const update_info = va_arg(args, int *);
516
517  if (update_info) {
518    if (ctx->pbi)
519      *update_info = ctx->pbi->refresh_frame_flags;
520    else
521      return VPX_CODEC_ERROR;
522    return VPX_CODEC_OK;
523  } else {
524    return VPX_CODEC_INVALID_PARAM;
525  }
526}
527
528
529static vpx_codec_err_t ctrl_get_frame_corrupted(vpx_codec_alg_priv_t *ctx,
530                                                int ctrl_id, va_list args) {
531  int *corrupted = va_arg(args, int *);
532
533  if (corrupted) {
534    if (ctx->pbi)
535      *corrupted = ctx->pbi->common.frame_to_show->corrupted;
536    else
537      return VPX_CODEC_ERROR;
538    return VPX_CODEC_OK;
539  } else {
540    return VPX_CODEC_INVALID_PARAM;
541  }
542}
543
544static vpx_codec_err_t ctrl_get_display_size(vpx_codec_alg_priv_t *ctx,
545                                             int ctrl_id, va_list args) {
546  int *const display_size = va_arg(args, int *);
547
548  if (display_size) {
549    if (ctx->pbi) {
550      const VP9_COMMON *const cm = &ctx->pbi->common;
551      display_size[0] = cm->display_width;
552      display_size[1] = cm->display_height;
553    } else {
554      return VPX_CODEC_ERROR;
555    }
556    return VPX_CODEC_OK;
557  } else {
558    return VPX_CODEC_INVALID_PARAM;
559  }
560}
561
562static vpx_codec_err_t ctrl_set_invert_tile_order(vpx_codec_alg_priv_t *ctx,
563                                                  int ctr_id, va_list args) {
564  ctx->invert_tile_order = va_arg(args, int);
565  return VPX_CODEC_OK;
566}
567
568static vpx_codec_ctrl_fn_map_t decoder_ctrl_maps[] = {
569  {VP8_COPY_REFERENCE,            ctrl_copy_reference},
570
571  // Setters
572  {VP8_SET_REFERENCE,             ctrl_set_reference},
573  {VP8_SET_POSTPROC,              ctrl_set_postproc},
574  {VP8_SET_DBG_COLOR_REF_FRAME,   ctrl_set_dbg_options},
575  {VP8_SET_DBG_COLOR_MB_MODES,    ctrl_set_dbg_options},
576  {VP8_SET_DBG_COLOR_B_MODES,     ctrl_set_dbg_options},
577  {VP8_SET_DBG_DISPLAY_MV,        ctrl_set_dbg_options},
578  {VP9_INVERT_TILE_DECODE_ORDER,  ctrl_set_invert_tile_order},
579
580  // Getters
581  {VP8D_GET_LAST_REF_UPDATES,     ctrl_get_last_ref_updates},
582  {VP8D_GET_FRAME_CORRUPTED,      ctrl_get_frame_corrupted},
583  {VP9_GET_REFERENCE,             ctrl_get_reference},
584  {VP9D_GET_DISPLAY_SIZE,         ctrl_get_display_size},
585
586  { -1, NULL},
587};
588
589#ifndef VERSION_STRING
590#define VERSION_STRING
591#endif
592CODEC_INTERFACE(vpx_codec_vp9_dx) = {
593  "WebM Project VP9 Decoder" VERSION_STRING,
594  VPX_CODEC_INTERNAL_ABI_VERSION,
595  VPX_CODEC_CAP_DECODER | VP9_CAP_POSTPROC |
596      VPX_CODEC_CAP_EXTERNAL_FRAME_BUFFER,  // vpx_codec_caps_t
597  decoder_init,       // vpx_codec_init_fn_t
598  decoder_destroy,    // vpx_codec_destroy_fn_t
599  decoder_ctrl_maps,  // vpx_codec_ctrl_fn_map_t
600  NOT_IMPLEMENTED,    // vpx_codec_get_mmap_fn_t
601  NOT_IMPLEMENTED,    // vpx_codec_set_mmap_fn_t
602  { // NOLINT
603    decoder_peek_si,    // vpx_codec_peek_si_fn_t
604    decoder_get_si,     // vpx_codec_get_si_fn_t
605    decoder_decode,     // vpx_codec_decode_fn_t
606    decoder_get_frame,  // vpx_codec_frame_get_fn_t
607    decoder_set_fb_fn,  // vpx_codec_set_fb_fn_t
608  },
609  { // NOLINT
610    NOT_IMPLEMENTED,
611    NOT_IMPLEMENTED,
612    NOT_IMPLEMENTED,
613    NOT_IMPLEMENTED,
614    NOT_IMPLEMENTED,
615    NOT_IMPLEMENTED
616  }
617};
618