SatCat5
file_aeroftp.cc
1 // Copyright 2024 The Aerospace Corporation.
3 // This file is a part of SatCat5, licensed under CERN-OHL-W v2 or later.
5 
7 #include <satcat5/eth_dispatch.h>
8 #include <satcat5/log.h>
9 #include <satcat5/udp_dispatch.h>
10 #include <satcat5/utils.h>
11 
12 // Set verbosity level (0/1/2)
13 constexpr unsigned DEBUG_VERBOSE = 0;
14 
15 // Define the transport-layer wrappers first.
16 using satcat5::net::Type;
17 
18 satcat5::eth::AeroFtpServer::AeroFtpServer(
19  const char* work_folder,
21  const satcat5::eth::MacType& type)
22  : satcat5::net::AeroFtpServer(work_folder, eth, Type(type.value))
23 {
24  // Nothing else to initialize.
25 }
26 
27 satcat5::udp::AeroFtpServer::AeroFtpServer(
28  const char* work_folder,
30  const satcat5::udp::Port& port)
31  : satcat5::net::AeroFtpServer(work_folder, udp, Type(port.value))
32 {
33  // Nothing else to initialize.
34 }
35 
36 // Begin defining the core functionality.
43 
44 static constexpr unsigned WORDS_PER_BLOCK = 256;
45 
46 static constexpr unsigned words2blocks(unsigned words)
47  { return satcat5::util::div_ceil(words, WORDS_PER_BLOCK); }
48 
49 inline std::string make_filename(const char* work, unsigned id, const char* typ)
50 {
51  char temp[512];
52  snprintf(temp, sizeof(temp), "%s/file_%08u.%s", work, id, typ);
53  return std::string(temp);
54 }
55 
56 AeroFtpFile::AeroFtpFile(const char* work, unsigned id, unsigned len, bool resume)
57  : m_name_data(make_filename(work, id, "data"))
58  , m_name_meta(make_filename(work, id, "rcvd"))
59  , m_name_part(make_filename(work, id, "part"))
60  , m_file_id(id)
61  , m_file_len(len)
62  , m_bcount(words2blocks(len))
63  , m_pcount(0)
64  , m_data(0)
65  , m_meta(0)
66  , m_pending(new u8[m_bcount])
67  , m_status(m_pending, 0)
68 {
69  // Does the complete file already exist?
70  FILE* temp = fopen(m_name_data.c_str(), "rb");
71  bool done = (temp && !ferror(temp));
72  if (temp) fclose(temp);
73  if (done && resume) {
74  log(satcat5::log::INFO, "Already complete");
75  return;
76  } else if (done) {
77  remove(m_name_data.c_str());
78  }
79 
80  // Open or create the in-progress files.
81  m_data = fopen(m_name_part.c_str(), resume ? "ab+" : "wb+");
82  m_meta = fopen(m_name_meta.c_str(), resume ? "ab+" : "wb+");
83 
84  // Use length of metadata file to indicate previous state...
85  unsigned meta_len = (unsigned)ftell(m_meta);
86  if (!(m_data && m_meta)) {
87  // Couldn't open working files. Permission error?
88  log(satcat5::log::ERROR, "File creation error");
89  cleanup();
90  m_pcount = UINT_MAX;
91  } else if (meta_len == 0) {
92  // New file -> Initialize to empty all-pending state.
93  log(satcat5::log::INFO, done ? "Restart file" : "New file");
94  m_pcount = m_bcount;
95  memset(m_pending, 1, m_bcount);
96  fwrite(m_pending, 1, m_bcount, m_meta);
97  } else if (meta_len == m_bcount) {
98  // Partial file -> Reload state and count pending blocks.
99  log(satcat5::log::INFO, "Continued file");
100  fseek(m_meta, 0, SEEK_SET);
101  fread(m_pending, 1, m_bcount, m_meta);
102  for (unsigned a = 0 ; a < m_bcount ; ++a) {
103  if (m_pending[a]) ++m_pcount;
104  }
105  } else {
106  // Mismatched length -> Error, unable to proceed.
107  log(satcat5::log::ERROR, "Length mismatch");
108  cleanup();
109  m_pcount = UINT_MAX;
110  }
111 }
112 
113 AeroFtpFile::~AeroFtpFile()
114 {
115  cleanup();
116  delete[] m_pending;
117 }
118 
119 void AeroFtpFile::cleanup()
120 {
121  if (m_data) fclose(m_data);
122  if (m_meta) fclose(m_meta);
123  m_data = 0;
124  m_meta = 0;
125 }
126 
127 void AeroFtpFile::log(s8 level, const char* msg)
128 {
129  satcat5::log::Log(level, "AeroFTP", msg)
130  .write(", ID").write10(m_file_id)
131  .write(", length").write10(4u*m_file_len);
132 }
133 
135  u32 file_id, u32 file_len, u32 offset, u32 rxlen,
137 {
138  if (DEBUG_VERBOSE > 1) log(satcat5::log::DEBUG, "Frame received");
139 
140  // Calculate block number and expected length.
141  u32 block = words2blocks(offset);
142  u32 expected = min_u32(WORDS_PER_BLOCK, m_file_len - offset);
143 
144  // Reject any packets that fail sanity checks.
145  if (done() || error()) return;
146  if (file_id != m_file_id) return;
147  if (file_len != m_file_len) return;
148  if (block >= m_bcount) return;
149  if (block*WORDS_PER_BLOCK != offset) return;
150  if (rxlen != expected) return;
151  if (data.get_read_ready() < 4*expected) return;
152 
153  // Has this block already been saved?
154  if (!m_pending[block]) return;
155 
156  // Write the newly-received data.
157  u8 temp[4*WORDS_PER_BLOCK];
158  data.read_bytes(4*rxlen, temp);
159  fseek(m_data, (s64)(4ull*offset), SEEK_SET);
160  fwrite(temp, 1, 4*rxlen, m_data);
161 
162  // Update the pending-blocks state.
163  --m_pcount;
164  m_pending[block] = 0;
165  fseek(m_meta, (s64)block, SEEK_SET);
166  fwrite(m_pending + block, 1, 1, m_meta);
167 
168  // Was this the last block in the file?
169  if (done()) {
170  log(satcat5::log::INFO, "Completed file");
171  cleanup();
172  rename(m_name_part.c_str(), m_name_data.c_str());
173  remove(m_name_meta.c_str());
174  } else if (DEBUG_VERBOSE > 0) {
175  log(satcat5::log::DEBUG, "Frame accepted");
176  }
177 }
178 
180 {
181  if (error()) return 0;
182  m_status.read_reset(m_bcount);
183  return &m_status;
184 }
185 
187  const char* work_folder,
188  satcat5::net::Dispatch* iface,
189  const satcat5::net::Type& typ)
190  : satcat5::net::Protocol(typ)
191  , m_work_folder(work_folder)
192  , m_iface(iface)
193  , m_files()
194 {
195  m_iface->add(this);
196 }
197 
198 AeroFtpServer::~AeroFtpServer()
199 {
200  m_iface->remove(this);
201  for (auto x = m_files.begin() ; x != m_files.end() ; ++x) {
202  delete x->second;
203  }
204 }
205 
207 {
208  auto x = m_files.find(file_id);
209  if (x != m_files.end()) {
210  return x->second->missing_blocks();
211  } else {
212  return 0;
213  }
214 }
215 
216 bool AeroFtpServer::done(u32 file_id) const
217 {
218  auto x = m_files.find(file_id);
219  if (x != m_files.end()) {
220  return x->second->done();
221  } else {
222  return false;
223  }
224 }
225 
227 {
228  // Read the packet header.
229  u32 file_id = src.read_u32();
230  u32 file_len = src.read_u32();
231  u32 blk_off = src.read_u32();
232  u32 blk_len = src.read_u32();
233 
234  // Sanity checks before we continue.
235  if (file_len == 0) return;
236  if (blk_off % WORDS_PER_BLOCK != 0) return;
237  if (blk_len > WORDS_PER_BLOCK) return;
238 
239  // First time seeing this file?
240  if (m_files.find(file_id) == m_files.end()) {
241  m_files[file_id] = new AeroFtpFile(
242  m_work_folder.c_str(), file_id, file_len, m_resume);
243  }
244 
245  // Dispatch to the appropriate handler.
246  m_files[file_id]->frame_rcvd(file_id, file_len, blk_off, blk_len, src);
247 }
Implemention of "net::Dispatch" for Ethernet frames.
Definition: eth_dispatch.h:21
Ephemeral Readable interface for a simple array.
Definition: io_readable.h:206
void read_reset(unsigned len)
Reset read position to the start of the backing array, and set the readable length to the specified v...
Definition: io_readable.cc:277
Limited read of next N bytes.
Definition: io_readable.h:255
bool read_bytes(unsigned nbytes, void *dst) override
Read 0 or more bytes into a buffer.
Definition: io_readable.cc:296
unsigned get_read_ready() const override
How many bytes can be read without blocking?
Definition: io_readable.cc:293
Abstract API for reading byte-streams and packets.
Definition: io_readable.h:68
The Log class creates and formats one log message.
Definition: log.h:195
Log & write10(s32 val)
Print integer as a decimal value with no leading zeros.
Definition: log.cc:261
Log & write(const char *str)
Formatting methods for various data types.
Definition: log.cc:198
Helper class representing a saving data to a particular file.
Definition: file_aeroftp.h:33
bool error() const
Has there been an unrecoverable file-transfer error?
Definition: file_aeroftp.h:60
bool done() const
Has the complete file been received successfully?
Definition: file_aeroftp.h:58
void frame_rcvd(u32 file_id, u32 file_len, u32 offset, u32 rxlen, satcat5::io::LimitedRead &data)
Handler for each received packet relating to this file.
satcat5::io::Readable * missing_blocks()
Get a stream of missing blocks for this file.
Server for receiving file(s) using AeroFTP.
Definition: file_aeroftp.h:80
void frame_rcvd(satcat5::io::LimitedRead &src) override
Callback for incoming packets.
satcat5::io::Readable * missing_blocks(u32 file_id)
Get a stream of missing blocks for the designated file-ID.
AeroFtpServer(const char *work_folder, satcat5::net::Dispatch *iface, const satcat5::net::Type &typ)
Constructor is only available to child classes.
bool done(u32 file_id) const
Has the complete file been received successfully?
The "Dispatch" is a generic interface that knows how to read a designated protocol layer and sort inc...
Definition: net_dispatch.h:36
void add(satcat5::net::Protocol *proto)
Register a Protocol object.
Definition: net_dispatch.h:55
void remove(satcat5::net::Protocol *proto)
Unregister a Protocol object.
Definition: net_dispatch.h:59
A net::Protocol is the counterpart to net::Dispatch that handles a particular data stream,...
Definition: net_protocol.h:28
Dispatcher sorts incoming UDP messages by port index.
Definition: udp_dispatch.h:20
AeroCube File Transfer Protocol (AeroFTP) receiver.
Diagnostic logging to UART and/or Ethernet ports.
EtherType field (uint16) is used a protocol-ID [1536..65535].
Definition: eth_header.h:96
UDP and TCP ports are both 16-bit unsigned integers.
Definition: ip_core.h:119
Multipurpose filter for matching fields in network packets.
Definition: net_type.h:38
Miscellaneous mathematical utility functions.
constexpr u32 min_u32(u32 a, u32 b)
Min and max functions.
Definition: utils.h:103
constexpr T div_ceil(T a, T b)
Integer division functions with various rounding options:
Definition: utils.h:265