Skip to content

Commit

Permalink
finito
Browse files Browse the repository at this point in the history
  • Loading branch information
FrogTheFrog committed Feb 1, 2025
1 parent ebf96e7 commit ea2ec80
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 19 deletions.
1 change: 0 additions & 1 deletion src/common/include/display_device/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ namespace display_device {
std::string m_manufacturer_id {};
std::string m_product_code {};
std::uint32_t m_serial_number {};
std::uint16_t m_manufacture_date {};

/**
* @brief Parse EDID data.
Expand Down
2 changes: 1 addition & 1 deletion src/common/json_serializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace display_device {
DD_JSON_DEFINE_SERIALIZE_STRUCT(Resolution, width, height)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Rational, numerator, denominator)
DD_JSON_DEFINE_SERIALIZE_STRUCT(Point, x, y)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EdidData, manufacturer_id, product_code, serial_number, manufacture_date)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EdidData, manufacturer_id, product_code, serial_number)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice::Info, resolution, resolution_scale, refresh_rate, primary, origin_point, hdr_state)
DD_JSON_DEFINE_SERIALIZE_STRUCT(EnumeratedDevice, device_id, display_name, friendly_name, edid, info)
DD_JSON_DEFINE_SERIALIZE_STRUCT(SingleDisplayConfiguration, device_id, device_prep, resolution, refresh_rate, hdr_state)
Expand Down
92 changes: 89 additions & 3 deletions src/common/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
// header include
#include "display_device/types.h"

// system includes
#include <iomanip>
#include <sstream>

// local includes
#include "display_device/logging.h"

namespace {
bool fuzzyCompare(const double lhs, const double rhs) {
return std::abs(lhs - rhs) * 1000000000000. <= std::min(std::abs(lhs), std::abs(rhs));
Expand All @@ -19,6 +26,10 @@ namespace {
}
return false;
}

std::byte operator+(const std::byte &lhs, const std::byte &rhs) {
return std::byte {static_cast<std::uint8_t>(static_cast<int>(lhs) + static_cast<int>(rhs))};
}
} // namespace

namespace display_device {
Expand All @@ -34,12 +45,87 @@ namespace display_device {
return lhs.m_height == rhs.m_height && lhs.m_width == rhs.m_width;
}

std::optional<EdidData> EdidData::parse(const std::vector<std::byte> &) {
return std::nullopt;
std::optional<EdidData> EdidData::parse(const std::vector<std::byte> &data) {
if (data.empty()) {
return std::nullopt;
}

if (data.size() < 128) {
DD_LOG(warning) << "EDID data size is too small: " << data.size();
return std::nullopt;
}

// ---- Verify fixed header
static const std::vector fixed_header {std::byte {0x00}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0xFF}, std::byte {0x00}};
if (!std::equal(std::begin(fixed_header), std::end(fixed_header), std::begin(data))) {
DD_LOG(warning) << "EDID data does not contain fixed header.";
return std::nullopt;
}

// ---- Verify checksum
{
int sum = 0;
for (std::size_t i = 0; i < 128; ++i) {
sum += static_cast<int>(data[i]);
}

if (sum % 256 != 0) {
DD_LOG(warning) << "EDID checksum verification failed.";
return std::nullopt;
}
}

EdidData edid {};

// ---- Get manufacturer ID (ASCII code A-Z)
{
constexpr std::byte ascii_offset {'@'};

const auto byte_a {data[8]};
const auto byte_b {data[9]};
std::array<char, 3> man_id {};

man_id[0] = static_cast<char>(ascii_offset + ((byte_a & std::byte {0x7C}) >> 2));
man_id[1] = static_cast<char>(ascii_offset + ((byte_a & std::byte {0x03}) << 3) + ((byte_b & std::byte {0xE0}) >> 5));
man_id[2] = static_cast<char>(ascii_offset + ((byte_b & std::byte {0x1F})));

for (const char ch : man_id) {
if (ch < 'A' || ch > 'Z') {
DD_LOG(warning) << "EDID manufacturer id is out of range.";
return std::nullopt;
}
}

edid.m_manufacturer_id = {std::begin(man_id), std::end(man_id)};
}

// ---- Product code (HEX representation)
{
std::uint16_t prod_num {0};
prod_num |= static_cast<int>(data[10]) << 0;
prod_num |= static_cast<int>(data[11]) << 8;

std::stringstream stream;
stream << std::setfill('0') << std::setw(4) << std::hex << std::uppercase << prod_num;
edid.m_product_code = stream.str();
}

// ---- Serial number
{
std::uint32_t serial_num {0};
serial_num |= static_cast<int>(data[12]) << 0;
serial_num |= static_cast<int>(data[13]) << 8;
serial_num |= static_cast<int>(data[14]) << 16;
serial_num |= static_cast<int>(data[15]) << 24;

edid.m_serial_number = serial_num;
}

return edid;
}

bool operator==(const EdidData &lhs, const EdidData &rhs) {
return lhs.m_manufacturer_id == rhs.m_manufacturer_id && lhs.m_product_code == rhs.m_product_code && lhs.m_serial_number == rhs.m_serial_number && lhs.m_manufacture_date == rhs.m_manufacture_date;
return lhs.m_manufacturer_id == rhs.m_manufacturer_id && lhs.m_product_code == rhs.m_product_code && lhs.m_serial_number == rhs.m_serial_number;
}

bool operator==(const EnumeratedDevice::Info &lhs, const EnumeratedDevice::Info &rhs) {
Expand Down
4 changes: 3 additions & 1 deletion src/windows/win_display_device_general.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ namespace display_device {
const bool is_active {win_utils::isActive(best_path)};
const auto source_mode {is_active ? win_utils::getSourceMode(win_utils::getSourceIndex(best_path, display_data->m_modes), display_data->m_modes) : nullptr};
const auto display_name {is_active ? m_w_api->getDisplayName(best_path) : std::string {}}; // Inactive devices can have multiple display names, so it's just meaningless use any
const auto edid {EdidData::parse(m_w_api->getEdid(best_path))};

if (is_active && !source_mode) {
DD_LOG(warning) << "Device " << device_id << " is missing source mode!";
Expand All @@ -68,14 +69,15 @@ namespace display_device {
{device_id,
display_name,
friendly_name,
EdidData::parse(m_w_api->getEdid(best_path)),
edid,
info}
);
} else {
available_devices.push_back(
{device_id,
display_name,
friendly_name,
edid,
std::nullopt}
);
}
Expand Down
13 changes: 13 additions & 0 deletions tests/fixtures/include/fixtures/test_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
// system includes
#include <optional>
#include <string>
#include <vector>

// local includes
#include "display_device/types.h"

/**
* @brief Contains some useful predefined structures for UTs.
* @note Data is to be extended with relevant information as needed.
*/
namespace ut_consts {
extern const std::vector<std::byte> DEFAULT_EDID;
extern const display_device::EdidData DEFAULT_EDID_DATA;
} // namespace ut_consts

/**
* @brief Test regular expression against string.
Expand Down
33 changes: 33 additions & 0 deletions tests/fixtures/test_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,42 @@
#include <cstdlib>

// system includes
#include <cstdint>
#include <iostream>
#include <regex>

namespace ut_consts {
namespace {
template<typename... Ts>
std::vector<std::byte> make_bytes(Ts &&...args) {
return {std::byte {static_cast<std::uint8_t>(args)}...};
}
} // namespace

const std::vector<std::byte> DEFAULT_EDID {make_bytes(
// clang-format off
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x04, 0x69,
0xEC, 0x27, 0xAA, 0x55, 0x00, 0x00, 0x13, 0x1D, 0x01, 0x04,
0xA5, 0x3C, 0x22, 0x78, 0x06, 0xEE, 0x91, 0xA3, 0x54, 0x4C,
0x99, 0x26, 0x0F, 0x50, 0x54, 0x21, 0x08, 0x00, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x56, 0x5E, 0x00, 0xA0, 0xA0, 0xA0,
0x29, 0x50, 0x30, 0x20, 0x35, 0x00, 0x56, 0x50, 0x21, 0x00,
0x00, 0x1A, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x23, 0x41, 0x53,
0x4E, 0x39, 0x4A, 0x36, 0x6E, 0x4E, 0x49, 0x54, 0x62, 0x64,
0x00, 0x00, 0x00, 0xFD, 0x00, 0x1E, 0x90, 0x22, 0xDE, 0x3B,
0x01, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00,
0x00, 0xFC, 0x00, 0x52, 0x4F, 0x47, 0x20, 0x50, 0x47, 0x32,
0x37, 0x39, 0x51, 0x0A, 0x20, 0x20, 0x01, 0x8B
// clang-format on
)};
const display_device::EdidData DEFAULT_EDID_DATA {
.m_manufacturer_id = "ACI",
.m_product_code = "27EC",
.m_serial_number = 21930
};
} // namespace ut_consts

bool testRegex(const std::string &input, const std::string &pattern) {
std::regex regex(pattern);
std::smatch match;
Expand Down
9 changes: 4 additions & 5 deletions tests/unit/general/test_comparison.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ TEST_S(Resolution) {
}

TEST_S(EdidData) {
EXPECT_EQ(display_device::EdidData({"LOL", "1337", 1234, 777}), display_device::EdidData({"LOL", "1337", 1234, 777}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234, 777}), display_device::EdidData({"MEH", "1337", 1234, 777}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234, 777}), display_device::EdidData({"LOL", "1338", 1234, 777}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234, 777}), display_device::EdidData({"LOL", "1337", 1235, 777}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234, 777}), display_device::EdidData({"LOL", "1337", 1234, 778}));
EXPECT_EQ(display_device::EdidData({"LOL", "1337", 1234}), display_device::EdidData({"LOL", "1337", 1234}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234}), display_device::EdidData({"MEH", "1337", 1234}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234}), display_device::EdidData({"LOL", "1338", 1234}));
EXPECT_NE(display_device::EdidData({"LOL", "1337", 1234}), display_device::EdidData({"LOL", "1337", 1235}));
}

TEST_S(EnumeratedDevice, Info) {
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/general/test_edid_parsing.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// local includes
#include "display_device/types.h"
#include "fixtures/fixtures.h"

namespace {
// Specialized TEST macro(s) for this test file
#define TEST_S(...) DD_MAKE_TEST(TEST, EdidParsing, __VA_ARGS__)
} // namespace

TEST_S(NoData) {
EXPECT_EQ(display_device::EdidData::parse({}), std::nullopt);
}

TEST_S(BadFixedHeader) {
auto EDID_DATA {ut_consts::DEFAULT_EDID};
EDID_DATA[1] = std::byte {0xAA};
EXPECT_EQ(display_device::EdidData::parse(EDID_DATA), std::nullopt);
}

TEST_S(BadChecksum) {
auto EDID_DATA {ut_consts::DEFAULT_EDID};
EDID_DATA[16] = std::byte {0x00};
EXPECT_EQ(display_device::EdidData::parse(EDID_DATA), std::nullopt);
}

TEST_S(InvalidManufacturerId, BelowLimit) {
auto EDID_DATA {ut_consts::DEFAULT_EDID};
// The sum of 8th and 9th bytes should remain 109
EDID_DATA[8] = std::byte {0x00};
EDID_DATA[9] = std::byte {0x6D};
EXPECT_EQ(display_device::EdidData::parse(EDID_DATA), std::nullopt);
}

TEST_S(InvalidManufacturerId, AboveLimit) {
auto EDID_DATA {ut_consts::DEFAULT_EDID};
// The sum of 8th and 9th bytes should remain 109
EDID_DATA[8] = std::byte {0x6D};
EDID_DATA[9] = std::byte {0x00};
EXPECT_EQ(display_device::EdidData::parse(EDID_DATA), std::nullopt);
}

TEST_S(ValidOutput) {
EXPECT_EQ(display_device::EdidData::parse(ut_consts::DEFAULT_EDID), ut_consts::DEFAULT_EDID_DATA);
}
11 changes: 5 additions & 6 deletions tests/unit/general/test_json_converter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ TEST_F_S(EdidData) {
display_device::EdidData item {
.m_manufacturer_id = "LOL",
.m_product_code = "ABCD",
.m_serial_number = 777777,
.m_manufacture_date = 2021
.m_serial_number = 777777
};

executeTestCase(display_device::EdidData {}, R"({"manufacture_date":0,"manufacturer_id":"","product_code":"","serial_number":0})");
executeTestCase(item, R"({"manufacture_date":2021,"manufacturer_id":"LOL","product_code":"ABCD","serial_number":777777})");
executeTestCase(display_device::EdidData {}, R"({"manufacturer_id":"","product_code":"","serial_number":0})");
executeTestCase(item, R"({"manufacturer_id":"LOL","product_code":"ABCD","serial_number":777777})");
}

TEST_F_S(EnumeratedDevice) {
Expand Down Expand Up @@ -50,7 +49,7 @@ TEST_F_S(EnumeratedDevice) {

executeTestCase(display_device::EnumeratedDevice {}, R"({"device_id":"","display_name":"","edid":null,"friendly_name":"","info":null})");
executeTestCase(item_1, R"({"device_id":"ID_1","display_name":"NAME_2","edid":null,"friendly_name":"FU_NAME_3","info":{"hdr_state":"Enabled","origin_point":{"x":1,"y":2},"primary":false,"refresh_rate":{"type":"double","value":119.9554},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"rational","value":{"denominator":100,"numerator":175}}}})");
executeTestCase(item_2, R"({"device_id":"ID_2","display_name":"NAME_2","edid":{"manufacture_date":0,"manufacturer_id":"","product_code":"","serial_number":0},"friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":{"type":"rational","value":{"denominator":10000,"numerator":1199554}},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"double","value":1.75}}})");
executeTestCase(item_2, R"({"device_id":"ID_2","display_name":"NAME_2","edid":{"manufacturer_id":"","product_code":"","serial_number":0},"friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":{"type":"rational","value":{"denominator":10000,"numerator":1199554}},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"double","value":1.75}}})");
}

TEST_F_S(EnumeratedDeviceList) {
Expand Down Expand Up @@ -86,7 +85,7 @@ TEST_F_S(EnumeratedDeviceList) {

executeTestCase(display_device::EnumeratedDeviceList {}, R"([])");
executeTestCase(display_device::EnumeratedDeviceList {item_1, item_2, item_3}, R"([{"device_id":"ID_1","display_name":"NAME_2","edid":null,"friendly_name":"FU_NAME_3","info":{"hdr_state":"Enabled","origin_point":{"x":1,"y":2},"primary":false,"refresh_rate":{"type":"double","value":119.9554},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"rational","value":{"denominator":100,"numerator":175}}}},)"
R"({"device_id":"ID_2","display_name":"NAME_2","edid":{"manufacture_date":0,"manufacturer_id":"","product_code":"","serial_number":0},"friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":{"type":"rational","value":{"denominator":10000,"numerator":1199554}},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"double","value":1.75}}},)"
R"({"device_id":"ID_2","display_name":"NAME_2","edid":{"manufacturer_id":"","product_code":"","serial_number":0},"friendly_name":"FU_NAME_2","info":{"hdr_state":"Disabled","origin_point":{"x":0,"y":0},"primary":true,"refresh_rate":{"type":"rational","value":{"denominator":10000,"numerator":1199554}},"resolution":{"height":1080,"width":1920},"resolution_scale":{"type":"double","value":1.75}}},)"
R"({"device_id":"","display_name":"","edid":null,"friendly_name":"","info":null}])");
}

Expand Down
25 changes: 23 additions & 2 deletions tests/unit/windows/test_win_display_device_general.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ TEST_F_S_MOCKED(EnumAvailableDevices) {
.Times(1)
.WillOnce(Return("DisplayName1"))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getEdid(_))
.Times(1)
.WillOnce(Return(std::vector<std::byte> {}))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getDisplayScale(_, _))
.Times(1)
.WillOnce(Return(std::nullopt))
Expand All @@ -146,6 +150,10 @@ TEST_F_S_MOCKED(EnumAvailableDevices) {
.Times(1)
.WillOnce(Return("DisplayName2"))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getEdid(_))
.Times(1)
.WillOnce(Return(ut_consts::DEFAULT_EDID))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getDisplayScale(_, _))
.Times(1)
.WillOnce(Return(display_device::Rational {175, 100}))
Expand All @@ -159,6 +167,10 @@ TEST_F_S_MOCKED(EnumAvailableDevices) {
.Times(1)
.WillOnce(Return("FriendlyName3"))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getEdid(_))
.Times(1)
.WillOnce(Return(std::vector<std::byte> {}))
.RetiresOnSaturation();

const display_device::EnumeratedDeviceList expected_list {
{"DeviceId1",
Expand All @@ -176,7 +188,7 @@ TEST_F_S_MOCKED(EnumAvailableDevices) {
{"DeviceId2",
"DisplayName2",
"FriendlyName2",
std::nullopt,
ut_consts::DEFAULT_EDID_DATA,
display_device::EnumeratedDevice::Info {
{1920, 2160},
display_device::Rational {175, 100},
Expand All @@ -188,6 +200,7 @@ TEST_F_S_MOCKED(EnumAvailableDevices) {
{"DeviceId3",
"",
"FriendlyName3",
std::nullopt,
std::nullopt}
};
EXPECT_EQ(m_win_dd.enumAvailableDevices(), expected_list);
Expand Down Expand Up @@ -227,6 +240,10 @@ TEST_F_S_MOCKED(EnumAvailableDevices, MissingSourceModes) {
.Times(1)
.WillOnce(Return("DisplayName1"))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getEdid(_))
.Times(1)
.WillOnce(Return(std::vector<std::byte> {}))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getDisplayScale(_, _))
.Times(1)
.WillOnce(Return(std::nullopt))
Expand All @@ -244,6 +261,10 @@ TEST_F_S_MOCKED(EnumAvailableDevices, MissingSourceModes) {
.Times(1)
.WillOnce(Return("DisplayName2"))
.RetiresOnSaturation();
EXPECT_CALL(*m_layer, getEdid(_))
.Times(1)
.WillOnce(Return(ut_consts::DEFAULT_EDID))
.RetiresOnSaturation();

const display_device::EnumeratedDeviceList expected_list {
{"DeviceId1",
Expand All @@ -261,7 +282,7 @@ TEST_F_S_MOCKED(EnumAvailableDevices, MissingSourceModes) {
{"DeviceId2",
"DisplayName2",
"FriendlyName2",
std::nullopt,
ut_consts::DEFAULT_EDID_DATA,
std::nullopt}
};
EXPECT_EQ(m_win_dd.enumAvailableDevices(), expected_list);
Expand Down

0 comments on commit ea2ec80

Please sign in to comment.