All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.brightsparklabs.asanti.model.data.AsantiAsnDataImpl Maven / Gradle / Ivy

/*
 * Maintained by brightSPARK Labs.
 * www.brightsparklabs.com
 *
 * Refer to LICENSE at repository root for license details.
 */

package com.brightsparklabs.asanti.model.data;

import static com.google.common.base.Preconditions.*;

import com.brightsparklabs.asanti.common.OperationResult;
import com.brightsparklabs.asanti.decoder.DecoderVisitor;
import com.brightsparklabs.asanti.decoder.builtin.BuiltinTypeDecoder;
import com.brightsparklabs.asanti.exception.DecodeException;
import com.brightsparklabs.asanti.model.schema.*;
import com.brightsparklabs.asanti.model.schema.type.AsnSchemaType;
import com.brightsparklabs.asanti.model.schema.type.AsnSchemaTypePrimitiveAliased;
import com.brightsparklabs.asanti.reader.AsnBerDataReader;
import com.brightsparklabs.asanti.schema.AsnPrimitiveType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteSource;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implementation of {@link AsantiAsnData}
 *
 * @author brightSPARK Labs
 */
public class AsantiAsnDataImpl implements AsantiAsnData {

    // -------------------------------------------------------------------------
    // CLASS VARIABLES
    // -------------------------------------------------------------------------

    /** class logger */
    private static final Logger logger = LoggerFactory.getLogger(AsantiAsnDataImpl.class);

    // -------------------------------------------------------------------------
    // INSTANCE VARIABLES
    // -------------------------------------------------------------------------

    /** ASN data to decode */
    private final RawAsnData rawAsnData;

    /** all tags which could be decoded. Map is of form: { decodedTagString => decodedTag } */
    private final ImmutableMap decodedTags;

    /** all tags which could not be decoded. Map is of form: { decodedTagString => decodedTag } */
    private final ImmutableMap unmappedTags;

    /**
     * all tags (decoded and unmapped) found in the data. Map is of form: { decodedTagString =>
     * decodedTag }
     */
    private final ImmutableMap allTags;

    /** visitor used to determine which decoder to use for decoding data */
    private final DecoderVisitor decoderVisitor = new DecoderVisitor();

    /** the schema used to decode */
    private final AsnSchema asnSchema;

    // -------------------------------------------------------------------------
    // CONSTRUCTION
    // -------------------------------------------------------------------------

    /**
     * Default constructor
     *
     * @param rawAsnData data to decode
     * @param asnSchema schema to use to decode data
     * @param topLevelTypeName the name of the top level type in this module from which to begin
     *     decoding the raw tag
     * @throws NullPointerException if any of the parameters are {@code null}
     * @throws IllegalArgumentException if topLevelTypeName is blank
     */
    public AsantiAsnDataImpl(
            final RawAsnData rawAsnData, final AsnSchema asnSchema, final String topLevelTypeName) {
        checkNotNull(asnSchema);
        checkNotNull(topLevelTypeName);
        checkArgument(!topLevelTypeName.trim().isEmpty(), "Top level type name must be specified");

        // The RawAsnData is where we get the data (byte array) associated with a raw tag.
        // Since INS-434 we are supporting "CONTAINS" constraints, that appear in the schema as
        // an octet string, but we should treat as an aliased type.
        // Our current mechanism for handling this is to extract the bytes of the octet string
        // and parse it with our BER/DER parser.  This then produces a new RawAsnData that we
        // slot in to the appropriate spot in the "tree".  We then perform the normal mapping
        // of raw tags to the schema to produce decoded tags.
        final Map rawAsnDataBuilder = Maps.newLinkedHashMap();
        rawAsnDataBuilder.putAll(rawAsnData.getBytes());

        final Optional rootType = asnSchema.getType(topLevelTypeName);
        if (rootType.isEmpty()) {
            throw new RuntimeException("type [" + topLevelTypeName + "] does not exist in schema");
        }

        final String decodedTagRootPrefix = "/" + topLevelTypeName;
        final String rawTagRootPrefix = "";

        // decode the tags in the data, use LinkedHashMap to preserve insertion order
        final Map decodedToRawTags = Maps.newLinkedHashMap();
        final Map unmappedTags = Maps.newLinkedHashMap();

        // Decode (match up raw tags to schema), in a way that may need to recurse if we encounter
        // "aliased" types, eg OCTET STRING (CONTAINS otherType)
        recursiveDecode(
                rawAsnData,
                rootType.get(),
                decodedTagRootPrefix,
                rawTagRootPrefix,
                rawAsnDataBuilder,
                decodedToRawTags,
                unmappedTags);

        this.rawAsnData = new RawAsnDataImpl(rawAsnDataBuilder);

        this.asnSchema = asnSchema;
        this.decodedTags = ImmutableMap.copyOf(decodedToRawTags);
        this.unmappedTags = ImmutableMap.copyOf(unmappedTags);
        this.allTags =
                ImmutableMap.builder()
                        .putAll(decodedToRawTags)
                        .putAll(unmappedTags)
                        .build();
    }

