Now picture the same controller during a test run. Alice’s admin card should open the server room, Eve’s guest card should not, a disabled card should fail, and repeated unknown cards should trigger lockout without leaking useful clues. This implementation chapter turns the setup into the decision path that answers three separate questions: who is this, what may they do, and what evidence did the system record?
The IoT punchline is that a secure access decision is a sequence, not one if statement. Start simple: run a known-good card, a known-bad card, and a lockout case, then check that the LEDs, buzzer, serial output, and audit log all tell the same story.
Chapter Roadmap
This chapter turns the setup lab into an end-to-end access decision:
First load the credential database, access levels, zone table, security state, and audit-log structures.
Then trace the code path from button input through lockout, authentication, authorization, feedback, and logging.
Next run the five testing scenarios and connect each visible signal to the internal reason code.
Finally compare the lab with production authentication choices, throttling, timing-safe comparison, and review quizzes.
Checkpoints recap key decisions. Deep-dive sections are useful for review, but you can skim them on a first pass.
Shield Shelly
“Security is not a wall, it’s a list of who can do what — and the proof that the list is enforced.”
In this lab she traces the decision path — lockout first, then identity, then zone level — with the audit log as her evidence.
Putting Numbers to It
Lab execution time can be estimated before starting runs:
Worked example: With 5 runs and per-run times of 4 min setup, 6 min execution, and 3 min review, total lab time is \(5\times(4+6+3)=65\) minutes. This prevents under-scoping and helps schedule complete experimental cycles.
Show code
viewof labRuns = Inputs.range([1,20], {value:5,step:1,label:"Number of runs"})viewof labSetup = Inputs.range([1,15], {value:4,step:1,label:"Setup time (min)"})viewof labExec = Inputs.range([1,30], {value:6,step:1,label:"Execution time (min)"})viewof labReview = Inputs.range([1,15], {value:3,step:1,label:"Review time (min)"})
By completing this implementation, you will be able to:
Implement the full authentication flow from card scan to access decision
Build authorization logic with role-based access control
Create comprehensive audit logging for security forensics
Test multiple security scenarios including lockouts and disabled accounts
Understand the relationship between real-world systems and this lab implementation
For Beginners: Lab: Access Control Implementation
Access control determines what each user or device is allowed to do in an IoT system. Think of a hospital where doctors, nurses, and visitors each have different access levels – doctors can prescribe medication, nurses can administer it, and visitors can only visit patients. Similarly, IoT access control ensures each device and user can only perform actions appropriate to their role.
Copy this complete code into the Arduino IDE or an optional Wokwi ESP32 sketch. This includes all functions from the setup plus the core authentication, authorization, and feedback logic. Paste all sections below in order into a single file.
14.3.1 Includes, Pin Definitions, and Data Structures
/* * Secure IoT Access Control System * Demonstrates: RFID-style authentication, role-based access control, * lockout policies, and comprehensive audit logging */#include <Arduino.h>// Pin definitionsconstint LED_ACCESS_GRANTED =2;// Green LEDconstint LED_ACCESS_DENIED =4;// Red LEDconstint LED_SYSTEM_STATUS =5;// Yellow LEDconstint LED_ADMIN_MODE =18;// Blue LEDconstint BUZZER_PIN =19;constint BUTTON_SELECT =15;constint BUTTON_ACCESS =16;// Access levels and credential structureenum AccessLevel { ACCESS_NONE=0, ACCESS_GUEST=1, ACCESS_USER=2, ACCESS_ADMIN=3};struct UserCredential {constchar* cardId;constchar* userName; AccessLevel accessLevel;bool isActive;};
14.3.2 Credential Database and Security Configuration
UserCredential* authenticateCard(constchar* cardId){for(int i =0; i < NUM_USERS; i++)if(constantTimeCompare(cardId, userDatabase[i].cardId))return&userDatabase[i];return NULL;}bool constantTimeCompare(constchar* a,constchar* b){size_t lenA = strlen(a), lenB = strlen(b);size_t maxLen =(lenA > lenB)? lenA : lenB;volatileint result =0;for(size_t i =0; i < maxLen; i++){char charA =(i < lenA)? a[i]:0;char charB =(i < lenB)? b[i]:0; result |= charA ^ charB;} result |=(lenA != lenB);return(result ==0);}bool authorizeAccess(AccessLevel userLevel, AccessLevel required){return userLevel >= required;}
14.3.6 Access Request Processing
This is the core logic that combines authentication (identity check), authorization (permission check), and lockout enforcement into a single access decision.
You now know how the lab makes the code path observable:
The audit ring keeps up to 50 entries with timestamp, card ID, user name, event type, zone name, outcome, and attempted level.
The access button calls requestAccess(2), so the default hardware path tests the Server Room unless a serial ZONE n command changes the target.
A 250 ms debounce delay keeps one button press from turning into repeated access requests.
The code is complete enough to run; now the scenarios separate the main outcomes.
14.4 Testing Scenarios
14.4.1 Scenario A: Valid Admin Access
CARD 0
ZONE 2
Expected Results:
Alice Admin (ADMIN level) accessing Server Room
Green LED on + Blue LED on (admin indicator)
High-pitched success tones
“ACCESS GRANTED” message
14.4.2 Scenario B: Valid User - Insufficient Privileges
CARD 1
ZONE 2
Expected Results:
Charlie User (USER level) trying Server Room (needs ADMIN)
Red LED flashes
Low error tones
“ACCESS DENIED - Insufficient Privileges” message
Authentication PASSED but Authorization FAILED
14.4.3 Scenario C: Guest Access to Public Area
CARD 2
ZONE 0
Expected Results:
Eve Guest accessing Public Lobby
Green LED on (no blue - not admin)
Success tones
“ACCESS GRANTED” message
14.4.4 Scenario D: Unknown Card (Brute Force Prevention)
CARD 4
ZONE 0
ZONE 0
ZONE 0
Expected Results after 3 failed attempts:
Yellow LED blinking
System LOCKED for 60 seconds
“Possible brute force attack detected” message
14.4.5 Scenario E: Disabled Account
CARD 3
ZONE 0
Expected Results:
Frank Former (disabled account)
Red LED flashes
“Account DISABLED” message
Shelly’s Access Ledger
Who: Charlie’s card authenticates successfully; the system knows exactly who he is.
What: the Server Room requires ADMIN, so his USER level fails authorization, not authentication.
Proof: after three unknown-card attempts the system locks for 60 seconds and reports the brute-force detection.
14.5 Security Concepts Demonstrated
Concept
Implementation
Real-World Application
Token-Based Auth
RFID card IDs matched against database
Physical access cards, API keys, JWT tokens
Role-Based Access Control
GUEST < USER < ADMIN hierarchy
Corporate access levels, AWS IAM roles
Account Lockout
60s lockout after 3 failures, escalating
Windows account lockout, banking apps
Disabled Accounts
Active flag checked during authentication
Employee offboarding, compromised accounts
Constant-Time Compare
XOR comparison prevents timing attacks
Secure password validation, crypto libraries
Audit Logging
Complete event trail with timestamps
SIEM systems, compliance logging
Separation of Concerns
Authentication separate from Authorization
OAuth 2.0 (AuthN) vs resource permissions (AuthZ)
Defense in Depth
Multiple checks: valid card, active account, sufficient level
Enterprise security architecture
14.6 Challenge Exercises
Challenge 1: Add Time-Based Access Control
Modify the system so certain zones are only accessible during “business hours” (simulate with a time window): - Server Room: Only accessible 9 AM - 5 PM - After hours, even admins need a special override code - Log all after-hours access attempts
Challenge 2: Implement Two-Person Rule
For high-security zones (Control Center), require two different admin cards within 30 seconds: 1. First admin presents card 2. System prompts “Waiting for second administrator…” 3. Second admin presents different card within time limit 4. Both must be admin level 5. If timeout or same card, deny access
Challenge 3: Add Temporary Access Passes
Implement visitor passes with expiration: - Create a new ACCESS_VISITOR level - Visitor cards expire after a set time (e.g., 8 hours from first use) - Track first-use timestamp per card - Automatically disable expired passes
Challenge 4: Implement Anti-Passback
Prevent “tailgating” by tracking entry/exit: - Users must exit a zone before re-entering - If card used for entry without previous exit, deny access - Track current zone for each user - Alert on anti-passback violations
The challenges extend the same implementation pattern. Before adding them, compare each lab shortcut with the production control it stands in for.
14.7 Connecting to Real-World Systems
Lab Implementation
Production Equivalent
RFID card IDs
Smart cards with cryptographic challenge-response
Hardcoded user database
LDAP/Active Directory integration
Serial monitor
Secure management API (TLS + client certificates)
LED indicators
Security Operations Center (SOC) dashboards
Buzzer alerts
SIEM integration, SMS/email notifications
Simple lockout
Adaptive authentication with risk scoring
Role hierarchy
Attribute-Based Access Control (ABAC)
Real-world systems these concepts apply to:
Corporate badge access (HID, SALTO)
Data center entry (biometric + card + PIN)
AWS IAM policies (principals, resources, actions)
Kubernetes RBAC (roles, bindings, service accounts)
Worked Example: Calculating Account Lockout Impact on User Experience
Scenario: Your smart building access control system has 3,000 employees. You need to choose appropriate lockout settings that balance security with user experience.
Step 1: Analyze Current Failed Login Patterns
Data from 30 days (before lockout policy):
Total login attempts: 450,000
Failed logins: 18,000 (4% failure rate)
Users with >3 failures: 427 users (14.2% of workforce)
Breakdown of failures:
1-2 failures: 2,100 users (70% - typos, forgotten passwords)
3-5 failures: 427 users (14.2% - legitimate users struggling)
6+ failures: 73 users (2.4% - suspicious, possible brute force)
Step 2: Calculate Lockout Impact
// Scenario A: Aggressive Lockout (3 attempts, 30 minutes)constint MAX_FAILED_ATTEMPTS =3;constunsignedlong LOCKOUT_DURATION_MS =1800000;// 30 minutesImpact Calculation: Users affected:427+73=500 users/month (16.7%) Average lockout time:30 minutes Productivity loss:500 users × 30 min =250 person-hours/month Help desk calls:500 calls/month × $15/call =$7,500/month User frustration: HIGH// Scenario B: Balanced Lockout (5 attempts, 15 minutes)constint MAX_FAILED_ATTEMPTS =5;constunsignedlong LOCKOUT_DURATION_MS =900000;// 15 minutesImpact Calculation: Users affected:73 users/month (2.4%) Average lockout time:15 minutes Productivity loss:73 × 15 min =18.25 person-hours/month Help desk calls:73 calls/month × $15/call =$1,095/month User frustration: LOW// Scenario C: Lenient (10 attempts, 60 minutes)constint MAX_FAILED_ATTEMPTS =10;constunsignedlong LOCKOUT_DURATION_MS =3600000;// 60 minutesImpact Calculation: Users affected:~15 users/month (0.5%) Security risk: Brute force attacks can try10 passwords Average lockout time:60 minutes Productivity loss:15 × 60 min =15 person-hours/month Help desk calls:15 calls/month × $15/call =$225/month Security posture: WEAK
Step 4: Measure Real-World Results After Implementation
After 30 days with escalating lockout (3 attempts, escalating):
Legitimate User Impact:
Users locked out: 127 (down from 500)
Average lockout: 1.8 minutes (down from 30)
Help desk calls: 127 × $15 = $1,905/month (73% reduction)
User satisfaction: 87% (was 62%)
Security Improvements:
Brute force attempts detected: 47
Accounts compromised: 0 (was 3 suspected)
Average attacker attempts before lockout: 3.2
Maximum attacker attempts: 8 (hit 5-minute cap)
ROI Calculation:
Cost savings: $7,500 - $1,905 = $5,595/month
Security incidents prevented: 3 compromises × $25,000 avg = $75,000
Annual ROI: $67,140 + $75,000 = $142,140
Key Decisions Made:
3-attempt threshold: Catches obvious attacks while allowing typos
Escalating duration: First lockout is short (1 min), grows with repeated failures
5-minute cap: Prevents excessive lockouts for confused legitimate users
30-second escalation steps: Gradual increase balances security and UX
Lesson Learned: Escalating lockouts provide better security than fixed timeouts while significantly improving user experience. The 73% reduction in help desk calls alone justified the implementation.
Interactive: Lockout Policy Impact Calculator
Use this calculator to explore how different lockout settings affect user experience and security costs for your deployment.
Decision Framework: Choosing RFID vs Biometric vs PIN Authentication
Choose the right authentication method based on your building’s security requirements and user population.
Factor
RFID Card
PIN Code
Fingerprint
Face Recognition
Security Level
Medium
Low
High
High
Cost per User
$3-8 (card)
$0
$15-30 (reader)
$50-200 (reader)
Speed
<1 second
3-5 seconds
1-2 seconds
1-3 seconds
User Acceptance
Very High (95%+)
High (85%)
Medium (70%)
Low (50%)
Hygiene Concerns
None
None
High (COVID)
None (contactless)
Lost/Stolen Risk
High (card theft)
Medium (shoulder surfing)
Low (can’t steal)
Low (can’t steal)
Works with Gloves
Yes
Yes
No
Yes
Privacy Concerns
Low
None
Medium (biometric data)
High (biometric + video)
Backup Method Required
Yes (lost cards)
No
Yes (injury, sweat)
Yes (poor lighting, masks)
ADA Compliance
Excellent
Good (audio prompts)
Fair (tactile aids)
Poor (blind users)
Use Case Recommendations:
Facility Type
Primary Method
Backup Method
Rationale
Office (low security)
RFID card
PIN code
Fast, cheap, high acceptance. PIN for lost cards.
Data Center (high security)
Fingerprint + RFID
PIN (admin override)
Two-factor: something you have + something you are.
Hospital (hygiene priority)
Face recognition
RFID card
No-touch for infection control. Card for masked staff.
Factory (gloves worn)
RFID card
Face recognition
Gloves prevent fingerprint. Face works with PPE.
Apartment Building (residents)
RFID fob + PIN
Mobile app (Bluetooth)
Low-cost. Residents unlikely to accept biometrics.
Decision Tree:
Q1: What is the security level required?
Low (office, apartments) → RFID card + PIN
High (data center, labs) → Continue to Q2
Q2: Are biometrics acceptable to users?
NO → Multi-factor RFID (card + PIN)
YES → Continue to Q3
Q3: Do users wear gloves or have hand injuries?
YES → Face recognition + RFID backup
NO → Fingerprint + RFID backup
Q4: Is contactless important (hygiene)?
YES → Face recognition or mobile app (Bluetooth)
NO → Fingerprint is most reliable biometric
Common Mistake: Choosing face recognition for “cool factor” without considering: - Privacy backlash (biometric data storage concerns) - Poor performance with masks, glasses, hats - High false rejection rate (5-10%) frustrates legitimate users - Expensive ($200+/reader vs $50 for RFID) - Requires backup method (adding cost and complexity)
Best Practice: Start with RFID + PIN (proven, cheap, high acceptance). Add biometrics only where security requirements justify the cost and user friction.
Common Mistake: Using strcmp() for Credential Comparison
Mistake: Using standard string comparison functions exposes your system to timing attacks.
Vulnerable Code:
// BAD: strcmp returns immediately on first mismatchUserCredential* authenticateCard(constchar* cardId){for(int i =0; i < NUM_USERS; i++){if(strcmp(cardId, userDatabase[i].cardId)==0){return&userDatabase[i];// FOUND!}}return NULL;// Not found}
Attack Scenario:
An attacker measures response times for different card IDs:
Attempt 1: cardId = "AAAA_ADMIN_001"
Response time: 0.23 ms (fast - mismatched at first character 'A' vs 'R')
Attempt 2: cardId = "RFID_AAAA_001"
Response time: 0.51 ms (slower - matched first 5 chars "RFID_")
Attempt 3: cardId = "RFID_ADMIN_001"
Response time: 1.34 ms (even slower - matched 14 chars!)
By measuring timing differences, the attacker deduces: - Card IDs start with “RFID_” - Valid format is “RFID_ADMIN_NNN” - Can guess valid card IDs character-by-character
The loop exits on the FIRST mismatch. If strings match for 5 characters, the loop runs 5 iterations. If they match for 10 characters, it runs 10 iterations. This timing difference leaks information!
Secure Implementation (from lab code):
// GOOD: Constant-time comparisonbool constantTimeCompare(constchar* a,constchar* b){size_t lenA = strlen(a);size_t lenB = strlen(b);// Always process maximum length (timing independent of input)size_t maxLen =(lenA > lenB)? lenA : lenB;volatileint result =0;// volatile prevents compiler optimizationfor(size_t i =0; i < maxLen; i++){char charA =(i < lenA)? a[i]:0;char charB =(i < lenB)? b[i]:0; result |= charA ^ charB;// XOR accumulates differences}// Also check lengths match result |=(lenA != lenB);return(result ==0);// True only if ALL bits matched}
Why This Works:
Fixed iterations: Always loops maxLen times, regardless of where mismatch occurs
No early exit: Cannot break out of loop early (timing constant)
XOR accumulation: Differences accumulate in result, but timing doesn’t reveal WHICH characters differ
Volatile keyword: Prevents compiler from optimizing away comparisons
Timing Test Results:
Input Comparison | strcmp() Time | constantTimeCompare() Time
------------------------------|---------------|---------------------------
"AAAA" vs "RFID_ADMIN_001" | 0.18 ms | 1.42 ms
"RFID" vs "RFID_ADMIN_001" | 0.43 ms | 1.42 ms
"RFID_ADMIN_00" vs "RFID_ADMIN_001" | 1.21 ms | 1.42 ms
"RFID_ADMIN_001" vs "RFID_ADMIN_001" | 1.34 ms | 1.42 ms
Standard deviation (strcmp): 0.47 ms (HUGE variance)
Standard deviation (constantTime): 0.03 ms (negligible)
Real-World Impact: In 2015, timing attacks were used to extract PayPal API keys character-by-character. The attack took 2 hours to extract a 32-character key by measuring response times. Constant-time comparison would have prevented this entirely.
Testing Checklist:
Matching Quiz: Match Basic Lab Components to Roles
Ordering Quiz: Order the Basic Authentication Lab Setup
Label the Diagram
Code Challenge
14.8 Deep Dive: Login Throttling and Offline Cracking
Strong password hashing protects credentials if the verifier database leaks. It does not stop an attacker from standing at the live login prompt and guessing. The implementation has to defend two separate attack surfaces: online guessing against the running service and offline cracking against a stolen verifier file.
Attack Surface
Attacker Limit
Primary Defences
Online guessing
Service policy, rate limits, lockouts, network latency, telemetry, and MFA prompts.
Per-account and per-source throttles, exponential backoff, step-up MFA, generic failure messages, and monitoring.
Offline cracking
Attacker hardware and the cost of each KDF attempt.
Unique salts, slow work factors, memory-hard KDF parameters, breached-password prevention, and credential reset after compromise.
The same password can face very different economics. Against the login page, the defender controls the clock: reject bursts, delay repeated failures, alert on suspicious sources, and require a second factor when risk rises. Against a leaked verifier table, the defender no longer controls the clock; the attacker can run GPUs or rented instances until guesses run out. That is why this lab treats the login handler, password verifier, and audit log as separate controls.
Visible feedback in a lab can also mislead if copied directly into production. A red LED or detailed error helps students understand the decision path, but a public service should return a bland failure while recording precise reasons internally. The user experience should remain recoverable for legitimate users, while monitoring still distinguishes typos, credential stuffing, targeted brute force, and account lockout abuse.
Rate limiting and lockout turn a fast online attack into a slow one:
Account lockout:
after N failed attempts, lock or challenge for cooldown T
Exponential backoff:
delay doubles after repeated failures
1 s, 2 s, 4 s, 8 s, ...
Effective guess rate:
attempts allowed per unit time
The arithmetic is the design check. If a policy allows five failed attempts per 15 minutes, an attacker gets about 480 guesses per day against one account. Exhausting a 4-digit PIN space of 10,000 combinations would take about 21 days in the worst case, or roughly 10 days on average, instead of seconds. If a policy allows three guesses and then a 10-minute cooldown, the cap is about 18 guesses per hour per account. These estimates are not enough by themselves, but they reveal whether the policy is in the right order of magnitude.
The rate limit should match the asset and the recovery path. A shared classroom lab can lock an account aggressively because an instructor can reset it. A production IoT fleet should not hard-lock every field technician after three failures if that blocks maintenance during an outage. Combine counters: per account to protect the user, per source network or device fingerprint to slow automation, and per credential pattern to catch password-spraying across many accounts. The policy should also define when to step up to MFA rather than simply refusing access.
Risk
Safer Implementation Pattern
Lockout denial of service
Prefer temporary delays, step-up challenges, and per-source-plus-account throttles over easily triggered hard lockouts.
Credential stuffing
Watch for one guess across many accounts, use breached-password checks, MFA, and source/device reputation.
Username enumeration
Return one generic failure message and keep response timing uniform; log the precise reason internally.
Counter reset by restart
Store counters in a shared, durable rate-limit service or database rather than one process’s memory.
Overcollection in logs
Log account identifier, method, source, result, and reason code; never log attempted passwords or bearer tokens.
Good implementations expire counters carefully so a single typo does not poison an account all day, and they avoid leaking state through messages such as “that email exists but is locked.” The safest public response is usually the same generic failure, while internal logs carry reason codes, counter values, IP or device signals, and whether a step-up challenge was offered. That split keeps the interface usable without turning it into an enumeration oracle.
Shelly’s Access Ledger
Who: throttles count per account, per source, and per credential pattern.
What: five failures per 15 minutes caps one account near 480 guesses a day; a 10,000-combination PIN survives ten days on average.
Proof: outside, one generic failure; inside, reason codes and counters — never the attempted password.
Throttle to stop guessing, but design the throttle so it cannot become a lockout weapon. The login prompt is defended by policy and rate, the database by hashing, and the account by MFA.
Checkpoint: Production Boundaries
You now know which lab controls need production hardening:
The live login prompt is defended by throttles, lockouts, monitoring, and MFA; the leaked verifier database is defended by salts and slow KDF work factors.
Five failures per 15 minutes allows about 480 guesses per day, while three guesses and a 10-minute cooldown allows about 18 guesses per hour.
Public failures should stay generic, but internal logs should keep reason codes, counter values, source signals, and step-up decisions.
14.9 Summary
By completing this lab, you have:
Implemented token-based authentication using simulated RFID cards
Built a role-based access control system with three distinct permission levels
Applied account lockout policies with escalating timeouts to prevent brute force attacks
Created comprehensive audit logging for security forensics
Designed intuitive feedback systems using LEDs and audio
Understood the critical difference between authentication and authorization
Key Takeaway
Authentication answers “Who are you?” while Authorization answers “What can you do?”
The audit log ties it all together for the third “A” - Accounting - so you can investigate incidents after the fact.
14.10 Knowledge Check
Quiz: Lab: Access Control Implementation
Common Pitfalls
1. Storing Passwords as MD5 or SHA256 Hashes Without Salt
MD5 and SHA256 are cryptographic hash functions, not password hashing functions. They are fast (enabling brute force) and lack salt (enabling precomputed rainbow table attacks). Always use bcrypt, scrypt, or Argon2 for password storage.
2. Returning Detailed Error Messages on Authentication Failure
Returning “User not found” vs “Password incorrect” tells attackers which usernames are valid. Always return the same generic error (“Invalid credentials”) regardless of whether the username or password was wrong.
3. Not Implementing the Authorization Check in Middleware
Adding authorization checks directly inside each route handler leads to inconsistent enforcement — it’s easy to forget to add the check in a new handler. Implement authorization as Express middleware (or equivalent) that runs before every protected handler.
4. Using String Comparison Instead of Timing-Safe Functions for Token Verification
Standard string equality comparison (===) short-circuits on the first mismatch, creating a timing side-channel that can reveal how many characters of a guessed token match. Use crypto.timingSafeEqual() for all security-sensitive comparisons.
bcrypt: A password hashing function with built-in salt and adjustable cost factor; the standard for securely storing user passwords in databases
Password Salt: A random value added to the password before hashing; ensures that identical passwords produce different hashes, preventing rainbow table attacks
Protected Route: An API endpoint that requires a valid authentication token; implemented as middleware that rejects unauthorized requests before they reach the handler
Role Assignment: The process of associating a user or device with one or more roles that define their permissions; typically stored in the user record or a separate roles table
Permission Check: A validation that the authenticated principal’s role includes permission to perform the requested operation on the target resource
Timing-Safe Comparison: Using constant-time comparison functions for password verification to prevent timing attacks that can reveal whether a password attempt was close to correct
Token Expiry: The time-to-live value embedded in authentication tokens; balances security (short-lived = less exposure if stolen) against usability (long-lived = fewer re-logins)
In 60 Seconds
This hands-on lab builds a working RBAC system from scratch — implementing user authentication with hashed passwords, role assignment, permission checking middleware, and protected API endpoints — giving you practical experience with the security code patterns used in real IoT platforms.