Public Access
OTA: answer once the announced image size has arrived
The Arduino network client treats a half-closed connection as closed, so the device's reply after the sender's EOF was lost. The header already carries the image size: UpdateParser::complete() lets the device finish and answer while the connection is open. ota_push.py half-closes only if no answer comes within 3 s, for older firmware. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -50,6 +50,8 @@ class UpdateParser {
|
|||||||
|
|
||||||
void feed(const uint8_t* data, size_t len);
|
void feed(const uint8_t* data, size_t len);
|
||||||
bool end(); // no more data: true if the update was installed
|
bool end(); // no more data: true if the update was installed
|
||||||
|
// The whole image the header announced has arrived (the sender need not close the connection).
|
||||||
|
bool complete() const { return state_ == State::Image && received_ == imageSize_; }
|
||||||
|
|
||||||
State state() const { return state_; }
|
State state() const { return state_; }
|
||||||
const std::string& error() const { return error_; }
|
const std::string& error() const { return error_; }
|
||||||
|
|||||||
+18
-5
@@ -17,16 +17,29 @@ def main():
|
|||||||
data = open(path, "rb").read()
|
data = open(path, "rb").read()
|
||||||
with socket.create_connection((host, PORT), timeout=15) as s:
|
with socket.create_connection((host, PORT), timeout=15) as s:
|
||||||
s.settimeout(60)
|
s.settimeout(60)
|
||||||
sent = 0
|
sent, last = 0, -1
|
||||||
while sent < len(data):
|
while sent < len(data):
|
||||||
chunk = data[sent:sent + 4096]
|
chunk = data[sent:sent + 4096]
|
||||||
s.sendall(chunk)
|
s.sendall(chunk)
|
||||||
sent += len(chunk)
|
sent += len(chunk)
|
||||||
print(f"\rsending {sent * 100 // len(data):3d}%", end="", flush=True)
|
pct = sent * 100 // len(data)
|
||||||
s.shutdown(socket.SHUT_WR) # end of file: the device checks it and answers
|
if pct != last:
|
||||||
|
print(f"\rsending {pct:3d}%", end="", flush=True)
|
||||||
|
last = pct
|
||||||
|
# The header announces the image size, so current firmware answers once it has it all.
|
||||||
|
# Firmware from before that change only knows the file ended when we half-close.
|
||||||
reply = b""
|
reply = b""
|
||||||
while not reply.endswith(b"\n"):
|
s.settimeout(3)
|
||||||
part = s.recv(256)
|
try:
|
||||||
|
reply = s.recv(256)
|
||||||
|
except socket.timeout:
|
||||||
|
s.shutdown(socket.SHUT_WR)
|
||||||
|
s.settimeout(60)
|
||||||
|
while reply == b"" or not reply.endswith(b"\n"):
|
||||||
|
try:
|
||||||
|
part = s.recv(256)
|
||||||
|
except socket.timeout:
|
||||||
|
break
|
||||||
if not part:
|
if not part:
|
||||||
break
|
break
|
||||||
reply += part
|
reply += part
|
||||||
|
|||||||
@@ -132,6 +132,10 @@ void UpdateService::install(UpdateSource& source, const char* via) {
|
|||||||
if (incoming_.empty() && parser.state() == UpdateParser::State::Image) incoming_ = parser.version();
|
if (incoming_.empty() && parser.state() == UpdateParser::State::Image) incoming_ = parser.version();
|
||||||
percent_ = parser.percent();
|
percent_ = parser.percent();
|
||||||
if (parser.state() == UpdateParser::State::Failed) break;
|
if (parser.state() == UpdateParser::State::Failed) break;
|
||||||
|
if (parser.complete()) { // all announced bytes are here: answer while the connection is open
|
||||||
|
ok = parser.end();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!ok && parser.error().empty()) parser.end(); // e.g. a stall: abort the slot
|
if (!ok && parser.error().empty()) parser.end(); // e.g. a stall: abort the slot
|
||||||
|
|
||||||
|
|||||||
@@ -138,6 +138,18 @@ void test_progress_counts_image_bytes() {
|
|||||||
TEST_ASSERT_EQUAL(25, p.percent());
|
TEST_ASSERT_EQUAL(25, p.percent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_complete_once_the_declared_image_size_has_arrived() {
|
||||||
|
FakeVerifier v;
|
||||||
|
MemorySink sink;
|
||||||
|
auto file = makeFile("v0.3.0", image(300));
|
||||||
|
UpdateParser p(v, sink, 100000, "v0.2.1");
|
||||||
|
p.feed(file.data(), file.size() - 1);
|
||||||
|
TEST_ASSERT_FALSE(p.complete());
|
||||||
|
p.feed(file.data() + file.size() - 1, 1);
|
||||||
|
TEST_ASSERT_TRUE(p.complete());
|
||||||
|
TEST_ASSERT_TRUE(p.end());
|
||||||
|
}
|
||||||
|
|
||||||
void test_not_an_update_file_is_refused_before_writing() {
|
void test_not_an_update_file_is_refused_before_writing() {
|
||||||
FakeVerifier v;
|
FakeVerifier v;
|
||||||
MemorySink sink;
|
MemorySink sink;
|
||||||
@@ -273,6 +285,7 @@ int main() {
|
|||||||
RUN_TEST(test_valid_file_in_one_chunk_is_installed);
|
RUN_TEST(test_valid_file_in_one_chunk_is_installed);
|
||||||
RUN_TEST(test_valid_file_byte_by_byte);
|
RUN_TEST(test_valid_file_byte_by_byte);
|
||||||
RUN_TEST(test_progress_counts_image_bytes);
|
RUN_TEST(test_progress_counts_image_bytes);
|
||||||
|
RUN_TEST(test_complete_once_the_declared_image_size_has_arrived);
|
||||||
RUN_TEST(test_not_an_update_file_is_refused_before_writing);
|
RUN_TEST(test_not_an_update_file_is_refused_before_writing);
|
||||||
RUN_TEST(test_bad_signature_is_refused_before_writing);
|
RUN_TEST(test_bad_signature_is_refused_before_writing);
|
||||||
RUN_TEST(test_tampered_version_breaks_the_signature);
|
RUN_TEST(test_tampered_version_breaks_the_signature);
|
||||||
|
|||||||
Reference in New Issue
Block a user