Digipeater emits malformed path with a double asterisk (CALL**) in WIDE2-n mode #283

Closed
opened 2026-10-05 05:56:57 +02:00 by adam · 14 comments
Owner

Summary

When a received packet carries two already-consumed hops and no WIDE1-1, the
WIDE2-n branch of the digipeater produces an invalid path where the station
callsign is followed by two asterisks (CALL**).

Observed over the air on 433.775 MHz (received by a nearby tracker):

F1ZCJ-3>APFD11,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:!4321.45NV00225.50E# ...
                              ^^^^^^^^^^^

Still present in V3.2.4.

Cause

src/digi_utils.cpp, cleanPathAsterisks():

String cleanPathAsterisks(String path) {
    String terms[] = {",WIDE1*", ",WIDE2*", "*"};
    for (String term : terms) {
        int index = path.indexOf(term);
        if (index != -1) path.remove(index, term.length());   // only one occurrence
    }
    return path;
}

Each term is removed only once. When the path contains more than one consumed
hop, the extra asterisk survives. buildPacket() then does:

tempPath.replace("WIDE2-1", stationCallsign + "*");

which places CALL* immediately before the leftover *, yielding CALL**.

Note: the WIDE1-1 branch is protected by
if (tempPath.indexOf("*") != -1) return "";, but the WIDE2- branch has no
equivalent guard.

Reproduction

Digi in mode = 3, callsign CALL-10:

input path current output expected
N0CALL*,N1CALL-11,WIDE2-1*,WIDE3-3 N0CALL,N1CALL-11,CALL-10**,WIDE3-3 N0CALL,N1CALL-11,CALL-10*,WIDE3-3
N0CALL,N1CALL-11*,WIDE2-1*,WIDE3-3 N0CALL,N1CALL-11,CALL-10**,WIDE3-3 N0CALL,N1CALL-11,CALL-10*,WIDE3-3

Suggested fix

Loop until each term is exhausted:

String cleanPathAsterisks(String path) {
    String terms[] = {",WIDE1*", ",WIDE2*", "*"};
    for (String term : terms) {
        int index;
        while ((index = path.indexOf(term)) != -1) path.remove(index, term.length());
    }
    return path;
}

Verified against both cases above: output becomes CALL-10* as expected.

Related consideration

indexOf("WIDE2-") also matches the consumed form WIDE2-1*, so the digi
currently decides to repeat packets whose WIDE2 hop is already spent. Rejecting
those (as the WIDE1-1 branch does) would be more correct and would make the
malformed output impossible in the first place.

## Summary When a received packet carries **two already-consumed hops** and no `WIDE1-1`, the WIDE2-n branch of the digipeater produces an invalid path where the station callsign is followed by two asterisks (`CALL**`). Observed over the air on 433.775 MHz (received by a nearby tracker): ``` F1ZCJ-3>APFD11,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:!4321.45NV00225.50E# ... ^^^^^^^^^^^ ``` Still present in **V3.2.4**. ## Cause `src/digi_utils.cpp`, `cleanPathAsterisks()`: ```cpp String cleanPathAsterisks(String path) { String terms[] = {",WIDE1*", ",WIDE2*", "*"}; for (String term : terms) { int index = path.indexOf(term); if (index != -1) path.remove(index, term.length()); // only one occurrence } return path; } ``` Each term is removed **only once**. When the path contains more than one consumed hop, the extra asterisk survives. `buildPacket()` then does: ```cpp tempPath.replace("WIDE2-1", stationCallsign + "*"); ``` which places `CALL*` immediately before the leftover `*`, yielding `CALL**`. Note: the `WIDE1-1` branch is protected by `if (tempPath.indexOf("*") != -1) return "";`, but the `WIDE2-` branch has no equivalent guard. ## Reproduction Digi in `mode = 3`, callsign `CALL-10`: | input path | current output | expected | |---|---|---| | `N0CALL*,N1CALL-11,WIDE2-1*,WIDE3-3` | `N0CALL,N1CALL-11,CALL-10**,WIDE3-3` | `N0CALL,N1CALL-11,CALL-10*,WIDE3-3` | | `N0CALL,N1CALL-11*,WIDE2-1*,WIDE3-3` | `N0CALL,N1CALL-11,CALL-10**,WIDE3-3` | `N0CALL,N1CALL-11,CALL-10*,WIDE3-3` | ## Suggested fix Loop until each term is exhausted: ```cpp String cleanPathAsterisks(String path) { String terms[] = {",WIDE1*", ",WIDE2*", "*"}; for (String term : terms) { int index; while ((index = path.indexOf(term)) != -1) path.remove(index, term.length()); } return path; } ``` Verified against both cases above: output becomes `CALL-10*` as expected. ## Related consideration `indexOf("WIDE2-")` also matches the **consumed** form `WIDE2-1*`, so the digi currently decides to repeat packets whose WIDE2 hop is already spent. Rejecting those (as the `WIDE1-1` branch does) would be more correct and would make the malformed output impossible in the first place. <!-- github-sync-id:4942937961 -->
adam closed this issue 2026-10-05 05:56:58 +02:00
Author
Owner

Closing this — my analysis was based on an outdated local clone (V3.1.7), not on current code.

In V3.2.4 the WIDE1-1 branch already rejects paths carrying a consumed hop:

if (tempPath.indexOf("*") != -1) return "";

which covers the case I reported. Sorry for the noise, and thanks for the work.

Closing this — my analysis was based on an outdated local clone (V3.1.7), not on current code. In V3.2.4 the `WIDE1-1` branch already rejects paths carrying a consumed hop: ```cpp if (tempPath.indexOf("*") != -1) return ""; ``` which covers the case I reported. Sorry for the noise, and thanks for the work. <!-- github-comment-id:5039047616 -->
Author
Owner

Reopening — I closed this too hastily, and I was wrong.

I had checked the WIDE1-1 branch, which does carry the guard
if (tempPath.indexOf("*") != -1) return "";. But this report is about the
WIDE2-n branch of mode = 3, which has no such guard: it relies on
cleanPathAsterisks(), and that helper removes only one occurrence per term.

Captured off the air today, from a station running V3.2.4:

F1ZCP-3>APFD09,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:=4256.07N200056.12E#PHG5730/...
                              ^^^^^^^^^^^

Reproduction against the V3.2.4 code, mode = 3, callsign F4MLV-10:

input path V3.2.4 output
F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3 F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3
F6DEV,F6DEV-11*,WIDE2-1*,WIDE3-3 F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3
F6DEV,F6DEV-11,WIDE2-1*,WIDE3-3 F6DEV,F6DEV-11,F4MLV-10*,WIDE3-3 (ok — single *)

With two consumed hops, cleanPathAsterisks() clears only the first one; the
asterisk belonging to WIDE2-1* survives, and tempPath.replace("WIDE2-1", callsign + "*")
then lands right in front of it, producing CALL**.

Suggested fix, in cleanPathAsterisks():

