# SmsForwarder Rule Matching Algorithm and Condition Evaluation System Explained

> Explore SmsForwarder's rule matching algorithm and condition evaluation. Understand how it parses conditions and validates triggers for efficient SMS forwarding.

- Repository: [pppscn/SmsForwarder](https://github.com/pppscn/SmsForwarder)
- Tags: deep-dive
- Published: 2026-06-22

---

**SmsForwarder uses a hierarchical tree-based rule matching system that parses textual conditions into `RuleLine` objects and evaluates them against message attributes, combined with a separate `ConditionUtils` subsystem that validates task triggers like cron schedules, network states, and battery levels before execution.**

SmsForwarder is an open-source SMS forwarding application for Android that filters incoming messages and triggers actions based on user-defined criteria. The system relies on two distinct but complementary subsystems: the **rule matching engine** that evaluates message content against filtering criteria, and the **condition evaluation system** that manages task execution logic. This analysis examines the actual Kotlin implementation found in the `pppscn/SmsForwarder` repository to explain how these components process data and make routing decisions.

## How Rule Matching Works in SmsForwarder

The rule matching subsystem determines whether an incoming message satisfies the filtering criteria defined by users. It handles both simple single-field comparisons and complex multi-line logical expressions.

### Rule Definition and Data Structure

Rules are stored as `Rule` entities in [`app/src/main/kotlin/cn/ppps/forwarder/database/entity/Rule.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/database/entity/Rule.kt). Each rule contains three key fields that define the matching logic:

- `filed` – Specifies which message attribute to examine (e.g., phone number, content).
- `check` – Defines the comparison operator (e.g., "contain", "equal", "regex").
- `value` – Contains the reference value or complex rule text.

For **simple rules**, these fields directly describe a condition such as "phone number contains 10086". For **complex rules**, the `filed` value equals `FILED_MULTI_MATCH`, and the actual rule logic is stored as structured text in the `value` field.

### Parsing Multi-Line Rules into a Tree Structure

When `Rule.checkMsg()` encounters a `FILED_MULTI_MATCH` field at line 236 of [`Rule.kt`](https://github.com/pppscn/SmsForwarder/blob/main/Rule.kt), it delegates parsing to `RuleLineUtils.checkRuleLines` in [`app/src/main/kotlin/cn/ppps/forwarder/utils/RuleLineUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/RuleLineUtils.kt). This static utility parses the raw rule text into a **hierarchical tree of `RuleLine` objects**.

The parser splits each line into four tokens:

1. **Conjunction** – Logical operator (`并且`/`或者` mapping to `CONJUNCTION_AND` or `CONJUNCTION_OR`).
2. **Sure** – Confirmation flag (`是`/`否`).
3. **Field** – Message attribute to check (phone, content, UID).
4. **Check operator** – Comparison type (`is`, `contain`, `not contain`, `start with`, `end with`, `regex`).

**Indentation determines hierarchy** in the tree structure:

- Same indentation creates a sibling node.
- Deeper indentation creates a child node.
- Shallower indentation climbs back to a parent's sibling.

The parser throws an exception if any line does not contain exactly four tokens, ensuring strict validation of rule syntax.

### Tree Traversal and Logical Evaluation

The `RuleLineUtils.checkRuleTree` function traverses the parsed tree recursively to evaluate the message. The algorithm follows this pattern:

```kotlin
// Conceptual flow from RuleLineUtils.kt
result = current.checkMsg(msg)
if (current.child != null) {
    result = combine(result, child.checkMsg(msg), child.conjunction)
}
if (current.next != null) {
    result = combine(result, next.checkMsg(msg), next.conjunction)
}

```

Each `RuleLine.checkMsg` call extracts the appropriate attribute from the incoming `MsgInfo` object (e.g., `msg.from`, `msg.content`, `msg.uid`) and passes it to `RuleLine.checkValue`.

**Value expressions** support composite logic through `&&` and `||` operators. The method splits values into **OR-groups** (delimited by `||`) and then into **AND-groups** (delimited by `&&`), evaluating each atomic condition with `evaluateCondition`. `CONJUNCTION_AND` behaves like logical `&&`, while `CONJUNCTION_OR` behaves like `||`. The final boolean result determines whether the entire rule matches the message.

## Task Condition Evaluation System

While rules filter message content, the condition evaluation system determines whether a task should execute based on device state and timing constraints. The core logic resides in `ConditionUtils` at [`app/src/main/kotlin/cn/ppps/forwarder/utils/task/ConditionUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/app/src/main/kotlin/cn/ppps/forwarder/utils/task/ConditionUtils.kt).

### Condition List Architecture

Each task stores a list of `TaskSetting` objects representing its conditions. The **first element** in the list serves as the trigger (e.g., `TASK_CONDITION_SIM` for SIM readiness). Subsequent elements act as **filters** that must all evaluate to `true` for the task to run.

The main entry point `ConditionUtils.checkCondition(taskId, conditionList, beginIndex = 1)` iterates over filters starting at index 1 (after the trigger). If any filter returns `false`, the function immediately returns `false`. If the list is empty or `beginIndex` exceeds the list size, it returns `true`.

### Supported Condition Types

A `when` block in `ConditionUtils` dispatches to type-specific handlers:

- **Cron** – Parses CRON expressions using `CronExpression`, aligns the current time to whole seconds, and compares the next valid time against the current timestamp.
- **Location** – Supports distance and address checks (enter/leave) by comparing `TaskUtils.locationInfo` values against previous states.
- **Network** – Validates Wi-Fi SSID or mobile data SIM slot configurations.
- **SIM** – Matches the current SIM state against `TaskUtils.simState`.
- **Battery / Charge** – Uses `TaskUtils.batteryLevel`, `batteryStatus`, and `batteryPlugged` to apply `BatterySetting` or `ChargeSetting` constraints.
- **Lock Screen** – Compares the current lock-screen action against `TaskUtils.lockScreenAction`.
- **Bluetooth** – Handles state changes, device connections, and discovery results.
- **SMS / Call / App** – Deserializes stored JSON into `Rule` objects and tests incoming messages against the rule criteria.

### Execution Flow and Delay Handling

When the trigger involves SIM readiness or network changes, the system introduces a **5-second delay** (`DELAY_TIME_AFTER_SIM_READY`) to allow the system to stabilize before evaluating remaining conditions. This prevents premature execution during transient state changes.

Workers such as [`SendWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/SendWorker.kt), [`NetworkWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/NetworkWorker.kt), and [`BluetoothWorker.kt`](https://github.com/pppscn/SmsForwarder/blob/main/BluetoothWorker.kt) invoke `ConditionUtils.checkCondition(task.id, conditionList)` to validate execution requirements before proceeding with message forwarding.

## Implementation Examples

### Matching a Simple Phone Number Rule

```kotlin
import cn.ppps.forwarder.database.entity.Rule
import cn.ppps.forwarder.database.entity.MsgInfo
import java.util.Date

// Define a simple rule
val rule = Rule(
    id = 0L,
    type = "sms",
    filed = Rule.FILED_PHONE_NUM,
    check = Rule.CHECK_CONTAIN,
    value = "10086",
    senderId = 0L,
    senderList = emptyList(),
    senderLogic = Rule.SENDER_LOGIC_UNTIL_SUCCESS
)

// Incoming message
val msg = MsgInfo(
    type = "sms",
    from = "15810086186",
    content = "Hello",
    time = Date(),
    uid = "12345"
)

// Evaluate
val matches = rule.checkMsg(msg)  // Returns true

```

### Evaluating Complex Multi-Line Rules

```kotlin
import cn.ppps.forwarder.database.entity.Rule
import cn.ppps.forwarder.utils.RuleLineUtils

val multiRuleText = """
    并且 是 手机号 相等 10086
    或者 是 内容 包含 test
"""

val rule = Rule(
    id = 0,
    type = "sms",
    filed = Rule.FILED_MULTI_MATCH,
    check = Rule.CHECK_IS,
    value = multiRuleText
)

// RuleLineUtils.checkRuleLines parses the text and builds the tree
val matches = RuleLineUtils.checkRuleLines(msg, rule.value)

```

### Validating Cron-Based Task Conditions

```kotlin
import cn.ppps.forwarder.utils.task.ConditionUtils
import cn.ppps.forwarder.database.entity.TaskSetting
import com.google.gson.Gson

// Cron expression: every 5 minutes
val cronSetting = CronSetting(expression = "0 0/5 * * * ?")
val condition = TaskSetting(
    type = TaskSetting.TASK_CONDITION_CRON,
    setting = Gson().toJson(cronSetting)
)

val canRun = ConditionUtils.checkCondition(
    taskId = 42L,
    conditionList = mutableListOf(condition)
)

```

## Summary

- **Rule Matching** operates through `RuleLineUtils.checkRuleLines` which parses textual rule definitions into hierarchical trees of `RuleLine` objects, supporting logical operators (`&&`, `||`) and comparison operators (`contain`, `regex`, etc.).
- **Condition Evaluation** uses `ConditionUtils.checkCondition` to validate task execution requirements across multiple dimension types including cron schedules, location, network, battery, and SIM state.
- **Indentation-based parsing** in `RuleLineUtils` creates parent-child relationships between rule conditions, enabling complex nested logic without requiring parentheses.
- **Source Files**: Core logic resides in [`Rule.kt`](https://github.com/pppscn/SmsForwarder/blob/main/Rule.kt) (entity definition), [`RuleLine.kt`](https://github.com/pppscn/SmsForwarder/blob/main/RuleLine.kt) (node representation), [`RuleLineUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/RuleLineUtils.kt) (tree parsing), and [`ConditionUtils.kt`](https://github.com/pppscn/SmsForwarder/blob/main/ConditionUtils.kt) (task validation).

## Frequently Asked Questions

### How does SmsForwarder handle nested logical operators in rule definitions?

SmsForwarder parses multi-line rule text into a tree structure where **indentation determines nesting level**, not parentheses. Each line specifies a conjunction (`并且`/`或者` for AND/OR) and the tree traversal in `RuleLineUtils.checkRuleTree` evaluates children recursively. Within individual values, the system supports `&&` (AND) and `||` (OR) operators by splitting values into OR-groups first, then AND-groups, evaluating each atomic condition separately.

### What is the difference between a rule and a condition in SmsForwarder?

A **rule** filters message content (SMS text, phone numbers, app data) and resides in the `Rule` entity class, evaluated through `Rule.checkMsg()`. A **condition** controls task execution timing and device state requirements (cron schedules, battery level, network connectivity) and is evaluated through `ConditionUtils.checkCondition()`. Tasks use conditions to decide *when* to run, then apply rules to decide *which* messages to forward.

### Which message attributes can rules evaluate against?

According to `RuleLine.checkMsg` in [`RuleLine.kt`](https://github.com/pppscn/SmsForwarder/blob/main/RuleLine.kt), rules can evaluate the `from` field (phone number), `content` field (message body), and `uid` field (unique identifier). The `filed` token in the rule definition selects which attribute to examine, supporting operators like `contain`, `start with`, `end with`, `regex`, and exact matching.

### Why does SmsForwarder delay condition evaluation after SIM or network changes?

The system implements a **5-second delay** (`DELAY_TIME_AFTER_SIM_READY`) when the trigger condition involves SIM readiness or network state changes. This delay, handled in `ConditionUtils`, allows the Android system to stabilize its internal state before evaluating subsequent conditions, preventing race conditions where the device might report transient states that don't reflect stable connectivity.