Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e6d912a
Fix zone NULL/uninitialized issues in timezone fallback reader
Copilot Apr 10, 2026
134706c
Address code review: separate error/empty checks, add size upper boun…
Copilot Apr 10, 2026
5171908
Remove upper bound check on timeZoneDST file size
Copilot Apr 10, 2026
f94256c
Merge branch 'develop' into copilot/improve-zone-null-checks
yogeswaransky Apr 13, 2026
1cbe842
RDKEMW-12350: Update xconfclient.c
yogeswaransky Apr 13, 2026
7401952
RDKEMW-12350: Update L1-tests.yml
yogeswaransky Apr 13, 2026
4780c19
RDKEMW-12350: Update L2-tests.yml
yogeswaransky Apr 13, 2026
c7d368f
RDKEMW-12350: Fix missing error checks in timeZoneDST fallback path
yogeswaransky Apr 14, 2026
aa88d6d
Initial plan
Copilot Apr 14, 2026
0ceb491
Add unit tests for timeZoneDST fallback path in getTimezone()
Copilot Apr 14, 2026
46f82cd
Revert build infrastructure changes, keep only test and source changes
Copilot Apr 14, 2026
d10e308
Update L1-tests.yml
yogeswaransky Apr 14, 2026
e9e1117
Fix unqualified Invoke in getTimezone_timeZoneDST_valid_timezone test
Copilot Apr 16, 2026
0bbe815
Fix SetupReachTimeZoneDSTFallback: fscanf called once not twice due t…
Copilot Apr 16, 2026
25ceb4f
L1 testcase addition
yogeswaransky Apr 16, 2026
6f6777f
Update L1-tests.yml
yogeswaransky Apr 16, 2026
246582a
Merge branch 'develop' into copilot/improve-zone-null-checks
yogeswaransky May 6, 2026
9974cad
Merge branch 'develop' into copilot/improve-zone-null-checks
shibu-kv May 8, 2026
557ea4e
Merge branch 'develop' into copilot/improve-zone-null-checks
yogeswaransky May 16, 2026
9b10eb9
Merge branch 'develop' into copilot/improve-zone-null-checks
yogeswaransky May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions source/test/xconf-client/xconfclientTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,3 +762,195 @@ TEST(STOPXCONFCLIENT, success_check)
{
EXPECT_EQ(T2ERROR_SUCCESS, stopXConfClient());
}

/*
* Tests for the getTimezone() timeZoneDST fallback path.
*
* getTimezone() is a static function exercised indirectly via appendRequestParams().
* The fallback to /opt/persistent/timeZoneDST is reached after the JSON-based
* primary path exhausts its 10 retries without finding a timezone.
*
* Preconditions for each test:
* - All getParameterValue calls succeed (6 calls with dummy values).
Comment thread
yogeswaransky marked this conversation as resolved.
* - getBuildType succeeds: fopen(DEVICE_PROPERTIES) returns a fake handle, fscanf
* fills "BUILD_TYPE=PROD" and then returns EOF.
* - getTimezone CPU_ARCH read: fopen(DEVICE_PROPERTIES) returns NULL (no CPU_ARCH).
* - getTimezone JSON loop: fopen("/opt/output.json") returns NULL (10 retries fail).
* After these preconditions the timeZoneDST fallback is exercised.
*/
Comment on lines +773 to +780
#if !defined(ENABLE_RDKB_SUPPORT) && !defined(ENABLE_RDKC_SUPPORT)

// Helper to set up the mocks needed to reach the timeZoneDST fallback in getTimezone().
// Requires g_fileIOMock and m_xconfclientMock to be set.
static void SetupReachTimeZoneDSTFallback(FILE* devicePropsHandle)
{
using namespace testing;

// All 6 getParameterValue calls succeed with dummy values.
EXPECT_CALL(*m_xconfclientMock, getParameterValue(_, _))
.Times(AtLeast(6))
.WillRepeatedly(Invoke([](const char*, char** val) -> T2ERROR {
*val = strdup("dummy");
return T2ERROR_SUCCESS;
}));

// getBuildType: fopen(DEVICE_PROPERTIES) -> devicePropsHandle, fscanf fills
// "BUILD_TYPE=PROD" and breaks (no EOF call), fclose returns 0.
EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/etc/device.properties"), _))
.WillOnce(Return(devicePropsHandle)) // getBuildType
.WillOnce(Return(nullptr)); // getTimezone CPU_ARCH read

// getBuildType breaks out of its fscanf loop as soon as it finds "BUILD_TYPE",
// so fscanf is called exactly once.
EXPECT_CALL(*g_fileIOMock, fscanf(devicePropsHandle, _, _))
.WillOnce(Invoke([](FILE*, const char*, va_list args) -> int {
char* buf = va_arg(args, char*);
strncpy(buf, "BUILD_TYPE=PROD", 254);
buf[254] = '\0';
return 1;
}));

EXPECT_CALL(*g_fileIOMock, fclose(devicePropsHandle))
.WillOnce(Return(0));

Comment thread
yogeswaransky marked this conversation as resolved.
// getTimezone JSON loop: fopen("/opt/output.json") returns NULL all 10 retries.
EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/output.json"), _))
.Times(10)
.WillRepeatedly(Return(nullptr));
}

