1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.location.cts.asn1.base;
18 
19 import static android.location.cts.asn1.base.PerAlignedUtils.SIXTYFOUR_K;
20 
21 import com.google.common.base.Preconditions;
22 import com.google.common.collect.ImmutableList;
23 
24 import java.nio.ByteBuffer;
25 import java.nio.CharBuffer;
26 import java.nio.charset.CharacterCodingException;
27 import java.nio.charset.Charset;
28 import java.nio.charset.CharsetDecoder;
29 import java.nio.charset.CharsetEncoder;
30 import java.nio.charset.CoderResult;
31 import java.nio.charset.StandardCharsets;
32 import java.util.Arrays;
33 import java.util.Collection;
34 
35 /**
36  * Represents strings in 7-bit US ASCII (actually pages 1 and 6 of ISO
37  * International Register of Coded Character Sets plus SPACE and DELETE).
38  *
39  * Implements ASN.1 functionality.
40  *
41  */
42 public class Asn1IA5String extends Asn1Object {
43   private static final Collection<Asn1Tag> possibleFirstTags =
44       ImmutableList.of(Asn1Tag.IA5_STRING);
45 
46   private String alphabet = null;
47   private byte largestCanonicalValue = 127;
48   private String value;
49   private int minimumSize = 0;
50   private Integer maximumSize = null; // Null == unconstrained.
51 
getPossibleFirstTags()52   public static Collection<Asn1Tag> getPossibleFirstTags() {
53     return possibleFirstTags;
54   }
55 
getDefaultTag()56   @Override Asn1Tag getDefaultTag() {
57     return Asn1Tag.IA5_STRING;
58   }
59 
getBerValueLength()60   @Override int getBerValueLength() {
61     Preconditions.checkNotNull(value, "No value set.");
62     return value.length();
63   }
64 
encodeBerValue(ByteBuffer buf)65   @Override void encodeBerValue(ByteBuffer buf) {
66     Preconditions.checkNotNull(value, "No value set.");
67     buf.put(value.getBytes(StandardCharsets.US_ASCII));
68   }
69 
decodeBerValue(ByteBuffer buf)70   @Override void decodeBerValue(ByteBuffer buf) {
71     setValue(new String(getRemaining(buf), StandardCharsets.US_ASCII));
72   }
73 
setAlphabet(String alphabet)74   protected void setAlphabet(String alphabet) {
75     Preconditions.checkNotNull(alphabet);
76     Preconditions.checkArgument(alphabet.length() > 0, "Empty alphabet");
77     try {
78       ByteBuffer buffer = StandardCharsets.US_ASCII.newEncoder().encode(CharBuffer.wrap(alphabet));
79       byte[] canonicalValues = buffer.array();
80       Arrays.sort(canonicalValues);
81       largestCanonicalValue = canonicalValues[canonicalValues.length - 1];
82       this.alphabet = new String(canonicalValues, StandardCharsets.US_ASCII);
83     } catch (CharacterCodingException e) {
84       throw new IllegalArgumentException("Invalid alphabet " + alphabet, e);
85     }
86   }
87 
setMinSize(int min)88   protected void setMinSize(int min) {
89     minimumSize = min;
90   }
91 
setMaxSize(int max)92   protected void setMaxSize(int max) {
93     maximumSize = max;
94   }
95 
getValue()96   public String getValue() {
97     return value;
98   }
99 
setValue(String value)100   public void setValue(String value) {
101     Preconditions.checkArgument(value.length() >= minimumSize,
102                                 "Value too short.");
103     Preconditions.checkArgument(maximumSize == null
104                                 || value.length() <= maximumSize,
105                                 "Value too long.");
106     try {
107       Charset charset = (alphabet != null) ? new RestrictedCharset() : StandardCharsets.US_ASCII;
108       charset.newEncoder().encode(CharBuffer.wrap(value));
109       this.value = value;
110     } catch (CharacterCodingException e) {
111       throw new IllegalArgumentException("Illegal value '" + value + "'", e);
112     }
113   }
114 
encodePerImpl(boolean aligned)115   private Iterable<BitStream> encodePerImpl(boolean aligned) {
116     Preconditions.checkNotNull(value, "No value set.");
117 
118     int characterBitCount = calculateBitsPerCharacter(aligned);
119 
120     // Use real character values if they fit.
121     boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
122 
123     // In aligned case, pad unless result size is known to be 16 bits or less [X.691-0207, 27.5.6-7]
124     BitStream result = encodeValueCharacters(characterBitCount, recodeValues);
125     if (aligned && (maximumSize == null || maximumSize * characterBitCount > 16)) {
126       result.setBeginByteAligned();
127     }
128 
129     if (maximumSize != null) {
130       if (minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
131         return ImmutableList.of(result);
132       }
133 
134       if (maximumSize >= SIXTYFOUR_K) {
135         throw new UnsupportedOperationException("large string unimplemented");
136       }
137 
138       // A little oddity when maximumSize != minimumSize [X.691-0207, 27.5.7].
139       if (aligned && maximumSize * characterBitCount == 16) {
140         result.setBeginByteAligned();
141       }
142     }
143 
144     // Must be preceded by a count. The count and the bit field may be independently aligned.
145     BitStream count = null;
146     if (maximumSize == null) {
147       count = aligned
148           ? PerAlignedUtils.encodeSemiConstrainedLength(value.length())
149           : PerUnalignedUtils.encodeSemiConstrainedLength(value.length());
150     } else {
151       if (aligned) {
152         count = PerAlignedUtils.encodeSmallConstrainedWholeNumber(
153             value.length(), minimumSize, maximumSize);
154       } else {
155         count = PerUnalignedUtils.encodeConstrainedWholeNumber(
156             value.length(), minimumSize, maximumSize);
157       }
158     }
159     return ImmutableList.of(count, result);
160   }
161 
encodePerUnaligned()162   @Override public Iterable<BitStream> encodePerUnaligned() {
163     return encodePerImpl(false);
164   }
165 
encodePerAligned()166   @Override public Iterable<BitStream> encodePerAligned() {
167     return encodePerImpl(true);
168   }
169 
encodeValueCharacters(int characterBitCount, boolean recodeValues)170   private BitStream encodeValueCharacters(int characterBitCount,
171                                           boolean recodeValues) {
172     BitStream result = new BitStream();
173     try {
174       Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
175       ByteBuffer buffer = charset.newEncoder().encode(CharBuffer.wrap(value));
176       while (buffer.hasRemaining()) {
177         byte b = buffer.get();
178         if (characterBitCount == 8) {
179           result.appendByte(b);
180         } else {
181           result.appendLowBits(characterBitCount, b);
182         }
183       }
184     } catch (CharacterCodingException e) {
185       throw new IllegalStateException("Invalid value", e);
186     }
187     return result;
188   }
189 
calculateBitsPerCharacter(boolean aligned)190   private int calculateBitsPerCharacter(boolean aligned) {
191     // must be power of 2 in aligned version.
192     int characterBitCount = aligned ? 8 : 7;
193     if (alphabet != null) {
194       for (int i = 1; i < characterBitCount; i += aligned ? i : 1) {
195         if (1 << i >= alphabet.length()) {
196           characterBitCount = i;
197           break;
198         }
199       }
200     }
201     return characterBitCount;
202   }
203 
decodePerImpl(BitStreamReader reader, boolean aligned)204   private void decodePerImpl(BitStreamReader reader, boolean aligned) {
205 
206     int characterBitCount = calculateBitsPerCharacter(aligned);
207 
208     // Use real character values if they fit.
209     boolean recodeValues = largestCanonicalValue >= (1 << characterBitCount);
210 
211     if (maximumSize != null && minimumSize == maximumSize && maximumSize < SIXTYFOUR_K) {
212       if (aligned && maximumSize * characterBitCount > 16) {
213         reader.spoolToByteBoundary();
214       }
215       decodeValueCharacters(reader, maximumSize,
216                             characterBitCount, recodeValues);
217       return;
218     }
219 
220     if (maximumSize != null && maximumSize >= SIXTYFOUR_K) {
221       throw new UnsupportedOperationException("large string unimplemented");
222     }
223 
224     int count = 0;
225     if (maximumSize == null) {
226       count = aligned
227           ? PerAlignedUtils.decodeSemiConstrainedLength(reader)
228           : PerUnalignedUtils.decodeSemiConstrainedLength(reader);
229     } else {
230       if (aligned) {
231         count = PerAlignedUtils.decodeSmallConstrainedWholeNumber(
232               reader, minimumSize, maximumSize);
233       } else {
234         count = PerUnalignedUtils.decodeConstrainedWholeNumber(
235               reader, minimumSize, maximumSize);
236       }
237     }
238 
239     if (aligned && (maximumSize == null || maximumSize * characterBitCount >= 16)) {
240       reader.spoolToByteBoundary();
241     }
242     decodeValueCharacters(reader, count,
243                           characterBitCount, recodeValues);
244   }
245 
decodePerUnaligned(BitStreamReader reader)246   @Override public void decodePerUnaligned(BitStreamReader reader) {
247     decodePerImpl(reader, false);
248   }
249 
decodePerAligned(BitStreamReader reader)250   @Override public void decodePerAligned(BitStreamReader reader) {
251     decodePerImpl(reader, true);
252   }
253 
decodeValueCharacters(BitStreamReader reader, int count, int characterBitCount, boolean recodeValues)254   private void decodeValueCharacters(BitStreamReader reader, int count,
255                                      int characterBitCount,
256                                      boolean recodeValues) {
257     ByteBuffer exploded = ByteBuffer.allocate(count);
258     for (int i = 0; i < count; i++) {
259       if (characterBitCount == 8) {
260         exploded.put(reader.readByte());
261       } else {
262         exploded.put((byte) reader.readLowBits(characterBitCount));
263       }
264     }
265     exploded.flip();
266     Charset charset = recodeValues ? new RestrictedCharset() : StandardCharsets.US_ASCII;
267     try {
268       CharBuffer valueCharacters = charset.newDecoder().decode(exploded);
269       value = valueCharacters.toString();
270     } catch (CharacterCodingException e) {
271       throw new IllegalStateException("Invalid character", e);
272     }
273   }
274 
275   private class RestrictedCharset extends Charset {
RestrictedCharset()276     RestrictedCharset() {
277       super("Restricted_IA5", new String[0]);
278     }
279 
280     @Override
contains(Charset cs)281     public boolean contains(Charset cs) {
282       return false;
283     }
284 
285     @Override
newDecoder()286     public CharsetDecoder newDecoder() {
287       return new RestrictedCharsetDecoder(this);
288     }
289 
290     @Override
newEncoder()291     public CharsetEncoder newEncoder() {
292       return new RestrictedCharsetEncoder(this);
293     }
294   }
295 
296   private class RestrictedCharsetEncoder extends CharsetEncoder {
RestrictedCharsetEncoder(RestrictedCharset restrictedCharset)297     RestrictedCharsetEncoder(RestrictedCharset restrictedCharset) {
298       super(restrictedCharset, 1, 1, new byte[] {0});
299     }
300 
301     @Override
encodeLoop(CharBuffer in, ByteBuffer out)302     protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) {
303       while (in.hasRemaining() && out.hasRemaining()) {
304         char c = in.get();
305         int encodedValue = alphabet.indexOf(c);
306         if (encodedValue < 0) {
307           return CoderResult.unmappableForLength(1);
308         }
309         out.put((byte) encodedValue);
310       }
311       if (in.hasRemaining()) {
312         return CoderResult.OVERFLOW;
313       }
314       return CoderResult.UNDERFLOW;
315     }
316   }
317 
318   private class RestrictedCharsetDecoder extends CharsetDecoder {
RestrictedCharsetDecoder(RestrictedCharset restrictedCharset)319     RestrictedCharsetDecoder(RestrictedCharset restrictedCharset) {
320       super(restrictedCharset, 1, 1);
321     }
322 
323     @Override
decodeLoop(ByteBuffer in, CharBuffer out)324     protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
325       while (in.hasRemaining() && out.hasRemaining()) {
326         byte b = in.get();
327         int position = b & 0xFF;
328         if (position >= alphabet.length()) {
329           return CoderResult.unmappableForLength(1);
330         }
331         char decodedValue = alphabet.charAt(position);
332         out.put(decodedValue);
333       }
334       if (in.hasRemaining()) {
335         return CoderResult.OVERFLOW;
336       }
337       return CoderResult.UNDERFLOW;
338     }
339   }
340 }
341