#include "file_receiver.h" #include #include #include namespace roro { namespace { int hexDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } // Splits on spaces (no iostreams: they cost about 200 KB of flash on the device). std::vector words(const std::string& s) { std::vector out; size_t i = 0; while (i < s.size()) { if (s[i] == ' ') { i++; continue; } size_t end = s.find(' ', i); if (end == std::string::npos) end = s.size(); out.push_back(s.substr(i, end - i)); i = end; } return out; } } // namespace std::string FileReceiver::begin(const std::string& args, uint32_t nowMs) { reset(); std::vector w = words(args); if (w.size() != 3) return "usage: sd put "; const std::string &path = w[0], &size = w[1], &sha = w[2]; if (path.empty() || path[0] != '/' || path.size() > 128 || path.find("..") != std::string::npos) return "the path must be absolute, without .."; if (size.empty() || size.size() > 9 || size.find_first_not_of("0123456789") != std::string::npos) return "bad size"; uint32_t bytes = static_cast(std::strtoul(size.c_str(), nullptr, 10)); if (bytes == 0 || bytes > kMaxBytes) return "bad size"; if (sha.size() != 64) return "bad sha256"; for (size_t i = 0; i < 32; i++) { int hi = hexDigit(sha[2 * i]), lo = hexDigit(sha[2 * i + 1]); if (hi < 0 || lo < 0) return "bad sha256"; expected_[i] = static_cast(hi << 4 | lo); } path_ = path; size_ = bytes; lastActivityMs_ = nowMs; chunk_.reserve(kChunk); state_ = State::Receiving; return ""; } size_t FileReceiver::wanted() const { if (state_ != State::Receiving) return 0; return std::min(kChunk, size_ - received_) - chunk_.size(); } size_t FileReceiver::feed(const uint8_t* data, size_t len, uint32_t nowMs) { if (state_ != State::Receiving || len == 0) return 0; size_t take = std::min(len, wanted()); chunk_.insert(chunk_.end(), data, data + take); sha_.update(data, take); lastActivityMs_ = nowMs; if (wanted() > 0) return take; if (received_ + chunk_.size() == size_) { // The last chunk: refuse it before writing if the file arrived damaged. uint8_t got[32]; sha_.finish(got); if (std::memcmp(got, expected_, sizeof got) != 0) { fail("checksum mismatch"); return take; } } state_ = State::Writing; return take; } void FileReceiver::chunkWritten(bool ok, uint32_t nowMs) { if (state_ != State::Writing) return; if (!ok) return fail("write failed"); received_ += static_cast(chunk_.size()); chunk_.clear(); lastActivityMs_ = nowMs; state_ = received_ == size_ ? State::Finishing : State::Receiving; } void FileReceiver::cardChecked(const uint8_t digest[32]) { if (state_ != State::Finishing) return; if (std::memcmp(digest, expected_, sizeof expected_) != 0) fail("the copy on the card differs"); } void FileReceiver::finished(bool ok) { if (state_ != State::Finishing) return; if (!ok) return fail("rename failed"); state_ = State::Done; } void FileReceiver::tick(uint32_t nowMs) { if (state_ != State::Receiving && state_ != State::Writing && state_ != State::Finishing) return; if (nowMs - lastActivityMs_ >= kTimeoutMs) fail(state_ == State::Receiving ? "timed out" : "card not writable"); } void FileReceiver::fail(const char* why) { error_ = why; chunk_.clear(); state_ = State::Failed; } } // namespace roro