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

com.highmobility.autoapi.Command Maven / Gradle / Ivy

/*
 * The MIT License
 *
 * Copyright (c) 2014- High-Mobility GmbH (https://high-mobility.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.highmobility.autoapi;

import com.highmobility.autoapi.property.Property;
import com.highmobility.autoapi.value.Brand;
import com.highmobility.value.Bytes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;

import javax.annotation.Nullable;

import static com.highmobility.autoapi.AutoApiLogger.getLogger;

/**
 * Used for commands with properties. Can have 0 properties.
 */
public class Command extends Bytes {
    /*
    Properties logic:
    * Empty properties are created as fields in subclasses with correct identifier but 0x00 bytes
      and no components.

    * Empty property will be updated with a base property that has the components parsed. If
      the subclass knows the identifier, it copies the components and replaces the base property
      with itself.
     */
    private static final String INVALID_VERSION_EXCEPTION = "Invalid AutoAPI version. This " +
            "package supports level %d.";

    public static final byte NONCE_IDENTIFIER = (byte) 0xA0;
    public static final byte SIGNATURE_IDENTIFIER = (byte) 0xA1;
    public static final byte TIMESTAMP_IDENTIFIER = (byte) 0xA2;
    public static final byte VIN_IDENTIFIER = (byte) 0xA3;
    public static final byte BRAND_IDENTIFIER = (byte) 0xA4;

    static final byte AUTO_API_VERSION = 0x0C;
    static final int HEADER_LENGTH = 1;
    static final int COMMAND_TYPE_POSITION = HEADER_LENGTH + 2;

    int type;
    int identifier;
    int autoApiVersion;

    Property[] properties;
    Bytes nonce;
    Bytes signature;
    Calendar timestamp;
    String vin;
    Brand brand;

    /**
     * @return The Auto API version of the command.
     */
    public int getAutoApiVersion() {
        return autoApiVersion;
    }

    /**
     * @return The identifier of the command.
     */
    public int getIdentifier() {
        return identifier;
    }

    /**
     * @return The type of the command.
     * @see Type
     */
    public int getCommandType() {
        return type;
    }

    /**
     * @return The nonce for the signature.
     */
    @Nullable public Bytes getNonce() {
        return nonce;
    }

    /**
     * @return The signature for the signed bytes(the whole command except the signature property).
     */
    @Nullable public Bytes getSignature() {
        return signature;
    }

    /**
     * @return Timestamp of when the data was transmitted from the car.
     */
    @Nullable public Calendar getTimestamp() {
        return timestamp;
    }

    /**
     * @return The unique Vehicle Identification Number
     */
    @Nullable public String getVin() {
        return vin;
    }

    /**
     * @return The vehicle brand
     */
    @Nullable public Brand getBrand() {
        return brand;
    }

    /**
     * @return The bytes that are signed with the signature
     */
    public Bytes getSignedBytes() {
        return new Bytes(Arrays.copyOfRange(bytes, 0, bytes.length - 64 - 3 - 3));
    }

    /**
     * @return All of the properties with raw values
     */
    public Property[] getProperties() {
        return properties;
    }

    public Property getProperty(byte identifier) {
        for (int i = 0; i < properties.length; i++) {
            Property prop = properties[i];
            if (prop.getPropertyIdentifier() == identifier) return prop;
        }

        return null;
    }

    protected Command() {
        super();
        this.autoApiVersion = AUTO_API_VERSION;
    }

    // only called from CommandResolver
    Command(byte[] bytes) {
        super(bytes);

        if (bytes[0] != AUTO_API_VERSION)
            getLogger().error(String.format(INVALID_VERSION_EXCEPTION, (int) AUTO_API_VERSION));

        setTypeAndBytes(bytes);

        ArrayList builder = new ArrayList<>();
        PropertyEnumeration enumeration = new PropertyEnumeration(this.bytes);

        // create the base properties
        while (enumeration.hasMoreElements()) {
            PropertyEnumeration.EnumeratedProperty propertyEnumeration = enumeration.nextElement();
            if (propertyEnumeration.isValid(bytes.length)) {
                Property property = new Property(Arrays.copyOfRange(bytes, propertyEnumeration
                                .valueStart - 3,
                        propertyEnumeration.valueStart + propertyEnumeration.size));
                builder.add(property);
            }
        }

        findUniversalProperties(identifier, type, builder.toArray(new Property[0]));
    }

    Command(Integer identifier, Integer type, Property[] properties) {
        this.autoApiVersion = AUTO_API_VERSION;
        this.type = type;
        this.identifier = identifier;
        // here there are no timestamps. This constructor is called from setter commands only.
        findUniversalProperties(identifier, type, properties, true);
    }

