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
18package javax.crypto;
19
20import java.io.FilterOutputStream;
21import java.io.IOException;
22import java.io.OutputStream;
23import libcore.io.Streams;
24
25/**
26 * This class wraps an output stream and a cipher so that {@code write} methods
27 * send the data through the cipher before writing them to the underlying output
28 * stream.
29 * <p>
30 * The cipher must be initialized for the requested operation before being used
31 * by a {@code CipherOutputStream}. For example, if a cipher initialized for
32 * encryption is used with a {@code CipherOutputStream}, the {@code
33 * CipherOutputStream} tries to encrypt the data writing it out.
34 */
35public class CipherOutputStream extends FilterOutputStream {
36
37    private final Cipher cipher;
38
39    /**
40     * Creates a new {@code CipherOutputStream} instance for an {@code
41     * OutputStream} and a {@code Cipher}.
42     *
43     * @param os
44     *            the output stream to write data to.
45     * @param c
46     *            the cipher to process the data with.
47     */
48    public CipherOutputStream(OutputStream os, Cipher c) {
49        super(os);
50        cipher = c;
51    }
52
53    /**
54     * Creates a new {@code CipherOutputStream} instance for an {@code
55     * OutputStream} without a cipher.
56     * <p>
57     * A {@code NullCipher} is created to process the data.
58     *
59     * @param os
60     *            the output stream to write the data to.
61     */
62    protected CipherOutputStream(OutputStream os) {
63        this(os, new NullCipher());
64    }
65
66    /**
67     * Writes the single byte to this cipher output stream.
68     *
69     * @param b
70     *            the byte to write.
71     * @throws IOException
72     *             if an error occurs.
73     */
74    @Override public void write(int b) throws IOException {
75        Streams.writeSingleByte(this, b);
76    }
77
78    /**
79     * Writes the {@code len} bytes from buffer {@code b} starting at offset
80     * {@code off} to this cipher output stream.
81     *
82     * @param b
83     *            the buffer.
84     * @param off
85     *            the offset to start at.
86     * @param len
87     *            the number of bytes.
88     * @throws IOException
89     *             if an error occurs.
90     */
91    @Override public void write(byte[] b, int off, int len) throws IOException {
92        if (len == 0) {
93            return;
94        }
95        byte[] result = cipher.update(b, off, len);
96        if (result != null) {
97            out.write(result);
98        }
99    }
100
101    /**
102     * Flushes this cipher output stream.
103     *
104     * @throws IOException
105     *             if an error occurs
106     */
107    @Override
108    public void flush() throws IOException {
109        out.flush();
110    }
111
112    /**
113     * Close this cipher output stream.
114     * <p>
115     * On the underlying cipher {@code doFinal} will be invoked, and any
116     * buffered bytes from the cipher are also written out, and the cipher is
117     * reset to its initial state. The underlying output stream is also closed.
118     *
119     * @throws IOException
120     *             if an error occurs.
121     */
122    @Override
123    public void close() throws IOException {
124        byte[] result;
125        try {
126            if (cipher != null) {
127                result = cipher.doFinal();
128                if (result != null) {
129                    out.write(result);
130                }
131            }
132            if (out != null) {
133                out.flush();
134            }
135        } catch (BadPaddingException e) {
136            throw new IOException(e.getMessage());
137        } catch (IllegalBlockSizeException e) {
138            throw new IOException(e.getMessage());
139        } finally {
140            if (out != null) {
141                out.close();
142            }
143        }
144    }
145}
146