1package org.bouncycastle.asn1;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5
6import org.bouncycastle.util.Arrays;
7
8/**
9 * Base class for an application specific object
10 */
11public class DERApplicationSpecific
12    extends ASN1Primitive
13{
14    private final boolean   isConstructed;
15    private final int       tag;
16    private final byte[]    octets;
17
18    DERApplicationSpecific(
19        boolean isConstructed,
20        int     tag,
21        byte[]  octets)
22    {
23        this.isConstructed = isConstructed;
24        this.tag = tag;
25        this.octets = octets;
26    }
27
28    public DERApplicationSpecific(
29        int    tag,
30        byte[] octets)
31    {
32        this(false, tag, octets);
33    }
34
35    public DERApplicationSpecific(
36        int                  tag,
37        ASN1Encodable object)
38        throws IOException
39    {
40        this(true, tag, object);
41    }
42
43    public DERApplicationSpecific(
44        boolean      explicit,
45        int          tag,
46        ASN1Encodable object)
47        throws IOException
48    {
49        ASN1Primitive primitive = object.toASN1Primitive();
50
51        byte[] data = primitive.getEncoded(ASN1Encoding.DER);
52
53        this.isConstructed = explicit || (primitive instanceof ASN1Set || primitive instanceof ASN1Sequence);
54        this.tag = tag;
55
56        if (explicit)
57        {
58            this.octets = data;
59        }
60        else
61        {
62            int lenBytes = getLengthOfHeader(data);
63            byte[] tmp = new byte[data.length - lenBytes];
64            System.arraycopy(data, lenBytes, tmp, 0, tmp.length);
65            this.octets = tmp;
66        }
67    }
68
69    public DERApplicationSpecific(int tagNo, ASN1EncodableVector vec)
70    {
71        this.tag = tagNo;
72        this.isConstructed = true;
73        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
74
75        for (int i = 0; i != vec.size(); i++)
76        {
77            try
78            {
79                bOut.write(((ASN1Object)vec.get(i)).getEncoded(ASN1Encoding.DER));
80            }
81            catch (IOException e)
82            {
83                throw new ASN1ParsingException("malformed object: " + e, e);
84            }
85        }
86        this.octets = bOut.toByteArray();
87    }
88
89    public static DERApplicationSpecific getInstance(Object obj)
90    {
91        if (obj == null || obj instanceof DERApplicationSpecific)
92        {
93            return (DERApplicationSpecific)obj;
94        }
95        else if (obj instanceof byte[])
96        {
97            try
98            {
99                return DERApplicationSpecific.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
100            }
101            catch (IOException e)
102            {
103                throw new IllegalArgumentException("failed to construct object from byte[]: " + e.getMessage());
104            }
105        }
106        else if (obj instanceof ASN1Encodable)
107        {
108            ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
109
110            if (primitive instanceof ASN1Sequence)
111            {
112                return (DERApplicationSpecific)primitive;
113            }
114        }
115
116        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
117    }
118
119    private int getLengthOfHeader(byte[] data)
120    {
121        int length = data[1] & 0xff; // TODO: assumes 1 byte tag
122
123        if (length == 0x80)
124        {
125            return 2;      // indefinite-length encoding
126        }
127
128        if (length > 127)
129        {
130            int size = length & 0x7f;
131
132            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
133            if (size > 4)
134            {
135                throw new IllegalStateException("DER length more than 4 bytes: " + size);
136            }
137
138            return size + 2;
139        }
140
141        return 2;
142    }
143
144    public boolean isConstructed()
145    {
146        return isConstructed;
147    }
148
149    public byte[] getContents()
150    {
151        return octets;
152    }
153
154    public int getApplicationTag()
155    {
156        return tag;
157    }
158
159    /**
160     * Return the enclosed object assuming explicit tagging.
161     *
162     * @return  the resulting object
163     * @throws IOException if reconstruction fails.
164     */
165    public ASN1Primitive getObject()
166        throws IOException
167    {
168        return new ASN1InputStream(getContents()).readObject();
169    }
170
171    /**
172     * Return the enclosed object assuming implicit tagging.
173     *
174     * @param derTagNo the type tag that should be applied to the object's contents.
175     * @return  the resulting object
176     * @throws IOException if reconstruction fails.
177     */
178    public ASN1Primitive getObject(int derTagNo)
179        throws IOException
180    {
181        if (derTagNo >= 0x1f)
182        {
183            throw new IOException("unsupported tag number");
184        }
185
186        byte[] orig = this.getEncoded();
187        byte[] tmp = replaceTagNumber(derTagNo, orig);
188
189        if ((orig[0] & BERTags.CONSTRUCTED) != 0)
190        {
191            tmp[0] |= BERTags.CONSTRUCTED;
192        }
193
194        return new ASN1InputStream(tmp).readObject();
195    }
196
197    int encodedLength()
198        throws IOException
199    {
200        return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length;
201    }
202
203    /* (non-Javadoc)
204     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
205     */
206    void encode(ASN1OutputStream out) throws IOException
207    {
208        int classBits = BERTags.APPLICATION;
209        if (isConstructed)
210        {
211            classBits |= BERTags.CONSTRUCTED;
212        }
213
214        out.writeEncoded(classBits, tag, octets);
215    }
216
217    boolean asn1Equals(
218        ASN1Primitive o)
219    {
220        if (!(o instanceof DERApplicationSpecific))
221        {
222            return false;
223        }
224
225        DERApplicationSpecific other = (DERApplicationSpecific)o;
226
227        return isConstructed == other.isConstructed
228            && tag == other.tag
229            && Arrays.areEqual(octets, other.octets);
230    }
231
232    public int hashCode()
233    {
234        return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets);
235    }
236
237    private byte[] replaceTagNumber(int newTag, byte[] input)
238        throws IOException
239    {
240        int tagNo = input[0] & 0x1f;
241        int index = 1;
242        //
243        // with tagged object tag number is bottom 5 bits, or stored at the start of the content
244        //
245        if (tagNo == 0x1f)
246        {
247            tagNo = 0;
248
249            int b = input[index++] & 0xff;
250
251            // X.690-0207 8.1.2.4.2
252            // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
253            if ((b & 0x7f) == 0) // Note: -1 will pass
254            {
255                throw new ASN1ParsingException("corrupted stream - invalid high tag number found");
256            }
257
258            while ((b >= 0) && ((b & 0x80) != 0))
259            {
260                tagNo |= (b & 0x7f);
261                tagNo <<= 7;
262                b = input[index++] & 0xff;
263            }
264
265            tagNo |= (b & 0x7f);
266        }
267
268        byte[] tmp = new byte[input.length - index + 1];
269
270        System.arraycopy(input, index, tmp, 1, tmp.length - 1);
271
272        tmp[0] = (byte)newTag;
273
274        return tmp;
275    }
276}
277