1 /*
2  * Copyright (C) 2020 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.hdmicec.cts;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 
24 public class CecMessage {
25 
26     private static final int HEXADECIMAL_RADIX = 16;
27 
buildCecMessage( LogicalAddress source, LogicalAddress destination, CecOperand operand, int params)28     public static String buildCecMessage(
29             LogicalAddress source, LogicalAddress destination, CecOperand operand, int params) {
30         return "" + source + destination + ":" + operand + formatParams(params);
31     }
32 
formatParams(String rawParams)33     public static String formatParams(String rawParams) {
34         StringBuilder params = new StringBuilder("");
35         int position = 0;
36         int endPosition = 2;
37 
38         do {
39             params.append(":" + rawParams.substring(position, endPosition));
40             position = endPosition;
41             endPosition += 2;
42         } while (endPosition <= rawParams.length());
43         return params.toString();
44     }
45 
formatParams(long rawParam)46     public static String formatParams(long rawParam) {
47         StringBuilder params = new StringBuilder("");
48 
49         do {
50             params.insert(0, ":" + String.format("%02x", rawParam % 256));
51             rawParam >>= 8;
52         } while (rawParam > 0);
53 
54         return params.toString();
55     }
56 
57     /**
58      * Formats the rawParam into CEC message parameters. The parameters will be at least
59      * minimumNibbles long.
60      */
formatParams(long rawParam, int minimumNibbles)61     public static String formatParams(long rawParam, int minimumNibbles) {
62         StringBuilder params = new StringBuilder("");
63 
64         do {
65             params.insert(0, ":" + String.format("%02x", rawParam % 256));
66             rawParam >>= 8;
67             minimumNibbles -= 2;
68         } while (rawParam > 0 || minimumNibbles > 0);
69 
70         return params.toString();
71     }
72 
hexStringToInt(String message)73     public static int hexStringToInt(String message) {
74         return Integer.parseInt(message, HEXADECIMAL_RADIX);
75     }
76 
getAsciiString(String message)77     public static String getAsciiString(String message) {
78         String params = getNibbles(message).substring(4);
79         StringBuilder builder = new StringBuilder();
80 
81         for (int i = 2; i <= params.length(); i += 2) {
82             builder.append((char) hexStringToInt(params.substring(i - 2, i)));
83         }
84 
85         return builder.toString();
86     }
87 
getParamsAsString(String message)88     public static String getParamsAsString(String message) {
89         return getNibbles(message).substring(4);
90     }
91 
92     /** Gets the params from a CEC message. */
getParams(String message)93     public static int getParams(String message) {
94         return hexStringToInt(getNibbles(message).substring(4));
95     }
96 
97     /** Gets the first 'numNibbles' number of param nibbles from a CEC message. */
getParams(String message, int numNibbles)98     public static int getParams(String message, int numNibbles) {
99         int paramStart = 4;
100         int end = numNibbles + paramStart;
101         return hexStringToInt(getNibbles(message).substring(paramStart, end));
102     }
103 
104     /**
105      * From the params of a CEC message, gets the nibbles from position start to position end.
106      * The start and end are relative to the beginning of the params. For example, in the following
107      * message - 4F:82:10:00:04, getParams(message, 0, 4) will return 0x1000 and
108      * getParams(message, 4, 6) will return 0x04.
109      */
getParams(String message, int start, int end)110     public static int getParams(String message, int start, int end) {
111         return hexStringToInt(getNibbles(message).substring(4).substring(start, end));
112     }
113 
114     /**
115      * Gets the source logical address from a CEC message.
116      */
getSource(String message)117     public static LogicalAddress getSource(String message) {
118         String param = getNibbles(message).substring(0, 1);
119         return LogicalAddress.getLogicalAddress(hexStringToInt(param));
120     }
121 
122     /** Gets the destination logical address from a CEC message. */
getDestination(String message)123     public static LogicalAddress getDestination(String message) {
124         String param = getNibbles(message).substring(1, 2);
125         return LogicalAddress.getLogicalAddress(hexStringToInt(param));
126     }
127 
128     /** Gets the operand from a CEC message. */
getOperand(String message)129     public static CecOperand getOperand(String message) {
130         String param = getNibbles(message).substring(2, 4);
131         return CecOperand.getOperand(hexStringToInt(param));
132     }
133 
134     /**
135      * Converts ascii characters to hexadecimal numbers that can be appended to a CEC message as
136      * params. For example, "spa" will be converted to ":73:70:61"
137      */
convertStringToHexParams(String rawParams)138     public static String convertStringToHexParams(String rawParams) {
139         StringBuilder params = new StringBuilder("");
140         for (int i = 0; i < rawParams.length(); i++) {
141             params.append(String.format(":%02x", (int) rawParams.charAt(i)));
142         }
143         return params.toString();
144     }
145 
146     /**
147      *  Assert for the DUT's physical address with the value passed from command line argument.
148      *  Assert for the source's physical address in a <Routing Change> message.
149      */
assertPhysicalAddressValid(String message, int expectedPhysicalAddress)150     public static void assertPhysicalAddressValid(String message, int expectedPhysicalAddress) {
151         int physicalAddress = getParams(message, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
152         assertThat(physicalAddress).isEqualTo(expectedPhysicalAddress);
153     }
154 
155     /** Assert for the target's physical address in a <Routing Change> message. */
assertTargetPhysicalAddressValid(String message, int expectedTargetPhysicalAddress)156     public static void assertTargetPhysicalAddressValid(String message, int expectedTargetPhysicalAddress) {
157         int targetPhysicalAddress = getParams(message, 4, 8);
158         assertThat(targetPhysicalAddress).isEqualTo(expectedTargetPhysicalAddress);
159     }
160 
getNibbles(String message)161     private static String getNibbles(String message) {
162         final String tag1 = "group1";
163         final String tag2 = "group2";
164         String paramsPattern = "(?:.*[>>|<<].*?)" +
165                 "(?<" + tag1 + ">[\\p{XDigit}{2}:]+)" +
166                 "(?<" + tag2 + ">\\p{XDigit}{2})" +
167                 "(?:.*?)";
168         String nibbles = "";
169 
170         Pattern p = Pattern.compile(paramsPattern);
171         Matcher m = p.matcher(message);
172         if (m.matches()) {
173             nibbles = m.group(tag1).replace(":", "") + m.group(tag2);
174         }
175         return nibbles;
176     }
177 }
178