Create a Lightweight RCS Test Lab with Matrix and Mock Networks
Build a repeatable RCS lab in 2026: use Matrix bridges and a carrier simulator to test MLS E2EE, iOS beta behaviour, and privacy edge cases.
Build a lightweight RCS test lab with Matrix and mock carrier services — fast, private, repeatable
Hook: You’re an engineer or admin trying to validate cross-platform RCS behaviour — end‑to‑end encryption (E2EE) interactions, fallback cases, and how an iOS beta implements new RCS MLS flows — but you don’t want to rely on public carriers, nor expose test data on production networks. This guide gives you a practical, repeatable lab architecture (2026‑current), concrete Docker compose examples, bridge patterns for Matrix, and test plans for E2EE and privacy analysis.
Why a local RCS lab matters in 2026
RCS as a messaging substrate has changed fast since the GSMA’s Universal Profile 3.0 and the push to use MLS (Message Layer Security) for carrier-level E2EE. In late 2025 and early 2026 we saw pilot rollouts of MLS‑protected RCS by multiple carriers in Europe and Asia, plus the iOS 26.x beta including code hooks for RCS E2EE support. That means interoperability tests are now meaningful: carrier provisioning, MLS group key state, client fallbacks to SMS, and privacy leaks via provisioning endpoints.
Production carrier stacks are complex (IMS, SIP, MSRP, provisioning servers). For a developer/tester you don’t need to recreate every carrier component to validate end‑to‑end behaviour. A lightweight, contained lab that simulates the carrier control plane and bridges user endpoints into a shared, inspectable message bus (Matrix) gives the best tradeoff: speed, repeatability, and privacy.
What you’ll build (high level)
- A self‑hosted Matrix server (Synapse) and client (Element Web or ElementX) to act as the canonical test bus.
- A Matrix bridge skeleton that implements a configurable RCS carrier simulator — accepts provisioning calls, emulates MLS handshake outcomes, and forwards messages to Matrix rooms.
- Lightweight IMS/SIP stubs using Kamailio/Open5GS or SIPp where needed for SIP signalling tests.
- Android/iOS endpoints — Android emulators or devices running RCS clients, and an iPhone on the iOS beta — connected to the simulated carrier endpoints to observe client behaviour when the carrier “enables” E2EE.
- Test automation and capture — Wireshark, OpenMLS tools, and scripted test cases that exercise key rollover, group merges, and fallback to SMS.
Architecture diagram (concept)
Think of the lab as three layers:
- Endpoints — Android phones/emu, iPhone beta, web client to observe Matrix rooms.
- Simulated carrier layer — provisioning API + MLS emulator + simple IMS/SIP stub.
- Matrix core — Synapse + Element + bridge service that maps phone numbers to Matrix IDs.
Why use Matrix as the canonical bus?
- Observability: Rooms persist messages and metadata for offline forensic review.
- Bridgeability: Matrix has mature bridge frameworks (mautrix, matrix-puppet) to implement custom protocol translations.
- Isolation: Your lab traffic stays on your host or VPS; no test messages go out to real carriers.
Prerequisites
- A Linux host or small VPS (2+ vCPU, 4–8GB RAM) for Docker Compose; or a Kubernetes cluster if you prefer.
- Docker and docker‑compose (or Podman + Compose v2).
- At least one Android device or emulator and an iPhone on the iOS beta you want to test.
- Wireshark and basic CLI tools (curl, openssl).
- Optional: Open5GS and Kamailio if you plan to exercise full SIP/IMS flows.
Step 1 — Bring up a minimal Matrix testbed
Start with Synapse and Element. Use Docker Compose for reproducibility.
# docker-compose.yml (excerpt)
version: '3.7'
services:
synapse:
image: matrixdotorg/synapse:latest
volumes:
- ./synapse/data:/data
environment:
- SYNAPSE_SERVER_NAME=lab.local
ports:
- "8008:8008"
element:
image: matrixdotorg/element-web:latest
environment:
- DEFAULT_HS_URL=http://lab.local:8008
ports:
- "8080:80"
Initialize Synapse (register admin user) and create a set of test accounts that represent phone numbers using a naming convention: +1555000 -> @1555000:lab.local. This makes mapping between Matrix IDs and simulated MSISDNs trivial for the bridge.
Step 2 — Build a Matrix↔RCS bridge skeleton
If you’re familiar with the mautrix‑bridge framework (Python/Go/Node patterns exist), a bridge's responsibilities are straightforward:
- Map MSISDN <-> Matrix ID
- Receive incoming messages from the carrier simulator and post them into the appropriate Matrix room
- Take Matrix messages from the room and send them to the carrier simulator as outgoing RCS payloads
- Propagate encryption metadata where possible (e.g., signal to the simulator to emulate MLS state)
Example directory structure and a tiny Node.js express bridge:
// bridge/server.js (very condensed)
const express = require('express');
const axios = require('axios');
const { MatrixClient } = require('matrix-bot-sdk');
const client = new MatrixClient('http://lab.local:8008', 'BRIDGE_ACCESS_TOKEN');
const app = express();
app.use(express.json());
// carrier posts incoming RCS -> we forward to a Matrix room
app.post('/carrier/incoming', async (req, res) => {
const { msisdn, message, metadata } = req.body;
const mxid = `@${msisdn}:lab.local`;
const room = await ensureRoomForUser(mxid);
await client.sendMessage(room, { body: message, msgtype: 'm.text' });
res.send({ ok: true });
});
app.post('/matrix/outgoing', async (req, res) => {
const { mxid, message } = req.body;
// map mxid -> msisdn and POST to carrier simulator
await axios.post('http://rcs-sim:3000/outgoing', { msisdn: mxidToMsisdn(mxid), message });
res.send({ ok: true });
});
app.listen(4000);
This bridge is intentionally small. The heavy lifting is in the carrier simulator which controls provisioning and MLS simulation.
Step 3 — Implement a mock carrier (carrier simulator)
The simulator mimics key carrier APIs used by clients for provisioning and capability discovery. It exposes endpoints such as:
- /provision — returns capability set, policy, and an indicator whether E2EE is enabled
- /send — accepts outgoing SMS/RCS message submissions
- /incoming — used by the carrier simulator to inject inbound messages into the bridge
- /mls/state — optional: simulate MLS group state, key updates, and device lists
Key features to implement in the simulator:
- Tunable E2EE state: Toggle E2EE on/off per MSISDN, simulate key rollovers, and force client fallback to plain SMS.
- Simulated latency / error codes: Introduce spurious failures (403 provisioning, 503 transient) so you can test client error handling and retry logic.
- MLS handshake emulation: Use OpenMLS or a lightweight deterministic emulator to produce public group state blobs that clients will recognise (or not) — useful when testing MLS client behaviour without a full MLS server.
Practical tip: The simulator does not need to implement full MSRP or IMS media channels. Focus on control-plane behaviour (provisioning, capability flags, delivery receipts) and bridge the payloads to Matrix rooms where you can observe content and metadata.
Step 4 — Optional: SIP/IMS stubs for signalling tests
If you want to dig into SIP/IMS behaviour (REGISTER, SUBSCRIBE, OPTIONS), run lightweight SIP stacks in containers. Use:
- Kamailio as the SIP proxy / registrar
- SIPp for scripted SIP flows and load
- Open5GS if you want to simulate a full mobile core (attach/detach flows)
In most lab tests for messaging E2EE, you can emulate SIP registration by stubbing the register response and focus on the provisioning API the client hits after registration.
Step 5 — Connect Android and iOS test clients
Android:
- Use a physical Android device or emulator that supports RCS client stacks (Google Messages, OEM stacks). For faster iteration, a rooted emulator or an Android VM with apks installed helps to capture logs.
- Point provisioning endpoint to your simulator using DNS overrides or a local HTTP proxy (mitmproxy) if you control hostnames. Some clients discover provisioning via DNS/SRV; intercept or provide an explicit endpoint.
iOS:
- Install the iOS 26.x beta on an iPhone you control (not a production device). Apple’s beta introduced code paths for RCS MLS handshake — you’ll be exercising the set of provisioning/enable flags supplied by the simulator.
- Use a proxy or a small DNS resolver for the test device to ensure it talks to your simulated endpoints rather than real carrier servers.
Step 6 — Observability and validation
To validate E2EE behaviour, do the following:
- Capture network traces with Wireshark on the interface between the client and the simulator. Export TLS sessions where possible.
- Record the provisioning flows and ensure the simulator replies contain the expected flags (e.g., enable_mls: true).
- Inside Matrix rooms, check whether the bridge annotates messages with metadata indicating MLS/E2EE state. Your bridge should store the simulator’s MLS blobs as event fields for correlation.
- Use the OpenMLS library or CLI to parse MLS blobs you produce in the simulator. OpenMLS is a real open‑source implementation (Rust) and helps you inspect group state programmatically.
- Verify fallback: flip the simulator flag to disable E2EE mid‑conversation and confirm the Android and iOS clients either refuse to deliver encrypted payloads or fall back to plain SMS/RCS as spec’d.
Practical test cases (automatable)
- E2EE enable/disable: Start with E2EE off, send messages; flip on, trigger an MLS join; assert that clients create new sessions and that MLS group metadata appears in the simulator and bridge logs.
- Key rollover: Simulate a key rollover in /mls/state and verify clients fetch the new state and rekey conversations without data leakage.
- Cross‑platform delivery: Send a message from the iOS beta to an Android client and verify end‑to‑end content confidentiality by proving you cannot decrypt payloads in transit (only endpoints can).
- Fallback behaviour: Impose an MLS handshake failure (bad signature) and observe whether clients fall back to non‑E2EE delivery or show a user‑facing warning.
- Provisioning errors: Return 403/401 or malformed capability documents and assert the client’s error handling and logs.
Security and privacy testing checklist
- Ensure all simulator endpoints run TLS; test with valid and pinned certs to emulate carrier CA chains.
- Store MLS keys and blobs only in the simulator’s volatile test store; never reuse production keys.
- Log message metadata but redact message bodies when storing long‑term logs.
- Use network namespaces or isolated VPCs to avoid accidental egress to real carriers.
Advanced strategies — scale, CI, and Kubernetes
As your lab grows you’ll want:
- Infrastructure as code: Containerize the simulator and bridge and keep configs in Git. Use Docker Compose for local dev and Helm charts for Kubernetes CI clusters.
- Device farms: Integrate physical device clouds (on‑prem or paid services) for automated iOS/Android runs. Ensure these devices are isolated and on the same test VPC.
- Load testing: Use SIPp to create traffic loads against your stubbed IMS endpoints to measure client retry behaviour and server throttling logic.
- Fuzzing: Inject malformed MLS blobs and malformed provisioning JSON to test robustness.
Example: A concrete, repeatable Docker Compose snippet for the simulator and bridge
# docker-compose.sim.yml (condensed)
version: '3.7'
services:
rcs-sim:
image: ghcr.io/yourorg/rcs-sim:latest
ports:
- "3000:3000"
environment:
- SIM_ENABLE_MLS=true
matrix-bridge:
build: ./bridge
ports:
- "4000:4000"
depends_on:
- rcs-sim
environment:
- MATRIX_BASE_URL=http://lab.local:8008
- RCS_SIM_URL=http://rcs-sim:3000
This pattern makes it trivial to spin up the whole testbed in CI for nightly regression checks against new iOS betas or Android builds. Store expected behaviours as golden files and assert diffs on every test run.
Example logs and what to look for
When a client performs a provisioning request, expect logs like:
[rcs-sim] POST /provision msisdn=+1555000 — response: { enable_mls: true, mls_blob: <base64>... }
Bridge logs should show mapping and event enrichment:
[bridge] incoming msisdn +1555000 — forwarded to room !abc:lab.local — annotated mls_state_id=abc123
If the iOS beta attempts an MLS handshake, your simulator will either return a valid MLS state (causing the client to mark conversation as encrypted) or an error (client should show a warning).
Common pitfalls and how to avoid them
- Clients ignore your simulator: If the client uses operator DNS SRV records, you must either control DNS for the test device or use a transparent proxy to intercept and rewrite responses.
- Certificate errors: Clients often validate carrier TLS chains strictly. Use PRC‑style certs or a private CA that the test device trusts during experiments.
- Non‑deterministic state: MLS group state can be time‑sensitive. Keep simulator state deterministic or scripted so test cases remain repeatable.
2026 trends and what to test next
As of early 2026, two trends matter for labs:
- MLS adoption accelerates: Expect more carriers to enable MLS by default. Focus tests on group MLS scenarios (multiple devices per MSISDN, multi‑device handover) and key mix‑up attacks.
- Cross‑platform semantics: Apple’s iOS 26.x beta now includes RCS E2EE primitives; test interplay between iMessage fallbacks and RCS MLS handshakes to find UX inconsistencies and privacy edges.
Future lab investments to consider: adding a telemetry pipeline to capture device logs (adb logcat, device sysdiagnose), building reproducible test harnesses for MLS fuzzing, and integrating static analysis of provisioning responses to detect accidental metadata leakage.
Actionable takeaways (do this first)
- Spin up a Synapse + Element instance using Docker Compose and register MSISDN‑like Matrix IDs.
- Build a tiny bridge that forwards between your carrier simulator and Matrix rooms (the Node.js example above is enough to start).
- Create an RCS simulator that returns a configurable {enable_mls: boolean} flag and an MLS blob (or stub) for the client to consume.
- Connect one Android and one iPhone (iOS beta) via proxy/DNS overrides and run the basic enable/disable and key rollover tests.
- Capture all traffic and use OpenMLS/Wireshark to validate you cannot decrypt payloads outside endpoints when E2EE is enabled.
Final thoughts — why this lab will pay dividends
By running a local RCS lab that combines Matrix bridging with a mock carrier, you gain fast feedback on interoperability, privacy properties, and client UX. You’ll be able to validate real‑world scenarios (MLS key churn, multi‑device joins, provisioning failures) without waiting for carrier sandboxes or exposing test data to production networks. In 2026, as MLS and cross‑platform RCS move from pilots to broader rollouts, this lab model is the most pragmatic way to test, reproduce and harden behaviours that matter to users.
Call to action
Ready to try this setup? Clone our reference repo (starter Docker Compose, a bridge template and an RCS simulator) and run the five tests in the Actionable takeaways section. If you need help adapting the lab to Kubernetes or integrating an iOS device farm, reach out or open an issue on the repo — we’ll publish community recipes and CI workflows for iOS beta runs.
Get started now: spin up Synapse, drop in the bridge, and run your first E2EE toggle test. Share results and lab improvements back to help the community test cross‑platform RCS safely and privately.
Related Reading
- Festival Food at Santa Monica: What to Eat at the New Large-Scale Music Event
- What Dave Filoni’s Star Wars Slate Means for Fandom Watch Parties
- Where to Buy Cosy: London Shops for Hot‑Water Bottles, Fleecy Wraps and Winter Comforts
- From Tarot Aesthetics to Capsule Collections: What Netflix’s 'What Next' Campaign Teaches Fashion Marketers
- Storytelling as Retention: What Transmedia IP Means for Employee Engagement
Related Topics
selfhosting
Contributor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
The Future of AI in Regulatory Compliance: Case Studies and Insights
Post-COVID: The Future of Remote Work and Self-Hosting
How to Build a HIPAA-Ready Hybrid EHR: Practical Steps for Small Hospitals and Clinics
Integrating AI-Driven Workflows with Self-Hosted Tools
Harnessing the Power of Extreme Automation in Self-Hosted Environments
From Our Network
Trending stories across our publication group