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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * @author Alexander Y. Kleymenov
23 package org.apache.harmony.security.provider.cert;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.nio.charset.Charsets;
28 import java.security.cert.CRL;
29 import java.security.cert.CRLException;
30 import java.security.cert.CertPath;
31 import java.security.cert.Certificate;
32 import java.security.cert.CertificateException;
33 import java.security.cert.CertificateFactorySpi;
34 import java.security.cert.X509CRL;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Iterator;
38 import java.util.List;
39 import org.apache.harmony.luni.util.Base64;
40 import org.apache.harmony.security.asn1.ASN1Constants;
41 import org.apache.harmony.security.asn1.BerInputStream;
42 import org.apache.harmony.security.pkcs7.ContentInfo;
43 import org.apache.harmony.security.pkcs7.SignedData;
44 import org.apache.harmony.security.x509.CertificateList;
47 * X509 Certificate Factory Service Provider Interface Implementation.
48 * It supports CRLs and Certificates in (PEM) ASN.1 DER encoded form,
49 * and Certification Paths in PkiPath and PKCS7 formats.
50 * For Certificates and CRLs factory maintains the caching
51 * mechanisms allowing to speed up repeated Certificate/CRL
55 public class X509CertFactoryImpl extends CertificateFactorySpi {
57 // number of leading/trailing bytes used for cert hash computation
58 private static final int CERT_CACHE_SEED_LENGTH = 28;
60 private static final Cache CERT_CACHE = new Cache(CERT_CACHE_SEED_LENGTH);
61 // number of leading/trailing bytes used for crl hash computation
62 private static final int CRL_CACHE_SEED_LENGTH = 24;
64 private static final Cache CRL_CACHE = new Cache(CRL_CACHE_SEED_LENGTH);
67 * Default constructor.
68 * Creates the instance of Certificate Factory SPI ready for use.
70 public X509CertFactoryImpl() { }
73 * Generates the X.509 certificate from the data in the stream.
74 * The data in the stream can be either in ASN.1 DER encoded X.509
75 * certificate, or PEM (Base64 encoding bounded by
76 * <code>"-----BEGIN CERTIFICATE-----"</code> at the beginning and
77 * <code>"-----END CERTIFICATE-----"</code> at the end) representation
78 * of the former encoded form.
80 * Before the generation the encoded form is looked up in
81 * the cache. If the cache contains the certificate with requested encoded
82 * form it is returned from it, otherwise it is generated by ASN.1
85 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificate(InputStream)
86 * method documentation for more info
88 public Certificate engineGenerateCertificate(InputStream inStream)
89 throws CertificateException {
90 if (inStream == null) {
91 throw new CertificateException("inStream == null");
94 if (!inStream.markSupported()) {
95 // create the mark supporting wrapper
96 inStream = new RestoringInputStream(inStream);
98 // mark is needed to recognize the format of the provided encoding
101 // check whether the provided certificate is in PEM encoded form
102 if (inStream.read() == '-') {
103 // decode PEM, retrieve CRL
104 return getCertificate(decodePEM(inStream, CERT_BOUND_SUFFIX));
108 return getCertificate(inStream);
110 } catch (IOException e) {
111 throw new CertificateException(e);
116 * Generates the collection of the certificates on the base of provided
117 * via input stream encodings.
118 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertificates(InputStream)
119 * method documentation for more info
121 public Collection<? extends Certificate>
122 engineGenerateCertificates(InputStream inStream)
123 throws CertificateException {
124 if (inStream == null) {
125 throw new CertificateException("inStream == null");
127 ArrayList result = new ArrayList();
129 if (!inStream.markSupported()) {
130 // create the mark supporting wrapper
131 inStream = new RestoringInputStream(inStream);
133 // if it is PEM encoded form this array will contain the encoding
134 // so ((it is PEM) <-> (encoding != null))
135 byte[] encoding = null;
136 // The following by SEQUENCE ASN.1 tag, used for
137 // recognizing the data format
138 // (is it PKCS7 ContentInfo structure, X.509 Certificate, or
139 // unsupported encoding)
140 int second_asn1_tag = -1;
143 while ((ch = inStream.read()) != -1) {
144 // check if it is PEM encoded form
145 if (ch == '-') { // beginning of PEM encoding ('-' char)
146 // decode PEM chunk and store its content (ASN.1 encoding)
147 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
148 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
151 // prepare for data format determination
152 inStream.mark(CERT_CACHE_SEED_LENGTH);
153 } else { // unsupported data
154 if (result.size() == 0) {
155 throw new CertificateException("Unsupported encoding");
157 // it can be trailing user data,
158 // so keep it in the stream
163 // Check the data format
164 BerInputStream in = (encoding == null)
165 ? new BerInputStream(inStream)
166 : new BerInputStream(encoding);
167 // read the next ASN.1 tag
168 second_asn1_tag = in.next(); // inStream position changed
169 if (encoding == null) {
170 // keep whole structure in the stream
173 // check if it is a TBSCertificate structure
174 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
175 if (result.size() == 0) {
176 // there were not read X.509 Certificates, so
177 // break the cycle and check
178 // whether it is PKCS7 structure
181 // it can be trailing user data,
182 // so return what we already read
186 if (encoding == null) {
187 result.add(getCertificate(inStream));
189 result.add(getCertificate(encoding));
192 // mark for the next iteration
195 if (result.size() != 0) {
196 // some Certificates have been read
198 } else if (ch == -1) {
199 throw new CertificateException("There is no data in the stream");
201 // else: check if it is PKCS7
202 if (second_asn1_tag == ASN1Constants.TAG_OID) {
203 // it is PKCS7 ContentInfo structure, so decode it
204 ContentInfo info = (ContentInfo)
206 ? ContentInfo.ASN1.decode(encoding)
207 : ContentInfo.ASN1.decode(inStream));
208 // retrieve SignedData
209 SignedData data = info.getSignedData();
211 throw new CertificateException("Invalid PKCS7 data provided");
213 List certs = data.getCertificates();
215 for (int i = 0; i < certs.size(); i++) {
216 result.add(new X509CertImpl(
217 (org.apache.harmony.security.x509.Certificate)
223 // else: Unknown data format
224 throw new CertificateException("Unsupported encoding");
225 } catch (IOException e) {
226 throw new CertificateException(e);
231 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRL(InputStream)
232 * method documentation for more info
234 public CRL engineGenerateCRL(InputStream inStream)
235 throws CRLException {
236 if (inStream == null) {
237 throw new CRLException("inStream == null");
240 if (!inStream.markSupported()) {
241 // Create the mark supporting wrapper
242 // Mark is needed to recognize the format
243 // of provided encoding form (ASN.1 or PEM)
244 inStream = new RestoringInputStream(inStream);
247 // check whether the provided crl is in PEM encoded form
248 if (inStream.read() == '-') {
249 // decode PEM, retrieve CRL
250 return getCRL(decodePEM(inStream, FREE_BOUND_SUFFIX));
254 return getCRL(inStream);
256 } catch (IOException e) {
257 throw new CRLException(e);
262 * @see java.security.cert.CertificateFactorySpi#engineGenerateCRLs(InputStream)
263 * method documentation for more info
265 public Collection<? extends CRL> engineGenerateCRLs(InputStream inStream)
266 throws CRLException {
267 if (inStream == null) {
268 throw new CRLException("inStream == null");
270 ArrayList result = new ArrayList();
272 if (!inStream.markSupported()) {
273 inStream = new RestoringInputStream(inStream);
275 // if it is PEM encoded form this array will contain the encoding
276 // so ((it is PEM) <-> (encoding != null))
277 byte[] encoding = null;
278 // The following by SEQUENCE ASN.1 tag, used for
279 // recognizing the data format
280 // (is it PKCS7 ContentInfo structure, X.509 CRL, or
281 // unsupported encoding)
282 int second_asn1_tag = -1;
285 while ((ch = inStream.read()) != -1) {
286 // check if it is PEM encoded form
287 if (ch == '-') { // beginning of PEM encoding ('-' char)
288 // decode PEM chunk and store its content (ASN.1 encoding)
289 encoding = decodePEM(inStream, FREE_BOUND_SUFFIX);
290 } else if (ch == 0x30) { // beginning of ASN.1 sequence (0x30)
293 // prepare for data format determination
294 inStream.mark(CRL_CACHE_SEED_LENGTH);
295 } else { // unsupported data
296 if (result.size() == 0) {
297 throw new CRLException("Unsupported encoding");
299 // it can be trailing user data,
300 // so keep it in the stream
305 // Check the data format
306 BerInputStream in = (encoding == null)
307 ? new BerInputStream(inStream)
308 : new BerInputStream(encoding);
309 // read the next ASN.1 tag
310 second_asn1_tag = in.next();
311 if (encoding == null) {
312 // keep whole structure in the stream
315 // check if it is a TBSCertList structure
316 if (second_asn1_tag != ASN1Constants.TAG_C_SEQUENCE) {
317 if (result.size() == 0) {
318 // there were not read X.509 CRLs, so
319 // break the cycle and check
320 // whether it is PKCS7 structure
323 // it can be trailing user data,
324 // so return what we already read
328 if (encoding == null) {
329 result.add(getCRL(inStream));
331 result.add(getCRL(encoding));
336 if (result.size() != 0) {
337 // the stream was read out
339 } else if (ch == -1) {
340 throw new CRLException("There is no data in the stream");
342 // else: check if it is PKCS7
343 if (second_asn1_tag == ASN1Constants.TAG_OID) {
344 // it is PKCS7 ContentInfo structure, so decode it
345 ContentInfo info = (ContentInfo)
347 ? ContentInfo.ASN1.decode(encoding)
348 : ContentInfo.ASN1.decode(inStream));
349 // retrieve SignedData
350 SignedData data = info.getSignedData();
352 throw new CRLException("Invalid PKCS7 data provided");
354 List crls = data.getCRLs();
356 for (int i = 0; i < crls.size(); i++) {
357 result.add(new X509CRLImpl(
358 (CertificateList) crls.get(i)));
363 // else: Unknown data format
364 throw new CRLException("Unsupported encoding");
365 } catch (IOException e) {
366 throw new CRLException(e);
371 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream)
372 * method documentation for more info
374 public CertPath engineGenerateCertPath(InputStream inStream)
375 throws CertificateException {
376 if (inStream == null) {
377 throw new CertificateException("inStream == null");
379 return engineGenerateCertPath(inStream, "PkiPath");
383 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(InputStream,String)
384 * method documentation for more info
386 public CertPath engineGenerateCertPath(
387 InputStream inStream, String encoding) throws CertificateException {
388 if (inStream == null) {
389 throw new CertificateException("inStream == null");
391 if (!inStream.markSupported()) {
392 inStream = new RestoringInputStream(inStream);
398 // check if it is PEM encoded form
399 if ((ch = inStream.read()) == '-') {
400 // decode PEM chunk into ASN.1 form and decode CertPath object
401 return X509CertPathImpl.getInstance(
402 decodePEM(inStream, FREE_BOUND_SUFFIX), encoding);
403 } else if (ch == 0x30) { // ASN.1 Sequence
406 return X509CertPathImpl.getInstance(inStream, encoding);
408 throw new CertificateException("Unsupported encoding");
410 } catch (IOException e) {
411 throw new CertificateException(e);
416 * @see java.security.cert.CertificateFactorySpi#engineGenerateCertPath(List)
417 * method documentation for more info
419 public CertPath engineGenerateCertPath(List certificates)
420 throws CertificateException {
421 return new X509CertPathImpl(certificates);
425 * @see java.security.cert.CertificateFactorySpi#engineGetCertPathEncodings()
426 * method documentation for more info
428 public Iterator<String> engineGetCertPathEncodings() {
429 return X509CertPathImpl.encodings.iterator();
432 // ---------------------------------------------------------------------
433 // ------------------------ Staff methods ------------------------------
434 // ---------------------------------------------------------------------
436 private static final byte[] PEM_BEGIN = "-----BEGIN".getBytes(Charsets.UTF_8);
437 private static final byte[] PEM_END = "-----END".getBytes(Charsets.UTF_8);
439 * Code describing free format for PEM boundary suffix:
440 * "^-----BEGIN.*\n" at the beginning, and<br>
441 * "\n-----END.*(EOF|\n)$" at the end.
443 private static final byte[] FREE_BOUND_SUFFIX = null;
445 * Code describing PEM boundary suffix for X.509 certificate:
446 * "^-----BEGIN CERTIFICATE-----\n" at the beginning, and<br>
447 * "\n-----END CERTIFICATE-----" at the end.
449 private static final byte[] CERT_BOUND_SUFFIX = " CERTIFICATE-----".getBytes(Charsets.UTF_8);
452 * Method retrieves the PEM encoded data from the stream
453 * and returns its decoded representation.
454 * Method checks correctness of PEM boundaries. It supposes that
455 * the first '-' of the opening boundary has already been read from
456 * the stream. So first of all it checks that the leading bytes
457 * are equal to "-----BEGIN" boundary prefix. Than if boundary_suffix
458 * is not null, it checks that next bytes equal to boundary_suffix
459 * + new line char[s] ([CR]LF).
460 * If boundary_suffix parameter is null, method supposes free suffix
461 * format and skips any bytes until the new line.<br>
462 * After the opening boundary has been read and checked, the method
463 * read Base64 encoded data until closing PEM boundary is not reached.<br>
464 * Than it checks closing boundary - it should start with new line +
465 * "-----END" + boundary_suffix. If boundary_suffix is null,
466 * any characters are skipped until the new line.<br>
467 * After this any trailing new line characters are skipped from the stream,
468 * Base64 encoding is decoded and returned.
469 * @param inStream the stream containing the PEM encoding.
470 * @param boundary_suffix the suffix of expected PEM multipart
471 * boundary delimiter.<br>
472 * If it is null, that any character sequences are accepted.
473 * @throws IOException If PEM boundary delimiter does not comply
474 * with expected or some I/O or decoding problems occur.
476 private byte[] decodePEM(InputStream inStream, byte[] boundary_suffix)
478 int ch; // the char to be read
479 // check and skip opening boundary delimiter
480 // (first '-' is supposed as already read)
481 for (int i = 1; i < PEM_BEGIN.length; ++i) {
482 if (PEM_BEGIN[i] != (ch = inStream.read())) {
483 throw new IOException(
484 "Incorrect PEM encoding: '-----BEGIN"
485 + ((boundary_suffix == null)
486 ? "" : new String(boundary_suffix))
487 + "' is expected as opening delimiter boundary.");
490 if (boundary_suffix == null) {
491 // read (skip) the trailing characters of
492 // the beginning PEM boundary delimiter
493 while ((ch = inStream.read()) != '\n') {
495 throw new IOException("Incorrect PEM encoding: EOF before content");
499 for (int i=0; i<boundary_suffix.length; i++) {
500 if (boundary_suffix[i] != inStream.read()) {
501 throw new IOException("Incorrect PEM encoding: '-----BEGIN" +
502 new String(boundary_suffix) + "' is expected as opening delimiter boundary.");
505 // read new line characters
506 if ((ch = inStream.read()) == '\r') {
507 // CR has been read, now read LF character
508 ch = inStream.read();
511 throw new IOException("Incorrect PEM encoding: newline expected after " +
512 "opening delimiter boundary");
515 int size = 1024; // the size of the buffer containing Base64 data
516 byte[] buff = new byte[size];
518 // read bytes while ending boundary delimiter is not reached
519 while ((ch = inStream.read()) != '-') {
521 throw new IOException("Incorrect Base64 encoding: EOF without closing delimiter");
523 buff[index++] = (byte) ch;
525 // enlarge the buffer
526 byte[] newbuff = new byte[size+1024];
527 System.arraycopy(buff, 0, newbuff, 0, size);
532 if (buff[index-1] != '\n') {
533 throw new IOException("Incorrect Base64 encoding: newline expected before " +
534 "closing boundary delimiter");
536 // check and skip closing boundary delimiter prefix
537 // (first '-' was read)
538 for (int i = 1; i < PEM_END.length; ++i) {
539 if (PEM_END[i] != inStream.read()) {
540 throw badEnd(boundary_suffix);
543 if (boundary_suffix == null) {
544 // read (skip) the trailing characters of
545 // the closing PEM boundary delimiter
546 while (((ch = inStream.read()) != -1) && (ch != '\n') && (ch != '\r')) {
549 for (int i=0; i<boundary_suffix.length; i++) {
550 if (boundary_suffix[i] != inStream.read()) {
551 throw badEnd(boundary_suffix);
555 // skip trailing line breaks
557 while (((ch = inStream.read()) != -1) && (ch == '\n' || ch == '\r')) {
561 buff = Base64.decode(buff, index);
563 throw new IOException("Incorrect Base64 encoding");
568 private IOException badEnd(byte[] boundary_suffix) throws IOException {
569 String s = (boundary_suffix == null) ? "" : new String(boundary_suffix);
570 throw new IOException("Incorrect PEM encoding: '-----END" + s + "' is expected as closing delimiter boundary.");
574 * Reads the data of specified length from source
575 * and returns it as an array.
576 * @return the byte array contained read data or
577 * null if the stream contains not enough data
578 * @throws IOException if some I/O error has been occurred.
580 private static byte[] readBytes(InputStream source, int length)
582 byte[] result = new byte[length];
583 for (int i=0; i<length; i++) {
584 int bytik = source.read();
588 result[i] = (byte) bytik;
594 * Returns the Certificate object corresponding to the provided encoding.
595 * Resulting object is retrieved from the cache
596 * if it contains such correspondence
597 * and is constructed on the base of encoding
598 * and stored in the cache otherwise.
599 * @throws IOException if some decoding errors occur
600 * (in the case of cache miss).
602 private static Certificate getCertificate(byte[] encoding)
603 throws CertificateException, IOException {
604 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
605 throw new CertificateException("encoding.length < CERT_CACHE_SEED_LENGTH");
607 synchronized (CERT_CACHE) {
608 long hash = CERT_CACHE.getHash(encoding);
609 if (CERT_CACHE.contains(hash)) {
611 (Certificate) CERT_CACHE.get(hash, encoding);
616 Certificate res = new X509CertImpl(encoding);
617 CERT_CACHE.put(hash, encoding, res);
623 * Returns the Certificate object corresponding to the encoding provided
625 * Resulting object is retrieved from the cache
626 * if it contains such correspondence
627 * and is constructed on the base of encoding
628 * and stored in the cache otherwise.
629 * @throws IOException if some decoding errors occur
630 * (in the case of cache miss).
632 private static Certificate getCertificate(InputStream inStream)
633 throws CertificateException, IOException {
634 synchronized (CERT_CACHE) {
635 inStream.mark(CERT_CACHE_SEED_LENGTH);
636 // read the prefix of the encoding
637 byte[] buff = readBytes(inStream, CERT_CACHE_SEED_LENGTH);
640 throw new CertificateException("InputStream doesn't contain enough data");
642 long hash = CERT_CACHE.getHash(buff);
643 if (CERT_CACHE.contains(hash)) {
644 byte[] encoding = new byte[BerInputStream.getLength(buff)];
645 if (encoding.length < CERT_CACHE_SEED_LENGTH) {
646 throw new CertificateException("Bad Certificate encoding");
648 inStream.read(encoding);
649 Certificate res = (Certificate) CERT_CACHE.get(hash, encoding);
653 res = new X509CertImpl(encoding);
654 CERT_CACHE.put(hash, encoding, res);
658 Certificate res = new X509CertImpl(inStream);
659 CERT_CACHE.put(hash, res.getEncoded(), res);
666 * Returns the CRL object corresponding to the provided encoding.
667 * Resulting object is retrieved from the cache
668 * if it contains such correspondence
669 * and is constructed on the base of encoding
670 * and stored in the cache otherwise.
671 * @throws IOException if some decoding errors occur
672 * (in the case of cache miss).
674 private static CRL getCRL(byte[] encoding)
675 throws CRLException, IOException {
676 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
677 throw new CRLException("encoding.length < CRL_CACHE_SEED_LENGTH");
679 synchronized (CRL_CACHE) {
680 long hash = CRL_CACHE.getHash(encoding);
681 if (CRL_CACHE.contains(hash)) {
682 X509CRL res = (X509CRL) CRL_CACHE.get(hash, encoding);
687 X509CRL res = new X509CRLImpl(encoding);
688 CRL_CACHE.put(hash, encoding, res);
694 * Returns the CRL object corresponding to the encoding provided
696 * Resulting object is retrieved from the cache
697 * if it contains such correspondence
698 * and is constructed on the base of encoding
699 * and stored in the cache otherwise.
700 * @throws IOException if some decoding errors occur
701 * (in the case of cache miss).
703 private static CRL getCRL(InputStream inStream)
704 throws CRLException, IOException {
705 synchronized (CRL_CACHE) {
706 inStream.mark(CRL_CACHE_SEED_LENGTH);
707 byte[] buff = readBytes(inStream, CRL_CACHE_SEED_LENGTH);
708 // read the prefix of the encoding
711 throw new CRLException("InputStream doesn't contain enough data");
713 long hash = CRL_CACHE.getHash(buff);
714 if (CRL_CACHE.contains(hash)) {
715 byte[] encoding = new byte[BerInputStream.getLength(buff)];
716 if (encoding.length < CRL_CACHE_SEED_LENGTH) {
717 throw new CRLException("Bad CRL encoding");
719 inStream.read(encoding);
720 CRL res = (CRL) CRL_CACHE.get(hash, encoding);
724 res = new X509CRLImpl(encoding);
725 CRL_CACHE.put(hash, encoding, res);
728 X509CRL res = new X509CRLImpl(inStream);
729 CRL_CACHE.put(hash, res.getEncoded(), res);
736 * This class extends any existing input stream with
737 * mark functionality. It acts as a wrapper over the
738 * stream and supports reset to the
739 * marked state with readlimit no more than BUFF_SIZE.
741 private static class RestoringInputStream extends InputStream {
743 // wrapped input stream
744 private final InputStream inStream;
745 // specifies how much of the read data is buffered
746 // after the mark has been set up
747 private static final int BUFF_SIZE = 32;
748 // buffer to keep the bytes read after the mark has been set up
749 private final int[] buff = new int[BUFF_SIZE*2];
750 // position of the next byte to read,
751 // the value of -1 indicates that the buffer is not used
752 // (mark was not set up or was invalidated, or reset to the marked
753 // position has been done and all the buffered data was read out)
754 private int pos = -1;
755 // position of the last buffered byte
757 // position in the buffer where the mark becomes invalidated
761 * Creates the mark supporting wrapper over the stream.
763 public RestoringInputStream(InputStream inStream) {
764 this.inStream = inStream;
768 public int available() throws IOException {
769 return (bar - pos) + inStream.available();
773 public void close() throws IOException {
778 public void mark(int readlimit) {
784 end = (pos + BUFF_SIZE - 1) % BUFF_SIZE;
789 public boolean markSupported() {
794 * Reads the byte from the stream. If mark has been set up
795 * and was not invalidated byte is read from the underlying
796 * stream and saved into the buffer. If the current read position
797 * has been reset to the marked position and there are remaining
798 * bytes in the buffer, the byte is taken from it. In the other cases
799 * (if mark has been invalidated, or there are no buffered bytes)
800 * the byte is taken directly from the underlying stream and it is
801 * returned without saving to the buffer.
803 * @see java.io.InputStream#read()
804 * method documentation for more info
806 public int read() throws IOException {
807 // if buffer is currently used
809 // current position in the buffer
810 int cur = pos % BUFF_SIZE;
811 // check whether the buffer contains the data to be read
813 // return the data from the buffer
817 // check whether buffer has free space
819 // it has, so read the data from the wrapped stream
820 // and place it in the buffer
821 buff[cur] = inStream.read();
826 // buffer if full and can not operate
827 // any more, so invalidate the mark position
828 // and turn off the using of buffer
832 // buffer is not used, so return the data from the wrapped stream
833 return inStream.read();
837 public int read(byte[] b) throws IOException {
838 return read(b, 0, b.length);
842 public int read(byte[] b, int off, int len) throws IOException {
845 for (i=0; i<len; i++) {
846 if ((read_b = read()) == -1) {
847 return (i == 0) ? -1 : i;
849 b[off+i] = (byte) read_b;
855 public void reset() throws IOException {
857 pos = (end + 1) % BUFF_SIZE;
859 throw new IOException("Could not reset the stream: " +
860 "position became invalid or stream has not been marked");
865 public long skip(long n) throws IOException {
868 int av = available();
872 while ((i < n) && (read() != -1)) {
877 return inStream.skip(n);