    // -------------------------------------------------------------------------
    // IMPLEMENTATION: AsnData
    // -------------------------------------------------------------------------

    @Override
    public Optional getPrimitiveType(final String tag) {
        return getType(tag).map(AsnSchemaType::getPrimitiveType);
    }

    @Override
    public ImmutableSet getTags() {
        return ImmutableSet.copyOf(decodedTags.keySet());
    }

    @Override
    public ImmutableSet getTagsMatching(final Pattern regex) {
        if (regex == null) {
            return ImmutableSet.of();
        }

        return allTags.keySet().stream()
                .filter(tag -> regex.matcher(tag).matches())
                .collect(ImmutableSet.toImmutableSet());
    }

    @Override
    public ImmutableSet getUnmappedTags() {
        return ImmutableSet.copyOf(unmappedTags.keySet());
    }

    @Override
    public boolean contains(final String tag) {
        return allTags.containsKey(tag);
    }

    @Override
    public boolean contains(final Pattern regex) {
        if (regex == null) {
            return false;
        }

        for (final String tag : allTags.keySet()) {
            if (regex.matcher(tag).matches()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Optional getBytes(final String tag) {
        final DecodedTag decodedTag = allTags.get(tag);
        // if no decoded tag, assume supplied tag is is already raw tag
        final String rawTag = (decodedTag == null) ? tag : decodedTag.getRawTag();
        return rawAsnData.getBytes(rawTag);
    }

    @Override
    public ImmutableMap getBytesMatching(final Pattern regex) {
        final Map result = Maps.newHashMap();
        for (final String tag : getTagsMatching(regex)) {
            getBytes(tag).ifPresent(b -> result.put(tag, b));
        }

        // check against the raw tags too
        result.putAll(rawAsnData.getBytesMatching(regex));

        return ImmutableMap.copyOf(result);
    }

    @Override
    public Optional getHexString(final String tag) {
        return getBytes(tag).map(bytes -> BaseEncoding.base16().encode(bytes));
    }

    @Override
    public ImmutableMap getHexStringsMatching(final Pattern regex) {
        final Map result = Maps.newHashMap();
        for (final String tag : getTagsMatching(regex)) {
            getHexString(tag).ifPresent(h -> result.put(tag, h));
        }

        // Add any matched to raw tags
        final Map raw = rawAsnData.getBytesMatching(regex);
        for (Map.Entry entry : raw.entrySet()) {
            final String hexString = BaseEncoding.base16().encode(entry.getValue());
            result.put(entry.getKey(), hexString);
        }

        return ImmutableMap.copyOf(result);
    }

    @Override
    public Optional getPrintableString(final String tag) throws DecodeException {
        final DecodedTag decodedTag = decodedTags.get(tag);
        if (decodedTag == null) {
            return Optional.empty();
        }

        final AsnSchemaType schemaType = decodedTag.getType();
        final AsnPrimitiveType type = schemaType.getPrimitiveType();
        final BuiltinTypeDecoder decoder = (BuiltinTypeDecoder) type.accept(decoderVisitor);
        final String result = decoder.decodeAsString(tag, this);
        return Optional.of(result);
    }

    @Override
    public ImmutableMap getPrintableStringsMatching(final Pattern regex)
            throws DecodeException {
        final Map result = Maps.newHashMap();
        for (final String tag : getTagsMatching(regex)) {
            getPrintableString(tag).ifPresent(p -> result.put(tag, p));
        }

        return ImmutableMap.copyOf(result);
    }

    @Override
    public  Optional getDecodedObject(final String tag, final Class classOfT)
            throws DecodeException, ClassCastException {
        final DecodedTag decodedTag = decodedTags.get(tag);
        if (decodedTag == null) {
            return Optional.empty();
        }

        final AsnSchemaType schemaType = decodedTag.getType();
        final AsnPrimitiveType type = schemaType.getPrimitiveType();
        final BuiltinTypeDecoder decoder = (BuiltinTypeDecoder) type.accept(decoderVisitor);
        // this should throw a ClassCastException if it the types don't match.
        final T result = classOfT.cast(decoder.decode(tag, this));
        return Optional.of(result);
    }

    @Override
    public ImmutableMap getDecodedObjectsMatching(final Pattern regex)
            throws DecodeException {
        final Map result = Maps.newHashMap();
        for (final String tag : getTagsMatching(regex)) {
            getDecodedObject(tag, Object.class).ifPresent(o -> result.put(tag, o));
        }

        return ImmutableMap.copyOf(result);
    }

    @Override
    public Optional getType(final String tag) {
        return asnSchema.getType(tag);
    }

    // -------------------------------------------------------------------------
    // PRIVATE
    // -------------------------------------------------------------------------

    /**
     * The process of Decoding is matching the raw data provided with the schema provided. This may
     * need to be recursive in the case of some schema constructs, eg:
     *
     * 

OCTET STRING (CONTAINS otherType) * * @param rawAsnData the raw data from (BER) parsing of the binary that this will decode * @param rootType the type from the schema that the rawAsnData should align to * @param decodedPrefix any prefix that should be applied to decoded tags to "fully qualify" * them * @param rawPrefix any prefix that should be applied to raw tags to "fully qualify" them * @param rawAsnDataBuilder [OUTPUT] the existing mapping of raw tag to bytes, that will be * appended to * @param decodedToRawTags [OUTPUT] the existing mapping of decoded to raw tags, that will be * appended to * @param unmappedTags [OUTPUT] the existing mapping of unmapped tags, that will be appended to */ private void recursiveDecode( final RawAsnData rawAsnData, final AsnSchemaType rootType, final String decodedPrefix, final String rawPrefix, final Map rawAsnDataBuilder, final Map decodedToRawTags, final Map unmappedTags) { // TODO - passing in a few parameters that we modify, specifically // rawAsnDataBuilder, decodedToRawTags and unmappedTags final ImmutableSet> results = Decoder.getDecodedTags(rawAsnData.getRawTags(), rootType); for (final OperationResult decodeResult : results) { final DecodedTag decodedTag = decodeResult.getOutput(); // We may be decoding at the "root" or somewhere part way through the schema // hierarchy, so we need to be able to establish the fully qualified parth // for both decoded and raw tags. final DecodedTag fullyQualifiedTag = new DecodedTag( decodedPrefix + "/" + decodedTag.getTag(), rawPrefix + decodedTag.getRawTag(), decodedTag.getType(), decodedTag.isFullyDecoded()); if (decodeResult.wasSuccessful()) { final AsnSchemaType type = decodeResult.getOutput().getType(); if (type instanceof AsnSchemaTypePrimitiveAliased) { final Optional bytes = rawAsnData.getBytes(fullyQualifiedTag.getRawTag()); decodeAliased( bytes.orElse(new byte[0]), fullyQualifiedTag, rawAsnDataBuilder, decodedToRawTags, unmappedTags); } // TODO INS-434: we can decide if this should be "hidden" if decodeAliased // was called and successful. decodedToRawTags.put(fullyQualifiedTag.getTag(), fullyQualifiedTag); } else { // could not decode tag unmappedTags.put(fullyQualifiedTag.getTag(), fullyQualifiedTag); } } } /** * Takes the bytes from an OCTET STRING that is an alias for another type and parses them and * then decodes. * * @param bytes the bytes from the OCTET STRING * @param parentTag the tag that the bytes came from * @param rawAsnDataBuilder [OUTPUT] add the newly parsed data to this * @param decodedToRawTags [OUTPUT] add to this with new decode mappings * @param unmappedTags [OUTPUT] add to this with new unmapped tags */ private void decodeAliased( final byte[] bytes, final DecodedTag parentTag, final Map rawAsnDataBuilder, final Map decodedToRawTags, final Map unmappedTags) { // Now attempt to re-parse the bytes, and realign with the type... final ByteSource byteSource = ByteSource.wrap(bytes); try { final ImmutableList readPdus = AsnBerDataReader.read(byteSource); // TODO INS-434: should we ever expect anything other than 1 PDU from this??? // what if the CONTAINS is a collection? if (readPdus.isEmpty()) { throw new DecodeException( "No pdus found when parsing aliased bytes from " + parentTag.getTag()); } for (final RawAsnData rawAsnData : readPdus) { // Tack all these onto this raw tag. final ImmutableMap bytesMatching = rawAsnData.getBytes(); final String baseTag = parentTag.getRawTag(); for (final Map.Entry e : bytesMatching.entrySet()) { final String fullQualifiedTag = baseTag + e.getKey(); rawAsnDataBuilder.put(fullQualifiedTag, e.getValue()); } recursiveDecode( rawAsnData, parentTag.getType(), parentTag.getTag(), parentTag.getRawTag(), rawAsnDataBuilder, decodedToRawTags, unmappedTags); } } catch (Exception e) { // If we had issues processing the bytes and aligning it to the aliased type // then we should attempt to deal with that as a validation issue as opposed // to just throwing // The AsnSchemaContainsConstraint will also attempt to parse the bytes, so should // create a validation failure for issues with that. logger.error("Exception while processing aliased type at {}", parentTag.getTag(), e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy