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