View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.impl.io;
29  
30  import java.io.ByteArrayInputStream;
31  import java.io.ByteArrayOutputStream;
32  import java.io.InputStream;
33  import java.io.OutputStream;
34  import java.nio.charset.CharacterCodingException;
35  import java.nio.charset.CharsetDecoder;
36  import java.nio.charset.CharsetEncoder;
37  import java.nio.charset.CodingErrorAction;
38  import java.nio.charset.StandardCharsets;
39  
40  import org.apache.hc.core5.http.MessageConstraintException;
41  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
42  import org.apache.hc.core5.http.io.HttpTransportMetrics;
43  import org.apache.hc.core5.http.io.SessionInputBuffer;
44  import org.apache.hc.core5.http.io.SessionOutputBuffer;
45  import org.apache.hc.core5.util.CharArrayBuffer;
46  import org.junit.jupiter.api.Assertions;
47  import org.junit.jupiter.api.Test;
48  import org.mockito.ArgumentMatchers;
49  import org.mockito.Mockito;
50  
51  public class TestSessionInOutBuffers {
52  
53      @Test
54      public void testBasicBufferProperties() throws Exception {
55          final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
56          final ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] { 1, 2 , 3});
57          Assertions.assertEquals(16, inBuffer.capacity());
58          Assertions.assertEquals(16, inBuffer.available());
59          Assertions.assertEquals(0, inBuffer.length());
60          inBuffer.read(inputStream);
61          Assertions.assertEquals(14, inBuffer.available());
62          Assertions.assertEquals(2, inBuffer.length());
63  
64          final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
65          final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
66          Assertions.assertEquals(16, outbuffer.capacity());
67          Assertions.assertEquals(16, outbuffer.available());
68          Assertions.assertEquals(0, outbuffer.length());
69          outbuffer.write(new byte[] {1, 2, 3}, outputStream);
70          Assertions.assertEquals(13, outbuffer.available());
71          Assertions.assertEquals(3, outbuffer.length());
72      }
73  
74      @Test
75      public void testBasicReadWriteLine() throws Exception {
76  
77          final String[] teststrs = new String[5];
78          teststrs[0] = "Hello";
79          teststrs[1] = "This string should be much longer than the size of the output buffer " +
80                  "which is only 16 bytes for this test";
81          final StringBuilder buffer = new StringBuilder();
82          for (int i = 0; i < 15; i++) {
83              buffer.append("123456789 ");
84          }
85          buffer.append("and stuff like that");
86          teststrs[2] = buffer.toString();
87          teststrs[3] = "";
88          teststrs[4] = "And goodbye";
89  
90          final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
91          final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
92          final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
93          for (final String teststr : teststrs) {
94              chbuffer.clear();
95              chbuffer.append(teststr);
96              outbuffer.writeLine(chbuffer, outputStream);
97          }
98          //these write operations should have no effect
99          outbuffer.writeLine(null, outputStream);
100         outbuffer.flush(outputStream);
101 
102         HttpTransportMetrics tmetrics = outbuffer.getMetrics();
103         final long bytesWritten = tmetrics.getBytesTransferred();
104         long expected = 0;
105         for (final String teststr : teststrs) {
106             expected += (teststr.length() + 2/*CRLF*/);
107         }
108         Assertions.assertEquals(expected, bytesWritten);
109 
110         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
111         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
112 
113         for (final String teststr : teststrs) {
114             chbuffer.clear();
115             inBuffer.readLine(chbuffer, inputStream);
116             Assertions.assertEquals(teststr, chbuffer.toString());
117         }
118 
119         chbuffer.clear();
120         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
121         chbuffer.clear();
122         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
123         tmetrics = inBuffer.getMetrics();
124         final long bytesRead = tmetrics.getBytesTransferred();
125         Assertions.assertEquals(expected, bytesRead);
126     }
127 
128     @Test
129     public void testComplexReadWriteLine() throws Exception {
130         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
131         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
132         outbuffer.write(new byte[] {'a', '\n'}, outputStream);
133         outbuffer.write(new byte[] {'\r', '\n'}, outputStream);
134         outbuffer.write(new byte[] {'\r', '\r', '\n'}, outputStream);
135         outbuffer.write(new byte[] {'\n'},outputStream);
136         //these write operations should have no effect
137         outbuffer.write(null, outputStream);
138         outbuffer.write(null, 0, 12, outputStream);
139 
140         outbuffer.flush(outputStream);
141 
142         long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
143         Assertions.assertEquals(8, bytesWritten);
144 
145         final StringBuilder buffer = new StringBuilder();
146         for (int i = 0; i < 14; i++) {
147             buffer.append("a");
148         }
149         final String s1 = buffer.toString();
150         buffer.append("\r\n");
151         outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream);
152         outbuffer.flush(outputStream);
153         bytesWritten = outbuffer.getMetrics().getBytesTransferred();
154         Assertions.assertEquals(8 + 14 +2, bytesWritten);
155 
156         buffer.setLength(0);
157         for (int i = 0; i < 15; i++) {
158             buffer.append("a");
159         }
160         final String s2 = buffer.toString();
161         buffer.append("\r\n");
162         outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream);
163         outbuffer.flush(outputStream);
164         bytesWritten = outbuffer.getMetrics().getBytesTransferred();
165         Assertions.assertEquals(8 + 14 + 2 + 15 + 2 , bytesWritten);
166 
167         buffer.setLength(0);
168         for (int i = 0; i < 16; i++) {
169             buffer.append("a");
170         }
171         final String s3 = buffer.toString();
172         buffer.append("\r\n");
173         outbuffer.write(buffer.toString().getBytes(StandardCharsets.US_ASCII), outputStream);
174         outbuffer.flush(outputStream);
175         bytesWritten = outbuffer.getMetrics().getBytesTransferred();
176         Assertions.assertEquals(8 + 14 + 2 + 15 + 2 + 16 + 2, bytesWritten);
177 
178         outbuffer.write(new byte[] {'a'}, outputStream);
179         outbuffer.flush(outputStream);
180         bytesWritten = outbuffer.getMetrics().getBytesTransferred();
181         Assertions.assertEquals(8 + 14 + 2 + 15 + 2 + 16 + 2 + 1, bytesWritten);
182 
183         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
184         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
185 
186         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
187         chbuffer.clear();
188         inBuffer.readLine(chbuffer, inputStream);
189         Assertions.assertEquals("a", chbuffer.toString());
190         chbuffer.clear();
191         inBuffer.readLine(chbuffer, inputStream);
192         Assertions.assertEquals("", chbuffer.toString());
193         chbuffer.clear();
194         inBuffer.readLine(chbuffer, inputStream);
195         Assertions.assertEquals("\r", chbuffer.toString());
196         chbuffer.clear();
197         inBuffer.readLine(chbuffer, inputStream);
198         Assertions.assertEquals("", chbuffer.toString());
199         chbuffer.clear();
200         inBuffer.readLine(chbuffer, inputStream);
201         Assertions.assertEquals(s1, chbuffer.toString());
202         chbuffer.clear();
203         inBuffer.readLine(chbuffer, inputStream);
204         Assertions.assertEquals(s2, chbuffer.toString());
205         chbuffer.clear();
206         inBuffer.readLine(chbuffer, inputStream);
207         Assertions.assertEquals(s3, chbuffer.toString());
208         chbuffer.clear();
209         inBuffer.readLine(chbuffer, inputStream);
210         Assertions.assertEquals("a", chbuffer.toString());
211         chbuffer.clear();
212         inBuffer.readLine(chbuffer, inputStream);
213         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
214         chbuffer.clear();
215         inBuffer.readLine(chbuffer, inputStream);
216         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
217         final long bytesRead = inBuffer.getMetrics().getBytesTransferred();
218         Assertions.assertEquals(bytesWritten, bytesRead);
219     }
220 
221     @Test
222     public void testBasicReadWriteLineLargeBuffer() throws Exception {
223 
224         final String[] teststrs = new String[5];
225         teststrs[0] = "Hello";
226         teststrs[1] = "This string should be much longer than the size of the output buffer " +
227                 "which is only 16 bytes for this test";
228         final StringBuilder buffer = new StringBuilder();
229         for (int i = 0; i < 15; i++) {
230             buffer.append("123456789 ");
231         }
232         buffer.append("and stuff like that");
233         teststrs[2] = buffer.toString();
234         teststrs[3] = "";
235         teststrs[4] = "And goodbye";
236 
237         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
238         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
239         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
240         for (final String teststr : teststrs) {
241             chbuffer.clear();
242             chbuffer.append(teststr);
243             outbuffer.writeLine(chbuffer, outputStream);
244         }
245         //these write operations should have no effect
246         outbuffer.writeLine(null, outputStream);
247         outbuffer.flush(outputStream);
248 
249         final long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
250         long expected = 0;
251         for (final String teststr : teststrs) {
252             expected += (teststr.length() + 2/*CRLF*/);
253         }
254         Assertions.assertEquals(expected, bytesWritten);
255 
256         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(1024);
257         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
258 
259         for (final String teststr : teststrs) {
260             chbuffer.clear();
261             inBuffer.readLine(chbuffer, inputStream);
262             Assertions.assertEquals(teststr, chbuffer.toString());
263         }
264         chbuffer.clear();
265         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
266         chbuffer.clear();
267         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
268         final long bytesRead = inBuffer.getMetrics().getBytesTransferred();
269         Assertions.assertEquals(expected, bytesRead);
270     }
271 
272     @Test
273     public void testReadWriteBytes() throws Exception {
274         // make the buffer larger than that of outbuffer
275         final byte[] out = new byte[40];
276         for (int i = 0; i < out.length; i++) {
277             out[i] = (byte)('0' + i);
278         }
279         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
280         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
281         int off = 0;
282         int remaining = out.length;
283         while (remaining > 0) {
284             int chunk = 10;
285             if (chunk > remaining) {
286                 chunk = remaining;
287             }
288             outbuffer.write(out, off, chunk, outputStream);
289             off += chunk;
290             remaining -= chunk;
291         }
292         outbuffer.flush(outputStream);
293         final long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
294         Assertions.assertEquals(out.length, bytesWritten);
295 
296         final byte[] tmp = outputStream.toByteArray();
297         Assertions.assertEquals(out.length, tmp.length);
298         for (int i = 0; i < out.length; i++) {
299             Assertions.assertEquals(out[i], tmp[i]);
300         }
301 
302         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
303         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
304 
305         // these read operations will have no effect
306         Assertions.assertEquals(0, inBuffer.read(null, 0, 10, inputStream));
307         Assertions.assertEquals(0, inBuffer.read(null, inputStream));
308         long bytesRead = inBuffer.getMetrics().getBytesTransferred();
309         Assertions.assertEquals(0, bytesRead);
310 
311         final byte[] in = new byte[40];
312         off = 0;
313         remaining = in.length;
314         while (remaining > 0) {
315             int chunk = 10;
316             if (chunk > remaining) {
317                 chunk = remaining;
318             }
319             final int readLen = inBuffer.read(in, off, chunk, inputStream);
320             if (readLen == -1) {
321                 break;
322             }
323             off += readLen;
324             remaining -= readLen;
325         }
326         for (int i = 0; i < out.length; i++) {
327             Assertions.assertEquals(out[i], in[i]);
328         }
329         Assertions.assertEquals(-1, inBuffer.read(tmp, inputStream));
330         Assertions.assertEquals(-1, inBuffer.read(tmp, inputStream));
331         bytesRead = inBuffer.getMetrics().getBytesTransferred();
332         Assertions.assertEquals(out.length, bytesRead);
333     }
334 
335     @Test
336     public void testReadWriteByte() throws Exception {
337         // make the buffer larger than that of outbuffer
338         final byte[] out = new byte[40];
339         for (int i = 0; i < out.length; i++) {
340             out[i] = (byte)(120 + i);
341         }
342         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16);
343         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
344         for (final byte element : out) {
345             outbuffer.write(element, outputStream);
346         }
347         outbuffer.flush(outputStream);
348         final long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
349         Assertions.assertEquals(out.length, bytesWritten);
350 
351         final byte[] tmp = outputStream.toByteArray();
352         Assertions.assertEquals(out.length, tmp.length);
353         for (int i = 0; i < out.length; i++) {
354             Assertions.assertEquals(out[i], tmp[i]);
355         }
356 
357         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16);
358         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
359         final byte[] in = new byte[40];
360         for (int i = 0; i < in.length; i++) {
361             in[i] = (byte)inBuffer.read(inputStream);
362         }
363         for (int i = 0; i < out.length; i++) {
364             Assertions.assertEquals(out[i], in[i]);
365         }
366         Assertions.assertEquals(-1, inBuffer.read(inputStream));
367         Assertions.assertEquals(-1, inBuffer.read(inputStream));
368         final long bytesRead = inBuffer.getMetrics().getBytesTransferred();
369         Assertions.assertEquals(out.length, bytesRead);
370     }
371 
372     @Test
373     public void testWriteSmallFragmentBuffering() throws Exception {
374         final OutputStream outputStream = Mockito.mock(OutputStream.class);
375         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(new BasicHttpTransportMetrics(), 16, 16, null);
376         outbuffer.write(1, outputStream);
377         outbuffer.write(2, outputStream);
378         outbuffer.write(new byte[] {1, 2}, outputStream);
379         outbuffer.write(new byte[]{3, 4}, outputStream);
380         outbuffer.flush(outputStream);
381         Mockito.verify(outputStream, Mockito.times(1)).write(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt());
382         Mockito.verify(outputStream, Mockito.never()).write(ArgumentMatchers.anyInt());
383     }
384 
385     @Test
386     public void testWriteSmallFragmentNoBuffering() throws Exception {
387         final OutputStream outputStream = Mockito.mock(OutputStream.class);
388         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(new BasicHttpTransportMetrics(), 16, 0, null);
389         outbuffer.write(1, outputStream);
390         outbuffer.write(2, outputStream);
391         outbuffer.write(new byte[] {1, 2}, outputStream);
392         outbuffer.write(new byte[]{3, 4}, outputStream);
393         Mockito.verify(outputStream, Mockito.times(2)).write(ArgumentMatchers.any(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt());
394         Mockito.verify(outputStream, Mockito.times(2)).write(ArgumentMatchers.anyInt());
395     }
396 
397     @Test
398     public void testLineLimit() throws Exception {
399         final String s = "a very looooooooooooooooooooooooooooooooooooooooooong line\r\n";
400         final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII);
401         // no limit
402         final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(5);
403         final InputStream inputStream1 = new ByteArrayInputStream(tmp);
404         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
405         inBuffer1.readLine(chbuffer, inputStream1);
406         final long bytesRead = inBuffer1.getMetrics().getBytesTransferred();
407         Assertions.assertEquals(60, bytesRead);
408 
409         // 15 char limit
410         final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(5, 15);
411         final InputStream inputStream2 = new ByteArrayInputStream(tmp);
412         chbuffer.clear();
413         Assertions.assertThrows(MessageConstraintException.class, () ->
414                 inBuffer2.readLine(chbuffer, inputStream2));
415     }
416 
417     @Test
418     public void testLineLimit2() throws Exception {
419         final String s = "just a line\r\n";
420         final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII);
421         // no limit
422         final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(25);
423         final InputStream inputStream1 = new ByteArrayInputStream(tmp);
424         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
425         inBuffer1.readLine(chbuffer, inputStream1);
426         final long bytesRead = inBuffer1.getMetrics().getBytesTransferred();
427         Assertions.assertEquals(13, bytesRead);
428 
429         // 10 char limit
430         final SessionInputBuffer inBuffer2 = new SessionInputBufferImpl(25, 10);
431         final InputStream inputStream2 = new ByteArrayInputStream(tmp);
432         chbuffer.clear();
433         Assertions.assertThrows(MessageConstraintException.class, () ->
434                 inBuffer2.readLine(chbuffer, inputStream2));
435     }
436 
437     @Test //HTTPCORE-472
438     public void testLineLimit3() throws Exception {
439         final String s = "012345678\r\nblaaaaaaaaaaaaaaaaaah";
440         final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII);
441         final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128);
442         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
443         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
444         inBuffer1.readLine(chbuffer, inputStream);
445         Assertions.assertEquals("012345678", chbuffer.toString());
446     }
447 
448     @Test
449     public void testReadLineFringeCase1() throws Exception {
450         final String s = "abc\r\n";
451         final byte[] tmp = s.getBytes(StandardCharsets.US_ASCII);
452         final SessionInputBuffer inBuffer1 = new SessionInputBufferImpl(128);
453         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
454         Assertions.assertEquals('a', inBuffer1.read(inputStream));
455         Assertions.assertEquals('b', inBuffer1.read(inputStream));
456         Assertions.assertEquals('c', inBuffer1.read(inputStream));
457         Assertions.assertEquals('\r', inBuffer1.read(inputStream));
458         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
459         Assertions.assertEquals(0, inBuffer1.readLine(chbuffer, inputStream));
460     }
461 
462     static final int SWISS_GERMAN_HELLO [] = {
463         0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
464     };
465 
466     static final int RUSSIAN_HELLO [] = {
467         0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
468         0x432, 0x435, 0x442
469     };
470 
471     private static String constructString(final int [] unicodeChars) {
472         final StringBuilder buffer = new StringBuilder();
473         if (unicodeChars != null) {
474             for (final int unicodeChar : unicodeChars) {
475                 buffer.append((char)unicodeChar);
476             }
477         }
478         return buffer.toString();
479     }
480 
481     @Test
482     public void testMultibyteCodedReadWriteLine() throws Exception {
483         final String s1 = constructString(SWISS_GERMAN_HELLO);
484         final String s2 = constructString(RUSSIAN_HELLO);
485         final String s3 = "Like hello and stuff";
486 
487         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder());
488         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
489 
490         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
491         for (int i = 0; i < 10; i++) {
492             chbuffer.clear();
493             chbuffer.append(s1);
494             outbuffer.writeLine(chbuffer, outputStream);
495             chbuffer.clear();
496             chbuffer.append(s2);
497             outbuffer.writeLine(chbuffer, outputStream);
498             chbuffer.clear();
499             chbuffer.append(s3);
500             outbuffer.writeLine(chbuffer, outputStream);
501         }
502         outbuffer.flush(outputStream);
503         final long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
504         final long expected = ((s1.getBytes(StandardCharsets.UTF_8).length + 2)+
505                 (s2.getBytes(StandardCharsets.UTF_8).length + 2) +
506                 (s3.getBytes(StandardCharsets.UTF_8).length + 2)) * 10;
507         Assertions.assertEquals(expected, bytesWritten);
508 
509         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder());
510         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
511 
512         for (int i = 0; i < 10; i++) {
513             chbuffer.clear();
514             inBuffer.readLine(chbuffer, inputStream);
515             Assertions.assertEquals(s1, chbuffer.toString());
516             chbuffer.clear();
517             inBuffer.readLine(chbuffer, inputStream);
518             Assertions.assertEquals(s2, chbuffer.toString());
519             chbuffer.clear();
520             inBuffer.readLine(chbuffer, inputStream);
521             Assertions.assertEquals(s3, chbuffer.toString());
522         }
523         chbuffer.clear();
524         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
525         chbuffer.clear();
526         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
527         final long bytesRead = inBuffer.getMetrics().getBytesTransferred();
528         Assertions.assertEquals(expected, bytesRead);
529     }
530 
531     @Test
532     public void testMultibyteCodedReadWriteLongLine() throws Exception {
533         final String s1 = constructString(SWISS_GERMAN_HELLO);
534         final String s2 = constructString(RUSSIAN_HELLO);
535         final String s3 = "Like hello and stuff";
536         final StringBuilder buf = new StringBuilder();
537         for (int i = 0; i < 1024; i++) {
538             buf.append(s1).append(s2).append(s3);
539         }
540         final String s = buf.toString();
541 
542         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.UTF_8.newEncoder());
543         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
544 
545         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
546         chbuffer.append(s);
547         outbuffer.writeLine(chbuffer, outputStream);
548         outbuffer.flush(outputStream);
549 
550         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.UTF_8.newDecoder());
551         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
552 
553         chbuffer.clear();
554         inBuffer.readLine(chbuffer, inputStream);
555         Assertions.assertEquals(s, chbuffer.toString());
556     }
557 
558     @Test
559     public void testNonAsciiReadWriteLine() throws Exception {
560         final String s1 = constructString(SWISS_GERMAN_HELLO);
561 
562         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, StandardCharsets.ISO_8859_1.newEncoder());
563         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
564 
565         final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
566         for (int i = 0; i < 10; i++) {
567             chbuffer.clear();
568             chbuffer.append(s1);
569             outbuffer.writeLine(chbuffer, outputStream);
570         }
571         chbuffer.clear();
572         outbuffer.writeLine(chbuffer, outputStream);
573         outbuffer.flush(outputStream);
574         final long bytesWritten = outbuffer.getMetrics().getBytesTransferred();
575         final long expected = ((s1.getBytes(StandardCharsets.ISO_8859_1).length + 2)) * 10 + 2;
576         Assertions.assertEquals(expected, bytesWritten);
577 
578         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, StandardCharsets.ISO_8859_1.newDecoder());
579         final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
580 
581         for (int i = 0; i < 10; i++) {
582             chbuffer.clear();
583             final int len = inBuffer.readLine(chbuffer, inputStream);
584             Assertions.assertEquals(len, SWISS_GERMAN_HELLO.length);
585             Assertions.assertEquals(s1, chbuffer.toString());
586         }
587         chbuffer.clear();
588         Assertions.assertEquals(0, inBuffer.readLine(chbuffer, inputStream));
589         chbuffer.clear();
590         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
591         chbuffer.clear();
592         Assertions.assertEquals(-1, inBuffer.readLine(chbuffer, inputStream));
593         final long bytesRead = inBuffer.getMetrics().getBytesTransferred();
594         Assertions.assertEquals(expected, bytesRead);
595     }
596 
597     @Test
598     public void testUnmappableInputActionReport() throws Exception {
599         final String s = "This text contains a circumflex \u0302 !!!";
600         final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
601         encoder.onMalformedInput(CodingErrorAction.IGNORE);
602         encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
603         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder);
604         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
605         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
606         chbuffer.append(s);
607         Assertions.assertThrows(CharacterCodingException.class, () ->
608                 outbuffer.writeLine(chbuffer, outputStream));
609     }
610 
611     @Test
612     public void testUnmappableInputActionReplace() throws Exception {
613         final String s = "This text contains a circumflex \u0302 !!!";
614         final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
615         encoder.onMalformedInput(CodingErrorAction.IGNORE);
616         encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
617         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder);
618         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
619         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
620         chbuffer.append(s);
621         outbuffer.writeLine(chbuffer, outputStream);
622         outbuffer.flush(outputStream);
623         final String result = new String(outputStream.toByteArray(), StandardCharsets.ISO_8859_1);
624         Assertions.assertEquals("This text contains a circumflex ? !!!\r\n", result);
625     }
626 
627     @Test
628     public void testUnmappableInputActionIgnore() throws Exception {
629         final String s = "This text contains a circumflex \u0302 !!!";
630         final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
631         encoder.onMalformedInput(CodingErrorAction.IGNORE);
632         encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
633         final SessionOutputBuffer outbuffer = new SessionOutputBufferImpl(16, encoder);
634         final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
635         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
636         chbuffer.append(s);
637         outbuffer.writeLine(chbuffer, outputStream);
638         outbuffer.flush(outputStream);
639         final String result = new String(outputStream.toByteArray(), StandardCharsets.ISO_8859_1);
640         Assertions.assertEquals("This text contains a circumflex  !!!\r\n", result);
641     }
642 
643     @Test
644     public void testMalformedInputActionReport() throws Exception {
645         final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1);
646         final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
647         decoder.onMalformedInput(CodingErrorAction.REPORT);
648         decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
649         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder);
650         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
651         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
652         Assertions.assertThrows(CharacterCodingException.class, () ->
653                 inBuffer.readLine(chbuffer, inputStream));
654     }
655 
656     @Test
657     public void testMalformedInputActionReplace() throws Exception {
658         final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1);
659         final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
660         decoder.onMalformedInput(CodingErrorAction.REPLACE);
661         decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
662         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder);
663         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
664         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
665         inBuffer.readLine(chbuffer, inputStream);
666         Assertions.assertEquals("Gr\ufffdezi_z\ufffdm\ufffd", chbuffer.toString());
667     }
668 
669     @Test
670     public void testMalformedInputActionIgnore() throws Exception {
671         final byte[] tmp = constructString(SWISS_GERMAN_HELLO).getBytes(StandardCharsets.ISO_8859_1);
672         final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
673         decoder.onMalformedInput(CodingErrorAction.IGNORE);
674         decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
675         final SessionInputBuffer inBuffer = new SessionInputBufferImpl(16, decoder);
676         final ByteArrayInputStream inputStream = new ByteArrayInputStream(tmp);
677         final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
678         inBuffer.readLine(chbuffer, inputStream);
679         Assertions.assertEquals("Grezi_zm", chbuffer.toString());
680     }
681 
682 }
683