Skip to content

Secure LSL Manual Testing Guide

Date Created: 2025-12-12 Purpose: End-to-end validation of Secure LSL features in real hardware scenarios


Quick Start

IMPORTANT: You must build from source in each new location. Do NOT copy the build directory - the library paths (RPATH) are baked into executables at compile time.

# 1. Clone the repository
git clone https://github.com/sccn/secureLSL.git
cd secureLSL

# 2. Build secure liblsl (must build fresh, not copy!)
cd liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(nproc)

# 3. Generate keys
export LSLAPICFG=~/.lsl_api/lsl_api.cfg
./lsl-keygen

# 4. Test secure communication
./cpp_secure_outlet &   # Terminal 1
./cpp_secure_inlet      # Terminal 2

Building from Source

macOS

# Install dependencies
brew install libsodium cmake

# Clone and build
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(sysctl -n hw.ncpu)

# Verify build
./lslver  # Should show security version info

Linux (Ubuntu/Debian)

# Install dependencies
sudo apt update
sudo apt install libsodium-dev cmake build-essential

# Clone and build
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j$(nproc)

Raspberry Pi

# Install dependencies
sudo apt update
sudo apt install libsodium-dev cmake build-essential

# Clone and build (same as Linux)
git clone https://github.com/sccn/secureLSL.git
cd secureLSL/liblsl
mkdir -p build && cd build
cmake -DLSL_SECURITY=ON ..
make -j4

# Or deploy pre-built from Mac/Linux:
# From host machine:
./benchmarks/scripts/deploy_to_rpi.sh pi@<RPI_IP>

Prerequisites

Build Artifacts

After building, you should have (in liblsl/build/):

# Set your paths
export LSL_BUILD=$HOME/secureLSL/liblsl/build

# Secure liblsl library
$LSL_BUILD/liblsl-secure.dylib    # macOS
$LSL_BUILD/liblsl-secure.so       # Linux

# Command-line tools
$LSL_BUILD/lsl-keygen             # Key generation
$LSL_BUILD/lsl-config             # Configuration management

# Test executables
$LSL_BUILD/cpp_secure_outlet      # Test outlet
$LSL_BUILD/cpp_secure_inlet       # Test inlet

Lab Recorder (Optional)

# Clone and build Lab Recorder with secure liblsl
git clone https://github.com/labstreaminglayer/App-LabRecorder.git
cd App-LabRecorder
mkdir build && cd build
cmake -DLSL_DIR=$LSL_BUILD ..
make -j4

# Copy secure library to app bundle (macOS)
mkdir -p LabRecorder.app/Contents/Frameworks
cp $LSL_BUILD/liblsl-secure.dylib LabRecorder.app/Contents/Frameworks/liblsl.2.dylib

Devices

  • Primary machine (Mac/Linux): For development and testing
  • Raspberry Pi (optional): For cross-machine tests, RPi 4 recommended

Test Categories

1. Key Management Tests

1.1 Generate Keys Without Passphrase

# Create test config directory
mkdir -p /tmp/lsl_test
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg

# Generate key without passphrase
$LSL_BUILD/lsl-keygen

# Verify config was created
cat $LSLAPICFG

Expected Result: - [x] Config file created with [security] section - [x] private_key field contains base64-encoded key - [x] public_key field contains base64-encoded key - [x] No encrypted_private_key field

1.2 Generate Keys With Passphrase

mkdir -p /tmp/lsl_test_pass
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg

# Generate key with passphrase
$LSL_BUILD/lsl-keygen --passphrase

# When prompted, enter: testpassword123

Expected Result: - [x] Config file created with [security] section - [x] encrypted_private_key field present - [x] NO plaintext private_key field - [x] public_key field present

1.3 Device-Bound Session Token

export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg

# Show device ID
$LSL_BUILD/lsl-config --show-device-id

# Create session token (remember this device) - prompts for passphrase securely
$LSL_BUILD/lsl-config --remember-device --passphrase

# Or with custom expiry (90 days, or -1 for never)
$LSL_BUILD/lsl-config --remember-device --passphrase --days 90

# Check session token was created
ls -la ~/.lsl_api/

# Verify auto-unlock works (no passphrase needed now)
$LSL_BUILD/lsl-config --show-public

