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