{"id":13544910,"url":"https://github.com/moov-io/iso8583-connection","last_synced_at":"2025-08-12T22:30:53.848Z","repository":{"id":37055802,"uuid":"475134846","full_name":"moov-io/iso8583-connection","owner":"moov-io","description":":satellite: Go-powered ISO8583 connection handler offering advanced binary framing, message interleaving, and a robust connection pool for load distribution and seamless reconnections.","archived":false,"fork":false,"pushed_at":"2025-08-07T01:49:39.000Z","size":406,"stargazers_count":88,"open_issues_count":6,"forks_count":27,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-08-07T03:28:40.524Z","etag":null,"topics":["card-payment","hacktoberfest","iso8583"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moov-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-03-28T18:43:45.000Z","updated_at":"2025-08-07T01:49:42.000Z","dependencies_parsed_at":"2023-09-26T00:24:29.726Z","dependency_job_id":"c4d579e8-e273-4e37-aa65-45bdd9683c71","html_url":"https://github.com/moov-io/iso8583-connection","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"purl":"pkg:github/moov-io/iso8583-connection","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moov-io%2Fiso8583-connection","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moov-io%2Fiso8583-connection/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moov-io%2Fiso8583-connection/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moov-io%2Fiso8583-connection/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moov-io","download_url":"https://codeload.github.com/moov-io/iso8583-connection/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moov-io%2Fiso8583-connection/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269194790,"owners_count":24376485,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-07T02:00:09.698Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["card-payment","hacktoberfest","iso8583"],"created_at":"2024-08-01T11:00:54.878Z","updated_at":"2025-08-12T22:30:53.820Z","avatar_url":"https://github.com/moov-io.png","language":"Go","funding_links":[],"categories":["Banking Infrastructure"],"sub_categories":[],"readme":"[![Moov Banner Logo](https://user-images.githubusercontent.com/20115216/104214617-885b3c80-53ec-11eb-8ce0-9fc745fb5bfc.png)](https://github.com/moov-io)\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://moov-io.slack.com/archives/C014UT7C3ST\"\u003eCommunity channel #iso8583\u003c/a\u003e\n  ·\n  \u003ca href=\"https://moov.io/blog/\"\u003eBlog\u003c/a\u003e\n  \u003cbr\u003e\n  \u003cbr\u003e\n\u003c/p\u003e\n\n# moov-io/iso8583-connection\n\nMoov's mission is to give developers an easy way to create and integrate bank processing into their own software products. Our open source projects are each focused on solving a single responsibility in financial services and designed around performance, scalability, and ease of use.\n\nmoov-io/iso8583-connection is a package helping with sending, receiving and matching ISO 8583 messages between client and server. It can be used both for acquiring and issuing services.\n\n## Project status\n\nISO 8583 Connection package is used in production environments. Please star the project if you are interested in its progress. Please let us know if you encounter any bugs/unclear documentation or have feature suggestions by [opening up an issue](https://github.com/moov-io/iso8583-connection/issues/new) or pull request. Thanks!\n\n## Configuration\n\nFollowing options are supported:\n\n* SendTimeout - sets the timeout for a Send operation\n* IdleTime - sets the period of inactivity (no messages sent) after which a ping message will be sent to the server\n* ReadTimeout - sets the period of time to wait between reads before calling ReadTimeoutHandler \n* PingHandler - called when no message was sent during idle time. It should be safe for concurrent use.\n* InboundMessageHandler - called when a message from the server is received or no matching request for the message was found. InboundMessageHandler must be safe to be called concurrently.\n* ReadTimeoutHandler - called when no messages have been received during specified ReadTimeout wait time. It should be safe for concurrent use.\n* ConnectionClosedHandler - is called when connection is closed by us, by server or there were errors during network read/write that led to connection closure\n* ErrorHandler - is called with the error when connection fails to perform some operation. In some cases instance of a `SafeError` will be passed to prevent data leaks ([details](https://github.com/moov-io/iso8583/pull/185))\n\nIf you want to override default options, you can do this when creating instance of a client or setting it separately using `SetOptions(options...)` method.\n\n```go\npingHandler := func(c *connection.Connection) {\n\t// send ping/heartbeat message like this\n\tping := iso8583.NewMessage(brandSpec)\n\t// set other fields\n\tresponse, err := c.Send(ping)\n\t// handle error\n}\n\ninboundMessageHandler := func(c *connection.Connection, message *iso8583.Message) {\n\t// log received message or send a reply like this\n\tmti, err := message.GetMTI()\n\t// handle err\n\n\t// implement logic for network management messages\n\tswitch mti {\n\tcase \"0800\":\n\t\techo := iso8583.NewMessage(brandSpec)\n\t\techo.MTI(\"0810\")\n\t\t// set other fields\n\t\terr := c.Reply(echo)\n\t\t// handle error\n\tdefault:\n\t\t// log unrecognized message\n\t}\n}\n\nc, err := connection.New(\"127.0.0.1:9999\", brandSpec, readMessageLength, writeMessageLength,\n\tconnection.SendTimeout(100*time.Millisecond),\n\tconnection.IdleTime(50*time.Millisecond),\n\tconnection.PingHandler(pingHandler),\n\tconnection.InboundMessageHandler(inboundMessageHandler),\n)\n\n// work with the client\n```\n\n### Handler invocation during the connection life cycle\n\nThis section explains the various stages at which different handler functions are triggered throughout the lifecycle of the `Connection`.\n\n#### On connection establishment:\n\n- **`OnConnect`** or **`OnConnectCtx`**: This handler is invoked immediately after the TCP connection is made. It can be utilized for operations that should be performed before the connection is officially considered established (e.g., sending `SignOn` message and receiving its response). **NOTE** If both `OnConnect` and `OnConnectCtx` are defined, `OnConnectCtx` will be used.\n\n- **`ConnectionEstablishedHandler (async)`**: This asynchronous handler is triggered when the connection is logically considered established.\n\n#### On error occurrence:\n\n- **`ErrorHandler (async)`**: This asynchronous handler is executed when an error occurs during message reading or writing.\n\n#### On message receipt:\n\n- **`InboundMessageHandler (async)`**: This asynchronous handler is triggered when an incoming message is received, or a received message does not have a matching request (this can happen when we return an error for the `Send` method after a timeout and then, subsequently, receive a response, aka late response).\n\n#### On read timeout:\n\n- **`ReadTimeoutHandler (async)`**: This asynchronous handler is activated when no messages are received within the set `ReadTimeout` period.\n\n#### On idle time:\n\n- **`PingHandler (async)`**: This asynchronous handler is invoked when no messages are sent within the `IdleTime`.\n\n#### On connection closure:\n\n- **`ConnectionClosedHandlers (async)`**: These asynchronous handlers are invoked after connection is closed by us, by the server or due to the network errors\n\n- **`OnClose`** or **`OnCloseCtx`**: This handler is activated before the connection is closed when we manually close the connection. **NOTE** If both `OnClose` and `OnCloseCtx` are defined, `OnCloseCtx` will be used.\n\n\n### (m)TLS connection\n\nConfigure to use TLS during connect:\n\n```go\nc, err := connection.New(\"127.0.0.1:443\", testSpec, readMessageLength, writeMessageLength,\n\t// if server requires client certificate (mTLS)\n\tconnection.ClientCert(\"./testdata/client.crt\", \"./testdata/client.key\"),\n\t// if you use a self signed certificate, provide root certificate\n\tconnection.RootCAs(\"./testdata/ca.crt\"),\n)\n// handle error\n```\n\n## Usage\n\n```go\n// see configuration options for more details\nc, err := connection.New(\"127.0.0.1:9999\", brandSpec, readMessageLength, writeMessageLength,\n\tconnection.SendTimeout(100*time.Millisecond),\n\tconnection.IdleTime(50*time.Millisecond),\n\tconnection.PingHandler(pingHandler),\n\tconnection.UnmatchedMessageHandler(unmatchedMessageHandler),\n\tconnection.ConnectionClosedHandler(connectionClosedHandler),\n)\nerr := c.Connect()\nif err != nil {\n\t// handle error\n}\ndefer c.Close()\n\n// create iso8583 message\nmessage := iso8583.NewMessage(brandSpec)\nmessage.MTI(\"0800\")\n// ...\n\n// send message to the server\nresponse, err := connection.Send(message)\nif err != nil {\n\t// handle error\n}\n\n// work with the response\nmti, err := response.GetMTI()\nif err != nil {\n\t// handle error\n}\n\nif mti != \"0810\" {\n\t// handle error\n}\n```\n\n## Connection `Pool`\n\nSometimes you want to establish multiple connections and re-create\nthem when such connections are closed due to a network errors. Connection `Pool`\nis really helpful for such use cases.\n\nTo use Pool, first, you need to create a factory function that knows how to\ncreate connections and a list of addresses you want to establish connections\nwith. You can establish connections with different or the same addresses.\n\n```go\n// Factory method that will build connection\nfactory := func(addr string) (*connection.Connection, error) {\n\tc, err := connection.New(\n\t\taddr,\n\t\ttestSpec,\n\t\treadMessageLength,\n\t\twriteMessageLength,\n\t\t// set shot connect timeout so we can test re-connects\n\t\tconnection.ConnectTimeout(500*time.Millisecond),\n\t\tconnection.OnConnect(func(c *connection.Connection) {\n\t\t\tc.Set(\"status\", \"online\")\n\t\t}),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"building iso8583 connection: %w\", err)\n\t}\n\n\treturn c, nil\n}\n```\n\nif there is a need to apply address specific configurations like TLS, you can create a map or function that will return all needed options for the address:\n\n```go\nfunc getAddrOpts(addr string) []Option {\n\tswitch addr {\n\tcase \"127.0.0.1\":\n\t\treturn []Option{\n\t\t\tconnection.ClientCert(certA, keyA),\n\t\t}\n\tcase \"127.0.0.2\":\n\t\treturn []Option{\n\t\t\tconnection.ClientCert(certB, keyB),\n\t\t}\n\t}\n}\n\nfactory := func(addr string) (*connection.Connection, error) {\n\tc, err := connection.New(\n\t\taddr,\n\t\ttestSpec,\n\t\treadMessageLength,\n\t\twriteMessageLength,\n\t\tconnection.ConnectTimeout(500*time.Millisecond),\n\t\tgetAddrOpts(addr)...,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"building iso8583 connection: %w\", err)\n\t}\n\n\treturn c, nil\n}\n```\n\nNow you can create pool and establish all connections:\n\n```go\n// let's say we want Get() to return only online connections\nfilterOnlineConnections := func(conn *connection.Connection) bool {\n\treturn conn.Get(\"status\") == \"online\"\n}\n\npool, err := connection.NewPool(\n\tfactory,\n\taddrs,\n\tconnection.PoolConnectionsFilter(filterOnlineConnections),\n)\n// handle error\n\nerr = pool.Connect()\n// handle error\n```\n\nWhen pool is connected, you can get connection from the pool to send message to:\n\n```go\n// get connection (only \"online\") from the pool\nconn, err := pool.Get()\n// handle err\n\n// create iso8583 message\nmsg := iso8583.NewMessage(yourSpec)\n// ...\n\nreply, err := conn.Send(msg)\n// handle error\n```\n\nBecause `Connection` is safe to be used concurrently, you don't return\nconnection back to the pool. But don't close the connection directly as the\npool will remove it from the pool of connections only when connection is closed\nby the server. It does it using `ConnectionClosedHandler`.\n\n### Configuration of the Pool\n\nFollowing options are supported:\n\n* `ReconnectWait` sets the time to wait after first re-connect attempt\n* `MaxReconnectWait` specifies the maximum duration to wait between reconnection attempts, serving as the upper bound for exponential backoff; if set to zero, there's no exponential backoff and ReconnectWait is used for each retry.\n* `ErrorHandler` is called in a goroutine with the errors that can't be returned to the caller (from other goroutines)\n* `MinConnections` is the number of connections required to be established when we connect the pool\n* `ConnectionsFilter` is a function to filter connections in the pool for `Get`, `IsDegraded` or `IsUp` methods\n\n## Context\nYou can provide context to the Connect and Close functions in addition to defining `OnConnectCtx` and `OnCloseCtx` in the connection options. This will allow you to pass along telemetry or any other information on contexts through from the Connect/Close calls to your handler functions:\n\n```go\nc, err := connection.New(\"127.0.0.1:9999\", brandSpec, readMessageLength, writeMessageLength,\n\tconnection.SendTimeout(100*time.Millisecond),\n\tconnection.IdleTime(50*time.Millisecond),\n    connect.OnConnectCtx(func(ctx context.Context, c *connection.Connection){\n        return signOnFunc(ctx, c)\n    }),\n    connect.OnCloseCtx(func(ctx context.Context, c *connection.Connection){\n        return signOffFunc(ctx, c)\n    }),\n)\n\nctx := context.Background()\nc.ConnectCtx(ctx)\n\n...\n\nc.CloseCtx(ctx)\n\n```\n\n## Benchmark\n\nTo benchmark the connection, we created a test server that sends a response to\neach request. Therefore, the benchmark measures the time it takes to send a\nmessage and receive a response by both the client and the server. If you are\nlooking to measure client performance only, you should either run the test\nserver on a separate machine, or, with some approximation, you can multiply the\nresults by 2.\n\nFor the connection benchmark, we pack/unpack an ISO 8583 message with only 2\nfields: `MTI` and `STAN`.\n\nWe have two types of benchmarks: *BenchmarkParallel* and *BenchmarkProcess*.\n\n*BenchmarkParallel* uses `b.N` goroutines to send (and receive) messages to the\nserver. You can set the number of goroutines using the `-cpu` flag. Please note\nthat the `-cpu` flag also sets `GOMAXPROCS`.\n\nFor example, to run the benchmark with 6 goroutines/CPUs/cores, use the\nfollowing command:\n\n```\ngo test -bench=BenchmarkParallel -cpu=6\n```\n\nBe aware that results may vary depending on the number of actual CPUs, cores, throttling, and system load.\n\nHere is the result on MacBook Pro:\n\n```\n➜ go test -bench=BenchmarkParallel -cpu 6\ngoos: darwin\ngoarch: amd64\npkg: github.com/moov-io/iso8583-connection\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\nBenchmarkParallel-6        63703             18849 ns/op\nPASS\nok      github.com/moov-io/iso8583-connection   26.079s\n```\n\nIt shows that 53K messages were sent and received by both client and server in 1sec.\n\n*BenchmarkProcessNNN*, where NNN is the number of messages to send, is another type of benchmark. In\nthis benchmark, the we send and receive messages to the server concurrently by running NNN goroutines.\n\nTo run such benchmarks, use:\n\n```\ngo test -bench=BenchmarkProcess\n```\n\nHere are the latest results on MacBook Pro:\n\n```\n➜ go test -bench=BenchmarkProcess -cpu 6\ngoos: darwin\ngoarch: amd64\npkg: github.com/moov-io/iso8583-connection\ncpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz\nBenchmarkProcess100-6                732           1579450 ns/op\nBenchmarkProcess1000-6                75          15220504 ns/op\nBenchmarkProcess10000-6                7         149483539 ns/op\nBenchmarkProcess100000-6               1        1681237716 ns/op\nPASS\nok      github.com/moov-io/iso8583-connection   29.967s\n```\n\nIt shows that:\n* The time taken scales approximately linearly with the number of messages processed.\n* 1.681 seconds to send/receive 100,000 messages by both client and server.\n* 149.48 milliseconds to send/receive 10,000 messages by both client and server.\n* 15.22 milliseconds to send/receive 1,000 messages by both client and server.\n* 1.579 milliseconds to send/receive 100 messages by both client and server.\n\n\n## License\n\nApache License 2.0 - See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoov-io%2Fiso8583-connection","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoov-io%2Fiso8583-connection","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoov-io%2Fiso8583-connection/lists"}