← Back
Home › Systems › Security and identity

physical-access-control

Decides at the door in milliseconds, from a synchronised local table, whether a badge or phone may open it, and keeps an audit trail, alarms and video in a cloud service.

HID, Genetec, LenelS2, Verkada, Gallagher

Open the physical-access-control pagephysical-access-cont… Hdr histogramdata-structures: Log-linear bucketed histograms with bounded relative error for latency percentilesDesired-state reconciliationdistributed: Declare the target in a store; controllers repeatedly diff actual against desired from full state and apply idempotent fixes, so missed events only delay convergence.›Deterministic simulationtestingdistributed: Run the whole system in one thread under a seeded scheduler with simulated network, disk and clock, so every failure replays from its seed.›Device shadow state syncdistributed: Desired vs reported device state documents with versions and delta reconciliationEvent deduplication windowdistributed: Keep the ids of recently accepted events for a time window and drop repeats; memory stays bounded by rotating time buckets instead of per-entry timers.›Incremental sync cursorsdistributed: The client keeps one opaque token; each pull returns only the changes after it plus the next token, so sync cost scales with what changed, not with the dataset.›Primary-backup replicationdistributed: One node orders all writes and ships them to standbys; the acknowledgement rule (none, one, all) sets what a crash loses, and an external arbiter decides who takes over.›A/B partition updatesembedded: Two bootable slots and a small atomic metadata block: write the new image to the idle slot, try it once, and fall back by itself unless it reports healthy.›Alarm managementembedded: Turn raw process limits into a few actionable operator alarms with delay, deadband, acknowledge and shelving states, plus flood measurement.›Ble advertisingembedded: Bluetooth LE advertising packets, scanning and rotating identifiersOsdp reader protocolembedded: OSDP v2 secure channel between controller and reader over RS-485: polling, commands, key installProcess supervisionhardware-os: A parent process starts services, reaps their exits and restarts them under a delay and burst-limit policy, then stops them with SIGTERM before SIGKILL.›RTP and RTCPmedia: RTP stamps each media packet with sequence number, media-clock timestamp and source ID; RTCP reports loss, jitter, round trip and clock mapping within a bandwidth cap.›Video codecsmedia: H.264/HEVC/AV1 basics: frames, GOPs, hardware decode/encode APIs and latency settingsMobile push deliverynetworking: Sending notifications through APNs and FCM: HTTP/2 requests, token hygiene, collapse keys, TTL and priority, and the retry rules that keep you un-throttled.›AEAD (AES-GCM,ChaCha20-Poly1305)security: Authenticated encryption that encrypts and detects tampering in one call; the only kind of symmetric encryption you should use.›Anti passback statesecurity: Per-credential in/out zone state machine rejecting re-entry without exit, with timed and global-zone variantsApdu smart card protocolsecurity: ISO 7816 APDU command/response exchange with smart cards, eUICCs and secure elementsCoverage-guided fuzzingsecurity: Mutate inputs, run an instrumented target, and keep any input that reaches a new code edge; feedback turns blind guessing into incremental search.›HKDF key derivation(extract-and-expand)security: HMAC-based two-step key derivation: extract a uniform pseudorandom key from any secret, then expand it into as many context-bound keys as you need.›Ldap directorysecurity: LDAP directory model, bind, search filters and group membership queries for enterprise identityMutual TLSsecurity: TLS where the client also proves a certificate key, so every connection carries a verified, rotatable machine identity.›Policy engines (PDP/PEP, Cedar,Rego, ReBAC)security: Decide allow/deny for (principal, action, resource, context) in a separate engine with default deny, forbid-wins and an auditable list of determining policies.›Row-level security(multi-tenant isolation in the…security: Append a policy predicate to every query inside the database, so a forgotten WHERE tenant_id = ? in application code cannot leak another tenant's rows.›Samlsecurity: SAML 2.0 assertions, XML signatures, SP/IdP flows and canonicalization pitfallsScim provisioningsecurity: SCIM 2.0 user/group provisioning API: create, patch, deprovision, filtersTamper-evident audit logsecurity: An append-only log whose Merkle root, signed per batch, lets anyone verify an entry is present and history was never rewritten, with log-size proofs.›X509 certificatessecurity: Certificate chains, path validation, SANs, revocation and short-lived issuancePoint-in-time recoverystorage: Periodic base backups plus a continuously archived change log let you rebuild the state as of any instant, then branch from there.›Graceful drainingsystems: On a termination signal, stop taking new work, finish in-flight work until a deadline, tell clients to reconnect elsewhere, then exit; clients resume from a token.›Watch notificationsdistributed: A store numbers every write; clients list at revision R, then watch from R+1 and receive an ordered change stream with no gap, no duplicate, and a resume point.›Cooperative schedulingconcurrency: Green threads/coroutines yielding at await points on a user-space schedulerFault injectiondistributed: Inject latency, errors, kills and partitions via proxies, netem or hooksRecord replaydistributed: Record every nondeterministic input (time, randomness, I/O, model and tool outputs, per-tick player input) and re-execute deterministically for debugging, replays and state rebuildDelta encodingcompression: Store differences between consecutive values instead of the values, turning sorted IDs, timestamps and counters into small numbers that pack into few bits.›Mqtt protocolnetworking: MQTT pub/sub: topics, QoS 0/1/2, retained messages, sessions and keepaliveMessage sequence numbersdistributed: A per-stream counter on every message lets the receiver detect loss, duplicates and reordering, ack cheaply and resume after reconnect with "send from N".›Leasesdistributed: A lock that expires on its own: bounded-time ownership that survives crashes and partitions, given bounded clock drift and fencing tokens on writes.›Health checkingsystems: Decide per backend whether to route to it using probes and real-traffic errors, with consecutive-count hysteresis, capped ejection and a panic fallback.›Binary delta diffcompression: Bsdiff, VCDIFF and xdelta-style binary patches between file versionsWatchdog timerembedded: A hardware down-counter that resets the chip unless software proves, in time and in a plausible pattern, that it is still healthy.›CRC32C checksumshardware-os: The Castagnoli CRC-32 with dedicated instructions on x86 (SSE4.2) and ARMv8. It is the standard fast integrity check for storage blocks, logs and network frames.›+ 35 moreGrpc protocolnetworking: GRPC framing over HTTP/2: unary and streaming RPCs, deadlines, statusMVCCstorage: Keep timestamped versions of each row so readers see a consistent snapshot without blocking writers; the default concurrency control in modern databases.›Transparent interceptionnetworking: Redirect traffic to a local proxy (iptables TPROXY/REDIRECT, eBPF) without app changesSyscall interceptionsecurity: Intercept syscalls via ptrace, seccomp user notify or a user-space kernelBit packing and PFORcompression: Store blocks of integers with exactly b bits each and patch rare outliers separately (PFOR); the fastest integer codec family, billions of values per second.›Varint (LEB128)compression: Variable-length integers in 7-bit groups with a continuation bit; small numbers take 1 byte, the wire format of Protobuf, WebAssembly and DWARF.›Fencing tokensdistributed: A number that grows with every lock grant and is checked by the protected resource, so a paused ex-holder's late write is rejected.›LZ77compression: Replace repeated byte strings with (distance, length) back-references into a sliding window; the parsing stage of gzip, LZ4, zstd, Brotli and xz.›Rolling hashdata-structures: Rabin-Karp polynomial rolling hashes and winnowing for substring fingerprints and document matchingSuffix arraysearch-sort: Sorted array of all suffix start positions of a text; finds every occurrence of any substring by binary search, at 4-5 bytes per text byte.›SIMD programminghardware-os: Process 4-16 lanes per instruction with AVX2, AVX-512 or NEON via Rust std::arch, with runtime dispatch and a portable fallback.›+ 13 moreEbpfhardware-os: Verified in-kernel programs on hooks (kprobes, XDP, tc) with mapsPtrace process controlhardware-os: Ptrace: attach, read and write registers and memory, single-step, intercept signalsSeccomp bpfsecurity: Syscall filtering with seccomp BPF programsRadix sortsearch-sort: Non-comparison sort that distributes keys by 8-bit digits; several times faster than comparison sorts on large arrays of integers, floats or fixed-size keys.›+ 8 more+ 3 more

How it is built

Architecture overview

A physical access control system decides, thousands of times a day, whether a person may open a particular door right now, and keeps a record that cannot be quietly edited. Readers at the door read a credential (a card, a phone, a PIN); a door controller next to the door looks the credential up, checks the rules and drives the lock; a server (often a multi-tenant cloud service) owns the people, the rules and the history. Alarms and video attach to the same events. The idea that makes it work is decisions at the edge, truth at the server: the controller must decide in a fraction of a second and even when the network is down, so it does not ask the server; instead the server compiles "who may go where, when" into a small table, the controller holds a synchronised copy, and every event flows back with a sequence number so nothing is lost or counted twice. The one thing that cannot be decided purely at the edge is state shared between doors (anti-passback), and that is where the design makes an explicit, visible trade-off.

Parts:

 card / phone ──► reader ──OSDP (RS-485, secure channel)──► door controller ──► strike / relay
 (APDU, BLE)                                               │  ▲ door contact, REX, tamper
                                    local table (cache) ───┘  │
                                    anti-passback state       │ events (seq no., time)
                                                              ▼
                  mTLS ◄──────────────────── buffer (flash) ──►  ingest ─► dedup ─► audit log
                    ▲  config (desired state)                       │         │
                    │  access table (cursor sync)                   ▼         ▼
 identity (SCIM / LDAP / SAML) ─► rights compiler (PDP) ◄── admin UI    alarms ─► push ─► guard
                    ▲                                                   └─► video clip (RTP)
              cloud DB (row-level security per tenant)

How the parts fit together

Credentials and readers

A reader is deliberately dumb. It reads the credential and passes the data to the controller; it does not decide. That keeps secrets and logic inside the controlled cabinet, because the reader sits on the unsecured side of the door. The link between them is OSDP, an open protocol (IEC 60839-11-5) for a control panel talking to peripheral devices over two-wire RS-485 multi-drop, with a Secure Channel that is encrypted and authenticated (AES-128) (source: LibOSDP README). The older Wiegand wiring, a one-way pulse train with no authentication, is what OSDP replaces. A library such as LibOSDP does the polling: the application calls its refresh function at least every 50 ms to meet the protocol timing (source: LibOSDP README), which is a hard contract for the controller's main loop.

Credentials differ in how much the reader proves:

  • Smart card: the reader exchanges APDU smart-card protocol (no page yet) command and response pairs with the card, which holds a key and answers a challenge, so a cloned card number alone does not open the door.
  • Mobile credential: Bluetooth Low Energy or NFC between phone and reader. Aliro, a standard from the Connectivity Standards Alliance, defines the communication protocol between access readers and user devices (source: CSA Aliro page), so one wallet credential can work with readers of several makers. In BLE the phone's identity must not be a fixed advertised value or a tracker could follow it; advertising uses rotating identifiers (BLE advertising, no page yet).
  • Session keys: a long-term credential key never encrypts traffic directly; HKDF derives a fresh key per transaction from the credential key and a nonce, and the payload is sealed with AEAD so tampering is detected in the same call. A Replay protection window on the reader's counter stops a recorded exchange from being played again.

Why a push notification is not the unlock path: Mobile push delivery is best-effort with throttling, so it carries invitations and alerts ("your key is ready", "door held open"), never the unlock itself, which is local, between phone and reader.

Door controller

The controller holds the table the server compiled: for each credential, its validity window, the groups of doors it opens and the schedules that apply, plus the door's own configuration (lock type, unlock time, held-open limit, holidays). On a read it does a lookup, a schedule check, a state check (anti-passback, door already open) and then drives the relay. The budget from card presented to strike energised is a fraction of a second (order of a few hundred milliseconds), which is why the lookup is local and no network round trip is allowed in the path.

I/O is more than a relay: a door-position contact, a request-to-exit button, a tamper switch and a power fault input. Their changes become events, and a changed contact is debounced before it is trusted. Two safety rules shape the design: the lock fail mode is chosen per door (fail-secure stays locked on power loss, fail-safe releases), and life-safety exit is wired in hardware so no software fault can trap anyone. Software never sits in the egress path.

Anti-passback is a per-credential in-or-out state, anti-passback-state (no page yet): a credential that entered a zone cannot enter again until it has exited, which stops sharing a badge. Within one controller's zone, the state is local and exact. Across controllers (a "global" zone) the state needs a shared view, and there are only two honest options: ask the server in the decision path, and fail open or closed when the link is down, or let controllers exchange state peer-to-peer and accept short windows of disagreement.

Rights compiler

Cardholders belong to roles and groups; doors belong to areas; schedules and holidays qualify when. This is an authorization problem, and the server answers it with a policy engine: evaluate (person, door, time) against rules with default deny and "forbid wins", so a lockdown or a suspended badge overrides any grant, and return which rule decided so the audit can explain it.

The engine's output is not served per request. A compiler evaluates it ahead of time for every person and door and flattens it into the controller's table (credential → door groups and schedules). The server is expressive and slow to change; the controller is simple and fast; the cost is that a rule change must recompile and resync.

In a hosted service many customers share one database. Row-level security puts the tenant predicate inside the database so a forgotten WHERE tenant_id = ? in application code cannot expose another customer's cardholders.

Identity sources

Large customers do not type in cardholders. SCIM provisioning (no page yet) lets an identity provider create, patch and deactivate users through a standard API (SCIM, RFC 7643 and 7644), LDAP directory (no page yet) reads groups from an enterprise directory, and SAML (no page yet) signs administrators in to the management UI with their company login. The rule is that deprovisioning is the fastest path: a deactivated user must leave the table at once, so SCIM deactivation triggers the sync path below instead of waiting for a nightly batch.

Sync and state

Two kinds of data move to controllers, and they use two mechanisms.

  • Cardholder data (large, mostly unchanged): Incremental sync cursors. The controller keeps one opaque cursor; each pull returns the changes after it, including tombstones for removals, plus the next cursor. Cost scales with what changed, not with the number of cardholders, which matters when a site has tens of thousands of badges (order of) and a slow uplink. The server pokes the controller on change so a revocation does not wait for a poll interval.
  • Configuration (door settings, schedules, firmware version): Desired-state reconciliation. The server stores the desired document; the controller (or an agent for it) reports what it has; a loop diffs the two from full state and applies idempotent fixes, so a missed message only delays convergence. Device shadow state sync (no page yet) is the document shape, with versions so a stale write cannot overwrite a newer one.

A controller that has been offline holds the table it last got. How stale a table may get is a policy: revocations are the dangerous part, and a site that cannot tolerate it uses short credential lifetimes (visitors, contractors) so the controller itself expires them even with no network.

Event pipeline and audit

Every action becomes an event: access granted, denied (with reason), door forced, held open, tamper, power fault, configuration change. A controller stamps each with its identifier, a monotonic sequence number and its clock time, and writes it to a flash buffer before trying to send. On reconnect it replays from the last sequence the server acknowledged, so retransmits are normal and the server uses an Event deduplication window keyed by (controller, sequence) to drop repeats with bounded memory. Sequence gaps reveal lost events.

Events and administrative changes (who granted whom access to which door) go into the Tamper-evident audit log: append-only, with a signed root per batch, so an administrator or intruder cannot erase a door-open record without detection. Controller clocks drift, so the log keeps both the controller's time and the server's receive time.

Alarms, video and notification

A raw event is not an alarm. Alarm management turns events into a few actionable alarms: "door forced" when the contact opens with no valid grant and no request-to-exit, "held open" after a delay; each with a delay or deadband so a bouncing door does not flood the guard, plus acknowledge and shelve states. High-priority alarms reach a guard's phone through push.

Video turns an alarm into evidence. Cameras stream with RTP and RTCP, whose sequence numbers and media-clock timestamps let the recorder cut a clip around the event time; a clip starts at a keyframe, so codec and keyframe interval (Video codecs, no page yet) decide how tight it can be. Camera and controller clocks must agree within a second or two, or the clip misses the door.

Device security and updates

Each controller is a computer on the customer's network with the authority to unlock doors. It authenticates to the server with Mutual TLS using a per-device certificate (X.509 certificates, no page yet): the server knows which controller sent each event, and a revoked certificate cuts a stolen controller off. Firmware updates use A/B partition updates, because a controller at a remote site cannot be reflashed by hand: the new image is tried once, and the old one returns unless the new one reports healthy (reaches the server and answers readers). Doors still work during an update because the other slot's table is untouched.

Operations

The server runs under Process supervision; ingest uses Graceful draining so a deploy hands controllers off without a reconnect storm. Point-in-time recovery covers the cardholder database, and Primary-backup replication keeps it available. HDR histogram (no page yet) tracks command-to-unlock and event-to-alarm latency. The rights compiler is tested under Deterministic simulation testing with simulated controllers, lost links and clocks; Coverage-guided fuzzing targets the reader frames and the sync parser, which take input from outside the trust boundary.

Data and control flow

One badge tap (the path that must not wait):

  1. The reader reads the credential and sends it over the OSDP secure channel; the controller's refresh loop picks it up within its polling interval.
  2. The controller looks up the credential in the local table, checks validity, schedule and anti-passback state, and checks the door is not already open.
  3. On grant it energises the relay for the configured unlock time and starts a held-open timer; on deny it records the reason.
  4. The event is written to the flash buffer with its sequence number; the anti-passback state changes.
  5. When the link is up, the event is sent over mTLS; the server deduplicates, writes the audit log and evaluates alarm rules.

One revocation:

  1. HR deactivates a user; SCIM tells the access server.
  2. The rights compiler recomputes the person's rows; the change gets a new cursor value with a tombstone.
  3. The server pokes every controller that holds the person; each pulls from its cursor and removes the row.
  4. Offline controllers apply it on reconnect. Time spent is the push plus one pull; the exposure window is the controller's offline time.

One alarm:

  1. A controller sends "door forced" (contact open, no grant).
  2. Alarm management applies its delay and deadband, raises the alarm and pushes to the guard.
  3. The recorder cuts a clip around the event time from the camera's stream; the guard acknowledges, and the acknowledge is audited.

Scaling

  1. Per door the load is tiny; latency and correctness matter. Lever: a bounded, local decision path.
  2. Per site the limits are the controller's table, flash buffer and uplink: compact rows, cursor sync, a capped event buffer that drops low-priority events first.
  3. Fleet: many persistent mTLS connections and reconnect storms after an outage. Levers: shard ingest by tenant or controller id, jittered reconnect, draining on deploy, event batching.
  4. Rule complexity grows compile time; recompile only the affected people and doors.
  5. Tenants: row-level security first, then dedicated databases for the largest customers.
  6. Video is the largest byte volume; keep it out of the event path and fetch clips on demand.

Failure modes

FailureSymptomCauseFix
Network downdoors keep working, events delayed, revocations latecontroller decides locally from cacheflash buffer with replay, short credential lifetimes for risky groups, alarm on stale sync
Duplicate eventsdoubled entriesretransmit after reconnectdedup on (controller, sequence)
Lost eventgap in the sequencebuffer overflow, flash faultsequence-gap alert, larger buffer, drop low-priority first
Anti-passback lockoutperson cannot exit or re-entertailgating, badge not read on exittimed anti-passback, supervised reset, soft mode on global zones
Clock driftschedules open early or late, clip missescontroller clock wrongtime sync, store both controller and receive time, alarm on large skew
Door held open false alarmsguards ignore alarmscontact bounce, loading dock usedebounce, delay and deadband, shelving with expiry
Replayed mobile unlockdoor opens for a recorded exchangeno freshness checkchallenge-response with a nonce, replay window
Cross-tenant leakone customer sees another's peoplemissing tenant filterrow-level security in the database
Bad firmwarecontroller not responding at a remote siteupdate faultA/B slots with health confirm, staged rollout
Software in the egress pathperson trapped in a firelocks controlled only by softwarehardware release on fire alarm and exit hardware, outside the software

Real implementations

namewhat it isrepolicencenotes
LibOSDPOSDP control-panel and peripheral implementation, with Rust, Python, C++ bindingsgoToMain/libosdpApache-2.0the reader link, AES-128 secure channel
uhppotedbuilding blocks for access control on UHPPOTE TCP/IP controllers: API, CLI, REST, MQTTuhppoted/uhppotedMITopen software around cheap commodity controllers
Cedarpolicy language and evaluation enginecedar-policy/cedarApache-2.0for the rights compiler's policy layer
Open Policy Agentgeneral policy engine (Rego)open-policy-agent/opaApache-2.0alternative policy engine

Commercial platforms (HID, Genetec, LenelS2, Verkada, Gallagher) do not publish their internals.

Sources

  • LibOSDP README: github.com/goToMain/libosdp (OSDP, IEC 60839-11-5, Secure Channel AES-128, 50 ms refresh).
  • Connectivity Standards Alliance, Aliro.
  • P. Hunt et al., SCIM: Core Schema, RFC 7643, and SCIM: Protocol, RFC 7644.
  • H. Schulzrinne et al., RTP: A Transport Protocol for Real-Time Applications, RFC 3550.
  • H. Krawczyk, P. Eronen, HKDF, RFC 5869; D. McGrew, An Interface and Algorithms for Authenticated Encryption, RFC 5116.
  • uhppoted: README.

Similar systems

Share the most building blocks with this one.