# Later: remove session token (requires passphrase again on next startup)
$LSL_BUILD/lsl-config --forget-device

Expected Result: - [x] Device ID displayed as 64-character hex string - [x] Passphrase prompted securely (no echo) - [x] Session token created successfully - [x] Session token file created in ~/.lsl_api/ - [x] Public key displays WITHOUT passphrase after token creation - [x] --forget-device removes the token

1.4 Export/Import Keys

mkdir -p /tmp/lsl_export_test

# Export keys
export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
$LSL_BUILD/lsl-keygen --export /tmp/lsl_export_test/device1

# Verify exported files
ls -la /tmp/lsl_export_test/
cat /tmp/lsl_export_test/device1.pub

# Import on "new device" (simulated)
mkdir -p /tmp/lsl_import_test
export LSLAPICFG=/tmp/lsl_import_test/lsl_api.cfg
$LSL_BUILD/lsl-keygen --import /tmp/lsl_export_test/device1.key.enc
# Enter passphrase when prompted

Expected Result: - [x] device1.pub file created (public key) - [x] device1.key.enc file created (encrypted private key) - [ ] Import succeeds with correct passphrase - [ ] Import fails with wrong passphrase


2. Secure Stream Communication Tests

2.1 Secure Outlet to Secure Inlet (Same Machine)

# Terminal 1: Start secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet

# Terminal 2: Start secure inlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_inlet

Expected Result: - [ ] Outlet shows "Security enabled" message - [ ] Inlet discovers secure stream - [ ] Inlet receives data correctly - [ ] No encryption errors

2.2 Insecure Inlet Cannot Read Secure Stream

Setup:

# Terminal 1: Start secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet

# Terminal 2: Start insecure inlet (using standard liblsl)
export PYLSL_LIB=$LSL_BUILD/liblsl.dylib
python3 -c "
import pylsl
streams = pylsl.resolve_streams(wait_time=2.0)
for s in streams:
    print(f'Found: {s.name()} - security={s.desc().child(\"security\").child_value(\"enabled\")}')
    if 'secure' in s.name().lower():
        try:
            inlet = pylsl.StreamInlet(s)
            sample, ts = inlet.pull_sample(timeout=2.0)
            print(f'Sample received (should NOT happen): {sample}')
        except Exception as e:
            print(f'Connection rejected as expected: {e}')
"

Expected Result: - [ ] Stream is discovered (visible in resolver) - [ ] Connection attempt fails or data is unreadable - [ ] Error message indicates security mismatch

2.3 Mixed Security Environment Detection

Setup: Run one secure outlet and one insecure outlet simultaneously.

# Terminal 1: Secure outlet
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet &

# Terminal 2: Insecure outlet (Python with standard liblsl)
export PYLSL_LIB=$LSL_BUILD/liblsl.dylib
python3 -c "
import pylsl
import time
info = pylsl.StreamInfo('InsecureTest', 'Test', 1, 100, pylsl.cf_float32, 'insecure123')
outlet = pylsl.StreamOutlet(info)
print('Insecure outlet running...')
for i in range(60):
    outlet.push_sample([float(i)])
    time.sleep(0.1)
"
# Terminal 3: Resolve and list all streams
export PYLSL_LIB=$LSL_BUILD/liblsl-secure.dylib
python3 -c "
import pylsl
streams = pylsl.resolve_streams(wait_time=3.0)
print(f'Found {len(streams)} streams:')
for s in streams:
    sec = s.desc().child('security')
    enabled = sec.child_value('enabled') if sec else 'N/A'
    print(f'  - {s.name()}: security={enabled}')
"

Expected Result: - [ ] Both streams are discovered - [ ] Security status correctly reported for each stream - [ ] Secure inlet refuses to connect to insecure stream (unanimous security rule)


3. Lab Recorder Tests

3.1 Launch Lab Recorder with Secure Library

# Copy secure library to Lab Recorder
cp $LSL_BUILD/liblsl-secure.dylib \
   $LABRECORDER_BUILD/LabRecorder.app/Contents/Frameworks/liblsl.2.dylib

# Or use DYLD_LIBRARY_PATH
export DYLD_LIBRARY_PATH=/Users/yahya/Documents/git/secureLSL/liblsl/build:$DYLD_LIBRARY_PATH