for (String term : terms) {
    int index;
    while ((index = path.indexOf(term)) != -1) path.remove(index, term.length());
}

Verified: both failing cases above then yield F4MLV-10* as expected.

Sorry for the churn — the initial close was my mistake, the report stands.

Reopening — I closed this too hastily, and I was wrong. I had checked the `WIDE1-1` branch, which **does** carry the guard `if (tempPath.indexOf("*") != -1) return "";`. But this report is about the **`WIDE2-n` branch of `mode = 3`**, which has no such guard: it relies on `cleanPathAsterisks()`, and that helper removes only **one occurrence per term**. Captured off the air today, from a station running V3.2.4: ``` F1ZCP-3>APFD09,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:=4256.07N200056.12E#PHG5730/... ^^^^^^^^^^^ ``` Reproduction against the V3.2.4 code, `mode = 3`, callsign `F4MLV-10`: | input path | V3.2.4 output | |---|---| | `F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3` | `F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3` | | `F6DEV,F6DEV-11*,WIDE2-1*,WIDE3-3` | `F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3` | | `F6DEV,F6DEV-11,WIDE2-1*,WIDE3-3` | `F6DEV,F6DEV-11,F4MLV-10*,WIDE3-3` (ok — single `*`) | With two consumed hops, `cleanPathAsterisks()` clears only the first one; the asterisk belonging to `WIDE2-1*` survives, and `tempPath.replace("WIDE2-1", callsign + "*")` then lands right in front of it, producing `CALL**`. Suggested fix, in `cleanPathAsterisks()`: ```cpp for (String term : terms) { int index; while ((index = path.indexOf(term)) != -1) path.remove(index, term.length()); } ``` Verified: both failing cases above then yield `F4MLV-10*` as expected. Sorry for the churn — the initial close was my mistake, the report stands. <!-- github-comment-id:5039320856 -->
Author
Owner

Direct capture from the offending station itself, so this is no longer a reconstruction.

Station running V3.2.4, digi.mode = 3, callsign F4MLV-2. Serial output:

---> LoRa Packet Tx : F1ZCJ-3>APFD11,F6DEV,F4MLV-10,F4MLV-2**,WIDE3-3:!4321.45NV00225.50E# (Relais VHF UHF Le Mont Simel)
                                                    ^^^^^^^^^^

The incoming path carried two consumed hops (F4MLV-10* and WIDE2-1*).
cleanPathAsterisks() stripped only the first asterisk, so the one belonging to
WIDE2-1* survived, and tempPath.replace("WIDE2-1", callsign + "*") landed
directly in front of it.

Side effect visible in the same frame: F4MLV-10* comes out as F4MLV-10, i.e.
the previous digipeater's "has been repeated" mark is erased. Nothing in the path
then indicates that station already handled the frame.

Direct capture from the offending station itself, so this is no longer a reconstruction. Station running **V3.2.4**, `digi.mode = 3`, callsign `F4MLV-2`. Serial output: ``` ---> LoRa Packet Tx : F1ZCJ-3>APFD11,F6DEV,F4MLV-10,F4MLV-2**,WIDE3-3:!4321.45NV00225.50E# (Relais VHF UHF Le Mont Simel) ^^^^^^^^^^ ``` The incoming path carried two consumed hops (`F4MLV-10*` and `WIDE2-1*`). `cleanPathAsterisks()` stripped only the first asterisk, so the one belonging to `WIDE2-1*` survived, and `tempPath.replace("WIDE2-1", callsign + "*")` landed directly in front of it. Side effect visible in the same frame: `F4MLV-10*` comes out as `F4MLV-10`, i.e. the previous digipeater's "has been repeated" mark is erased. Nothing in the path then indicates that station already handled the frame. <!-- github-comment-id:5039377280 -->
Author
Owner

Glad you tested ;)

Glad you tested ;) <!-- github-comment-id:5039642161 -->
Author
Owner

Was it solved or not?

Was it solved or not? <!-- github-comment-id:5039651122 -->
Author
Owner

Tested on hardware. My earlier suggested fix was wrong — please disregard it.

Why the previous suggestion was wrong

Making cleanPathAsterisks() strip every asterisk removes the ** symptom but makes
the root cause worse: a consumed WIDE2-1* becomes an ordinary-looking WIDE2-1, so
the digi still repeats packets that have no hop left, and the previous digipeaters'
"has been repeated" marks are destroyed.

Root cause

Two separate issues in the WIDE2- branch of buildPacket():

  1. cleanPathAsterisks(path) is called before the decision, so a consumed
    WIDE2-1* is indistinguishable from a free token.
  2. Both the lookup (indexOf) and the substitution (replace) are substring
    operations, so they also match inside WIDE2-1*, and replace is global.

Fix that was validated

Decide on an exact comma-delimited token, and substitute positionally —
never with replace() in this branch. cleanPathAsterisks() is no longer called
here (it remains in use for the CrossFreq branch).

int pathTokenIndex(const String& path, const String& token) {
    int start = 0;
    while (start < path.length()) {
        int end = path.indexOf(",", start);
        if (end == -1) end = path.length();
        if (path.substring(start, end) == token) return start;
        start = end + 1;
    }
    return -1;
}
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) {
    int idx = pathTokenIndex(tempPath, "WIDE2-1");
    if (idx != -1) {
        tempPath = tempPath.substring(0, idx) + stationCallsign + "*"
                 + tempPath.substring(idx + 7);
    } else {
        idx = pathTokenIndex(tempPath, "WIDE2-2");
        if (idx == -1) return "";
        tempPath = tempPath.substring(0, idx) + stationCallsign + "*,WIDE2-1"
                 + tempPath.substring(idx + 7);
    }
}

Test cases

input path expected
F6DEV,CALL-10*,WIDE2-1*,WIDE3-3 "" — no free hop, must not repeat
F6DEV,CALL-10*,WIDE2-1,WIDE3-3 F6DEV,CALL-10*,MYCALL*,WIDE3-3
F6DEV,WIDE2-2 F6DEV,MYCALL*,WIDE2-1
F6DEV,WIDE2-2* ""
F6DEV,WIDE2-1*,WIDE2-1 F6DEV,WIDE2-1*,MYCALL* — mixed used/free, the case that breaks replace()

On-air validation

Built and flashed on a TTGO LoRa32 v2.1.6, digi.mode = 3, 433.775 MHz.
Serial capture, station F4MLV-2:

Previous digipeater's mark preserved:

Rx : F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1
Tx : F6ZDD-10>APLRG1,F4MLV-10*,F4MLV-2*

WIDE2-2 correctly decremented:

Rx : F4GCF-14>APWW11,F4MLV-10*,WIDE2-2
Tx : F4GCF-14>APWW11,F4MLV-10*,F4MLV-2*,WIDE2-1

Packets with no hop left are no longer repeated (previously they produced CALL**):

