13 using satcat5::udp::PORT_TFTP_SERVER;
24 static constexpr
unsigned DEBUG_VERBOSE = 0;
27 static constexpr
Type TYPE_TFTP_SERVER =
Type(PORT_TFTP_SERVER.
value);
30 static constexpr u16 OPCODE_RRQ = 1;
31 static constexpr u16 OPCODE_WRQ = 2;
32 static constexpr u16 OPCODE_DATA = 3;
33 static constexpr u16 OPCODE_ACK = 4;
34 static constexpr u16 OPCODE_ERROR = 5;
38 static constexpr u16 ERROR_TIMEOUT = 0;
39 static constexpr u16 ERROR_NOFILE = 1;
40 static constexpr u16 ERROR_PROTOCOL = 4;
43 static constexpr u16 FLAG_BUSY = 0x0001;
44 static constexpr u16 FLAG_EOF = 0x0002;
45 static constexpr u16 FLAG_FIRST = 0x0004;
49 static constexpr
unsigned RETRY_MAX = 4;
50 static constexpr
unsigned RETRY_MSEC = 100;
53 inline const char* error_lookup(u16 errcode) {
55 case ERROR_TIMEOUT:
return "Timeout";
56 case ERROR_NOFILE:
return "File not found";
57 case ERROR_PROTOCOL:
return "Illegal TFTP operation";
58 default:
return "Unknown error";
63 : satcat5::net::Protocol(satcat5::net::TYPE_NONE)
75 m_addr.udp()->
add(
this);
78 TftpTransfer::~TftpTransfer() {
79 m_addr.udp()->
remove(
this);
83 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"TftpTransfer::reset", msg);
89 if ((m_flags & FLAG_BUSY) && (m_flags & FLAG_EOF)) {
114 u16 opcode,
const char* filename)
116 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"TftpTransfer::request");
121 m_addr.
connect(dstaddr, PORT_TFTP_SERVER, srcport);
125 ArrayWrite pkt(m_retry_buff,
sizeof(m_retry_buff));
126 pkt.write_u16(opcode);
142 && iface->reply_ip() == m_addr.dstaddr()
143 && iface->reply_mac() == m_addr.dstmac()
144 && iface->reply_src() == m_addr.dstport();
145 if (duplicate) send_packet(m_retry_len, 0);
151 if (m_dst || m_src)
reset(
"Transfer interrupted.");
157 m_addr.udp()->reply_ip(),
158 m_addr.udp()->reply_mac(),
165 log::Log(log::INFO,
"TFTP: Connected to client")
166 .
write(m_addr.udp()->reply_ip())
171 if (DEBUG_VERBOSE > 1)
186 set_mask_u16(m_flags, FLAG_FIRST);
191 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"TftpTransfer::file_recv");
205 set_mask_u16(m_flags, FLAG_FIRST);
211 u16 opcode = src.read_u16();
212 if (DEBUG_VERBOSE > 1)
213 log::Log(log::DEBUG,
"TftpTransfer::frame_rcvd").
write(opcode);
216 if (m_addr.udp()->reply_ip() != m_addr.dstaddr())
return;
220 if (m_flags & FLAG_FIRST) {
221 clr_mask_u16(m_flags, FLAG_FIRST);
227 log::Log(log::INFO,
"TFTP: Connected to server")
228 .
write(m_addr.dstaddr())
234 if (opcode == OPCODE_ERROR) {
237 reset(
"Connection reset by peer.");
238 }
else if (m_dst && opcode == OPCODE_DATA) {
240 u16 block_id = src.read_u16();
241 read_data(block_id, src);
243 }
else if (m_src && opcode == OPCODE_ACK) {
245 u16 block_id = src.read_u16();
246 send_data(block_id + 1);
254 if (DEBUG_VERBOSE > 1)
255 log::Log(log::DEBUG,
"TftpTransfer::timer_event").
write10((u32)m_retry_count);
258 if (m_dst && (m_flags & FLAG_EOF)) {
260 reset(
"Transfer completed.");
261 }
else if (m_retry_count <= RETRY_MAX) {
263 send_packet(m_retry_len, m_retry_count+1);
271 if (DEBUG_VERBOSE > 1)
272 log::Log(log::DEBUG,
"TftpTransfer::read_data").
write(block_id);
275 if (m_flags & FLAG_EOF)
return;
278 u16 predicted = u16(m_block_id & 0xFFFF) + 1;
279 if (block_id == predicted) {
285 if (len > 0) src.
copy_to(m_dst);
289 set_mask_u16(m_flags, FLAG_EOF);
297 u16 errcode = src.read_u16();
298 char* errstr = (
char*)m_retry_buff;
299 src.
read_str(
sizeof(m_retry_buff), errstr);
302 log::Log(log::WARNING,
"TFTP: Remote error")
306 void TftpTransfer::send_ack(u16 block_id) {
307 if (DEBUG_VERBOSE > 1)
308 log::Log(log::DEBUG,
"TftpTransfer::send_ack").
write(block_id);
312 s16 diff = s16(block_id - u16(m_block_id & 0xFFFF));
315 }
else if (diff == 0) {
317 ArrayWrite pkt(m_retry_buff,
sizeof(m_retry_buff));
318 pkt.write_u16(OPCODE_ACK);
319 pkt.write_u16(block_id);
320 pkt.write_finalize();
322 send_packet(pkt.written_len(), 0);
329 void TftpTransfer::send_data(u16 block_id) {
330 if (DEBUG_VERBOSE > 1)
331 log::Log(log::DEBUG,
"TftpTransfer::send_data").
write(block_id);
335 s16 diff = s16(block_id - u16(m_block_id & 0xFFFF));
338 }
else if (diff == 0) {
340 send_packet(m_retry_len, 0);
341 }
else if (m_flags & FLAG_EOF) {
344 reset(
"Transfer completed.");
345 }
else if (diff == 1) {
353 if (len > 0) m_src->
read_bytes(len, m_retry_buff + 4);
356 send_packet(len + 4, 0);
364 if (DEBUG_VERBOSE > 1)
365 log::Log(log::DEBUG,
"TftpTransfer::send_error").
write(errcode);
368 const char* errstr = error_lookup(errcode);
371 ArrayWrite pkt(m_retry_buff,
sizeof(m_retry_buff));
372 pkt.write_u16(OPCODE_ERROR);
373 pkt.write_u16(errcode);
383 void TftpTransfer::send_packet(
unsigned len, u16 retry) {
384 if (DEBUG_VERBOSE > 1) {
385 u16 opcode = satcat5::util::extract_be_u16(m_retry_buff);
386 log::Log(log::DEBUG,
"TftpTransfer::send_packet").
write(opcode);
390 if (len >
sizeof(m_retry_buff))
return;
393 bool last_ack = m_dst && (m_flags & FLAG_EOF);
394 m_retry_len = (u16)len;
395 m_retry_count = retry;
396 u32 timeout = RETRY_MSEC * (1u << retry);
400 if (last_ack) timeout <<= RETRY_MAX;
403 timer_once(timeout + satcat5::util::prng.next(0, timeout/2));
409 wr->write_finalize();
410 }
else if (DEBUG_VERBOSE > 1) {
411 log::Log(log::DEBUG,
"TftpTransfer: Transmission delayed...");
424 const char* filename)
426 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"TftpClient::begin_download");
427 m_xfer.
request(server, OPCODE_RRQ, filename);
434 const char* filename)
436 if (DEBUG_VERBOSE > 0)
log::Log(log::DEBUG,
"TftpClient::begin_upload");
437 m_xfer.
request(server, OPCODE_WRQ, filename);
442 : satcat5::net::Protocol(TYPE_TFTP_SERVER)
450 TftpServerCore::~TftpServerCore() {
455 if (DEBUG_VERBOSE > 1)
log::Log(log::DEBUG,
"TftpServer::frame_rcvd");
463 u16 opcode = src.read_u16();
464 if (opcode == OPCODE_RRQ) {
466 src.
read_str(
sizeof(filename), filename);
467 auto file_src =
read(filename);
475 }
else if (opcode == OPCODE_WRQ) {
477 src.
read_str(
sizeof(filename), filename);
478 auto file_dst = write(filename);
Ephemeral Writeable interface for a simple array.
unsigned written_len() const
Report total length after write_finalize() is called.
bool write_finalize() override
Mark end of frame and release temporary working data.
Limited read of next N bytes.
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Abstract API for reading byte-streams and packets.
unsigned read_str(unsigned dst_size, char *dst)
Safely read a null-terminated input string.
virtual bool read_bytes(unsigned nbytes, void *dst)
Read 0 or more bytes into a buffer.
virtual void read_finalize()
Consume any remaining bytes in this frame, if applicable.
unsigned copy_to(satcat5::io::Writeable *dst)
Copy data to a Writeable object, without finalizing.
virtual unsigned get_read_ready() const =0
How many bytes can be read without blocking?
Abstract API for writing byte-streams and packets.
virtual void write_bytes(unsigned nbytes, const void *src)
Write 0 or more bytes from a buffer.
virtual void write_abort()
If possible, abort the current partially-written packet.
void write_u8(u8 data)
One of many functions for writing integer/floating point values, see details.
void write_str(const char *str)
Write the contents of a null-terminated string.
virtual bool write_finalize()
Mark end of frame and release temporary working data.
The Log class creates and formats one log message.
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Log & write(const char *str)
Formatting methods for various data types.
void add(satcat5::net::Protocol *proto)
Register a Protocol object.
void remove(satcat5::net::Protocol *proto)
Unregister a Protocol object.
satcat5::net::Type m_filter
Incoming packet filter.
void timer_stop()
Stop all future notifications.
void timer_once(unsigned msec)
Configure a one-time notification after X milliseconds.
void save_reply_address() override
Bind this Address object to the parent interface's current reply address, as provided in net::Dispatc...
satcat5::io::Writeable * open_write(unsigned len) override
Open a new frame to the designated address and type.
void connect(const satcat5::udp::Addr &dstaddr, const satcat5::eth::MacAddr &dstmac, const satcat5::udp::Port &dstport, const satcat5::udp::Port &srcport=satcat5::udp::PORT_NONE, const satcat5::eth::VlanTag &vtag=satcat5::eth::VTAG_NONE)
Manual address resolution (user supplies IP + MAC).
void close() override
Close any open connections and revert to idle.
Dispatcher sorts incoming UDP messages by port index.
satcat5::udp::Port next_free_port()
Get the next unclaimed dynamically-allocated port index.
A TFTP client makes request(s) to a remote server.
void begin_upload(satcat5::io::Readable *src, const satcat5::ip::Addr &server, const char *filename)
Upload data from a Readable stream to the server.
TftpClient(satcat5::udp::Dispatch *iface)
Attach this client to a network interface.
void begin_download(satcat5::io::Writeable *dst, const satcat5::ip::Addr &server, const char *filename)
Download a file from server to a Writeable stream.
ServerCore is the base class that handles TFTP network functions.
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
virtual satcat5::io::Readable * read(const char *filename)=0
Child class MUST override these methods.
TftpServerCore(satcat5::udp::Dispatch *iface)
Users cannot instantiate this class directly.
TFTP server with a simple streaming source and sink.
satcat5::io::Readable * read(const char *filename) override
Child class MUST override these methods.
TftpServerSimple(satcat5::udp::Dispatch *iface, satcat5::io::Readable *src, satcat5::io::Writeable *dst)
Attach this server to a network interface, and to a source and sink for transfered data.
Transfer objects used by both TftpClient and TftpServer.
void file_send(satcat5::io::Readable *src, bool now)
Begin transfer of a single file (DATA-ACK-DATA-ACK).
void request(const satcat5::ip::Addr &dstaddr, u16 opcode, const char *filename)
Issue a write-request or read-request.
bool is_duplicate_request()
Before calling accept, test if this is a duplicate request.
void timer_event() override
Child class MUST override this method.
void accept()
Accept remote connection and note reply address.
void frame_rcvd(satcat5::io::LimitedRead &src) override
Dispatch calls frame_rcvd(...) for each incoming frame with with a matching net::Type value.
void file_recv(satcat5::io::Writeable *dst, bool now)
Begin transfer of a single file (DATA-ACK-DATA-ACK).
void send_error(u16 errcode)
Send an error message.
bool active() const
Is there a transfer in progress?
void reset(const char *msg)
Immediately revert to the idle state.
Diagnostic logging to UART and/or Ethernet ports.
IPv4 address is a 32-bit unsigned integer.
UDP and TCP ports are both 16-bit unsigned integers.
u16 value
Raw access to the underlying representation.
Multipurpose filter for matching fields in network packets.
Client and server for the Trivial File Transfer Protocol (TFTP)
Miscellaneous mathematical utility functions.
constexpr unsigned min_unsigned(unsigned a, unsigned b)
Min and max functions.
void clr_mask_u16(u16 &val, u16 mask)
Set or clear bit masks.
void write_be_u16(u8 *dst, u16 val)
Store fields into a big-endian byte array.
void set_mask_u16(u16 &val, u16 mask)
Set or clear bit masks.