/** @file dyn_parsed_capabilities.c
 *
 * Report parsed capabilities, taking into account dynamic feature definitions.
 */

// Copyright (C) 2014-2025 Sanford Rockowitz <rockowitz@minsoft.com>
// SPDX-License-Identifier: GPL-2.0-or-later

/** \cond */
#include <assert.h>
#include <glib-2.0/glib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>

#include "util/data_structures.h"
#include "util/report_util.h"
#include "util/string_util.h"
/** \endcond */

#include "base/core.h"
#include "base/ddc_command_codes.h"
#include "base/displays.h"
#include "base/rtti.h"

#include "vcp/parsed_capabilities_feature.h"
#include "vcp/vcp_feature_codes.h"

#include "vcp/parse_capabilities.h"
#include "vcp/parsed_capabilities_feature.h"

#include "dynvcp/dyn_feature_codes.h"
#include "dynvcp/dyn_feature_files.h"

#include "dynvcp/dyn_parsed_capabilities.h"


static const DDCA_Trace_Group TRACE_GROUP = DDCA_TRC_VCP;

/** Given a byte representing an absolute gamma value, as used in
 *  feature x72 (gamma), format a string representation of that value.
 *
 *  \param  buf    buffer in which to return formatted value
 *  \param  bufsz  size of buffer
 *  \param  bgamma byte representing gamma
 *  \return buf
 */
static char *
format_absolute_gamma(char * buf, int bufsz, Byte bgamma) {
   int i_gamma = bgamma + 100;
   char sgamma1[10];
   g_snprintf(sgamma1, 10, "%d", i_gamma);
   char * sgamma1_left =   lsub(sgamma1, strlen(sgamma1)-2);
   char * sgamma1_right =  substr(sgamma1, strlen(sgamma1)-2, 2);
   g_snprintf(buf, bufsz, "%s.%s", sgamma1_left, sgamma1_right);
   free(sgamma1_left);
   free(sgamma1_right);
   return buf;
}


/** Given a byte representing a relative gamma value, as used in
 *  feature x72 (gamma), return a string representation of that value.
 *
 *  \param  relative_gamma byte representing gamma
 *  \return string representation (do not free)
 */
static char *
format_relative_gamma(Byte relative_gamma)
{
   char * desc = NULL;
   switch(relative_gamma) {
   case 0x00:   desc = "Display default gamma";    break;
   case 0x01:   desc = "Default gamma - 0.1";      break;
   case 0x02:   desc = "Default gamma - 0.2";      break;
   case 0x03:   desc = "Default gamma - 0.3";      break;
   case 0x04:   desc = "Default gamma - 0.4";      break;
   case 0x05:   desc = "Default gamma - 0.5";      break;
   case 0x06:   desc = "Default gamma - 0.6";      break;
   case 0x07:   desc = "Default gamma - 0.7";      break;
   case 0x08:   desc = "Default gamma - 0.8";      break;
   case 0x09:   desc = "Default gamma - 0.9";      break;
   case 0x0a:   desc = "Default gamma - 1.0";      break;

   case 0x11:   desc = "Default gamma + 0.1";      break;
   case 0x12:   desc = "Default gamma + 0.2";      break;
   case 0x13:   desc = "Default gamma + 0.3";      break;
   case 0x14:   desc = "Default gamma + 0.4";      break;
   case 0x15:   desc = "Default gamma + 0.5";      break;
   case 0x16:   desc = "Default gamma + 0.6";      break;
   case 0x17:   desc = "Default gamma + 0.7";      break;
   case 0x18:   desc = "Default gamma + 0.8";      break;
   case 0x19:   desc = "Default gamma + 0.9";      break;
   case 0x1a:   desc = "Default gamma + 1.0";      break;

   default:     desc = "Invalid value";
   }
   return desc;
}