Rx : F6ZDW-3>APFD09,F6DEV,F4MLV-10*,WIDE2-1*,WIDE3-3    → no Tx
Rx : F4GCF-3>APFD32,F6DEV,F4MLV-10*,WIDE2-1*,WIDE4-4    → no Tx
Rx : F1ZCJ-3>APFD11,F6DEV,F4MLV-10*,WIDE2-1*,WIDE3-3    → no Tx

Across every frame repeated by the patched station: no **, no lost asterisk.

For reference, a neighbouring digi still running stock V3.2.4 keeps emitting the
malformed form, received here during the same session:

Rx : F6ZDW-3>APFD09,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3
Tested on hardware. **My earlier suggested fix was wrong** — please disregard it. ## Why the previous suggestion was wrong Making `cleanPathAsterisks()` strip *every* asterisk removes the `**` symptom but makes the root cause worse: a consumed `WIDE2-1*` becomes an ordinary-looking `WIDE2-1`, so the digi still repeats packets that have no hop left, and the previous digipeaters' "has been repeated" marks are destroyed. ## Root cause Two separate issues in the `WIDE2-` branch of `buildPacket()`: 1. `cleanPathAsterisks(path)` is called **before** the decision, so a consumed `WIDE2-1*` is indistinguishable from a free token. 2. Both the lookup (`indexOf`) and the substitution (`replace`) are **substring** operations, so they also match inside `WIDE2-1*`, and `replace` is global. ## Fix that was validated Decide on an **exact comma-delimited token**, and substitute **positionally** — never with `replace()` in this branch. `cleanPathAsterisks()` is no longer called here (it remains in use for the CrossFreq branch). ```cpp int pathTokenIndex(const String& path, const String& token) { int start = 0; while (start < path.length()) { int end = path.indexOf(",", start); if (end == -1) end = path.length(); if (path.substring(start, end) == token) return start; start = end + 1; } return -1; } ``` ```cpp } else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) { int idx = pathTokenIndex(tempPath, "WIDE2-1"); if (idx != -1) { tempPath = tempPath.substring(0, idx) + stationCallsign + "*" + tempPath.substring(idx + 7); } else { idx = pathTokenIndex(tempPath, "WIDE2-2"); if (idx == -1) return ""; tempPath = tempPath.substring(0, idx) + stationCallsign + "*,WIDE2-1" + tempPath.substring(idx + 7); } } ``` ## Test cases | input path | expected | |---|---| | `F6DEV,CALL-10*,WIDE2-1*,WIDE3-3` | `""` — no free hop, must not repeat | | `F6DEV,CALL-10*,WIDE2-1,WIDE3-3` | `F6DEV,CALL-10*,MYCALL*,WIDE3-3` | | `F6DEV,WIDE2-2` | `F6DEV,MYCALL*,WIDE2-1` | | `F6DEV,WIDE2-2*` | `""` | | `F6DEV,WIDE2-1*,WIDE2-1` | `F6DEV,WIDE2-1*,MYCALL*` — mixed used/free, the case that breaks `replace()` | ## On-air validation Built and flashed on a TTGO LoRa32 v2.1.6, `digi.mode = 3`, 433.775 MHz. Serial capture, station `F4MLV-2`: Previous digipeater's mark preserved: ``` Rx : F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 Tx : F6ZDD-10>APLRG1,F4MLV-10*,F4MLV-2* ``` `WIDE2-2` correctly decremented: ``` Rx : F4GCF-14>APWW11,F4MLV-10*,WIDE2-2 Tx : F4GCF-14>APWW11,F4MLV-10*,F4MLV-2*,WIDE2-1 ``` Packets with no hop left are no longer repeated (previously they produced `CALL**`): ``` Rx : F6ZDW-3>APFD09,F6DEV,F4MLV-10*,WIDE2-1*,WIDE3-3 → no Tx Rx : F4GCF-3>APFD32,F6DEV,F4MLV-10*,WIDE2-1*,WIDE4-4 → no Tx Rx : F1ZCJ-3>APFD11,F6DEV,F4MLV-10*,WIDE2-1*,WIDE3-3 → no Tx ``` Across every frame repeated by the patched station: no `**`, no lost asterisk. For reference, a neighbouring digi still running stock V3.2.4 keeps emitting the malformed form, received here during the same session: ``` Rx : F6ZDW-3>APFD09,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3 ``` <!-- github-comment-id:5043026849 -->
Author
Owner

you should find the most recent branch with few fixes for this as well (but still , test it and don't share the news yet! I'm preparing info before releasing it)

you should find the most recent branch with few fixes for this as well (but still , test it and don't share the news yet! I'm preparing info before releasing it) <!-- github-comment-id:5046288934 -->
Author
Owner

For the sake of discretion, I sent you an email at the address found on QRZ.

For the sake of discretion, I sent you an email at the address found on QRZ. <!-- github-comment-id:5047743497 -->
Author
Owner

Awesome review.

But i got a little confused.

Is this "new" v4.0.0 working or what needs fix right away?

I mean: the * has to be taken away before repeating .... as there could be not there two * in the same path, right?

Ricardo.


