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
107        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
108    }
109
110    private int getLengthOfHeader(byte[] data)
111    {
112        int length = data[1] & 0xff; // TODO: assumes 1 byte tag
113
114        if (length == 0x80)
115        {
116            return 2;      // indefinite-length encoding
117        }
118
119        if (length > 127)
120        {
121            int size = length & 0x7f;
122
123            // Note: The invalid long form "0xff" (see X.690 8.1.3.5c) will be caught here
124            if (size > 4)
125            {
126                throw new IllegalStateException("DER length more than 4 bytes: " + size);
127            }
128
129            return size + 2;
130        }
131
132        return 2;
133    }
134
135    public boolean isConstructed()
136    {
137        return isConstructed;
138    }
139
140    public byte[] getContents()
141    {
142        return octets;
143    }
144
145    public int getApplicationTag()
146    {
147        return tag;
148    }
149
150    /**
151     * Return the enclosed object assuming explicit tagging.
152     *
153     * @return  the resulting object
154     * @throws IOException if reconstruction fails.
155     */
156    public ASN1Primitive getObject()
157        throws IOException
158    {
159        return new ASN1InputStream(getContents()).readObject();
160    }
161
162    /**
163     * Return the enclosed object assuming implicit tagging.
164     *
165     * @param derTagNo the type tag that should be applied to the object's contents.
166     * @return  the resulting object
167     * @throws IOException if reconstruction fails.
168     */
169    public ASN1Primitive getObject(int derTagNo)
170        throws IOException
171    {
172        if (derTagNo >= 0x1f)
173        {
174            throw new IOException("unsupported tag number");
175        }
176
177        byte[] orig = this.getEncoded();
178        byte[] tmp = replaceTagNumber(derTagNo, orig);
179
180        if ((orig[0] & BERTags.CONSTRUCTED) != 0)
181        {
182            tmp[0] |= BERTags.CONSTRUCTED;
183        }
184
185        return new ASN1InputStream(tmp).readObject();
186    }
187
188    int encodedLength()
189        throws IOException
190    {
191        return StreamUtil.calculateTagLength(tag) + StreamUtil.calculateBodyLength(octets.length) + octets.length;
192    }
193
194    /* (non-Javadoc)
195     * @see org.bouncycastle.asn1.ASN1Primitive#encode(org.bouncycastle.asn1.DEROutputStream)
196     */
197    void encode(ASN1OutputStream out) throws IOException
198    {
199        int classBits = BERTags.APPLICATION;
200        if (isConstructed)
201        {
202            classBits |= BERTags.CONSTRUCTED;
203        }
204
205        out.writeEncoded(classBits, tag, octets);
206    }
207
208    boolean asn1Equals(
209        ASN1Primitive o)
210    {
211        if (!(o instanceof DERApplicationSpecific))
212        {
213            return false;
214        }
215
216        DERApplicationSpecific other = (DERApplicationSpecific)o;
217
218        return isConstructed == other.isConstructed
219            && tag == other.tag
220            && Arrays.areEqual(octets, other.octets);
221    }
222
223    public int hashCode()
224    {
225        return (isConstructed ? 1 : 0) ^ tag ^ Arrays.hashCode(octets);
226    }
227
228    private byte[] replaceTagNumber(int newTag, byte[] input)
229        throws IOException
230    {
231        int tagNo = input[0] & 0x1f;
232        int index = 1;
233        //
234        // with tagged object tag number is bottom 5 bits, or stored at the start of the content
235        //
236        if (tagNo == 0x1f)
237        {
238            tagNo = 0;
239
240            int b = input[index++] & 0xff;
241
242            // X.690-0207 8.1.2.4.2
243            // "c) bits 7 to 1 of the first subsequent octet shall not all be zero."
244            if ((b & 0x7f) == 0) // Note: -1 will pass
245            {
246                throw new ASN1ParsingException("corrupted stream - invalid high tag number found");
247            }
248
249            while ((b >= 0) && ((b & 0x80) != 0))
250            {
251                tagNo |= (b & 0x7f);
252                tagNo <<= 7;
253                b = input[index++] & 0xff;
254            }
255
256            tagNo |= (b & 0x7f);
257        }
258
259        byte[] tmp = new byte[input.length - index + 1];
260
261        System.arraycopy(input, index, tmp, 1, tmp.length - 1);
262
263        tmp[0] = (byte)newTag;
264
265        return tmp;
266    }
267}
268