ronnie
2022-10-23 d7a691c7a2527f2da145355a40a0402c95c67aac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package com.android.server.hdmi;
 
import android.hardware.hdmi.HdmiDeviceInfo;
import android.util.SparseArray;
 
/**
 * A helper class to validates {@link HdmiCecMessage}.
 */
public class HdmiCecMessageValidator {
    private static final String TAG = "HdmiCecMessageValidator";
 
    static final int OK = 0;
    static final int ERROR_SOURCE = 1;
    static final int ERROR_DESTINATION = 2;
    static final int ERROR_PARAMETER = 3;
    static final int ERROR_PARAMETER_SHORT = 4;
 
    private final HdmiControlService mService;
 
    interface ParameterValidator {
        /**
         * @return errorCode errorCode can be {@link #OK}, {@link #ERROR_PARAMETER} or
         *         {@link #ERROR_PARAMETER_SHORT}.
         */
        int isValid(byte[] params);
    }
 
    // Only the direct addressing is allowed.
    private static final int DEST_DIRECT = 1 << 0;
    // Only the broadcast addressing is allowed.
    private static final int DEST_BROADCAST = 1 << 1;
    // Both the direct and the broadcast addressing are allowed.
    private static final int DEST_ALL = DEST_DIRECT | DEST_BROADCAST;
    // True if the messages from address 15 (unregistered) are allowed.
    private static final int SRC_UNREGISTERED = 1 << 2;
 
    private static class ValidationInfo {
        public final ParameterValidator parameterValidator;
        public final int addressType;
 
        public ValidationInfo(ParameterValidator validator, int type) {
            parameterValidator = validator;
            addressType = type;
        }
    }
 
    final SparseArray<ValidationInfo> mValidationInfo = new SparseArray<>();
 
    public HdmiCecMessageValidator(HdmiControlService service) {
        mService = service;
 
        // Messages related to the physical address.
        PhysicalAddressValidator physicalAddressValidator = new PhysicalAddressValidator();
        addValidationInfo(Constants.MESSAGE_ACTIVE_SOURCE,
                physicalAddressValidator, DEST_BROADCAST | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_INACTIVE_SOURCE, physicalAddressValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS,
                new ReportPhysicalAddressValidator(), DEST_BROADCAST | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_ROUTING_CHANGE,
                new RoutingChangeValidator(), DEST_BROADCAST | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_ROUTING_INFORMATION,
                physicalAddressValidator, DEST_BROADCAST | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_SET_STREAM_PATH,
                physicalAddressValidator, DEST_BROADCAST);
        addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST,
                new SystemAudioModeRequestValidator(), DEST_DIRECT);
 
        // Messages have no parameter.
        FixedLengthValidator noneValidator = new FixedLengthValidator(0);
        addValidationInfo(Constants.MESSAGE_ABORT, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_GET_CEC_VERSION, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_GET_MENU_LANGUAGE,
                noneValidator, DEST_DIRECT | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_GIVE_AUDIO_STATUS, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID,
                noneValidator, DEST_DIRECT | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_GIVE_OSD_NAME, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS,
                noneValidator, DEST_DIRECT | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS,
                noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_IMAGE_VIEW_ON, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_INITIATE_ARC, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_RECORD_OFF, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_RECORD_TV_SCREEN, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REPORT_ARC_INITIATED, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REPORT_ARC_TERMINATED, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REQUEST_ARC_INITIATION, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REQUEST_ARC_TERMINATION, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REQUEST_ACTIVE_SOURCE,
                noneValidator, DEST_BROADCAST | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_STANDBY, noneValidator, DEST_ALL | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_TERMINATE_ARC, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_TEXT_VIEW_ON, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_TUNER_STEP_DECREMENT, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_TUNER_STEP_INCREMENT, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_USER_CONTROL_RELEASED, noneValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_VENDOR_REMOTE_BUTTON_UP, noneValidator, DEST_ALL);
 
        // TODO: Validate more than length for the following messages.
 
