Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FL-3927] FuriThread stdin #3979

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
108 changes: 108 additions & 0 deletions applications/debug/unit_tests/tests/furi/furi_stdio_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include <furi.h>
#include <errno.h>
#include <stdio.h>
#include "../test.h" // IWYU pragma: keep

#define TAG "StdioTest"

#define CONTEXT_MAGIC ((void*)0xDEADBEEF)

// stdin

static char mock_in[256];
static size_t mock_in_len, mock_in_pos;

static void set_mock_in(const char* str) {
size_t len = strlen(str);
strcpy(mock_in, str);
mock_in_len = len;
mock_in_pos = 0;
}

static size_t mock_in_cb(char* buffer, size_t size, FuriWait wait, void* context) {
UNUSED(wait);
furi_check(context == CONTEXT_MAGIC);
size_t remaining = mock_in_len - mock_in_pos;
size = MIN(remaining, size);
memcpy(buffer, mock_in + mock_in_pos, size);
mock_in_pos += size;
return size;
}

void test_stdin(void) {
FuriThreadStdinReadCallback in_cb = furi_thread_get_stdin_callback();
furi_thread_set_stdin_callback(mock_in_cb, CONTEXT_MAGIC);
char buf[256];

// plain in
set_mock_in("Hello, World!\n");
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hello, World!\n", buf);
mu_assert_int_eq(EOF, getchar());

// ungetc
ungetc('i', stdin);
ungetc('H', stdin);
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hi", buf);
mu_assert_int_eq(EOF, getchar());

// ungetc + plain in
set_mock_in(" World");
ungetc('i', stdin);
ungetc('H', stdin);
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq("Hi World", buf);
mu_assert_int_eq(EOF, getchar());

// partial plain in
set_mock_in("Hello, World!\n");
fgets(buf, strlen("Hello") + 1, stdin);
mu_assert_string_eq("Hello", buf);
mu_assert_int_eq(',', getchar());
fgets(buf, sizeof(buf), stdin);
mu_assert_string_eq(" World!\n", buf);

furi_thread_set_stdin_callback(in_cb, CONTEXT_MAGIC);
}

// stdout

static FuriString* mock_out;
FuriThreadStdoutWriteCallback original_out_cb;

static void mock_out_cb(const char* data, size_t size, void* context) {
furi_check(context == CONTEXT_MAGIC);
// there's no furi_string_cat_strn :(
for(size_t i = 0; i < size; i++) {
furi_string_push_back(mock_out, data[i]);
}
}

static void assert_and_clear_mock_out(const char* expected) {
// return the original stdout callback for the duration of the check
// if the check fails, we don't want the error to end up in our buffer,
// we want to be able to see it!
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
mu_assert_string_eq(expected, furi_string_get_cstr(mock_out));
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);

furi_string_reset(mock_out);
}

void test_stdout(void) {
original_out_cb = furi_thread_get_stdout_callback();
furi_thread_set_stdout_callback(mock_out_cb, CONTEXT_MAGIC);
mock_out = furi_string_alloc();

puts("Hello, World!");
assert_and_clear_mock_out("Hello, World!\n");

printf("He");
printf("llo!");
fflush(stdout);
assert_and_clear_mock_out("Hello!");

furi_string_free(mock_out);
furi_thread_set_stdout_callback(original_out_cb, CONTEXT_MAGIC);
}
8 changes: 8 additions & 0 deletions applications/debug/unit_tests/tests/furi/furi_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ void test_furi_memmgr(void);
void test_furi_event_loop(void);
void test_errno_saving(void);
void test_furi_primitives(void);
void test_stdin(void);
void test_stdout(void);

static int foo = 0;

Expand Down Expand Up @@ -52,6 +54,11 @@ MU_TEST(mu_test_furi_primitives) {
test_furi_primitives();
}

MU_TEST(mu_test_stdio) {
test_stdin();
test_stdout();
}

