tuxji commented on a change in pull request #650:
URL: https://github.com/apache/daffodil/pull/650#discussion_r722694112



##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
##########
@@ -361,6 +436,9 @@ xmlNumberElem(XMLReader *reader, const ERD *erd, void 
*number)
             case PRIMITIVE_DOUBLE:
                 *(double *)number = strtodnum(number_from_xml, &error);
                 return error;
+            case PRIMITIVE_HEXBINARY:
+                error = hexToBinary(number_from_xml, (HexBinary *)number);

Review comment:
       All good suggestions.  I'm renaming `hexToBinary` to `strtohexbinary`, 
passing value out parameter `valueptr` to all `strtoX` functions, and getting 
`error` from their return value.  I had picked the other way around to make the 
functions more similar to the C library's own `strtoX` functions but this way 
makes sense.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/infoset.h
##########
@@ -91,6 +94,15 @@ typedef struct ERD
     const InitChoiceRD   initChoice;
 } ERD;
 
+// HexBinary - parameters of a hexBinary element
+
+typedef struct HexBinary
+{
+    uint8_t *array;   // pointer to element's byte array
+    size_t   size;    // size of element's byte array (in bytes)

Review comment:
       Good suggestions, thanks.  I'm fixing the comments and renaming `size` 
to `lengthInBytes`.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
##########
@@ -108,26 +109,65 @@ xmlEndComplex(XMLWriter *writer, const InfosetBase *base)
 static void
 fixNumberIfNeeded(const char *text)
 {
-    if (text[0] == 'N' && text[1] == 'A')
-    {
-        // xsd:float requires NaN to be capitalized correctly
-        char *modifyInPlace = (char *)text;
-        modifyInPlace[1] = 'a';
-    }
     // We aren't writing code to remove all textual differences
     // between runtime1/runtime2 because it would be better to make
     // Daffodil compare numbers numerically, not textually.  If we did
     // have to match runtime1 perfectly, we would have to:
     //  - Strip + from <f>E+<e> to get <f>E<e>
     //  - Add .0 to 1 to get 1.0
     //  - Change number of significant digits to match runtime1
+    if (text && text[0] == 'N' && text[1] == 'A')
+    {
+        // xsd:float requires NaN to be capitalized correctly
+        char *modifyInPlace = (char *)text;
+        modifyInPlace[1] = 'a';
+    }
+}
+
+// Convert a byte array to a string of hexadecimal characters (two
+// nibbles per byte).  Return NULL if no dynamic memory could be
+// allocated for string.  Reuse same dynamic memory next time if big
+// enough.  Caller is responsible for copying string before next call.
+
+static const char *
+binaryToHex(HexBinary hexBinary)
+{
+    static char * hexString = NULL;
+    static size_t capacity = 256;

Review comment:
       Currently the CLI is the only caller of xml_writer.c's functions and the 
CLI exits immediately, so I hadn't considered that behavior to be a problem.  
I'm modifying this function to take another parameter and call it in 
xmlEndDocument with that parameter to free the memory.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.h
##########
@@ -65,6 +65,14 @@ extern void parse_le_uint8(uint8_t *number, PState *pstate);
 
 extern void parse_fill_bytes(size_t end_position, PState *pstate);
 
+// Allocate or reallocate memory for hexBinary field
+
+extern void parse_alloc(HexBinary *hexBinary, size_t num_bytes, PState 
*pstate);

Review comment:
       I made two separate `malloc` calls because there are two different code 
paths, this function which gets called from generated parse methods that parse 
a prefixed length from binary data and then need to allocate the corresponding 
byte array, and the other code path in xml_reader.c which reads an XML 
document, processes a prefixed length hexBinary element, and needs to allocate 
a byte array large enough to hold the converted data in the newly initialized 
infoset.  The way an error gets propagated is different enough (stored into 
pstate->error in the first path, returned by return value in the second path) 
that I figured it was easier to have two `malloc` calls than to make 
xml_reader.c call `alloc_hexBinary` as well.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
##########
@@ -159,6 +199,9 @@ xmlNumberElem(XMLWriter *writer, const ERD *erd, const void 
*number)
         text = mxmlNewOpaquef(simple, "%.17lG", *(const double *)number);
         fixNumberIfNeeded(mxmlGetOpaque(text));
         break;
+    case PRIMITIVE_HEXBINARY:
+        text = mxmlNewOpaque(simple, binaryToHex(*(const HexBinary *)number));

Review comment:
       If the `malloc` fails and `binaryToHex` returns NULL, `mxmlNewOpaque` 
will return NULL too.  Code further below will detect the NULL and return an 
error, although with an unspecific error message (`CLI_XML_ELEMENT`).

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/unparsers.c
##########
@@ -133,10 +133,18 @@ unparse_fill_bytes(size_t end_position, const char 
fill_byte, UState *ustate)
 
     while (ustate->position < end_position)
     {
-        write_stream_update_position;
+        write_stream_update_position(buffer.c_val, 1);
     }
 }
 
+// Unparse 8-bit bytes from hexBinary field
+
+void
+unparse_hexBinary(HexBinary hexBinary, UState *ustate)

Review comment:
       Yes, I was striving for uniformity.  If you look at the prototypes in 
parsers.h and unparsers.h, you'll see that every parse method takes a value out 
pointer and every unparse method uses pass-by-value instead of taking a 
pointer.  I figured passing the struct by value would look better stylistically 
and wouldn't cause a lot of extra work (only three member fields versus one 
pointer to copy by value).

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_writer.c
##########
@@ -108,26 +109,65 @@ xmlEndComplex(XMLWriter *writer, const InfosetBase *base)
 static void
 fixNumberIfNeeded(const char *text)
 {
-    if (text[0] == 'N' && text[1] == 'A')
-    {
-        // xsd:float requires NaN to be capitalized correctly
-        char *modifyInPlace = (char *)text;
-        modifyInPlace[1] = 'a';
-    }
     // We aren't writing code to remove all textual differences
     // between runtime1/runtime2 because it would be better to make
     // Daffodil compare numbers numerically, not textually.  If we did
     // have to match runtime1 perfectly, we would have to:
     //  - Strip + from <f>E+<e> to get <f>E<e>
     //  - Add .0 to 1 to get 1.0
     //  - Change number of significant digits to match runtime1
+    if (text && text[0] == 'N' && text[1] == 'A')
+    {
+        // xsd:float requires NaN to be capitalized correctly
+        char *modifyInPlace = (char *)text;
+        modifyInPlace[1] = 'a';
+    }
+}
+
+// Convert a byte array to a string of hexadecimal characters (two
+// nibbles per byte).  Return NULL if no dynamic memory could be
+// allocated for string.  Reuse same dynamic memory next time if big
+// enough.  Caller is responsible for copying string before next call.
+
+static const char *
+binaryToHex(HexBinary hexBinary)
+{
+    static char * hexString = NULL;
+    static size_t capacity = 256;
+
+    // Make room for hexadecimal characters if necessary
+    size_t numNibbles = hexBinary.size * 2;
+    if (hexString == NULL || capacity < numNibbles)
+    {
+        free(hexString);
+        capacity = numNibbles > capacity ? numNibbles : capacity;
+        hexString = malloc(capacity + 1);
+    }
+
+    // Check whether dynamic memory allocation succeeded
+    if (hexString == NULL)
+    {
+        return NULL;
+    }
+
+    // Convert each binary byte to two hexadecimal characters
+    char *nibble = hexString;
+    for (size_t i = 0; i < hexBinary.size; i++)
+    {
+        static char hexDigit[] = "0123456789ABCDEF";
+        *(nibble++) = hexDigit[hexBinary.array[i] / 16]; // high nibble
+        *(nibble++) = hexDigit[hexBinary.array[i] % 16]; // low nibble
+    }
+    *(nibble++) = '\0';
+
+    return hexString;
 }
 
 // Write a boolean, 32-bit or 64-bit real number, or 8, 16, 32, or
 // 64-bit signed or unsigned integer as an XML element's value
 
 static const Error *