// Test 1: fopen("/opt/persistent/timeZoneDST") returns NULL.
// The fallback cannot open the file; timezone stays NULL; appendRequestParams returns FAILURE.
TEST_F(xconfclientTestFixture, getTimezone_timeZoneDST_fopen_null)
{
FILE* devicePropsHandle = reinterpret_cast<FILE*>(0x1234);
PREVENT_GTEST_LOGGING_DEADLOCK();
SetupReachTimeZoneDSTFallback(devicePropsHandle);

EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/persistent/timeZoneDST"), _))
.WillOnce(Return(nullptr));

CURLU* requestURL = curl_url();
curl_url_set(requestURL, CURLUPART_URL,
"https://mockxconf:50050/loguploader/getT2DCMSettings", 0);
EXPECT_EQ(T2ERROR_FAILURE, appendRequestParams(requestURL));
curl_free(requestURL);
requestURL = NULL;
Comment thread
yogeswaransky marked this conversation as resolved.
Comment on lines +833 to +838
}

// Test 2: ftell returns -1 (unreadable/error).
// The fallback opens the file but ftell fails; file is closed; timezone stays NULL.
TEST_F(xconfclientTestFixture, getTimezone_timeZoneDST_ftell_negative)
{
FILE* devicePropsHandle = reinterpret_cast<FILE*>(0x1234);
FILE* tzFile = reinterpret_cast<FILE*>(0x5678);
PREVENT_GTEST_LOGGING_DEADLOCK();
SetupReachTimeZoneDSTFallback(devicePropsHandle);

EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/persistent/timeZoneDST"), _))
.WillOnce(Return(tzFile));
EXPECT_CALL(*g_fileIOMock, fseek(tzFile, 0, SEEK_END))
.WillOnce(Return(0));
EXPECT_CALL(*g_fileIOMock, ftell(tzFile))
.WillOnce(Return(-1L));
EXPECT_CALL(*g_fileIOMock, fclose(tzFile))
.WillOnce(Return(0));

CURLU* requestURL = curl_url();
curl_url_set(requestURL, CURLUPART_URL,
"https://mockxconf:50050/loguploader/getT2DCMSettings", 0);
EXPECT_EQ(T2ERROR_FAILURE, appendRequestParams(requestURL));
curl_free(requestURL);
requestURL = NULL;
}

// Test 3: ftell returns 0 (empty file).
// The fallback opens the file but it is empty; file is closed; timezone stays NULL.
TEST_F(xconfclientTestFixture, getTimezone_timeZoneDST_empty_file)
{
FILE* devicePropsHandle = reinterpret_cast<FILE*>(0x1234);
FILE* tzFile = reinterpret_cast<FILE*>(0x5678);
PREVENT_GTEST_LOGGING_DEADLOCK();
SetupReachTimeZoneDSTFallback(devicePropsHandle);

EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/persistent/timeZoneDST"), _))
.WillOnce(Return(tzFile));
EXPECT_CALL(*g_fileIOMock, fseek(tzFile, 0, SEEK_END))
.WillOnce(Return(0));
EXPECT_CALL(*g_fileIOMock, ftell(tzFile))
.WillOnce(Return(0L));
EXPECT_CALL(*g_fileIOMock, fclose(tzFile))
.WillOnce(Return(0));

CURLU* requestURL = curl_url();
curl_url_set(requestURL, CURLUPART_URL,
"https://mockxconf:50050/loguploader/getT2DCMSettings", 0);
EXPECT_EQ(T2ERROR_FAILURE, appendRequestParams(requestURL));
curl_free(requestURL);
requestURL = NULL;
}

// Test 4: ftell returns a value exceeding the 256-byte limit.
// The fallback rejects oversized files; file is closed; timezone stays NULL.
TEST_F(xconfclientTestFixture, getTimezone_timeZoneDST_file_too_large)
{
FILE* devicePropsHandle = reinterpret_cast<FILE*>(0x1234);
FILE* tzFile = reinterpret_cast<FILE*>(0x5678);
PREVENT_GTEST_LOGGING_DEADLOCK();
SetupReachTimeZoneDSTFallback(devicePropsHandle);

EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/persistent/timeZoneDST"), _))
.WillOnce(Return(tzFile));
EXPECT_CALL(*g_fileIOMock, fseek(tzFile, 0, SEEK_END))
.WillOnce(Return(0));
EXPECT_CALL(*g_fileIOMock, ftell(tzFile))
.WillOnce(Return(512L));
EXPECT_CALL(*g_fileIOMock, fclose(tzFile))
.WillOnce(Return(0));

CURLU* requestURL = curl_url();
curl_url_set(requestURL, CURLUPART_URL,
"https://mockxconf:50050/loguploader/getT2DCMSettings", 0);
EXPECT_EQ(T2ERROR_FAILURE, appendRequestParams(requestURL));
curl_free(requestURL);
requestURL = NULL;
}

