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