# Unit 04 — Core Adapters ## Goal Pluggable adapter pattern for Open5GS and free5gc. New cores can be added by subclassing `CoreAdapter`. ## Files - `server/core_adapters/base.py` - `server/core_adapters/open5gs.py` - `server/core_adapters/free5gc.py` - `server/core_adapters/__init__.py` (factory function) --- ## `server/core_adapters/base.py` ```python from abc import ABC, abstractmethod from typing import Optional class CoreAdapter(ABC): @abstractmethod def add_subscriber(self, imsi: str, ki: str, opc: str) -> None: """Add a subscriber to the core network. ki and opc are hex strings.""" @abstractmethod def remove_subscriber(self, imsi: str) -> None: """Remove a subscriber from the core network.""" @abstractmethod def list_subscribers(self) -> list[dict]: """Return list of subscriber dicts from the core.""" ``` --- ## `server/core_adapters/open5gs.py` Uses direct MongoDB insertion (same approach as Open5GS web UI). ```python import logging from pymongo import MongoClient from .base import CoreAdapter logger = logging.getLogger(__name__) class Open5GSAdapter(CoreAdapter): def __init__(self, mongodb_uri: str, database_name: str = "open5gs"): self.client = MongoClient(mongodb_uri) self.db = self.client[database_name] def add_subscriber(self, imsi: str, ki: str, opc: str) -> None: doc = { "imsi": imsi, "security": { "k": ki, "opc": opc, "amf": "8000", "sqn": 0 }, "ambr": { "downlink": {"value": 1, "unit": 3}, "uplink": {"value": 1, "unit": 3} }, "slice": [{ "sst": 1, "default_indicator": True, "session": [{ "name": "internet", "type": 3, "ambr": { "downlink": {"value": 1, "unit": 3}, "uplink": {"value": 1, "unit": 3} } }] }] } self.db.subscribers.replace_one({"imsi": imsi}, doc, upsert=True) logger.info(f"Added subscriber {imsi} to Open5GS") def remove_subscriber(self, imsi: str) -> None: self.db.subscribers.delete_one({"imsi": imsi}) logger.info(f"Removed subscriber {imsi} from Open5GS") def list_subscribers(self) -> list[dict]: return list(self.db.subscribers.find({}, {"_id": 0})) ``` --- ## `server/core_adapters/free5gc.py` Stub — same document format, different DB name. Implement fully when free5gc is available. ```python from .open5gs import Open5GSAdapter class Free5GCAdapter(Open5GSAdapter): def __init__(self, mongodb_uri: str, database_name: str = "free5gc"): super().__init__(mongodb_uri, database_name) ``` --- ## `server/core_adapters/__init__.py` Factory function: ```python from .base import CoreAdapter from .open5gs import Open5GSAdapter from .free5gc import Free5GCAdapter from typing import Optional def get_adapter(config: dict) -> Optional[CoreAdapter]: core_cfg = config.get("core", {}) core_type = core_cfg.get("type", "none") uri = core_cfg.get("mongodb_uri", "mongodb://localhost:27017") db_name = core_cfg.get("database_name", "open5gs") if core_type == "open5gs": return Open5GSAdapter(uri, db_name) elif core_type == "free5gc": return Free5GCAdapter(uri, db_name) else: return None # No core integration (export-only mode) ``` --- ## Verification ```bash # With no MongoDB running — verify import and factory python -c " from server.core_adapters import get_adapter cfg = {'core': {'type': 'none'}} adapter = get_adapter(cfg) assert adapter is None print('Factory returns None for type=none: OK') # Test Open5GS adapter instantiation (no connection attempt until query) cfg2 = {'core': {'type': 'open5gs', 'mongodb_uri': 'mongodb://localhost:27017', 'database_name': 'open5gs'}} adapter2 = get_adapter(cfg2) print(f'Open5GS adapter: {adapter2.__class__.__name__}') " ```