# Set config
export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg

# Launch Lab Recorder
open $LABRECORDER_BUILD/LabRecorder.app
# OR
$LABRECORDER_BUILD/LabRecorder.app/Contents/MacOS/LabRecorder

3.2 Lab Recorder Discovers Secure Stream

Setup: 1. Start secure outlet in terminal 2. Launch Lab Recorder with secure library

Screenshot Checkpoints: - [x] Lab Recorder main window showing stream list - [x] Secure stream visible with security indicator (lock icon 🔒) - [x] Stream details showing security status

3.3 Lab Recorder Records Secure Stream

Test: 1. Start secure outlet 2. Connect Lab Recorder to secure stream 3. Start recording for 10 seconds 4. Stop recording 5. Verify XDF file was created

# Verify recording
ls -la ~/Documents/*.xdf

Expected Result: - [x] Recording completes without errors - [x] XDF file created with correct size (28kb in 12 seconds) - [ ] File can be opened with EEGLAB or MNE-Python (not tested)

3.4 Lab Recorder: Both Insecure

Test: 1. Start insecure outlet (no security config) 2. Run Lab Recorder without security config 3. Record data

Expected Result: - [x] Recording works without security enabled - [x] No security errors or warnings

3.5 Lab Recorder Rejects Secure Stream When Insecure

Test: 1. Start secure outlet (with security config) 2. Run Lab Recorder WITHOUT security config 3. Attempt to record

Expected Result: - [x] Stream IS discovered and shown in UI - [x] Recording button works (UI does not block) - [x] Connection REJECTED by outlet: "403 Security required but client has no security enabled" - [x] Recording file created but 0kb (empty) - no data transferred - [x] Inlet retries connection but keeps getting rejected

Note: This test now shows the behavior AFTER passing test 3.6 (security mismatch detection). If mismatch detection is bypassed (e.g., via hideWarnings), the connection is rejected at the protocol level with 403 errors.

3.6 Security Mismatch Detection (Pre-Recording Check)

Status: IMPLEMENTED (2025-12-17)

Feature: Lab Recorder now detects security mismatches BEFORE attempting to record and displays a clear error dialog.

Test: 1. Start a secure outlet (with security credentials) 2. Run Lab Recorder WITHOUT security credentials (LSLAPICFG pointing to insecure config) 3. Click Update to find the secure stream 4. Select the stream and click Start

Expected Result: - [x] Error dialog appears immediately (before recording starts) - [x] Dialog shows stream names in blue with bullet points - [x] Dialog explains the issue and how to fix it - [x] Red warning text: "Recording cannot proceed with mismatched security settings." - [x] Recording does NOT start (no 0kb files created)

Error Messages: - Secure stream + Insecure recorder: "The following streams require security, but Lab Recorder does not have security credentials configured" - Insecure stream + Secure recorder: "Security mismatch detected for the following streams"

Implementation Notes: - Uses new lsl::local_security_enabled() API to check local security status - Compares with stream.security_enabled() for each selected stream - Check happens in startRecording() before creating the recording object


4. Cross-Machine Tests (Raspberry Pi <-> Mac Mini)

4.1 Network Setup

Mac Mini (Inlet): - IP: (note your IP, e.g., 192.168.1.100) - Ensure firewall allows LSL ports (16571-16600)

Raspberry Pi (Outlet): - IP: (note your IP, e.g., 192.168.1.101) - Deploy secure liblsl:

# From Mac Mini
./benchmarks/scripts/deploy_to_rpi.sh pi@192.168.1.101 --build

4.2 Shared Keypair Authorization Model

IMPORTANT: Secure LSL uses a shared keypair model. All authorized devices must have the SAME keypair (public + private key). Devices with different keys will be rejected as "not authorized".

Setup: Distribute Same Key to All Devices

# 1. Generate key on Mac Mini with passphrase
mkdir -p ~/.lsl_api
$LSL_BUILD/lsl-keygen --passphrase --force

# 2. Export for distribution
$LSL_BUILD/lsl-keygen --export /tmp/lab_shared

# 3. Copy to Raspberry Pi
scp /tmp/lab_shared.key.enc pi@192.168.1.101:/tmp/
scp /tmp/lab_shared.pub pi@192.168.1.101:/tmp/

# 4. On Raspberry Pi, import the shared key
ssh pi@192.168.1.101
mkdir -p ~/.lsl_api
./lsl-keygen --import /tmp/lab_shared.key.enc -o ~/.lsl_api/lsl_api.cfg
# Enter the same passphrase used during generation

# 5. Verify both devices have same public key fingerprint
# Mac Mini:
$LSL_BUILD/lsl-config --show-public
# Raspberry Pi:
./lsl-config --show-public
# Fingerprints MUST match!

4.3 Test Case: Matching Keys (Should Work)

IMPORTANT: This test reflects real-world usage where outlets run continuously and inlets join mid-stream. Validation "errors" due to missing initial samples are EXPECTED behavior, not failures.

Raspberry Pi (Outlet) - Start first, runs continuously:

ssh pi@192.168.1.101
cd ~/securelsl_benchmark/liblsl/build

# Start outlet with many samples (runs for a while)
LSLAPICFG=~/.lsl_api/lsl_api.cfg ./cpp_secure_outlet --name SecureEEG --samples 100000 &

Mac Mini (Inlet) - Joins mid-stream:

# Wait a few seconds, then connect
LSLAPICFG=~/.lsl_api/lsl_api.cfg $LSL_BUILD/cpp_secure_inlet --stream SecureEEG --samples 50 --validate

Expected Result: - [x] Stream discovered across network - [x] Log shows: "Secure session established with client (fingerprint: SHA256:xx:xx...)" - [x] Encrypted data transmitted and decrypted correctly - [x] Sequential sample values prove correct decryption (e.g., 25388, 25389, 25390...) - [x] Validation "errors" about sample offset are NORMAL (inlet joined mid-stream) - [x] No "public key mismatch" errors

4.4 Test Case: Different Keys (Should Be Rejected)

Setup: Outlet running with shared key, inlet uses DIFFERENT key

Raspberry Pi (Outlet with Key A):

ssh pi@192.168.1.101
cd ~/securelsl_benchmark/liblsl/build

# Outlet using shared lab key (SHA256:79:8c:1d:7d)
LSLAPICFG=~/.lsl_api/lsl_api.cfg ./cpp_secure_outlet --name DifferentKeyTest --samples 100000 &

Mac Mini (Inlet with Key B - Different):

# Create config with DIFFERENT key
mkdir -p /tmp/lsl_different_key
cat > /tmp/lsl_different_key/lsl_api.cfg << 'EOF'
[security]
enabled = true
private_key = <different_base64_key>
EOF

# Attempt connection with different key
LSLAPICFG=/tmp/lsl_different_key/lsl_api.cfg $LSL_BUILD/cpp_secure_inlet --stream DifferentKeyTest --timeout 15

Expected Result: - [x] Stream IS discovered (visible in resolver) - [x] Connection attempt FAILS with "403 Public key mismatch - not authorized" - [x] Outlet logs: "Connection refused: client has different public key (fingerprint: SHA256:xx:xx...)" - [x] Inlet logs: "Stream transmission broke off (The other party sent an error: LSL/110 403 Public key mismatch)" - [x] No data is transferred - inlet times out - [x] Inlet retries connection but keeps getting rejected

4.5 Test Case: One Device Insecure (Should Be Rejected)

Setup: Pi has security enabled, Mac does NOT

Raspberry Pi (Secure Outlet):

ssh pi@192.168.1.101
./cpp_secure_outlet --name SecureOnlyTest --samples 500

Mac Mini (No Security):

# Use config without security or delete security section
mkdir -p /tmp/lsl_nosec
echo "[lab]" > /tmp/lsl_nosec/lsl_api.cfg
export LSLAPICFG=/tmp/lsl_nosec/lsl_api.cfg

$LSL_BUILD/cpp_secure_inlet --stream SecureOnlyTest

Expected Result: - [ ] Stream IS discovered - [ ] Connection FAILS with "403 Security required but client has no security enabled" - [ ] Unanimous security enforcement works

4.6 Test Case: Passphrase-Protected Key with Device Token

Setup: Key encrypted with passphrase, unlocked via device-bound token

Mac Mini:

# Generate passphrase-protected key
$LSL_BUILD/lsl-keygen --passphrase --force
# Enter: testpass123

# Create device-bound session token (no passphrase needed after this)
$LSL_BUILD/lsl-config --remember-device --passphrase
# Enter: testpass123

# Verify auto-unlock works (should NOT ask for passphrase)
$LSL_BUILD/lsl-config --show-public

# Export and distribute to Pi
$LSL_BUILD/lsl-keygen --export /tmp/lab_key
scp /tmp/lab_key.key.enc pi@192.168.1.101:/tmp/

Raspberry Pi:

ssh pi@192.168.1.101
./lsl-keygen --import /tmp/lab_key.key.enc
# Enter: testpass123

# Create device token on Pi too
./lsl-config --remember-device --passphrase
# Enter: testpass123

# Now test communication (no passphrase prompts)
./cpp_secure_outlet --name TokenTest --samples 500 &

Mac Mini:

$LSL_BUILD/cpp_secure_inlet --stream TokenTest --samples 100

Expected Result: - [ ] No passphrase prompts after token creation - [ ] Communication works between devices - [ ] Session tokens are device-specific (bound to hardware ID)

4.4 Cross-Machine Lab Recorder

Raspberry Pi (Outlet):

ssh pi@192.168.1.101
./cpp_secure_outlet

Mac Mini (Lab Recorder):

export LSLAPICFG=/tmp/lsl_test/lsl_api.cfg
open $LABRECORDER_BUILD/LabRecorder.app

Screenshot Checkpoints: - [x] Lab Recorder showing remote secure stream - [x] Recording from remote secure stream


5. Error Handling Tests

5.1 Wrong Passphrase

export LSLAPICFG=/tmp/lsl_test_pass/lsl_api.cfg
export LSL_KEY_PASSPHRASE=wrongpassword
$LSL_BUILD/cpp_secure_outlet

Expected Result: - [ ] Clear error message about decryption failure - [ ] Outlet does not start - [ ] No crash or undefined behavior

5.2 Missing Config File

export LSLAPICFG=/nonexistent/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet

Expected Result: - [ ] Clear error message about missing config - [ ] Falls back to insecure mode or refuses to start

5.3 Corrupted Key File

# Create corrupted config
mkdir -p /tmp/lsl_corrupt
cat > /tmp/lsl_corrupt/lsl_api.cfg << EOF
[security]
enabled = true
private_key = not_valid_base64!!!
public_key = also_not_valid!!!
EOF

export LSLAPICFG=/tmp/lsl_corrupt/lsl_api.cfg
$LSL_BUILD/cpp_secure_outlet

Expected Result: - [ ] Clear error message about invalid key format - [ ] No crash or undefined behavior


6. Performance Validation

6.1 Encryption Overhead Measurement

# Run benchmark suite (if available)
cd $SECURELSL_ROOT/benchmarks/scripts
python3 run_benchmark_suite.py --local-only

Expected Result: - [ ] Overhead < 5% for typical EEG data rates - [ ] No dropped samples at 1000 Hz


Screenshot Archive

Screenshots are stored in: .context/screenshots/

Naming convention: - labrecorder_stream_list_YYYYMMDD.png - labrecorder_secure_warning_YYYYMMDD.png - labrecorder_recording_YYYYMMDD.png - etc.


Test Log

Date Test Machine(s) Result Notes
2025-12-12 1.1 Key Gen Mac Mini PASS Keys generated successfully, config written
2025-12-12 3.1 Lab Recorder Launch Mac Mini PASS Security enabled, credentials loaded
2025-12-12 Lock icon display Mac Mini PASS Lock emoji shows at end of stream name
2025-12-16 1.1-1.4 Key Management Mac Mini PASS All key operations verified
2025-12-16 2.1 Insecure outlet + Secure inlet Mac Mini PASS Rejected with 403
2025-12-16 2.2 Secure outlet + Insecure inlet Mac Mini PASS Rejected with 403
2025-12-16 2.3 Both Insecure Mac Mini PASS 50 samples received
2025-12-16 4.3 Cross-machine Same Keys Mac + RPi (WiFi) PASS Secure session established, encrypted transfer works
2025-12-16 4.4 Cross-machine Different Keys Mac + RPi (WiFi) PASS Rejected with 403, no data transferred
2025-12-16 C++ Unit Tests Mac Mini PASS 27 cases, 3059 assertions
2025-12-16 C++ Exported Tests Mac Mini PASS 13 cases, 1036 assertions
2025-12-16 Lab Recorder: Secure lock icon Mac + RPi (WiFi) PASS Lock icon displays for secure streams
2025-12-16 Lab Recorder: Secure+Secure Mac + RPi (WiFi) PASS 28kb recorded in 12 seconds
2025-12-16 Lab Recorder: Insecure+Insecure Mac PASS Recording works without security
2025-12-16 Lab Recorder: Secure outlet + Insecure recorder Mac + RPi (WiFi) PASS Rejected with 403, 0kb file
2025-12-17 Lab Recorder: Security mismatch detection UI Mac PASS Error dialog with actionable message
2025-12-17 SigVisualizer: Security mismatch detection Mac PASS Error dialog shows stream names with lock icon
2025-12-17 SigVisualizer: Secure+Secure visualization Mac PASS Encrypted data visualized correctly
2025-12-17 Cross-machine Lab Recorder Mac + RPi (WiFi) PASS 32kb recorded in 14 seconds, lock icon visible

Test Session Notes (2025-12-16)

Lab Recorder Integration Testing

Status: PASSED (all scenarios verified)

Setup: - Lab Recorder built with secure liblsl (symlink liblsl.2.dylib -> liblsl-secure.1.16.1.dylib) - RPATH in Lab Recorder points to secureLSL build directory - Secure outlet running on Raspberry Pi (192.168.0.180, WiFi)

Test Results:

  1. Secure Stream Discovery with Lock Icon:
  2. Secure stream "SecureEEG" discovered by Lab Recorder
  3. Lock icon (🔒) displayed at end of stream name
  4. Security status visible in stream list

  5. Secure+Secure Recording:

  6. Pi outlet: Secure (key SHA256:79:8c:1d:7d)
  7. Lab Recorder: Secure (same key)
  8. Result: Recording successful, 28kb file created in 12 seconds
  9. Log: "Secure session established", "Using encrypted data transfer"

  10. Insecure+Insecure Recording:

  11. Pi outlet: Insecure (no security config)
  12. Lab Recorder: Insecure (no security config)
  13. Result: Recording successful, data transferred without encryption

  14. Secure Outlet + Insecure Recorder (Rejection):

  15. Pi outlet: Secure (key SHA256:79:8c:1d:7d)
  16. Lab Recorder: Insecure (no security config)
  17. Result: Connection REJECTED repeatedly
  18. Log: "403 Security required but client has no security enabled"
  19. Recording file: 0kb (empty) - no data transferred
  20. UI shows recording in progress but file stays at 0kb

Future Enhancement Noted: - Lab Recorder should display security error messages in UI - Currently errors are only visible in terminal logs - User sees recording "working" but file stays at 0kb

Cross-Machine WiFi Testing (Mac + Raspberry Pi)

Status: PASSED

Network Configuration: - Mac: 192.168.0.108 (WiFi en1) - Raspberry Pi: 192.168.0.180 (WiFi wlan0) - Latency: 5-8ms (ping)

Shared Key: - Fingerprint: SHA256:79:8c:1d:7d:a6:b4:34:22... - Same key deployed to both devices via lsl-keygen --export/--import

Test Results:

  1. Same Keys - Acceptance Test:
  2. Pi outlet: ./cpp_secure_outlet --name SecureSignal --samples 100000
  3. Mac inlet: ./cpp_secure_inlet --stream SecureSignal --samples 20 --validate
  4. Result: Stream discovered, secure session established, 20 samples received
  5. Outlet log: "Secure session established with client (fingerprint: SHA256:79:8c:1d:7d...)"
  6. Inlet received sequential data (25388, 25389, 25390...) proving correct decryption

  7. Different Keys - Rejection Test:

  8. Pi outlet: Same key (SHA256:79:8c:1d:7d)
  9. Mac inlet: Different key (SHA256:23:83:f5:f0)
  10. Result: Stream discovered but connection REJECTED
  11. Outlet log: "Connection refused: client has different public key"
  12. Inlet log: "403 Public key mismatch - not authorized"
  13. Inlet retried 9 times, all rejected, timed out with no data

Key Learnings: - Inlets joining mid-stream is NORMAL behavior (outlets run continuously) - Validation errors from sample offset are expected, not failures - Sequential sample values prove correct decryption - Both outlet AND inlet verify peer's public key (bidirectional authorization)


Test Session Notes (2025-12-12)

Lab Recorder Security Integration

Status: Working

Key Findings: 1. Lab Recorder correctly loads security credentials from LSLAPICFG 2. Lock emoji (UTF-8: \xF0\x9F\x94\x92) displays at end of secure stream names 3. About dialog shows security version info when using secure build

Library Linking Notes: - Lab Recorder uses @rpath/liblsl.2.dylib for library resolution - When testing with secure library, either: - Copy liblsl-secure.dylib to LabRecorder.app/Contents/Frameworks/liblsl.2.dylib - Or modify symlinks in build dir to point liblsl.2.dylib -> liblsl-secure.1.16.1.dylib

Code Changes Made: - mainwindow.cpp line 296: Changed lock icon from prefix to suffix - Before: prefix + name (host) - After: name (host) + suffix


Environment Setup

Before running tests, set these environment variables:

# Path to secure LSL build directory
export LSL_BUILD=$HOME/secureLSL/liblsl/build

# Path to secureLSL repository root
export SECURELSL_ROOT=$HOME/secureLSL

# Path to Lab Recorder build (if using Lab Recorder)
export LABRECORDER_BUILD=$HOME/App-LabRecorder/build

# Path to security configuration
export LSLAPICFG=~/.lsl_api/lsl_api.cfg

# Python pylsl (if using Python)
export PYLSL_LIB=$LSL_BUILD/liblsl-secure.dylib  # macOS
# export PYLSL_LIB=$LSL_BUILD/liblsl-secure.so   # Linux


Known Issues / Workarounds

  1. DYLD_LIBRARY_PATH on macOS: System Integrity Protection may prevent library path override for GUI apps. Use install_name_tool or copy library to app bundle instead.

  2. Lab Recorder library path: May need to copy secure library to Frameworks folder:

    mkdir -p $LABRECORDER_BUILD/LabRecorder.app/Contents/Frameworks
    cp $LSL_BUILD/liblsl-secure.dylib \
       $LABRECORDER_BUILD/LabRecorder.app/Contents/Frameworks/liblsl.2.dylib
    

  3. Firewall: For cross-machine tests, ensure LSL ports are open:

  4. UDP/TCP 16571-16600
  5. macOS: System Preferences → Security & Privacy → Firewall → Allow incoming connections
  6. Linux: sudo ufw allow 16571:16600/tcp && sudo ufw allow 16571:16600/udp

  7. Raspberry Pi Performance: For best results on RPi:

  8. Use Ethernet (not WiFi) for lower latency
  9. Use RPi 4 with 4GB+ RAM
  10. Disable unnecessary services to reduce jitter

Future Decisions

Library Naming: liblsl-secure vs liblsl 2.x

Current: liblsl-secure.dylib (separate from liblsl.dylib)

Proposed: Rename to liblsl 2.x with ABI version 3 - Rationale: "In 5 years nobody remembers version 1" - security becomes the default - liblsl 1.x = legacy insecure, liblsl 2.x = secure standard - No -secure suffix needed if security is the norm

Status: Pending community discussion


SigVisualizer Integration

Repository: https://github.com/labstreaminglayer/App-SigVisualizer

Status: COMPLETE (2025-12-17)

6.1 Security Mismatch Detection

Implementation: - Added local_security_enabled() to lsl_security_helper.py - Modified paintwidget.py to check security status before creating inlets - Error dialog shows stream names (with lock icon) and actionable fix instructions - Users can retry with Update button after fixing credentials

Test Results:

Scenario Result Notes
Secure outlet + Insecure SigVisualizer PASS Error dialog shown, stream name visible with lock icon
Secure outlet + Secure SigVisualizer PASS Data visualized correctly with encryption
Lock icon display PASS Lock emoji (🔒) shown for secure streams

Commits: - 216f6f6 - Add security mismatch detection with informative error dialogs


SigViewer Integration (Future Work)

SigViewer (cbrnr/sigviewer) is a different visualization tool that may need secure LSL support.

Repository: https://github.com/cbrnr/sigviewer

Status: Pending (lower priority - SigVisualizer is the primary LSL visualizer)