/** Special handling for interpreting the "value" bytes for feature x72 (gamma).
 *
 *  \param  feature_value_bytes  bytes to interpret
 *  \param  depth                logical indentation depth
 *
 *  \remark
 *  The bytes parm needs to be Byte_Value_Arrary, not a Bit_Bytes_Flag
 *  because the former returns the bytes in the order specified in the
 *  capabilities string, whereas the latter effectively sorts them.
 */
static void
report_gamma_capabilities(
      Byte_Value_Array               feature_value_bytes,
      int                            depth)
{
   bool debug = false;
   if (debug) {
      char * buf0 = bva_as_string(feature_value_bytes, true, " ");
      DBGMSG("feature_value_flags: %s", buf0);
      free(buf0);
   }

   int d0 = depth;
   Byte * bytes = bva_bytes(feature_value_bytes);
   int byte_ct = bva_length(feature_value_bytes);
   bool invalid_gamma_desc = false;
   bool relative_gamma = false;

   // meaningless default assignment to avoid subsequent -Werror=maybe-uninitialized error
   // when building for openSUSE_Leap_42.2 and openSUSE_Leap_42.3 under OBS
   // n. openSUSE_Leap_15.0 and openSUSE_Tumbleweed do not show this problem (2/2019)
   enum {gfull_range, glimited_range,gspecific_presets} gamma_mode = gfull_range;

   bool bypass_supported = false;
   char * absolute_tolerance_desc = "None";
   Byte specific_gammas[256];
   int  specific_gamma_ct = 0;

   if (byte_ct < 3) {
      // rpt_vstring(d0, "Invalid gamma values descriptor");
      invalid_gamma_desc = true;
      DBGMSF(debug, "Insufficient values, byte_ct=%d", byte_ct);
      goto bye;
   }

   // second value is always native gamma
   char s_native_gamma[10];
   format_absolute_gamma(s_native_gamma, 10, bytes[1]);
   DBGMSF(debug, "native gamma: %s (bytes[1] = 0x%02x)", s_native_gamma, bytes[1]);

   // third value describes range and how many specific values follow
   switch( bytes[2] ) {
   case 0xff:
   case 0xfe:
      gamma_mode = gfull_range;
      bypass_supported = (bytes[2] == 0xff) ? false : true;
      if (byte_ct != 3) {
         invalid_gamma_desc = true;
      }
      break;
   case 0xfd:
   case 0xfc:
      gamma_mode = glimited_range;
      bypass_supported = (bytes[2] == 0xfd) ? false : true;
      if (byte_ct != 5) {
         invalid_gamma_desc = true;
      }
      else {
         specific_gammas[0] = bytes[3];
         specific_gammas[1] = bytes[4];
         specific_gamma_ct = 2;
      }
      break;
   case 0xfb:
   case 0xfa:
      gamma_mode = gspecific_presets;
      bypass_supported = (bytes[2] == 0xfb) ? false : true;
      if (byte_ct < 4) {
         invalid_gamma_desc = true;
      }
      else {
         specific_gamma_ct = byte_ct - 3;
         memcpy(specific_gammas, bytes+3, specific_gamma_ct);
      }
      break;
   default:
      invalid_gamma_desc = true;
   }

   // first byte indicates relative vs absolute adjustment, and
   // for absolute adjustment the tolerance
   if (bytes[0] == 0xff) {   // relative adjustment
      DBGMSF(debug, "Relative adjustment (bytes[0] = 0x%02x)", bytes[0]);
      relative_gamma = true;
   }

   else {       // absolute adjustment
      if ( bytes[0] <= 0x0a ) {
         switch (bytes[0]) {
         case 0x00:  absolute_tolerance_desc = "ideal";   break;
         case 0x01:  absolute_tolerance_desc = "+/- 1%";  break;
         case 0x02:  absolute_tolerance_desc = "+/- 2%";  break;
         case 0x03:  absolute_tolerance_desc = "+/- 3%";  break;
         case 0x04:  absolute_tolerance_desc = "+/- 4%";  break;
         case 0x05:  absolute_tolerance_desc = "+/- 5%";  break;
         case 0x06:  absolute_tolerance_desc = "+/- 6%";  break;
         case 0x07:  absolute_tolerance_desc = "+/- 7%";  break;
         case 0x08:  absolute_tolerance_desc = "+/- 8%";  break;
         case 0x09:  absolute_tolerance_desc = "+/- 9%";  break;
         default:
            assert(bytes[0] == 0x0a);
            absolute_tolerance_desc = ">= 10%"; break;
         }
      }
      else {
         absolute_tolerance_desc = "None specified";
      }
      DBGMSF(debug, "absolute_tolerance: %s (bytes[0] = 0x%02x)", absolute_tolerance_desc, bytes[0]);
   }

   DBGMSF(debug, "Pre-output. invalid_gamma_desc=%s", sbool(invalid_gamma_desc));
   if (invalid_gamma_desc) {
      goto bye;
   }

   char * range_name = NULL;
   switch(gamma_mode) {
   case gfull_range:       range_name = "Full range";        break;
   case glimited_range:    range_name = "Limited range";     break;
   case gspecific_presets: range_name = "Specific presets";
   }
   rpt_vstring(d0, "%s of %s adjustment supported%s (%s0x%02x)",
         range_name,
         (relative_gamma) ? "relative" : "absolute",
               (bypass_supported)
               ? ", display has ability to bypass gamma correction"
                     : "",
         (debug) ? "bytes[2]=" : "",
         bytes[2]              );
   if (!relative_gamma) {   // absolute gamma
      rpt_vstring(d0, "Absolute tolerance: %s (%s=0x%02x)",
                       absolute_tolerance_desc,
                       (debug) ? "bytes[0]=" : "",
                       bytes[0]);
   }
   rpt_vstring(d0, "Native gamma: %s (0x%02x)", s_native_gamma, bytes[1]);
   if (gamma_mode == glimited_range) {
      if (relative_gamma) {
         rpt_vstring(d0, "Lower: %s (0x%02x), Upper: %s (0x%02x)",
               format_relative_gamma(bytes[3]), bytes[3],
               format_relative_gamma(bytes[4]), bytes[4]);
      }
      else {   // absolute gamma
         char sglower[10];
         char sgupper[10];
         rpt_vstring(d0, "Lower: %s (0x%02x), Upper: %s (0x%02x)",
               format_absolute_gamma(sglower, 10, bytes[3]),
               bytes[3],
               format_absolute_gamma(sgupper, 10, bytes[4]),
               bytes[4]
               );
      }
   }
   else if (gamma_mode == gspecific_presets) {
      // process specific_gammas
      char * buf = g_strdup("");
      char bgamma[10];
      char * sgamma = NULL;
      for (int ndx = 0; ndx < specific_gamma_ct; ndx++) {
         Byte raw_gamma = specific_gammas[ndx];
         if (relative_gamma) {
            sgamma = format_relative_gamma(raw_gamma);
         }
         else {  // absolute gamma
            sgamma = format_absolute_gamma(bgamma, 10, raw_gamma);
         }
         char buf2[100];
         g_snprintf(buf2, 100, "%s %s (0x%02x)",
                               (ndx > 0) ? "," : "",
                               sgamma, raw_gamma);
         char * bufold = buf;
         buf = g_strdup_printf("%s%s", bufold, buf2);
         free(bufold);
      }
      rpt_vstring(d0, "Specific presets: %s", buf);
      free(buf);
   }   // g_specific_presets

bye:
   if (invalid_gamma_desc) {
      char * buf0 = bva_as_string(feature_value_bytes, true, " ");
      rpt_vstring(d0, "Invalid gamma descriptor: %s", buf0);
      free(buf0);
   }

}