De: moricef @.>
Enviado: miércoles, 22 de julio de 2026 10:23
Para: richonguzman/LoRa_APRS_iGate @.
>
Cc: Ricardo Guzman @.>; Comment @.>
Asunto: Re: [richonguzman/LoRa_APRS_iGate] Digipeater emits malformed path with a double asterisk (CALL**) in WIDE2-n mode (Issue #443)

[https://avatars.githubusercontent.com/u/23724137?s=20&v=4]moricef left a comment (richonguzman/LoRa_APRS_iGate#443)https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047375421

Hi Ricardo,

I tested the recent branch as you asked ― keeping it off the public issue, of
course. Here is what I found, good and bad.

What works well

The reworked configuration menu is a real improvement. It is much easier to
navigate than the previous one, and the grouping makes far more sense.

The inline map is a very good fit for an iGate. An iGate has internet by
definition, so the tiles load, and seeing the heard stations on a map is exactly
what one wants there. Nice addition.

processMode3Path() is written the right way ― walking the path token by
token, checking token == stationCallsign, verifying the earlier tokens are
marked, and refusing when already digipeated. That is precisely the approach the
WIDE2- branch is missing (see below).

Issue: previous digipeaters' marks are erased

Tested on a TTGO LoRa32 v2.1.6, digi.mode = 2 (the new numbering), 433.775 MHz,
callsign F4MLV-15. Every single digipeat strips the * from the previous
digipeater:

Rx F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F6ZDD-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F4JQT-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F6DEV-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F1OFG-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1 → Tx F5ZQB-10>APLRG1,F5ZQC-10,F4MLV-15*
Rx F6DEV-13>APWW11,F4MLV-10*,WIDE2-1 → Tx F6DEV-13>APWW11,F4MLV-10,F4MLV-15*
Rx F4GCF-11>APIN21,F4MLV-10*,WIDE2-2 → Tx F4GCF-11>APIN21,F4MLV-10,F4MLV-15*,WIDE2-1
Rx F1ZCJ-3>APFD11,F6DEV*,WIDE2-2*,... → Tx F1ZCJ-3>APFD11,F6DEV,WIDE2-2*,...

cleanPath() is called on the whole path before substitution, so the first *
it finds gets removed ― and that one belongs to a station that really did
digipeat the frame. Once its mark is gone, nothing in the path stops that station
from repeating the same frame again if it hears it back. I have a capture from my
own network where F4MLV-10 appears twice in the same path, which is what
this leads to in practice.

The ** case reported in #443https://github.com/richonguzman/LoRa_APRS_iGate/issues/443 is still present too: the same cleanPath() only
removes one occurrence per term, so when a consumed WIDE2-1* survives, the
substitution lands right in front of the leftover asterisk. I could not capture it
on this branch (the stations producing those paths are too far for my indoor test
antenna) but it is the same code path as in #443https://github.com/richonguzman/LoRa_APRS_iGate/issues/443.

Both would be solved by doing in the WIDE2- branch what processMode3Path()
already does: find an exact comma-delimited token, substitute at that
index rather than with replace(), and leave the other asterisks untouched.

Heads-up: the digi mode numbers changed meaning

In this branch:

  • WIDE1-1 branch is now digiMode == 1 || digiMode == 2 (was 2 || 3)
  • WIDE2-n branch is now digiMode == 2 (was 3)
  • digiMode == 3 is the new callsign-based digipeating

So an existing installation configured with mode = 3 (WIDE1 + WIDE2) will
silently become "only repeat if my callsign is explicitly in the path" after the
update ― it will stop digipeating WIDEn-N altogether, with nothing in the logs
to explain why.

Worth adding a migration when loading an older config, or at least a warning in
the release notes.

73

―
Reply to this email directly, view it on GitHubhttps://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AQWBUGQPWZEUPQGCNIYC7DT5GDE77A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG4ZTONJUGIY2M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047375421, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AQWBUGUUX33YO6ZOWU2TYA35GDE77AVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC.
You are receiving this because you commented.Message ID: @.***>

Awesome review. But i got a little confused. Is this "new" v4.0.0 working or what needs fix right away? I mean: the * has to be taken away before repeating .... as there could be not there two * in the same path, right? Ricardo. ________________________________ De: moricef ***@***.***> Enviado: miércoles, 22 de julio de 2026 10:23 Para: richonguzman/LoRa_APRS_iGate ***@***.***> Cc: Ricardo Guzman ***@***.***>; Comment ***@***.***> Asunto: Re: [richonguzman/LoRa_APRS_iGate] Digipeater emits malformed path with a double asterisk (CALL**) in WIDE2-n mode (Issue #443) [https://avatars.githubusercontent.com/u/23724137?s=20&v=4]moricef left a comment (richonguzman/LoRa_APRS_iGate#443)<https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047375421> Hi Ricardo, I tested the recent branch as you asked ― keeping it off the public issue, of course. Here is what I found, good and bad. What works well The reworked configuration menu is a real improvement. It is much easier to navigate than the previous one, and the grouping makes far more sense. The inline map is a very good fit for an iGate. An iGate has internet by definition, so the tiles load, and seeing the heard stations on a map is exactly what one wants there. Nice addition. processMode3Path() is written the right way ― walking the path token by token, checking token == stationCallsign, verifying the earlier tokens are marked, and refusing when already digipeated. That is precisely the approach the WIDE2- branch is missing (see below). Issue: previous digipeaters' marks are erased Tested on a TTGO LoRa32 v2.1.6, digi.mode = 2 (the new numbering), 433.775 MHz, callsign F4MLV-15. Every single digipeat strips the * from the previous digipeater: Rx F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F6ZDD-10>APLRG1,F4MLV-10,F4MLV-15* Rx F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F4JQT-10>APLRG1,F4MLV-10,F4MLV-15* Rx F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F6DEV-10>APLRG1,F4MLV-10,F4MLV-15* Rx F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx F1OFG-10>APLRG1,F4MLV-10,F4MLV-15* Rx F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1 → Tx F5ZQB-10>APLRG1,F5ZQC-10,F4MLV-15* Rx F6DEV-13>APWW11,F4MLV-10*,WIDE2-1 → Tx F6DEV-13>APWW11,F4MLV-10,F4MLV-15* Rx F4GCF-11>APIN21,F4MLV-10*,WIDE2-2 → Tx F4GCF-11>APIN21,F4MLV-10,F4MLV-15*,WIDE2-1 Rx F1ZCJ-3>APFD11,F6DEV*,WIDE2-2*,... → Tx F1ZCJ-3>APFD11,F6DEV,WIDE2-2*,... cleanPath() is called on the whole path before substitution, so the first * it finds gets removed ― and that one belongs to a station that really did digipeat the frame. Once its mark is gone, nothing in the path stops that station from repeating the same frame again if it hears it back. I have a capture from my own network where F4MLV-10 appears twice in the same path, which is what this leads to in practice. The ** case reported in #443<https://github.com/richonguzman/LoRa_APRS_iGate/issues/443> is still present too: the same cleanPath() only removes one occurrence per term, so when a consumed WIDE2-1* survives, the substitution lands right in front of the leftover asterisk. I could not capture it on this branch (the stations producing those paths are too far for my indoor test antenna) but it is the same code path as in #443<https://github.com/richonguzman/LoRa_APRS_iGate/issues/443>. Both would be solved by doing in the WIDE2- branch what processMode3Path() already does: find an exact comma-delimited token, substitute at that index rather than with replace(), and leave the other asterisks untouched. Heads-up: the digi mode numbers changed meaning In this branch: * WIDE1-1 branch is now digiMode == 1 || digiMode == 2 (was 2 || 3) * WIDE2-n branch is now digiMode == 2 (was 3) * digiMode == 3 is the new callsign-based digipeating So an existing installation configured with mode = 3 (WIDE1 + WIDE2) will silently become "only repeat if my callsign is explicitly in the path" after the update ― it will stop digipeating WIDEn-N altogether, with nothing in the logs to explain why. Worth adding a migration when loading an older config, or at least a warning in the release notes. 73 ― Reply to this email directly, view it on GitHub<https://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AQWBUGQPWZEUPQGCNIYC7DT5GDE77A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG4ZTONJUGIY2M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047375421>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AQWBUGUUX33YO6ZOWU2TYA35GDE77AVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC>. You are receiving this because you commented.Message ID: ***@***.***> <!-- github-comment-id:5047775888 -->
Author
Owner

Follow-up — I said "nothing else blocking", but the unit kept running and
caught
something worse. Two things, then a heads-up.

1. A fully spent path gets repeated AND re-inflated

Rx : F6DEV-11>APLRG1,F6DEV*,WIDE2-2*,F4MLV-10*
Tx : F6DEV-11>APLRG1,F6DEV,F4MLV-15*,WIDE2-1*,F4MLV-10*

Every token in the received path was already marked — there was no hop
left at
all
, the frame should have been dropped. But indexOf("WIDE2-2") matched
inside
the spent WIDE2-2*, and the replace expanded it into CALL*,WIDE2-1. So
a brand
new WIDE2-1 token now exists in a path that was exhausted, and the frame
gets a
second life instead of dying.

This is the same root cause as the asterisk problem: substring match
instead of an
exact comma-delimited token.

2. The previous digipeater's mark is erased on every single repeat

Not occasional — systematic:

Rx : F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1   →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1   →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1   →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F4JQT-4>APLRG1,F4MLV-10*,WIDE2-1    →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1   →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F6DEV-13>APWW11,F4MLV-10*,WIDE2-1   →  Tx : ...,F4MLV-10,F4MLV-15*
Rx : F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1   →  Tx : ...,F5ZQC-10,F4MLV-15*

F4MLV-10 no longer appears as having handled the frame, so another digi
can
legitimately repeat it again. I have a capture from my network where
F4MLV-10
ends up twice in the same path — that is what this leads to in practice.

You already have the right code

processMode3Path() does exactly what the WIDE2- branch needs: it walks
the
path token by token, compares token == stationCallsign, checks that
earlier
tokens are marked, and refuses when already repeated.

Applying that same approach to the WIDE2- branch — exact token,
substitute at
its index, leave the other asterisks alone — fixes all three symptoms at
once
(the ** of #443, the erased marks, and the re-inflated spent path).

Heads-up before release: the mode numbers changed meaning

  • WIDE1-1 branch is now digiMode == 1 || 2 (was 2 || 3)
  • WIDE2-n branch is now digiMode == 2 (was 3)
  • digiMode == 3 is the new callsign-based digipeating

An existing installation set to mode = 3 (WIDE1 + WIDE2) will silently
become
"only repeat if my callsign is explicitly in the path" after updating — it
stops
digipeating WIDEn-N altogether, with nothing in the logs to explain why.

Worth a migration when loading an older config, or at least a line in the
release
notes. On a hilltop digipeater that one would be painful to diagnose
remotely.

73,
F4MLV

Le mer. 22 juil. 2026 à 16:58, Ricardo Guzman @.***> a
écrit :

richonguzman left a comment (richonguzman/LoRa_APRS_iGate#443)
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047775888
Awesome review.

But i got a little confused.

Is this "new" v4.0.0 working or what needs fix right away?

I mean: the * has to be taken away before repeating .... as there could be
not there two * in the same path, right?

Ricardo.


De: moricef @.>
Enviado: miércoles, 22 de julio de 2026 10:23
Para: richonguzman/LoRa_APRS_iGate @.
>
Cc: Ricardo Guzman @.>; Comment @.>
Asunto: Re: [richonguzman/LoRa_APRS_iGate] Digipeater emits malformed path
with a double asterisk (CALL**) in WIDE2-n mode (Issue #443)

[https://avatars.githubusercontent.com/u/23724137?s=20&v=4]moricef left a
comment (richonguzman/LoRa_APRS_iGate#443)<
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047375421>

Hi Ricardo,

I tested the recent branch as you asked ― keeping it off the public issue,
of
course. Here is what I found, good and bad.

What works well

The reworked configuration menu is a real improvement. It is much easier
to
navigate than the previous one, and the grouping makes far more sense.

The inline map is a very good fit for an iGate. An iGate has internet by
definition, so the tiles load, and seeing the heard stations on a map is
exactly
what one wants there. Nice addition.

processMode3Path() is written the right way ― walking the path token by
token, checking token == stationCallsign, verifying the earlier tokens are
marked, and refusing when already digipeated. That is precisely the
approach the
WIDE2- branch is missing (see below).

Issue: previous digipeaters' marks are erased

Tested on a TTGO LoRa32 v2.1.6, digi.mode = 2 (the new numbering), 433.775
MHz,
callsign F4MLV-15. Every single digipeat strips the * from the previous
digipeater:

Rx F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx
F6ZDD-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx
F4JQT-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx
F6DEV-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx
F1OFG-10>APLRG1,F4MLV-10,F4MLV-15*
Rx F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1 → Tx
F5ZQB-10>APLRG1,F5ZQC-10,F4MLV-15*
Rx F6DEV-13>APWW11,F4MLV-10*,WIDE2-1 → Tx
F6DEV-13>APWW11,F4MLV-10,F4MLV-15*
Rx F4GCF-11>APIN21,F4MLV-10*,WIDE2-2 → Tx
F4GCF-11>APIN21,F4MLV-10,F4MLV-15*,WIDE2-1
Rx F1ZCJ-3>APFD11,F6DEV*,WIDE2-2*,... → Tx
F1ZCJ-3>APFD11,F6DEV,WIDE2-2*,...

cleanPath() is called on the whole path before substitution, so the first
*
it finds gets removed ― and that one belongs to a station that really did
digipeat the frame. Once its mark is gone, nothing in the path stops that
station
from repeating the same frame again if it hears it back. I have a capture
from my
own network where F4MLV-10 appears twice in the same path, which is what
this leads to in practice.

The ** case reported in #443<
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443> is still
present too: the same cleanPath() only
removes one occurrence per term, so when a consumed WIDE2-1* survives, the
substitution lands right in front of the leftover asterisk. I could not
capture it
on this branch (the stations producing those paths are too far for my
indoor test
antenna) but it is the same code path as in #443<
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443>.

Both would be solved by doing in the WIDE2- branch what processMode3Path()
already does: find an exact comma-delimited token, substitute at that
index rather than with replace(), and leave the other asterisks untouched.

Heads-up: the digi mode numbers changed meaning

In this branch:

  • WIDE1-1 branch is now digiMode == 1 || digiMode == 2 (was 2 || 3)
  • WIDE2-n branch is now digiMode == 2 (was 3)
  • digiMode == 3 is the new callsign-based digipeating

So an existing installation configured with mode = 3 (WIDE1 + WIDE2) will
silently become "only repeat if my callsign is explicitly in the path"
after the
update ― it will stop digipeating WIDEn-N altogether, with nothing in the
logs
to explain why.

Worth adding a migration when loading an older config, or at least a
warning in
the release notes.

73

―
Reply to this email directly, view it on GitHub<
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AQWBUGQPWZEUPQGCNIYC7DT5GDE77A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG4ZTONJUGIY2M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047375421>,
or unsubscribe<
https://github.com/notifications/unsubscribe-auth/AQWBUGUUX33YO6ZOWU2TYA35GDE77AVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC>.

You are receiving this because you commented.Message ID: @.***>

—
Reply to this email directly, view it on GitHub
https://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AFVAA2KDMKXB2OYSXC74BMD5GDJCTA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG43TONJYHA4KM4TFMFZW63VMON2GC5DFL5RWQYLOM5S2KZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047775888,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AFVAA2M5DJLEB3BR225O2IL5GDJCTAVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC
.
You are receiving this because you modified the open/close state.Message
ID: @.***>

Follow-up — I said "nothing else blocking", but the unit kept running and caught something worse. Two things, then a heads-up. ## 1. A fully spent path gets repeated AND re-inflated ``` Rx : F6DEV-11>APLRG1,F6DEV*,WIDE2-2*,F4MLV-10* Tx : F6DEV-11>APLRG1,F6DEV,F4MLV-15*,WIDE2-1*,F4MLV-10* ``` Every token in the received path was already marked — there was **no hop left at all**, the frame should have been dropped. But `indexOf("WIDE2-2")` matched inside the spent `WIDE2-2*`, and the replace expanded it into `CALL*,WIDE2-1`. So a brand new `WIDE2-1` token now exists in a path that was exhausted, and the frame gets a second life instead of dying. This is the same root cause as the asterisk problem: substring match instead of an exact comma-delimited token. ## 2. The previous digipeater's mark is erased on every single repeat Not occasional — systematic: ``` Rx : F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F4JQT-4>APLRG1,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F6DEV-13>APWW11,F4MLV-10*,WIDE2-1 → Tx : ...,F4MLV-10,F4MLV-15* Rx : F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1 → Tx : ...,F5ZQC-10,F4MLV-15* ``` `F4MLV-10` no longer appears as having handled the frame, so another digi can legitimately repeat it again. I have a capture from my network where `F4MLV-10` ends up **twice** in the same path — that is what this leads to in practice. ## You already have the right code `processMode3Path()` does exactly what the `WIDE2-` branch needs: it walks the path token by token, compares `token == stationCallsign`, checks that earlier tokens are marked, and refuses when already repeated. Applying that same approach to the `WIDE2-` branch — exact token, substitute at its index, leave the other asterisks alone — fixes all three symptoms at once (the `**` of #443, the erased marks, and the re-inflated spent path). ## Heads-up before release: the mode numbers changed meaning - `WIDE1-1` branch is now `digiMode == 1 || 2` (was `2 || 3`) - `WIDE2-n` branch is now `digiMode == 2` (was `3`) - `digiMode == 3` is the new callsign-based digipeating An existing installation set to `mode = 3` (WIDE1 + WIDE2) will silently become "only repeat if my callsign is explicitly in the path" after updating — it stops digipeating `WIDEn-N` altogether, with nothing in the logs to explain why. Worth a migration when loading an older config, or at least a line in the release notes. On a hilltop digipeater that one would be painful to diagnose remotely. 73, F4MLV Le mer. 22 juil. 2026 à 16:58, Ricardo Guzman ***@***.***> a écrit : > *richonguzman* left a comment (richonguzman/LoRa_APRS_iGate#443) > <https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047775888> > Awesome review. > > But i got a little confused. > > Is this "new" v4.0.0 working or what needs fix right away? > > I mean: the * has to be taken away before repeating .... as there could be > not there two * in the same path, right? > > Ricardo. > > > ________________________________ > De: moricef ***@***.***> > Enviado: miércoles, 22 de julio de 2026 10:23 > Para: richonguzman/LoRa_APRS_iGate ***@***.***> > Cc: Ricardo Guzman ***@***.***>; Comment ***@***.***> > Asunto: Re: [richonguzman/LoRa_APRS_iGate] Digipeater emits malformed path > with a double asterisk (CALL**) in WIDE2-n mode (Issue #443) > > [https://avatars.githubusercontent.com/u/23724137?s=20&v=4]moricef left a > comment (richonguzman/LoRa_APRS_iGate#443)< > https://github.com/richonguzman/LoRa_APRS_iGate/issues/443#issuecomment-5047375421> > > > Hi Ricardo, > > I tested the recent branch as you asked ― keeping it off the public issue, > of > course. Here is what I found, good and bad. > > What works well > > The reworked configuration menu is a real improvement. It is much easier > to > navigate than the previous one, and the grouping makes far more sense. > > The inline map is a very good fit for an iGate. An iGate has internet by > definition, so the tiles load, and seeing the heard stations on a map is > exactly > what one wants there. Nice addition. > > processMode3Path() is written the right way ― walking the path token by > token, checking token == stationCallsign, verifying the earlier tokens are > marked, and refusing when already digipeated. That is precisely the > approach the > WIDE2- branch is missing (see below). > > Issue: previous digipeaters' marks are erased > > Tested on a TTGO LoRa32 v2.1.6, digi.mode = 2 (the new numbering), 433.775 > MHz, > callsign F4MLV-15. Every single digipeat strips the * from the previous > digipeater: > > Rx F6ZDD-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx > F6ZDD-10>APLRG1,F4MLV-10,F4MLV-15* > Rx F4JQT-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx > F4JQT-10>APLRG1,F4MLV-10,F4MLV-15* > Rx F6DEV-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx > F6DEV-10>APLRG1,F4MLV-10,F4MLV-15* > Rx F1OFG-10>APLRG1,F4MLV-10*,WIDE2-1 → Tx > F1OFG-10>APLRG1,F4MLV-10,F4MLV-15* > Rx F5ZQB-10>APLRG1,F5ZQC-10*,WIDE2-1 → Tx > F5ZQB-10>APLRG1,F5ZQC-10,F4MLV-15* > Rx F6DEV-13>APWW11,F4MLV-10*,WIDE2-1 → Tx > F6DEV-13>APWW11,F4MLV-10,F4MLV-15* > Rx F4GCF-11>APIN21,F4MLV-10*,WIDE2-2 → Tx > F4GCF-11>APIN21,F4MLV-10,F4MLV-15*,WIDE2-1 > Rx F1ZCJ-3>APFD11,F6DEV*,WIDE2-2*,... → Tx > F1ZCJ-3>APFD11,F6DEV,WIDE2-2*,... > > > cleanPath() is called on the whole path before substitution, so the first > * > it finds gets removed ― and that one belongs to a station that really did > digipeat the frame. Once its mark is gone, nothing in the path stops that > station > from repeating the same frame again if it hears it back. I have a capture > from my > own network where F4MLV-10 appears twice in the same path, which is what > this leads to in practice. > > The ** case reported in #443< > https://github.com/richonguzman/LoRa_APRS_iGate/issues/443> is still > present too: the same cleanPath() only > removes one occurrence per term, so when a consumed WIDE2-1* survives, the > substitution lands right in front of the leftover asterisk. I could not > capture it > on this branch (the stations producing those paths are too far for my > indoor test > antenna) but it is the same code path as in #443< > https://github.com/richonguzman/LoRa_APRS_iGate/issues/443>. > > Both would be solved by doing in the WIDE2- branch what processMode3Path() > already does: find an exact comma-delimited token, substitute at that > index rather than with replace(), and leave the other asterisks untouched. > > Heads-up: the digi mode numbers changed meaning > > In this branch: > > * WIDE1-1 branch is now digiMode == 1 || digiMode == 2 (was 2 || 3) > * WIDE2-n branch is now digiMode == 2 (was 3) > * digiMode == 3 is the new callsign-based digipeating > > So an existing installation configured with mode = 3 (WIDE1 + WIDE2) will > silently become "only repeat if my callsign is explicitly in the path" > after the > update ― it will stop digipeating WIDEn-N altogether, with nothing in the > logs > to explain why. > > Worth adding a migration when loading an older config, or at least a > warning in > the release notes. > > 73 > > ― > Reply to this email directly, view it on GitHub< > https://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AQWBUGQPWZEUPQGCNIYC7DT5GDE77A5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG4ZTONJUGIY2M4TFMFZW63VHMNXW23LFNZ2KKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047375421>, > or unsubscribe< > https://github.com/notifications/unsubscribe-auth/AQWBUGUUX33YO6ZOWU2TYA35GDE77AVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC>. > > You are receiving this because you commented.Message ID: ***@***.***> > > — > Reply to this email directly, view it on GitHub > <https://github.com/richonguzman/LoRa_APRS_iGate/issues/443?email_source=notifications&email_token=AFVAA2KDMKXB2OYSXC74BMD5GDJCTA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMBUG43TONJYHA4KM4TFMFZW63VMON2GC5DFL5RWQYLOM5S2KZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5047775888>, > or unsubscribe > <https://github.com/notifications/unsubscribe-auth/AFVAA2M5DJLEB3BR225O2IL5GDJCTAVCNFSNUABFKJSXA33TNF2G64TZHM2TSOJVGU4TGNJXHNEXG43VMU5TIOJUGI4TGNZZGYY2C5QC> > . > You are receiving this because you modified the open/close state.Message > ID: ***@***.***> > <!-- github-comment-id:5048094165 -->
Author
Owner

Hi Moricef, can we return to this. Could you check this test on your side with V4.0.0 ?

Hi Moricef, can we return to this. Could you check this test on your side with V4.0.0 ? <!-- github-comment-id:5318505654 -->
Author
Owner

Hi Ricardo,

Checked against the V4.0.0 tag — the behaviour cannot have changed, because src/digi_utils.cpp is byte-identical to what it was when this issue was opened:

$ git log --since=2026-07-21 origin/main -- src/digi_utils.cpp
(no commits)

$ git show V4.0.0:src/digi_utils.cpp | md5sum
0e0d912556639028d7de025410f14903

$ git show <last commit before 2026-07-21>:src/digi_utils.cpp | md5sum
0e0d912556639028d7de025410f14903

So both halves of the cause are still in place on V4.0.0:
src/digi_utils.cpp on V4.0.0 — cleanPath() still removes a single occurrence per term:

String cleanPath(String path) {
String terms[] = {"WIDE1*,", "WIDE2*,", "*"};
for (String term : terms) {
int index = path.indexOf(term);
if (index != -1) path.remove(index, term.length()); // one occurrence only
}
return path;
}

and the WIDE2-n branch (line 95) still substitutes by substring rather than by token:

} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) {
tempPath = cleanPath(path);
if (tempPath.indexOf("WIDE2-1") != -1) {
tempPath.replace("WIDE2-1", stationCallsign + "*");

Exact trigger. I could not capture it on the air again — the stations producing those paths are out of reach of my indoor antenna — so I reproduced the two functions verbatim on the host. The ** appears when an asterisk sits anywhere earlier in the path and WIDE2-1 is itself already consumed:

in : F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3
out : F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3 <-- malformed

That is byte-for-byte the tail of the frame reported in the issue. Step by step: cleanPath() finds the first * (the one on F6DEV) and removes it, leaving the one on WIDE2-1* in place; indexOf("WIDE2-1") then matches inside WIDE2-1*, and replace() inserts F4MLV-10* right in front of the surviving asterisk.

Note the second-order problem: WIDE2-1* is an already-used hop. It must not be consumed a second time at all — the malformed path is only the visible symptom.

Fix. Same approach processMode3Path() already takes: match an exact comma-delimited token and substitute at that index. No replace(), and the other asterisks are left alone.

static int pathTokenIndex(const String& path, const String& token) {
unsigned int start = 0;
while (start < path.length()) {
int end = path.indexOf(",", start);
if (end == -1) end = path.length();
if (path.substring(start, end) == token) return start;
start = end + 1;
}
return -1;
}

} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) {
int idx = pathTokenIndex(tempPath, "WIDE2-1");
if (idx != -1) {
tempPath = tempPath.substring(0, idx) + stationCallsign + "" + tempPath.substring(idx + 7);
} else {
idx = pathTokenIndex(tempPath, "WIDE2-2");
if (idx == -1) return "";
tempPath = tempPath.substring(0, idx) + stationCallsign + "
,WIDE2-1" + tempPath.substring(idx + 7);
}
}

Same three inputs through the patched code:

F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3 -> (hop already used, correct)
F6DEV*,F6DEV-11,WIDE2-1,WIDE3-3 -> F6DEV,F6DEV-11,F4MLV-10*,WIDE3-3
F6DEV*,F6DEV-11,WIDE2-2,WIDE3-3 -> F6DEV,F6DEV-11,F4MLV-10*,WIDE2-1,WIDE3-3

I have been running this on my own fork on F4MLV-10 (mountain-top digi, ~150 frames/h, mode 2). Confirmed on the air, e.g. F4MLV-7>APLRT1,WIDE1-1,WIDE2-1 relayed as F4MLV-7>APLRT1,F4MLV-10*,WIDE2-1 — WIDE1-1 consumed, WIDE2-1 left intact.

Happy to open a PR if that helps.

73

Hi Ricardo, Checked against the V4.0.0 tag — the behaviour cannot have changed, because src/digi_utils.cpp is byte-identical to what it was when this issue was opened: $ git log --since=2026-07-21 origin/main -- src/digi_utils.cpp (no commits) $ git show V4.0.0:src/digi_utils.cpp | md5sum 0e0d912556639028d7de025410f14903 $ git show <last commit before 2026-07-21>:src/digi_utils.cpp | md5sum 0e0d912556639028d7de025410f14903 So both halves of the cause are still in place on V4.0.0: src/digi_utils.cpp on V4.0.0 — cleanPath() still removes a single occurrence per term: String cleanPath(String path) { String terms[] = {"WIDE1*,", "WIDE2*,", "*"}; for (String term : terms) { int index = path.indexOf(term); if (index != -1) path.remove(index, term.length()); // one occurrence only } return path; } and the WIDE2-n branch (line 95) still substitutes by substring rather than by token: } else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { tempPath = cleanPath(path); if (tempPath.indexOf("WIDE2-1") != -1) { tempPath.replace("WIDE2-1", stationCallsign + "*"); Exact trigger. I could not capture it on the air again — the stations producing those paths are out of reach of my indoor antenna — so I reproduced the two functions verbatim on the host. The ** appears when an asterisk sits anywhere earlier in the path and WIDE2-1 is itself already consumed: in : F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3 out : F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3 <-- malformed That is byte-for-byte the tail of the frame reported in the issue. Step by step: cleanPath() finds the first * (the one on F6DEV) and removes it, leaving the one on WIDE2-1* in place; indexOf("WIDE2-1") then matches inside WIDE2-1*, and replace() inserts F4MLV-10* right in front of the surviving asterisk. Note the second-order problem: WIDE2-1* is an already-used hop. It must not be consumed a second time at all — the malformed path is only the visible symptom. Fix. Same approach processMode3Path() already takes: match an exact comma-delimited token and substitute at that index. No replace(), and the other asterisks are left alone. static int pathTokenIndex(const String& path, const String& token) { unsigned int start = 0; while (start < path.length()) { int end = path.indexOf(",", start); if (end == -1) end = path.length(); if (path.substring(start, end) == token) return start; start = end + 1; } return -1; } } else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { int idx = pathTokenIndex(tempPath, "WIDE2-1"); if (idx != -1) { tempPath = tempPath.substring(0, idx) + stationCallsign + "*" + tempPath.substring(idx + 7); } else { idx = pathTokenIndex(tempPath, "WIDE2-2"); if (idx == -1) return ""; tempPath = tempPath.substring(0, idx) + stationCallsign + "*,WIDE2-1" + tempPath.substring(idx + 7); } } Same three inputs through the patched code: F6DEV*,F6DEV-11,WIDE2-1*,WIDE3-3 -> <not digipeated> (hop already used, correct) F6DEV*,F6DEV-11,WIDE2-1,WIDE3-3 -> F6DEV,F6DEV-11,F4MLV-10*,WIDE3-3 F6DEV*,F6DEV-11,WIDE2-2,WIDE3-3 -> F6DEV,F6DEV-11,F4MLV-10*,WIDE2-1,WIDE3-3 I have been running this on my own fork on F4MLV-10 (mountain-top digi, ~150 frames/h, mode 2). Confirmed on the air, e.g. F4MLV-7>APLRT1,WIDE1-1,WIDE2-1 relayed as F4MLV-7>APLRT1,F4MLV-10*,WIDE2-1 — WIDE1-1 consumed, WIDE2-1 left intact. Happy to open a PR if that helps. 73 <!-- github-comment-id:5319603538 -->
Author
Owner

Hi Ricardo,

Checked on 4.0.1 (2026-09-07): src/digi_utils.cpp is byte-identical to V4.0.0 — git log V4.0.0..main -- src/digi_utils.cpp returns nothing — so the behaviour is unchanged.

Since the trigger is hard to catch on the air, I wrapped it in a small host harness that compiles your actual digi_utils.cpp against stub headers. No firmware source is modified, no board needed:

g++ -std=c++17 -Istubs -o run digi_host_test.cpp ../src/digi_utils.cpp && ./run

On current main, 16 cases covering modes 1/2/3, cross-frequency and exhausted hops:

13 passed, 3 failed

All three failures sit in the exhausted hops block:

FAIL  mode 2  WIDE2-1 already used
        in       : F4MLV-7>APLRT1,F6DEV-10*,WIDE2-1*:=/8gk=NmQF[LWQ
        expected : <not digipeated>
        got      : F4MLV-7>APLRT1,F6DEV-10,F4MLV-10**:=/8gk=NmQF[LWQ

FAIL  mode 2  reported frame: earlier '*' + used WIDE2-1
        got      : F1ZCJ-3>APFD11,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:!...

FAIL  mode 2  two consumed hops, WIDE2-1 free
        in       : F4MLV-7>APLRT1,F6DEV*,F6DEV-11*,WIDE2-1:=/...
        expected : F4MLV-7>APLRT1,F6DEV*,F6DEV-11*,F4MLV-10*:=/...
        got      : F4MLV-7>APLRT1,F6DEV,F6DEV-11*,F4MLV-10*:=/...

The ** is the cosmetic part. What matters more is that WIDE2-1* — a hop that has already been consumed — gets used again for digipeating. The third case shows a separate effect of the same function: cleanPath() erased the asterisk on F6DEV, losing the record that it had digipeated the frame.

The String stub mirrors WString.cpp from arduino-esp32 where it matters — replace() replaces every occurrence, remove() only one — so the harness reproduces the real semantics rather than approximating them.

With the token-based substitution the same 16 cases pass. Opened as #463, one file, +17/−6.

Happy to add the harness to the PR if you find it useful, or to leave the PR as the fix alone — whichever suits you.

73

Hi Ricardo, Checked on `4.0.1` (2026-09-07): `src/digi_utils.cpp` is byte-identical to V4.0.0 — `git log V4.0.0..main -- src/digi_utils.cpp` returns nothing — so the behaviour is unchanged. Since the trigger is hard to catch on the air, I wrapped it in a small host harness that compiles your **actual** `digi_utils.cpp` against stub headers. No firmware source is modified, no board needed: ```sh g++ -std=c++17 -Istubs -o run digi_host_test.cpp ../src/digi_utils.cpp && ./run ``` On current `main`, 16 cases covering modes 1/2/3, cross-frequency and exhausted hops: ``` 13 passed, 3 failed ``` All three failures sit in the `exhausted hops` block: ``` FAIL mode 2 WIDE2-1 already used in : F4MLV-7>APLRT1,F6DEV-10*,WIDE2-1*:=/8gk=NmQF[LWQ expected : <not digipeated> got : F4MLV-7>APLRT1,F6DEV-10,F4MLV-10**:=/8gk=NmQF[LWQ FAIL mode 2 reported frame: earlier '*' + used WIDE2-1 got : F1ZCJ-3>APFD11,F6DEV,F6DEV-11,F4MLV-10**,WIDE3-3:!... FAIL mode 2 two consumed hops, WIDE2-1 free in : F4MLV-7>APLRT1,F6DEV*,F6DEV-11*,WIDE2-1:=/... expected : F4MLV-7>APLRT1,F6DEV*,F6DEV-11*,F4MLV-10*:=/... got : F4MLV-7>APLRT1,F6DEV,F6DEV-11*,F4MLV-10*:=/... ``` The `**` is the cosmetic part. What matters more is that `WIDE2-1*` — a hop that has already been consumed — gets used again for digipeating. The third case shows a separate effect of the same function: `cleanPath()` erased the asterisk on `F6DEV`, losing the record that it had digipeated the frame. The `String` stub mirrors `WString.cpp` from arduino-esp32 where it matters — `replace()` replaces every occurrence, `remove()` only one — so the harness reproduces the real semantics rather than approximating them. With the token-based substitution the same 16 cases pass. Opened as #463, one file, +17/−6. Happy to add the harness to the PR if you find it useful, or to leave the PR as the fix alone — whichever suits you. 73 <!-- github-comment-id:5617701218 -->
Author
Owner

completely added to the firmare with a few APRSPacketLib updates too

completely added to the firmare with a few APRSPacketLib updates too <!-- github-comment-id:5780936213 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/LoRa_APRS_iGate#283