acvptool: implement pipelining.
This causes avcptool to send requests without blocking on responses. See
the diff in ACVP.md for details of how to use this feature.
Change-Id: I922b3bd2383cb7d22a5d12ead49d2fa733ee1b97
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/55345
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/util/fipstools/acvp/acvptool/acvp.go b/util/fipstools/acvp/acvptool/acvp.go
index fd009f5..e92424b 100644
--- a/util/fipstools/acvp/acvptool/acvp.go
+++ b/util/fipstools/acvp/acvptool/acvp.go
@@ -579,6 +579,12 @@
continue
}
}
+ if value, ok := algo["algorithm"]; ok {
+ algorithm, ok := value.(string)
+ if ok && algorithm == "acvptool" {
+ continue
+ }
+ }
nonTestAlgos = append(nonTestAlgos, algo)
}
diff --git a/util/fipstools/acvp/acvptool/subprocess/aead.go b/util/fipstools/acvp/acvptool/subprocess/aead.go
index f773546..ba0eee9 100644
--- a/util/fipstools/acvp/acvptool/subprocess/aead.go
+++ b/util/fipstools/acvp/acvptool/subprocess/aead.go
@@ -161,46 +161,48 @@
testResp := aeadTestResponse{ID: test.ID}
if encrypt {
- result, err := m.Transact(op, 1, uint32le(uint32(tagBytes)), key, input, nonce, aad)
- if err != nil {
- return nil, err
- }
+ m.TransactAsync(op, 1, [][]byte{uint32le(uint32(tagBytes)), key, input, nonce, aad}, func(result [][]byte) error {
+ if len(result[0]) < tagBytes {
+ return fmt.Errorf("ciphertext from subprocess for test case %d/%d is shorter than the tag (%d vs %d)", group.ID, test.ID, len(result[0]), tagBytes)
+ }
- if len(result[0]) < tagBytes {
- return nil, fmt.Errorf("ciphertext from subprocess for test case %d/%d is shorter than the tag (%d vs %d)", group.ID, test.ID, len(result[0]), tagBytes)
- }
-
- if a.tagMergedWithCiphertext {
- ciphertextHex := hex.EncodeToString(result[0])
- testResp.CiphertextHex = &ciphertextHex
- } else {
- ciphertext := result[0][:len(result[0])-tagBytes]
- ciphertextHex := hex.EncodeToString(ciphertext)
- testResp.CiphertextHex = &ciphertextHex
- tag := result[0][len(result[0])-tagBytes:]
- testResp.TagHex = hex.EncodeToString(tag)
- }
+ if a.tagMergedWithCiphertext {
+ ciphertextHex := hex.EncodeToString(result[0])
+ testResp.CiphertextHex = &ciphertextHex
+ } else {
+ ciphertext := result[0][:len(result[0])-tagBytes]
+ ciphertextHex := hex.EncodeToString(ciphertext)
+ testResp.CiphertextHex = &ciphertextHex
+ tag := result[0][len(result[0])-tagBytes:]
+ testResp.TagHex = hex.EncodeToString(tag)
+ }
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
} else {
- result, err := m.Transact(op, 2, uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad)
- if err != nil {
- return nil, err
- }
-
- if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
- return nil, fmt.Errorf("invalid AEAD status result from subprocess")
- }
- passed := result[0][0] == 1
- testResp.Passed = &passed
- if passed {
- plaintextHex := hex.EncodeToString(result[1])
- testResp.PlaintextHex = &plaintextHex
- }
+ m.TransactAsync(op, 2, [][]byte{uint32le(uint32(tagBytes)), key, append(input, tag...), nonce, aad}, func(result [][]byte) error {
+ if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
+ return fmt.Errorf("invalid AEAD status result from subprocess")
+ }
+ passed := result[0][0] == 1
+ testResp.Passed = &passed
+ if passed {
+ plaintextHex := hex.EncodeToString(result[1])
+ testResp.PlaintextHex = &plaintextHex
+ }
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
}
-
- response.Tests = append(response.Tests, testResp)
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/block.go b/util/fipstools/acvp/acvptool/subprocess/block.go
index 0387e09..2f05802 100644
--- a/util/fipstools/acvp/acvptool/subprocess/block.go
+++ b/util/fipstools/acvp/acvptool/subprocess/block.go
@@ -397,31 +397,35 @@
testResp := blockCipherTestResponse{ID: test.ID}
if !mct {
- var result [][]byte
- var err error
-
+ var args [][]byte
if b.hasIV {
- result, err = m.Transact(op, b.numResults, key, input, iv, uint32le(1))
+ args = [][]byte{key, input, iv, uint32le(1)}
} else {
- result, err = m.Transact(op, b.numResults, key, input, uint32le(1))
- }
- if err != nil {
- panic("block operation failed: " + err.Error())
+ args = [][]byte{key, input, uint32le(1)}
}
- if encrypt {
- testResp.CiphertextHex = hex.EncodeToString(result[0])
- } else {
- testResp.PlaintextHex = hex.EncodeToString(result[0])
- }
+ m.TransactAsync(op, b.numResults, args, func(result [][]byte) error {
+ if encrypt {
+ testResp.CiphertextHex = hex.EncodeToString(result[0])
+ } else {
+ testResp.PlaintextHex = hex.EncodeToString(result[0])
+ }
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
} else {
testResp.MCTResults = b.mctFunc(transact, encrypt, key, input, iv)
+ response.Tests = append(response.Tests, testResp)
}
-
- response.Tests = append(response.Tests, testResp)
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/drbg.go b/util/fipstools/acvp/acvptool/subprocess/drbg.go
index 6db8a64..b403f04 100644
--- a/util/fipstools/acvp/acvptool/subprocess/drbg.go
+++ b/util/fipstools/acvp/acvptool/subprocess/drbg.go
@@ -116,7 +116,8 @@
var outLenBytes [4]byte
binary.LittleEndian.PutUint32(outLenBytes[:], uint32(outLen))
- var result [][]byte
+ var cmd string
+ var args [][]byte
if group.PredictionResistance {
var a1, a2, a3, a4 []byte
if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
@@ -124,7 +125,8 @@
{"generate", group.AdditionalDataBits, &a3, group.EntropyBits, &a4}}); err != nil {
return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err = m.Transact(d.algo+"-pr/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+ cmd = d.algo + "-pr/" + group.Mode
+ args = [][]byte{outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce}
} else if group.Reseed {
var a1, a2, a3, a4 []byte
if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
@@ -133,7 +135,8 @@
{"generate", group.AdditionalDataBits, &a4, 0, nil}}); err != nil {
return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err = m.Transact(d.algo+"-reseed/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce)
+ cmd = d.algo + "-reseed/" + group.Mode
+ args = [][]byte{outLenBytes[:], ent, perso, a1, a2, a3, a4, nonce}
} else {
var a1, a2 []byte
if err := extractOtherInputs(test.Other, []drbgOtherInputExpectations{
@@ -141,25 +144,31 @@
{"generate", group.AdditionalDataBits, &a2, 0, nil}}); err != nil {
return nil, fmt.Errorf("failed to parse other inputs from test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err = m.Transact(d.algo+"/"+group.Mode, 1, outLenBytes[:], ent, perso, a1, a2, nonce)
+ cmd = d.algo + "/" + group.Mode
+ args = [][]byte{outLenBytes[:], ent, perso, a1, a2, nonce}
}
- if err != nil {
- return nil, fmt.Errorf("DRBG operation failed: %s", err)
- }
+ m.TransactAsync(cmd, 1, args, func(result [][]byte) error {
+ if l := uint64(len(result[0])); l != outLen {
+ return fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLen)
+ }
- if l := uint64(len(result[0])); l != outLen {
- return nil, fmt.Errorf("wrong length DRBG result: %d bytes but wanted %d", l, outLen)
- }
-
- // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-responses
- response.Tests = append(response.Tests, drbgTestResponse{
- ID: test.ID,
- OutHex: hex.EncodeToString(result[0]),
+ // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#name-responses
+ response.Tests = append(response.Tests, drbgTestResponse{
+ ID: test.ID,
+ OutHex: hex.EncodeToString(result[0]),
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
index 619323c..16d3a83 100644
--- a/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
+++ b/util/fipstools/acvp/acvptool/subprocess/ecdsa.go
@@ -94,19 +94,20 @@
for _, test := range group.Tests {
var testResp ecdsaTestResponse
+ testResp.ID = test.ID
switch parsed.Mode {
case "keyGen":
if group.SecretGenerationMode != "testing candidates" {
return nil, fmt.Errorf("invalid secret generation mode in test group %d: %q", group.ID, group.SecretGenerationMode)
}
- result, err := m.Transact(e.algo+"/"+"keyGen", 3, []byte(group.Curve))
- if err != nil {
- return nil, fmt.Errorf("key generation failed for test case %d/%d: %s", group.ID, test.ID, err)
- }
- testResp.DHex = hex.EncodeToString(result[0])
- testResp.QxHex = hex.EncodeToString(result[1])
- testResp.QyHex = hex.EncodeToString(result[2])
+ m.TransactAsync(e.algo+"/"+"keyGen", 3, [][]byte{[]byte(group.Curve)}, func(result [][]byte) error {
+ testResp.DHex = hex.EncodeToString(result[0])
+ testResp.QxHex = hex.EncodeToString(result[1])
+ testResp.QyHex = hex.EncodeToString(result[2])
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
case "keyVer":
qx, err := hex.DecodeString(test.QxHex)
@@ -117,21 +118,21 @@
if err != nil {
return nil, fmt.Errorf("failed to decode qy in test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err := m.Transact(e.algo+"/"+"keyVer", 1, []byte(group.Curve), qx, qy)
- if err != nil {
- return nil, fmt.Errorf("key verification failed for test case %d/%d: %s", group.ID, test.ID, err)
- }
- // result[0] should be a single byte: zero if false, one if true
- switch {
- case bytes.Equal(result[0], []byte{00}):
- f := false
- testResp.Passed = &f
- case bytes.Equal(result[0], []byte{01}):
- t := true
- testResp.Passed = &t
- default:
- return nil, fmt.Errorf("key verification returned unexpected result: %q", result[0])
- }
+ m.TransactAsync(e.algo+"/"+"keyVer", 1, [][]byte{[]byte(group.Curve), qx, qy}, func(result [][]byte) error {
+ // result[0] should be a single byte: zero if false, one if true
+ switch {
+ case bytes.Equal(result[0], []byte{00}):
+ f := false
+ testResp.Passed = &f
+ case bytes.Equal(result[0], []byte{01}):
+ t := true
+ testResp.Passed = &t
+ default:
+ return fmt.Errorf("key verification returned unexpected result: %q", result[0])
+ }
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
case "sigGen":
p := e.primitives[group.HashAlgo]
@@ -163,12 +164,12 @@
}
op += "/componentTest"
}
- result, err := m.Transact(op, 2, []byte(group.Curve), sigGenPrivateKey, []byte(group.HashAlgo), msg)
- if err != nil {
- return nil, fmt.Errorf("signature generation failed for test case %d/%d: %s", group.ID, test.ID, err)
- }
- testResp.RHex = hex.EncodeToString(result[0])
- testResp.SHex = hex.EncodeToString(result[1])
+ m.TransactAsync(op, 2, [][]byte{[]byte(group.Curve), sigGenPrivateKey, []byte(group.HashAlgo), msg}, func(result [][]byte) error {
+ testResp.RHex = hex.EncodeToString(result[0])
+ testResp.SHex = hex.EncodeToString(result[1])
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
case "sigVer":
p := e.primitives[group.HashAlgo]
@@ -197,31 +198,34 @@
if err != nil {
return nil, fmt.Errorf("failed to decode S in test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err := m.Transact(e.algo+"/"+"sigVer", 1, []byte(group.Curve), []byte(group.HashAlgo), msg, qx, qy, r, s)
- if err != nil {
- return nil, fmt.Errorf("signature verification failed for test case %d/%d: %s", group.ID, test.ID, err)
- }
- // result[0] should be a single byte: zero if false, one if true
- switch {
- case bytes.Equal(result[0], []byte{00}):
- f := false
- testResp.Passed = &f
- case bytes.Equal(result[0], []byte{01}):
- t := true
- testResp.Passed = &t
- default:
- return nil, fmt.Errorf("signature verification returned unexpected result: %q", result[0])
- }
+ m.TransactAsync(e.algo+"/"+"sigVer", 1, [][]byte{[]byte(group.Curve), []byte(group.HashAlgo), msg, qx, qy, r, s}, func(result [][]byte) error {
+ // result[0] should be a single byte: zero if false, one if true
+ switch {
+ case bytes.Equal(result[0], []byte{00}):
+ f := false
+ testResp.Passed = &f
+ case bytes.Equal(result[0], []byte{01}):
+ t := true
+ testResp.Passed = &t
+ default:
+ return fmt.Errorf("signature verification returned unexpected result: %q", result[0])
+ }
+ response.Tests = append(response.Tests, testResp)
+ return nil
+ })
default:
return nil, fmt.Errorf("invalid mode %q in ECDSA vector set", parsed.Mode)
}
-
- testResp.ID = test.ID
- response.Tests = append(response.Tests, testResp)
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/hash.go b/util/fipstools/acvp/acvptool/subprocess/hash.go
index 33cea04..1f34d1a 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hash.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hash.go
@@ -89,14 +89,12 @@
// http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-sha-00.html#rfc.section.3
switch group.Type {
case "AFT":
- result, err := m.Transact(h.algo, 1, msg)
- if err != nil {
- panic(h.algo + " hash operation failed: " + err.Error())
- }
-
- response.Tests = append(response.Tests, hashTestResponse{
- ID: test.ID,
- DigestHex: hex.EncodeToString(result[0]),
+ m.TransactAsync(h.algo, 1, [][]byte{msg}, func(result [][]byte) error {
+ response.Tests = append(response.Tests, hashTestResponse{
+ ID: test.ID,
+ DigestHex: hex.EncodeToString(result[0]),
+ })
+ return nil
})
case "MCT":
@@ -124,7 +122,13 @@
}
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/hkdf.go b/util/fipstools/acvp/acvptool/subprocess/hkdf.go
index 555646a..3a6ba04 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hkdf.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hkdf.go
@@ -169,24 +169,29 @@
info = append(info, uData...)
info = append(info, vData...)
- resp, err := m.Transact("HKDF/"+hashName, 1, key, salt, info, uint32le(outBytes))
- if err != nil {
- return nil, fmt.Errorf("HKDF operation failed: %s", err)
- }
- if len(resp[0]) != int(outBytes) {
- return nil, fmt.Errorf("HKDF operation resulted in %d bytes but wanted %d", len(resp[0]), outBytes)
- }
+ m.TransactAsync("HKDF/"+hashName, 1, [][]byte{key, salt, info, uint32le(outBytes)}, func(result [][]byte) error {
+ if len(result[0]) != int(outBytes) {
+ return fmt.Errorf("HKDF operation resulted in %d bytes but wanted %d", len(result[0]), outBytes)
+ }
+ if isValidationTest {
+ passed := bytes.Equal(expected, result[0])
+ testResp.Passed = &passed
+ } else {
+ testResp.KeyOut = hex.EncodeToString(result[0])
+ }
- if isValidationTest {
- passed := bytes.Equal(expected, resp[0])
- testResp.Passed = &passed
- } else {
- testResp.KeyOut = hex.EncodeToString(resp[0])
- }
-
- groupResp.Tests = append(groupResp.Tests, testResp)
+ groupResp.Tests = append(groupResp.Tests, testResp)
+ return nil
+ })
}
- respGroups = append(respGroups, groupResp)
+
+ m.Barrier(func() {
+ respGroups = append(respGroups, groupResp)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return respGroups, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/hmac.go b/util/fipstools/acvp/acvptool/subprocess/hmac.go
index 1859886..8fc7695 100644
--- a/util/fipstools/acvp/acvptool/subprocess/hmac.go
+++ b/util/fipstools/acvp/acvptool/subprocess/hmac.go
@@ -93,6 +93,10 @@
if group.MACBits > h.mdLen*8 {
return nil, fmt.Errorf("test group %d specifies MAC length should be %d, but maximum possible length is %d", group.ID, group.MACBits, h.mdLen*8)
}
+ if group.MACBits%8 != 0 {
+ return nil, fmt.Errorf("fractional-byte HMAC output length requested: %d", group.MACBits)
+ }
+ outBytes := group.MACBits / 8
for _, test := range group.Tests {
if len(test.MsgHex)*4 != group.MsgBits {
@@ -111,14 +115,27 @@
return nil, fmt.Errorf("failed to decode key in test case %d/%d: %s", group.ID, test.ID, err)
}
- // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors
- response.Tests = append(response.Tests, hmacTestResponse{
- ID: test.ID,
- MACHex: hex.EncodeToString(h.hmac(msg, key, group.MACBits, m)),
+ m.TransactAsync(h.algo, 1, [][]byte{msg, key}, func(result [][]byte) error {
+ if l := len(result[0]); l < outBytes {
+ return fmt.Errorf("HMAC result too short: %d bytes but wanted %d", l, outBytes)
+ }
+
+ // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors
+ response.Tests = append(response.Tests, hmacTestResponse{
+ ID: test.ID,
+ MACHex: hex.EncodeToString(result[0][:outBytes]),
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/kas.go b/util/fipstools/acvp/acvptool/subprocess/kas.go
index 9625334..cbc99ed 100644
--- a/util/fipstools/acvp/acvptool/subprocess/kas.go
+++ b/util/fipstools/acvp/acvptool/subprocess/kas.go
@@ -155,40 +155,42 @@
return nil, err
}
- result, err := m.Transact(method, 3, peerX, peerY, privateKey)
- if err != nil {
- return nil, err
- }
-
- ok := bytes.Equal(result[2], expectedOutput)
- response.Tests = append(response.Tests, kasTestResponse{
- ID: test.ID,
- Passed: &ok,
+ m.TransactAsync(method, 3, [][]byte{peerX, peerY, privateKey}, func(result [][]byte) error {
+ ok := bytes.Equal(result[2], expectedOutput)
+ response.Tests = append(response.Tests, kasTestResponse{
+ ID: test.ID,
+ Passed: &ok,
+ })
+ return nil
})
} else {
- result, err := m.Transact(method, 3, peerX, peerY, nil)
- if err != nil {
- return nil, err
- }
+ m.TransactAsync(method, 3, [][]byte{peerX, peerY, nil}, func(result [][]byte) error {
+ testResponse := kasTestResponse{
+ ID: test.ID,
+ ResultHex: hex.EncodeToString(result[2]),
+ }
- testResponse := kasTestResponse{
- ID: test.ID,
- ResultHex: hex.EncodeToString(result[2]),
- }
+ if useStaticNamedFields {
+ testResponse.StaticXHex = hex.EncodeToString(result[0])
+ testResponse.StaticYHex = hex.EncodeToString(result[1])
+ } else {
+ testResponse.EphemeralXHex = hex.EncodeToString(result[0])
+ testResponse.EphemeralYHex = hex.EncodeToString(result[1])
+ }
- if useStaticNamedFields {
- testResponse.StaticXHex = hex.EncodeToString(result[0])
- testResponse.StaticYHex = hex.EncodeToString(result[1])
- } else {
- testResponse.EphemeralXHex = hex.EncodeToString(result[0])
- testResponse.EphemeralYHex = hex.EncodeToString(result[1])
- }
-
- response.Tests = append(response.Tests, testResponse)
+ response.Tests = append(response.Tests, testResponse)
+ return nil
+ })
}
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/kasdh.go b/util/fipstools/acvp/acvptool/subprocess/kasdh.go
index a9de2e3..f262b82 100644
--- a/util/fipstools/acvp/acvptool/subprocess/kasdh.go
+++ b/util/fipstools/acvp/acvptool/subprocess/kasdh.go
@@ -139,31 +139,33 @@
return nil, err
}
- result, err := m.Transact(method, 2, p, q, g, peerPublic, privateKey, publicKey)
- if err != nil {
- return nil, err
- }
-
- ok := bytes.Equal(result[1], expectedOutput)
- response.Tests = append(response.Tests, kasDHTestResponse{
- ID: test.ID,
- Passed: &ok,
+ m.TransactAsync(method, 2, [][]byte{p, q, g, peerPublic, privateKey, publicKey}, func(result [][]byte) error {
+ ok := bytes.Equal(result[1], expectedOutput)
+ response.Tests = append(response.Tests, kasDHTestResponse{
+ ID: test.ID,
+ Passed: &ok,
+ })
+ return nil
})
} else {
- result, err := m.Transact(method, 2, p, q, g, peerPublic, nil, nil)
- if err != nil {
- return nil, err
- }
-
- response.Tests = append(response.Tests, kasDHTestResponse{
- ID: test.ID,
- LocalPublicHex: hex.EncodeToString(result[0]),
- ResultHex: hex.EncodeToString(result[1]),
+ m.TransactAsync(method, 2, [][]byte{p, q, g, peerPublic, nil, nil}, func(result [][]byte) error {
+ response.Tests = append(response.Tests, kasDHTestResponse{
+ ID: test.ID,
+ LocalPublicHex: hex.EncodeToString(result[0]),
+ ResultHex: hex.EncodeToString(result[1]),
+ })
+ return nil
})
}
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/kdf.go b/util/fipstools/acvp/acvptool/subprocess/kdf.go
index e61d26b..e27fcaa 100644
--- a/util/fipstools/acvp/acvptool/subprocess/kdf.go
+++ b/util/fipstools/acvp/acvptool/subprocess/kdf.go
@@ -106,26 +106,30 @@
}
// Make the call to the crypto module.
- resp, err := m.Transact("KDF-counter", 3, outputBytes, []byte(group.MACMode), []byte(group.CounterLocation), key, counterBits)
- if err != nil {
- return nil, fmt.Errorf("wrapper KDF operation failed: %s", err)
- }
+ m.TransactAsync("KDF-counter", 3, [][]byte{outputBytes, []byte(group.MACMode), []byte(group.CounterLocation), key, counterBits}, func(result [][]byte) error {
+ testResp.ID = test.ID
+ if test.Deferred {
+ testResp.KeyIn = hex.EncodeToString(result[0])
+ }
+ testResp.FixedData = hex.EncodeToString(result[1])
+ testResp.KeyOut = hex.EncodeToString(result[2])
- // Parse results.
- testResp.ID = test.ID
- if test.Deferred {
- testResp.KeyIn = hex.EncodeToString(resp[0])
- }
- testResp.FixedData = hex.EncodeToString(resp[1])
- testResp.KeyOut = hex.EncodeToString(resp[2])
+ if !test.Deferred && !bytes.Equal(result[0], key) {
+ return fmt.Errorf("wrapper returned a different key for non-deferred KDF operation")
+ }
- if !test.Deferred && !bytes.Equal(resp[0], key) {
- return nil, fmt.Errorf("wrapper returned a different key for non-deferred KDF operation")
- }
-
- groupResp.Tests = append(groupResp.Tests, testResp)
+ groupResp.Tests = append(groupResp.Tests, testResp)
+ return nil
+ })
}
- respGroups = append(respGroups, groupResp)
+
+ m.Barrier(func() {
+ respGroups = append(respGroups, groupResp)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return respGroups, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
index a722ac9..e43ab5d 100644
--- a/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
+++ b/util/fipstools/acvp/acvptool/subprocess/keyedMac.go
@@ -122,17 +122,17 @@
}
if generate {
- result, err := m.Transact(k.algo, 1, outputBytes, key, msg)
- if err != nil {
- return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err)
- }
+ expectedNumBytes := int(group.MACBits / 8)
- calculatedMAC := result[0]
- if len(calculatedMAC) != int(group.MACBits/8) {
- return nil, fmt.Errorf("%s operation returned incorrect length value", k.algo)
- }
+ m.TransactAsync(k.algo, 1, [][]byte{outputBytes, key, msg}, func(result [][]byte) error {
+ calculatedMAC := result[0]
+ if len(calculatedMAC) != expectedNumBytes {
+ return fmt.Errorf("%s operation returned incorrect length value", k.algo)
+ }
- respTest.MACHex = hex.EncodeToString(calculatedMAC)
+ respTest.MACHex = hex.EncodeToString(calculatedMAC)
+ return nil
+ })
} else {
expectedMAC, err := hex.DecodeString(test.MACHex)
if err != nil {
@@ -142,23 +142,29 @@
return nil, fmt.Errorf("MACHex in test case %d/%d is %x, but should be %d bits", group.ID, test.ID, expectedMAC, group.MACBits)
}
- result, err := m.Transact(k.algo+"/verify", 1, key, msg, expectedMAC)
- if err != nil {
- return nil, fmt.Errorf("wrapper %s operation failed: %s", k.algo, err)
- }
+ m.TransactAsync(k.algo+"/verify", 1, [][]byte{key, msg, expectedMAC}, func(result [][]byte) error {
+ if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
+ return fmt.Errorf("wrapper %s returned invalid success flag: %x", k.algo, result[0])
+ }
- if len(result[0]) != 1 || (result[0][0]&0xfe) != 0 {
- return nil, fmt.Errorf("wrapper %s returned invalid success flag: %x", k.algo, result[0])
- }
-
- ok := result[0][0] == 1
- respTest.Passed = &ok
+ ok := result[0][0] == 1
+ respTest.Passed = &ok
+ return nil
+ })
}
- respGroup.Tests = append(respGroup.Tests, respTest)
+ m.Barrier(func() {
+ respGroup.Tests = append(respGroup.Tests, respTest)
+ })
}
- respGroups = append(respGroups, respGroup)
+ m.Barrier(func() {
+ respGroups = append(respGroups, respGroup)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return respGroups, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/rsa.go b/util/fipstools/acvp/acvptool/subprocess/rsa.go
index d29dd23..d975026 100644
--- a/util/fipstools/acvp/acvptool/subprocess/rsa.go
+++ b/util/fipstools/acvp/acvptool/subprocess/rsa.go
@@ -137,22 +137,26 @@
}
for _, test := range group.Tests {
- results, err := m.Transact("RSA/keyGen", 5, uint32le(group.ModulusBits))
- if err != nil {
- return nil, err
- }
-
- response.Tests = append(response.Tests, rsaKeyGenTestResponse{
- ID: test.ID,
- E: hex.EncodeToString(results[0]),
- P: hex.EncodeToString(results[1]),
- Q: hex.EncodeToString(results[2]),
- N: hex.EncodeToString(results[3]),
- D: hex.EncodeToString(results[4]),
+ m.TransactAsync("RSA/keyGen", 5, [][]byte{uint32le(group.ModulusBits)}, func(result [][]byte) error {
+ response.Tests = append(response.Tests, rsaKeyGenTestResponse{
+ ID: test.ID,
+ E: hex.EncodeToString(result[0]),
+ P: hex.EncodeToString(result[1]),
+ Q: hex.EncodeToString(result[2]),
+ N: hex.EncodeToString(result[3]),
+ D: hex.EncodeToString(result[4]),
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
@@ -185,25 +189,29 @@
return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err)
}
- results, err := m.Transact(operation, 3, uint32le(group.ModulusBits), msg)
- if err != nil {
- return nil, err
- }
+ m.TransactAsync(operation, 3, [][]byte{uint32le(group.ModulusBits), msg}, func(result [][]byte) error {
+ if len(response.N) == 0 {
+ response.N = hex.EncodeToString(result[0])
+ response.E = hex.EncodeToString(result[1])
+ } else if response.N != hex.EncodeToString(result[0]) {
+ return fmt.Errorf("module wrapper returned different RSA keys for the same SigGen configuration")
+ }
- if len(response.N) == 0 {
- response.N = hex.EncodeToString(results[0])
- response.E = hex.EncodeToString(results[1])
- } else if response.N != hex.EncodeToString(results[0]) {
- return nil, fmt.Errorf("module wrapper returned different RSA keys for the same SigGen configuration")
- }
-
- response.Tests = append(response.Tests, rsaSigGenTestResponse{
- ID: test.ID,
- Sig: hex.EncodeToString(results[2]),
+ response.Tests = append(response.Tests, rsaSigGenTestResponse{
+ ID: test.ID,
+ Sig: hex.EncodeToString(result[2]),
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
@@ -249,18 +257,22 @@
return nil, fmt.Errorf("test case %d/%d contains invalid hex: %s", group.ID, test.ID, err)
}
- results, err := m.Transact(operation, 1, n, e, msg, sig)
- if err != nil {
- return nil, err
- }
-
- response.Tests = append(response.Tests, rsaSigVerTestResponse{
- ID: test.ID,
- Passed: len(results[0]) == 1 && results[0][0] == 1,
+ m.TransactAsync(operation, 1, [][]byte{n, e, msg, sig}, func(result [][]byte) error {
+ response.Tests = append(response.Tests, rsaSigVerTestResponse{
+ ID: test.ID,
+ Passed: len(result[0]) == 1 && result[0][0] == 1,
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/subprocess.go b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
index ee1cb87..9167b47 100644
--- a/util/fipstools/acvp/acvptool/subprocess/subprocess.go
+++ b/util/fipstools/acvp/acvptool/subprocess/subprocess.go
@@ -30,6 +30,9 @@
// that don't call a server.
type Transactable interface {
Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error)
+ TransactAsync(cmd string, expectedResults int, args [][]byte, callback func([][]byte) error)
+ Barrier(callback func()) error
+ Flush() error
}
// Subprocess is a "middle" layer that interacts with a FIPS module via running
@@ -39,6 +42,26 @@
stdin io.WriteCloser
stdout io.ReadCloser
primitives map[string]primitive
+ // supportsFlush is true if the modulewrapper indicated that it wants to receive flush commands.
+ supportsFlush bool
+ // pendingReads is a queue of expected responses. `readerRoutine` reads each response and calls the callback in the matching pendingRead.
+ pendingReads chan pendingRead
+ // readerFinished is a channel that is closed if `readerRoutine` has finished (e.g. because of a read error).
+ readerFinished chan struct{}
+ // readerError is set iff readerFinished is closed. If non-nil then it is the read error that caused `readerRoutine` to finished.
+ readerError error
+}
+
+// pendingRead represents an expected response from the modulewrapper.
+type pendingRead struct {
+ // barrierCallback is called as soon as this pendingRead is the next in the queue, before any read from the modulewrapper.
+ barrierCallback func()
+
+ // callback is called with the result from the modulewrapper. If this is nil then no read is performed.
+ callback func(result [][]byte) error
+ // cmd is the command that requested this read for logging purposes.
+ cmd string
+ expectedNumResults int
}
// New returns a new Subprocess middle layer that runs the given binary.
@@ -61,13 +84,18 @@
return NewWithIO(cmd, stdin, stdout), nil
}
+// maxPending is the maximum number of requests that can be in the pipeline.
+const maxPending = 4096
+
// NewWithIO returns a new Subprocess middle layer with the given ReadCloser and
// WriteCloser. The returned Subprocess will call Wait on the Cmd when closed.
func NewWithIO(cmd *exec.Cmd, in io.WriteCloser, out io.ReadCloser) *Subprocess {
m := &Subprocess{
- cmd: cmd,
- stdin: in,
- stdout: out,
+ cmd: cmd,
+ stdin: in,
+ stdout: out,
+ pendingReads: make(chan pendingRead, maxPending),
+ readerFinished: make(chan struct{}),
}
m.primitives = map[string]primitive{
@@ -116,6 +144,7 @@
}
m.primitives["ECDSA"] = &ecdsa{"ECDSA", map[string]bool{"P-224": true, "P-256": true, "P-384": true, "P-521": true}, m.primitives}
+ go m.readerRoutine()
return m
}
@@ -124,10 +153,57 @@
m.stdout.Close()
m.stdin.Close()
m.cmd.Wait()
+ <-m.readerFinished
}
-// Transact performs a single request--response pair with the subprocess.
-func (m *Subprocess) Transact(cmd string, expectedResults int, args ...[]byte) ([][]byte, error) {
+func (m *Subprocess) flush() error {
+ if !m.supportsFlush {
+ return nil
+ }
+
+ const cmd = "flush"
+ buf := make([]byte, 8, 8+len(cmd))
+ binary.LittleEndian.PutUint32(buf, 1)
+ binary.LittleEndian.PutUint32(buf[4:], uint32(len(cmd)))
+ buf = append(buf, []byte(cmd)...)
+
+ if _, err := m.stdin.Write(buf); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (m *Subprocess) enqueueRead(pending pendingRead) error {
+ select {
+ case <-m.readerFinished:
+ return m.readerError
+ default:
+ }
+
+ select {
+ case m.pendingReads <- pending:
+ break
+ default:
+ // `pendingReads` is full. Ensure that the modulewrapper will process
+ // some outstanding requests to free up space in the queue.
+ if err := m.flush(); err != nil {
+ return err
+ }
+ m.pendingReads <- pending
+ }
+
+ return nil
+}
+
+// TransactAsync performs a single request--response pair with the subprocess.
+// The callback will run at some future point, in a separate goroutine. All
+// callbacks will, however, be run in the order that TransactAsync was called.
+// Use Flush to wait for all outstanding callbacks.
+func (m *Subprocess) TransactAsync(cmd string, expectedNumResults int, args [][]byte, callback func(result [][]byte) error) {
+ if err := m.enqueueRead(pendingRead{nil, callback, cmd, expectedNumResults}); err != nil {
+ panic(err)
+ }
+
argLength := len(cmd)
for _, arg := range args {
argLength += len(arg)
@@ -145,17 +221,90 @@
}
if _, err := m.stdin.Write(buf); err != nil {
+ panic(err)
+ }
+}
+
+// Flush tells the subprocess to complete all outstanding requests and waits
+// for all outstanding TransactAsync callbacks to complete.
+func (m *Subprocess) Flush() error {
+ if m.supportsFlush {
+ m.flush()
+ }
+
+ done := make(chan struct{})
+ if err := m.enqueueRead(pendingRead{barrierCallback: func() {
+ close(done)
+ }}); err != nil {
+ return err
+ }
+
+ <-done
+ return nil
+}
+
+// Barrier runs callback after all outstanding TransactAsync callbacks have
+// been run.
+func (m *Subprocess) Barrier(callback func()) error {
+ return m.enqueueRead(pendingRead{barrierCallback: callback})
+}
+
+func (m *Subprocess) Transact(cmd string, expectedNumResults int, args ...[]byte) ([][]byte, error) {
+ done := make(chan struct{})
+ var result [][]byte
+ m.TransactAsync(cmd, expectedNumResults, args, func(r [][]byte) error {
+ result = r
+ close(done)
+ return nil
+ })
+
+ if err := m.flush(); err != nil {
return nil, err
}
- buf = buf[:4]
+ select {
+ case <-done:
+ return result, nil
+ case <-m.readerFinished:
+ return nil, m.readerError
+ }
+}
+
+func (m *Subprocess) readerRoutine() {
+ defer close(m.readerFinished)
+
+ for pendingRead := range m.pendingReads {
+ if pendingRead.barrierCallback != nil {
+ pendingRead.barrierCallback()
+ }
+
+ if pendingRead.callback == nil {
+ continue
+ }
+
+ result, err := m.readResult(pendingRead.cmd, pendingRead.expectedNumResults)
+ if err != nil {
+ m.readerError = err
+ return
+ }
+
+ if err := pendingRead.callback(result); err != nil {
+ m.readerError = err
+ return
+ }
+ }
+}
+
+func (m *Subprocess) readResult(cmd string, expectedNumResults int) ([][]byte, error) {
+ buf := make([]byte, 4)
+
if _, err := io.ReadFull(m.stdout, buf); err != nil {
return nil, err
}
numResults := binary.LittleEndian.Uint32(buf)
- if int(numResults) != expectedResults {
- return nil, fmt.Errorf("expected %d results from %q but got %d", expectedResults, cmd, numResults)
+ if int(numResults) != expectedNumResults {
+ return nil, fmt.Errorf("expected %d results from %q but got %d", expectedNumResults, cmd, numResults)
}
buf = make([]byte, 4*numResults)
@@ -197,16 +346,25 @@
return nil, err
}
var config []struct {
- Algorithm string `json:"algorithm"`
+ Algorithm string `json:"algorithm"`
+ Features []string `json:"features"`
}
if err := json.Unmarshal(results[0], &config); err != nil {
return nil, errors.New("failed to parse config response from wrapper: " + err.Error())
}
for _, algo := range config {
- if _, ok := m.primitives[algo.Algorithm]; !ok {
+ if algo.Algorithm == "acvptool" {
+ for _, feature := range algo.Features {
+ switch feature {
+ case "batch":
+ m.supportsFlush = true
+ }
+ }
+ } else if _, ok := m.primitives[algo.Algorithm]; !ok {
return nil, fmt.Errorf("wrapper config advertises support for unknown algorithm %q", algo.Algorithm)
}
}
+
return results[0], nil
}
diff --git a/util/fipstools/acvp/acvptool/subprocess/tls13.go b/util/fipstools/acvp/acvptool/subprocess/tls13.go
index 6e14942..af2aae8 100644
--- a/util/fipstools/acvp/acvptool/subprocess/tls13.go
+++ b/util/fipstools/acvp/acvptool/subprocess/tls13.go
@@ -36,7 +36,7 @@
}
type tls13Test struct {
- ID uint64 `json:"tcId"`
+ ID uint64 `json:"tcId"`
// Although ACVP refers to these as client and server randoms, these
// fields are misnamed and really contain portions of the handshake
// transcript. Concatenated in order, they give the transcript up to
diff --git a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
index 5b28e3f..3a0d7ce 100644
--- a/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
+++ b/util/fipstools/acvp/acvptool/subprocess/tlskdf.go
@@ -118,19 +118,23 @@
binary.LittleEndian.PutUint32(outLenBytes[:], uint32(group.KeyBlockBits/8))
// TLS 1.0, 1.1, and 1.2 use a different order for the client and server
// randoms when computing the key block.
- result2, err := m.Transact(method, 1, outLenBytes[:], result[0], []byte(keyBlockLabel), serverRandom, clientRandom)
- if err != nil {
- return nil, err
- }
-
- response.Tests = append(response.Tests, tlsKDFTestResponse{
- ID: test.ID,
- MasterSecretHex: hex.EncodeToString(result[0]),
- KeyBlockHex: hex.EncodeToString(result2[0]),
+ m.TransactAsync(method, 1, [][]byte{outLenBytes[:], result[0], []byte(keyBlockLabel), serverRandom, clientRandom}, func(result2 [][]byte) error {
+ response.Tests = append(response.Tests, tlsKDFTestResponse{
+ ID: test.ID,
+ MasterSecretHex: hex.EncodeToString(result[0]),
+ KeyBlockHex: hex.EncodeToString(result2[0]),
+ })
+ return nil
})
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/subprocess/xts.go b/util/fipstools/acvp/acvptool/subprocess/xts.go
index 50eb6fd..e813409 100644
--- a/util/fipstools/acvp/acvptool/subprocess/xts.go
+++ b/util/fipstools/acvp/acvptool/subprocess/xts.go
@@ -126,22 +126,26 @@
return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
}
- result, err := m.Transact(funcName, 1, key, msg, tweak[:])
- if err != nil {
- return nil, fmt.Errorf("submodule failed on test case %d/%d: %s", group.ID, test.ID, err)
- }
+ m.TransactAsync(funcName, 1, [][]byte{key, msg, tweak[:]}, func(result [][]byte) error {
+ testResponse := xtsTestResponse{ID: test.ID}
+ if decrypt {
+ testResponse.PlaintextHex = hex.EncodeToString(result[0])
+ } else {
+ testResponse.CiphertextHex = hex.EncodeToString(result[0])
+ }
- testResponse := xtsTestResponse{ID: test.ID}
- if decrypt {
- testResponse.PlaintextHex = hex.EncodeToString(result[0])
- } else {
- testResponse.CiphertextHex = hex.EncodeToString(result[0])
- }
-
- response.Tests = append(response.Tests, testResponse)
+ response.Tests = append(response.Tests, testResponse)
+ return nil
+ })
}
- ret = append(ret, response)
+ m.Barrier(func() {
+ ret = append(ret, response)
+ })
+ }
+
+ if err := m.Flush(); err != nil {
+ return nil, err
}
return ret, nil
diff --git a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
index 8c4c97a..4cf5069 100644
--- a/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
+++ b/util/fipstools/acvp/acvptool/testmodulewrapper/testmodulewrapper.go
@@ -35,7 +35,13 @@
"golang.org/x/crypto/xts"
)
+var (
+ output io.Writer
+ outputBuffer *bytes.Buffer
+)
+
var handlers = map[string]func([][]byte) error{
+ "flush": flush,
"getConfig": getConfig,
"KDF-counter": kdfCounter,
"AES-XTS/encrypt": xtsEncrypt,
@@ -47,13 +53,29 @@
"AES-CBC-CS3/decrypt": ctsDecrypt,
}
+func flush(args [][]byte) error {
+ if outputBuffer == nil {
+ return nil
+ }
+
+ if _, err := os.Stdout.Write(outputBuffer.Bytes()); err != nil {
+ return err
+ }
+ outputBuffer = new(bytes.Buffer)
+ output = outputBuffer
+ return nil
+}
+
func getConfig(args [][]byte) error {
if len(args) != 0 {
return fmt.Errorf("getConfig received %d args", len(args))
}
- return reply([]byte(`[
+ if err := reply([]byte(`[
{
+ "algorithm": "acvptool",
+ "features": ["batch"]
+ }, {
"algorithm": "KDF",
"revision": "1.0",
"capabilities": [{
@@ -146,7 +168,11 @@
256
]
}
-]`))
+]`)); err != nil {
+ return err
+ }
+
+ return flush(nil)
}
func kdfCounter(args [][]byte) error {
@@ -207,12 +233,12 @@
}
lengthsLength := (1 + len(responses)) * 4
- if n, err := os.Stdout.Write(lengths[:lengthsLength]); n != lengthsLength || err != nil {
+ if n, err := output.Write(lengths[:lengthsLength]); n != lengthsLength || err != nil {
return fmt.Errorf("write failed: %s", err)
}
for _, response := range responses {
- if n, err := os.Stdout.Write(response); n != len(response) || err != nil {
+ if n, err := output.Write(response); n != len(response) || err != nil {
return fmt.Errorf("write failed: %s", err)
}
}
@@ -460,6 +486,10 @@
}
func do() error {
+ // In order to exercise pipelining, all output is buffered until a "flush".
+ outputBuffer = new(bytes.Buffer)
+ output = outputBuffer
+
var nums [4 * (1 + maxArgs)]byte
var argLengths [maxArgs]uint32
var args [maxArgs][]byte