Skip to content

Commit 7a06f21

Browse files
committed
hostlink: accept extended CMD_TX_APP_DATA layout
1 parent 6a0895e commit 7a06f21

File tree

2 files changed

+156
-40
lines changed

2 files changed

+156
-40
lines changed

src/hostlink/hostlink_protocol.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,12 @@ u16 payload_len
189189
u8[] payload
190190
```
191191

192+
Compatibility:
193+
- Device also accepts an extended compatibility layout used by newer PC clients:
194+
`portnum, from, to, channel, flags, team_id[8], team_key_id, [timestamp_s], total_len, offset, chunk_len, chunk`.
195+
- `timestamp_s` is optional in this compatibility layout.
196+
- Current firmware requires a full payload in one frame (`offset=0` and `chunk_len=total_len`) when using the compatibility layout.
197+
192198
Flags:
193199
- bit0: `want_response` (forwarded to mesh adapter `want_ack`)
194200
- bit1: `team_mgmt_plain` (only for Team mgmt `portnum=300`; forces plain mgmt send path)

src/hostlink/hostlink_service.cpp

Lines changed: 150 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,152 @@ bool parse_u64(const uint8_t* data, size_t len, size_t& off, uint64_t& out)
301301
return true;
302302
}
303303

304+
bool try_parse_cmd_tx_app_data_legacy(const Frame& frame, PendingCommand* command)
305+
{
306+
if (!command)
307+
{
308+
return false;
309+
}
310+
311+
size_t off = 0;
312+
uint32_t portnum = 0;
313+
uint32_t to = 0;
314+
uint16_t payload_len = 0;
315+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, portnum))
316+
{
317+
return false;
318+
}
319+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, to))
320+
{
321+
return false;
322+
}
323+
if (off + 2 > frame.payload.size())
324+
{
325+
return false;
326+
}
327+
uint8_t channel = frame.payload[off++];
328+
uint8_t flags = frame.payload[off++];
329+
if (!parse_u16(frame.payload.data(), frame.payload.size(), off, payload_len))
330+
{
331+
return false;
332+
}
333+
if (off + payload_len != frame.payload.size())
334+
{
335+
return false;
336+
}
337+
if (channel >= static_cast<uint8_t>(chat::ChannelId::MAX_CHANNELS))
338+
{
339+
return false;
340+
}
341+
if (payload_len > kMaxFrameLen)
342+
{
343+
return false;
344+
}
345+
346+
command->type = PendingCommandType::TxAppData;
347+
command->to = to;
348+
command->portnum = portnum;
349+
command->channel = channel;
350+
command->flags = flags;
351+
command->payload_len = payload_len;
352+
if (payload_len > 0)
353+
{
354+
memcpy(command->payload, frame.payload.data() + off, payload_len);
355+
}
356+
return true;
357+
}
358+
359+
bool try_parse_cmd_tx_app_data_extended(const Frame& frame,
360+
bool include_timestamp_field,
361+
PendingCommand* command)
362+
{
363+
if (!command)
364+
{
365+
return false;
366+
}
367+
368+
size_t off = 0;
369+
uint32_t portnum = 0;
370+
uint32_t from = 0;
371+
uint32_t to = 0;
372+
uint32_t team_key_id = 0;
373+
uint32_t timestamp_s = 0;
374+
uint32_t total_len = 0;
375+
uint32_t chunk_offset = 0;
376+
uint16_t chunk_len = 0;
377+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, portnum))
378+
{
379+
return false;
380+
}
381+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, from))
382+
{
383+
return false;
384+
}
385+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, to))
386+
{
387+
return false;
388+
}
389+
if (off + 2 > frame.payload.size())
390+
{
391+
return false;
392+
}
393+
uint8_t channel = frame.payload[off++];
394+
uint8_t flags = frame.payload[off++];
395+
if (off + team::proto::kTeamIdSize > frame.payload.size())
396+
{
397+
return false;
398+
}
399+
off += team::proto::kTeamIdSize;
400+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, team_key_id))
401+
{
402+
return false;
403+
}
404+
if (include_timestamp_field &&
405+
!parse_u32(frame.payload.data(), frame.payload.size(), off, timestamp_s))
406+
{
407+
return false;
408+
}
409+
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, total_len) ||
410+
!parse_u32(frame.payload.data(), frame.payload.size(), off, chunk_offset) ||
411+
!parse_u16(frame.payload.data(), frame.payload.size(), off, chunk_len))
412+
{
413+
return false;
414+
}
415+
if (off + chunk_len != frame.payload.size())
416+
{
417+
return false;
418+
}
419+
if (channel >= static_cast<uint8_t>(chat::ChannelId::MAX_CHANNELS))
420+
{
421+
return false;
422+
}
423+
if (chunk_len > kMaxFrameLen)
424+
{
425+
return false;
426+
}
427+
// Current command queue carries one payload per command; require full payload in one frame.
428+
if (chunk_offset != 0 || total_len != chunk_len)
429+
{
430+
return false;
431+
}
432+
433+
(void)from;
434+
(void)team_key_id;
435+
(void)timestamp_s;
436+
437+
command->type = PendingCommandType::TxAppData;
438+
command->to = to;
439+
command->portnum = portnum;
440+
command->channel = channel;
441+
command->flags = flags;
442+
command->payload_len = chunk_len;
443+
if (chunk_len > 0)
444+
{
445+
memcpy(command->payload, frame.payload.data() + off, chunk_len);
446+
}
447+
return true;
448+
}
449+
304450
ErrorCode map_team_send_error(team::TeamService::SendError err)
305451
{
306452
switch (err)
@@ -557,48 +703,12 @@ ErrorCode handle_cmd_tx_msg(const Frame& frame)
557703

558704
ErrorCode handle_cmd_tx_app_data(const Frame& frame)
559705
{
560-
size_t off = 0;
561-
uint32_t portnum = 0;
562-
uint32_t to = 0;
563-
uint16_t payload_len = 0;
564-
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, portnum))
565-
{
566-
return ErrorCode::InvalidParam;
567-
}
568-
if (!parse_u32(frame.payload.data(), frame.payload.size(), off, to))
569-
{
570-
return ErrorCode::InvalidParam;
571-
}
572-
if (off + 2 > frame.payload.size())
573-
{
574-
return ErrorCode::InvalidParam;
575-
}
576-
uint8_t channel = frame.payload[off++];
577-
uint8_t flags = frame.payload[off++];
578-
if (!parse_u16(frame.payload.data(), frame.payload.size(), off, payload_len))
579-
{
580-
return ErrorCode::InvalidParam;
581-
}
582-
if (off + payload_len != frame.payload.size())
583-
{
584-
return ErrorCode::InvalidParam;
585-
}
586-
587-
if (channel >= static_cast<uint8_t>(chat::ChannelId::MAX_CHANNELS))
588-
{
589-
return ErrorCode::InvalidParam;
590-
}
591-
592706
PendingCommand command{};
593-
command.type = PendingCommandType::TxAppData;
594-
command.to = to;
595-
command.portnum = portnum;
596-
command.channel = channel;
597-
command.flags = flags;
598-
command.payload_len = payload_len;
599-
if (payload_len > 0)
707+
if (!try_parse_cmd_tx_app_data_legacy(frame, &command) &&
708+
!try_parse_cmd_tx_app_data_extended(frame, false, &command) &&
709+
!try_parse_cmd_tx_app_data_extended(frame, true, &command))
600710
{
601-
memcpy(command.payload, frame.payload.data() + off, payload_len);
711+
return ErrorCode::InvalidParam;
602712
}
603713

604714
if (!enqueue_pending_command(command))

0 commit comments

Comments
 (0)