12#if PC_ENABLE_FTP_SESSION
32static FtpSessionCtx s_ftp = {-1, -1, {0}, 0, 0, {0}, {0}};
39static const pc_field LOG_REPLY_TOO_BIG[] = {
41static const pc_field LOG_CTRL_CLOSED[] = {
43static const pc_field LOG_REPLY_TIMEOUT[] = {
44 {
PC_FK_LIT, 0, 20,
"ftp: reply timeout, "},
PC_U32, {
PC_FK_LIT, 0, 15,
" bytes buffered"},
PC_END};
45static const pc_field LOG_DATA_CONNECT_FAILED[] = {{
PC_FK_LIT, 0, 21,
"ftp: data connect to "},
51static const pc_field LOG_CTRL_CONNECT_FAILED[] = {{
PC_FK_LIT, 0, 24,
"ftp: control connect to "},
63static bool ftp_send(
const char *verb,
const char *arg)
65 size_t n = pc_ftp_build_command(s_ftp.cmd,
sizeof(s_ftp.cmd), verb, arg);
68 PC_LOGW(LOG_BUILD_FAILED, verb);
82static bool ftp_await(
int *code,
size_t *rlen)
84 if (s_ftp.rx_consumed > 0)
86 memmove(s_ftp.rx, s_ftp.rx + s_ftp.rx_consumed, s_ftp.rx_len - s_ftp.rx_consumed);
87 s_ftp.rx_len -= s_ftp.rx_consumed;
88 s_ftp.rx_consumed = 0;
95 if (pc_ftp_parse_reply(s_ftp.rx, s_ftp.rx_len, code, &consumed))
97 s_ftp.rx_consumed = consumed;
102 PC_LOGD(LOG_REPLY, (uint32_t)*code);
105 if (s_ftp.rx_len ==
sizeof(s_ftp.rx))
107 PC_LOGW(LOG_REPLY_TOO_BIG, (uint32_t)
sizeof(s_ftp.rx));
112 PC_LOGW(LOG_CTRL_CLOSED, (uint32_t)s_ftp.rx_len);
116 if ((int32_t)(
pc_millis() - deadline) >= 0)
118 PC_LOGW(LOG_REPLY_TIMEOUT, (uint32_t)s_ftp.rx_len);
122 size_t got =
pc_client_read(s_ftp.ctrl, (uint8_t *)s_ftp.rx + s_ftp.rx_len,
sizeof(s_ftp.rx) - s_ftp.rx_len);
135static bool ftp_cmd_ok(
const char *verb,
const char *arg)
138 return ftp_send(verb, arg) && ftp_await(&code,
nullptr) && pc_ftp_reply_ok(code);
151static bool ftp_open_data(
const FtpTarget *target)
158 if (ftp_send(
"EPSV",
nullptr) && ftp_await(&code, &rlen) && code == 229 && pc_ftp_parse_epsv(s_ftp.rx, rlen, &port))
161 strncpy(host, target->host,
sizeof(host) - 1);
162 host[
sizeof(host) - 1] =
'\0';
166 uint8_t ip[4] = {0, 0, 0, 0};
167 if (!ftp_send(
"PASV",
nullptr) || !ftp_await(&code, &rlen) || code != 227 ||
168 !pc_ftp_parse_pasv(s_ftp.rx, rlen, ip, &port))
172 pc_sb sb_host = {host,
sizeof(host), 0,
true};
173 pc_sb_u32(&sb_host, (uint32_t)((
unsigned)ip[0]));
175 pc_sb_u32(&sb_host, (uint32_t)((
unsigned)ip[1]));
177 pc_sb_u32(&sb_host, (uint32_t)((
unsigned)ip[2]));
179 pc_sb_u32(&sb_host, (uint32_t)((
unsigned)ip[3]));
193 PC_LOGW(LOG_DATA_CONNECT_FAILED, host, (uint32_t)port);
195 return s_ftp.data >= 0;
199static void ftp_teardown(
void)
212 s_ftp.rx_consumed = 0;
219bool pc_ftp_store(
const FtpTarget *target,
const char *remote_path,
size_t total, pc_ftp_source src,
void *ctx)
221 if (!target || !target->host || !remote_path || remote_path[0] ==
'\0' || !src)
230 uint16_t ctrl_port = target->port ? target->port : 21;
232 s_ftp.rx_consumed = 0;
236 PC_LOGW(LOG_CTRL_CONNECT_FAILED, target->host, (uint32_t)ctrl_port);
241 if (!ftp_await(&code,
nullptr) || code != 220)
248 if (!ftp_send(
"USER", target->user ? target->user :
"anonymous") || !ftp_await(&code, nullptr))
255 if (!ftp_send(
"PASS", target->pass ? target->pass :
"") || !ftp_await(&code, nullptr))
261 if (!pc_ftp_reply_ok(code))
268 if (!ftp_cmd_ok(
"TYPE",
"I") || !ftp_open_data(target))
275 if (!ftp_send(
"STOR", remote_path) || !ftp_await(&code,
nullptr) || pc_ftp_reply_class(code) != 1)
285 size_t want = (total - off <
sizeof(s_ftp.chunk)) ? total - off :
sizeof(s_ftp.chunk);
286 size_t got = src(ctx, off, s_ftp.chunk, want);
302 ok = ftp_await(&code,
nullptr) && code == 226;
305 ftp_send(
"QUIT",
nullptr);
bool pc_client_send(int, const void *, size_t)
Queue len wire bytes for transmission (marshaled tcp_write + output).
size_t pc_client_read(int, uint8_t *, size_t)
Drain up to cap buffered wire bytes into buf; returns the count.
bool pc_client_is_closed(int)
True once the peer closed (FIN) or the connection errored.
int pc_client_open(const char *, uint16_t, uint32_t)
Resolve host (dotted-quad fast path, else DNS) and connect to host : port, blocking up to timeout_ms.
size_t pc_client_available(int)
Wire bytes currently buffered and ready to read.
void pc_client_close(int)
Tear down the connection (marshaled) and return the slot to the pool.
Layer 4 outbound TCP client transport - the client-side peer of the (server) transport in tcp....
Pluggable monotonic clock for all library timing.
void pcdelay(uint32_t ms)
Block for at least ms milliseconds - the library's single delay primitive.
uint32_t pc_millis(void)
The library's monotonic time at 1000 Hz (milliseconds).
@ PC_FK_LIT
literal text from lit; takes no argument
FTP client wire codec (RFC 959 + RFC 2428 + RFC 3659), PC_ENABLE_FTP.
FTP client session driver: the two sockets the ftp.h codec deliberately does not own.
Abstract logging whose disabled levels cost nothing at all (PC_LOG_LEVEL).
#define PC_FTP_CMD_MAX
Suggested FTP control-command buffer size (PC_ENABLE_FTP).
#define PC_FTP_REPLY_BUF
Control-reply accumulator for the FTP session driver (PC_ENABLE_FTP_SESSION).
#define PC_FTP_TIMEOUT_MS
Per-step timeout for the FTP session driver: connect, and each control reply.
#define PC_FTP_CHUNK
Bytes staged per data-channel write when the session driver streams a payload.
Bounded no-heap string builder that fails closed on overflow (one shared copy).
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
void pc_sb_u32(pc_sb *b, uint32_t v)
Append v as decimal (no leading zeros; "0" for zero).
One field of a frame. Frames are static const pc_field[], so they live in rodata.
Bump-append target; ok latches false once an append would overflow cap.