Home | History | Annotate | Download | only in base
      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 
     52   public static Collection<Asn1Tag> getPossibleFirstTags() {
     53     return possibleFirstTags;
     54   }
     55 
     56   @Override Asn1Tag getDefaultTag() {
     57     return Asn1Tag.IA5_STRING;
     58   }
     59 
     60   @Override int getBerValueLength() {
     61     Preconditions.checkNotNull(value, "No value set.");
     62     return value.length();
     63   }
     64 
     65   @Override void encodeBerValue(ByteBuffer buf) {
     66     Preconditions.checkNotNull(value, "No value set.");
     67     buf.put(value.getBytes(StandardCharsets.US_ASCII));
     68   }
     69 
     70   @Override void decodeBerValue(ByteBuffer buf) {
     71     setValue(new String(getRemaining(buf), StandardCharsets.US_ASCII));
     72   }
     73 
     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 
     88   protected void setMinSize(int min) {
     89     minimumSize = min;
     90   }
     91 
     92   protected void setMaxSize(int max) {
     93     maximumSize = max;
     94   }
     95 
     96   public String getValue() {
     97     return value;
     98   }
     99 
    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 
    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 
    162   @Override public Iterable<BitStream> encodePerUnaligned() {
    163     return encodePerImpl(false);
    164   }
    165 
    166   @Override public Iterable<BitStream> encodePerAligned() {
    167     return encodePerImpl(true);
    168   }
    169 
    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 
    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 
    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 
    246   @Override public void decodePerUnaligned(BitStreamReader reader) {
    247     decodePerImpl(reader, false);
    248   }
    249 
    250   @Override public void decodePerAligned(BitStreamReader reader) {
    251     decodePerImpl(reader, true);
    252   }
    253 
    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 {
    276     RestrictedCharset() {
    277       super("Restricted_IA5", new String[0]);
    278     }
    279 
    280     @Override
    281     public boolean contains(Charset cs) {
    282       return false;
    283     }
    284 
    285     @Override
    286     public CharsetDecoder newDecoder() {
    287       return new RestrictedCharsetDecoder(this);
    288     }
    289 
    290     @Override
    291     public CharsetEncoder newEncoder() {
    292       return new RestrictedCharsetEncoder(this);
    293     }
    294   }
    295 
    296   private class RestrictedCharsetEncoder extends CharsetEncoder {
    297     RestrictedCharsetEncoder(RestrictedCharset restrictedCharset) {
    298       super(restrictedCharset, 1, 1, new byte[] {0});
    299     }
    300 
    301     @Override
    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 {
    319     RestrictedCharsetDecoder(RestrictedCharset restrictedCharset) {
    320       super(restrictedCharset, 1, 1);
    321     }
    322 
    323     @Override
    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