Files
can-utils/isobusfs/isobusfs_cli_selftests.c
T
Oleksij RempelandOliver Hartkopp 64280dc9c3 isobusfs: selftest: Handle read requests larger than file size
Add expected_size field to test patterns to properly validate cases
where the requested read size exceeds the actual file size. This allows
testing the server's behavior when clients request more data than is
available.

When a read request is larger than the file, the server correctly
returns data up to EOF. The test framework now distinguishes between
this expected scenario and genuine read failures.

Signed-off-by: Oleksij Rempel <linux@rempel-privat.de>
[socketcan: addressed review comments from PR #629:
 - keep uint32_t instead of size_t for offset/read_size/expected_size:
   ISO 11783-13 limits the file offset (Seek File) and the file size
   (directory entry) to 32 bit, so uint32_t matches the protocol. The
   sum offset + read_size can exceed 32 bit (see UINT32_MAX pattern)
   and is therefore calculated in int64_t. size_t would not help here
   as it is only 32 bit wide on 32 bit targets.
 - remaining_size is a signed int64_t: the signed type is needed to
   detect the 'remaining_size < 0' case (server returned more data than
   requested) and int64_t holds the 33 bit sum on all platforms.
 - fix printf format for the uint32_t read_offset (%zu -> %u)]
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Oliver Hartkopp <socketcan@hartkopp.net>
2026-10-14 10:21:55 +00:00

939 lines
25 KiB
C

