@@ -59,33 +59,86 @@ describe('decrypt', () => {
59
59
fixtures . base64CiphertextAlgAes256GcmIv12Tag16HkdfSha384EcdsaP384 ( ) ,
60
60
'base64'
61
61
)
62
- const i = ciphertext . values ( )
63
- const ciphertextStream = from (
64
- (
65
- _ : number ,
66
- next : ( err : Error | null , chunk : Uint8Array | null ) => void
67
- ) => {
68
- /* Pushing 1 byte at time is the most annoying thing.
69
- * This is done intentionally to hit _every_ boundary condition.
70
- */
71
- const { value, done } = i . next ( )
72
- if ( done ) return next ( null , null )
73
- next ( null , new Uint8Array ( [ value ] ) )
74
- }
75
- )
76
62
77
- const { plaintext : test , messageHeader } = await decrypt (
78
- fixtures . decryptKeyring ( ) ,
79
- ciphertextStream
63
+ /* Pushing 1 byte at time is annoying.
64
+ * But there can be state that can get reset
65
+ * from a 1 byte push when the buffering
66
+ * is internal to the stream.
67
+ * So while 1 byte will hit the beginning
68
+ * boundary conditions,
69
+ * and the exiting boundary conditions,
70
+ * but it will never span any boundary conditions.
71
+ * The prime example of this is
72
+ * the `VerifyStream`.
73
+ * It has 4 byte boundary in the `_transform` function.
74
+ * The body header, the body, the auth tag, and signature.
75
+ * By sending 1 byte
76
+ * as the code transitions from the body header
77
+ * to the body
78
+ * the code handling the exiting
79
+ * of the body header has no way to interact
80
+ * with the body,
81
+ * because the 1 byte of data
82
+ * completely isolates these code branches.
83
+ * So I push at different sizes to hit
84
+ * as many boundary conditions as possible.
85
+ * Why 20 as a max?
86
+ * Permuting every possible combination
87
+ * would take too long
88
+ * and so anything short of _everything_
89
+ * is a compromise.
90
+ * 20 seems large enough.
91
+ * I did not do an initial offset
92
+ * because that just moves me closer
93
+ * to permuting every option.
94
+ * An alternative would be a variable chunk size.
95
+ * Doing this randomly is a bad idea.
96
+ * Tests that only fail _sometimes_
97
+ * are bad tests.
98
+ * It is too easy to try again,
99
+ * and when everything passes just move on.
100
+ * And again, doing every permutation
101
+ * is too expensive at this time.
102
+ */
103
+ const results = await Promise . all (
104
+ [
105
+ { size : 1 } ,
106
+ { size : 2 } ,
107
+ { size : 3 } ,
108
+ { size : 4 } ,
109
+ { size : 5 } ,
110
+ { size : 6 } ,
111
+ { size : 7 } ,
112
+ { size : 8 } ,
113
+ { size : 9 } ,
114
+ { size : 10 } ,
115
+ { size : 11 } ,
116
+ { size : 12 } ,
117
+ { size : 13 } ,
118
+ { size : 14 } ,
119
+ { size : 15 } ,
120
+ { size : 16 } ,
121
+ { size : 17 } ,
122
+ { size : 18 } ,
123
+ { size : 19 } ,
124
+ { size : 20 } ,
125
+ ] . map ( async ( op ) =>
126
+ decrypt (
127
+ fixtures . decryptKeyring ( ) ,
128
+ chunkCipherTextStream ( ciphertext , op )
129
+ )
130
+ )
80
131
)
81
132
82
- expect ( messageHeader . suiteId ) . to . equal (
83
- AlgorithmSuiteIdentifier . ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384
84
- )
85
- expect ( messageHeader . encryptionContext ) . to . deep . equal (
86
- fixtures . encryptionContext ( )
87
- )
88
- expect ( test . toString ( 'base64' ) ) . to . equal ( fixtures . base64Plaintext ( ) )
133
+ results . map ( ( { plaintext : test , messageHeader } ) => {
134
+ expect ( messageHeader . suiteId ) . to . equal (
135
+ AlgorithmSuiteIdentifier . ALG_AES256_GCM_IV12_TAG16_HKDF_SHA384_ECDSA_P384
136
+ )
137
+ expect ( messageHeader . encryptionContext ) . to . deep . equal (
138
+ fixtures . encryptionContext ( )
139
+ )
140
+ expect ( test . toString ( 'base64' ) ) . to . equal ( fixtures . base64Plaintext ( ) )
141
+ } )
89
142
} )
90
143
91
144
it ( 'Precondition: The sequence number is required to monotonically increase, starting from 1.' , async ( ) => {
@@ -125,3 +178,32 @@ describe('decrypt', () => {
125
178
) . to . rejectedWith ( Error , 'maxBodySize exceeded.' )
126
179
} )
127
180
} )
181
+
182
+ function chunkCipherTextStream ( ciphertext : Buffer , { size } : { size : number } ) {
183
+ const i = ciphertext . values ( )
184
+
185
+ return from (
186
+ (
187
+ _ : number ,
188
+ next : ( err : Error | null , chunk : Uint8Array | null ) => void
189
+ ) => {
190
+ const { value, done } = eat ( i , size )
191
+ if ( done ) return next ( null , null )
192
+ next ( null , new Uint8Array ( value ) )
193
+ }
194
+ )
195
+
196
+ function eat ( i : IterableIterator < number > , size : number ) {
197
+ return Array ( size )
198
+ . fill ( 1 )
199
+ . reduce < IteratorResult < number [ ] , any > > (
200
+ ( { value } ) => {
201
+ const { value : item , done } = i . next ( )
202
+ if ( done && value . length ) return { value, done : false }
203
+ if ( ! done ) value . push ( item )
204
+ return { value, done }
205
+ } ,
206
+ { value : < number [ ] > [ ] , done : false }
207
+ )
208
+ }
209
+ }
0 commit comments