Parity with the Android client: drop the "Allow self-signed certificate" escape hatch. The app now performs system TLS trust only — a self-signed appliance must have its CA installed on the device (MDM/profile). A code-only public-key pinning policy remains (no UI). - ServerTrustEvaluator: remove ServerTrustPolicy.allowSelfSignedForHost and its challenge handler; keep .system (default) and .pinPublicKeySHA256. - SettingsView: remove the self-signed toggle/state/prefill; makeConfig uses the default .system policy. - MCMSConnection / APIConfiguration: update usage-sketch + doc comments. - README / GUI-NOTES / MCMS_API.md: document system-trust-only; self-signed boxes need their CA installed on the device. - Add CHANGELOG.md for this session, flagged for the Android port. Note: configs previously persisted with the self-signed policy fail to decode and reset to defaults (one-time re-entry of the server URL). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
761 lines
24 KiB
Markdown
761 lines
24 KiB
Markdown
# Ciena MCMS 6.2 REST API — Implementer's Field Guide
|
||
|
||
Reference for building an HTTP client against a Ciena MicroClimate
|
||
Management System (MCMS) 6.2 PON Manager. Distilled from the official
|
||
dev guide (`323-1961-306`) plus empirical findings against a working
|
||
deployment with GNXS / Tibit MicroPlug ONUs on Interos Everest 5.x
|
||
firmware.
|
||
|
||
Language-agnostic — every quirk listed here was hit in production. If
|
||
you skip the gotchas in §3, you will lose an afternoon to one of them.
|
||
|
||
---
|
||
|
||
## 1. Connection basics
|
||
|
||
- **Base URL.** The root of the PON Manager web UI (e.g.
|
||
`https://mcms.example.net`). All REST paths are prefixed with `/api`.
|
||
- **Transport.** HTTPS. Internal deployments commonly use self-signed
|
||
certificates on private IPs — your client needs an opt-in to skip
|
||
TLS verification for that case (do **not** disable globally).
|
||
- **HTTP version.** HTTP/1.1 works. The server also negotiates HTTP/2
|
||
but the dev-guide examples are 1.1 and the only behavioural
|
||
difference observed is irrelevant to clients.
|
||
- **Path normalisation.** The dev guide writes paths as
|
||
`/v1/users/authenticate/` — the real wire path is
|
||
`/api/v1/users/authenticate/`. Normalise once in your client.
|
||
- **API versions.** Both `/api/v1/...` and `/api/v3/...` exist and serve
|
||
different endpoints. **Use whichever the dev guide lists for that
|
||
resource — they are not interchangeable.** See §4 endpoint table.
|
||
- **Trailing slashes.** Required. `/api/v1/onus/configs/<id>/` works;
|
||
`/api/v1/onus/configs/<id>` does not (Django's `APPEND_SLASH` is
|
||
on, but a few endpoints 500 instead of redirecting).
|
||
|
||
---
|
||
|
||
## 2. Login & session
|
||
|
||
MCMS uses Django session cookies + a CSRF token. Sessions do **not**
|
||
auto-expire on idle — log out explicitly when done.
|
||
|
||
### 2.1 Authenticate
|
||
|
||
```
|
||
POST /api/v1/users/authenticate/
|
||
Content-Type: application/json
|
||
|
||
{ "data": { "email": "user@example.net", "password": "..." } }
|
||
```
|
||
|
||
Response on success (`HTTP 200`):
|
||
|
||
```json
|
||
{ "status": "success" }
|
||
```
|
||
|
||
Set-Cookie headers carry the session. On observed MCMS 6.2 builds:
|
||
|
||
```
|
||
Set-Cookie: __Host-csrftoken=<token>; Path=/; Secure; SameSite=Lax
|
||
Set-Cookie: __Host-sessionid=<id>; Path=/; Secure; HttpOnly; SameSite=Lax
|
||
Set-Cookie: __Host-sessionexpire=<epoch>; Path=/; Secure; SameSite=Lax
|
||
```
|
||
|
||
→ **The `__Host-` prefix is significant.** Some MCMS builds (older
|
||
ones, mostly) use bare `csrftoken` / `sessionid`. Your cookie reader
|
||
should accept either prefix.
|
||
|
||
→ Some build variants accept an unwrapped body too:
|
||
`{"email": ..., "password": ...}` with no `data` envelope. If wrapped
|
||
returns HTTP 400, retry unwrapped.
|
||
|
||
### 2.2 Subsequent requests
|
||
|
||
Every authenticated request must send:
|
||
|
||
```
|
||
Cookie: __Host-csrftoken=<token>; __Host-sessionid=<id>; ...
|
||
X-CSRFToken: <token> # the value from __Host-csrftoken
|
||
Referer: <baseURL>/ # MCMS rejects POST/PUT/DELETE without this
|
||
User-Agent: <anything> # see §3.1 — must not be empty
|
||
Accept: application/json
|
||
```
|
||
|
||
For PUT/POST/PATCH also:
|
||
|
||
```
|
||
Content-Type: application/json
|
||
Content-Length: <bytes>
|
||
```
|
||
|
||
### 2.3 (Optional) Database selection
|
||
|
||
If the deployment manages multiple databases:
|
||
|
||
```
|
||
PUT /api/v1/databases/selection/
|
||
{ "data": "<database_id>" }
|
||
```
|
||
|
||
Most deployments only have one and skip this step.
|
||
|
||
### 2.4 Logout
|
||
|
||
```
|
||
GET /api/v1/users/logout/
|
||
```
|
||
|
||
Best-effort — don't fail your client if this errors.
|
||
|
||
---
|
||
|
||
## 3. The gotchas (read this section twice)
|
||
|
||
### 3.1 User-Agent header is REQUIRED
|
||
|
||
If you don't send a `User-Agent`, some MCMS middleware dereferences
|
||
`HTTP_USER_AGENT` without a null check and returns **HTTP 500** with
|
||
body:
|
||
|
||
```json
|
||
{"status": "fail", "details": {"message": "Internal server error. See server logs for details."}}
|
||
```
|
||
|
||
Reproduce: `curl -A '' https://mcms/api/v3/onus/configs/`. Any non-empty
|
||
string works (`PonFleetClient/1.0`, etc.).
|
||
|
||
**For iOS/Swift**: `URLSession` sets a default User-Agent automatically
|
||
(e.g. `<bundle>/1 CFNetwork/... Darwin/...`), so this is unlikely to
|
||
bite you there — but if you ever set a custom `URLRequest` with
|
||
`setValue(nil, forHTTPHeaderField: "User-Agent")`, the 500s will
|
||
return.
|
||
|
||
### 3.2 CSRF token field name
|
||
|
||
The token cookie is `__Host-csrftoken` on new builds, bare `csrftoken`
|
||
on older. The header you send back is always `X-CSRFToken` (no
|
||
prefix, capital `T` in `Token`). Both Django case-fold the header
|
||
name, so casing is fine — but the body name is exact.
|
||
|
||
### 3.3 Pagination: use `next`, NOT `skip`
|
||
|
||
`/api/v3/onus/configs/?skip=N` has been observed returning HTTP 500.
|
||
The working approach uses the last document's `_id` as a cursor:
|
||
|
||
```
|
||
GET /api/v3/onus/configs/?limit=100
|
||
→ returns 100 docs, the last with _id = "X"
|
||
GET /api/v3/onus/configs/?limit=100&next=X
|
||
→ returns the next 100, EXCLUDING all _ids <= X
|
||
```
|
||
|
||
Loop until you get a short page (< limit) or an empty page. Most list
|
||
endpoints accept this pattern.
|
||
|
||
### 3.4 Page size 100 is the safe default
|
||
|
||
`?limit=1000` against `/api/v3/onus/configs/` on a ~1500-ONU fleet
|
||
times out at the Apache front-end before Django logs the request.
|
||
Full `ONU-CFG` documents are ~30 KB each, so 1000 × 30 KB = 30 MB,
|
||
which exceeds the proxy timeout. **100 per page keeps each response
|
||
~3 MB** and well under any default proxy budget.
|
||
|
||
This applies to all bulk list endpoints (configs, states, OLT states).
|
||
|
||
### 3.5 Request body envelope: `{"data": payload}`
|
||
|
||
PUT/POST/PATCH bodies are wrapped:
|
||
|
||
```json
|
||
{ "data": { "_id": "...", "ONU": { ... } } }
|
||
```
|
||
|
||
Response envelope is similar:
|
||
|
||
```json
|
||
{ "status": "success", "data": { ... } }
|
||
{ "status": "fail", "details": { "message": "..." } }
|
||
{ "status": "warning", "data": { ... }, "details": { ... } }
|
||
```
|
||
|
||
`"warning"` is treated as success by MCMS (the write happened) but
|
||
indicates a schema mismatch you should log.
|
||
|
||
### 3.6 PUT is full-document replace, NOT patch
|
||
|
||
`PUT /api/v1/onus/configs/<id>/` replaces the entire `ONU-CFG`
|
||
document. You **must** GET the current doc, mutate fields, and PUT
|
||
the entire result. There is no PATCH equivalent that works reliably
|
||
on MCMS 6.2.
|
||
|
||
Forgetting this turns into "I PUT'd just the FW Bank fields and now
|
||
the ONU has no service config" — the dev guide is explicit but easy
|
||
to skim past.
|
||
|
||
### 3.7 Timestamp format
|
||
|
||
MCMS emits and accepts timestamps as:
|
||
|
||
```
|
||
"YYYY-MM-DD HH:MM:SS" # second precision
|
||
"YYYY-MM-DD HH:MM:SS.ffffff" # microsecond precision (for STATE.Time)
|
||
```
|
||
|
||
Always **UTC**, always with a **literal space** between date and time
|
||
(not ISO-8601 `T`). Used on:
|
||
|
||
- `ONU-STATE.Time`
|
||
- `OLT-STATE.Time`
|
||
- Alarm timestamps
|
||
- `AUTO-TASK-CFG.Task["Scheduled Start Time"]`
|
||
- `start-time` / `end-time` URL params on stats endpoints
|
||
|
||
**For Swift**: `ISO8601DateFormatter` will NOT parse this. Use a
|
||
plain `DateFormatter` with:
|
||
|
||
```swift
|
||
let fmt = DateFormatter()
|
||
fmt.timeZone = TimeZone(identifier: "UTC")
|
||
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS" // or "yyyy-MM-dd HH:mm:ss"
|
||
fmt.locale = Locale(identifier: "en_US_POSIX")
|
||
```
|
||
|
||
The `en_US_POSIX` locale matters — without it, devices set to a
|
||
24-hour-format locale will still parse fine but devices set to
|
||
12-hour-format locales will fail. POSIX locale forces parse rules.
|
||
|
||
### 3.8 Server-side query/projection filters are brittle
|
||
|
||
The dev guide documents `query`, `projection`, `sort`, `limit`,
|
||
`skip`, `next`, `distinct` URL parameters. In practice:
|
||
|
||
- **`query`** (Mongo-style filter) works for simple equality on
|
||
bare field names. Anything with **spaces in field names**, quoted
|
||
values with non-ASCII characters, or projection paths that don't
|
||
exist on every document version is unreliable — you'll get either
|
||
empty results or 400s.
|
||
- **`projection`** is *very* picky about paths. Better to fetch full
|
||
docs and project client-side.
|
||
|
||
**Recommended approach**: load the full fleet (paginated by `next`)
|
||
and filter client-side. The data volume is fine for any device with
|
||
enough RAM to hold a few thousand 30 KB documents.
|
||
|
||
### 3.9 URL encoding
|
||
|
||
Use percent-encoding (`encodeURIComponent` equivalent). MCMS does NOT
|
||
accept `+` for spaces in URL parameters — use `%20`.
|
||
|
||
This matters for query params like `start-time` which carry literal
|
||
spaces in the timestamp.
|
||
|
||
### 3.10 ONU IDs
|
||
|
||
The `_id` field of an ONU document is:
|
||
|
||
- The **serial number** for XGS-PON / GPON ONUs (e.g. `GNXS05057fee`)
|
||
- The **MAC address** for 10G-EPON ONUs
|
||
|
||
Treat as opaque strings. They're URL-safe in observed deployments
|
||
but always `encodeURIComponent` to be defensive.
|
||
|
||
### 3.11 Registration status lives on OLT-STATE, not ONU-STATE
|
||
|
||
This is non-obvious. The 9 registration buckets live as arrays on the
|
||
OLT's state document:
|
||
|
||
```json
|
||
{
|
||
"_id": "<OLT MAC>",
|
||
"ONU States": {
|
||
"Registered": ["GNXS01", "GNXS02", ...],
|
||
"Deregistered": ["GNXS09", ...],
|
||
"Dying Gasp": [...],
|
||
"Disabled": [...],
|
||
"Disallowed Admin": [...],
|
||
"Disallowed Error": [...],
|
||
"Disallowed Reg ID": [...],
|
||
"Unspecified": [...],
|
||
"Unprovisioned": [...]
|
||
}
|
||
}
|
||
```
|
||
|
||
To resolve "what's the status of ONU X?", you walk every OLT-STATE
|
||
doc, find which bucket contains X, and that's your answer. See
|
||
Procedure 2 in the dev guide.
|
||
|
||
An ONU should appear in exactly one bucket on exactly one OLT. In the
|
||
rare duplicate case, prefer the non-`Registered` bucket — that's the
|
||
interesting signal.
|
||
|
||
### 3.12 FEC counters live on ONU-STATE.STATS, not on `/onus/stats/`
|
||
|
||
`GET /api/v1/onus/stats/<id>/?start-time=...` returns historical PM
|
||
samples (time-series). For the *current* FEC counters and optical
|
||
levels, read the live `ONU-STATE` document:
|
||
|
||
```
|
||
GET /api/v3/onus/states/<id>/
|
||
→ data.STATS["ONU-PON"]["RX Pre-FEC BER"]
|
||
→ data.STATS["ONU-PON"]["RX Post-FEC BER"]
|
||
→ data.STATS["ONU-PON"]["RX Optical Level"] (dBm)
|
||
→ data.STATS["ONU-PON"]["TX Optical Level"] (dBm)
|
||
→ data.STATS["OLT-PON"]["RX Pre-FEC BER"]
|
||
→ data.STATS["OLT-PON"]["RX Post-FEC BER"]
|
||
```
|
||
|
||
The MCMS Web UI's `/api/onu/summary` helper returns the same data
|
||
under `data.state_collection.STATS.*` (wrapper shape).
|
||
|
||
### 3.13 Bulk endpoints don't return everything in one shot
|
||
|
||
Even on a small fleet, MCMS may silently truncate large list
|
||
responses. Always paginate — don't trust `?limit=large_number` to be
|
||
authoritative.
|
||
|
||
### 3.14 Verbose error logging matters
|
||
|
||
When MCMS 500s, the response body sometimes contains useful detail
|
||
(missing required param, schema validation failure, etc.). Always log
|
||
both the response headers and the FULL body on non-2xx during
|
||
development — most "mysterious 500" cases resolve to a one-line hint
|
||
in the body.
|
||
|
||
---
|
||
|
||
## 4. Endpoints used by the fleet-upgrade app
|
||
|
||
| Method | Path | Purpose |
|
||
|---|---|---|
|
||
| `POST` | `/api/v1/users/authenticate/` | Login |
|
||
| `GET` | `/api/v1/users/logout/` | Logout |
|
||
| `PUT` | `/api/v1/databases/selection/` | (Optional) DB selection |
|
||
| `GET` | `/api/v3/onus/configs/` | List ONU configs (paginated) |
|
||
| `GET` | `/api/v1/onus/configs/<id>/` | Get single ONU config |
|
||
| `PUT` | `/api/v1/onus/configs/<id>/` | Replace ONU config (full doc) |
|
||
| `DELETE` | `/api/v1/onus/configs/<id>/` | Delete ONU (Procedure 43) |
|
||
| `GET` | `/api/v3/onus/states/` | List ONU states (paginated) |
|
||
| `GET` | `/api/v3/onus/states/<id>/` | Get single ONU state |
|
||
| `DELETE` | `/api/v1/onus/states/<id>/` | Delete ONU state (best-effort) |
|
||
| `GET` | `/api/v3/olts/states/` | List OLT states (paginated; registration) |
|
||
| `GET` | `/api/v1/files/onu-firmware/` | List uploaded firmware files |
|
||
| `POST` | `/api/v1/files/onu-firmware/<name>/` | Upload firmware |
|
||
| `PUT` | `/api/v3/tasks/configs/<id>/` | Create bulk upgrade task (Procedure 8) |
|
||
| `GET` | `/api/v3/tasks/configs/<id>/` | Poll bulk task config |
|
||
| `GET` | `/api/v1/onus/<id>/upgrade/status/` | (Build-dependent) per-ONU status |
|
||
|
||
### Other endpoints documented but not (yet) used
|
||
|
||
| Method | Path | Notes |
|
||
|---|---|---|
|
||
| `GET` | `/api/v1/onus/stats/<id>/?start-time=...` | Time-series ONU PM data |
|
||
| `GET` | `/api/v1/olts/stats/<id>/?start-time=...` | OLT PM data |
|
||
| `GET` | `/api/v1/onus/logs/<id>/?start-time=...` | Per-ONU syslog |
|
||
| `GET` | `/api/v1/olts/logs/<id>/?start-time=...` | Per-OLT syslog |
|
||
| `GET` | `/api/v3/tasks/states/<id>/` | Bulk task progress (good follow-up) |
|
||
| `GET` | `/api/v1/onus/automation/states/` | PON automation state |
|
||
| `GET` | `/api/v1/onus/cpe-states/` | ONU CPE states (downstream devices) |
|
||
|
||
---
|
||
|
||
## 5. Data shapes (observed)
|
||
|
||
These are not formally specified in the dev guide for MCMS 6.2 — they
|
||
were extracted from live traffic.
|
||
|
||
### 5.1 ONU-CFG
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "GNXS05057fee",
|
||
"ONU": {
|
||
"Name": "Operator-set label",
|
||
"Address": "Street, city",
|
||
"PON Mode": "GPON", // GPON | XGS-PON | 10G-EPON
|
||
"Equipment ID": "FT-XGS2110",
|
||
|
||
"FW Bank Files": ["...bin", "...bin"], // [slot 0, slot 1]
|
||
"FW Bank Versions": ["EV05110R", "EV05100R"],
|
||
"FW Bank Ptr": 0, // 0 | 1 | 65535 (unset)
|
||
|
||
"ONU-ALARM-CFG": "Default",
|
||
"Vendor": "GNXS",
|
||
"Serial Number": "GNXS05057fee",
|
||
"Host MAC Address": "58:00:32:05:7f:ee",
|
||
// ...many more fields - preserve all of them when PUTing
|
||
},
|
||
"OLT-Service 0": { ... }, // service config slots
|
||
"OLT-Service 1": { ... },
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**Critical**: when mutating for an upgrade, only change `ONU["FW Bank
|
||
Files"]`, `ONU["FW Bank Versions"]`, and `ONU["FW Bank Ptr"]`. Pass
|
||
through everything else unchanged.
|
||
|
||
### 5.2 ONU-STATE
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "GNXS05057fee",
|
||
"Time": "2026-05-04 21:51:57.394127", // UTC, last update
|
||
"ONU": {
|
||
"Equipment ID": "FT-XGS2110",
|
||
"FW Bank Files": [...],
|
||
"FW Bank Versions": [...],
|
||
"FW Bank Ptr": 0,
|
||
"FW Version": "EV05110R", // resolved active version
|
||
"FW Upgrade Status": {
|
||
"Bank": 0,
|
||
"Status": "Success", // status string
|
||
"Progress": 100, // percent
|
||
"Upgrade Time": "2026-03-18 17:12:43.093893",
|
||
"Total Blocks": 482915,
|
||
"Sent Blocks": 482915,
|
||
"Failures": 0,
|
||
// ...
|
||
},
|
||
"PON Mode": "GPON",
|
||
"Online Time": "2026-03-18 17:13:42.285965",
|
||
// ...
|
||
},
|
||
"STATS": {
|
||
"ONU-PON": {
|
||
"RX Pre-FEC BER": 22388041291, // cumulative since reset
|
||
"RX Post-FEC BER": 22379111198,
|
||
"RX Optical Level": -17.544, // dBm
|
||
"TX Optical Level": 5.532 // dBm
|
||
},
|
||
"OLT-PON": {
|
||
"RX Pre-FEC BER": 0, // upstream view
|
||
"RX Post-FEC BER": 0,
|
||
"TX Optical Level": 5.6,
|
||
"RX Optical Level": -18.3
|
||
},
|
||
"ONU-EnhancedFecPmHistData-32769": { // NOT the same as the BER fields above
|
||
"uncorrectable_code_words": 1829, // (these are codeword counts,
|
||
"corrected_bytes": 181, // not bit-error-rate ratios)
|
||
"corrected_code_words": 181
|
||
},
|
||
// ...many more
|
||
},
|
||
"Alarm": {
|
||
"0-EMERG": [], "1-ALERT": [], "2-CRIT": [],
|
||
"3-ERROR": [], "4-WARNING": [], "5-NOTICE": [],
|
||
"6-INFO": [], "7-DEBUG": []
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 OLT-STATE (relevant subset)
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "<OLT MAC>",
|
||
"Time": "2026-05-04 21:51:57.394127",
|
||
"ONU States": {
|
||
"Registered": ["onu1", "onu2", ...],
|
||
"Deregistered": [...],
|
||
"Dying Gasp": [...],
|
||
"Disabled": [...],
|
||
"Disallowed Admin": [...],
|
||
"Disallowed Error": [...],
|
||
"Disallowed Reg ID": [...],
|
||
"Unspecified": [...],
|
||
"Unprovisioned": [...]
|
||
}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 5.4 AUTO-TASK-CFG (bulk firmware task, Procedure 8)
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "<your-task-id>", // unique, caller-generated
|
||
"Task": {
|
||
"Device Type": "ONU",
|
||
"Operation": "Firmware Upgrade",
|
||
"Scheduled Start Time": "2026-05-12 03:00:00" // UTC space format
|
||
},
|
||
"Task Details": {
|
||
"Devices": ["GNXS01", "GNXS02", ...], // ONU IDs (serial / MAC)
|
||
"ONU": {
|
||
"FW Bank Ptr": 1, // target slot (single value!)
|
||
"FW Bank Files": { "0": "old.bin", "1": "new.bin" },
|
||
"FW Bank Versions": { "0": "EV05110R", "1": "EV05120R" }
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
PUT to `/api/v3/tasks/configs/<task-id>/`. Note: `FW Bank Files` and
|
||
`FW Bank Versions` here are **objects keyed by slot index as strings**
|
||
(`"0"`, `"1"`), not arrays. This is different from the `ONU-CFG`
|
||
shape where the same fields are arrays — almost certainly a Mongo
|
||
serialisation quirk. Match it as-is.
|
||
|
||
**Single bank per task.** Because `FW Bank Ptr` accepts one slot, if
|
||
your selection spans ONUs with different active banks, bucket them
|
||
by target slot and create one task per bucket.
|
||
|
||
### 5.5 Firmware file entry (from `/api/v1/files/onu-firmware/`)
|
||
|
||
```jsonc
|
||
{
|
||
"_id": "...",
|
||
"filename": "Interos-Everest-5.12.0-R-EV05120R.bin",
|
||
"metadata": {
|
||
"Compatible Manufacturer": "TIBITCOM",
|
||
"Compatible Model": ["MicroPlug ONU"],
|
||
"Version": "EV05120R"
|
||
},
|
||
// GridFS metadata...
|
||
}
|
||
```
|
||
|
||
To upload:
|
||
|
||
```
|
||
POST /api/v1/files/onu-firmware/<filename>/
|
||
{
|
||
"data": {
|
||
"file": "<base64 binary contents>",
|
||
"metadata": {
|
||
"Compatible Manufacturer": "TIBITCOM",
|
||
"Compatible Model": ["MicroPlug ONU"],
|
||
"Version": "EV05120R"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Firmware upgrade flow
|
||
|
||
Two methods documented:
|
||
|
||
### 6.1 Procedure 7 — per-ONU PUT
|
||
|
||
For each ONU:
|
||
|
||
1. `GET /api/v1/onus/configs/<id>/` → full doc.
|
||
2. Compute the inactive bank (see §6.3).
|
||
3. Set:
|
||
- `ONU["FW Bank Files"][writeSlot] = "<filename>"`
|
||
- `ONU["FW Bank Versions"][writeSlot] = "<version>"`
|
||
- `ONU["FW Bank Ptr"] = writeSlot` (this initiates the download)
|
||
4. `PUT /api/v1/onus/configs/<id>/` with the entire mutated document.
|
||
|
||
Pros: see errors per ONU live. Cons: slow for large fleets, you own
|
||
retries.
|
||
|
||
### 6.2 Procedure 8 — bulk task (recommended)
|
||
|
||
Build one `AUTO-TASK-CFG` per (target slot) bucket:
|
||
|
||
```
|
||
PUT /api/v3/tasks/configs/<task-id>/
|
||
(body shape in §5.4)
|
||
```
|
||
|
||
MCMS owns the rollout: scheduled start, retries, pacing.
|
||
Monitor progress (build permitting):
|
||
|
||
```
|
||
GET /api/v3/tasks/states/<task-id>/
|
||
```
|
||
|
||
Or just poll the ONU configs and watch `FW Bank Versions` /
|
||
`FW Bank Ptr` flip.
|
||
|
||
### 6.3 Inactive-bank selection
|
||
|
||
`FW Bank Ptr` identifies the active bank (0, 1, or 65535 = unset).
|
||
**Always write to the inactive bank** so you don't blow away the
|
||
running image:
|
||
|
||
| Current `FW Bank Ptr` | Write slot | Why |
|
||
|---|---|---|
|
||
| 0 | 1 | preserve active |
|
||
| 1 | 0 | preserve active |
|
||
| 65535 (unset) | 1 | matches Procedure 7 example; slot 0 stays as factory fallback |
|
||
|
||
MCMS flips `FW Bank Ptr` to the new slot when the download completes.
|
||
The ONU reboots into the new image on its next reboot.
|
||
|
||
---
|
||
|
||
## 7. FEC + optical interpretation
|
||
|
||
### 7.1 Four FEC counters
|
||
|
||
Per the user guide green-LED criteria (`323-1961-302` p149):
|
||
|
||
| Field | Path | Green | Red |
|
||
|---|---|---|---|
|
||
| ONU Pre-FEC BER | `STATS["ONU-PON"]["RX Pre-FEC BER"]` | 0 | (informational) |
|
||
| ONU Post-FEC BER | `STATS["ONU-PON"]["RX Post-FEC BER"]` | 0 | > 0 |
|
||
| OLT Pre-FEC BER | `STATS["OLT-PON"]["RX Pre-FEC BER"]` | 0 | (informational) |
|
||
| OLT Post-FEC BER | `STATS["OLT-PON"]["RX Post-FEC BER"]` | 0 | > 0 |
|
||
|
||
Note: the field name is "BER" but the magnitudes are big integers
|
||
(cumulative error byte counts since reset), not ratios in [0,1].
|
||
Treat them as counts.
|
||
|
||
### 7.2 Health bucket rules
|
||
|
||
Suggested 4-state reduction:
|
||
|
||
- **OK**: all four counters present and == 0.
|
||
- **Pre-FEC errors**: any pre > 0, all post == 0. (Marginal link;
|
||
FEC is correcting.)
|
||
- **Post-FEC errors**: any post > 0. (Uncorrected bits reaching the
|
||
user.)
|
||
- **Buggy (pre ≈ post)**: ONU pre > 0 AND post > 0 AND
|
||
`post / pre >= 0.95`. Real FEC reduces errors; if post is within
|
||
5% of pre, the firmware is mirroring the counter. Observed on
|
||
Interos Everest ONUs.
|
||
|
||
### 7.3 Optical levels
|
||
|
||
```
|
||
STATS["ONU-PON"]["RX Optical Level"] // ONU receive power, dBm
|
||
STATS["ONU-PON"]["TX Optical Level"] // ONU transmit power, dBm
|
||
```
|
||
|
||
User guide thresholds:
|
||
|
||
| | Green | Red |
|
||
|---|---|---|
|
||
| RX | ≥ −30 dBm | < −30 dBm |
|
||
| TX | ≥ 3 dBm | < 3 dBm |
|
||
|
||
Operators usually want a margin warning band — e.g. yellow when RX
|
||
is between −28 and −30 dBm.
|
||
|
||
---
|
||
|
||
## 8. iOS-specific notes
|
||
|
||
### 8.1 Library stack suggestions
|
||
|
||
- **HTTP**: `URLSession` is fine. The features you need are standard
|
||
cookie storage, custom headers, and the option to bypass cert
|
||
validation. Don't reach for Alamofire just for this.
|
||
- **Cookies**: `HTTPCookieStorage` handles `__Host-` prefixes
|
||
correctly. Get the CSRF value via:
|
||
```swift
|
||
HTTPCookieStorage.shared.cookies(for: baseURL)?
|
||
.first { $0.name == "__Host-csrftoken" || $0.name == "csrftoken" }?.value
|
||
```
|
||
- **Self-signed certs**: NOT accepted in-app — both clients use system trust only,
|
||
with no "accept self-signed" bypass. A self-signed appliance must have its CA
|
||
installed on the device's OS trust store (MDM/configuration profile). Do not
|
||
add a trust-bypass toggle.
|
||
- **JSON**: `JSONSerialization` is simpler than `Codable` for the
|
||
envelope pattern (`{status, data, details}`) because the `data`
|
||
payload is heterogeneous. `Codable` works if you model each
|
||
endpoint individually.
|
||
|
||
### 8.2 Concurrency
|
||
|
||
The fleet load is N HTTPS round-trips for per-ONU state (the bulk
|
||
endpoint is the optimisation but the per-ID fallback may also be
|
||
needed). Use Swift Concurrency (`TaskGroup`) with a concurrency cap
|
||
of 8–10 to avoid stampeding the server. The desktop client uses 5
|
||
for deletes and 8 for state fetches.
|
||
|
||
### 8.3 Background uploads
|
||
|
||
Firmware `.bin` files are 10–50 MB base64-encoded. If you implement
|
||
uploads:
|
||
|
||
- Don't block the main thread.
|
||
- Consider `URLSession` background configuration so an upload can
|
||
survive an app suspend.
|
||
- The upload is a single POST; no chunking required.
|
||
|
||
### 8.4 Storing the session
|
||
|
||
For UX: persist the base URL and username in `UserDefaults`, never
|
||
the password. Re-prompt for password on launch.
|
||
|
||
The session cookie itself goes in the per-session cookie storage
|
||
and is automatically discarded — don't try to persist it.
|
||
|
||
### 8.5 Date handling
|
||
|
||
See §3.7. Default `DateFormatter` with `en_US_POSIX` locale + UTC
|
||
timezone, format `"yyyy-MM-dd HH:mm:ss.SSSSSS"` (microseconds) or
|
||
`"yyyy-MM-dd HH:mm:ss"` (seconds). Try the microsecond format
|
||
first, fall back to second if it fails.
|
||
|
||
### 8.6 Cell-vs-Wi-Fi
|
||
|
||
MCMS APIs are typically reachable only from the operator's internal
|
||
network. Test on cellular: many internal hosts won't be reachable,
|
||
and your error UI needs to communicate that clearly.
|
||
|
||
### 8.7 Self-signed certs → install the CA
|
||
|
||
There is no in-app "accept self-signed" prompt (removed for iOS/Android parity).
|
||
The lab MCMS at `https://mcms.lab.svorka.net/` uses a self-signed cert, so it is
|
||
unreachable until that CA is installed on the device's OS trust store
|
||
(MDM/configuration profile) — or the box is given a CA-trusted cert. Surface the
|
||
resulting trust failure clearly in the error UI so it doesn't look like a silent
|
||
login break.
|
||
|
||
---
|
||
|
||
## 9. Procedures referenced in the dev guide
|
||
|
||
| # | Title | Notes |
|
||
|---|---|---|
|
||
| 2 | Determining ONU registration status | OLT-STATE["ONU States"] buckets |
|
||
| 7 | Per-ONU firmware upgrade | Full-doc PUT to ONU-CFG |
|
||
| 8 | Bulk firmware upgrade | AUTO-TASK-CFG via PUT |
|
||
| 38 | Resetting bit error rate values | Names the four FEC fields |
|
||
| 43 | Deleting an ONU | DELETE on ONU-CFG |
|
||
|
||
(Page numbers vary by document revision; cite by procedure number.)
|
||
|
||
---
|
||
|
||
## 10. Quick implementation checklist for a new client
|
||
|
||
A correct first POST-login GET against `/api/v3/onus/configs/?limit=1`
|
||
returning HTTP 200 means you've got the eight critical pieces right:
|
||
|
||
- [ ] HTTPS reachable (with cert exception if needed)
|
||
- [ ] Path prefix `/api` added
|
||
- [ ] User-Agent set to non-empty value
|
||
- [ ] Cookie jar storing `__Host-sessionid` + `__Host-csrftoken`
|
||
- [ ] `X-CSRFToken` header pulled from `__Host-csrftoken` cookie value
|
||
- [ ] `Referer` header set to base URL
|
||
- [ ] `Accept: application/json`
|
||
- [ ] Trailing slash on the path
|
||
|
||
Add for write operations:
|
||
|
||
- [ ] `Content-Type: application/json`
|
||
- [ ] Body wrapped: `{"data": ...}`
|
||
- [ ] Full-document PUT (never partial)
|
||
|
||
Add for list endpoints:
|
||
|
||
- [ ] Page size ≤ 100
|
||
- [ ] `next` cursor (not `skip`)
|
||
- [ ] Loop until short page
|
||
|
||
Once those land, the API is comfortable to work with — the hard parts
|
||
are upfront.
|
||
|
||
---
|
||
|
||
*Compiled from `323-1961-306` (REST API dev guide), `323-1961-302`
|
||
(PON Manager User Guide), and empirical testing against MCMS 6.2 with
|
||
Interos Everest 5.11/5.12 firmware on GNXS / Tibit MicroPlug ONUs.*
|