Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: Bug report
about: Something in SimBLE behaves incorrectly
title: ""
labels: bug
---

<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 Nirapod Labs
-->

## What happened

## What you expected

## Steps to reproduce

## Environment

- SimBLE version (`simblectl version` or the release tag):
- macOS version:
- Xcode and Simulator runtime:
- Role: central or peripheral

## Logs or screenshots
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Nirapod Labs

blank_issues_enabled: false
contact_links:
- name: Security report
url: https://github.com/nirapod-labs/simble/security/advisories/new
about: Report a security vulnerability privately, not as a public issue.
22 changes: 22 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest a capability for SimBLE
title: ""
labels: enhancement
---

<!--
SPDX-License-Identifier: Apache-2.0
SPDX-FileCopyrightText: 2026 Nirapod Labs
-->

## The problem

## The capability you want

## Alternatives you considered

## Scope

SimBLE is a faithful dev bridge to the Mac's Bluetooth radio, simulator-only and behind a fence.
Note anything the request would touch in the fence or the custody boundary.
7 changes: 7 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs
## How

## Testing

## Checklist

- [ ] The change is scoped and small enough to review in one sitting.
- [ ] Conventional commits, and CI is green (lint, build, tests, fence).
- [ ] A change to the mechanism, protocol, or security model was designed first.
- [ ] Nothing here lets the interposer reach a production build or cross the custody boundary.
58 changes: 58 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Nirapod Labs
#
# Build the architecture document with Quarkdown and publish it to GitHub Pages as the project site.
# Quarkdown ships a self-contained Linux build (bundled JRE), pinned by version and checksum here.
name: pages

on:
push:
branches: [main]
paths:
- "docs/architecture/**"
- ".github/workflows/pages.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Serialize deploys; a newer push supersedes an in-flight one.
concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
env:
QD_VERSION: "2.2.0"
QD_SHA256: "810b885087cdb41e07279311c0b0fb141d86ac89adf1b18533623d3c4eb97836"
steps:
- uses: actions/checkout@v4

- name: Fetch Quarkdown (pinned, checksum-verified)
run: |
url="https://github.com/iamgio/quarkdown/releases/download/v${QD_VERSION}/quarkdown-linux-x64.zip"
curl -fsSL "$url" -o quarkdown.zip
echo "${QD_SHA256} quarkdown.zip" | sha256sum -c -
unzip -q quarkdown.zip -d qd

- name: Build the document
run: ./qd/quarkdown/bin/quarkdown c docs/architecture/simble-architecture.qd --strict --out build

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: build/SimBLE-Architecture

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deploy.outputs.page_url }}
steps:
- id: deploy
uses: actions/deploy-pages@v4
82 changes: 82 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<!--
SPDX-License-Identifier: CC-BY-4.0
SPDX-FileCopyrightText: Contributor Covenant contributors
-->

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our community include:

- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the maintainers responsible for enforcement, privately, through the [Nirapod Labs](https://github.com/nirapod-labs) organization. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of actions.

**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1, available at <https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.

Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).

For answers to common questions about this code of conduct, see the FAQ at <https://www.contributor-covenant.org/faq>. Translations are available at <https://www.contributor-covenant.org/translations>.
43 changes: 24 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,34 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs
# Contributing

SimBLE is PR-driven. Everything lands through a pull request that a maintainer
reviews. Nothing goes straight to `main` after the repository seed.
reviews. Nothing goes straight to `main`.

## Basics
## The basics

- Branch off `main` and keep the branch focused.
- Use conventional commit subjects. The allowed types and scopes are in
`.commitlintrc.json`.
- Keep PRs small enough to review in one sitting.
- CI must be green before merge.
- The release is cut from a version tag on merged `main`.
- Branch off `main` and keep the branch focused on one thing.
- Conventional commits, lowercase subject after the type and scope. The allowed
types and scopes are in `.commitlintrc.json`, and the commit-msg hook checks
them, so a bad message will not commit.
- The PR title becomes the squash subject on `main`, so it has to be a valid
conventional subject too. GitHub appends ` (#N)` and `subject-max-length` is
50, so keep the title around 45 characters. commitlint runs on the PR.
- Small PRs. If a reviewer cannot hold the whole change in their head, split it.
- CI has to be green before merge: lint, build, and the relevant tests.