// SPDX-License-Identifier: LGPL-2.0-only
// SPDX-FileCopyrightText: 2023 Oleksij Rempel <linux@rempel-privat.de>
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>
#include "isobusfs_cli.h"
#include "isobusfs_cmn.h"
#include "isobusfs_cmn_fa.h"
size_t current_test;
bool test_running;
struct timespec test_start_time;
struct isobusfs_cli_test_case {
int (*test_func)(struct isobusfs_priv *priv, bool *complete);
const char *test_description;
};
static int isobusfs_cli_test_connect(struct isobusfs_priv *priv, bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
priv->state = ISOBUSFS_CLI_STATE_CONNECTING;
/* fall through */
case ISOBUSFS_CLI_STATE_CONNECTING:
if (priv->fs_is_active) {
*complete = true;
break;
}
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
/* without server all other tests make no sense */
priv->run_selftest = false;
*complete = true;
return ret;
}
static int isobusfs_cli_test_property_req(struct isobusfs_priv *priv, bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_property_req(priv);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_FS_PROPERTIES:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_GET_FS_PROPERTIES_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
static int isobusfs_cli_test_volume_status_req(struct isobusfs_priv *priv, bool *complete)
{
static const char volume_name[] = "\\\\vol1";
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_volume_status_req(priv, 0,
sizeof(volume_name) - 1, volume_name);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_VOLUME_STATUS:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_VOLUME_STATUS_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
static int isobusfs_cli_test_current_dir_req(struct isobusfs_priv *priv,
bool *complete)
{
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
ret = isobusfs_cli_get_current_dir_req(priv);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_CURRENT_DIR:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_GET_CURRENT_DIR_DONE:
*complete = true;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_dir_path {
const char *dir_name;
bool expect_pass;
};
static struct isobusfs_cli_test_dir_path test_dir_patterns[] = {
/* expected result \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true },
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2", true },
/* expected result \\vol1\dir1\dir2\dir3\dir4\ */
{ ".\\dir3\\dir4", true },
/* expected result \\vol1\dir1\dir2\dir3\dir5\ */
{ "..\\dir5", true },
/* expected result \\vol1\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true },
/* expected result \\vol1\~\ */
{ "~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
{ "~\\msd_dir1\\msd_dir2", true },
/* expected result \\vol1\~\ */
{ "\\\\vol1\\~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\ */
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\ */
{ ".\\~\\", true },
/* expected result \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir */
{ "~tilde_dir", true },
/* expected result \\vol1\dir1\~\ */
{ "\\\\vol1\\dir1\\~", true },
/* expected result \\vol1\~\ not clear if it is manufacture specific dir */
{ "\\~\\", true },
/* expected result \\~\ */
{ "\\\\~\\", false },
/* expected result: should fail */
{ "\\\\\\\\\\\\\\\\", false },
/* Set back to dir1 for other test. Expected result \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true },
/* Initialize server path to root: Expected initial state: root */
{ "\\\\vol1\\dir1", true }, /* Set server path to \\vol1\dir1\ */
/* Test absolute paths: Expected state: \\vol1\dir1\ */
{ "\\\\vol1\\dir1\\dir2", true }, /* Changes to \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
/* Test relative path .\ : Expected state: \\vol1\dir1\ */
{ ".\\dir2\\dir3\\dir4", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir4\ */
{ "..\\dir5", true }, /* Changes to \\vol1\dir1\dir2\dir3\dir5\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
{ ".\\dir1\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
/* Test relative path ..\ with multiple backslashes:
* Expected state: \\vol1\dir1\dir2\
*/
{ "..\\\\\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2\ */
{ "..\\\\\\\\\\\\\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "\\\\vol1\\dir1", true }, /* Changes back to \\vol1\dir1\ */
/* Test relative path .\ with multiple backslashes: Expected state:
* \\vol1\dir1\
*/
{ ".\\\\\\", true }, /* Remains at \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
{ ".\\\\\\\\\\\\\\", true }, /* Remains at \\vol1\dir1\ */
{ ".\\dir2", true }, /* Changes back to \\vol1\dir1\dir2 */
{ "..\\", true }, /* Changes to \\vol1\dir1\ */
/* Test navigating up and down: Expected state: \\vol1\dir1\ */
{ "..\\..\\..\\..\\..\\..\\vol1", true }, /* Changes to \\vol1\ */
/* prepare for tilde tests */
{ "\\\\vol1\\", true }, /* Set server path to \\vol1\ */
/* Tilde used correctly at the beginning of a path */
{ "~\\", true }, /* Replace with the manufacturer-specific directory on
* the current volume
*/
/* Tilde used correctly after a volume name */
{ "\\\\vol1\\~\\", true }, /* Replace ~ with the manufacturer-specific
* directory on vol1
*/
/* Tilde used in non-root locations, treated as a regular directory */
{ "\\\\vol1\\dir1\\~", true }, /* Treated as a regular directory named
* '~' under \\vol1\dir1\
*/
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
* current directory: \\vol1\dir1\~\
*/
/* Tilde used with a specific manufacturer directory at the root */
{ "~\\msd_dir1\\msd_dir2", true }, /* Replace ~ and append rest of the
* path on the current volume
*/
{ "\\\\vol1\\~\\msd_dir1\\msd_dir2", true },
/* Replace ~ and append rest of the
* path on vol1
*/
{ ".\\~\\", true }, /* Treated as a regular directory named '~' in the
* current directory: \\vol1\~\msd_dir1\msd_dir2\~\
*/
{ "~tilde_dir", true }, /* Treated as a regular directory named
* '~tilde_dir' in the current directory:
* \\vol1\~\msd_dir1\msd_dir2\~\~tilde_dir\
*/
/* Invalid usage of tilde at non-root locations (as a
* manufacturer-specific directory)
*/
{ "\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
* to fail
*/
{ "\\\\~\\", false }, /* Incorrect usage of tilde at non-root, expected
* to fail
*/
/* Test invalid or ambiguous paths: Expected state: \\vol1\dir1\ */
{ "\\\\\\\\\\\\\\\\", false }, /* Invalid path, should fail */
/* Set back to dir1 for other tests: Expected state: \\vol1\dir1\ */
{ "\\\\vol1\\dir1", true }, /* Ensure server path is set to \\vol1\dir1\ */
};
size_t current_dir_pattern_test;
static int isobusfs_cli_test_ccd_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_dir_patterns);
struct isobusfs_cli_test_dir_path *tp =
&test_dir_patterns[current_dir_pattern_test];
struct timespec current_time;
bool fail = false;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->dir_name);
ret = isobusfs_cli_ccd_req(priv, tp->dir_name,
strlen(tp->dir_name));
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_CCD_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_CCD_FAIL:
fail = true;
/* fallthrough */
case ISOBUSFS_CLI_STATE_CCD_DONE:
if (tp->expect_pass && fail) {
pr_err("pattern test failed: %s", tp->dir_name);
ret = -EINVAL;
goto test_fail;
} else if (!tp->expect_pass && !fail) {
pr_err("pattern test failed: %s", tp->dir_name);
ret = -EINVAL;
goto test_fail;
}
current_dir_pattern_test++;
if (current_dir_pattern_test >= num_patterns) {
*complete = true;
break;
}
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_of_path {
const char *path_name;
uint8_t flags;
bool expect_pass;
};
static struct isobusfs_cli_test_of_path test_of_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2", 0, false },
{ "\\\\vol1\\dir1\\dir2\\file0", 0, true },
};
size_t current_of_pattern_test;
static int isobusfs_cli_test_of_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_of_patterns);
struct isobusfs_cli_test_of_path *tp =
&test_of_patterns[current_of_pattern_test];
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->path_name);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
isobusfs_cli_fa_cf_req(priv, priv->handle);
else
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_of_pattern_test++;
if (current_of_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_sf_path {
const char *path_name;
uint8_t flags;
uint32_t offset;
uint32_t read_size;
bool expect_pass;
};
static struct isobusfs_cli_test_sf_path test_sf_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 10, 0, true },
};
size_t current_sf_pattern_test;
static int isobusfs_cli_test_sf_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_sf_patterns);
struct isobusfs_cli_test_sf_path *tp =
&test_sf_patterns[current_sf_pattern_test];
struct timespec current_time;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start pattern test: %s", tp->path_name);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
ISOBUSFS_FA_SEEK_SET, tp->offset);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_SF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_SF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->read_offset != tp->offset) {
pr_err("Not expected read offset: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
tp->read_size);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_RF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
break;
case ISOBUSFS_CLI_STATE_RF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
/* fall troth */
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR)
isobusfs_cli_fa_cf_req(priv, priv->handle);
else
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_of_pattern_test++;
if (current_of_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__, priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_rf_path {
const char *path_name;
uint8_t flags;
uint32_t offset;
uint32_t read_size;
uint32_t expected_size;
bool expect_pass;
};
static struct isobusfs_cli_test_rf_path test_rf_patterns[] = {
/* expected result \\vol1\dir1\dir2\ */
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 0, 0, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 0, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 1, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 2, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1k", 0, 3, 1, 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8, 8, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, 8 * 100, 8 * 100, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 100, 8 * 100, 8 * 100, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH, ISOBUSFS_MAX_DATA_LENGH, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, (ISOBUSFS_MAX_DATA_LENGH & ~3) + 16, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, ISOBUSFS_MAX_DATA_LENGH + 1, ISOBUSFS_MAX_DATA_LENGH + 1, true },
{ "\\\\vol1\\dir1\\dir2\\file1m", 0, 0, UINT32_MAX, 1024 * 1024, true },
};
size_t current_rf_pattern_test;
static uint32_t isobusfs_cli_calculate_sum(uint8_t *data, size_t size,
uint32_t offset)
{
const uint8_t xor_pattern[] = {0xde, 0xad, 0xbe, 0xef};
uint32_t actual_sum = 0;
uint32_t current_value = 0;
uint8_t byte_offset = 0;
size_t idx;
byte_offset = offset % 4;
for (idx = 0; idx < size; idx++) {
uint8_t byte;
if (data) {
byte = data[idx] ^ xor_pattern[byte_offset];
current_value |= (byte << ((3 - byte_offset) * 8));
} else {
uint32_t value_at_offset;
/* if no data is provided, generate the data based on
* offset
*/
value_at_offset = (offset + idx) / 4;
byte_offset = (offset + idx) % 4;
byte = (value_at_offset >> ((3 - byte_offset) * 8)) & 0xff;
current_value |= (byte << ((3 - byte_offset) * 8));
}
if (byte_offset == 3) {
actual_sum += current_value;
current_value = 0;
}
byte_offset = (byte_offset + 1) % 4;
/* if this is the last byte in the buffer but it's not aligned,
* add the partial uint32_t to the sum
*/
if (idx == size - 1 && byte_offset != 0)
actual_sum += current_value;
}
return actual_sum;
}
static int isobusfs_cli_test_rf_req(struct isobusfs_priv *priv, bool *complete)
{
size_t num_patterns = ARRAY_SIZE(test_rf_patterns);
struct isobusfs_cli_test_rf_path *tp =
&test_rf_patterns[current_rf_pattern_test];
uint32_t actual_sum, expected_sum;
struct timespec current_time;
int64_t remaining_size;
ssize_t read_size;
int ret;
clock_gettime(CLOCK_MONOTONIC, &current_time);
switch (priv->state) {
case ISOBUSFS_CLI_STATE_SELFTEST:
test_start_time = current_time;
pr_info("Start read test. Path: %s, size: %d, offset: %d, flags: %x",
tp->path_name, tp->read_size, tp->offset, tp->flags);
ret = isobusfs_cli_fa_of_req(priv, tp->path_name,
strlen(tp->path_name), tp->flags);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_WAIT_OF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_OF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_OF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
ret = isobusfs_cli_fa_sf_req(priv, priv->handle,
ISOBUSFS_FA_SEEK_SET, tp->offset);
if (ret)
goto test_fail;
break;
case ISOBUSFS_CLI_STATE_SF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
break;
case ISOBUSFS_CLI_STATE_SF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (priv->read_offset != tp->offset) {
pr_err("Not expected read offset: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
if (tp->read_size > 0xffff)
read_size = 0xffff;
else
read_size = tp->read_size;
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
read_size);
if (ret)
goto test_fail;
test_start_time = current_time;
break;
case ISOBUSFS_CLI_STATE_WAIT_RF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_RF_FAIL:
if (tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
priv->state = ISOBUSFS_CLI_STATE_TEST_CLEANUP;
break;
case ISOBUSFS_CLI_STATE_RF_DONE:
if (!tp->expect_pass) {
pr_err("pattern test failed: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
}
pr_info("read file size: %zu", priv->read_data_len);
actual_sum = isobusfs_cli_calculate_sum(priv->read_data,
priv->read_data_len,
priv->read_offset);
expected_sum = isobusfs_cli_calculate_sum(NULL,
priv->read_data_len,
priv->read_offset);
if (actual_sum != expected_sum) {
pr_err("pattern test failed: incorrect sum in %s. Sum got: %d, expected: %d",
tp->path_name, actual_sum, expected_sum);
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
priv->read_data_len, 16);
ret = -EINVAL;
goto test_fail;
} else {
pr_info("pattern test passed: %s. Sum got: %d, expected: %d",
tp->path_name, actual_sum, expected_sum);
isobusfs_cmn_dump_last_x_bytes(priv->read_data,
priv->read_data_len, 16);
}
free(priv->read_data);
priv->read_data = NULL;
remaining_size = ((int64_t)tp->offset + tp->read_size) -
((int64_t)priv->read_offset + priv->read_data_len);
pr_debug("remaining_size: %lld, read_offset: %u, read_data_len: %zu, test read size: %u, test offset %u",
(long long)remaining_size, priv->read_offset, priv->read_data_len,
tp->read_size, tp->offset);
if (remaining_size < 0) {
pr_err("pattern test failed: %s. Read size is too big",
tp->path_name);
ret = -EINVAL;
goto test_fail;
} else if (remaining_size == 0) {
if (tp->read_size > tp->expected_size) {
pr_err("read test failed: %s. Server returned more data than expected file size (%u > %u)",
tp->path_name, tp->read_size, tp->expected_size);
ret = -EINVAL;
goto test_fail;
}
} else if (remaining_size > 0 && priv->read_data_len != 0) {
priv->read_offset += priv->read_data_len;
if (remaining_size > 0xffff)
read_size = 0xffff;
else
read_size = remaining_size;
ret = isobusfs_cli_fa_rf_req(priv, priv->handle,
read_size);
if (ret)
goto test_fail;
test_start_time = current_time;
break;
} else if (remaining_size > 0 && priv->read_data_len == 0) {
if (tp->read_size > tp->expected_size &&
tp->read_size - remaining_size == tp->expected_size) {
/* this is acceptable case when read size
* is larger than actual file size
*/
pr_info("read test passed: %s. Reached end of file as expected.",
tp->path_name);
} else {
pr_err("read test failed: %s. Server returned zero bytes, but expected more data: %lld",
tp->path_name, (long long)remaining_size);
ret = -EINVAL;
goto test_fail;
}
}
/* fall troth */
case ISOBUSFS_CLI_STATE_TEST_CLEANUP:
if (priv->handle != ISOBUSFS_FILE_HANDLE_ERROR) {
isobusfs_cli_fa_cf_req(priv, priv->handle);
test_start_time = current_time;
} else {
priv->state = ISOBUSFS_CLI_STATE_TEST_DONE;
}
break;
case ISOBUSFS_CLI_STATE_WAIT_CF_RESP:
if (current_time.tv_sec - test_start_time.tv_sec >= 5) {
ret = -ETIMEDOUT;
goto test_fail;
}
break;
case ISOBUSFS_CLI_STATE_CF_FAIL:
pr_err("failed to close file: %s", tp->path_name);
ret = -EINVAL;
goto test_fail;
case ISOBUSFS_CLI_STATE_CF_DONE:
case ISOBUSFS_CLI_STATE_TEST_DONE:
current_rf_pattern_test++;
if (current_rf_pattern_test >= num_patterns)
*complete = true;
else
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
break;
default:
pr_err("%s:%i: unknown state: %d", __func__, __LINE__,
priv->state);
ret = -EINVAL;
goto test_fail;
}
return 0;
test_fail:
*complete = true;
return ret;
}
struct isobusfs_cli_test_case test_cases[] = {
{ isobusfs_cli_test_connect, "Server connection" },
{ isobusfs_cli_test_property_req, "Server property request" },
{ isobusfs_cli_test_volume_status_req, "Volume status request" },
{ isobusfs_cli_test_current_dir_req, "Get current dir request" },
{ isobusfs_cli_test_ccd_req, "Change current dir request" },
{ isobusfs_cli_test_of_req, "Open File request" },
{ isobusfs_cli_test_sf_req, "Seek File request" },
{ isobusfs_cli_test_rf_req, "Read File request" },
};
void isobusfs_cli_run_self_tests(struct isobusfs_priv *priv)
{
if (priv->run_selftest) {
size_t num_tests = ARRAY_SIZE(test_cases);
if (current_test < num_tests) {
bool test_complete = false;
int ret;
if (!test_running) {
pr_int("Executing test %zu: %s\n", current_test + 1, test_cases[current_test].test_description);
test_running = true;
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
}
ret = test_cases[current_test].test_func(priv, &test_complete);
if (test_complete) {
test_running = false;
pr_int("Test %zu: %s.\n", current_test + 1, ret ? "FAILED" : "PASSED");
current_test++;
priv->state = ISOBUSFS_CLI_STATE_SELFTEST;
}
} else {
pr_int("All tests completed.\n");
priv->run_selftest = false;
current_test = 0;
priv->state = ISOBUSFS_CLI_STATE_IDLE;
}
}
}