1package org.bouncycastle.asn1.x500.style;
2
3import java.io.IOException;
4import java.util.Enumeration;
5import java.util.Hashtable;
6
7import org.bouncycastle.asn1.ASN1Encodable;
8import org.bouncycastle.asn1.ASN1ObjectIdentifier;
9import org.bouncycastle.asn1.DERUTF8String;
10import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
11import org.bouncycastle.asn1.x500.RDN;
12import org.bouncycastle.asn1.x500.X500Name;
13import org.bouncycastle.asn1.x500.X500NameStyle;
14
15/**
16 * This class provides some default behavior and common implementation for a
17 * X500NameStyle. It should be easily extendable to support implementing the
18 * desired X500NameStyle.
19 */
20public abstract class AbstractX500NameStyle
21    implements X500NameStyle
22{
23
24    /**
25     * Tool function to shallow copy a Hashtable.
26     *
27     * @param paramsMap table to copy
28     * @return the copy of the table
29     */
30    public static Hashtable copyHashTable(Hashtable paramsMap)
31    {
32        Hashtable newTable = new Hashtable();
33
34        Enumeration keys = paramsMap.keys();
35        while (keys.hasMoreElements())
36        {
37            Object key = keys.nextElement();
38            newTable.put(key, paramsMap.get(key));
39        }
40
41        return newTable;
42    }
43
44    private int calcHashCode(ASN1Encodable enc)
45    {
46        String value = IETFUtils.valueToString(enc);
47        value = IETFUtils.canonicalize(value);
48        return value.hashCode();
49    }
50
51    public int calculateHashCode(X500Name name)
52    {
53        int hashCodeValue = 0;
54        RDN[] rdns = name.getRDNs();
55
56        // this needs to be order independent, like equals
57        for (int i = 0; i != rdns.length; i++)
58        {
59            if (rdns[i].isMultiValued())
60            {
61                AttributeTypeAndValue[] atv = rdns[i].getTypesAndValues();
62
63                for (int j = 0; j != atv.length; j++)
64                {
65                    hashCodeValue ^= atv[j].getType().hashCode();
66                    hashCodeValue ^= calcHashCode(atv[j].getValue());
67                }
68            }
69            else
70            {
71                hashCodeValue ^= rdns[i].getFirst().getType().hashCode();
72                hashCodeValue ^= calcHashCode(rdns[i].getFirst().getValue());
73            }
74        }
75
76        return hashCodeValue;
77    }
78
79
80    /**
81     * For all string values starting with '#' is assumed, that these are
82     * already valid ASN.1 objects encoded in hex.
83     * <p>
84     * All other string values are send to
85     * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)}.
86     * </p>
87     * Subclasses should overwrite
88     * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)}
89     * to change the encoding of specific types.
90     *
91     * @param oid the DN name of the value.
92     * @param value the String representation of the value.
93     */
94    public ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value)
95    {
96        if (value.length() != 0 && value.charAt(0) == '#')
97        {
98            try
99            {
100                return IETFUtils.valueFromHexString(value, 1);
101            }
102            catch (IOException e)
103            {
104                throw new RuntimeException("can't recode value for oid " + oid.getId());
105            }
106        }
107
108        if (value.length() != 0 && value.charAt(0) == '\\')
109        {
110            value = value.substring(1);
111        }
112
113        return encodeStringValue(oid, value);
114    }
115
116    /**
117     * Encoded every value into a UTF8String.
118     * <p>
119     * Subclasses should overwrite
120     * this method to change the encoding of specific types.
121     * </p>
122     *
123     * @param oid the DN oid of the value
124     * @param value the String representation of the value
125     * @return a the value encoded into a ASN.1 object. Never returns <code>null</code>.
126     */
127    protected ASN1Encodable encodeStringValue(ASN1ObjectIdentifier oid, String value)
128    {
129        return new DERUTF8String(value);
130    }
131
132    public boolean areEqual(X500Name name1, X500Name name2)
133    {
134        RDN[] rdns1 = name1.getRDNs();
135        RDN[] rdns2 = name2.getRDNs();
136
137        if (rdns1.length != rdns2.length)
138        {
139            return false;
140        }
141
142        boolean reverse = false;
143
144        if (rdns1[0].getFirst() != null && rdns2[0].getFirst() != null)
145        {
146            reverse = !rdns1[0].getFirst().getType().equals(rdns2[0].getFirst().getType());  // guess forward
147        }
148
149        for (int i = 0; i != rdns1.length; i++)
150        {
151            if (!foundMatch(reverse, rdns1[i], rdns2))
152            {
153                return false;
154            }
155        }
156
157        return true;
158    }
159
160    private boolean foundMatch(boolean reverse, RDN rdn, RDN[] possRDNs)
161    {
162        if (reverse)
163        {
164            for (int i = possRDNs.length - 1; i >= 0; i--)
165            {
166                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
167                {
168                    possRDNs[i] = null;
169                    return true;
170                }
171            }
172        }
173        else
174        {
175            for (int i = 0; i != possRDNs.length; i++)
176            {
177                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
178                {
179                    possRDNs[i] = null;
180                    return true;
181                }
182            }
183        }
184
185        return false;
186    }
187
188    protected boolean rdnAreEqual(RDN rdn1, RDN rdn2)
189    {
190        return IETFUtils.rDNAreEqual(rdn1, rdn2);
191    }
192}
193