// From parsed_capabiliies_feature.c:

/** Displays the contents of a #Capabilities_Feature_Record as part
 *  of the **capabilities** command.
 *
 *  Output is written to the #FOUT device.
 *
 *  @param vfr         pointer to #Capabilities_Feature_Record
 *  @param dref        display reference
 *  @param vcp_version monitor VCP version, used in case feature
 *                     information is version specific, from parsed capabilities if possible
 *
 *  @param depth       logical indentation depth
 */
static void
dyn_report_one_cap_feature(
      Capabilities_Feature_Record *  vfr,
      Display_Ref *                  dref,
      DDCA_MCCS_Version_Spec         vcp_version,
      int                            depth)
{
   bool debug = false;
   DBGTRC_STARTING(debug, TRACE_GROUP, "vfr=%p, dref=%s, vcp_version=%d.%d",
                 vfr, dref_repr_t(dref), vcp_version.major, vcp_version.minor);
   assert(vfr && memcmp(vfr->marker, CAPABILITIES_FEATURE_MARKER, 4) == 0);

   int d0 = depth;
   int d1 = depth+1;
   int d2 = depth+2;

   Dyn_Feature_Metadata* dfr_metadata = NULL;
   if (dref) {
      assert(dref->dfr);  // will be dummy if no dfr read
      dfr_metadata = dyn_get_dynamic_feature_metadata(dref->dfr, vfr->feature_id);
   }
   if (dfr_metadata) {
      rpt_vstring(d0, "Feature: %02X (UDF:%s)",
                     vfr->feature_id, dfr_metadata->feature_name);
      if ((dfr_metadata->version_feature_flags & (DDCA_SIMPLE_NC|DDCA_EXTENDED_NC)) && dfr_metadata->sl_values) {
         rpt_label(d1 , "UDF Values:");
         DDCA_Feature_Value_Entry * cur = dfr_metadata->sl_values;
         while (cur->value_name) {
            rpt_vstring(d2, "%02x: %s", cur->value_code, cur->value_name);
            cur++;
         }
      }
   }

   else {
      rpt_vstring(d0, "Feature: %02X (%s)",
                     vfr->feature_id,
                     dyn_get_feature_name(vfr->feature_id, dref));  // n. handles dref == NULL
                     // get_feature_name_by_id_and_vcp_version(vfr->feature_id, vcp_version));

      DDCA_Output_Level ol = get_output_level();
      DBGTRC_NOPREFIX(debug,  DDCA_TRC_NONE, "vfr->value_string=%p", vfr->value_string);
      if (ol >= DDCA_OL_VERBOSE && vfr->value_string) {
         rpt_vstring(d1, "Values (unparsed): %s", vfr->value_string);
      }

      // only BVA variant works because of feature x72 (gamma)

#ifdef CFR_BVA
      // hex_dump((Byte*) vfr, sizeof(VCP_Feature_Record));
      // if (vfr->values)
      //    report_id_array(vfr->values, "Feature values:");
      char * buf0 = NULL;
      DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "vfr->values=%p", vfr->values);
      if (vfr->values) {
         if (vfr->feature_id == 0x72) { // special handling for feature x72 (gamma)
            report_gamma_capabilities(vfr->values, d1);
         }
         else {
            // Get the descriptions of the documented values for the feature
            DDCA_Feature_Value_Entry * feature_values =
                  find_feature_values_for_capabilities(vfr->feature_id, vcp_version);

            DBGTRC_NOPREFIX(debug, DDCA_TRC_NONE, "Feature values %sfound for feature 0x%02x",
                          (feature_values) ? "" : "NOT ",
                          vfr->feature_id);
            int ct = bva_length(vfr->values);

            if (feature_values) {  // did we find descriptions for the features?
               if (ol >= DDCA_OL_VERBOSE)
                  rpt_label(d1, "Values (  parsed):");
               else
                  rpt_label(d1 , "Values:");
               int ndx = 0;
               for (; ndx < ct; ndx++) {
                  Byte hval = bva_get(vfr->values, ndx);
                  char *  value_name = sl_value_table_lookup(feature_values, hval);
                  if (!value_name)
                     value_name = "Unrecognized value";
                  rpt_vstring(d2, "%02x: %s", hval, value_name);
               }
            }
            else {          // no interpretation available, just show the values
               int required_size = 3 * ct;
               buf0 = malloc(required_size);
               char * bufend = buf0+required_size;

               char * pos = buf0;
               int ndx = 0;
               for (; ndx < ct; ndx++) {
                  Byte hval = bva_get(vfr->values, ndx);
                  snprintf(pos, bufend-pos, "%02X ", hval);
                  pos = pos+3;
               }
               *(pos-1) = '\0';
               if (ol >= DDCA_OL_VERBOSE)
                  rpt_vstring(d1, "Values (  parsed): %s (interpretation unavailable)", buf0);
               else
                  rpt_vstring(d1, "Values: %s (interpretation unavailable)", buf0);
            }
         }
      }

      // assert( streq(buf0, vfr->value_string));
      if (buf0)
         free(buf0);
#endif

#ifdef CFR_BBF

      // WRONG: report_gamma_capabilities requires value bytes in order given in the
      // capabilities string, no way to get that from BitByteFlags
      DBGMSF(debug, "vfr->bbflags=%p", vfr->bbflags);
      if (vfr->bbflags) {    // WRONG TEST, NOT A POINTER

         // Get the descriptions of the documented values for the feature
         DDCA_Feature_Value_Entry * feature_values = NULL;
         bool found_dynamic_feature = false;
         if (dref && dref->dfr) {
            DDCA_Feature_Metadata * dfr_metadata
                     = dyn_get_dynamic_feature_metadata(dref->dfr, vfr->feature_id);
            if (dfr_metadata) {
               found_dynamic_feature = true;
               feature_values = dfr_metadata->sl_values;
            }
         }
         if (!found_dynamic_feature) {
            feature_values =
               find_feature_values_for_capabilities(vfr->feature_id, vcp_version);
         }
         DBGMSF(debug, "Feature values %sfound for feature 0x%02x",
                       (feature_values) ? "" : "NOT ",
                       vfr->feature_id);

         if (feature_values || vfr->feature_id == 0x72) {  // did we find descriptions for the features?
            if (ol >= DDCA_OL_VERBOSE)
               rpt_vstring(d1, "Values (  parsed):");
            else
               rpt_vstring(d1, "Values:");

            char * dynamic_disclaimer = "";
            if (found_dynamic_feature)
               dynamic_disclaimer = " (from user defined feature definition)";

            if (vfr->feature_id == 0x72) {    // special handling for gamma
               // WRONG: report_gamma_capabilities requires value bytes in order given in the
               // capabilities string, no way to get that from BitByteFlags
               report_gamma_capabilities(vfr->values, d2);
            }
            else {
               Byte_Bit_Flags iter = bbf_iter_new(vfr->bbflags);
               int nextval = -1;
               while ( (nextval = bbf_iter_next(iter)) >= 0) {
                     assert(nextval < 256);
                     char *  value_name = sl_value_table_lookup(feature_values, nextval);
                     if (!value_name)
                        value_name = "Unrecognized value";
                     rpt_vstring(d2, "%02x: %s%s", nextval, value_name, dynamic_disclaimer);
               }
               bbf_iter_free(iter);
            }
         }
         else {          // no interpretation available, just show the values
            char * buf1 = bbf_to_string(vfr->bbflags, NULL, 0);  // allocate buffer
            if (ol >= DDCA_OL_VERBOSE)
               rpt_vstring(d1, "Values (  parsed): %s (interpretation unavailable)", buf1);
            else
               rpt_vstring(d1, "Values: %s (interpretation unavailable)", buf1);
            free(buf1);
         }
      }
#endif
   }
   DBGTRC_DONE(debug, TRACE_GROUP, "");
}


