1 /*
2  * Copyright (C) 2018 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 #undef LOG_TAG
18 #define LOG_TAG "DisplayIdentification"
19 
20 #include <algorithm>
21 #include <cctype>
22 #include <numeric>
23 #include <optional>
24 
25 #include <log/log.h>
26 
27 #include "DisplayIdentification.h"
28 #include "Hash.h"
29 
30 namespace android {
31 namespace {
32 
33 using byte_view = std::basic_string_view<uint8_t>;
34 
35 constexpr size_t kEdidBlockSize = 128;
36 constexpr size_t kEdidHeaderLength = 5;
37 
38 constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
39 
getEdidDescriptorType(const byte_view & view)40 std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
41     if (view.size() < kEdidHeaderLength || view[0] || view[1] || view[2] || view[4]) {
42         return {};
43     }
44 
45     return view[3];
46 }
47 
parseEdidText(const byte_view & view)48 std::string_view parseEdidText(const byte_view& view) {
49     std::string_view text(reinterpret_cast<const char*>(view.data()), view.size());
50     text = text.substr(0, text.find('\n'));
51 
52     if (!std::all_of(text.begin(), text.end(), ::isprint)) {
53         ALOGW("Invalid EDID: ASCII text is not printable.");
54         return {};
55     }
56 
57     return text;
58 }
59 
60 // Big-endian 16-bit value encodes three 5-bit letters where A is 0b00001.
61 template <size_t I>
getPnpLetter(uint16_t id)62 char getPnpLetter(uint16_t id) {
63     static_assert(I < 3);
64     const char letter = 'A' + (static_cast<uint8_t>(id >> ((2 - I) * 5)) & 0b00011111) - 1;
65     return letter < 'A' || letter > 'Z' ? '\0' : letter;
66 }
67 
buildDeviceProductInfo(const Edid & edid)68 DeviceProductInfo buildDeviceProductInfo(const Edid& edid) {
69     DeviceProductInfo info;
70     info.name.assign(edid.displayName);
71     info.productId = std::to_string(edid.productId);
72     info.manufacturerPnpId = edid.pnpId;
73 
74     constexpr uint8_t kModelYearFlag = 0xff;
75     constexpr uint32_t kYearOffset = 1990;
76 
77     const auto year = edid.manufactureOrModelYear + kYearOffset;
78     if (edid.manufactureWeek == kModelYearFlag) {
79         info.manufactureOrModelDate = DeviceProductInfo::ModelYear{.year = year};
80     } else if (edid.manufactureWeek == 0) {
81         DeviceProductInfo::ManufactureYear date;
82         date.year = year;
83         info.manufactureOrModelDate = date;
84     } else {
85         DeviceProductInfo::ManufactureWeekAndYear date;
86         date.year = year;
87         date.week = edid.manufactureWeek;
88         info.manufactureOrModelDate = date;
89     }
90 
91     if (edid.cea861Block && edid.cea861Block->hdmiVendorDataBlock) {
92         const auto& address = edid.cea861Block->hdmiVendorDataBlock->physicalAddress;
93         info.relativeAddress = {address.a, address.b, address.c, address.d};
94     }
95     return info;
96 }
97 
parseCea861Block(const byte_view & block)98 Cea861ExtensionBlock parseCea861Block(const byte_view& block) {
99     Cea861ExtensionBlock cea861Block;
100 
101     constexpr size_t kRevisionNumberOffset = 1;
102     cea861Block.revisionNumber = block[kRevisionNumberOffset];
103 
104     constexpr size_t kDetailedTimingDescriptorsOffset = 2;
105     const size_t dtdStart =
106             std::min(kEdidBlockSize, static_cast<size_t>(block[kDetailedTimingDescriptorsOffset]));
107 
108     // Parse data blocks.
109     for (size_t dataBlockOffset = 4; dataBlockOffset < dtdStart;) {
110         const uint8_t header = block[dataBlockOffset];
111         const uint8_t tag = header >> 5;
112         const size_t bodyLength = header & 0b11111;
113         constexpr size_t kDataBlockHeaderSize = 1;
114         const size_t dataBlockSize = bodyLength + kDataBlockHeaderSize;
115 
116         if (block.size() < dataBlockOffset + dataBlockSize) {
117             ALOGW("Invalid EDID: CEA 861 data block is truncated.");
118             break;
119         }
120 
121         const byte_view dataBlock(block.data() + dataBlockOffset, dataBlockSize);
122         constexpr uint8_t kVendorSpecificDataBlockTag = 0x3;
123 
124         if (tag == kVendorSpecificDataBlockTag) {
125             const uint32_t ieeeRegistrationId = static_cast<uint32_t>(
126                     dataBlock[1] | (dataBlock[2] << 8) | (dataBlock[3] << 16));
127             constexpr uint32_t kHdmiIeeeRegistrationId = 0xc03;
128 
129             if (ieeeRegistrationId == kHdmiIeeeRegistrationId) {
130                 const uint8_t a = dataBlock[4] >> 4;
131                 const uint8_t b = dataBlock[4] & 0b1111;
132                 const uint8_t c = dataBlock[5] >> 4;
133                 const uint8_t d = dataBlock[5] & 0b1111;
134                 cea861Block.hdmiVendorDataBlock =
135                         HdmiVendorDataBlock{.physicalAddress = HdmiPhysicalAddress{a, b, c, d}};
136             } else {
137                 ALOGV("Ignoring vendor specific data block for vendor with IEEE OUI %x",
138                       ieeeRegistrationId);
139             }
140         } else {
141             ALOGV("Ignoring CEA-861 data block with tag %x", tag);
142         }
143         dataBlockOffset += bodyLength + kDataBlockHeaderSize;
144     }
145 
146     return cea861Block;
147 }
148 
149 } // namespace
150 
isEdid(const DisplayIdentificationData & data)151 bool isEdid(const DisplayIdentificationData& data) {
152     const uint8_t kMagic[] = {0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0};
153     return data.size() >= sizeof(kMagic) &&
154             std::equal(std::begin(kMagic), std::end(kMagic), data.begin());
155 }
156 
parseEdid(const DisplayIdentificationData & edid)157 std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
158     if (edid.size() < kEdidBlockSize) {
159         ALOGW("Invalid EDID: structure is truncated.");
160         // Attempt parsing even if EDID is malformed.
161     } else {
162         ALOGW_IF(std::accumulate(edid.begin(), edid.begin() + kEdidBlockSize,
163                                  static_cast<uint8_t>(0)),
164                  "Invalid EDID: structure does not checksum.");
165     }
166 
167     constexpr size_t kManufacturerOffset = 8;
168     if (edid.size() < kManufacturerOffset + sizeof(uint16_t)) {
169         ALOGE("Invalid EDID: manufacturer ID is truncated.");
170         return {};
171     }
172 
173     // Plug and play ID encoded as big-endian 16-bit value.
174     const uint16_t manufacturerId =
175             static_cast<uint16_t>((edid[kManufacturerOffset] << 8) | edid[kManufacturerOffset + 1]);
176 
177     const auto pnpId = getPnpId(manufacturerId);
178     if (!pnpId) {
179         ALOGE("Invalid EDID: manufacturer ID is not a valid PnP ID.");
180         return {};
181     }
182 
183     constexpr size_t kProductIdOffset = 10;
184     if (edid.size() < kProductIdOffset + sizeof(uint16_t)) {
185         ALOGE("Invalid EDID: product ID is truncated.");
186         return {};
187     }
188     const uint16_t productId =
189             static_cast<uint16_t>(edid[kProductIdOffset] | (edid[kProductIdOffset + 1] << 8));
190 
191     constexpr size_t kManufactureWeekOffset = 16;
192     if (edid.size() < kManufactureWeekOffset + sizeof(uint8_t)) {
193         ALOGE("Invalid EDID: manufacture week is truncated.");
194         return {};
195     }
196     const uint8_t manufactureWeek = edid[kManufactureWeekOffset];
197     ALOGW_IF(0x37 <= manufactureWeek && manufactureWeek <= 0xfe,
198              "Invalid EDID: week of manufacture cannot be in the range [0x37, 0xfe].");
199 
200     constexpr size_t kManufactureYearOffset = 17;
201     if (edid.size() < kManufactureYearOffset + sizeof(uint8_t)) {
202         ALOGE("Invalid EDID: manufacture year is truncated.");
203         return {};
204     }
205     const uint8_t manufactureOrModelYear = edid[kManufactureYearOffset];
206     ALOGW_IF(manufactureOrModelYear <= 0xf,
207              "Invalid EDID: model year or manufacture year cannot be in the range [0x0, 0xf].");
208 
209     constexpr size_t kDescriptorOffset = 54;
210     if (edid.size() < kDescriptorOffset) {
211         ALOGE("Invalid EDID: descriptors are missing.");
212         return {};
213     }
214 
215     byte_view view(edid.data(), edid.size());
216     view.remove_prefix(kDescriptorOffset);
217 
218     std::string_view displayName;
219     std::string_view serialNumber;
220     std::string_view asciiText;
221 
222     constexpr size_t kDescriptorCount = 4;
223     constexpr size_t kDescriptorLength = 18;
224 
225     for (size_t i = 0; i < kDescriptorCount; i++) {
226         if (view.size() < kDescriptorLength) {
227             break;
228         }
229 
230         if (const auto type = getEdidDescriptorType(view)) {
231             byte_view descriptor(view.data(), kDescriptorLength);
232             descriptor.remove_prefix(kEdidHeaderLength);
233 
234             switch (*type) {
235                 case 0xfc:
236                     displayName = parseEdidText(descriptor);
237                     break;
238                 case 0xfe:
239                     asciiText = parseEdidText(descriptor);
240                     break;
241                 case 0xff:
242                     serialNumber = parseEdidText(descriptor);
243                     break;
244             }
245         }
246 
247         view.remove_prefix(kDescriptorLength);
248     }
249 
250     std::string_view modelString = displayName;
251 
252     if (modelString.empty()) {
253         ALOGW("Invalid EDID: falling back to serial number due to missing display name.");
254         modelString = serialNumber;
255     }
256     if (modelString.empty()) {
257         ALOGW("Invalid EDID: falling back to ASCII text due to missing serial number.");
258         modelString = asciiText;
259     }
260     if (modelString.empty()) {
261         ALOGE("Invalid EDID: display name and fallback descriptors are missing.");
262         return {};
263     }
264 
265     // Hash model string instead of using product code or (integer) serial number, since the latter
266     // have been observed to change on some displays with multiple inputs. Use a stable hash instead
267     // of std::hash which is only required to be same within a single execution of a program.
268     const uint32_t modelHash = static_cast<uint32_t>(cityHash64Len0To16(modelString));
269 
270     // Parse extension blocks.
271     std::optional<Cea861ExtensionBlock> cea861Block;
272     if (edid.size() < kEdidBlockSize) {
273         ALOGW("Invalid EDID: block 0 is truncated.");
274     } else {
275         constexpr size_t kNumExtensionsOffset = 126;
276         const size_t numExtensions = edid[kNumExtensionsOffset];
277         view = byte_view(edid.data(), edid.size());
278         for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
279             view.remove_prefix(kEdidBlockSize);
280             if (view.size() < kEdidBlockSize) {
281                 ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
282                 break;
283             }
284 
285             const byte_view block(view.data(), kEdidBlockSize);
286             ALOGW_IF(std::accumulate(block.begin(), block.end(), static_cast<uint8_t>(0)),
287                      "Invalid EDID: block %zu does not checksum.", blockNumber);
288             const uint8_t tag = block[0];
289 
290             constexpr uint8_t kCea861BlockTag = 0x2;
291             if (tag == kCea861BlockTag) {
292                 cea861Block = parseCea861Block(block);
293             } else {
294                 ALOGV("Ignoring block number %zu with tag %x.", blockNumber, tag);
295             }
296         }
297     }
298 
299     return Edid{.manufacturerId = manufacturerId,
300                 .productId = productId,
301                 .pnpId = *pnpId,
302                 .modelHash = modelHash,
303                 .displayName = displayName,
304                 .manufactureOrModelYear = manufactureOrModelYear,
305                 .manufactureWeek = manufactureWeek,
306                 .cea861Block = cea861Block};
307 }
308 
getPnpId(uint16_t manufacturerId)309 std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
310     const char a = getPnpLetter<0>(manufacturerId);
311     const char b = getPnpLetter<1>(manufacturerId);
312     const char c = getPnpLetter<2>(manufacturerId);
313     return a && b && c ? std::make_optional(PnpId{a, b, c}) : std::nullopt;
314 }
315 
getPnpId(PhysicalDisplayId displayId)316 std::optional<PnpId> getPnpId(PhysicalDisplayId displayId) {
317     return getPnpId(displayId.getManufacturerId());
318 }
319 
parseDisplayIdentificationData(uint8_t port,const DisplayIdentificationData & data)320 std::optional<DisplayIdentificationInfo> parseDisplayIdentificationData(
321         uint8_t port, const DisplayIdentificationData& data) {
322     if (!isEdid(data)) {
323         ALOGE("Display identification data has unknown format.");
324         return {};
325     }
326 
327     const auto edid = parseEdid(data);
328     if (!edid) {
329         return {};
330     }
331 
332     const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
333     return DisplayIdentificationInfo{.id = displayId,
334                                      .name = std::string(edid->displayName),
335                                      .deviceProductInfo = buildDeviceProductInfo(*edid)};
336 }
337 
getVirtualDisplayId(uint32_t id)338 PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
339     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
340 }
341 
342 } // namespace android
343 
344