acvp: detect header element in JSON.

Sometimes JSON vector files contain a header element that must be
duplicated into the output and sometimes they don't. Auto-detect this by
looking for a “url” field in the first element.

Change-Id: I76046adb8ea64fe5ac9bae9d6583546504723918
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45524
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/util/fipstools/acvp/acvptool/acvp.go b/util/fipstools/acvp/acvptool/acvp.go
index 87c2f87..f497532 100644
--- a/util/fipstools/acvp/acvptool/acvp.go
+++ b/util/fipstools/acvp/acvptool/acvp.go
@@ -178,6 +178,20 @@
 	return s
 }
 
+// looksLikeHeaderElement returns true iff element looks like it's a header,
+// not a test. Some ACVP files contain a header as the first element that
+// should be duplicated into the response, and some don't. If the element
+// contains a "url" field then we guess that it's a header.
+func looksLikeHeaderElement(element json.RawMessage) bool {
+	var headerFields struct {
+		URL string `json:"url"`
+	}
+	if err := json.Unmarshal(element, &headerFields); err != nil {
+		return false
+	}
+	return len(headerFields.URL) > 0
+}
+
 // processFile reads a file containing vector sets, at least in the format
 // preferred by our lab, and writes the results to stdout.
 func processFile(filename string, supportedAlgos []map[string]interface{}, middle Middle) error {
@@ -191,11 +205,18 @@
 		return err
 	}
 
-	// There must be at least a header and one vector set in the file.
-	if len(elements) < 2 {
-		return fmt.Errorf("only %d elements in JSON array", len(elements))
+	// There must be at least one element in the file.
+	if len(elements) < 1 {
+		return errors.New("JSON input is empty")
 	}
-	header := elements[0]
+
+	var header json.RawMessage
+	if looksLikeHeaderElement(elements[0]) {
+		header, elements = elements[0], elements[1:]
+		if len(elements) == 0 {
+			return errors.New("JSON input is empty")
+		}
+	}
 
 	// Build a map of which algorithms our Middle supports.
 	algos := make(map[string]struct{})
@@ -213,13 +234,17 @@
 
 	var result bytes.Buffer
 	result.WriteString("[")
-	headerBytes, err := json.MarshalIndent(header, "", "    ")
-	if err != nil {
-		return err
-	}
-	result.Write(headerBytes)
 
-	for i, element := range elements[1:] {
+	if header != nil {
+		headerBytes, err := json.MarshalIndent(header, "", "    ")
+		if err != nil {
+			return err
+		}
+		result.Write(headerBytes)
+		result.WriteString(",")
+	}
+
+	for i, element := range elements {
 		var commonFields struct {
 			Algo string `json:"algorithm"`
 			ID   uint64 `json:"vsId"`
@@ -247,7 +272,9 @@
 			return err
 		}
 
-		result.WriteString(",")
+		if i != 0 {
+			result.WriteString(",")
+		}
 		result.Write(replyBytes)
 	}