-xmlNumberElem(XMLWriter *writer, const ERD *erd, const void *number)
+xmlSimpleElem(XMLWriter *writer, const ERD *erd, const void *number)

Review comment:
       Agreed, I'm replacing `number` with `valueptr`.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
##########
@@ -326,7 +401,7 @@ xmlEndComplex(XMLReader *reader, const InfosetBase *base)
 // 64-bit signed or unsigned integer from XML data
 
 static const Error *
-xmlNumberElem(XMLReader *reader, const ERD *erd, void *number)
+xmlSimpleElem(XMLReader *reader, const ERD *erd, void *number)

Review comment:
       Agreed, I'm renaming `number` to `valueptr`.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
##########
@@ -341,7 +416,7 @@ xmlNumberElem(XMLReader *reader, const ERD *erd, void 
*number)
     reader->node = mxmlWalkNext(reader->node, reader->xml, MXML_DESCEND);
 
     // Check whether we are walking both XML data and infoset in lockstep
-    if (name_from_xml && name_from_erd)
+    if (name_from_xml && name_from_erd && number_from_xml)

Review comment:
       Agreed, l'm renaming `number_from_xml` to `text`.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
##########
@@ -217,6 +217,81 @@ strtounum(const char *numptr, uintmax_t maxval, const 
Error **errorptr)
     return value;
 }
 