## Building
## Hooks

Run `pnpm install` and `pnpm exec lefthook install` once. After that, formatting
and the commit-message check run on commit, and the no-direct-main guard runs on
push. If a hook blocks you and you genuinely need around it, that is a
conversation with a maintainer, not a quiet `--no-verify`.

Run:
## Building

```sh
make bootstrap
make build
make test
```
`make bootstrap` from a fresh clone, then `make build` and `make test`. The
toolchain and every `make` target are in [docs/development.md](docs/development.md).

## Design changes
## Design before code

If your PR changes the BLE routing mechanism, wire protocol, security model, or
platform limits, describe the design in the PR before the code, so a reviewer can
check the approach first.
If a change touches the BLE routing mechanism, the wire protocol, the security
model, or the platform limits, describe the design in the PR before the code, so
a reviewer can check the approach first. Not the other way around.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ WATCH_TARGET := arm64-apple-watchos10.0-simulator
SWIFT_PKGS := packages/host-core packages/protocol/swift apps/helper tools/simblectl
C_FILES := $(shell find packages -type f \( -name '*.c' -o -name '*.h' \) 2>/dev/null)

.PHONY: help bootstrap configure build app test test-portable fence docs lint format clean \
.PHONY: help bootstrap configure build app dylib test test-portable fence docs lint format clean \
mechanism-ios mechanism-watchos mechanism-peripheral-ios

help: ## Show targets
Expand Down Expand Up @@ -42,6 +42,10 @@ build: configure ## Build C targets and Swift packages
@if [ -d build-watchsim ]; then cmake --build build-watchsim -j; fi
@for p in $(SWIFT_PKGS); do echo "== swift build: $$p =="; ( cd $$p && xcrun swift build ) || exit 1; done

dylib: configure ## Build the interposer simulator slices (ios and watchos)
@if [ -d build-sim ]; then cmake --build build-sim -j; fi
@if [ -d build-watchsim ]; then cmake --build build-watchsim -j; fi

app: ## Build the menubar SimBLE.app bundle into dist/ (ad-hoc signed)
bash scripts/build-menubar-app.sh

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ SPDX-FileCopyrightText: 2026 Nirapod Labs
<img alt="C" src="https://img.shields.io/badge/C-A8B9CC?style=flat-square&logo=c&logoColor=white">
<img alt="Platforms: iOS and watchOS Simulators, macOS" src="https://img.shields.io/badge/Platforms-iOS_%C2%B7_watchOS_Sim_%C2%B7_macOS-lightgrey?style=flat-square&logo=apple&logoColor=white">
<a href="https://github.com/nirapod-labs/simble/releases"><img alt="Latest release" src="https://img.shields.io/github/v/release/nirapod-labs/simble?style=flat-square&label=release&color=2563eb"></a>
<a href="https://nirapod-labs.github.io/simble/"><img alt="Documentation" src="https://img.shields.io/badge/docs-architecture-2563eb?style=flat-square"></a>
</p>

# SimBLE
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0-beta
1.0.0
8 changes: 4 additions & 4 deletions docs/architecture/simble-architecture.qd
Original file line number Diff line number Diff line change
Expand Up @@ -327,10 +327,10 @@ $$$

CI runs the fence on every change. It asserts the static naming and wiring rules: that any scheme
carrying the variable is Debug-only, that no Xcode project links the dylib into a build, and that the
variable appears only in a reviewed allowlist. The fence also defines a binary bundle check, that
every interposer the helper carries is a simulator slice and fails closed on any device platform; the
release workflow runs it against the built `.app`, where it is a placeholder pending the full
slice-platform assertion.
variable appears only in a reviewed allowlist. The fence also runs a binary bundle check, that the
helper carries exactly the interposers it injects and each is a simulator slice, reading the Mach-O
build platform and failing closed on a missing slice or any device platform; the release workflow
runs it against the built `.app` before publishing.