// Test 5: Valid timezone string read from /opt/persistent/timeZoneDST.
// The fallback reads a valid timezone; appendRequestParams returns SUCCESS.
TEST_F(xconfclientTestFixture, getTimezone_timeZoneDST_valid_timezone)
{
FILE* devicePropsHandle = reinterpret_cast<FILE*>(0x1234);
FILE* tzFile = reinterpret_cast<FILE*>(0x5678);
PREVENT_GTEST_LOGGING_DEADLOCK();
SetupReachTimeZoneDSTFallback(devicePropsHandle);

EXPECT_CALL(*g_fileIOMock, fopen(StrEq("/opt/persistent/timeZoneDST"), _))
.WillOnce(Return(tzFile));
EXPECT_CALL(*g_fileIOMock, fseek(tzFile, 0, SEEK_END))
.WillOnce(Return(0));
EXPECT_CALL(*g_fileIOMock, ftell(tzFile))
.WillOnce(Return(10L));
EXPECT_CALL(*g_fileIOMock, fseek(tzFile, 0, SEEK_SET))
.WillOnce(Return(0));
// fscanf fills the zone buffer with "US/Eastern" and then signals end-of-file.
EXPECT_CALL(*g_fileIOMock, fscanf(tzFile, _, _))
.WillOnce(::testing::Invoke([](FILE*, const char*, va_list args) -> int {
char* buf = va_arg(args, char*);
strncpy(buf, "US/Eastern", 10);
buf[10] = '\0';
return 1;
}))
.WillOnce(Return(0));
Comment thread
yogeswaransky marked this conversation as resolved.
EXPECT_CALL(*g_fileIOMock, fclose(tzFile))
.WillOnce(Return(0));

CURLU* requestURL = curl_url();
curl_url_set(requestURL, CURLUPART_URL,
"https://mockxconf:50050/loguploader/getT2DCMSettings", 0);
EXPECT_EQ(T2ERROR_SUCCESS, appendRequestParams(requestURL));
curl_free(requestURL);
requestURL = NULL;
}

#endif /* !defined(ENABLE_RDKB_SUPPORT) && !defined(ENABLE_RDKC_SUPPORT) */
51 changes: 34 additions & 17 deletions source/xconf-client/xconfclient.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,30 +307,47 @@ static char *getTimezone ()
{
fseek(file, 0, SEEK_END);
long numbytes = ftell(file);
char *zone = (char*)malloc(sizeof(char) * (numbytes + 1));
fseek(file, 0, SEEK_SET);

char fmt[32];
snprintf(fmt, sizeof(fmt), "%%%lds", numbytes); //using numbytes as the length for reading the file

while (fscanf(file, fmt, zone) != EOF)
if (numbytes < 0)
Comment thread
yogeswaransky marked this conversation as resolved.
{
if(zoneValue)
{
free(zoneValue);
}
if (zone != NULL && strlen(zone) > 0)
T2Warning("timeZoneDST file is unreadable (ftell returned %ld)\n", numbytes);
fclose(file);
}
else if (numbytes == 0 || numbytes > 256)
{
T2Warning("Warning: timeZoneDST file has unexpected size %ld, skipping\n", numbytes);
fclose(file);
Comment on lines +315 to +318
}
else
{
char *zone = (char*)malloc(sizeof(char) * (numbytes + 1));
if (zone == NULL)
{
Comment thread
yogeswaransky marked this conversation as resolved.
zoneValue = strdup(zone);
T2Error("Failed to allocate %ld bytes for timezone\n", numbytes + 1);
fclose(file);
}
else
{
zoneValue = NULL;
T2Warning("Warning: zone is NULL or empty, skipping\n");
zone[0] = '\0';
fseek(file, 0, SEEK_SET);

Comment thread
yogeswaransky marked this conversation as resolved.
char fmt[32];
snprintf(fmt, sizeof(fmt), "%%%lds", numbytes);

while (fscanf(file, fmt, zone) == 1)
{
if (strlen(zone) > 0)
{
if(zoneValue)
{
free(zoneValue);
}
zoneValue = strdup(zone);
Comment thread
yogeswaransky marked this conversation as resolved.
}
}
fclose(file);
free(zone);
}
}
fclose(file);
free(zone);
}

}
Expand Down
Loading