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