1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * @author Vladimir N. Molotkov 20 * @version $Revision$ 21 */ 22 23 package org.apache.harmony.security.tests.java.security; 24 25 import java.io.ByteArrayOutputStream; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.security.DigestOutputStream; 29 import java.security.MessageDigest; 30 import java.security.NoSuchAlgorithmException; 31 import java.util.Arrays; 32 import junit.framework.TestCase; 33 import org.apache.harmony.security.tests.support.MDGoldenData; 34 import org.apache.harmony.security.tests.support.MyMessageDigest1; 35 import tests.support.Support_OutputStream; 36 37 /** 38 * Tests for fields and methods of class <code>DigestInputStream</code> 39 * 40 */ 41 public class DigestOutputStreamTest extends TestCase { 42 43 /** 44 * Message digest algorithm name used during testing 45 */ 46 private static final String algorithmName[] = { 47 "SHA-1", 48 "SHA", 49 "SHA1", 50 "SHA-256", 51 "SHA-384", 52 "SHA-512", 53 "MD5", 54 }; 55 /** 56 * Chunk size for read(byte, off, len) tests 57 */ 58 private static final int CHUNK_SIZE = 32; 59 /** 60 * Test message for digest computations 61 */ 62 private static final byte[] myMessage = MDGoldenData.getMessage(); 63 /** 64 * The length of test message 65 */ 66 private static final int MY_MESSAGE_LEN = myMessage.length; 67 68 // 69 // Tests 70 // 71 72 /** 73 * java.security.DigestOutputStream#DigestOutputStream(java.io.OutputStream, 74 * java.security.MessageDigest) 75 */ 76 public void test_CtorLjava_io_OutputStreamLjava_security_MessageDigest() { 77 78 // non-null parameters 79 MessageDigest md = new MyMessageDigest1(); 80 MyOutputStream out = new MyOutputStream(); 81 82 MyDigestOutputStream dos = new MyDigestOutputStream(out, md); 83 assertSame(out, dos.myOutputStream()); 84 assertSame(md, dos.myMessageDigest()); 85 86 // null parameters 87 dos = new MyDigestOutputStream(null, null); 88 assertNull(dos.myOutputStream()); 89 assertNull(dos.myMessageDigest()); 90 91 dos = new MyDigestOutputStream(null, md); 92 assertNull(dos.myOutputStream()); 93 assertNotNull(dos.myMessageDigest()); 94 95 dos = new MyDigestOutputStream(out, null); 96 assertNotNull(dos.myOutputStream()); 97 assertNull(dos.myMessageDigest()); 98 } 99 100 /** 101 * java.security.DigestOutputStream#getMessageDigest() 102 */ 103 public void test_getMessageDigest() { 104 105 MessageDigest digest = new MyMessageDigest1(); 106 OutputStream out = new MyOutputStream(); 107 108 // non-null parameter 109 DigestOutputStream dos = new DigestOutputStream(out, digest); 110 assertSame(digest, dos.getMessageDigest()); 111 112 // null parameter 113 dos = new DigestOutputStream(out, null); 114 assertNull("getMessageDigest should have returned null", dos.getMessageDigest()); 115 } 116 117 /** 118 * java.security.DigestOutputStream#setMessageDigest(MessageDigest) 119 */ 120 public void test_setMessageDigestLjava_security_MessageDigest() { 121 122 MessageDigest digest = new MyMessageDigest1(); 123 OutputStream out = new MyOutputStream(); 124 125 DigestOutputStream dos = new DigestOutputStream(out, null); 126 127 // non-null parameter 128 dos.setMessageDigest(digest); 129 assertSame(digest, dos.getMessageDigest()); 130 131 // null parameter 132 dos.setMessageDigest(null); 133 assertNull("getMessageDigest should have returned null", dos.getMessageDigest()); 134 } 135 136 137 /** 138 * Test #1 for <code>write(int)</code> method<br> 139 * 140 * Assertion: writes the byte to the output stream<br> 141 * Assertion: updates associated digest<br> 142 */ 143 public final void testWriteint01() 144 throws IOException { 145 for (int k=0; k<algorithmName.length; k++) { 146 try { 147 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 148 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 149 DigestOutputStream dos = new DigestOutputStream(bos, md); 150 for (int i=0; i<MY_MESSAGE_LEN; i++) { 151 dos.write(myMessage[i]); 152 } 153 // check that bytes have been written correctly 154 assertTrue("write", Arrays.equals(MDGoldenData.getMessage(), bos.toByteArray())); 155 // check that associated digest has been updated properly 156 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 157 MDGoldenData.getDigest(algorithmName[k]))); 158 return; 159 } catch (NoSuchAlgorithmException e) { 160 // allowed failure 161 } 162 } 163 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 164 } 165 166 /** 167 * Test #2 for <code>write(int)</code> method<br> 168 * Test #1 for <code>on(boolean)</code> method<br> 169 * 170 * Assertion: <code>write(int)</code> must not update digest if it is off<br> 171 * Assertion: <code>on(boolean)</code> turns digest functionality on 172 * if <code>true</code> passed as a parameter or off if <code>false</code> 173 * passed 174 */ 175 public final void testWriteint02() 176 throws IOException { 177 for (int k=0; k<algorithmName.length; k++) { 178 try { 179 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 180 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 181 DigestOutputStream dos = new DigestOutputStream(bos, md); 182 183 // turn digest off 184 dos.on(false); 185 186 for (int i=0; i<MY_MESSAGE_LEN; i++) { 187 dos.write(myMessage[i]); 188 } 189 190 // check that bytes have been written correctly 191 assertTrue("write", Arrays.equals(MDGoldenData.getMessage(), bos.toByteArray())); 192 // check that digest value has not been updated by write() 193 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 194 MDGoldenData.getDigest(algorithmName[k]+"_NU"))); 195 return; 196 } catch (NoSuchAlgorithmException e) { 197 // allowed failure 198 } 199 } 200 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 201 } 202 203 /** 204 * Test #3 for <code>write(int)</code> method<br> 205 * 206 * Assertion: broken <code>DigestOutputStream</code>instance: 207 * <code>OutputStream</code> not set. <code>write(int)</code> must 208 * not work 209 */ 210 public final void testWriteint03() throws IOException { 211 for (int k=0; k<algorithmName.length; k++) { 212 try { 213 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 214 DigestOutputStream dos = new DigestOutputStream(null, md); 215 // must result in an exception 216 try { 217 for (int i=0; i<MY_MESSAGE_LEN; i++) { 218 dos.write(myMessage[i]); 219 } 220 fail("OutputStream not set. write(int) must not work"); 221 } catch (Exception e) { 222 return; 223 } 224 } catch (NoSuchAlgorithmException e) { 225 // allowed failure 226 } 227 } 228 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 229 } 230 231 /** 232 * Test #4 for <code>write(int)</code> method<br> 233 * 234 * Assertion: broken <code>DigestOutputStream</code>instance: 235 * associated <code>MessageDigest</code> not set. 236 * <code>write(int)</code> must not work when digest 237 * functionality is on 238 */ 239 public final void testWriteint04() throws IOException { 240 OutputStream os = new ByteArrayOutputStream(MY_MESSAGE_LEN); 241 DigestOutputStream dos = new DigestOutputStream(os, null); 242 243 // must result in an exception 244 try { 245 for (int i=0; i<MY_MESSAGE_LEN; i++) { 246 dos.write(myMessage[i]); 247 } 248 fail("OutputStream not set. write(int) must not work"); 249 } catch (Exception e) { 250 return; 251 } 252 } 253 254 /** 255 * Test #5 for <code>write(int)</code> method<br> 256 * Test #2 for <code>on(boolean)</code> method<br> 257 * 258 * Assertion: broken <code>DigestOutputStream</code>instance: 259 * associated <code>MessageDigest</code> not set. 260 * <code>write(int)</code> must work when digest 261 * functionality is off 262 */ 263 public final void testWriteint05() throws IOException { 264 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 265 DigestOutputStream dos = new DigestOutputStream(bos, null); 266 // set digest functionality to off 267 dos.on(false); 268 // the following must pass without any exception 269 for (int i=0; i<MY_MESSAGE_LEN; i++) { 270 dos.write(myMessage[i]); 271 } 272 // check that bytes have been written correctly 273 assertTrue(Arrays.equals(MDGoldenData.getMessage(), bos.toByteArray())); 274 } 275 276 /** 277 * Test #1 for <code>write(byte[],int,int)</code> method<br> 278 * 279 * Assertion: put bytes into output stream<br> 280 * 281 * Assertion: updates associated digest<br> 282 */ 283 public final void test_write$BII_1() throws IOException { 284 for (int k=0; k<algorithmName.length; k++) { 285 try { 286 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 287 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 288 DigestOutputStream dos = new DigestOutputStream(bos, md); 289 290 // write message at once 291 dos.write(myMessage, 0, MY_MESSAGE_LEN); 292 293 // check write 294 assertTrue("write", Arrays.equals(myMessage, bos.toByteArray())); 295 // check that associated digest has been updated properly 296 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 297 MDGoldenData.getDigest(algorithmName[k]))); 298 return; 299 } catch (NoSuchAlgorithmException e) { 300 // allowed failure 301 } 302 } 303 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 304 } 305 306 /** 307 * Test #2 for <code>write(byte[],int,int)</code> method<br> 308 * 309 * Assertion: put bytes into output stream<br> 310 * 311 * Assertion: updates associated digest<br> 312 */ 313 public final void test_write$BII_2() throws IOException { 314 // check precondition 315 assertEquals(0, MY_MESSAGE_LEN % CHUNK_SIZE); 316 for (int k=0; k<algorithmName.length; k++) { 317 try { 318 319 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 320 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 321 DigestOutputStream dos = new DigestOutputStream(bos, md); 322 323 // write message by chunks 324 for (int i=0; i<MY_MESSAGE_LEN/CHUNK_SIZE; i++) { 325 dos.write(myMessage, i*CHUNK_SIZE, CHUNK_SIZE); 326 } 327 // check write 328 assertTrue("write", Arrays.equals(myMessage, bos.toByteArray())); 329 // check that associated digest has been updated properly 330 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 331 MDGoldenData.getDigest(algorithmName[k]))); 332 return; 333 } catch (NoSuchAlgorithmException e) { 334 // allowed failure 335 } 336 } 337 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 338 } 339 340 341 /** 342 * Test #3 for <code>write(byte[],int,int)</code> method<br> 343 * 344 * Assertion: put bytes into output stream<br> 345 * 346 * Assertion: updates associated digest<br> 347 */ 348 public final void test_write$BII_3() 349 throws NoSuchAlgorithmException, 350 IOException { 351 // check precondition 352 assertTrue(MY_MESSAGE_LEN % (CHUNK_SIZE+1) != 0); 353 354 for (int k=0; k<algorithmName.length; k++) { 355 try { 356 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 357 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 358 DigestOutputStream dos = new DigestOutputStream(bos, md); 359 360 // write message by chunks 361 for (int i=0; i<MY_MESSAGE_LEN/(CHUNK_SIZE+1); i++) { 362 dos.write(myMessage, i*(CHUNK_SIZE+1), CHUNK_SIZE+1); 363 } 364 // write remaining bytes 365 dos.write(myMessage, 366 MY_MESSAGE_LEN/(CHUNK_SIZE+1)*(CHUNK_SIZE+1), 367 MY_MESSAGE_LEN % (CHUNK_SIZE+1)); 368 // check write 369 assertTrue("write", Arrays.equals(myMessage, bos.toByteArray())); 370 // check that associated digest has been updated properly 371 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 372 MDGoldenData.getDigest(algorithmName[k]))); 373 return; 374 } catch (NoSuchAlgorithmException e) { 375 // allowed failure 376 } 377 } 378 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 379 } 380 381 /** 382 * Test #4 for <code>write(byte[],int,int)</code> method<br> 383 * 384 * Assertion: put bytes into output stream<br> 385 * 386 * Assertion: does not update associated digest if digest 387 * functionality is off<br> 388 */ 389 public final void test_write$BII_4() 390 throws NoSuchAlgorithmException, 391 IOException { 392 // check precondition 393 assertEquals(0, MY_MESSAGE_LEN % CHUNK_SIZE); 394 395 for (int k=0; k<algorithmName.length; k++) { 396 try { 397 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 398 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 399 DigestOutputStream dos = new DigestOutputStream(bos, md); 400 401 // set digest functionality off 402 dos.on(false); 403 404 // write message by chunks 405 for (int i=0; i<MY_MESSAGE_LEN/CHUNK_SIZE; i++) { 406 dos.write(myMessage, i*CHUNK_SIZE, CHUNK_SIZE); 407 } 408 409 // check write 410 assertTrue("write", Arrays.equals(myMessage, bos.toByteArray())); 411 // check that associated digest has not been updated 412 assertTrue("update", Arrays.equals(dos.getMessageDigest().digest(), 413 MDGoldenData.getDigest(algorithmName[k]+"_NU"))); 414 return; 415 } catch (NoSuchAlgorithmException e) { 416 // allowed failure 417 } 418 } 419 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 420 } 421 422 /** 423 * java.security.DigestOutputStream#write(byte[], int, int) 424 */ 425 public void test_write$BII_6() throws Exception { 426 427 // Regression form HARMONY-1091. 428 MessageDigest md = new MyMessageDigest1(); 429 byte[] bytes = new byte[] { 1, 2 }; 430 DigestOutputStream dig = new DigestOutputStream( 431 new ByteArrayOutputStream(), md); 432 // buf == null 433 try { 434 dig.write(null, -1, 0); 435 fail("No expected IllegalArgumentException"); 436 } catch (IllegalArgumentException e) { 437 } 438 // offset + len > buf.length 439 try { 440 dig.write(bytes, 0, bytes.length + 1); 441 fail("No expected IllegalArgumentException"); 442 } catch (IllegalArgumentException e) { 443 } 444 // offset < 0 445 try { 446 dig.write(bytes, -1, 1); 447 fail("No expected IndexOutOfBoundsException"); 448 } catch (IndexOutOfBoundsException e) { 449 } 450 // len < 0 451 try { 452 dig.write(bytes, 0, -1); 453 fail("No expected IndexOutOfBoundsException"); 454 } catch (IndexOutOfBoundsException e) { 455 } 456 } 457 458 /** 459 * java.io.DigestOutputStream#write(byte[], int, int) 460 */ 461 public void test_write$BII_7() 462 throws IOException, NoSuchAlgorithmException { 463 Support_OutputStream sos = new Support_OutputStream(MY_MESSAGE_LEN); 464 MessageDigest md = MessageDigest.getInstance(algorithmName[0]); 465 DigestOutputStream dos = new DigestOutputStream(sos, md); 466 467 dos.write(myMessage, 0, MY_MESSAGE_LEN); 468 469 try { 470 // Support_OutputStream throws an IOException if the internal 471 // buffer is full, which it should be now. 472 dos.write(myMessage, 0, MY_MESSAGE_LEN); 473 fail("Test 1: IOException expected."); 474 } catch (IOException e) { 475 // Expected. 476 } 477 } 478 479 /** 480 * Test for <code>on()</code> method<br> 481 * Assertion: turns digest functionality on or off 482 */ 483 public final void testOn() throws IOException { 484 for (int k=0; k<algorithmName.length; k++) { 485 try { 486 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 487 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 488 DigestOutputStream dos = new DigestOutputStream(bos, md); 489 490 // turn digest off 491 dos.on(false); 492 493 for (int i=0; i<MY_MESSAGE_LEN-1; i++) { 494 dos.write(myMessage[i]); 495 } 496 497 // turn digest on 498 dos.on(true); 499 500 // read remaining byte 501 dos.write(myMessage[MY_MESSAGE_LEN-1]); 502 503 byte[] digest = dos.getMessageDigest().digest(); 504 505 // check that digest value has been 506 // updated by the last write(int) call 507 assertFalse(Arrays.equals(digest,MDGoldenData.getDigest(algorithmName[k]))); 508 assertFalse(Arrays.equals(digest,MDGoldenData.getDigest(algorithmName[k]+"_NU"))); 509 return; 510 } catch (NoSuchAlgorithmException e) { 511 // allowed failure 512 } 513 } 514 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 515 } 516 517 /** 518 * Test for <code>toString()</code> method<br> 519 * Assertion: returns <code>String</code> representation of this object 520 */ 521 public final void testToString() throws NoSuchAlgorithmException { 522 for (int k=0; k<algorithmName.length; k++) { 523 try { 524 ByteArrayOutputStream bos = new ByteArrayOutputStream(MY_MESSAGE_LEN); 525 MessageDigest md = MessageDigest.getInstance(algorithmName[k]); 526 DigestOutputStream dos = new DigestOutputStream(bos, md); 527 528 assertNotNull(dos.toString()); 529 return; 530 } catch (NoSuchAlgorithmException e) { 531 // allowed failure 532 } 533 } 534 fail(getName() + ": no MessageDigest algorithms available - test not performed"); 535 } 536 537 /** 538 * java.security.DigestOutputStream#on(boolean) 539 */ 540 public void test_onZ() throws Exception { 541 // Test for method void java.security.DigestOutputStream.on(boolean) 542 DigestOutputStream dos = new DigestOutputStream( 543 new ByteArrayOutputStream(), MessageDigest.getInstance("SHA")); 544 dos.on(false); 545 byte digestArray[] = { 23, 43, 44 }; 546 dos.write(digestArray, 1, 1); 547 byte digestResult[] = dos.getMessageDigest().digest(); 548 byte expected[] = { -38, 57, -93, -18, 94, 107, 75, 13, 50, 85, 549 -65, -17, -107, 96, 24, -112, -81, -40, 7, 9 }; 550 assertTrue("Digest did not return expected result.", 551 Arrays.equals(digestResult, expected)); 552 // now turn on processing and re-run 553 dos.on(true); 554 dos.write(digestArray, 1, 1); 555 digestResult = dos.getMessageDigest().digest(); 556 byte expected1[] = { -87, 121, -17, 16, -52, 111, 106, 54, -33, 557 107, -118, 50, 51, 7, -18, 59, -78, -30, -37, -100 }; 558 559 assertTrue("Digest did not return expected result.", 560 Arrays.equals(digestResult, expected1)); 561 } 562 563 /** 564 * java.security.DigestOutputStream#write(byte[], int, int) 565 */ 566 public void test_write$BII_5() throws Exception { 567 // Test for method void java.security.DigestOutputStream.write(byte [], 568 // int, int) 569 DigestOutputStream dos = new DigestOutputStream( 570 new ByteArrayOutputStream(), MessageDigest.getInstance("SHA")); 571 byte digestArray[] = { 23, 43, 44 }; 572 dos.write(digestArray, 1, 1); 573 byte digestResult[] = dos.getMessageDigest().digest(); 574 byte expected[] = { -87, 121, -17, 16, -52, 111, 106, 54, -33, 107, 575 -118, 50, 51, 7, -18, 59, -78, -30, -37, -100 }; 576 577 assertTrue("Digest did not return expected result.", 578 Arrays.equals(digestResult, expected)); 579 } 580 581 /** 582 * java.security.DigestOutputStream#write(int) 583 */ 584 public void test_writeI() throws Exception { 585 // Test for method void java.security.DigestOutputStream.write(int) 586 DigestOutputStream dos = new DigestOutputStream( 587 new ByteArrayOutputStream(), MessageDigest.getInstance("SHA")); 588 dos.write((byte) 43); 589 byte digestResult[] = dos.getMessageDigest().digest(); 590 byte expected[] = { -87, 121, -17, 16, -52, 111, 106, 54, -33, 107, 591 -118, 50, 51, 7, -18, 59, -78, -30, -37, -100 }; 592 593 assertTrue("Digest did not return expected result.", 594 Arrays.equals(digestResult, expected)); 595 } 596 597 private class MessageDigestWithUnsupportedUpdate extends MessageDigest { 598 private MessageDigestWithUnsupportedUpdate() { 599 super("SomeAlgorithm"); 600 } 601 602 @Override 603 protected void engineUpdate(byte input) { 604 throw new UnsupportedOperationException(); 605 } 606 607 @Override 608 protected void engineUpdate(byte[] input, int offset, int len) { 609 throw new UnsupportedOperationException(); 610 } 611 612 @Override 613 protected byte[] engineDigest() { 614 return new byte[0]; 615 } 616 617 @Override 618 protected void engineReset() { 619 620 } 621 } 622 623 public void test_write_writeToUnderlyingStreamBeforeUpdatingDigest() { 624 MessageDigest messageDigestWithUnsupportedUpdate = new MessageDigestWithUnsupportedUpdate(); 625 OutputStream outputStreamThatThrowsIOException = new OutputStream() { 626 @Override 627 public void write(int b) throws IOException { 628 throw new IOException(); 629 } 630 }; 631 632 DigestOutputStream digestOutputStream = new DigestOutputStream( 633 outputStreamThatThrowsIOException, messageDigestWithUnsupportedUpdate); 634 635 // Writing throws an IOException (and not an UnsupportedOperationException) meaning than 636 // it tried to write to the underlying stream before updating the digest. 637 digestOutputStream.on(true); 638 try { 639 digestOutputStream.write(3); 640 fail(); 641 } catch (IOException expected) { 642 } 643 644 digestOutputStream.on(true); 645 try { 646 digestOutputStream.write(new byte[10], 0, 10); 647 fail(); 648 } catch (IOException expected) { 649 } 650 651 digestOutputStream.on(true); 652 try { 653 digestOutputStream.write(new byte[10]); 654 fail(); 655 } catch (IOException expected) { 656 } 657 } 658 659 private class MyOutputStream extends OutputStream { 660 @Override 661 public void write(int arg0) throws IOException { 662 } 663 } 664 665 private class MyDigestOutputStream extends DigestOutputStream { 666 public MyDigestOutputStream(OutputStream out, MessageDigest digest) { 667 super(out, digest); 668 } 669 670 public MessageDigest myMessageDigest() { 671 return digest; 672 } 673 674 public OutputStream myOutputStream() { 675 return out; 676 } 677 } 678 } 679