OpenHop's ACL permission/role numbering does not match upstream MeshCore
(ClientACL.h). Most importantly, PERM_ACL_ADMIN = 0x02 in OpenHop vs 3
upstream. The login‑response ACL byte is interpreted by every standard
MeshCore client using upstream's numbering (low 2 bits = role, ADMIN == 3),
so an OpenHop admin session is decoded as read‑write / non‑admin.
Effect: a user logs in with the correct admin password, OpenHop's own UI
correctly shows admin, but a standard MeshCore client (e.g. the KiekR
Android/iOS app talking through a stock MeshCore BLE companion) shows the
session as guest / non‑admin and therefore gates off all admin controls.
Environment
Repeater: OpenHop (Python)
Client: standard MeshCore client (reproduced with KiekR 0.6.0) via a stock
MeshCore BLE companion, companion fw_ver = 13
Login response is the 14‑byte PUSH_CODE_LOGIN_SUCCESS (0x85) form
Upstream sets this byte from isAdmin(): reply_data[6] = client->isAdmin() ? 1 : 0 = (permissions & 3) == 3.
OpenHop's permissions & 0x02 is only self‑consistent with OpenHop's own
(non‑standard) numbering; against upstream numbering it would also mark READ_WRITE (2) as admin.
How a standard client decodes it (for reference)
The stock MeshCore BLE companion forwards these repeater‑reply bytes verbatim
into PUSH_CODE_LOGIN_SUCCESS: frame[1] = reply_data[6] (is_admin) and frame[12] = reply_data[7] (ACL byte) — see MeshCore examples/companion_radio/MyMesh.cpp. The client then derives the displayed
role from the ACL byte's low 2 bits. For an OpenHop admin (0x02) that yields
"not ADMIN(3)" → the session is shown as guest / non‑admin, even though the
admin password was correct.
Suggested fix
Align the ACL constants with upstream MeshCore in repeater/handler_helpers/acl.py:
This makes OpenHop wire‑compatible with every stock‑based MeshCore client for
admin/role recognition, with no change required on the client side.
Reproduction
Configure an OpenHop repeater with an admin password.
From a standard MeshCore client (reproduced with KiekR), log in to the
repeater using the admin password.
Observed: the client shows the session as guest / non‑admin; admin
controls are unavailable. Expected: admin.
Notes
Constants and code above were read from the current HEAD of openhop-dev/openhop_repeater (repeater/handler_helpers/acl.py) and openhop-dev/openhop_core (src/pymc_core/node/handlers/login_server.py).
Upstream references are from MeshCore src/helpers/ClientACL.h and examples/companion_radio/MyMesh.cpp.
## Summary
OpenHop's ACL permission/role numbering does not match upstream MeshCore
(`ClientACL.h`). Most importantly, **`PERM_ACL_ADMIN = 0x02` in OpenHop vs `3`
upstream**. The login‑response ACL byte is interpreted by every standard
MeshCore client using upstream's numbering (low 2 bits = role, `ADMIN == 3`),
so an OpenHop **admin** session is decoded as **read‑write / non‑admin**.
Effect: a user logs in with the correct **admin** password, OpenHop's own UI
correctly shows admin, but a standard MeshCore client (e.g. the KiekR
Android/iOS app talking through a stock MeshCore BLE companion) shows the
session as **guest / non‑admin** and therefore gates off all admin controls.
## Environment
- Repeater: OpenHop (Python)
- Client: standard MeshCore client (reproduced with KiekR 0.6.0) via a stock
MeshCore BLE companion, companion `fw_ver = 13`
- Login response is the 14‑byte `PUSH_CODE_LOGIN_SUCCESS (0x85)` form
## Root cause — OpenHop ACL constants
`openhop_repeater` → `repeater/handler_helpers/acl.py`:
```python
PERM_ACL_GUEST = 0x01
PERM_ACL_ADMIN = 0x02
PERM_ACL_READ_WRITE = 0x01
PERM_ACL_ROLE_MASK = 0x03
```
Admin authentication assigns `permissions = PERM_ACL_ADMIN` (= `0x02`):
```python
if admin_pwd and password == admin_pwd:
permissions = PERM_ACL_ADMIN # 0x02
elif guest_pwd and password == guest_pwd:
permissions = PERM_ACL_READ_WRITE # 0x01
else:
return False, 0
```
## Upstream MeshCore reference
`MeshCore` → `src/helpers/ClientACL.h`:
```c
#define PERM_ACL_ROLE_MASK 3 // lower 2 bits
#define PERM_ACL_GUEST 0
#define PERM_ACL_READ_ONLY 1
#define PERM_ACL_READ_WRITE 2
#define PERM_ACL_ADMIN 3
// ...
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; }
```
### Divergence (role value in the low 2 bits)
| Role | Upstream MeshCore | OpenHop |
|-------------|-------------------|---------|
| GUEST | `0` | `0x01` |
| READ_ONLY | `1` | — |
| READ_WRITE | `2` | `0x01` |
| **ADMIN** | **`3`** | **`0x02`** |
Two concrete problems:
1. **`PERM_ACL_ADMIN = 0x02` collides with upstream `PERM_ACL_READ_WRITE = 2`.**
A standard client computes `role = permissions & PERM_ACL_ROLE_MASK` →
`0x02 & 0x03 = 2` → `READ_WRITE`, and `isAdmin()` → `(perms & 3) == 3` →
`2 == 3` → **false**. The admin session is therefore rendered as
read‑write / non‑admin.
2. **`PERM_ACL_GUEST` and `PERM_ACL_READ_WRITE` are both `0x01`** — they are
indistinguishable from each other, and both differ from upstream
(`GUEST = 0`).
## Secondary: the login reply also flags admin on the wrong bit
`openhop_core` → `src/pymc_core/node/handlers/login_server.py` builds the login
reply:
```python
reply_data = bytearray(13)
struct.pack_into("<I", reply_data, 0, current_time) # timestamp
reply_data[4] = response_code # RESP_SERVER_LOGIN_OK = 0x00
reply_data[5] = 0 # legacy keep-alive
reply_data[6] = 1 if (permissions & 0x02) else 0 # "is_admin" flag
reply_data[7] = permissions # full ACL byte
struct.pack_into("<I", reply_data, 8, random.randint(0, 0xFFFFFFFF))
reply_data[12] = FIRMWARE_VER_LEVEL # 1
```
Upstream sets this byte from `isAdmin()`:
`reply_data[6] = client->isAdmin() ? 1 : 0` = `(permissions & 3) == 3`.
OpenHop's `permissions & 0x02` is only self‑consistent with OpenHop's own
(non‑standard) numbering; against upstream numbering it would also mark
`READ_WRITE` (`2`) as admin.
## How a standard client decodes it (for reference)
The stock MeshCore BLE companion forwards these repeater‑reply bytes verbatim
into `PUSH_CODE_LOGIN_SUCCESS`: `frame[1] = reply_data[6]` (is_admin) and
`frame[12] = reply_data[7]` (ACL byte) — see MeshCore
`examples/companion_radio/MyMesh.cpp`. The client then derives the displayed
role from the ACL byte's low 2 bits. For an OpenHop admin (`0x02`) that yields
"not `ADMIN(3)`" → the session is shown as guest / non‑admin, even though the
admin password was correct.
## Suggested fix
Align the ACL constants with upstream MeshCore in
`repeater/handler_helpers/acl.py`:
```python
PERM_ACL_ROLE_MASK = 0x03
PERM_ACL_GUEST = 0
PERM_ACL_READ_ONLY = 1
PERM_ACL_READ_WRITE = 2
PERM_ACL_ADMIN = 3
```
and make the reply's admin flag match upstream `isAdmin()` in
`login_server.py`:
```python
reply_data[6] = 1 if (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN else 0
```
This makes OpenHop wire‑compatible with every stock‑based MeshCore client for
admin/role recognition, with no change required on the client side.
## Reproduction
1. Configure an OpenHop repeater with an admin password.
2. From a standard MeshCore client (reproduced with KiekR), log in to the
repeater using the **admin** password.
3. **Observed:** the client shows the session as **guest / non‑admin**; admin
controls are unavailable.
**Expected:** admin.
## Notes
- Constants and code above were read from the current `HEAD` of
`openhop-dev/openhop_repeater` (`repeater/handler_helpers/acl.py`) and
`openhop-dev/openhop_core` (`src/pymc_core/node/handlers/login_server.py`).
- Upstream references are from MeshCore `src/helpers/ClientACL.h` and
`examples/companion_radio/MyMesh.cpp`.
<!-- github-sync-id:5141574509 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
OpenHop's ACL permission/role numbering does not match upstream MeshCore
(
ClientACL.h). Most importantly,PERM_ACL_ADMIN = 0x02in OpenHop vs3upstream. The login‑response ACL byte is interpreted by every standard
MeshCore client using upstream's numbering (low 2 bits = role,
ADMIN == 3),so an OpenHop admin session is decoded as read‑write / non‑admin.
Effect: a user logs in with the correct admin password, OpenHop's own UI
correctly shows admin, but a standard MeshCore client (e.g. the KiekR
Android/iOS app talking through a stock MeshCore BLE companion) shows the
session as guest / non‑admin and therefore gates off all admin controls.
Environment
MeshCore BLE companion, companion
fw_ver = 13PUSH_CODE_LOGIN_SUCCESS (0x85)formRoot cause — OpenHop ACL constants
openhop_repeater→repeater/handler_helpers/acl.py:Admin authentication assigns
permissions = PERM_ACL_ADMIN(=0x02):Upstream MeshCore reference
MeshCore→src/helpers/ClientACL.h:Divergence (role value in the low 2 bits)
00x01120x0130x02Two concrete problems:
PERM_ACL_ADMIN = 0x02collides with upstreamPERM_ACL_READ_WRITE = 2.A standard client computes
role = permissions & PERM_ACL_ROLE_MASK→0x02 & 0x03 = 2→READ_WRITE, andisAdmin()→(perms & 3) == 3→2 == 3→ false. The admin session is therefore rendered asread‑write / non‑admin.
PERM_ACL_GUESTandPERM_ACL_READ_WRITEare both0x01— they areindistinguishable from each other, and both differ from upstream
(
GUEST = 0).Secondary: the login reply also flags admin on the wrong bit
openhop_core→src/pymc_core/node/handlers/login_server.pybuilds the loginreply:
Upstream sets this byte from
isAdmin():reply_data[6] = client->isAdmin() ? 1 : 0=(permissions & 3) == 3.OpenHop's
permissions & 0x02is only self‑consistent with OpenHop's own(non‑standard) numbering; against upstream numbering it would also mark
READ_WRITE(2) as admin.How a standard client decodes it (for reference)
The stock MeshCore BLE companion forwards these repeater‑reply bytes verbatim
into
PUSH_CODE_LOGIN_SUCCESS:frame[1] = reply_data[6](is_admin) andframe[12] = reply_data[7](ACL byte) — see MeshCoreexamples/companion_radio/MyMesh.cpp. The client then derives the displayedrole from the ACL byte's low 2 bits. For an OpenHop admin (
0x02) that yields"not
ADMIN(3)" → the session is shown as guest / non‑admin, even though theadmin password was correct.
Suggested fix
Align the ACL constants with upstream MeshCore in
repeater/handler_helpers/acl.py:and make the reply's admin flag match upstream
isAdmin()inlogin_server.py:This makes OpenHop wire‑compatible with every stock‑based MeshCore client for
admin/role recognition, with no change required on the client side.
Reproduction
repeater using the admin password.
controls are unavailable.
Expected: admin.
Notes
HEADofopenhop-dev/openhop_repeater(repeater/handler_helpers/acl.py) andopenhop-dev/openhop_core(src/pymc_core/node/handlers/login_server.py).src/helpers/ClientACL.handexamples/companion_radio/MyMesh.cpp.I believe this work has been done in the DEV branch, just awaiting more testing before merging to Main.