blob: 9fa586fad23f1c879d336d9361dd3d7baead425b [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements unit tests for the Server port retry functionality.
*/
#include <pw_unit_test/framework.h>
#include <lib/core/CHIPCore.h>
#include <lib/support/CodeUtils.h>
#include <functional>
#include <limits>
namespace chip {
namespace app {
namespace {
/**
* Helper function to check if an error is an "address already in use" error
* (Copied from Server.cpp for testing)
*/
bool IsAddressInUseError(CHIP_ERROR err)
{
return (err == CHIP_ERROR_POSIX(EADDRINUSE));
}
/**
* Helper function to safely increment a port number with overflow protection
* (Copied from Server.cpp for testing)
*/
bool SafePortIncrement(uint16_t basePort, uint16_t increment, uint16_t & outPort)
{
if (increment > (UINT16_MAX - basePort))
{
return false;
}
outPort = static_cast<uint16_t>(basePort + increment);
return true;
}
/**
* Helper function to initialize a transport with automatic port selection and retry logic
* (Copied from Server.cpp for testing)
*/
CHIP_ERROR InitTransportWithPortRetry(uint16_t basePort, uint16_t maxRetries, const char * componentName,
std::function<CHIP_ERROR(uint16_t)> initFunction, std::function<void()> closeFunction,
uint16_t & outBoundPort)
{
uint16_t portToTry = basePort;
uint16_t attemptNumber = 0;
CHIP_ERROR err = CHIP_NO_ERROR;
for (;;)
{
if (attemptNumber > 0)
{
if (!SafePortIncrement(basePort, attemptNumber, portToTry))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
err = initFunction(portToTry);
if (err == CHIP_NO_ERROR)
{
outBoundPort = portToTry;
break;
}
if (IsAddressInUseError(err) && attemptNumber < maxRetries)
{
closeFunction();
attemptNumber++;
continue;
}
break;
}
return err;
}
// Test fixture for Server port retry tests
class TestServerPortRetry : public ::testing::Test
{
public:
static void SetUpTestSuite() {}
static void TearDownTestSuite() {}
};
// Test IsAddressInUseError function
TEST_F(TestServerPortRetry, TestIsAddressInUseError)
{
// Test that EADDRINUSE is correctly identified
EXPECT_TRUE(IsAddressInUseError(CHIP_ERROR_POSIX(EADDRINUSE)));
// Test that other errors are not identified as address in use
EXPECT_FALSE(IsAddressInUseError(CHIP_NO_ERROR));
EXPECT_FALSE(IsAddressInUseError(CHIP_ERROR_INVALID_ARGUMENT));
EXPECT_FALSE(IsAddressInUseError(CHIP_ERROR_POSIX(ECONNREFUSED)));
EXPECT_FALSE(IsAddressInUseError(CHIP_ERROR_POSIX(ETIMEDOUT)));
}
// Test SafePortIncrement with normal values
TEST_F(TestServerPortRetry, TestSafePortIncrementNormal)
{
uint16_t outPort = 0;
// Test normal increment
EXPECT_TRUE(SafePortIncrement(5000, 1, outPort));
EXPECT_EQ(outPort, 5001);
// Test larger increment
EXPECT_TRUE(SafePortIncrement(5000, 100, outPort));
EXPECT_EQ(outPort, 5100);
// Test zero increment
EXPECT_TRUE(SafePortIncrement(5000, 0, outPort));
EXPECT_EQ(outPort, 5000);
}
// Test SafePortIncrement with overflow conditions
TEST_F(TestServerPortRetry, TestSafePortIncrementOverflow)
{
uint16_t outPort = 0;
// Test overflow at max value
EXPECT_FALSE(SafePortIncrement(UINT16_MAX, 1, outPort));
// Test overflow near max value
EXPECT_FALSE(SafePortIncrement(UINT16_MAX - 5, 10, outPort));
// Test exact boundary (should succeed)
EXPECT_TRUE(SafePortIncrement(UINT16_MAX - 10, 10, outPort));
EXPECT_EQ(outPort, UINT16_MAX);
// Test one past boundary (should fail)
EXPECT_FALSE(SafePortIncrement(UINT16_MAX - 10, 11, outPort));
}
// Test SafePortIncrement with edge cases
TEST_F(TestServerPortRetry, TestSafePortIncrementEdgeCases)
{
uint16_t outPort = 0;
// Test from port 0
EXPECT_TRUE(SafePortIncrement(0, 1, outPort));
EXPECT_EQ(outPort, 1);
// Test maximum safe increment from 0
EXPECT_TRUE(SafePortIncrement(0, UINT16_MAX, outPort));
EXPECT_EQ(outPort, UINT16_MAX);
// Test increment that would overflow from 0
EXPECT_FALSE(SafePortIncrement(1, UINT16_MAX, outPort));
}
// Test InitTransportWithPortRetry with successful first attempt
TEST_F(TestServerPortRetry, TestInitTransportSuccessFirstAttempt)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
uint16_t expectedPort = 5000;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
initCallCount++;
EXPECT_EQ(port, expectedPort);
return CHIP_NO_ERROR;
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, 3, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(boundPort, 5000);
EXPECT_EQ(initCallCount, 1);
EXPECT_EQ(closeCallCount, 0); // Should not call close on success
}
// Test InitTransportWithPortRetry with retry on address in use
TEST_F(TestServerPortRetry, TestInitTransportRetryOnAddressInUse)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
int successAttempt = 2; // Succeed on third attempt (0-indexed)
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
int currentAttempt = initCallCount++;
if (currentAttempt < successAttempt)
{
return CHIP_ERROR_POSIX(EADDRINUSE);
}
return CHIP_NO_ERROR;
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, 5, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(boundPort, 5002); // Should bind to port 5002 (base + 2)
EXPECT_EQ(initCallCount, 3); // Should have tried 3 times
EXPECT_EQ(closeCallCount, 2); // Should have closed 2 times (after first two failures)
}
// Test InitTransportWithPortRetry with max retries exceeded
TEST_F(TestServerPortRetry, TestInitTransportMaxRetriesExceeded)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
uint16_t maxRetries = 3;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
initCallCount++;
return CHIP_ERROR_POSIX(EADDRINUSE); // Always fail
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, maxRetries, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_ERROR_POSIX(EADDRINUSE));
EXPECT_EQ(initCallCount, maxRetries + 1); // Should try maxRetries + 1 times (initial + retries)
EXPECT_EQ(closeCallCount, maxRetries); // Should close maxRetries times
}
// Test InitTransportWithPortRetry with non-retryable error
TEST_F(TestServerPortRetry, TestInitTransportNonRetryableError)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
initCallCount++;
return CHIP_ERROR_INVALID_ARGUMENT; // Non-retryable error
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, 5, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT);
EXPECT_EQ(initCallCount, 1); // Should only try once
EXPECT_EQ(closeCallCount, 0); // Should not close on non-retryable error
}
// Test InitTransportWithPortRetry with port overflow
TEST_F(TestServerPortRetry, TestInitTransportPortOverflow)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
initCallCount++;
return CHIP_ERROR_POSIX(EADDRINUSE);
};
auto closeFunc = [&]() { closeCallCount++; };
// Start near max port value
CHIP_ERROR err = InitTransportWithPortRetry(UINT16_MAX - 1, 5, "test", initFunc, closeFunc, boundPort);
// Should fail with INVALID_ARGUMENT when port increment would overflow
EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT);
EXPECT_EQ(initCallCount, 2); // Should try twice before overflow
EXPECT_EQ(closeCallCount, 2); // Should close twice (after each failure before overflow)
}
// Test InitTransportWithPortRetry with zero retries
TEST_F(TestServerPortRetry, TestInitTransportZeroRetries)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
initCallCount++;
return CHIP_ERROR_POSIX(EADDRINUSE);
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, 0, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_ERROR_POSIX(EADDRINUSE));
EXPECT_EQ(initCallCount, 1); // Should only try once
EXPECT_EQ(closeCallCount, 0); // Should not close when no retries allowed
}
// Test InitTransportWithPortRetry with alternating errors
TEST_F(TestServerPortRetry, TestInitTransportAlternatingErrors)
{
uint16_t boundPort = 0;
int initCallCount = 0;
int closeCallCount = 0;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
int attempt = initCallCount++;
if (attempt == 0)
{
return CHIP_ERROR_POSIX(EADDRINUSE); // Retryable
}
if (attempt == 1)
{
return CHIP_ERROR_INVALID_ARGUMENT; // Non-retryable
}
return CHIP_NO_ERROR;
};
auto closeFunc = [&]() { closeCallCount++; };
CHIP_ERROR err = InitTransportWithPortRetry(5000, 5, "test", initFunc, closeFunc, boundPort);
// Should stop on non-retryable error
EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT);
EXPECT_EQ(initCallCount, 2); // Should try twice
EXPECT_EQ(closeCallCount, 1); // Should close once after first failure
}
// Test InitTransportWithPortRetry verifies correct port increments
TEST_F(TestServerPortRetry, TestInitTransportPortIncrements)
{
uint16_t boundPort = 0;
int initCallCount = 0;
uint16_t basePort = 5000;
std::vector<uint16_t> attemptedPorts;
auto initFunc = [&](uint16_t port) -> CHIP_ERROR {
attemptedPorts.push_back(port);
int attempt = initCallCount++;
if (attempt < 3)
{
return CHIP_ERROR_POSIX(EADDRINUSE);
}
return CHIP_NO_ERROR;
};
auto closeFunc = [&]() {};
CHIP_ERROR err = InitTransportWithPortRetry(basePort, 5, "test", initFunc, closeFunc, boundPort);
EXPECT_EQ(err, CHIP_NO_ERROR);
EXPECT_EQ(boundPort, 5003);
EXPECT_EQ(attemptedPorts.size(), 4u);
EXPECT_EQ(attemptedPorts[0], 5000);
EXPECT_EQ(attemptedPorts[1], 5001);
EXPECT_EQ(attemptedPorts[2], 5002);
EXPECT_EQ(attemptedPorts[3], 5003);
}
} // namespace
} // namespace app
} // namespace chip