mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-30 02:05:22 -04:00
feat(admin): append the optional ham long_name to the call sign (#11612)
* feat(admin): append the optional ham long_name to the call sign HamParameters gained a long_name field (meshtastic/protobufs#941) that handleSetHamMode never read, so a client that sent one still ended up with a node named after the bare call sign. Join it behind the call sign with the "//" separator hams already use on the air: call_sign "N0CALL" plus long_name "Attic Heltec" becomes "N0CALL//Attic Heltec". An absent long_name keeps the previous call-sign-only name, which is what the on-device region picker still sends. Being cosmetic, long_name stays out of the whitespace-only rejection that guards call_sign and short_name: a blank one is dropped rather than costing the operator the whole licensing request over a stray space, which that path would report only as a LOG_WARN and so would be invisible from the app. The composed name is finished with clampLongName() rather than a bare sanitizeUtf8(), matching handleSetOwner and NodeDB: the proto caps the parts at 7 + 2 + 14 bytes, inside the 24-byte local budget, and clampLongName is the backstop if either cap moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(admin): enhance handleSetHamMode to return status for request validation --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ad2d27ab5b
commit
514b476189
3 files changed
+259
-22
No files matched your search
+35
-21
@@ -368,7 +368,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
||||
break;
|
||||
case meshtastic_AdminMessage_set_ham_mode_tag:
|
||||
LOG_DEBUG("Client set ham mode");
|
||||
handleSetHamMode(r->set_ham_mode);
|
||||
// Without this a rejected request falls through to the generic Routing_Error_NONE ack below,
|
||||
// so a client would report ham mode as enabled on a node that changed nothing.
|
||||
if (!handleSetHamMode(r->set_ham_mode))
|
||||
myReply = allocErrorResponse(meshtastic_Routing_Error_BAD_REQUEST, &mp);
|
||||
break;
|
||||
case meshtastic_AdminMessage_get_ui_config_request_tag: {
|
||||
LOG_DEBUG("Client is getting device-ui config");
|
||||
@@ -1920,30 +1923,40 @@ void AdminModule::handleStoreDeviceUIConfig(const meshtastic_DeviceUIConfig &uic
|
||||
#endif
|
||||
}
|
||||
|
||||
void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
||||
// Unset, or set to nothing but whitespace - the two ways a client can leave a name field empty.
|
||||
static bool isBlankName(const char *start)
|
||||
{
|
||||
// Validate ham parameters before setting since this would bypass validation in the owner struct
|
||||
const char *fieldsToCheck[] = {p.call_sign, p.short_name};
|
||||
const char *fieldNames[] = {"call_sign", "short_name"};
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (*fieldsToCheck[i]) {
|
||||
const char *start = fieldsToCheck[i];
|
||||
while (*start && isspace((unsigned char)*start))
|
||||
start++;
|
||||
if (*start == '\0') {
|
||||
LOG_WARN("Rejected ham %s: needs 1+ non-whitespace char", fieldNames[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
while (*start && isspace((unsigned char)*start))
|
||||
start++;
|
||||
return *start == '\0';
|
||||
}
|
||||
|
||||
bool AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
||||
{
|
||||
// Validate ham parameters before setting since this would bypass validation in the owner struct.
|
||||
|
||||
// The call sign is the station ID the whole licensed mode is built around, so it is required;
|
||||
// without it we would license a node that never identifies itself on the air.
|
||||
if (isBlankName(p.call_sign)) {
|
||||
LOG_WARN("Rejected ham call_sign: needs 1+ non-whitespace char");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set call sign and override lora limitations for licensed use
|
||||
strncpy(owner.long_name, p.call_sign, sizeof(owner.long_name));
|
||||
// Set call sign and override lora limitations for licensed use. An optional long_name rides
|
||||
// behind the call sign with the "//" separator hams already use on the air.
|
||||
// e.g. call_sign "N0CALL" plus long_name "Attic Heltec" becomes "N0CALL//Attic Heltec".
|
||||
if (!isBlankName(p.long_name))
|
||||
snprintf(owner.long_name, sizeof(owner.long_name), "%s//%s", p.call_sign, p.long_name);
|
||||
else
|
||||
strncpy(owner.long_name, p.call_sign, sizeof(owner.long_name));
|
||||
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.long_name, sizeof(owner.long_name));
|
||||
strncpy(owner.short_name, p.short_name, sizeof(owner.short_name));
|
||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
|
||||
clampLongName(owner.long_name);
|
||||
// short_name is optional per the schema, so a blank one keeps the name the node already had
|
||||
if (!isBlankName(p.short_name)) {
|
||||
strncpy(owner.short_name, p.short_name, sizeof(owner.short_name));
|
||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||
sanitizeUtf8(owner.short_name, sizeof(owner.short_name));
|
||||
}
|
||||
owner.is_licensed = true;
|
||||
config.lora.override_duty_cycle = true;
|
||||
config.lora.tx_power = p.tx_power;
|
||||
@@ -1977,6 +1990,7 @@ void AdminModule::handleSetHamMode(const meshtastic_HamParameters &p)
|
||||
|
||||
service->reloadOwner(false);
|
||||
saveChanges(SEGMENT_CONFIG | SEGMENT_NODEDATABASE | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||
return true;
|
||||
}
|
||||
|
||||
AdminModule::AdminModule() : ProtobufModule("Admin", meshtastic_PortNum_ADMIN_APP, &meshtastic_AdminMessage_msg)
|
||||
|
||||
Reference in new issue
Block a user