        // Messages for the One Touch Record.
        FixedLengthValidator oneByteValidator = new FixedLengthValidator(1);
        addValidationInfo(Constants.MESSAGE_RECORD_ON,
                new VariableLengthValidator(1, 8), DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_RECORD_STATUS, oneByteValidator, DEST_DIRECT);
 
        // TODO: Handle messages for the Timer Programming.
 
        // Messages for the System Information.
        addValidationInfo(Constants.MESSAGE_CEC_VERSION, oneByteValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
                new FixedLengthValidator(3), DEST_BROADCAST);
 
        // TODO: Handle messages for the Deck Control.
 
        // TODO: Handle messages for the Tuner Control.
 
        // Messages for the Vendor Specific Commands.
        VariableLengthValidator maxLengthValidator = new VariableLengthValidator(0, 14);
        addValidationInfo(Constants.MESSAGE_DEVICE_VENDOR_ID,
                new FixedLengthValidator(3), DEST_BROADCAST);
        // Allow unregistered source for all vendor specific commands, because we don't know
        // how to use the commands at this moment.
        addValidationInfo(Constants.MESSAGE_VENDOR_COMMAND,
                new VariableLengthValidator(1, 14), DEST_DIRECT | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_VENDOR_COMMAND_WITH_ID,
                new VariableLengthValidator(4, 14), DEST_ALL | SRC_UNREGISTERED);
        addValidationInfo(Constants.MESSAGE_VENDOR_REMOTE_BUTTON_DOWN,
                maxLengthValidator, DEST_ALL | SRC_UNREGISTERED);
 
        // Messages for the OSD.
        addValidationInfo(Constants.MESSAGE_SET_OSD_STRING, maxLengthValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_SET_OSD_NAME, maxLengthValidator, DEST_DIRECT);
 
        // Messages for the Device Menu Control.
        addValidationInfo(Constants.MESSAGE_MENU_REQUEST, oneByteValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_MENU_STATUS, oneByteValidator, DEST_DIRECT);
 
        // Messages for the Remote Control Passthrough.
        // TODO: Parse the first parameter and determine if it can have the next parameter.
        addValidationInfo(Constants.MESSAGE_USER_CONTROL_PRESSED,
                new VariableLengthValidator(1, 2), DEST_DIRECT);
 
        // Messages for the Power Status.
        addValidationInfo(Constants.MESSAGE_REPORT_POWER_STATUS, oneByteValidator, DEST_DIRECT);
 
        // Messages for the General Protocol.
        addValidationInfo(Constants.MESSAGE_FEATURE_ABORT,
                new FixedLengthValidator(2), DEST_DIRECT);
 