.mermaid caption:{The fence as a decision: a load survives only the Simulator-and-debug path.}
flowchart TD
Expand Down
27 changes: 27 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2026 Nirapod Labs

pre-commit:
parallel: true
commands:
biome:
glob: "*.{js,ts,tsx,json,jsonc}"
run: pnpm exec biome check --write --no-errors-on-unmatched {staged_files}
stage_fixed: true

commit-msg:
commands:
commitlint:
run: pnpm exec commitlint --edit {1}

# Branch protection also lives on the remote, but the gate is enforced locally
# too. To bypass in a genuine emergency: git push --no-verify.
pre-push:
commands:
no-direct-main:
run: |
branch="$(git symbolic-ref --short HEAD 2>/dev/null || echo detached)"
if [ "$branch" = "main" ]; then
echo "Direct push to main is blocked. Branch, open a PR, let CI run."
exit 1
fi
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "simble",
"private": true,
"version": "1.0.0-beta",
"version": "1.0.0",
"description": "Real Bluetooth Low Energy for the iOS and watchOS Simulators.",
"author": "athexweb3",
"license": "Apache-2.0",
Expand Down
15 changes: 15 additions & 0 deletions scripts/build-menubar-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,21 @@ cat > "$APP/Contents/Info.plist" <<PLIST
</plist>
PLIST

# Bundle the interposers the helper injects, one per simulator platform. Each is a simulator-slice
# binary the fence keeps out of any shipped app; the helper carries them and arms a booted simulator
# with the slice matching its platform. Without these the installed helper finds no slice and arms
# nothing. Build, copy, and sign each into Resources before the bundle is sealed.
command -v cmake >/dev/null || { echo "cmake is required to build the interposers: brew install cmake"; exit 1; }
echo "building the interposers (ios and watchos sim slices)..."
( cd "$REPO" && make dylib ) || { echo "failed to build the interposers"; exit 1; }
for slice in build-sim/bin/simble-interpose.dylib build-watchsim/bin/simble-interpose-watchos.dylib; do
[ -f "$REPO/$slice" ] || { echo "missing interposer slice: $slice (is the simulator SDK installed?)"; exit 1; }
name="$(basename "$slice")"
cp "$REPO/$slice" "$APP/Contents/Resources/$name"
codesign -s "$SIGN_ID" --force --timestamp=none "$APP/Contents/Resources/$name" >/dev/null 2>&1 \
|| { echo "codesign failed for $name"; exit 1; }
done

# Ad-hoc sign the bundle so the Bluetooth grant attributes to a stable identity across runs.
# --deep covers any nested code; --force replaces an existing signature.
codesign --force --deep --sign "$SIGN_ID" "$APP" >/dev/null 2>&1 \
Expand Down
20 changes: 18 additions & 2 deletions scripts/fence-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,24 @@ fi
if [ "${1:-}" = "--helper" ]; then
BUNDLE="${2:-}"
[ -d "$BUNDLE" ] || { echo "usage: fence-check.sh --helper <path.app>" >&2; exit 2; }
echo "FENCE (helper): placeholder ok"
exit 0
RES="$BUNDLE/Contents/Resources"
# The helper must carry exactly the interposers it injects, and each must be a simulator slice:
# a missing slice means the helper arms nothing; a device slice would defeat the fence. Read the
# Mach-O build platform and fail closed on anything that is not a *SIMULATOR platform.
for name in "${DYLIB_NAME}.dylib" "${DYLIB_NAME}-watchos.dylib"; do
slice="$RES/$name"
if [ ! -f "$slice" ]; then
fail "helper bundle carries no $name; it would arm nothing"
continue
fi
plat="$(vtool -show-build "$slice" 2>/dev/null | awk '/platform/ {print $2; exit}')"
case "$plat" in
*SIMULATOR) ;;
*) fail "$name is not a simulator slice (platform: ${plat:-unknown})" ;;
esac
done
[ "$FAIL" -eq 0 ] && echo "FENCE (helper): ok ($(basename "$BUNDLE") carries simulator-only interposers)"
exit "$FAIL"
fi

git ls-files '*.xcscheme' | while read -r scheme; do
Expand Down
Loading
Loading