Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f430140f authored by Yan Han's avatar Yan Han
Browse files

Validate all messages on construction

Refactors message validation in preparation for letting messages have
their own class:
- HdmiCecMessage is now built using a static factory method, which
validates the message and stores the result in an instance variable.
- HdmiCecMessageValidator is now static to remove its dependence on
HdmiControlService and allow HdmiCecMessage construction to use it.
- To achieve this, validation steps that are dependent on a
HdmiControlService instance are now done in HdmiControlService instead.
These are really verification steps instead of validation steps, as they
depend on the state a specific CEC network - for example, they include
checks for whether a physical address has a valid port on a local TV
device.

Change-Id: I5e8c0bbeb7069e3b5cc797d6fe98470ecddda438
Bug: 207359637
Test: atest com.android.server.hdmi
parent abbbf2ae
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import libcore.util.EmptyArray;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@@ -694,7 +695,19 @@ final class HdmiCecController {
    @ServiceThreadOnly
    private void handleIncomingCecCommand(int srcAddress, int dstAddress, byte[] body) {
        assertRunOnServiceThread();
        HdmiCecMessage command = HdmiCecMessageBuilder.of(srcAddress, dstAddress, body);

        if (body.length == 0) {
            Slog.e(TAG, "Message with empty body received.");
            return;
        }

        HdmiCecMessage command = HdmiCecMessage.build(srcAddress, dstAddress, body[0],
                Arrays.copyOfRange(body, 1, body.length));

        if (command.getValidationResult() != HdmiCecMessageValidator.OK) {
            Slog.e(TAG, "Invalid message received: " + command);
        }

        HdmiLogger.debug("[R]:" + command);
        addCecMessageToHistory(true /* isReceived */, command);

+71 −10
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.hdmi;

import static com.android.server.hdmi.HdmiCecMessageValidator.ValidationResult;

import android.annotation.Nullable;

import com.android.server.hdmi.Constants.FeatureOpcode;
@@ -26,11 +28,13 @@ import java.util.Arrays;
import java.util.Objects;

/**
 * A class to encapsulate HDMI-CEC message used for the devices connected via
 * HDMI cable to communicate with one another. A message is defined by its
 * source and destination address, command (or opcode), and optional parameters.
 * Encapsulates the data that defines an HDMI-CEC message: source and destination address,
 * command (or opcode), and optional parameters. Also stores the result of validating the message.
 *
 * Subclasses of this class represent specific messages that have been validated, and expose their
 * parsed parameters.
 */
public final class HdmiCecMessage {
public class HdmiCecMessage {
    public static final byte[] EMPTY_PARAM = EmptyArray.BYTE;

    private final int mSource;
@@ -39,24 +43,53 @@ public final class HdmiCecMessage {
    private final int mOpcode;
    private final byte[] mParams;

    private final int mValidationResult;

    /**
     * Constructor.
     * Constructor that allows the caller to provide the validation result.
     * Must only be called by subclasses; other callers should use {@link #build}.
     */
    public HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
    protected HdmiCecMessage(int source, int destination, int opcode, byte[] params,
            @ValidationResult int validationResult) {
        mSource = source;
        mDestination = destination;
        mOpcode = opcode & 0xFF;
        mParams = Arrays.copyOf(params, params.length);
        mValidationResult = validationResult;
    }

    private HdmiCecMessage(int source, int destination, int opcode, byte[] params) {
        this(source, destination, opcode, params,
                HdmiCecMessageValidator.validate(source, destination, opcode & 0xFF, params));
    }

    /**
     * Constructs and validates a message. The result of validation will be accessible via
     * {@link #getValidationResult}.
     *
     * Intended for parsing incoming messages, as it takes raw bytes as message parameters.
     *
     * If the opcode has its own subclass of this one, this method will instead validate and build
     * the message using the logic in that class. If successful, it will return a validated
     * instance of that class that exposes parsed parameters.
     */
    static HdmiCecMessage build(int source, int destination, int opcode, byte[] params) {
        return new HdmiCecMessage(source, destination, opcode, params);
    }

    static HdmiCecMessage build(int source, int destination, int opcode) {
        return new HdmiCecMessage(source, destination, opcode, EMPTY_PARAM);
    }

    @Override
    public boolean equals(@Nullable Object message) {
        if (message instanceof HdmiCecMessage) {
            HdmiCecMessage that = (HdmiCecMessage) message;
            return this.mSource == that.getSource() &&
                this.mDestination == that.getDestination() &&
                this.mOpcode == that.getOpcode() &&
                Arrays.equals(this.mParams, that.getParams());
            return this.mSource == that.getSource()
                    && this.mDestination == that.getDestination()
                    && this.mOpcode == that.getOpcode()
                    && Arrays.equals(this.mParams, that.getParams())
                    && this.mValidationResult == that.getValidationResult();
        }
        return false;
    }
@@ -111,6 +144,13 @@ public final class HdmiCecMessage {
        return mParams;
    }

    /**
     * Returns the validation result of the message.
     */
    public int getValidationResult() {
        return mValidationResult;
    }

    @Override
    public String toString() {
        StringBuilder s = new StringBuilder();
@@ -129,9 +169,30 @@ public final class HdmiCecMessage {
                }
            }
        }
        if (mValidationResult != HdmiCecMessageValidator.OK) {
            s.append(String.format(" <Validation error: %s>",
                    validationResultToString(mValidationResult)));
        }
        return s.toString();
    }

    private static String validationResultToString(@ValidationResult int validationResult) {
        switch (validationResult) {
            case HdmiCecMessageValidator.OK:
                return "ok";
            case HdmiCecMessageValidator.ERROR_SOURCE:
                return "invalid source";
            case HdmiCecMessageValidator.ERROR_DESTINATION:
                return "invalid destination";
            case HdmiCecMessageValidator.ERROR_PARAMETER:
                return "invalid parameters";
            case HdmiCecMessageValidator.ERROR_PARAMETER_SHORT:
                return "short parameters";
            default:
                return "unknown error";
        }
    }

    private static String opcodeToString(@FeatureOpcode int opcode) {
        switch (opcode) {
            case Constants.MESSAGE_FEATURE_ABORT:
+56 −88

File changed.

Preview size limit exceeded, changes collapsed.

+106 −105

File changed.

Preview size limit exceeded, changes collapsed.

+88 −18

File changed.

Preview size limit exceeded, changes collapsed.

Loading