        // Messages for the System Audio Control.
        addValidationInfo(Constants.MESSAGE_REPORT_AUDIO_STATUS, oneByteValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR,
                new FixedLengthValidator(3), DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR,
                oneByteValidator, DEST_DIRECT);
        addValidationInfo(Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, oneByteValidator, DEST_ALL);
        addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
                oneByteValidator, DEST_DIRECT);
 
        // Messages for the Audio Rate Control.
        addValidationInfo(Constants.MESSAGE_SET_AUDIO_RATE, oneByteValidator, DEST_DIRECT);
 
        // All Messages for the ARC have no parameters.
 
        // Messages for the Capability Discovery and Control.
        addValidationInfo(Constants.MESSAGE_CDC_MESSAGE, maxLengthValidator,
                DEST_BROADCAST | SRC_UNREGISTERED);
    }
 
    private void addValidationInfo(int opcode, ParameterValidator validator, int addrType) {
        mValidationInfo.append(opcode, new ValidationInfo(validator, addrType));
    }
 
    int isValid(HdmiCecMessage message) {
        int opcode = message.getOpcode();
        ValidationInfo info = mValidationInfo.get(opcode);
        if (info == null) {
            HdmiLogger.warning("No validation information for the message: " + message);
            return OK;
        }
 
        // Check the source field.
        if (message.getSource() == Constants.ADDR_UNREGISTERED &&
                (info.addressType & SRC_UNREGISTERED) == 0) {
            HdmiLogger.warning("Unexpected source: " + message);
            return ERROR_SOURCE;
        }
        // Check the destination field.
        if (message.getDestination() == Constants.ADDR_BROADCAST) {
            if ((info.addressType & DEST_BROADCAST) == 0) {
                HdmiLogger.warning("Unexpected broadcast message: " + message);
                return ERROR_DESTINATION;
            }
        } else {  // Direct addressing.
            if ((info.addressType & DEST_DIRECT) == 0) {
                HdmiLogger.warning("Unexpected direct message: " + message);
                return ERROR_DESTINATION;
            }
        }
 
        // Check the parameter type.
        int errorCode = info.parameterValidator.isValid(message.getParams());
        if (errorCode != OK) {
            HdmiLogger.warning("Unexpected parameters: " + message);
            return errorCode;
        }
        return OK;
    }
 
    private static class FixedLengthValidator implements ParameterValidator {
        private final int mLength;
 
        public FixedLengthValidator(int length) {
            mLength = length;
        }
 
        @Override
        public int isValid(byte[] params) {
            // If the length is longer than expected, we assume it's OK since the parameter can be
            // extended in the future version.
            return params.length < mLength ? ERROR_PARAMETER_SHORT : OK;
        }
    }
 
    private static class VariableLengthValidator implements ParameterValidator {
        private final int mMinLength;
        private final int mMaxLength;
 
        public VariableLengthValidator(int minLength, int maxLength) {
            mMinLength = minLength;
            mMaxLength = maxLength;
        }
 
        @Override
        public int isValid(byte[] params) {
            return params.length < mMinLength ? ERROR_PARAMETER_SHORT : OK;
        }
    }
 
    private boolean isValidPhysicalAddress(byte[] params, int offset) {
        // TODO: Add more logic like validating 1.0.1.0.
 
        if (!mService.isTvDevice()) {
            // If the device is not TV, we can't convert path to port-id, so stop here.
            return true;
        }
        int path = HdmiUtils.twoBytesToInt(params, offset);
        if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == mService.getPhysicalAddress()) {
            return true;
        }
        int portId = mService.pathToPortId(path);
        if (portId == Constants.INVALID_PORT_ID) {
            return false;
        }
        return true;
    }
 
    /**
     * Check if the given type is valid. A valid type is one of the actual logical device types
     * defined in the standard ({@link HdmiDeviceInfo#DEVICE_TV},
     * {@link HdmiDeviceInfo#DEVICE_PLAYBACK}, {@link HdmiDeviceInfo#DEVICE_TUNER},
     * {@link HdmiDeviceInfo#DEVICE_RECORDER}, and {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}).
     *
     * @param type device type
     * @return true if the given type is valid
     */
    static boolean isValidType(int type) {
        return (HdmiDeviceInfo.DEVICE_TV <= type
                && type <= HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)
                && type != HdmiDeviceInfo.DEVICE_RESERVED;
    }
 
    private static int toErrorCode(boolean success) {
        return success ? OK : ERROR_PARAMETER;
    }
 
    private class PhysicalAddressValidator implements ParameterValidator {
        @Override
        public int isValid(byte[] params) {
            if (params.length < 2) {
                return ERROR_PARAMETER_SHORT;
            }
            return toErrorCode(isValidPhysicalAddress(params, 0));
        }
    }
 
    private class SystemAudioModeRequestValidator extends PhysicalAddressValidator {
        @Override
        public int isValid(byte[] params) {
            // TV can send <System Audio Mode Request> with no parameters to terminate system audio.
            if (params.length == 0) {
                return OK;
            }
            return super.isValid(params);
        }
    }
 
    private class ReportPhysicalAddressValidator implements ParameterValidator {
        @Override
        public int isValid(byte[] params) {
            if (params.length < 3) {
                return ERROR_PARAMETER_SHORT;
            }
            return toErrorCode(isValidPhysicalAddress(params, 0) && isValidType(params[2]));
        }
    }
 
    private class RoutingChangeValidator implements ParameterValidator {
        @Override
        public int isValid(byte[] params) {
            if (params.length < 4) {
                return ERROR_PARAMETER_SHORT;
            }
            return toErrorCode(
                    isValidPhysicalAddress(params, 0) && isValidPhysicalAddress(params, 2));
        }
    }
}