MU_TEST_SUITE(test_suite) {
MU_SUITE_CONFIGURE(&test_setup, &test_teardown);
MU_RUN_TEST(test_check);
Expand All @@ -61,6 +68,7 @@ MU_TEST_SUITE(test_suite) {
MU_RUN_TEST(mu_test_furi_pubsub);
MU_RUN_TEST(mu_test_furi_memmgr);
MU_RUN_TEST(mu_test_furi_event_loop);
MU_RUN_TEST(mu_test_stdio);
MU_RUN_TEST(mu_test_errno_saving);
MU_RUN_TEST(mu_test_furi_primitives);
}
Expand Down
10 changes: 5 additions & 5 deletions applications/services/cli/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@ void cli_session_open(Cli* cli, void* session) {
cli->session = session;
if(cli->session != NULL) {
cli->session->init();
furi_thread_set_stdout_callback(cli->session->tx_stdout);
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
}
furi_semaphore_release(cli->idle_sem);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
Expand All @@ -447,7 +447,7 @@ void cli_session_close(Cli* cli) {
cli->session->deinit();
}
cli->session = NULL;
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk);
}

Expand All @@ -461,9 +461,9 @@ int32_t cli_srv(void* p) {
furi_record_create(RECORD_CLI, cli);

if(cli->session != NULL) {
furi_thread_set_stdout_callback(cli->session->tx_stdout);
furi_thread_set_stdout_callback(cli->session->tx_stdout, NULL);
} else {
furi_thread_set_stdout_callback(NULL);
furi_thread_set_stdout_callback(NULL, NULL);
}

if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) {
Expand Down
3 changes: 2 additions & 1 deletion applications/services/cli/cli_i.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ struct CliSession {
void (*init)(void);
void (*deinit)(void);
size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout);
size_t (*rx_stdin)(uint8_t* buffer, size_t size, uint32_t timeout, void* context);
void (*tx)(const uint8_t* buffer, size_t size);
void (*tx_stdout)(const char* data, size_t size);
void (*tx_stdout)(const char* data, size_t size, void* context);
bool (*is_connected)(void);
};

Expand Down
9 changes: 8 additions & 1 deletion applications/services/cli/cli_vcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,11 @@ static size_t cli_vcp_rx(uint8_t* buffer, size_t size, uint32_t timeout) {
return rx_cnt;
}

static size_t cli_vcp_rx_stdin(uint8_t* data, size_t size, uint32_t timeout, void* context) {
UNUSED(context);
return cli_vcp_rx(data, size, timeout);
}

static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
furi_assert(vcp);
furi_assert(buffer);
Expand All @@ -267,7 +272,8 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) {
VCP_DEBUG("tx %u end", size);
}

