2 * Copyright (c) 2017 Thomas Pornin <pornin@bolet.org>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30 internal class OutputRecord {
33 * Splitting modes are for debugging and tests. Note that the
34 * automatic 1/n-1 split for CBC cipher suites in TLS 1.0 is
35 * handled independently in RecordEncryptCBC.
39 internal const int MODE_NORMAL = 0;
41 /* Each record is split into two, of approximately the same size. */
42 internal const int MODE_SPLIT_HALF = 1;
44 /* Each record is preceded with an extra record of size 0. */
45 internal const int MODE_SPLIT_ZERO_BEFORE = 2;
47 /* Each record is split into two records (like SPLIT_HALF), and
48 an extra zero-length record is added between the two halves. */
49 internal const int MODE_SPLIT_ZERO_HALF = 3;
51 /* The first byte of each record is separated into its own record. */
52 internal const int MODE_SPLIT_ONE_START = 4;
54 /* The last byte of each record is separated into its own record. */
55 internal const int MODE_SPLIT_ONE_END = 5;
57 /* The record is split into records of length 1 byte each. */
58 internal const int MODE_SPLIT_MULTI_ONE = 6;
61 * Spliting modes are only applied on the specified record types
62 * (these are bit flags that can be combined).
64 internal const int MODE_MT_CCS = 1 << SSL.CHANGE_CIPHER_SPEC;
65 internal const int MODE_MT_ALERT = 1 << SSL.ALERT;
66 internal const int MODE_MT_HANDSHAKE = 1 << SSL.HANDSHAKE;
67 internal const int MODE_MT_APPLICATION_DATA = 1 << SSL.APPLICATION_DATA;
69 const int MODE_MASK = 0xFFFF;
73 int ptr, basePtr, maxPtr;
83 long thresholdZeroHandshake;
84 long thresholdZeroAppData;
87 internal OutputRecord(Stream sub)
90 buffer = new byte[16384 + 500];
93 splitMode = MODE_NORMAL;
97 thresholdZeroHandshake = 0;
98 thresholdZeroAppData = 0;
99 extra2 = new byte[500];
100 renc = new RecordEncryptPlain();
105 * If set, then all I/O errors while writing on the underlying
106 * stream will be converted to a generic SSLException with message
107 * "Unexpected transport closure". This helps test code that
108 * expects the peer to abort asynchronously, so the error may
109 * be detected during both reading or writing.
111 internal bool NormalizeIOError {
115 internal void SetVersion(int version)
117 if (version != this.version) {
118 if (ptr != basePtr) {
121 this.version = version;
126 internal int RecordType {
131 if (value != recordType) {
132 if (ptr != basePtr) {
140 internal void SetEncryption(RecordEncrypt renc)
142 if (ptr != basePtr) {
149 internal void SetSplitMode(int splitMode)
151 this.splitMode = splitMode;
152 if ((splitMode & MODE_MASK) != MODE_NORMAL && extra == null) {
153 extra = new byte[buffer.Length];
157 internal void SetThresholdZeroAppData(long t)
159 thresholdZeroAppData = t;
162 internal void SetThresholdZeroHandshake(long t)
164 thresholdZeroAppData = t;
170 int end = buffer.Length;
171 renc.GetMaxPlaintext(ref start, ref end);
177 internal void Flush()
179 if (ptr == basePtr) {
186 internal void SendZeroLength(int type)
199 int len = ptr - basePtr;
201 throw new Exception("Record version is not set");
203 int m = splitMode & MODE_MASK;
204 if (m == MODE_NORMAL || (splitMode & (1 << recordType)) == 0) {
205 EncryptAndWrite(off, len);
207 Array.Copy(buffer, off, extra, off, len);
209 case MODE_SPLIT_HALF:
210 case MODE_SPLIT_ZERO_HALF:
211 int hlen = (len >> 1);
213 EncryptAndWrite(off, hlen);
215 if (m == MODE_SPLIT_ZERO_HALF) {
216 EncryptAndWrite(off, 0);
218 Array.Copy(extra, off + hlen,
219 buffer, off, len - hlen);
222 EncryptAndWrite(off, hlen);
225 case MODE_SPLIT_ZERO_BEFORE:
226 EncryptAndWrite(off, 0);
227 Array.Copy(extra, off, buffer, off, len);
229 EncryptAndWrite(off, len);
232 case MODE_SPLIT_ONE_START:
234 EncryptAndWrite(off, 1);
237 Array.Copy(extra, off + 1,
238 buffer, off, len - 1);
239 EncryptAndWrite(off, len - 1);
242 case MODE_SPLIT_ONE_END:
244 EncryptAndWrite(off, len - 1);
247 buffer[off] = extra[off + len - 1];
248 EncryptAndWrite(off, 1);
251 case MODE_SPLIT_MULTI_ONE:
252 for (int i = 0; i < len; i ++) {
253 buffer[off] = extra[off + i];
254 EncryptAndWrite(off, 1);
258 throw new SSLException(string.Format(
259 "Bad record splitting value: {0}", m));
265 void EncryptAndWrite(int off, int len)
268 EncryptAndWriteInner(off, len);
270 if (NormalizeIOError) {
271 throw new SSLException(
272 "Unexpected transport closure");
279 void EncryptAndWriteInner(int off, int len)
281 if (recordType == SSL.HANDSHAKE) {
283 if (countHandshake == thresholdZeroAppData) {
285 int end = extra2.Length;
286 renc.GetMaxPlaintext(ref start, ref end);
289 renc.Encrypt(SSL.APPLICATION_DATA, version,
290 extra2, ref zoff, ref zlen);
291 sub.Write(extra2, zoff, zlen);
293 } else if (recordType == SSL.APPLICATION_DATA) {
295 if (countAppData == thresholdZeroHandshake) {
297 int end = extra2.Length;
298 renc.GetMaxPlaintext(ref start, ref end);
301 renc.Encrypt(SSL.HANDSHAKE, version,
302 extra2, ref zoff, ref zlen);
303 sub.Write(extra2, zoff, zlen);
307 renc.Encrypt(recordType, version, buffer, ref off, ref len);
308 sub.Write(buffer, off, len);
311 internal void Write(byte x)
319 internal void Write(byte[] data)
321 Write(data, 0, data.Length);
324 internal void Write(byte[] data, int off, int len)
327 int clen = Math.Min(len, maxPtr - ptr);
328 Array.Copy(data, off, buffer, ptr, clen);