// from vcp/parse_capabilities.c:


//
// Report parsed data structures
//

static void
report_commands(Byte_Value_Array cmd_ids, int depth)
{
   rpt_label(depth, "Commands:");
   int ct = bva_length(cmd_ids);
   int ndx = 0;
   for (; ndx < ct; ndx++) {
      Byte hval = bva_get(cmd_ids, ndx);
      rpt_vstring(depth+1, "Op Code: %02X (%s)", hval, ddc_cmd_code_name(hval));
   }
}


STATIC void
dyn_report_cap_features(
      GPtrArray*             features,     // GPtrArray of Capabilities_Feature_Record
      Display_Ref *          dref,
      DDCA_MCCS_Version_Spec vcp_version)  // from parsed capabilities if possible
{
   bool debug = false;
   int d0 = 0;
   int d1 = 1;

   DBGTRC_STARTING(debug, TRACE_GROUP, "dref=%s, vcp_version=%s",
                   dref_repr_t(dref), format_vspec(vcp_version));
   // assert(dref);

   if (dref) {
      if (!(dref->flags & DREF_DYNAMIC_FEATURES_CHECKED)) {
         Monitor_Model_Key mmk = mmk_value_from_edid(dref->pedid);
         Error_Info * erec = dfr_load_by_mmk(mmk, &dref->dfr);
         if (erec) {
            if (erec->status_code != DDCRC_NOT_FOUND || debug)
               errinfo_report(erec,1);
            errinfo_free(erec);
         }
         dref->flags |= DREF_DYNAMIC_FEATURES_CHECKED;
      }
   }

   rpt_label(d0, "VCP Features:");
   int ct = features->len;
   int ndx;
   for (ndx=0; ndx < ct; ndx++) {
      Capabilities_Feature_Record * vfr = g_ptr_array_index(features, ndx);
      DBGMSF(debug, "vfr = %p", vfr);
      dyn_report_one_cap_feature(vfr, dref, vcp_version, d1);
   }

   DBGTRC_DONE(debug, TRACE_GROUP, "");
}