static void cli_vcp_tx_stdout(const char* data, size_t size) {
static void cli_vcp_tx_stdout(const char* data, size_t size, void* context) {
UNUSED(context);
cli_vcp_tx((const uint8_t*)data, size);
}

Expand Down Expand Up @@ -310,6 +316,7 @@ CliSession cli_vcp = {
cli_vcp_init,
cli_vcp_deinit,
cli_vcp_rx,
cli_vcp_rx_stdin,
cli_vcp_tx,
cli_vcp_tx_stdout,
cli_vcp_is_connected,
Expand Down
8 changes: 4 additions & 4 deletions furi/core/string.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,12 @@ void furi_string_swap(FuriString* string_1, FuriString* string_2);

/** Move string_2 content to string_1.
*
* Set the string to the other one, and destroy the other one.
* Copy data from one string to another and destroy the source.
*
* @param string_1 The FuriString instance 1
* @param string_2 The FuriString instance 2
* @param destination The destination FuriString
* @param source The source FuriString
*/
void furi_string_move(FuriString* string_1, FuriString* string_2);
void furi_string_move(FuriString* destination, FuriString* source);

/** Compute a hash for the string.
*
Expand Down
72 changes: 64 additions & 8 deletions furi/core/thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,17 @@

#define THREAD_MAX_STACK_SIZE (UINT16_MAX * sizeof(StackType_t))

typedef struct FuriThreadStdout FuriThreadStdout;

struct FuriThreadStdout {
typedef struct {
FuriThreadStdoutWriteCallback write_callback;
FuriString* buffer;
};
void* context;
} FuriThreadStdout;

typedef struct {
FuriThreadStdinReadCallback read_callback;
FuriString* unread_buffer; // <! stores data from `ungetc` and friends
void* context;
} FuriThreadStdin;

struct FuriThread {
StaticTask_t container;
Expand All @@ -55,6 +60,7 @@ struct FuriThread {
size_t heap_size;

FuriThreadStdout output;
FuriThreadStdin input;

// Keep all non-alignable byte types in one place,
// this ensures that the size of this structure is minimal
Expand Down Expand Up @@ -136,6 +142,7 @@ static void furi_thread_body(void* context) {

static void furi_thread_init_common(FuriThread* thread) {
thread->output.buffer = furi_string_alloc();
thread->input.unread_buffer = furi_string_alloc();

FuriThread* parent = NULL;
if(xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
Expand Down Expand Up @@ -245,6 +252,7 @@ void furi_thread_free(FuriThread* thread) {
}

furi_string_free(thread->output.buffer);
furi_string_free(thread->input.unread_buffer);
free(thread);
}

Expand Down Expand Up @@ -710,13 +718,22 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) {

static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) {
if(thread->output.write_callback != NULL) {
thread->output.write_callback(data, size);
thread->output.write_callback(data, size, thread->output.context);
} else {
furi_log_tx((const uint8_t*)data, size);
}
return size;
}

static size_t
__furi_thread_stdin_read(FuriThread* thread, char* data, size_t size, FuriWait timeout) {
if(thread->input.read_callback != NULL) {
return thread->input.read_callback(data, size, timeout, thread->input.context);
} else {
return 0;
}
}

static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
FuriString* buffer = thread->output.buffer;
size_t size = furi_string_size(buffer);
Expand All @@ -727,17 +744,31 @@ static int32_t __furi_thread_stdout_flush(FuriThread* thread) {
return 0;
}

void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
return thread->output.write_callback;
}

FuriThreadStdinReadCallback furi_thread_get_stdin_callback(void) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
return thread->input.read_callback;
}

void furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback, void* context) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
__furi_thread_stdout_flush(thread);
thread->output.write_callback = callback;
thread->output.context = context;
}

FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback(void) {
void furi_thread_set_stdin_callback(FuriThreadStdinReadCallback callback, void* context) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);
return thread->output.write_callback;
thread->input.read_callback = callback;
thread->input.context = context;
}

size_t furi_thread_stdout_write(const char* data, size_t size) {
Expand Down Expand Up @@ -772,6 +803,31 @@ int32_t furi_thread_stdout_flush(void) {
return __furi_thread_stdout_flush(thread);
}

size_t furi_thread_stdin_read(char* buffer, size_t size, FuriWait timeout) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);

size_t from_buffer = MIN(furi_string_size(thread->input.unread_buffer), size);
size_t from_input = size - from_buffer;
size_t from_input_actual =
__furi_thread_stdin_read(thread, buffer + from_buffer, from_input, timeout);
memcpy(buffer, furi_string_get_cstr(thread->input.unread_buffer), from_buffer);
furi_string_right(thread->input.unread_buffer, from_buffer);

return from_buffer + from_input_actual;
}

void furi_thread_stdin_unread(char* buffer, size_t size) {
FuriThread* thread = furi_thread_get_current();
furi_check(thread);

FuriString* new_buf = furi_string_alloc(); // there's no furi_string_alloc_set_strn :(
furi_string_set_strn(new_buf, buffer, size);
furi_string_cat(new_buf, thread->input.unread_buffer);
furi_string_free(thread->input.unread_buffer);
thread->input.unread_buffer = new_buf;
}

void furi_thread_suspend(FuriThreadId thread_id) {
furi_check(thread_id);

Expand Down
Loading
Loading