+// Store an XML element's string of hexadecimal characters (two
+// nibbles per byte) into a byte array.  Allocate memory for byte
+// array if needed.  Return error if string does not fit into byte
+// array or does not contain valid hexadecimal characters.
+
+static const Error *
+hexToBinary(const char *hexstring, HexBinary *hexBinary)
+{
+    // Check whether string has even number of nibbles
+    size_t numNibbles = strlen(hexstring);
+    size_t numBytes = numNibbles / 2;
+    if (numNibbles == 0 || (numNibbles % 2) != 0)

Review comment:
       I think allowing this is worth the effort.  I'm modifying the logic to 
allow zero-length hex binary elements and adding test zero-length elements to 
verify no code or function dereferences `hexBinary->array` when its length is 
zero.

##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
##########
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.runtime2.generators
+
+import org.apache.daffodil.dpath.NodeInfo.PrimType
+import org.apache.daffodil.grammar.primitives.HexBinaryLengthPrefixed
+import org.apache.daffodil.grammar.primitives.HexBinarySpecifiedLength
+import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
+import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
+
+trait HexBinaryCodeGenerator extends BinaryAbstractCodeGenerator {
+
+  def hexBinaryLengthPrefixedGenerateCode(g: HexBinaryLengthPrefixed, cgState: 
CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+    val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
+    val intType = e.prefixedLengthElementDecl.optPrimType.get match {
+      case PrimType.Byte
+           | PrimType.Short
+           | PrimType.Int
+           | PrimType.Long
+           | PrimType.Integer => "int"
+      case PrimType.UnsignedByte
+           | PrimType.UnsignedShort
+           | PrimType.UnsignedInt
+           | PrimType.UnsignedLong
+           | PrimType.NonNegativeInteger => "uint"
+      case p => e.SDE("Prefixed length PrimType %s is not supported in C code 
generator.", p.toString)
+    }
+    val intLen = 
e.prefixedLengthElementDecl.elementLengthInBitsEv.constValue.get
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else 
""
+      val lenVar = s"_l_$fieldName$i"
+      val initStatement =
+        s"""    $field.array = NULL;
+           |    $field.size = 0;
+           |    $field.dynamic = true;""".stripMargin
+      val parseStatement =
+        s"""    $intType${intLen}_t $lenVar;
+           |    parse_${conv}_$intType$intLen(&$lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_alloc(&$field, $lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_${conv}_$intType$intLen($field.size, ustate);
+           |    if (ustate->error) return;
+           |    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")
+        val init2 = ""
+        val parse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    parse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", pstate);
+             |    if (pstate->error) return;""".stripMargin
+        val unparse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    unparse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", ustate);
+             |    if (ustate->error) return;""".stripMargin
+        cgState.addSimpleTypeStatements(init2, parse2, unparse2)
+      }

Review comment:
       This code should already be covered since ex_nums.dfdl.xsd puts fixed 
attributes on all of its types.  I'll check again.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.c
##########
@@ -149,10 +150,47 @@ parse_fill_bytes(size_t end_position, PState *pstate)
 
     while (pstate->position < end_position)
     {
-        read_stream_update_position;
+        read_stream_update_position(&buffer.c_val, 1);
     }
 }
 
+// Allocate or reallocate memory for hexBinary field
+
+void
+parse_alloc(HexBinary *hexBinary, size_t num_bytes, PState *pstate)

Review comment:
       Agreed, I'm renaming `parse_alloc` to `alloc_hexBinary` in case we ever 
need to allocate text strings later.

##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
##########
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.runtime2.generators
+
+import org.apache.daffodil.dpath.NodeInfo.PrimType
+import org.apache.daffodil.grammar.primitives.HexBinaryLengthPrefixed
+import org.apache.daffodil.grammar.primitives.HexBinarySpecifiedLength
+import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
+import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
+
+trait HexBinaryCodeGenerator extends BinaryAbstractCodeGenerator {
+
+  def hexBinaryLengthPrefixedGenerateCode(g: HexBinaryLengthPrefixed, cgState: 
CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+    val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
+    val intType = e.prefixedLengthElementDecl.optPrimType.get match {
+      case PrimType.Byte
+           | PrimType.Short
+           | PrimType.Int
+           | PrimType.Long
+           | PrimType.Integer => "int"
+      case PrimType.UnsignedByte
+           | PrimType.UnsignedShort
+           | PrimType.UnsignedInt
+           | PrimType.UnsignedLong
+           | PrimType.NonNegativeInteger => "uint"
+      case p => e.SDE("Prefixed length PrimType %s is not supported in C code 
generator.", p.toString)
+    }
+    val intLen = 
e.prefixedLengthElementDecl.elementLengthInBitsEv.constValue.get
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else 
""
+      val lenVar = s"_l_$fieldName$i"
+      val initStatement =
+        s"""    $field.array = NULL;
+           |    $field.size = 0;
+           |    $field.dynamic = true;""".stripMargin
+      val parseStatement =
+        s"""    $intType${intLen}_t $lenVar;
+           |    parse_${conv}_$intType$intLen(&$lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_alloc(&$field, $lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_${conv}_$intType$intLen($field.size, ustate);
+           |    if (ustate->error) return;
+           |    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")

Review comment:
       Agreed, not only does `.mkString("0x", ", 0x", "")` put the logic in one 
place, it also handles zero-length elements correctly.  Haven't decided if a 
test is worth it since `.grouped(2)` will return a single nibble as the last 
byte which is valid C syntax and some other code probably will report an error 
anyway.  

##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
##########
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.runtime2.generators
+
+import org.apache.daffodil.dpath.NodeInfo.PrimType
+import org.apache.daffodil.grammar.primitives.HexBinaryLengthPrefixed
+import org.apache.daffodil.grammar.primitives.HexBinarySpecifiedLength
+import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
+import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
+
+trait HexBinaryCodeGenerator extends BinaryAbstractCodeGenerator {
+
+  def hexBinaryLengthPrefixedGenerateCode(g: HexBinaryLengthPrefixed, cgState: 
CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+    val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
+    val intType = e.prefixedLengthElementDecl.optPrimType.get match {
+      case PrimType.Byte
+           | PrimType.Short
+           | PrimType.Int
+           | PrimType.Long
+           | PrimType.Integer => "int"
+      case PrimType.UnsignedByte
+           | PrimType.UnsignedShort
+           | PrimType.UnsignedInt
+           | PrimType.UnsignedLong
+           | PrimType.NonNegativeInteger => "uint"
+      case p => e.SDE("Prefixed length PrimType %s is not supported in C code 
generator.", p.toString)
+    }
+    val intLen = 
e.prefixedLengthElementDecl.elementLengthInBitsEv.constValue.get
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else 
""
+      val lenVar = s"_l_$fieldName$i"
+      val initStatement =
+        s"""    $field.array = NULL;
+           |    $field.size = 0;
+           |    $field.dynamic = true;""".stripMargin
+      val parseStatement =
+        s"""    $intType${intLen}_t $lenVar;
+           |    parse_${conv}_$intType$intLen(&$lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_alloc(&$field, $lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_${conv}_$intType$intLen($field.size, ustate);
+           |    if (ustate->error) return;
+           |    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")
+        val init2 = ""
+        val parse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    parse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", pstate);
+             |    if (pstate->error) return;""".stripMargin
+        val unparse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    unparse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", ustate);
+             |    if (ustate->error) return;""".stripMargin
+        cgState.addSimpleTypeStatements(init2, parse2, unparse2)
+      }
+    }
+    if (arraySize > 0)
+      for (i <- 0 until arraySize)
+        addStatements(s"[$i]")
+    else
+      addStatements("")
+  }
+
+  def hexBinarySpecifiedLengthGenerateCode(g: HexBinarySpecifiedLength, 
cgState: CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+    e.schemaDefinitionUnless(e.elementLengthInBitsEv.isConstant, "Runtime 
dfdl:length expressions not supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val fieldArray = s"instance->_a_$fieldName$deref"
+      val initStatement =
+        s"""    $field.array = $fieldArray;
+           |    $field.size = sizeof($fieldArray);
+           |    $field.dynamic = false;
+           |    memset($fieldArray, 0x77, sizeof($fieldArray));""".stripMargin
+      val parseStatement =
+        s"""    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val i = if (deref.length > 2) deref.substring(1, deref.length - 1) 
else ""
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")
+        val init2 = ""
+        val parse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    parse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", pstate);
+             |    if (pstate->error) return;""".stripMargin
+        val unparse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    unparse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", ustate);
+             |    if (ustate->error) return;""".stripMargin
+        cgState.addSimpleTypeStatements(init2, parse2, unparse2)
+      }
+    }
+    if (arraySize > 0)
+      for (i <- 0 until arraySize)
+        addStatements(s"[$i]")
+    else
+      addStatements("")
+  }

Review comment:
       Easier said than done.  I'll have to think of a way.

##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
##########
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.runtime2.generators
+
+import org.apache.daffodil.dpath.NodeInfo.PrimType
+import org.apache.daffodil.grammar.primitives.HexBinaryLengthPrefixed
+import org.apache.daffodil.grammar.primitives.HexBinarySpecifiedLength
+import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
+import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
+
+trait HexBinaryCodeGenerator extends BinaryAbstractCodeGenerator {
+
+  def hexBinaryLengthPrefixedGenerateCode(g: HexBinaryLengthPrefixed, cgState: 
CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+    val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
+    val intType = e.prefixedLengthElementDecl.optPrimType.get match {
+      case PrimType.Byte
+           | PrimType.Short
+           | PrimType.Int
+           | PrimType.Long
+           | PrimType.Integer => "int"
+      case PrimType.UnsignedByte
+           | PrimType.UnsignedShort
+           | PrimType.UnsignedInt
+           | PrimType.UnsignedLong
+           | PrimType.NonNegativeInteger => "uint"
+      case p => e.SDE("Prefixed length PrimType %s is not supported in C code 
generator.", p.toString)
+    }
+    val intLen = 
e.prefixedLengthElementDecl.elementLengthInBitsEv.constValue.get
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else 
""
+      val lenVar = s"_l_$fieldName$i"
+      val initStatement =
+        s"""    $field.array = NULL;
+           |    $field.size = 0;
+           |    $field.dynamic = true;""".stripMargin
+      val parseStatement =
+        s"""    $intType${intLen}_t $lenVar;
+           |    parse_${conv}_$intType$intLen(&$lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_alloc(&$field, $lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_${conv}_$intType$intLen($field.size, ustate);
+           |    if (ustate->error) return;
+           |    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")
+        val init2 = ""
+        val parse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    parse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", pstate);
+             |    if (pstate->error) return;""".stripMargin
+        val unparse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    unparse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", ustate);
+             |    if (ustate->error) return;""".stripMargin
+        cgState.addSimpleTypeStatements(init2, parse2, unparse2)
+      }
+    }
+    if (arraySize > 0)
+      for (i <- 0 until arraySize)
+        addStatements(s"[$i]")
+    else

Review comment:
       Agreed, I'm adding an array of prefixed length hex binary.

##########
File path: 
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
##########
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.daffodil.runtime2.generators
+
+import org.apache.daffodil.dpath.NodeInfo.PrimType
+import org.apache.daffodil.grammar.primitives.HexBinaryLengthPrefixed
+import org.apache.daffodil.grammar.primitives.HexBinarySpecifiedLength
+import org.apache.daffodil.schema.annotation.props.gen.BitOrder
+import org.apache.daffodil.schema.annotation.props.gen.ByteOrder
+import org.apache.daffodil.schema.annotation.props.gen.OccursCountKind
+
+trait HexBinaryCodeGenerator extends BinaryAbstractCodeGenerator {
+
+  def hexBinaryLengthPrefixedGenerateCode(g: HexBinaryLengthPrefixed, cgState: 
CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
+    val conv = if (e.prefixedLengthElementDecl.byteOrderEv.constValue == 
ByteOrder.BigEndian) "be" else "le"
+    val intType = e.prefixedLengthElementDecl.optPrimType.get match {
+      case PrimType.Byte
+           | PrimType.Short
+           | PrimType.Int
+           | PrimType.Long
+           | PrimType.Integer => "int"
+      case PrimType.UnsignedByte
+           | PrimType.UnsignedShort
+           | PrimType.UnsignedInt
+           | PrimType.UnsignedLong
+           | PrimType.NonNegativeInteger => "uint"
+      case p => e.SDE("Prefixed length PrimType %s is not supported in C code 
generator.", p.toString)
+    }
+    val intLen = 
e.prefixedLengthElementDecl.elementLengthInBitsEv.constValue.get
+
+    def addStatements(deref: String): Unit = {
+      val field = s"instance->$fieldName$deref"
+      val i = if (deref.length > 2) deref.substring(1, deref.length - 1) else 
""
+      val lenVar = s"_l_$fieldName$i"
+      val initStatement =
+        s"""    $field.array = NULL;
+           |    $field.size = 0;
+           |    $field.dynamic = true;""".stripMargin
+      val parseStatement =
+        s"""    $intType${intLen}_t $lenVar;
+           |    parse_${conv}_$intType$intLen(&$lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_alloc(&$field, $lenVar, pstate);
+           |    if (pstate->error) return;
+           |    parse_hexBinary(&$field, pstate);
+           |    if (pstate->error) return;""".stripMargin
+      val unparseStatement =
+        s"""    unparse_${conv}_$intType$intLen($field.size, ustate);
+           |    if (ustate->error) return;
+           |    unparse_hexBinary($field, ustate);
+           |    if (ustate->error) return;""".stripMargin
+      cgState.addSimpleTypeStatements(initStatement, parseStatement, 
unparseStatement)
+
+      if (e.hasFixedValue) {
+        val variable = s"${fieldName}_fixed$i"
+        val value = e.fixedValueAsString.grouped(2).mkString(", 0x")
+        val init2 = ""
+        val parse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    parse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", pstate);
+             |    if (pstate->error) return;""".stripMargin
+        val unparse2 =
+          s"""    uint8_t $variable[] = {0x$value};
+             |    unparse_validate_fixed(memcmp($field.array, $variable, 
sizeof($variable)) == 0, "$fieldName", ustate);
+             |    if (ustate->error) return;""".stripMargin
+        cgState.addSimpleTypeStatements(init2, parse2, unparse2)
+      }
+    }
+    if (arraySize > 0)
+      for (i <- 0 until arraySize)
+        addStatements(s"[$i]")
+    else
+      addStatements("")
+  }
+
+  def hexBinarySpecifiedLengthGenerateCode(g: HexBinarySpecifiedLength, 
cgState: CodeGeneratorState): Unit = {
+    // For the time being this is a very limited back end.
+    // So there are some restrictions to enforce.
+    val e = g.e
+    e.schemaDefinitionUnless(e.bitOrder eq BitOrder.MostSignificantBitFirst, 
"Only dfdl:bitOrder 'mostSignificantBitFirst' is supported.")
+    e.schemaDefinitionUnless(e.elementLengthInBitsEv.isConstant, "Runtime 
dfdl:length expressions not supported.")
+
+    val fieldName = e.namedQName.local
+    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0

Review comment:
       I already compute arraySize redundantly in 3 places.  If I use your 
suggested check, I'll have to refactor like you also pointed below.
   
   ```shell
   interran@GH3WPL13E:~/apache/daffodil-runtime2$ rg 'val arraySize = if 
\(e.occursCountKind'
   
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/HexBinaryCodeGenerator.scala
   36:    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
   106:    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
   
   
daffodil-runtime2/src/main/scala/org/apache/daffodil/runtime2/generators/BinaryAbstractCodeGenerator.scala
   39:    val arraySize = if (e.occursCountKind == OccursCountKind.Fixed) 
e.maxOccurs else 0
   interran@GH3WPL13E:~/apache/daffodil-runtime2$
   ```
   

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libcli/xml_reader.c
##########
@@ -217,6 +217,81 @@ strtounum(const char *numptr, uintmax_t maxval, const 
Error **errorptr)
     return value;
 }
 
+// Store an XML element's string of hexadecimal characters (two
+// nibbles per byte) into a byte array.  Allocate memory for byte
+// array if needed.  Return error if string does not fit into byte
+// array or does not contain valid hexadecimal characters.
+
+static const Error *
+hexToBinary(const char *hexstring, HexBinary *hexBinary)

Review comment:
       I checked and the Mini-XML library doesn't have any concept of schema 
types, so it doesn't have any hexBinary code.  GNOME's libxml2 library does 
have occurrences of hexBinary in its code 
(https://github.com/GNOME/libxml2/search?q=hexBinary), but it's not worth 
switching XML libraries for this small function.

##########
File path: 
daffodil-runtime2/src/test/resources/org/apache/daffodil/runtime2/ex_nums_unparse_runtime2.xml
##########
@@ -62,11 +67,14 @@
     <le_uint64>64</le_uint64>
     <le_uint8>8</le_uint8>
     <le_nonNegativeInteger8>8</le_nonNegativeInteger8>
+    <hexBinary7>1A2B3C4D5E6F70</hexBinary7>
+    <hexBinaryPrefixed>DEADBEEF</hexBinaryPrefixed>

Review comment:
       There are no tests with larger hex binary sizes, but the only code using 
256 as a size is in xml_writer.c's binaryToHex function and looks like this:
   
   ```c
       static char * text = NULL;
       static size_t capacity = 256;
       size_t numNibbles = hexBinary.size * 2;
       if (text == NULL || capacity < numNibbles)
       {
           free(text);
           capacity = numNibbles > capacity ? numNibbles : capacity;
           text = malloc(capacity + 1);
       }
   ```
   
   That code is already tested except for the `capacity < numNibbles` condition 
in the if statement and the `numNibbles` expression of the ternary operator `? 
:`.

##########
File path: 
daffodil-runtime2/src/main/resources/org/apache/daffodil/runtime2/c/libruntime/parsers.c
##########
@@ -149,10 +150,47 @@ parse_fill_bytes(size_t end_position, PState *pstate)
 
     while (pstate->position < end_position)
     {
-        read_stream_update_position;
+        read_stream_update_position(&buffer.c_val, 1);
     }
 }
 
+// Allocate or reallocate memory for hexBinary field
+
+void
+parse_alloc(HexBinary *hexBinary, size_t num_bytes, PState *pstate)
+{
+    // Reuse old array if possible
+    if (hexBinary->size >= num_bytes)
+    {
+        hexBinary->size = num_bytes;
+        return;
+    }

Review comment:
       Agreed, but rather than adding a `capacity` field, I'm changing the code 
to always call `free` and `malloc` together.  That code will be simpler and if 
we get into a situation where we need to reuse previously allocated arrays, we 
can take measurements, modify this code, and take measurements again.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to