/** Reports the Parsed_Capabilities struct for human consumption.
 *
 * @param pcaps parsed capabilities
 * @param dh    display handle, may be null
 * @param dref  display reference, may be null
 * @param depth logical indentation depth
 *
 * Output is written to the current report destination.
 *
 * @remark
 * dh/dref alternatives are needed to avoid double open of already opened device
 * @remark
 * If **dh** is non-null, dref is set to dh->dref.
 */
void dyn_report_parsed_capabilities(
      Parsed_Capabilities *    pcaps,
      Display_Handle *         dh,
      Display_Ref *            dref,
      int                      depth)
{
   int d0 = depth;
   int d1 = depth+1;
   int d2 = depth+2;
   bool debug = false;
   assert(pcaps && memcmp(pcaps->marker, PARSED_CAPABILITIES_MARKER, 4) == 0);
   DBGTRC_STARTING(debug, TRACE_GROUP, "dh=%s, dref=%s, pcaps->raw_cmds_segment_seen=%s, "
                 "pcaps->commands=%p, pcaps->vcp_features=%p",
                 dh_repr(dh), dref_repr_t(dref), sbool(pcaps->raw_cmds_segment_seen),
                 pcaps->commands, pcaps->vcp_features);

   if (dh)
      dref = dh->dref;

   bool saved_prefix_report_output = rpt_set_ornamentation_enabled(false);

   bool has_error_messages = pcaps->messages && pcaps->messages->len > 0;
   DDCA_Output_Level output_level = get_output_level();
   if (output_level >= DDCA_OL_VERBOSE || has_error_messages) {
      rpt_vstring(d0, "%s capabilities string: %s",
                      (pcaps->raw_value_synthesized) ? "Synthesized unparsed" : "Unparsed",
                      pcaps->raw_value);
   }

   if (has_error_messages) {
      rpt_label(d0, "Errors parsing capabilities string:");
      for (int ndx = 0; ndx < pcaps->messages->len; ndx++) {
         rpt_label(d1, g_ptr_array_index(pcaps->messages, ndx));
      }
   }

   rpt_vstring(d0, "Model: %s", (pcaps->model) ? pcaps->model : "Not specified");

   bool damaged = false;
   assert( ( pcaps->mccs_version_string && !vcp_version_eq(pcaps->parsed_mccs_version, DDCA_VSPEC_UNQUERIED) ) ||
           (!pcaps->mccs_version_string &&  vcp_version_eq(pcaps->parsed_mccs_version, DDCA_VSPEC_UNQUERIED) ) );
   if (pcaps->mccs_version_string) {
      char * s = (vcp_version_eq(pcaps->parsed_mccs_version, DDCA_VSPEC_UNKNOWN)) ? " (invalid)" : "";
      rpt_vstring(d0, "MCCS version: %s%s", pcaps->mccs_version_string, s);
   }
   else {
      rpt_vstring(d0, "MCCS version: Not specified");
   }

   if (pcaps->commands)
      report_commands(pcaps->commands, d0);
   else {
      // not an error in the case of USB_IO, as the capabilities string was
      // synthesized and does not include a commands segment
      // also, HP LP2480zx does not have cmds segment
      if (pcaps->raw_cmds_segment_seen)
         damaged = true;
   }

   // if vcp version unspecified in capabilities string, use queried value
   DDCA_MCCS_Version_Spec vspec = pcaps->parsed_mccs_version;
   // if (true) {
   if ( vcp_version_eq(vspec, DDCA_VSPEC_UNKNOWN) || vcp_version_eq(vspec, DDCA_VSPEC_UNQUERIED)) {
      if (dh)
         vspec = get_vcp_version_by_dh(dh);
      else if (dref)
         vspec = get_vcp_version_by_dref(dref);
   }

   if (pcaps->vcp_features) {

#ifdef X72_CAPABILITIES_TEST_CASES
      // for testing feature x72 gamma
      char * vstring = NULL;
      Capabilities_Feature_Record * cfr = NULL;

      vstring = "05 78 FB 50 64 78 8C";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
      vstring = "02 96 fe 50 a0";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
      vstring = "00 78 ff";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
      // invalid example in spec, inserted 3rd byte FB
      vstring = "FF 00 FB 01 03 05 07 09 11 13 15 17 19";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
      // invalid example in spec, 3rd byte should be bd or fc
      vstring = "FF 01 FC 05 15";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
      vstring = "FF 01 FE";
       cfr = new_capabilities_feature(0x72, vstring, strlen(vstring));
      g_ptr_array_add(pcaps->vcp_features, cfr);
#endif

      dyn_report_cap_features(pcaps->vcp_features, dref, vspec);
   }
   else {
      // handle pathological case of 0 length capabilities string, e.g. Samsung S32D850T
      if (pcaps->raw_vcp_features_seen)
         damaged = true;
   }

   if (damaged)   { // should use pcaps->caps_validity
      rpt_nl();
      rpt_label(d0, "Capabilities string not completely parsed");

      if (pcaps->messages && pcaps->messages->len > 0) {
         rpt_label(d1, "Errors:");
         for (int ndx = 0; ndx < pcaps->messages->len; ndx++) {
            char * s = g_ptr_array_index(pcaps->messages, ndx);
            rpt_label(d2, s);
         }
      }
   }

   rpt_set_ornamentation_enabled(saved_prefix_report_output);

   DBGTRC_DONE(debug, TRACE_GROUP, "");
}


void init_dyn_parsed_capabilities() {
   RTTI_ADD_FUNC(dyn_report_parsed_capabilities);
   RTTI_ADD_FUNC(dyn_report_cap_features);
   RTTI_ADD_FUNC(dyn_report_one_cap_feature);
}
