1package org.bouncycastle.asn1;
2
3import java.io.ByteArrayOutputStream;
4import java.io.EOFException;
5import java.io.IOException;
6import java.io.InputStream;
7
8import org.bouncycastle.util.Arrays;
9import org.bouncycastle.util.io.Streams;
10
11/**
12 * Base class for BIT STRING objects
13 */
14public abstract class ASN1BitString
15    extends ASN1Primitive
16    implements ASN1String
17{
18    private static final char[]  table = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
19
20    protected final byte[]      data;
21    protected final int         padBits;
22
23    /**
24     * @param bitString an int containing the BIT STRING
25     * @return the correct number of pad bits for a bit string defined in
26     * a 32 bit constant
27     */
28    static protected int getPadBits(
29        int bitString)
30    {
31        int val = 0;
32        for (int i = 3; i >= 0; i--)
33        {
34            //
35            // this may look a little odd, but if it isn't done like this pre jdk1.2
36            // JVM's break!
37            //
38            if (i != 0)
39            {
40                if ((bitString >> (i * 8)) != 0)
41                {
42                    val = (bitString >> (i * 8)) & 0xFF;
43                    break;
44                }
45            }
46            else
47            {
48                if (bitString != 0)
49                {
50                    val = bitString & 0xFF;
51                    break;
52                }
53            }
54        }
55
56        if (val == 0)
57        {
58            return 0;
59        }
60
61        int bits = 1;
62
63        while (((val <<= 1) & 0xFF) != 0)
64        {
65            bits++;
66        }
67
68        return 8 - bits;
69    }
70
71    /**
72     * @param bitString an int containing the BIT STRING
73     * @return the correct number of bytes for a bit string defined in
74     * a 32 bit constant
75     */
76    static protected byte[] getBytes(int bitString)
77    {
78        if (bitString == 0)
79        {
80            return new byte[0];
81        }
82
83        int bytes = 4;
84        for (int i = 3; i >= 1; i--)
85        {
86            if ((bitString & (0xFF << (i * 8))) != 0)
87            {
88                break;
89            }
90            bytes--;
91        }
92
93        byte[] result = new byte[bytes];
94        for (int i = 0; i < bytes; i++)
95        {
96            result[i] = (byte) ((bitString >> (i * 8)) & 0xFF);
97        }
98
99        return result;
100    }
101
102    /**
103     * Base constructor.
104     *
105     * @param data the octets making up the bit string.
106     * @param padBits the number of extra bits at the end of the string.
107     */
108    public ASN1BitString(
109        byte[]  data,
110        int     padBits)
111    {
112        if (data == null)
113        {
114            throw new NullPointerException("data cannot be null");
115        }
116        if (data.length == 0 && padBits != 0)
117        {
118            throw new IllegalArgumentException("zero length data with non-zero pad bits");
119        }
120        if (padBits > 7 || padBits < 0)
121        {
122            throw new IllegalArgumentException("pad bits cannot be greater than 7 or less than 0");
123        }
124
125        this.data = Arrays.clone(data);
126        this.padBits = padBits;
127    }
128
129    /**
130     * Return a String representation of this BIT STRING
131     *
132     * @return a String representation.
133     */
134    public String getString()
135    {
136        StringBuffer          buf = new StringBuffer("#");
137        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
138        ASN1OutputStream      aOut = new ASN1OutputStream(bOut);
139
140        try
141        {
142            aOut.writeObject(this);
143        }
144        catch (IOException e)
145        {
146            throw new ASN1ParsingException("Internal error encoding BitString: " + e.getMessage(), e);
147        }
148
149        byte[]    string = bOut.toByteArray();
150
151        for (int i = 0; i != string.length; i++)
152        {
153            buf.append(table[(string[i] >>> 4) & 0xf]);
154            buf.append(table[string[i] & 0xf]);
155        }
156
157        return buf.toString();
158    }
159
160    /**
161     * @return the value of the bit string as an int (truncating if necessary)
162     */
163    public int intValue()
164    {
165        int value = 0;
166        byte[] string = data;
167
168        if (padBits > 0 && data.length <= 4)
169        {
170            string = derForm(data, padBits);
171        }
172
173        for (int i = 0; i != string.length && i != 4; i++)
174        {
175            value |= (string[i] & 0xff) << (8 * i);
176        }
177
178        return value;
179    }
180
181    /**
182     * Return the octets contained in this BIT STRING, checking that this BIT STRING really
183     * does represent an octet aligned string. Only use this method when the standard you are
184     * following dictates that the BIT STRING will be octet aligned.
185     *
186     * @return a copy of the octet aligned data.
187     */
188    public byte[] getOctets()
189    {
190        if (padBits != 0)
191        {
192            throw new IllegalStateException("attempt to get non-octet aligned data from BIT STRING");
193        }
194
195        return Arrays.clone(data);
196    }
197
198    public byte[] getBytes()
199    {
200        return derForm(data, padBits);
201    }
202
203    public int getPadBits()
204    {
205        return padBits;
206    }
207
208    public String toString()
209    {
210        return getString();
211    }
212
213    public int hashCode()
214    {
215        return padBits ^ Arrays.hashCode(this.getBytes());
216    }
217
218    protected boolean asn1Equals(
219        ASN1Primitive  o)
220    {
221        if (!(o instanceof ASN1BitString))
222        {
223            return false;
224        }
225
226        ASN1BitString other = (ASN1BitString)o;
227
228        return this.padBits == other.padBits
229            && Arrays.areEqual(this.getBytes(), other.getBytes());
230    }
231
232    protected static byte[] derForm(byte[] data, int padBits)
233    {
234        byte[] rv = Arrays.clone(data);
235        // DER requires pad bits be zero
236        if (padBits > 0)
237        {
238            rv[data.length - 1] &= 0xff << padBits;
239        }
240
241        return rv;
242    }
243
244    static ASN1BitString fromInputStream(int length, InputStream stream)
245        throws IOException
246    {
247        if (length < 1)
248        {
249            throw new IllegalArgumentException("truncated BIT STRING detected");
250        }
251
252        int padBits = stream.read();
253        byte[] data = new byte[length - 1];
254
255        if (data.length != 0)
256        {
257            if (Streams.readFully(stream, data) != data.length)
258            {
259                throw new EOFException("EOF encountered in middle of BIT STRING");
260            }
261
262            if (padBits > 0 && padBits < 8)
263            {
264                if (data[data.length - 1] != (byte)(data[data.length - 1] & (0xff << padBits)))
265                {
266                    return new DLBitString(data, padBits);
267                }
268            }
269        }
270
271        return new DERBitString(data, padBits);
272    }
273
274    public ASN1Primitive getLoadedObject()
275    {
276        return this.toASN1Primitive();
277    }
278
279    ASN1Primitive toDERObject()
280    {
281        return new DERBitString(data, padBits);
282    }
283
284    ASN1Primitive toDLObject()
285    {
286        return new DLBitString(data, padBits);
287    }
288
289    abstract void encode(ASN1OutputStream out)
290        throws IOException;
291}
292