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