    /**
     * @param sizeAfterType The command size after the header and command type.
     */
    Command(Integer identifier, int type, int sizeAfterType) {
        this.autoApiVersion = AUTO_API_VERSION;
        this.type = type;
        this.identifier = identifier;
        createBytes(sizeAfterType);
    }

    private void setTypeAndBytes(byte[] bytes) {
        byte versionByte = 0, firstByte = 0, secondByte = 0, thirdByte = 0;

        if (bytes != null) {
            if (bytes.length > 0) versionByte = bytes[0];
            if (bytes.length > 1) firstByte = bytes[1];
            if (bytes.length > 2) secondByte = bytes[2];
            if (bytes.length > 3) thirdByte = bytes[3];
        }

        identifier = Identifier.fromBytes(firstByte, secondByte);
        type = Type.fromByte(thirdByte);
        autoApiVersion = versionByte;
    }

    protected void findUniversalProperties(Integer identifier, Integer type, Property[] properties) {
        findUniversalProperties(identifier, type, properties, false);
    }

    protected void findUniversalProperties(Integer identifier, Integer type, Property[] properties,
                                           boolean createBytes) {
        this.properties = properties;

        // if from builder, bytes need to be built
        int propertyPosition = COMMAND_TYPE_POSITION + 1;
        if (createBytes) createBytes(getPropertiesSize(properties));

        for (int i = 0; i < properties.length; i++) {
            try {
                Property property = properties[i];

                if (createBytes) {
                    set(propertyPosition, property);
                    propertyPosition += property.size();
                }

                switch (property.getPropertyIdentifier()) {
                    case NONCE_IDENTIFIER: {
                        if (property.getValueComponent().getValueBytes().getLength() != 9)
                            continue; // invalid nonce length, just ignore
                        nonce = property.getValueComponent().getValueBytes();
                    }
                    case SIGNATURE_IDENTIFIER: {
                        if (property.getValueComponent().getValueBytes().getLength() != 64)
                            continue; // ignore invalid length
                        signature = property.getValueComponent().getValueBytes();
                    }
                    case TIMESTAMP_IDENTIFIER: {
                        timestamp = Property.getCalendar(property.getValueComponent().getValueBytes());
                    }
                    case VIN_IDENTIFIER: {
                        vin = Property.getString(property.getValueComponent().getValueBytes());
                    }
                    case BRAND_IDENTIFIER: {
                        brand = Brand.Companion.fromInt(property.getValueComponent().getValueByte());
                    }
                }
            } catch (Exception e) {
                // ignore if some universal property had invalid data. just keep the base one.
            }
        }

        // iterator is used by subclass
        propertyIterator = new PropertyIterator();
    }


    private void createBytes(int sizeAfterType) {
        bytes = new byte[4 + sizeAfterType];
        set(0, AUTO_API_VERSION);
        set(1, Identifier.toBytes(identifier));
        set(3, Type.toByte(type));
    }

    private int getPropertiesSize(Property[] properties) {
        int size = 0;
        for (int i = 0; i < properties.length; i++) size += properties[i].size();
        return size;
    }

    // Used to catch the property parsing exception, managing parsed properties in this class.
    protected PropertyIterator propertyIterator;

    protected class PropertyIterator implements Iterator {
        private final int currentSize;
        private int propertiesReplaced = 0;

        PropertyIterator() {
            this.currentSize = Command.this.properties.length;
        }

        private int currentIndex = 0;

        @Override
        public boolean hasNext() {
            return currentIndex < currentSize && properties[currentIndex] != null;
        }

        @Override
        public Property next() {
            return properties[currentIndex++];
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }

        public void parseNext(PropertyIteration next) {
            Property nextProperty = next();

            try {
                Object parsedProperty = next.iterate(nextProperty);
                // failure and timestamp are separate properties and should be retained in base
                // properties array
                if (parsedProperty != null) {
                    // replace the base property with parsed one
                    properties[currentIndex - 1] = (Property) parsedProperty;
                    propertiesReplaced++;
                }
            } catch (Exception e) {
                nextProperty.printFailedToParse(e, null);
            }
        }
    }

    public interface PropertyIteration {
        /**
         * @param p The base property.
         * @return The parsed property or object, if matched to a field.
         * @throws Exception The property parse exception is caught internally. If a property
         *                   parsing fails, it is ignored and parsing continues with the next
         *                   property.
         */
        @Nullable
        Property iterate(@Nullable Property p) throws Exception;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy