> ## Documentation Index
> Fetch the complete documentation index at: https://iso8583sim.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Networking

> iso8583sim can talk to real hosts over TCP, and can stand in for one.

Use it to test acquirer, switch or terminal software without a card network connection.

* **`iso8583sim serve`** runs a mock issuer host that answers by rules.
* **`iso8583sim send`** sends one message and shows the response.
* **`iso8583sim load`** sends many requests and reports throughput and latency.
* **`ISO8583Client`** and **`MockHost`** do the same from Python (asyncio).

Messages travel in a [wire format](../core/wire) (ASCII, BCD, EBCDIC...), each preceded by a length header and, optionally, a TPDU.

## Quick start

In one terminal, start a mock host:

```bash theme={null}
iso8583sim serve --port 8583
```

In another, generate a message and send it:

```bash theme={null}
iso8583sim generate --type auth -o msg.txt
iso8583sim send "$(cat msg.txt)" --port 8583
```

Or put it under load:

```bash theme={null}
iso8583sim load --port 8583 --count 10000 --concurrency 50
```

## Framing options

All three commands take the same options, and both ends must agree on them:

| Option | Values | Default |
| - | - | - |
| `--format` | `ascii-hex`, `ascii-binary`, `bcd`, `ebcdic` | `ascii-binary` |
| `--header` | `2b`, `4b` (binary length), `2a`, `4a` (ASCII digits) | `2b` |
| `--tpdu` | 10 hex digits, e.g. `6000010000` | none |

The length in the header covers everything after it, including the TPDU. On responses, the mock host swaps the TPDU's destination and source addresses.

## Mock host rules

Without a rules file, the mock host approves everything. A rules file lists rules in order; the first rule that matches a request decides the response.

```yaml theme={null}
# rules.yaml
rules:
  - match: {amount_gt: 100000}        # over 1,000.00
    respond: "51"                      # insufficient funds
  - match: {pan_prefix: ["4000", "5100"]}
    respond: "05"                      # do not honor
    delay_ms: 500                      # answer slowly
  - match: {pan_prefix: "4999"}
    action: drop                       # never answer: tests your timeout handling
  - match: {mti: "0800"}
    respond: "00"
  - respond: "00"                      # everything else is approved
```

```bash theme={null}
iso8583sim serve --port 8583 --rules rules.yaml --format bcd --tpdu 6000010000
```

Match conditions (all must hold):

| Key | Matches when |
| - | - |
| `mti` | The request MTI is one of these |
| `pan_prefix` | The PAN starts with one of these |
| `amount_gt`, `amount_lt` | Field 4 (minor units) is above / below this |
| `network` | The card network (detected from the PAN) is this |
| `fields` | These fields have exactly these values, e.g. `{"49": "978"}` |

Actions: `respond` (default, with `respond` as the field 39 code), `drop` (no answer) or `close` (close the connection). `delay_ms` delays any action.

Responses echo the request's matching fields (PAN, processing code, amount, times, STAN, IDs, currency, field 70), set field 39, and add an approval code in field 38 for approved authorizations and financial requests. Rules files can be JSON or YAML (YAML needs `pip install pyyaml`).

Press Ctrl+C (or send SIGTERM) to stop; the host prints a summary of what it received and how it answered.

## Python API

```python theme={null}
import asyncio

from iso8583sim.core.samples import sample_message
from iso8583sim.net import Framing, ISO8583Client, MockHost, Rule
from iso8583sim.wire import WireFormat


async def main():
    fmt = WireFormat.bcd()
    framing = Framing(header="2b", tpdu=bytes.fromhex("6000010000"))

    host = MockHost(rules=[Rule(amount_gt=100000, respond="51"), Rule()], wire_format=fmt, framing=framing)
    await host.start("127.0.0.1", 0)  # port 0 picks a free port

    async with ISO8583Client("127.0.0.1", host.port, wire_format=fmt, framing=framing, timeout=5) as client:
        response = await client.send(sample_message(amount_minor_units=250000))
        print(response.mti, response.fields[39])  # 0110 51

    await host.stop()


asyncio.run(main())
```

The client matches responses to requests by STAN (field 11), so many requests can be in flight on one connection and responses may arrive in any order. Every request needs a STAN, and two requests waiting at the same time can't share one.

| Situation | What happens |
| - | - |
| No response in time | `TimeoutError` |
| Connection closes while waiting | `ConnectionError`; the next `send` reconnects |
| A response arrives after its timeout | Logged and ignored |
| TLS | Pass `ssl=True` or an `ssl.SSLContext` to the client |

`run_load` runs a load test from Python and returns a `LoadReport` with counts, response codes and latency percentiles.

## From an AI assistant

The [MCP server](../mcp-server) has a `send_to_host` tool. To stop an assistant from being used to reach arbitrary machines, it only connects to hosts listed in the `ISO8583SIM_ALLOWED_HOSTS` environment variable of the MCP server. The default is this machine only (`127.0.0.1,localhost,::1`). Entries can be a host (any port) or `host:port`:

```json theme={null}
{
  "mcpServers": {
    "iso8583sim": {
      "command": "iso8583sim",
      "args": ["mcp"],
      "env": {"ISO8583SIM_ALLOWED_HOSTS": "127.0.0.1,uat-switch.internal:9000"}
    }
  }
}
```

## Performance

On an Apple M4 Pro, the client and mock host in one process handle about 12,000 round trips per second; with the host in a separate process, a load run reached about 20,000. See `benchmarks/BASELINE.md` and `benchmarks/bench_network.py`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.