├── .gitignore ├── githooks ├── post-commit └── update_commit_id.sh ├── docs └── images │ ├── home.png │ ├── build_send.gif │ ├── def_server.png │ ├── home_rel2020.04.png │ ├── home_rel2020.04_01.png │ ├── home_rel2020.04_02.png │ ├── ReactApp_Screenshot.png │ ├── WikiExample_StartServer.png │ ├── WikiExample_LoadMessage1.png │ ├── WikiExample_LoadMessage2.png │ ├── WikiExample_StartServer2.png │ └── WikiExample_ServerResponse.png ├── cmd ├── isosim │ ├── version.go │ ├── run.bat │ └── isosim.go └── isoserver │ ├── run.bat │ └── isoserver.go ├── internal ├── db │ ├── isosim.bdb │ ├── db_test.go │ ├── db.go │ └── data_set_manager.go ├── iso │ ├── iso.go │ └── server │ │ ├── iso_def.go │ │ ├── response_builder.go │ │ ├── iso_server_test.go │ │ ├── iso_msg_process.go │ │ └── iso_server.go └── services │ ├── websim │ ├── http_urls.go │ ├── endpoints.go │ ├── http_transport.go │ └── http_transport_test.go │ ├── data │ ├── json_field_rep_test.go │ ├── server_definition.go │ └── json_field_rep.go │ ├── crypto │ ├── service.go │ ├── http_transport.go │ ├── http_transport_test.go │ └── endpoint.go │ ├── handlers │ ├── mg_hist_handler.go │ ├── misc_handler.go │ └── isoserver_handlers.go │ └── service_registration.go ├── test └── testdata │ ├── specs │ ├── specs.yaml │ ├── isoSpecs.spec │ └── sample_spec.yaml │ ├── appdata │ ├── 1 │ │ ├── 1 │ │ │ ├── Test_F2_000_Approved │ │ │ ├── TC_Declined │ │ │ ├── TC_000_Approved │ │ │ └── TC_Referral │ │ └── IsoTestSpec_Server_01.srvdef.json │ ├── 2 │ │ ├── 1 │ │ │ ├── TC_Approval │ │ │ ├── TC_Reversal │ │ │ ├── TC_ActionCode_100 │ │ │ └── TC_ActionCode_200 │ │ ├── IsoMiniSpec_Server_01.srvdef.json │ │ └── IsoMiniSpec_Server_WithReversal.srvdef.json │ ├── 3 │ │ ├── 1 │ │ │ ├── TC_Amt100_ActionCode_100 │ │ │ ├── TC_Amt91_ActionCode_000 │ │ │ ├── TC_Amount_1090 │ │ │ ├── TC_Amount_1091 │ │ │ ├── TC_Amount_1090_2 │ │ │ ├── TC_Amount_1091_response_ref.json │ │ │ ├── TC_Amount_1090_2_response_ref.json │ │ │ └── TC_Amount_1090_response_ref.json │ │ ├── 2 │ │ │ └── TC_Reversal │ │ ├── 3 │ │ │ ├── save_test_01 │ │ │ ├── TC_ASCII_Bitmap_Declined │ │ │ └── TC_ASCII_Bitmap_Approved │ │ ├── 4 │ │ │ ├── TC_EBCDIC_Bitmap_Approved │ │ │ └── TC_EBCDIC_Bitmap_Declined │ │ ├── 5 │ │ │ └── Test_PaddingSupport_01 │ │ ├── IsoTestSpec_Server_05.srvdef.json │ │ ├── IsoTestSpec_Server_06.srvdef.json │ │ ├── Iso8583TestSpec_Server_01.srvdef.json │ │ ├── Iso8583TestSpec_Server_02.srvdef.json │ │ ├── Iso8583TestSpec_Server_03.srvdef.json │ │ └── Iso8583TestSpec_Server_04.srvdef.json │ ├── 4 │ │ ├── 1 │ │ │ ├── TC_Amount99 │ │ │ └── TC_Amount99_response_ref.json │ │ └── 2 │ │ │ ├── TC_Reversal │ │ │ └── TC_Reversal_response_ref.json │ └── isosim.bdb │ └── certs │ ├── cert.pem │ └── key.pem ├── web ├── react-fe │ └── build │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── static │ │ ├── media │ │ │ ├── Lato-Regular.2d36b1a9.ttf │ │ │ ├── PTSerif-Regular.5f7303c0.ttf │ │ │ ├── roboto-latin-100.5cb7edfc.woff │ │ │ ├── roboto-latin-100.7370c367.woff2 │ │ │ ├── roboto-latin-300.b00849e0.woff │ │ │ ├── roboto-latin-300.ef7c6637.woff2 │ │ │ ├── roboto-latin-400.479970ff.woff2 │ │ │ ├── roboto-latin-400.60fa3c06.woff │ │ │ ├── roboto-latin-500.020c97dc.woff2 │ │ │ ├── roboto-latin-500.87284894.woff │ │ │ ├── roboto-latin-700.2735a3a6.woff2 │ │ │ ├── roboto-latin-700.adcde98f.woff │ │ │ ├── roboto-latin-900.9b3766ef.woff2 │ │ │ ├── roboto-latin-900.bb1e4dc6.woff │ │ │ ├── ShadowsIntoLight-Regular.47c22e0a.ttf │ │ │ ├── roboto-latin-100italic.f8b1df51.woff2 │ │ │ ├── roboto-latin-100italic.f9e8e590.woff │ │ │ ├── roboto-latin-300italic.14286f3b.woff2 │ │ │ ├── roboto-latin-300italic.4df32891.woff │ │ │ ├── roboto-latin-400italic.51521a2a.woff2 │ │ │ ├── roboto-latin-400italic.fe65b833.woff │ │ │ ├── roboto-latin-500italic.288ad9c6.woff │ │ │ ├── roboto-latin-500italic.db4a2a23.woff2 │ │ │ ├── roboto-latin-700italic.81f57861.woff │ │ │ ├── roboto-latin-700italic.da0e7178.woff2 │ │ │ ├── roboto-latin-900italic.28f91510.woff │ │ │ └── roboto-latin-900italic.ebf6d164.woff2 │ │ ├── css │ │ │ ├── main.bbd4fcb0.chunk.css │ │ │ └── main.bbd4fcb0.chunk.css.map │ │ └── js │ │ │ ├── runtime-main.d1dafa31.js │ │ │ ├── 2.5683fb71.chunk.js.LICENSE.txt │ │ │ └── runtime-main.d1dafa31.js.map │ │ ├── manifest.json │ │ ├── service-worker.js │ │ ├── asset-manifest.json │ │ ├── index.html │ │ └── precache-manifest.c31fdb430857a50a4adec070266b497b.js ├── bootstrap-dialog.min.css ├── isosim_styles.css └── isosim_common.js ├── go.mod ├── .github └── workflows │ └── build_isosim.yml ├── Dockerfile ├── api └── swagger.yaml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | coverage.out -------------------------------------------------------------------------------- /githooks/post-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | cd githooks 4 | ./update_commit_id.sh 5 | -------------------------------------------------------------------------------- /docs/images/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/home.png -------------------------------------------------------------------------------- /cmd/isosim/version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | var version = "0.12.4" 4 | var build = "9a76b5ed" 5 | -------------------------------------------------------------------------------- /internal/db/isosim.bdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/internal/db/isosim.bdb -------------------------------------------------------------------------------- /test/testdata/specs/specs.yaml: -------------------------------------------------------------------------------- 1 | - isoSpecs.spec 2 | - iso_specs.yaml 3 | - sample_spec.yaml 4 | -------------------------------------------------------------------------------- /docs/images/build_send.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/build_send.gif -------------------------------------------------------------------------------- /docs/images/def_server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/def_server.png -------------------------------------------------------------------------------- /web/react-fe/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /docs/images/home_rel2020.04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/home_rel2020.04.png -------------------------------------------------------------------------------- /web/react-fe/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/favicon.ico -------------------------------------------------------------------------------- /web/react-fe/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/logo192.png -------------------------------------------------------------------------------- /web/react-fe/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/logo512.png -------------------------------------------------------------------------------- /docs/images/home_rel2020.04_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/home_rel2020.04_01.png -------------------------------------------------------------------------------- /docs/images/home_rel2020.04_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/home_rel2020.04_02.png -------------------------------------------------------------------------------- /test/testdata/appdata/isosim.bdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/test/testdata/appdata/isosim.bdb -------------------------------------------------------------------------------- /docs/images/ReactApp_Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/ReactApp_Screenshot.png -------------------------------------------------------------------------------- /docs/images/WikiExample_StartServer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/WikiExample_StartServer.png -------------------------------------------------------------------------------- /docs/images/WikiExample_LoadMessage1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/WikiExample_LoadMessage1.png -------------------------------------------------------------------------------- /docs/images/WikiExample_LoadMessage2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/WikiExample_LoadMessage2.png -------------------------------------------------------------------------------- /docs/images/WikiExample_StartServer2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/WikiExample_StartServer2.png -------------------------------------------------------------------------------- /docs/images/WikiExample_ServerResponse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/docs/images/WikiExample_ServerResponse.png -------------------------------------------------------------------------------- /internal/iso/iso.go: -------------------------------------------------------------------------------- 1 | package iso 2 | 3 | // HTMLDir will point to the directory containing the static assets (HTML/JS/CSS etc) 4 | var HTMLDir string 5 | -------------------------------------------------------------------------------- /web/react-fe/build/static/media/Lato-Regular.2d36b1a9.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/Lato-Regular.2d36b1a9.ttf -------------------------------------------------------------------------------- /web/react-fe/build/static/media/PTSerif-Regular.5f7303c0.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/PTSerif-Regular.5f7303c0.ttf -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-100.5cb7edfc.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-100.5cb7edfc.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-100.7370c367.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-100.7370c367.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-300.b00849e0.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-300.b00849e0.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-300.ef7c6637.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-300.ef7c6637.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-400.479970ff.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-400.479970ff.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-400.60fa3c06.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-400.60fa3c06.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-500.020c97dc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-500.020c97dc.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-500.87284894.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-500.87284894.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-700.2735a3a6.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-700.2735a3a6.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-700.adcde98f.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-700.adcde98f.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-900.9b3766ef.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-900.9b3766ef.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-900.bb1e4dc6.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-900.bb1e4dc6.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/ShadowsIntoLight-Regular.47c22e0a.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/ShadowsIntoLight-Regular.47c22e0a.ttf -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-100italic.f8b1df51.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-100italic.f8b1df51.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-100italic.f9e8e590.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-100italic.f9e8e590.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-300italic.14286f3b.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-300italic.14286f3b.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-300italic.4df32891.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-300italic.4df32891.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-400italic.51521a2a.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-400italic.51521a2a.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-400italic.fe65b833.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-400italic.fe65b833.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-500italic.288ad9c6.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-500italic.288ad9c6.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-500italic.db4a2a23.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-500italic.db4a2a23.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-700italic.81f57861.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-700italic.81f57861.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-700italic.da0e7178.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-700italic.da0e7178.woff2 -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-900italic.28f91510.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-900italic.28f91510.woff -------------------------------------------------------------------------------- /web/react-fe/build/static/media/roboto-latin-900italic.ebf6d164.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkbalgi/isosim/HEAD/web/react-fe/build/static/media/roboto-latin-900italic.ebf6d164.woff2 -------------------------------------------------------------------------------- /cmd/isoserver/run.bat: -------------------------------------------------------------------------------- 1 | REM "---- Running ISO Server .. ----" 2 | go run isoserver.go -specs-dir ..\..\test\testdata\specs --def-file ..\..\test\testdata\appdata\2\IsoMiniSpec_Server_01.srvdef.json 3 | -------------------------------------------------------------------------------- /githooks/update_commit_id.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | commit_id=`git log -1 HEAD | head -1 | awk '{print substr($2,0,8)}'` 4 | echo "package main 5 | 6 | var version=\"0.12.1\" 7 | var build=\"$commit_id\"" > ../cmd/isosim/version.go 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module isosim 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-kit/kit v0.10.0 7 | github.com/google/uuid v1.0.0 8 | github.com/rkbalgi/libiso v1.0.1 9 | github.com/rkbalgi/libiso/v2 v2.0.2-dev0 10 | github.com/sirupsen/logrus v1.6.0 11 | github.com/stretchr/testify v1.5.1 12 | go.etcd.io/bbolt v1.3.3 13 | ) 14 | -------------------------------------------------------------------------------- /test/testdata/appdata/1/1/Test_F2_000_Approved: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01100000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"Fixed2_ASCII","Value":"000"},{"ID":4,"Name":"Fixed3_EBCDIC","Value":"ABC"},{"ID":17,"Name":"VarField55_BCD_BINARY","Value":"CAFEBABE"}] -------------------------------------------------------------------------------- /test/testdata/appdata/2/1/TC_Approval: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"548876515544244"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Amount","Value":"000000000100"},{"ID":6,"Name":"STAN","Value":"122332"}] -------------------------------------------------------------------------------- /test/testdata/appdata/2/1/TC_Reversal: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1420"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"548876515544244"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Amount","Value":"000000000900"},{"ID":6,"Name":"STAN","Value":"919919"}] -------------------------------------------------------------------------------- /test/testdata/appdata/2/1/TC_ActionCode_100: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"548876515544244"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Amount","Value":"000000000900"},{"ID":6,"Name":"STAN","Value":"122332"}] -------------------------------------------------------------------------------- /test/testdata/appdata/2/1/TC_ActionCode_200: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"548876515544244"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Amount","Value":"000000000200"},{"ID":6,"Name":"STAN","Value":"122332"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/4/TC_EBCDIC_Bitmap_Approved: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"4132332424242442"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":8,"Name":"Amount","Value":"000000000010"},{"ID":9,"Name":"STAN","Value":"133444"}] -------------------------------------------------------------------------------- /cmd/isosim/run.bat: -------------------------------------------------------------------------------- 1 | REM "---- Starting ISO Websim .. ----" 2 | REM "---- Setting ENV variables -----" 3 | set TLS_ENABLED=false 4 | set TLS_CERT_FILE=C:\Users\rkbal\IdeaProjects\isosim-prj\src\isosim\certs\cert.pem 5 | set TLS_KEY_FILE=C:\Users\rkbal\IdeaProjects\isosim-prj\src\isosim\certs\key.pem 6 | REM "---- Starting App -----" 7 | go run . -http-port 8080 --log-level TRACE -specs-dir ..\..\test\testdata\specs -html-dir ..\..\web -data-dir ..\..\test\testdata\appdata 8 | -------------------------------------------------------------------------------- /test/testdata/appdata/3/3/save_test_01: -------------------------------------------------------------------------------- 1 | [{"ID":3,"Name":"PAN","Value":"56554433367776"},{"ID":8,"Name":"Amount","Value":"000000000090"},{"ID":2,"Name":"Bitmap","Value":"0111000000100000000000000000000000100000000000000001000000000000"},{"ID":9,"Name":"STAN","Value":"123456"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":10,"Name":"Track 2","Value":"56554433367776f20030202020"},{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":14,"Name":"PIN Data","Value":"1234567890abcd11"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/2/TC_Reversal: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1420"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"4355555555555555"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Amount","Value":"000000000100"},{"ID":6,"Name":"STAN","Value":"123456"},{"ID":7,"Name":"Retrieval Ref Code","Value":"655144425244"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/3/TC_ASCII_Bitmap_Declined: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"56554433367776"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":8,"Name":"Amount","Value":"000000000190"},{"ID":9,"Name":"STAN","Value":"827276"},{"ID":14,"Name":"PIN Data","Value":"1234567890ABCD11"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/4/TC_EBCDIC_Bitmap_Declined: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"4132332424242442"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":8,"Name":"Amount","Value":"000000000900"},{"ID":9,"Name":"STAN","Value":"133444"},{"ID":15,"Name":"ICC Data","Value":"5F3C928822777666"}] -------------------------------------------------------------------------------- /internal/services/websim/http_urls.go: -------------------------------------------------------------------------------- 1 | package websim 2 | 3 | const ( 4 | URLAllSpecs = "/iso/v1/specs" 5 | URLMessages4Spec = "/iso/v1/msgs/" //{specId} 6 | 7 | URLGetMessageTemplate = "/iso/v1/template/" //{specId}/{msgId} 8 | URLParseTrace = "/iso/v1/parse/" //{specId}/{msgId}/ 9 | 10 | URLParseTraceExternal = "/iso/v1/parse/external" 11 | URLSendMessageToHost = "/iso/v1/send" 12 | URLSaveMsg = "/iso/v1/save" 13 | URLLoadMsg = "/iso/v1/loadmsg" 14 | ) 15 | -------------------------------------------------------------------------------- /test/testdata/appdata/3/3/TC_ASCII_Bitmap_Approved: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000001000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"56554433367776"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":8,"Name":"Amount","Value":"000000000090"},{"ID":9,"Name":"STAN","Value":"827276"},{"ID":10,"Name":"Track 2","Value":"56554433367776F20030202020"},{"ID":14,"Name":"PIN Data","Value":"1234567890ABCD11"}] -------------------------------------------------------------------------------- /test/testdata/appdata/4/1/TC_Amount99: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"11110000001001000010000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"},{"ID":16,"Name":"pan","Value":"827272622524"},{"ID":3,"Name":"proc_code","Value":"004000"},{"ID":4,"Name":"amount","Value":"000000000099"},{"ID":5,"Name":"stan","Value":"877654"},{"ID":6,"Name":"expiration_date","Value":"2209"},{"ID":7,"Name":"country_code","Value":"840"},{"ID":14,"Name":"key_mgmt_data","Value":"1234"}] -------------------------------------------------------------------------------- /test/testdata/appdata/4/2/TC_Reversal: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1420"},{"ID":2,"Name":"Bitmap","Value":"11110000001001000010000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"},{"ID":3,"Name":"pan","Value":"122334566667"},{"ID":4,"Name":"proc_code","Value":"004000"},{"ID":8,"Name":"amount","Value":"000000000876"},{"ID":9,"Name":"stan","Value":"766554"},{"ID":16,"Name":"expiration_date","Value":"2209"},{"ID":17,"Name":"country_code","Value":"840"},{"ID":14,"Name":"key_mgmt_data","Value":"7666"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amt100_ActionCode_100: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"4522222343333661"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":8,"Name":"Amount","Value":"000000000100"},{"ID":9,"Name":"STAN","Value":"123457"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amt91_ActionCode_000: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01110000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"4522222343333661"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":8,"Name":"Amount","Value":"000000000091"},{"ID":9,"Name":"STAN","Value":"123456"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/IsoTestSpec_Server_05.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"IsoTestSpec_Server_05","ServerPort":6666,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"COVID"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"100"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":5,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/3/IsoTestSpec_Server_06.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"IsoTestSpec_Server_06","ServerPort":6666,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"COVID1"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"100"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":5,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"}]} -------------------------------------------------------------------------------- /.github/workflows/build_isosim.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | schedule: 7 | - cron: '0 0 */7 * *' 8 | 9 | jobs: 10 | checks: 11 | name: run 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - run: go test -v -coverprofile=coverage.out ./... 16 | 17 | - name: run 18 | uses: cedrickring/golang-action@1.5.1 19 | 20 | - uses: codecov/codecov-action@v1 21 | with: 22 | token: ${{ secrets.CODECOV_TOKEN }} 23 | file: ./coverage.out 24 | fail_ci_if_error: true 25 | -------------------------------------------------------------------------------- /test/testdata/appdata/4/2/TC_Reversal_response_ref.json: -------------------------------------------------------------------------------- 1 | [{"ID":9,"Name":"stan","Value":"766554"},{"ID":4,"Name":"proc_code","Value":"004000"},{"ID":1,"Name":"Message Type","Value":"1430"},{"ID":3,"Name":"pan","Value":"122334566667"},{"ID":14,"Name":"key_mgmt_data","Value":"7666"},{"ID":8,"Name":"amount","Value":"000000000876"},{"ID":16,"Name":"expiration_date","Value":"2209"},{"ID":11,"Name":"action_code","Value":"400"},{"ID":2,"Name":"Bitmap","Value":"11110000001001000010000000000000000000100000000000000000000000000000000000000000000000000000000100000000000000000000000000000000"},{"ID":17,"Name":"country_code","Value":"840"}] -------------------------------------------------------------------------------- /web/react-fe/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /test/testdata/appdata/1/1/TC_Declined: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01100100000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"Fixed2_ASCII","Value":"100"},{"ID":4,"Name":"Fixed3_EBCDIC","Value":"ABC"},{"ID":7,"Name":"FxdField6_WithSubFields","Value":"12345678"},{"ID":8,"Name":"SF6_1","Value":"1234"},{"ID":9,"Name":"SF6_1_1","Value":"12"},{"ID":10,"Name":"SF6_1_2","Value":"34"},{"ID":11,"Name":"SF6_2","Value":"56"},{"ID":12,"Name":"SF6_3","Value":"78"},{"ID":17,"Name":"VarField55_BCD_BINARY","Value":"0987ABCDFE"}] -------------------------------------------------------------------------------- /test/testdata/appdata/1/1/TC_000_Approved: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01100100000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"Fixed2_ASCII","Value":"000"},{"ID":4,"Name":"Fixed3_EBCDIC","Value":"ABC"},{"ID":7,"Name":"FxdField6_WithSubFields","Value":"12345678"},{"ID":8,"Name":"SF6_1","Value":"1234"},{"ID":9,"Name":"SF6_1_1","Value":"12"},{"ID":10,"Name":"SF6_1_2","Value":"34"},{"ID":11,"Name":"SF6_2","Value":"56"},{"ID":12,"Name":"SF6_3","Value":"78"},{"ID":17,"Name":"VarField55_BCD_BINARY","Value":"0987ABCDFE"}] -------------------------------------------------------------------------------- /test/testdata/appdata/2/IsoMiniSpec_Server_01.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":2,"ServerName":"IsoMiniSpec_Server_01","ServerPort":6667,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":7,"FieldValue":"APPV01"},{"FieldId":8,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":5,"MatchConditionType":"IntEquals","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":8,"FieldValue":"200"}],"FieldId":5,"MatchConditionType":"IntEquals","FieldValue":"200"},{"OffFields":[],"ValFields":[{"FieldId":8,"FieldValue":"100"}],"FieldId":5,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":1,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/1/IsoTestSpec_Server_01.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":1,"ServerName":"IsoTestSpec_Server_01","ServerPort":6666,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":18,"FieldValue":"Approved"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":3,"MatchConditionType":"StringEquals","FieldValue":"000"},{"OffFields":[],"ValFields":[{"FieldId":18,"FieldValue":"Declined"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":3,"MatchConditionType":"StringEquals","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":18,"FieldValue":"Referral"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":3,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":1,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1090: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000000000000001000000000000000"},{"ID":3,"Name":"PAN","Value":"4766977654327777"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":8,"Name":"Amount","Value":"000000001090"},{"ID":9,"Name":"STAN","Value":"666551"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522"},{"ID":19,"Name":"Merchant Type","Value":"9311"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1091: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000000000000001000000000000000"},{"ID":3,"Name":"PAN","Value":"4766977654327777"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":8,"Name":"Amount","Value":"000000001091"},{"ID":9,"Name":"STAN","Value":"666551"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522"},{"ID":19,"Name":"Merchant Type","Value":"9311"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1090_2: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000000000000001000000000000000"},{"ID":3,"Name":"PAN","Value":"4766977654327777"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":8,"Name":"Amount","Value":"000000001090"},{"ID":9,"Name":"STAN","Value":"666551"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522"},{"ID":19,"Name":"Merchant Type","Value":"9311"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826"}] -------------------------------------------------------------------------------- /test/testdata/appdata/1/1/TC_Referral: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01100110000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"Fixed2_ASCII","Value":"999"},{"ID":4,"Name":"Fixed3_EBCDIC","Value":"ABC"},{"ID":7,"Name":"FxdField6_WithSubFields","Value":"12345678"},{"ID":8,"Name":"SF6_1","Value":"1234"},{"ID":9,"Name":"SF6_1_1","Value":"12"},{"ID":10,"Name":"SF6_1_2","Value":"34"},{"ID":11,"Name":"SF6_2","Value":"56"},{"ID":12,"Name":"SF6_3","Value":"78"},{"ID":13,"Name":"VarField7_WithSubFields","Value":"919190612345"},{"ID":14,"Name":"SF7_1","Value":"91919"},{"ID":15,"Name":"SF7_2","Value":"767655"},{"ID":16,"Name":"SF7_3","Value":"12345"},{"ID":17,"Name":"VarField55_BCD_BINARY","Value":"0987ABCDFE"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/Iso8583TestSpec_Server_01.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"Iso8583TestSpec_Server_01","ServerPort":6668,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":10,"FieldValue":"001230"},{"FieldId":11,"FieldValue":"000"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":11,"FieldValue":"100"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":1,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"},{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":9,"FieldValue":"400"},{"FieldId":1,"FieldValue":"1430"}],"FieldId":5,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":2,"BytesFrom":0,"BytesTo":4,"BytesValue":"31343230"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1091_response_ref.json: -------------------------------------------------------------------------------- 1 | [{"ID":5,"Name":"Transaction Type","Value":"00"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336"},{"ID":1,"Name":"Message Type","Value":"1110"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000010000000001000000000000000"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":11,"Name":"Action Code","Value":"100"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826"},{"ID":9,"Name":"STAN","Value":"666551"},{"ID":7,"Name":"Acct To","Value":"00"},{"ID":19,"Name":"Merchant Type","Value":"9311"},{"ID":6,"Name":"Acct From","Value":"40"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522"},{"ID":8,"Name":"Amount","Value":"000000001090"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335"},{"ID":3,"Name":"PAN","Value":"4766977654327777"}] -------------------------------------------------------------------------------- /test/testdata/appdata/4/1/TC_Amount99_response_ref.json: -------------------------------------------------------------------------------- 1 | [{"ID":14,"Name":"key_mgmt_data","Value":"1234","CompareOp":"Equals"},{"ID":1,"Name":"Message Type","Value":"1110","CompareOp":"Equals"},{"ID":3,"Name":"proc_code","Value":"004000","CompareOp":"Equals"},{"ID":4,"Name":"amount","Value":"000000000099","CompareOp":"Equals"},{"ID":13,"Name":"private_3","Value":"007","CompareOp":"Equals"},{"ID":16,"Name":"pan","Value":"827272622524","CompareOp":"Equals"},{"ID":7,"Name":"country_code","Value":"840","CompareOp":"Equals"},{"ID":9,"Name":"action_code","Value":"000","CompareOp":"Equals"},{"ID":2,"Name":"Bitmap","Value":"11110000001001000010000000000000000001100000000000000000000000100000000000000000000000000000000100000000000000000000000000000000","CompareOp":"Equals"},{"ID":5,"Name":"stan","Value":"877654","CompareOp":"Equals"},{"ID":6,"Name":"expiration_date","Value":"2209","CompareOp":"Equals"},{"ID":8,"Name":"approval_code","Value":"APPR01","CompareOp":"Equals"}] -------------------------------------------------------------------------------- /internal/services/data/json_field_rep_test.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 7 | log "github.com/sirupsen/logrus" 8 | "path/filepath" 9 | "testing" 10 | ) 11 | 12 | func init() { 13 | log.SetLevel(log.TraceLevel) 14 | err := isov2.ReadSpecs(filepath.Join("..", "..", "..", "test", "testdata", "specs")) 15 | if err != nil { 16 | fmt.Print(err) 17 | } 18 | } 19 | func TestNewJsonMessageTemplate(t *testing.T) { 20 | 21 | msg := isov2.SpecByName("ISO8583-Test").MessageByName("1100 - Authorization") 22 | 23 | jmt := NewJsonMessageTemplate(msg) 24 | if jsonData, err := json.Marshal(jmt); err != nil { 25 | t.Fatal(err) 26 | } else { 27 | t.Log(string(jsonData)) 28 | } 29 | 30 | } 31 | 32 | func printChildren(f *isov2.Field) { 33 | if f.HasChildren() { 34 | for _, c := range f.Children { 35 | fmt.Println("->" + c.Name) 36 | 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/testdata/appdata/2/IsoMiniSpec_Server_WithReversal.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":2,"ServerName":"IsoMiniSpec_Server_WithReversal","ServerPort":6669,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":7,"FieldValue":"APPV01"},{"FieldId":8,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":5,"MatchConditionType":"IntEquals","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":8,"FieldValue":"200"}],"FieldId":5,"MatchConditionType":"IntEquals","FieldValue":"200"},{"OffFields":[],"ValFields":[{"FieldId":8,"FieldValue":"100"}],"FieldId":5,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":1,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"},{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":9,"FieldValue":"400"},{"FieldId":1,"FieldValue":"1430"}],"FieldId":5,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":2,"BytesFrom":0,"BytesTo":4,"BytesValue":"31343230"}]} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # stage 1 2 | FROM golang:alpine 3 | MAINTAINER Raghavendra Balgi;rkbalgi@gmail.com 4 | COPY . /home/isosim/app/isosim 5 | WORKDIR /home/isosim/app/isosim/cmd/isosim 6 | RUN go build -v -o app . 7 | 8 | # stage 2 9 | FROM alpine 10 | #USER 1001:1001 11 | ADD web /etc/isosim/web 12 | ADD test/testdata/specs /etc/isosim/specs 13 | ADD test/testdata/appdata /etc/isosim/data 14 | ADD test/testdata/certs /etc/isosim/certs 15 | ENV HTTP_PORT 8080 16 | ENV LOG_LEVEL DEBUG 17 | ENV TLS_ENABLED=false 18 | ENV TLS_CERT_FILE=/etc/isosim/certs/cert.pem 19 | ENV TLS_KEY_FILE=/etc/isosim/certs/key.pem 20 | COPY --from=0 /home/isosim/app/isosim/cmd/isosim/app /usr/apps/isosim 21 | ENTRYPOINT /usr/apps/isosim --http-port $HTTP_PORT \ 22 | --log-level $LOG_LEVEL \ 23 | --html-dir /etc/isosim/web \ 24 | --data-dir /etc/isosim/data \ 25 | --specs-dir /etc/isosim/specs 26 | -------------------------------------------------------------------------------- /test/testdata/appdata/3/Iso8583TestSpec_Server_02.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"Iso8583TestSpec_Server_02","ServerPort":7777,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":10,"FieldValue":"001230"},{"FieldId":11,"FieldValue":"000"}],"FieldId":1,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":11,"FieldValue":"100"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":3,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"},{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":9,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"000130"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"100"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":3,"BytesFrom":0,"BytesTo":4,"BytesValue":"F1F1F0F0"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/3/Iso8583TestSpec_Server_03.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"Iso8583TestSpec_Server_03","ServerPort":7777,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"001230"},{"FieldId":13,"FieldValue":"000"}],"FieldId":1,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":13,"FieldValue":"100"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":3,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"},{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"000130"}],"FieldId":1,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"100"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":1,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":4,"BytesFrom":0,"BytesTo":4,"BytesValue":"F1F1F0F0"}]} -------------------------------------------------------------------------------- /test/testdata/appdata/3/Iso8583TestSpec_Server_04.srvdef.json: -------------------------------------------------------------------------------- 1 | {"SpecId":3,"ServerName":"Iso8583TestSpec_Server_04","ServerPort":7777,"MliType":"2I","MsgSelectionConfigs":[{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"001230"},{"FieldId":13,"FieldValue":"000"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":1,"FieldValue":"1110"},{"FieldId":13,"FieldValue":"100"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":3,"BytesFrom":0,"BytesTo":4,"BytesValue":"31313030"},{"ProcessingConditions":[{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"000"},{"FieldId":1,"FieldValue":"1110"},{"FieldId":12,"FieldValue":"000130"}],"FieldId":8,"MatchConditionType":"IntLt","FieldValue":"100"},{"OffFields":[],"ValFields":[{"FieldId":13,"FieldValue":"100"},{"FieldId":1,"FieldValue":"1110"}],"FieldId":8,"MatchConditionType":"Any","FieldValue":"?"}],"Msg":4,"BytesFrom":0,"BytesTo":4,"BytesValue":"F1F1F0F0"}]} -------------------------------------------------------------------------------- /internal/iso/server/iso_def.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "isosim/internal/db" 8 | "isosim/internal/services/data" 9 | 10 | "sync" 11 | ) 12 | 13 | var sd map[string]*data.ServerDef 14 | var sdMu sync.Mutex 15 | 16 | func init() { 17 | sd = make(map[string]*data.ServerDef, 10) 18 | } 19 | 20 | func getDef(specId string, defName string) (data.ServerDef, error) { 21 | 22 | defId := specId + defName 23 | 24 | sdMu.Lock() 25 | defer sdMu.Unlock() 26 | 27 | def, ok := sd[defId] 28 | if !ok { 29 | //do processing 30 | def = &data.ServerDef{MsgSelectionConfigs: make([]data.MsgSelectionConfig, 0, 10)} 31 | serverDef, err := db.DataSetManager().ServerDef(specId, defName) 32 | if err != nil { 33 | return data.ServerDef{}, fmt.Errorf("isosim: Unexpected error while reading server definition : %w", err) 34 | } 35 | err = json.NewDecoder(bytes.NewBuffer(serverDef)).Decode(def) 36 | if err != nil { 37 | return data.ServerDef{}, err 38 | } 39 | sd[defId] = def 40 | } 41 | return *def, nil 42 | 43 | } 44 | -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1090_2_response_ref.json: -------------------------------------------------------------------------------- 1 | [{"ID":9,"Name":"STAN","Value":"666551","CompareOp":"Equals"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826","CompareOp":"Equals"},{"ID":3,"Name":"PAN","Value":"4766977654327777","CompareOp":"Equals"},{"ID":4,"Name":"Processing Code","Value":"004000","CompareOp":"Equals"},{"ID":6,"Name":"Acct From","Value":"40","CompareOp":"Equals"},{"ID":19,"Name":"Merchant Type","Value":"9311","CompareOp":"Equals"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522","CompareOp":"Equals"},{"ID":5,"Name":"Transaction Type","Value":"00","CompareOp":"Equals"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000010000000001000000000000000","CompareOp":"Equals"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336","CompareOp":"Present"},{"ID":1,"Name":"Message Type","Value":"1110","CompareOp":"Equals"},{"ID":7,"Name":"Acct To","Value":"00","CompareOp":"Equals"},{"ID":11,"Name":"Action Code","Value":"100","CompareOp":"Equals"},{"ID":8,"Name":"Amount","Value":"000000001090","CompareOp":"Exclude"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335","CompareOp":"Exclude"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/1/TC_Amount_1090_response_ref.json: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1110","CompareOp":"Equals"},{"ID":8,"Name":"Amount","Value":"000000001090","CompareOp":"Equals"},{"ID":2,"Name":"Bitmap","Value":"0111000000111000011000000000000000000010000000001000000000000000","CompareOp":"Equals"},{"ID":5,"Name":"Transaction Type","Value":"00","CompareOp":"Present"},{"ID":6,"Name":"Acct From","Value":"40","CompareOp":"StartsWith"},{"ID":16,"Name":"Time, Local Transaction","Value":"201335","CompareOp":"Exclude"},{"ID":19,"Name":"Merchant Type","Value":"9311","CompareOp":"Equals"},{"ID":4,"Name":"Processing Code","Value":"004000","CompareOp":"Equals"},{"ID":18,"Name":"Country Code, Acquiring Institution","Value":"336","CompareOp":"Equals"},{"ID":14,"Name":"Currency Code, Transaction","Value":"826","CompareOp":"Equals"},{"ID":3,"Name":"PAN","Value":"4766977654327777","CompareOp":"Equals"},{"ID":9,"Name":"STAN","Value":"666551","CompareOp":"Exclude"},{"ID":7,"Name":"Acct To","Value":"00","CompareOp":"Equals"},{"ID":11,"Name":"Action Code","Value":"100","CompareOp":"Equals"},{"ID":17,"Name":"Date, Local Transaction","Value":"0522","CompareOp":"Present"}] -------------------------------------------------------------------------------- /test/testdata/appdata/3/5/Test_PaddingSupport_01: -------------------------------------------------------------------------------- 1 | [{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"01101111101111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"},{"ID":3,"Name":"PAN","Value":"66543345566777665"},{"ID":4,"Name":"Processing Code","Value":"000000"},{"ID":8,"Name":"Amount","Value":"000000000098"},{"ID":17,"Name":"Amount","Value":"980000000000"},{"ID":18,"Name":"BCDField_PadLeading0","Value":"000022"},{"ID":19,"Name":"BCDField_PadTrailing0","Value":"100000"},{"ID":20,"Name":"BinaryField_PadLeading0","Value":"000032"},{"ID":21,"Name":"BinaryField_PadTrailing0","Value":"120000"},{"ID":9,"Name":"STAN","Value":"111111"},{"ID":22,"Name":"BinaryField_PadLeadingF","Value":"FFFF21"},{"ID":23,"Name":"BinaryField_PadTrailingF","Value":"12FFFF"},{"ID":29,"Name":"PAN_BCD_Special","Value":"876526544676665F"},{"ID":25,"Name":"ASCIIField_PadTrailingSpaces","Value":"87 "},{"ID":26,"Name":"ASCIIField_PadLeadingSpaces","Value":" 98"},{"ID":27,"Name":"EBCDICField_PadTrailingSpaces","Value":"87 "},{"ID":28,"Name":"EBCDICField_LeadingZeroes","Value":"9800000000"}] -------------------------------------------------------------------------------- /internal/services/data/server_definition.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | // MsgSelectionConfig defines a selection criteria for message selection 4 | type MsgSelectionConfig struct { 5 | Msg int 6 | BytesFrom int 7 | BytesTo int 8 | BytesValue string 9 | ProcessingConditions []ProcessingCondition 10 | } 11 | 12 | // ProcessingCondition defines a matching condition (based on a field) and actions 13 | // that are to be taken once the condition has matched (setting of fields to specific value, turning them off etc) 14 | type ProcessingCondition struct { 15 | FieldId int 16 | FieldValue string 17 | MatchConditionType string 18 | 19 | OffFields []int 20 | ValFields []ValFieldConfig 21 | } 22 | 23 | // ValFieldConfig is a tuple of a field id and a value (used in @ProcessingCondition) 24 | type ValFieldConfig struct { 25 | FieldId int 26 | FieldValue string 27 | } 28 | 29 | // ServerDef defines a server's behaviour based on selection conditions, processing conditions etc 30 | type ServerDef struct { 31 | SpecId int 32 | ServerName string 33 | ServerPort int 34 | MliType string 35 | MsgSelectionConfigs []MsgSelectionConfig 36 | } 37 | -------------------------------------------------------------------------------- /web/react-fe/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.c31fdb430857a50a4adec070266b497b.js" 18 | ); 19 | 20 | self.addEventListener('message', (event) => { 21 | if (event.data && event.data.type === 'SKIP_WAITING') { 22 | self.skipWaiting(); 23 | } 24 | }); 25 | 26 | workbox.core.clientsClaim(); 27 | 28 | /** 29 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 30 | * requests for URLs in the manifest. 31 | * See https://goo.gl/S9QRab 32 | */ 33 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 34 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 35 | 36 | workbox.routing.registerNavigationRoute(workbox.precaching.getCacheKeyForURL("/index.html"), { 37 | 38 | blacklist: [/^\/_/,/\/[^/?]+\.[^/]+$/], 39 | }); 40 | -------------------------------------------------------------------------------- /test/testdata/certs/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDozCCAosCFAxxm/BVZwcdIxWATqaBU1j/ybxdMA0GCSqGSIb3DQEBCwUAMIGN 3 | MQswCQYDVQQGEwJJTjESMBAGA1UECAwJS2FybmF0YWthMRIwEAYDVQQHDAlCZW5n 4 | YWx1cnUxFTATBgNVBAoMDGRhYWxpdG9pIGluYzEMMAoGA1UECwwDZGV2MQ8wDQYD 5 | VQQDDAZyYWdoYXYxIDAeBgkqhkiG9w0BCQEWEXJrYmFsZ2lAZ21haWwuY29tMB4X 6 | DTIwMDQwNTEyMjU0M1oXDTMzMTIxMzEyMjU0M1owgY0xCzAJBgNVBAYTAklOMRIw 7 | EAYDVQQIDAlLYXJuYXRha2ExEjAQBgNVBAcMCUJlbmdhbHVydTEVMBMGA1UECgwM 8 | ZGFhbGl0b2kgaW5jMQwwCgYDVQQLDANkZXYxDzANBgNVBAMMBnJhZ2hhdjEgMB4G 9 | CSqGSIb3DQEJARYRcmtiYWxnaUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUA 10 | A4IBDwAwggEKAoIBAQCpsnkGNvPD5i+R/MZ09mjDyQ6piNZ8wnZgH8GVOzVqqFxm 11 | UgbjBnvANktUwTRqxsUo/8yMTt8MY9cCdlWaP2v7oVs8z8uQbWKaAnedljD/L3ke 12 | WLMqxmuSqYNO9lQoMmUZYI7LrSRFogWCgxaRZyGCTIEWDqEKMo1NGizRKlIrg+nk 13 | zFc+ilqhdR41qzoWfBcrpKe7jV4sAKFJ5b36OqCV/yJYdclXI8oK/XahvSdfJq7u 14 | NtkuP4obVUmagvzsZLKMtixY08Je+HkIBrKfrQdF7ZvyeoEeRGjhj3UM+q7P1m9F 15 | eVAJ2XolFb6Y8nU9ZwbLOnxT+v2qfqFKHWCF3QapAgMBAAEwDQYJKoZIhvcNAQEL 16 | BQADggEBAADVnLywCv0ycnIy047NEUvqJUCJleHJb8fqlCAXceSevvqs2eI4E0ip 17 | VCLZkQ61giNH4G4R+yU+SGq+0qjQ3291CQ9IEOBTsrycNcemaqqAAuE4ANEHcIGL 18 | oTQAEuy3w6cujaMUNeaHR8OunvQfcPqXCmPXJcd+12bY/M83o7V6KL97zK79ucHJ 19 | m2G1qrkZwF9ARs/CVCYP5uavjATmDdcfMpVhwSWFd34BjhTfhh7d5PkrkQ3f5KY8 20 | bROQAv5NhNsEmMfFFjdYZ9CeYkmXDmRfcXBam9PGaV7SgbEEplrVzsIDGKYiXgi8 21 | M5ck1iYgCcp9l8uXaQob0RYyl1n5ZDY= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /web/react-fe/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.bbd4fcb0.chunk.css", 4 | "main.js": "/static/js/main.f4601f26.chunk.js", 5 | "main.js.map": "/static/js/main.f4601f26.chunk.js.map", 6 | "runtime-main.js": "/static/js/runtime-main.d1dafa31.js", 7 | "runtime-main.js.map": "/static/js/runtime-main.d1dafa31.js.map", 8 | "static/css/2.10ffe1ee.chunk.css": "/static/css/2.10ffe1ee.chunk.css", 9 | "static/js/2.5683fb71.chunk.js": "/static/js/2.5683fb71.chunk.js", 10 | "static/js/2.5683fb71.chunk.js.map": "/static/js/2.5683fb71.chunk.js.map", 11 | "index.html": "/index.html", 12 | "precache-manifest.c31fdb430857a50a4adec070266b497b.js": "/precache-manifest.c31fdb430857a50a4adec070266b497b.js", 13 | "service-worker.js": "/service-worker.js", 14 | "static/css/2.10ffe1ee.chunk.css.map": "/static/css/2.10ffe1ee.chunk.css.map", 15 | "static/css/main.bbd4fcb0.chunk.css.map": "/static/css/main.bbd4fcb0.chunk.css.map", 16 | "static/js/2.5683fb71.chunk.js.LICENSE.txt": "/static/js/2.5683fb71.chunk.js.LICENSE.txt", 17 | "static/media/index.css": "/static/media/roboto-latin-900italic.ebf6d164.woff2" 18 | }, 19 | "entrypoints": [ 20 | "static/js/runtime-main.d1dafa31.js", 21 | "static/css/2.10ffe1ee.chunk.css", 22 | "static/js/2.5683fb71.chunk.js", 23 | "static/css/main.bbd4fcb0.chunk.css", 24 | "static/js/main.f4601f26.chunk.js" 25 | ] 26 | } -------------------------------------------------------------------------------- /web/react-fe/build/static/css/main.bbd4fcb0.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}@font-face{font-family:"shadows-into-light";src:url(/static/media/ShadowsIntoLight-Regular.47c22e0a.ttf)}@font-face{font-family:"lato-regular";src:url(/static/media/Lato-Regular.2d36b1a9.ttf)}@font-face{font-family:"ptserif-regular";src:url(/static/media/PTSerif-Regular.5f7303c0.ttf)}.class_btn{font-family:"lato-regular",serif;font-size:12px}.class_small_div{display:inline-block;background-color:#ffcc72;min-width:20px;max-width:100px;max-height:100px;border-style:groove;margin:2px;align-content:center;align-items:center}.App,.class_small_div{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite}}.App-header{background-color:#282c34;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:calc(10px + 2vmin);color:#fff}.App-link{color:#61dafb}@-webkit-keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes App-logo-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}} 2 | /*# sourceMappingURL=main.bbd4fcb0.chunk.css.map */ -------------------------------------------------------------------------------- /internal/iso/server/response_builder.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | iso "github.com/rkbalgi/libiso/v2/iso8583" 5 | log "github.com/sirupsen/logrus" 6 | "isosim/internal/services/data" 7 | ) 8 | 9 | func buildResponse(isoMsg *iso.Iso, pc *data.ProcessingCondition) { 10 | 11 | parsedMsg := isoMsg.ParsedMsg() 12 | 13 | for _, id := range pc.OffFields { 14 | field := parsedMsg.Msg.FieldById(id) 15 | if field.Position > 0 { 16 | if field.ParentId > 0 { 17 | fd := parsedMsg.FieldDataMap[field.ParentId] 18 | if fd.Bitmap != nil { 19 | fd.Bitmap.SetOff(field.Position) 20 | } 21 | } 22 | 23 | } else { 24 | ///not a bitmapped field 25 | parsedMsg.FieldDataMap[id].Data = nil 26 | 27 | } 28 | } 29 | 30 | for _, vf := range pc.ValFields { 31 | 32 | field := parsedMsg.Msg.FieldById(vf.FieldId) 33 | fd := parsedMsg.GetById(vf.FieldId) 34 | log.Tracef("Setting field %s: ==> %s\n", field.Name, vf.FieldValue) 35 | 36 | if field.Position > 0 { 37 | if field.ParentId > 0 { 38 | fd := parsedMsg.FieldDataMap[field.ParentId] 39 | if fd.Bitmap != nil { 40 | // if the field is a bitmap then turn on the bit 41 | fd.Bitmap.Set(field.Position, vf.FieldValue) 42 | } 43 | } 44 | } else { 45 | if err := fd.Set(vf.FieldValue); err != nil { 46 | log.WithFields(log.Fields{"type": "iso_server"}).Errorf("Failed to set field value for field: %s : provided field value: %s\n", field.Name, vf.FieldValue) 47 | } 48 | } 49 | 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /internal/db/db_test.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | _ "net/http/pprof" 5 | "testing" 6 | ) 7 | 8 | func Test_ReadWriteToBolt(t *testing.T) { 9 | 10 | //t.SkipNow() 11 | if err := Init("."); err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | dbMsg := DbMessage{ 16 | ID: "", 17 | SpecID: 100, 18 | MsgID: 1, 19 | RequestTS: 436466364678, 20 | ResponseTS: 767366436647, 21 | RequestMsg: "110100101010010101010", 22 | ParsedRequestMsg: nil, 23 | ResponseMsg: "11110........", 24 | ParsedResponseMsg: nil, 25 | HostAddr: "localhost:7777", 26 | } 27 | for i := 0; i < 10; i++ { 28 | if err := Write(dbMsg); err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | //time.Sleep(1 * time.Second) 33 | } 34 | 35 | entries, err := ReadLast(100, 1, 5) 36 | if entries == nil { 37 | t.Fatal("No entries found!") 38 | } 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | t.Log(entries) 43 | } 44 | 45 | /*func Test_Read(t *testing.T) { 46 | 47 | log.SetLevel(log.DebugLevel) 48 | 49 | /*go func() { 50 | log.Fatal(http.ListenAndServe("localhost:8765", nil)) 51 | }()*/ 52 | 53 | //t.SkipNow() 54 | /* 55 | if err := Init("."); err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | entries, err := ReadLast(100, 1, 90) 60 | if entries == nil { 61 | t.Fatal("No entries found!") 62 | } 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | for _, e := range entries { 67 | t.Log(e) 68 | } 69 | t.Log(len(entries)) 70 | 71 | }*/ 72 | -------------------------------------------------------------------------------- /web/react-fe/build/static/js/runtime-main.d1dafa31.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,i,l=r[0],a=r[1],f=r[2],p=0,s=[];p 0 { 51 | buf.Write([]byte(``)) 52 | } else { 53 | rw.Write([]byte("No records found..")) 54 | return 55 | } 56 | 57 | for _, tmp := range res { 58 | buf.Write([]byte(`
`)) 59 | buf.Write([]byte(tmp)) 60 | buf.Write([]byte("
")) 61 | } 62 | 63 | buf.Write([]byte(``)) 64 | rw.Header().Add("Content-Type", "text/html") 65 | _, _ = rw.Write(buf.Bytes()) 66 | } 67 | 68 | }) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /web/react-fe/build/index.html: -------------------------------------------------------------------------------- 1 | ISO WebSim - ISO8583 Web Simulator
-------------------------------------------------------------------------------- /web/react-fe/build/static/css/main.bbd4fcb0.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["index.css","App.css"],"names":[],"mappings":"AAAA,KACE,QAAS,CACT,mJAEU,CACV,kCAAmC,CACnC,iCACF,CAEA,KACE,yEAEF,CAEA,WACE,gCAAiC,CACjC,4DACF,CAEA,WACE,0BAA2B,CAC3B,gDACF,CAEA,WACE,6BAA8B,CAC9B,mDACF,CAEA,WACE,gCAAkC,CAClC,cACF,CAEA,iBACE,oBAAqB,CACrB,wBAAyB,CACzB,cAAe,CACf,eAAgB,CAChB,gBAAiB,CACjB,mBAAoB,CACpB,UAAW,CACX,oBAAqB,CACrB,kBAEF,CC7CA,sBD4CE,iBC1CF,CAEA,UACE,aAAc,CACd,mBACF,CAEA,8CACE,UACE,mDAA4C,CAA5C,2CACF,CACF,CAEA,YACE,wBAAyB,CACzB,gBAAiB,CACjB,YAAa,CACb,qBAAsB,CACtB,kBAAmB,CACnB,sBAAuB,CACvB,4BAA6B,CAC7B,UACF,CAEA,UACE,aACF,CAEA,iCACE,GACE,sBACF,CACA,GACE,uBACF,CACF,CAPA,yBACE,GACE,sBACF,CACA,GACE,uBACF,CACF","file":"main.bbd4fcb0.chunk.css","sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n\n@font-face {\n font-family: \"shadows-into-light\";\n src: url(\"./fonts/ShadowsIntoLight-Regular.ttf\");\n}\n\n@font-face {\n font-family: \"lato-regular\";\n src: url(\"./fonts/Lato-Regular.ttf\");\n}\n\n@font-face {\n font-family: \"ptserif-regular\";\n src: url(\"./fonts/PTSerif-Regular.ttf\");\n}\n\n.class_btn {\n font-family: 'lato-regular', serif;\n font-size: 12px;\n}\n\n.class_small_div {\n display: inline-block;\n background-color: #ffcc72;\n min-width: 20px;\n max-width: 100px;\n max-height: 100px;\n border-style: groove;\n margin: 2px;\n align-content: center;\n align-items: center;\n text-align: center;\n}",".App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n"]} -------------------------------------------------------------------------------- /internal/services/service_registration.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "isosim/internal/iso" 6 | "isosim/internal/services/crypto" 7 | "isosim/internal/services/handlers" 8 | "isosim/internal/services/websim" 9 | "net/http" 10 | "path/filepath" 11 | "strings" 12 | ) 13 | 14 | type IsoHttpHandler struct { 15 | } 16 | 17 | func Init() error { 18 | 19 | setRoutes() 20 | return nil 21 | 22 | } 23 | 24 | func setRoutes() { 25 | 26 | //react front-end resources 27 | //for static resources 28 | http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { 29 | 30 | if req.Method == http.MethodOptions { 31 | log.Info("Responding to Options for CORS ") 32 | rw.Header().Set("Access-Control-Allow-Origin", "*") 33 | rw.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 34 | rw.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") 35 | 36 | rw.WriteHeader(http.StatusOK) 37 | 38 | return 39 | } 40 | 41 | if req.RequestURI == "/" || req.RequestURI == "/index.html" { 42 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, "react-fe", "build", "index.html")) 43 | return 44 | } 45 | 46 | i := strings.LastIndex(req.RequestURI, "/") 47 | fileName := req.RequestURI[i+1 : len(req.RequestURI)] 48 | subDir := "" 49 | 50 | switch { 51 | 52 | case strings.HasSuffix(req.RequestURI, ".css"): 53 | subDir = "css" 54 | case strings.HasSuffix(req.RequestURI, ".js"): 55 | subDir = "js" 56 | case 57 | strings.HasSuffix(req.RequestURI, ".ttf"), 58 | strings.HasSuffix(req.RequestURI, ".woff"), 59 | strings.HasSuffix(req.RequestURI, ".woff2"): 60 | subDir = "media" 61 | default: 62 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, "react-fe", "build", subDir, fileName)) 63 | return 64 | } 65 | 66 | if strings.HasPrefix(req.RequestURI, "/iso/v0/") && subDir != "" { 67 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, fileName)) 68 | } else { 69 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, "react-fe", "build", "static", subDir, fileName)) 70 | } 71 | }) 72 | 73 | //old legacy handlers 74 | handlers.AddAll() 75 | handlers.AddMiscHandlers() 76 | 77 | //v1 78 | websim.RegisterHTTPTransport() 79 | crypto.RegisterHTTPTransport() 80 | 81 | //misc 82 | handlers.MsgHistoryHandler() 83 | 84 | } 85 | -------------------------------------------------------------------------------- /api/swagger.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: "This is ISO WebSim [http://github.com/rkbalgi/isosim](http://github.com/rkbalgi/isosim)" 4 | version: "0.7.0" 5 | title: "Iso WebSim" 6 | contact: 7 | email: "rkbalgi@gmail.com" 8 | license: 9 | name: "Apache 2.0" 10 | url: "http://www.apache.org/licenses/LICENSE-2.0.html" 11 | host: "localhost:8080" 12 | basePath: "/iso/v1" 13 | tags: 14 | - name: "ISO8583" 15 | description: "A specification used for transmitting financial data between payment entities" 16 | - name: "simulator" 17 | description: "A tool/utility to simulate real world POS/ATM devices, Payment Gateways/Hosts etc" 18 | schemes: 19 | - "https" 20 | - "http" 21 | paths: 22 | /parse/external: 23 | post: 24 | tags: 25 | - "parse-trace" 26 | description: "Parse a ISO8583 trace given a ASCII encode hex trace, a spec and a msg name" 27 | operationId: "addPet" 28 | consumes: 29 | - "application/json" 30 | produces: 31 | - "application/json" 32 | parameters: 33 | - in: "body" 34 | name: "body" 35 | description: "The input data for parsing a trace" 36 | required: true 37 | schema: 38 | $ref: "#/definitions/parseTraceExternalInput" 39 | 40 | responses: 41 | 200: 42 | description: "Parsing successful" 43 | schema: 44 | type: "array" 45 | items: 46 | $ref: "#/definitions/ParsedField" 47 | 400: 48 | description: "Invalid input" 49 | schema: 50 | $ref: "#/definitions/Error" 51 | 500: 52 | description: "Server Error" 53 | 54 | 55 | definitions: 56 | parseTraceExternalInput: 57 | type: "object" 58 | properties: 59 | spec_name: 60 | type: "string" 61 | format: "string" 62 | msg_name: 63 | type: "string" 64 | format: "string" 65 | trace_data: 66 | type: "string" 67 | format:"string" 68 | example: 69 | spec_name: "ISO8583-Test" 70 | msg_name: "1100(A) - Authorization" 71 | trace_data: "313130303730323030303030323030303130303031343536353534343333333637373736303034303030303030303030303030303930313233343536313356554433367776f200302020201234567890abcd11" 72 | 73 | 74 | ParsedField: 75 | type: "object" 76 | properties: 77 | ID: 78 | type: "integer" 79 | format: "int64" 80 | Name: 81 | type: "string" 82 | Value: 83 | type: "string" 84 | Error: 85 | type: "object" 86 | properties: 87 | error: 88 | type: "string" 89 | format: "string" 90 | -------------------------------------------------------------------------------- /internal/services/crypto/http_transport.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/go-kit/kit/endpoint" 7 | "github.com/go-kit/kit/log/logrus" 8 | "github.com/go-kit/kit/transport" 9 | httptransport "github.com/go-kit/kit/transport/http" 10 | log "github.com/sirupsen/logrus" 11 | "io/ioutil" 12 | "net/http" 13 | ) 14 | 15 | const URLCryptoPinGen = "/iso/v1/crypto/pin_gen" 16 | const URLCryptoMacGen = "/iso/v1/crypto/mac_gen" 17 | 18 | func macGenReqDecoder(ctx context.Context, req *http.Request) (response interface{}, err error) { 19 | 20 | reqData, err := ioutil.ReadAll(req.Body) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | defer req.Body.Close() 26 | 27 | log.Debug("Received Mac Request - ", string(reqData)) 28 | 29 | mgr := &MacGenRequest{} 30 | if err := json.Unmarshal(reqData, mgr); err != nil { 31 | log.Debug("MacRequest unmarshal Error", err) 32 | return nil, err 33 | } 34 | 35 | return *mgr, nil 36 | 37 | } 38 | 39 | func pinGenReqDecoder(ctx context.Context, req *http.Request) (response interface{}, err error) { 40 | 41 | reqData, err := ioutil.ReadAll(req.Body) 42 | log.Tracef("Received pin_gen request - RequestData: %s\n", string(reqData)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer req.Body.Close() 47 | 48 | pgr := &PinGenRequest{} 49 | if err := json.Unmarshal(reqData, pgr); err != nil { 50 | return nil, err 51 | } 52 | 53 | return *pgr, nil 54 | 55 | } 56 | 57 | // decode the response into JSON - generic decoder 58 | func respEncoder(ctx context.Context, rw http.ResponseWriter, response interface{}) error { 59 | 60 | if f, ok := response.(endpoint.Failer); ok && f.Failed() != nil { 61 | errorEncoder(ctx, f.Failed(), rw) 62 | return nil 63 | } 64 | rw.Header().Add("Access-Control-Allow-Origin", "*") 65 | rw.Header().Add("Content-Type", "application/json; charset=utf-8") 66 | return json.NewEncoder(rw).Encode(response) 67 | } 68 | 69 | type errorWrapper struct { 70 | Error string `json:"error"` 71 | } 72 | 73 | func errorEncoder(_ context.Context, err error, w http.ResponseWriter) { 74 | //TODO:: construct specific error types based on err 75 | w.Header().Add("Access-Control-Allow-Origin", "*") 76 | w.WriteHeader(http.StatusBadRequest) 77 | _ = json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()}) 78 | } 79 | 80 | func RegisterHTTPTransport() { 81 | 82 | options := []httptransport.ServerOption{ 83 | httptransport.ServerErrorEncoder(errorEncoder), 84 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logrus.NewLogrusLogger(log.New()))), 85 | } 86 | 87 | service := &serviceImpl{} 88 | 89 | http.Handle(URLCryptoPinGen, httptransport.NewServer(pinGenEndpoint(service), pinGenReqDecoder, respEncoder, options...)) 90 | http.Handle(URLCryptoMacGen, httptransport.NewServer(macGenEndpoint(service), macGenReqDecoder, respEncoder, options...)) 91 | 92 | } 93 | -------------------------------------------------------------------------------- /web/react-fe/build/static/js/2.5683fb71.chunk.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | Copyright (c) 2017 Jed Watson. 9 | Licensed under the MIT License (MIT), see 10 | http://jedwatson.github.io/classnames 11 | */ 12 | 13 | /*! 14 | Copyright (c) 2017 Jed Watson. 15 | Licensed under the MIT License (MIT), see 16 | http://jedwatson.github.io/classnames 17 | */ 18 | 19 | /** 20 | * A better abstraction over CSS. 21 | * 22 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 23 | * @website https://github.com/cssinjs/jss 24 | * @license MIT 25 | */ 26 | 27 | /** @license React v0.19.0 28 | * scheduler.production.min.js 29 | * 30 | * Copyright (c) Facebook, Inc. and its affiliates. 31 | * 32 | * This source code is licensed under the MIT license found in the 33 | * LICENSE file in the root directory of this source tree. 34 | */ 35 | 36 | /** @license React v16.13.0 37 | * react-dom.production.min.js 38 | * 39 | * Copyright (c) Facebook, Inc. and its affiliates. 40 | * 41 | * This source code is licensed under the MIT license found in the 42 | * LICENSE file in the root directory of this source tree. 43 | */ 44 | 45 | /** @license React v16.13.0 46 | * react-is.production.min.js 47 | * 48 | * Copyright (c) Facebook, Inc. and its affiliates. 49 | * 50 | * This source code is licensed under the MIT license found in the 51 | * LICENSE file in the root directory of this source tree. 52 | */ 53 | 54 | /** @license React v16.13.0 55 | * react.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /**! 64 | * @fileOverview Kickass library to create and place poppers near their reference elements. 65 | * @version 1.16.1 66 | * @license 67 | * Copyright (c) 2016 Federico Zivolo and contributors 68 | * 69 | * Permission is hereby granted, free of charge, to any person obtaining a copy 70 | * of this software and associated documentation files (the "Software"), to deal 71 | * in the Software without restriction, including without limitation the rights 72 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 73 | * copies of the Software, and to permit persons to whom the Software is 74 | * furnished to do so, subject to the following conditions: 75 | * 76 | * The above copyright notice and this permission notice shall be included in all 77 | * copies or substantial portions of the Software. 78 | * 79 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 80 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 81 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 82 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 83 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 84 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 85 | * SOFTWARE. 86 | */ 87 | -------------------------------------------------------------------------------- /cmd/isosim/isosim.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "isosim/internal/iso" 7 | 8 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 9 | 10 | "isosim/internal/db" 11 | "isosim/internal/services" 12 | "net/http" 13 | _ "net/http/pprof" 14 | "os" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | 19 | log "github.com/sirupsen/logrus" 20 | ) 21 | 22 | func main() { 23 | 24 | if err := runApp(); err != nil { 25 | log.Error("Failed to start ISO WebSim. Error= " + err.Error()) 26 | os.Exit(1) 27 | } 28 | 29 | } 30 | 31 | func runApp() error { 32 | fmt.Println("======================================================") 33 | fmt.Printf("ISO WebSim v%s commit: %s\n", version, build) 34 | fmt.Println("======================================================") 35 | 36 | logLevel := flag.String("log-level", "debug", "Log level - [trace|debug|warn|info|error].") 37 | flag.StringVar(&iso.HTMLDir, "html-dir", "", "Directory that contains any HTML's and js/css files etc.") 38 | specsDir := flag.String("specs-dir", "", "The directory containing the ISO spec definition files.") 39 | httpPort := flag.Int("http-port", 8080, "HTTP/s port to listen on.") 40 | dataDir := flag.String("data-dir", "", "Directory to store messages (data sets). This is a required field.") 41 | 42 | flag.Parse() 43 | 44 | switch { 45 | case strings.EqualFold("trace", *logLevel): 46 | log.SetLevel(log.TraceLevel) 47 | case strings.EqualFold("debug", *logLevel): 48 | log.SetLevel(log.DebugLevel) 49 | case strings.EqualFold("info", *logLevel): 50 | log.SetLevel(log.InfoLevel) 51 | case strings.EqualFold("warn", *logLevel): 52 | log.SetLevel(log.WarnLevel) 53 | case strings.EqualFold("error", *logLevel): 54 | log.SetLevel(log.ErrorLevel) 55 | default: 56 | log.Warn("Invalid log-level specified, will default to DEBUG") 57 | log.SetLevel(log.DebugLevel) 58 | } 59 | 60 | log.SetFormatter(&log.TextFormatter{ForceColors: true, DisableColors: false}) 61 | 62 | if *dataDir == "" || *specsDir == "" || iso.HTMLDir == "" { 63 | flag.Usage() 64 | return fmt.Errorf("isosim: invalid/unspecified command line args") 65 | } 66 | 67 | err := db.Init(*dataDir) 68 | if err != nil { 69 | log.Fatal(err.Error()) 70 | } 71 | 72 | //read all the specs from the spec file 73 | err = isov2.ReadSpecs(*specsDir) 74 | if err != nil { 75 | log.Fatal(err.Error()) 76 | } 77 | 78 | //check if all the required HTML files are available 79 | if err = services.Init(); err != nil { 80 | log.Fatal(err.Error()) 81 | } 82 | 83 | // TLS parameters 84 | tlsEnabled := os.Getenv("TLS_ENABLED") 85 | certFile, keyFile := "", "" 86 | if strings.EqualFold(tlsEnabled, "true") { 87 | certFile = os.Getenv("TLS_CERT_FILE") 88 | keyFile = os.Getenv("TLS_KEY_FILE") 89 | if certFile == "" || keyFile == "" { 90 | return fmt.Errorf("isosim: SSL enabled, but certificate/key file unspecified") 91 | } 92 | log.Infof("tls: Using Certificate file : %s, Key file: %s\n", certFile, keyFile) 93 | } 94 | 95 | go func() { 96 | 97 | addr := ":" + strconv.Itoa(*httpPort) 98 | if strings.EqualFold(tlsEnabled, "true") { 99 | log.Fatal(http.ListenAndServeTLS(addr, certFile, keyFile, nil)) 100 | } else { 101 | log.Fatal(http.ListenAndServe(addr, nil)) 102 | } 103 | 104 | }() 105 | 106 | wg := sync.WaitGroup{} 107 | wg.Add(1) 108 | 109 | log.Infof("ISO WebSim started!") 110 | wg.Wait() 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /internal/services/crypto/http_transport_test.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "github.com/go-kit/kit/log/logrus" 7 | "github.com/go-kit/kit/transport" 8 | httptransport "github.com/go-kit/kit/transport/http" 9 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 10 | log "github.com/sirupsen/logrus" 11 | "github.com/stretchr/testify/assert" 12 | "io/ioutil" 13 | "net/http" 14 | "net/http/httptest" 15 | "strings" 16 | "testing" 17 | ) 18 | 19 | func init() { 20 | log.SetLevel(log.DebugLevel) 21 | } 22 | 23 | type testHttpHandler struct{} 24 | 25 | func (testHttpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 26 | options := []httptransport.ServerOption{ 27 | httptransport.ServerErrorEncoder(errorEncoder), 28 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logrus.NewLogrusLogger(log.New()))), 29 | } 30 | 31 | s := &serviceImpl{} 32 | switch { 33 | case strings.HasPrefix(req.URL.Path, URLCryptoPinGen): 34 | httptransport.NewServer(pinGenEndpoint(s), pinGenReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 35 | case strings.HasPrefix(req.URL.Path, URLCryptoMacGen): 36 | httptransport.NewServer(macGenEndpoint(s), macGenReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 37 | 38 | default: 39 | log.Errorf("Failed to handle request - " + req.URL.Path) 40 | 41 | } 42 | 43 | } 44 | 45 | func Test_PinGenHTTPService(t *testing.T) { 46 | 47 | s := httptest.NewServer(testHttpHandler{}) 48 | defer s.Close() 49 | 50 | t.Run("PIN generation ISO-0 format", func(t *testing.T) { 51 | 52 | pgr := &PinGenRequest{ 53 | PINClear: "1234", 54 | PINFormat: "ISO0", 55 | PINKey: "AB9292288227277226252525224665FE", 56 | PAN: "4356876509876788", 57 | } 58 | 59 | jsonReq, err := json.Marshal(pgr) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | req, err := http.NewRequest(http.MethodPost, s.URL+URLCryptoPinGen, bytes.NewReader(jsonReq)) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | resp, err := http.DefaultClient.Do(req) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | var data []byte 73 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 74 | t.Fatal(err) 75 | } 76 | defer resp.Body.Close() 77 | t.Log(string(data)) 78 | 79 | pgResp := &PinGenResponse{} 80 | if err = json.Unmarshal(data, pgResp); err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | assert.Equal(t, "B4BF8522DFFB6FFB", pgResp.PinBlock) 85 | 86 | }) 87 | 88 | t.Run("MAC Generation X9.19", func(t *testing.T) { 89 | 90 | pgr := &MacGenRequest{ 91 | MacAlgo: isov2.ANSIX9_19, 92 | MacKey: "76850752AD7307ADE554D06D3BA73279", 93 | MacData: "8155ADCC76B2FB0064F2C40037710477CE13C4BF75FD3DADF13B6D137AC1B915", 94 | } 95 | 96 | jsonReq, err := json.Marshal(pgr) 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | 101 | req, err := http.NewRequest(http.MethodPost, s.URL+URLCryptoMacGen, bytes.NewReader(jsonReq)) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | resp, err := http.DefaultClient.Do(req) 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | var data []byte 110 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 111 | t.Fatal(err) 112 | } 113 | defer resp.Body.Close() 114 | t.Log(string(data)) 115 | 116 | macGenResponse := &MacGenResponse{} 117 | if err = json.Unmarshal(data, macGenResponse); err != nil { 118 | t.Fatal(err) 119 | } 120 | 121 | assert.Equal(t, "B2A45602664C486F", macGenResponse.Mac) 122 | 123 | }) 124 | 125 | } 126 | -------------------------------------------------------------------------------- /internal/services/crypto/endpoint.go: -------------------------------------------------------------------------------- 1 | package crypto 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "github.com/go-kit/kit/endpoint" 9 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 10 | log "github.com/sirupsen/logrus" 11 | "isosim/internal/services/data" 12 | "strings" 13 | ) 14 | 15 | type PinGenRequest struct { 16 | PINClear string `yaml:"pin_clear",json:"pin_clear"` 17 | PINFormat isov2.PinFormat `yaml:"pin_format",json:"pin_format"` 18 | PINKey string `yaml:"pin_key",json:"pin_key"` 19 | PAN string `yaml:"pan",json:"pan"` 20 | } 21 | 22 | type PinGenResponse struct { 23 | PinBlock string `yaml:"pin_block",json:"pin_block"` 24 | Err error `json:"-"` 25 | } 26 | 27 | func (pgr PinGenResponse) Failed() error { 28 | return pgr.Err 29 | } 30 | 31 | type MacGenRequest struct { 32 | MacAlgo isov2.MacAlgo `json:"mac_algo"` 33 | MacKey string `json:"mac_key"` 34 | MacData string `json:"mac_data"` 35 | 36 | SpecID int `json:"spec_id"` 37 | MsgID int `json:"msg_id"` 38 | ParsedFields []*data.JsonFieldDataRep `json:"parsed_fields"` 39 | } 40 | 41 | type MacGenResponse struct { 42 | Mac string `yaml:"mac",json:"mac"` 43 | Err error `json:"-"` 44 | } 45 | 46 | func (mgr MacGenResponse) Failed() error { 47 | return mgr.Err 48 | } 49 | 50 | func pinGenEndpoint(s Service) endpoint.Endpoint { 51 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 52 | 53 | req := request.(PinGenRequest) 54 | key, err := hex.DecodeString(req.PINKey) 55 | if err != nil { 56 | return PinGenResponse{Err: err}, nil 57 | } 58 | 59 | if pb, err := s.GeneratePin(req.PINFormat, req.PINClear, req.PAN, key); err != nil { 60 | return PinGenResponse{Err: err}, nil 61 | } else { 62 | return PinGenResponse{ 63 | PinBlock: strings.ToUpper(hex.EncodeToString(pb)), 64 | Err: nil, 65 | }, nil 66 | } 67 | } 68 | } 69 | 70 | func macGenEndpoint(s Service) endpoint.Endpoint { 71 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 72 | 73 | req := request.(MacGenRequest) 74 | var macData []byte 75 | fmt.Println(req) 76 | if req.MacData != "" { 77 | macData, err = hex.DecodeString(req.MacData) 78 | if err != nil { 79 | log.Error("Failed to decode macData", err) 80 | return MacGenResponse{Err: err}, nil 81 | } 82 | } else { 83 | //parse from fields 84 | spec := isov2.SpecByID(req.SpecID) 85 | if spec == nil { 86 | return MacGenResponse{Err: fmt.Errorf("isosim: Invalid specID : %d", req.SpecID)}, nil 87 | } 88 | msg := spec.MessageByID(req.MsgID) 89 | if msg == nil { 90 | return MacGenResponse{Err: fmt.Errorf("isosim: Invalid msgID : %d", req.MsgID)}, nil 91 | } 92 | 93 | jsonStr, err := json.Marshal(req.ParsedFields) 94 | if err != nil { 95 | return MacGenResponse{Err: err}, nil 96 | } 97 | 98 | if parsedMsg, err := msg.ParseJSON(string(jsonStr)); err != nil { 99 | return MacGenResponse{Err: err}, nil 100 | } else { 101 | macData, _, err = isov2.FromParsedMsg(parsedMsg).Assemble() 102 | if err != nil { 103 | return MacGenResponse{Err: err}, nil 104 | } 105 | } 106 | } 107 | 108 | if pb, err := s.GenerateMac(req.MacAlgo, req.MacKey, macData); err != nil { 109 | log.Error("Failed to generate Mac", err) 110 | return MacGenResponse{Err: err}, nil 111 | } else { 112 | log.Debug("Generated MAC = ", strings.ToUpper(hex.EncodeToString(pb))) 113 | return MacGenResponse{ 114 | Mac: strings.ToUpper(hex.EncodeToString(pb)), 115 | Err: nil, 116 | }, nil 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /internal/services/data/json_field_rep.go: -------------------------------------------------------------------------------- 1 | // package data contains types and functions related to JSON representation of 2 | // specs/messages 3 | package data 4 | 5 | import ( 6 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 7 | ) 8 | 9 | // JsonFieldInfoRep is a field info that is used in the front end application (sent as a result of 10 | // API calls) 11 | type JsonFieldInfoRep struct { 12 | Name string 13 | ID int 14 | ParentId int 15 | Children []*JsonFieldInfoRep 16 | Position int 17 | Type string 18 | MinSize int 19 | MaxSize int 20 | ContentType string 21 | FixedSize int 22 | LengthIndicatorSize int 23 | DataEncoding string 24 | LengthEncoding string 25 | Padding string 26 | GenType string 27 | PinGenProps *isov2.PinGenProps 28 | MacGenProps *isov2.MacGenProps 29 | Hint isov2.Hint 30 | } 31 | 32 | // JsonFieldDataRep is the representation of a field's data 33 | type JsonFieldDataRep struct { 34 | ID int 35 | Name string 36 | Value string 37 | } 38 | 39 | // TCResponseFieldDataRep is the representation of a response field's data 40 | // and comparison operator 41 | type TCResponseFieldDataRep struct { 42 | ID int 43 | Name string 44 | Value string 45 | CompareOp string 46 | } 47 | 48 | type JsonMessageTemplate struct { 49 | Fields []*JsonFieldInfoRep 50 | } 51 | 52 | func newJsonFieldTemplate(field *isov2.Field) *JsonFieldInfoRep { 53 | 54 | jFieldInfo := &JsonFieldInfoRep{ 55 | Name: field.Name, 56 | ID: field.ID, 57 | Children: make([]*JsonFieldInfoRep, 0, 10), 58 | Position: field.Position, 59 | DataEncoding: field.DataEncoding.AsString(), 60 | Padding: string(field.Padding), 61 | GenType: field.ValueGeneratorType, 62 | PinGenProps: field.PinGenProps, 63 | MacGenProps: field.MacGenProps, 64 | Hint: field.Hint, 65 | } 66 | 67 | jFieldInfo.Type = string(field.Type) 68 | 69 | switch field.Type { 70 | case isov2.BitmappedType: 71 | 72 | case isov2.FixedType: 73 | jFieldInfo.Type = "Fixed" 74 | jFieldInfo.FixedSize = field.Size 75 | if len(field.Constraints.ContentType) > 0 { 76 | jFieldInfo.ContentType = field.Constraints.ContentType 77 | } else { 78 | jFieldInfo.ContentType = isov2.ContentTypeAny 79 | } 80 | 81 | case isov2.VariableType: 82 | 83 | jFieldInfo.LengthIndicatorSize = field.LengthIndicatorSize 84 | jFieldInfo.LengthEncoding = field.LengthIndicatorEncoding.AsString() 85 | if len(field.Constraints.ContentType) > 0 { 86 | jFieldInfo.ContentType = field.Constraints.ContentType 87 | } else { 88 | jFieldInfo.ContentType = isov2.ContentTypeAny 89 | } 90 | 91 | jFieldInfo.MinSize = field.Constraints.MinSize 92 | jFieldInfo.MaxSize = field.Constraints.MaxSize 93 | 94 | } 95 | 96 | if field.HasChildren() { 97 | for _, childField := range field.Children { 98 | childJsonFieldTemplate := newJsonFieldTemplate(childField) 99 | childJsonFieldTemplate.ParentId = field.ID 100 | childJsonFieldTemplate.Position = childField.Position 101 | jFieldInfo.Children = append(jFieldInfo.Children, childJsonFieldTemplate) 102 | } 103 | 104 | } 105 | 106 | return jFieldInfo 107 | 108 | } 109 | 110 | func NewJsonMessageTemplate(msg *isov2.Message) *JsonMessageTemplate { 111 | 112 | jsonMsgTemplate := &JsonMessageTemplate{Fields: make([]*JsonFieldInfoRep, 0, 10)} 113 | for _, field := range msg.Fields { 114 | jsonFieldTemplate := newJsonFieldTemplate(field) 115 | jsonMsgTemplate.Fields = append(jsonMsgTemplate.Fields, jsonFieldTemplate) 116 | 117 | } 118 | 119 | return jsonMsgTemplate 120 | 121 | } 122 | -------------------------------------------------------------------------------- /internal/iso/server/iso_server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | netutil "github.com/rkbalgi/libiso/net" 7 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/stretchr/testify/assert" 10 | "isosim/internal/db" 11 | "strconv" 12 | "testing" 13 | ) 14 | 15 | func Test_IsoServer_MessageProcessing(t *testing.T) { 16 | 17 | log.SetLevel(log.InfoLevel) 18 | 19 | if err := db.Init("../../../test/testdata/appdata"); err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | if err := isov2.ReadSpecs("../../../test/testdata/specs"); err != nil { 24 | t.Fatal(err) 25 | } 26 | specName := "Iso8583-MiniSpec" 27 | spec := isov2.SpecByName(specName) 28 | if spec == nil { 29 | t.Fatal("No such spec - " + specName) 30 | } 31 | 32 | specId := spec.ID 33 | msgId := spec.MessageByName("1100").ID 34 | 35 | strSpecId := strconv.Itoa(specId) 36 | dataSets, err := db.DataSetManager().GetAll(strSpecId, strconv.Itoa(msgId)) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | if len(dataSets) == 0 { 41 | t.Fatalf("No datasets defined for spec/msg - %d:%d\n", specId, msgId) 42 | } 43 | 44 | for _, ds := range dataSets { 45 | t.Log(ds) 46 | } 47 | 48 | defs, err := db.DataSetManager().ServerDefinitions(strSpecId) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if len(defs) == 0 { 53 | t.Fatalf("No server definitions for spec - %s/%d", specName, specId) 54 | } 55 | defName := "IsoMiniSpec_Server_01.srvdef.json" 56 | 57 | if err := Start(strSpecId, defName, 6665, "2i"); err != nil { 58 | t.Fatal(err) 59 | } 60 | defer Stop(defName) 61 | 62 | var dsData []byte 63 | if dsData, err = db.DataSetManager().Get(strSpecId, strconv.Itoa(msgId), "TC_ActionCode_100"); err != nil { 64 | t.Fatal(err) 65 | } 66 | ncc := netutil.NewNetCatClient("localhost:6665", netutil.Mli2i) 67 | if err := ncc.OpenConnection(); err != nil { 68 | t.Fatal(err) 69 | } 70 | var parsedMsg *isov2.ParsedMsg 71 | t.Log(string(dsData)) 72 | 73 | tc := &db.TestCase{} 74 | if err := json.Unmarshal(dsData, tc); err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | var reqData []byte 79 | if reqData, err = json.Marshal(tc.ReqData); err != nil { 80 | t.Fatal(err) 81 | } 82 | 83 | if parsedMsg, err = spec.MessageByName("1100").ParseJSON(string(reqData)); err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | isoReqMsg := isov2.FromParsedMsg(parsedMsg) 88 | 89 | t.Run("with amount 900", func(t *testing.T) { 90 | isoReqMsg.Bitmap().Set(4, "000000000900") 91 | sendAndVerify(t, ncc, spec, isoReqMsg, "100") 92 | 93 | }) 94 | t.Run("with amount 200", func(t *testing.T) { 95 | isoReqMsg := isov2.FromParsedMsg(parsedMsg) 96 | isoReqMsg.Bitmap().Set(4, "000000000200") 97 | sendAndVerify(t, ncc, spec, isoReqMsg, "200") 98 | 99 | }) 100 | 101 | t.Run("with amount 100", func(t *testing.T) { 102 | isoReqMsg := isov2.FromParsedMsg(parsedMsg) 103 | isoReqMsg.Bitmap().Set(4, "000000000100") 104 | sendAndVerify(t, ncc, spec, isoReqMsg, "000") 105 | 106 | }) 107 | 108 | } 109 | 110 | func sendAndVerify(t *testing.T, ncc *netutil.NetCatClient, spec *isov2.Spec, isoReqMsg *isov2.Iso, expectedF39 string) { 111 | 112 | data, _, err := isoReqMsg.Assemble() 113 | if err != nil { 114 | t.Fatal(err) 115 | } 116 | 117 | t.Log("Writing to ISO server .. \n" + hex.Dump(data)) 118 | ncc.Write(data) 119 | responseData, err := ncc.ReadNextPacket() 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | t.Log("Response = \n" + hex.Dump(responseData)) 124 | 125 | pResponseMsg, err := spec.MessageByName("1100").Parse(responseData) 126 | if err != nil { 127 | t.Fatal(err) 128 | } 129 | isoResponseMsg := isov2.FromParsedMsg(pResponseMsg) 130 | assert.Equal(t, expectedF39, isoResponseMsg.Bitmap().Get(39).Value()) 131 | 132 | } 133 | -------------------------------------------------------------------------------- /test/testdata/specs/isoSpecs.spec: -------------------------------------------------------------------------------- 1 | # . is a special character and cannot appear between field names, spec names etc. 2 | # field names within a spec are required to be unique 3 | # Message Type (or can also be called MTI) and Bitmap are special fields and their names should'nt be changed 4 | # 5 | # 6 | ### 7 | #specDef := spec.{specName}.{Id}=UniqueSpecId 8 | #msgDef :={UniqueSpecId|SpecName}.{MessageName}.Id={UniqueMsgIdInSpec} 9 | ### 10 | #fieldFormat := {fieldDef}={fieldSpecification} 11 | #fieldDef := {specId|specName}.{msgId|msgName}.{fieldName}[.{positionInParent}.{childFieldName}] 12 | #fieldSpefication (fixed size fields) := {fieldId}.{fieldType}.{fieldEncoding}.{sizeSpec}[.constraints] 13 | #sizeSpec := size:[0-9]+ 14 | #fieldSpefication (variable size fields) := {fieldType}.{lengthEncoding}.{dataEncoding}.{lengthEncodingSizeSpec}[.constraints} 15 | #lengthEncodingSizeSpec := size:[0-9]+ 16 | #constraints:=constraints'{' [content:{Numeric|AlphaNumeric}];[minSize:[0-9]+];[maxSize:[0-9]+]'}' 17 | ## 18 | # 19 | # 20 | ### 21 | #### 22 | #TestSpec 23 | ### 24 | #### 25 | spec.TestSpec.Id=1 26 | 1.Default Message.Id=1 27 | #### 28 | 1.1.Message Type.1=fixed.ascii.size:4 29 | 1.1.Bitmap.2=bitmap.binary 30 | 1.1.Bitmap.2.Fixed2_ASCII.3=fixed.ascii.size:3.constraints{content:Numeric;} 31 | 1.1.2.3.Fixed3_EBCDIC.4=fixed.ebcdic.size:3.constraints{content:Alpha;} 32 | 1.1.Bitmap.4.Fixed4_BCD.5=fixed.bcd.size:3 33 | 1.1.Bitmap.5.Fixed5_BINARY.6=fixed.binary.size:3 34 | # 35 | ## An example of a fixed field with embedded/nested subfields 36 | 1.1.2.6.FxdField6_WithSubFields.7=fixed.ascii.size:8 37 | 1.1.7.1.SF6_1.8=fixed.ascii.size:4 38 | 1.1.8.1.SF6_1_1.9=fixed.ascii.size:2 39 | 1.1.SF6_1.1.SF6_1_2.10=fixed.ascii.size:2 40 | 1.1.FxdField6_WithSubFields.2.SF6_2.11=fixed.ascii.size:2 41 | 1.1.FxdField6_WithSubFields.3.SF6_3.12=fixed.ascii.size:2 42 | # 43 | ## An example of a variable field with embedded/nested subfields 44 | 1.1.Bitmap.7.VarField7_WithSubFields.13=variable.ascii.binary.size:2 45 | 1.1.VarField7_WithSubFields.1.SF7_1.14=fixed.ascii.size:5 46 | 1.1.VarField7_WithSubFields.2.SF7_2.15=variable.bcd.ascii.size:2 47 | 1.1.VarField7_WithSubFields.3.SF7_3.16=fixed.ascii.size:5 48 | # 49 | # Rest of the fields 50 | 1.1.Bitmap.55.VarField55_BCD_BINARY.17=variable.bcd.binary.size:2 51 | 1.1.Bitmap.56.VarField56_BCD_ASCII.18=variable.bcd.ascii.size:2 52 | 1.1.Bitmap.57.VarField57_BINARY_EBCDIC.19=variable.binary.ebcdic.size:2 53 | 1.1.Bitmap.58.VarField58_EBCDIC_EBCDIC.20=variable.ebcdic.ebcdic.size:2 54 | 1.1.Bitmap.59.VarField59_EBCDIC_ASCII.21=variable.ebcdic.ascii.size:2 55 | 1.1.Bitmap.60.VarField60_EBCDIC_BINARY.22=variable.ebcdic.binary.size:3.constraints{minSize:8;maxSize:12;} 56 | 1.1.Bitmap.91.VarField91_ASCII_EBCDIC.23=variable.ascii.ebcdic.size:2.constraints{minSize:5;maxSize:15;content:Alpha;} 57 | # 58 | ### 59 | #### 60 | #Iso8583-MiniSpec 61 | #### 62 | ### 63 | spec.Iso8583-MiniSpec.Id=2 64 | 2.1100.Id=1 65 | Iso8583-MiniSpec.1420.Id=2 66 | ### 67 | 2.1.Message Type.1=fixed.ascii.size:4 68 | 2.1.Bitmap.2=bitmap.binary 69 | 2.1.Bitmap.2.PAN.3=variable.ebcdic.ebcdic.size:2.constraints{content:Numeric;} 70 | 2.1.Bitmap.3.Processing Code.4=fixed.ebcdic.size:6.constraints{content:Numeric;} 71 | 2.1.2.4.Amount.5=fixed.ascii.size:12 72 | 2.1.2.11.STAN.6=fixed.ascii.size:6 73 | 2.1.2.38.Approval Code.7=fixed.ebcdic.size:6 74 | 2.1.Bitmap.39.Action Code.8=fixed.ascii.size:3.constraints{content:Numeric;} 75 | # 76 | 2.2.Message Type.1=fixed.ascii.size:4 77 | 2.2.Bitmap.2=bitmap.binary 78 | 2.2.Bitmap.2.PAN.3=variable.ebcdic.ebcdic.size:2.constraints{content:Numeric;} 79 | 2.2.Bitmap.3.Processing Code.4=fixed.ebcdic.size:6.constraints{content:Numeric;} 80 | 2.2.Bitmap.4.Amount.5=fixed.ascii.size:12 81 | 2.2.2.11.STAN.6=fixed.ascii.size:6 82 | 2.2.Bitmap.37.Retrieval Reference Number.7=variable.ascii.ascii.size:2 83 | 2.2.2.38.Approval Code.8=fixed.ebcdic.size:6 84 | 2.2.Bitmap.39.Action Code.9=fixed.ascii.size:3.constraints{content:Numeric;} 85 | # -------------------------------------------------------------------------------- /internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "github.com/google/uuid" 10 | log "github.com/sirupsen/logrus" 11 | bolt "go.etcd.io/bbolt" 12 | "isosim/internal/services/data" 13 | "time" 14 | ) 15 | 16 | const timeFormat = "2006-01-02T15" 17 | 18 | // DbMessage is an entry of a request/response that will be persisted to 19 | // storage 20 | type DbMessage struct { 21 | ID string `json:"id"` 22 | SpecID int `json:"spec_id"` 23 | MsgID int `json:"msg_id"` 24 | HostAddr string `json:"host_addr"` 25 | 26 | LogTS string `json:"log_ts"` 27 | RequestTS int64 `json:"request_ts"` 28 | ResponseTS int64 `json:"response_ts"` 29 | 30 | RequestMsg string `json:"request_msg"` 31 | ParsedRequestMsg []data.JsonFieldDataRep `json:"parsed_request_msg"` 32 | ResponseMsg string `json:"response_msg"` 33 | ParsedResponseMsg []data.JsonFieldDataRep `json:"parsed_response_msg"` 34 | } 35 | 36 | // Write writes a message into bolt (into a hourly bucket) 37 | func Write(dbMsg DbMessage) error { 38 | 39 | var err error 40 | 41 | if dbMsg.MsgID == 0 || dbMsg.SpecID == 0 { 42 | return errors.New("isosim: Invalid SpecID/MsgID") 43 | } 44 | 45 | uniqueID, err := uuid.NewUUID() 46 | if err != nil { 47 | log.Warn("Failed to generate UUID for DbMessage", err) 48 | } else { 49 | dbMsg.LogTS = time.Now().Format(time.RFC3339) 50 | dbMsg.ID = uniqueID.String() 51 | } 52 | var jsonData []byte 53 | 54 | if jsonData, err = json.Marshal(dbMsg); err != nil { 55 | return err 56 | } 57 | 58 | tx, err := bdb.Begin(true) 59 | if err != nil { 60 | return err 61 | } 62 | defer tx.Rollback() 63 | 64 | bkt, err := tx.CreateBucketIfNotExists([]byte(fmt.Sprintf("%d_%d", dbMsg.SpecID, dbMsg.MsgID))) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | //hourly buckets 70 | tBkt, err := bkt.CreateBucketIfNotExists([]byte(time.Now().Format(timeFormat))) 71 | if err != nil { 72 | return err 73 | } 74 | bSeq := make([]byte, 8) 75 | binary.BigEndian.PutUint64(bSeq, tBkt.Sequence()) 76 | if err = tBkt.Put(bSeq, jsonData); err != nil { 77 | return err 78 | } 79 | _, err = tBkt.NextSequence() 80 | if err != nil { 81 | return err 82 | } 83 | if err := tx.Commit(); err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | 89 | } 90 | 91 | // ReadLast reads last 'n' messages for spec and msg 92 | func ReadLast(specID int, msgID int, n int) ([]string, error) { 93 | 94 | res := make([]string, 0) 95 | 96 | err := bdb.View(func(tx *bolt.Tx) error { 97 | 98 | bktName := fmt.Sprintf("%d_%d", specID, msgID) 99 | bkt := tx.Bucket([]byte(bktName)) 100 | if bkt == nil { 101 | log.Debugf("No bucket for spec/msg - %d:%d", specID, msgID) 102 | return nil 103 | } 104 | 105 | retrieved := 0 106 | now := time.Now() 107 | ctx, cancelFunc := context.WithTimeout(context.Background(), 2*time.Second) 108 | defer cancelFunc() 109 | for { 110 | //hourly buckets 111 | bktName := now.Format(timeFormat) 112 | tBkt := bkt.Bucket([]byte(bktName)) 113 | if tBkt != nil { 114 | //start from the last on the latest bucket 115 | c := tBkt.Cursor() 116 | k, v := c.Last() 117 | 118 | if k == nil || v == nil { 119 | now = now.Add(-1 * time.Hour) 120 | continue 121 | } 122 | for len(res) < n { 123 | res = append(res, string(v)) 124 | retrieved++ 125 | if len(res) == n { 126 | return nil 127 | } 128 | k, v = c.Prev() 129 | if k == nil || v == nil { 130 | // nothing more in this hour, 131 | // break out of this loop 132 | goto PREV_HOUR 133 | } 134 | } 135 | 136 | } 137 | PREV_HOUR: 138 | // we cannot keep looking endlessly 139 | select { 140 | case <-ctx.Done(): 141 | return nil 142 | default: 143 | break 144 | } 145 | now = now.Add(-1 * time.Hour) 146 | } 147 | }) 148 | 149 | return res, err 150 | 151 | } 152 | -------------------------------------------------------------------------------- /internal/services/handlers/misc_handler.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/rkbalgi/libiso/hsm" 6 | "github.com/rkbalgi/libiso/net" 7 | log "github.com/sirupsen/logrus" 8 | "isosim/internal/iso" 9 | 10 | "net/http" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | ) 16 | 17 | var thalesHsm *hsm.ThalesHsm 18 | var lock sync.Mutex 19 | 20 | func init() { 21 | thalesHsm = nil 22 | 23 | } 24 | 25 | func AddMiscHandlers() { 26 | 27 | http.HandleFunc("/iso/misc", func(rw http.ResponseWriter, req *http.Request) { 28 | 29 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, "misc.html")) 30 | 31 | }) 32 | 33 | //for starting a hsm instance 34 | http.HandleFunc("/iso/misc/thales/start", func(rw http.ResponseWriter, req *http.Request) { 35 | 36 | lock.Lock() 37 | defer lock.Unlock() 38 | if thalesHsm != nil { 39 | sendError(rw, "HSM already running. Please stop before trying again.") 40 | return 41 | } 42 | 43 | if err := req.ParseForm(); err != nil { 44 | sendError(rw, err.Error()) 45 | } 46 | 47 | port := req.Form.Get("hsmPort") 48 | log.Debugln("Request to start HSM @ port = ", port) 49 | intPort, err := strconv.Atoi(port) 50 | if port == "" || err != nil { 51 | rw.WriteHeader(500) 52 | rw.Write([]byte("Please provide a valid HSM port")) 53 | return 54 | } 55 | 56 | thalesHsm = hsm.NewThalesHsm("", intPort, hsm.AsciiEncoding) 57 | go func() { thalesHsm.Start() }() 58 | }) 59 | 60 | //for stopping a hsm instance 61 | http.HandleFunc("/iso/misc/thales/stop", func(rw http.ResponseWriter, req *http.Request) { 62 | 63 | lock.Lock() 64 | defer lock.Unlock() 65 | 66 | if thalesHsm == nil { 67 | rw.WriteHeader(500) 68 | rw.Write([]byte("No HSM running.")) 69 | } else { 70 | thalesHsm.Stop() 71 | thalesHsm = nil 72 | } 73 | }) 74 | 75 | //for stopping a hsm instance 76 | http.HandleFunc("/iso/misc/sendraw", func(rw http.ResponseWriter, req *http.Request) { 77 | 78 | if err := req.ParseForm(); err != nil { 79 | sendError(rw, err.Error()) 80 | } 81 | 82 | pHost := req.PostForm.Get("host") 83 | pPort := req.PostForm.Get("port") 84 | pMli := req.PostForm.Get("mli") 85 | pData := req.PostForm.Get("data") 86 | 87 | if pHost == "" || pPort == "" || pMli == "" || pData == "" { 88 | sendError(rw, "Required parameters 'host', 'port', 'mli' or 'data' missing.") 89 | return 90 | } 91 | 92 | if pMli != "2I" && pMli != "2E" { 93 | sendError(rw, "Invalid mli = "+pMli) 94 | return 95 | } 96 | 97 | log.Debugln("[send-raw] params = ", pHost+":"+pPort, " mli= ", pMli, " data = ", pData) 98 | 99 | data, err := hex.DecodeString(pData) 100 | if err != nil { 101 | sendError(rw, "Invalid data. Error = "+err.Error()) 102 | return 103 | } 104 | 105 | mli := net.Mli2i 106 | if pMli == "2E" { 107 | mli = net.Mli2e 108 | } 109 | 110 | client := net.NewNetCatClient(pHost+":"+pPort, mli) 111 | err = client.OpenConnection() 112 | 113 | if err != nil { 114 | sendError(rw, "Failed to open connection to target. "+err.Error()) 115 | return 116 | } 117 | 118 | client.Write(data) 119 | response, err := client.ReadNextPacket() 120 | if err != nil { 121 | client.Close() 122 | sendError(rw, "Error reading. Error = "+err.Error()) 123 | return 124 | } 125 | 126 | log.Debugln("[send-raw] Response received = " + hex.EncodeToString(data)) 127 | client.Close() 128 | rw.Write([]byte(hex.EncodeToString(response))) 129 | 130 | }) 131 | 132 | //for static resources 133 | http.HandleFunc("/iso/misc/", func(rw http.ResponseWriter, req *http.Request) { 134 | 135 | if strings.HasSuffix(req.RequestURI, ".css") || 136 | strings.HasSuffix(req.RequestURI, ".js") { 137 | 138 | i := strings.LastIndex(req.RequestURI, "/") 139 | fileName := req.RequestURI[i+1 : len(req.RequestURI)] 140 | //log.Print("Requested File = " + fileName) 141 | http.ServeFile(rw, req, filepath.Join(iso.HTMLDir, fileName)) 142 | 143 | } 144 | 145 | }) 146 | 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/rkbalgi/isosim)](https://goreportcard.com/report/github.com/rkbalgi/isosim) 2 | [![codecov](https://codecov.io/gh/rkbalgi/isosim/branch/master/graph/badge.svg)](https://codecov.io/gh/rkbalgi/isosim) 3 | [![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/rkbalgi/isosim?tab=doc) 4 | ![build](https://github.com/rkbalgi/isosim/workflows/build/badge.svg) 5 | ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/rkbalgi/isosim?include_prereleases&style=flat) 6 | ![Docker Pulls](https://img.shields.io/docker/pulls/rkbalgi/isosim?color=%23FF6528&label=docker%20pulls) 7 | 8 | # ISO WebSim 9 | A very short screencast - [https://youtu.be/vSRZ_nzU-Jg](https://youtu.be/vSRZ_nzU-Jg) 10 | 11 | Pulling a [docker](https://github.com/rkbalgi/isosim/wiki/Running-on-Docker) image would be the quickest way to run without any hassles of building/configuring etc! 12 | 13 | ![](https://github.com/rkbalgi/isosim/wiki/home_collage.png) 14 | 15 | 16 | Iso Websim is a ISO8583 simulator built using [Go](http://golang.org), [React](https://reactjs.org/), [Material-UI](https://material-ui.com/) and 17 | other amazing open source libraries. 18 | 19 | ## Features - 20 | * A mechanism to define ISO specifications 21 | * ASCII, EBCDIC, BCD and BINARY encoding for fields 22 | * Fixed, Variable, Bitmapped fields 23 | * Embedded/Nested fields 24 | * Supported MLI's - 2I, 2E, 4I, 4E 25 | * Define and run servers based on specs 26 | * Run servers from the UI or in [standalone mode](https://github.com/rkbalgi/isosim/wiki/Start-standalone-ISO-server-from-command-line) 27 | * Rules to respond to messages based on fields (rules based on amount, currency etc) 28 | * A UI to build and send transactions to servers (as a client) 29 | * Ability to edit fields on UI 30 | * [UI hints](https://github.com/rkbalgi/isosim/wiki/Field-Hints-for-UI) to render special fields 31 | * Client-side validation of fields for content, length 32 | * [PIN](https://github.com/rkbalgi/isosim/wiki/Pin-Field-(DF52)-Generation) and [MAC](https://github.com/rkbalgi/isosim/wiki/MAC-Generation-(DF64-128)) generation 33 | * [Padding](https://github.com/rkbalgi/isosim/wiki/Field-Padding) support 34 | * Save messages that be can be replayed later 35 | * Build [Test Cases](https://github.com/rkbalgi/isosim/wiki/Test-Cases) that can be saved and re-run for regression testing etc 36 | * [Log](https://github.com/rkbalgi/isosim/wiki/Message-History) of past messages 37 | * TLS, [Docker](https://github.com/rkbalgi/isosim/wiki/Running-on-Docker) 38 | 39 | 40 | 41 | Checkout the [wiki](https://github.com/rkbalgi/isosim/wiki) for more details! 42 | 43 | The specifications themselves are defined in yaml file (Check out an example - [iso_specs.yaml](https://github.com/rkbalgi/isosim/blob/master/test/testdata/specs/iso_specs.yaml)) 44 | 45 | The [frontend](https://github.com/rkbalgi/isosim-react-frontend) is bundled with the application and can be accessed at [http://localhost:8080/](http://localhost:8080/) 46 | 47 | 48 | * A quick walkthrough - https://github.com/rkbalgi/isosim/wiki/Test-Examples 49 | 50 | 51 | 52 | ` Please note that this application has been tested only on the chrome browser.` 53 | 54 | ### Usage: 55 | ``` 56 | C:>go run isosim.go -help 57 | -data-dir string 58 | Directory to store messages (data sets). This is a required field. 59 | -html-dir string 60 | Directory that contains any HTML's and js/css files etc. 61 | -http-port int 62 | HTTP/s port to listen on. (default 8080) 63 | -log-level string 64 | Log level - [trace|debug|warn|info|error]. (default "debug") 65 | -specs-dir string 66 | The directory containing the ISO spec definition files. 67 | ``` 68 | 69 | ### Running Iso WebSim 70 | ``` 71 | $> git checkout https://github.com/rkbalgi/isosim.git 72 | $> cd isosim\cmd\isosim 73 | $> go run isosim.go -http-port 8080 -specs-dir ..\..\test\testdata\specs -html-dir ..\..\web -data-dir ..\..\test\testdata\appdata 74 | ``` 75 | Open chrome and hit this URL [http://localhost:8080/](http://localhost:8080/) 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /web/react-fe/build/precache-manifest.c31fdb430857a50a4adec070266b497b.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = (self.__precacheManifest || []).concat([ 2 | { 3 | "revision": "cbaec99d241d60a1b67f79343075f1a5", 4 | "url": "/index.html" 5 | }, 6 | { 7 | "revision": "2aaa4d505b8b5156aaa9", 8 | "url": "/static/css/2.10ffe1ee.chunk.css" 9 | }, 10 | { 11 | "revision": "46daa87f9e9968e88aa3", 12 | "url": "/static/css/main.bbd4fcb0.chunk.css" 13 | }, 14 | { 15 | "revision": "2aaa4d505b8b5156aaa9", 16 | "url": "/static/js/2.5683fb71.chunk.js" 17 | }, 18 | { 19 | "revision": "7d2af499a3e7c8548965362b6dd2f785", 20 | "url": "/static/js/2.5683fb71.chunk.js.LICENSE.txt" 21 | }, 22 | { 23 | "revision": "46daa87f9e9968e88aa3", 24 | "url": "/static/js/main.f4601f26.chunk.js" 25 | }, 26 | { 27 | "revision": "9e78e51408a4d627057a", 28 | "url": "/static/js/runtime-main.d1dafa31.js" 29 | }, 30 | { 31 | "revision": "2d36b1a925432bae7f3c53a340868c6e", 32 | "url": "/static/media/Lato-Regular.2d36b1a9.ttf" 33 | }, 34 | { 35 | "revision": "5f7303c0e7f09925586e218ab8fd9b19", 36 | "url": "/static/media/PTSerif-Regular.5f7303c0.ttf" 37 | }, 38 | { 39 | "revision": "47c22e0adf5e3659bb254daabc61392f", 40 | "url": "/static/media/ShadowsIntoLight-Regular.47c22e0a.ttf" 41 | }, 42 | { 43 | "revision": "5cb7edfceb233100075dc9a1e12e8da3", 44 | "url": "/static/media/roboto-latin-100.5cb7edfc.woff" 45 | }, 46 | { 47 | "revision": "7370c3679472e9560965ff48a4399d0b", 48 | "url": "/static/media/roboto-latin-100.7370c367.woff2" 49 | }, 50 | { 51 | "revision": "f8b1df51ba843179fa1cc9b53d58127a", 52 | "url": "/static/media/roboto-latin-100italic.f8b1df51.woff2" 53 | }, 54 | { 55 | "revision": "f9e8e590b4e0f1ff83469bb2a55b8488", 56 | "url": "/static/media/roboto-latin-100italic.f9e8e590.woff" 57 | }, 58 | { 59 | "revision": "b00849e00f4c2331cddd8ffb44a6720b", 60 | "url": "/static/media/roboto-latin-300.b00849e0.woff" 61 | }, 62 | { 63 | "revision": "ef7c6637c68f269a882e73bcb57a7f6a", 64 | "url": "/static/media/roboto-latin-300.ef7c6637.woff2" 65 | }, 66 | { 67 | "revision": "14286f3ba79c6627433572dfa925202e", 68 | "url": "/static/media/roboto-latin-300italic.14286f3b.woff2" 69 | }, 70 | { 71 | "revision": "4df32891a5f2f98a363314f595482e08", 72 | "url": "/static/media/roboto-latin-300italic.4df32891.woff" 73 | }, 74 | { 75 | "revision": "479970ffb74f2117317f9d24d9e317fe", 76 | "url": "/static/media/roboto-latin-400.479970ff.woff2" 77 | }, 78 | { 79 | "revision": "60fa3c0614b8fb2f394fa29944c21540", 80 | "url": "/static/media/roboto-latin-400.60fa3c06.woff" 81 | }, 82 | { 83 | "revision": "51521a2a8da71e50d871ac6fd2187e87", 84 | "url": "/static/media/roboto-latin-400italic.51521a2a.woff2" 85 | }, 86 | { 87 | "revision": "fe65b8335ee19dd944289f9ed3178c78", 88 | "url": "/static/media/roboto-latin-400italic.fe65b833.woff" 89 | }, 90 | { 91 | "revision": "020c97dc8e0463259c2f9df929bb0c69", 92 | "url": "/static/media/roboto-latin-500.020c97dc.woff2" 93 | }, 94 | { 95 | "revision": "87284894879f5b1c229cb49c8ff6decc", 96 | "url": "/static/media/roboto-latin-500.87284894.woff" 97 | }, 98 | { 99 | "revision": "288ad9c6e8b43cf02443a1f499bdf67e", 100 | "url": "/static/media/roboto-latin-500italic.288ad9c6.woff" 101 | }, 102 | { 103 | "revision": "db4a2a231f52e497c0191e8966b0ee58", 104 | "url": "/static/media/roboto-latin-500italic.db4a2a23.woff2" 105 | }, 106 | { 107 | "revision": "2735a3a69b509faf3577afd25bdf552e", 108 | "url": "/static/media/roboto-latin-700.2735a3a6.woff2" 109 | }, 110 | { 111 | "revision": "adcde98f1d584de52060ad7b16373da3", 112 | "url": "/static/media/roboto-latin-700.adcde98f.woff" 113 | }, 114 | { 115 | "revision": "81f57861ed4ac74741f5671e1dff2fd9", 116 | "url": "/static/media/roboto-latin-700italic.81f57861.woff" 117 | }, 118 | { 119 | "revision": "da0e717829e033a69dec97f1e155ae42", 120 | "url": "/static/media/roboto-latin-700italic.da0e7178.woff2" 121 | }, 122 | { 123 | "revision": "9b3766ef4a402ad3fdeef7501a456512", 124 | "url": "/static/media/roboto-latin-900.9b3766ef.woff2" 125 | }, 126 | { 127 | "revision": "bb1e4dc6333675d11ada2e857e7f95d7", 128 | "url": "/static/media/roboto-latin-900.bb1e4dc6.woff" 129 | }, 130 | { 131 | "revision": "28f9151055c950874d2c6803a39b425b", 132 | "url": "/static/media/roboto-latin-900italic.28f91510.woff" 133 | }, 134 | { 135 | "revision": "ebf6d1640ccddb99fb49f73c052c55a8", 136 | "url": "/static/media/roboto-latin-900italic.ebf6d164.woff2" 137 | } 138 | ]); -------------------------------------------------------------------------------- /internal/iso/server/iso_msg_process.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "fmt" 7 | iso "github.com/rkbalgi/libiso/v2/iso8583" 8 | log "github.com/sirupsen/logrus" 9 | "isosim/internal/services/data" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | //ErrNoMessageSelected is a error which implies that a message wasn't selected in the UI 15 | var ErrNoMessageSelected = errors.New("isosim: no message selected") 16 | 17 | // ErrNoProcessingConditionMatch indicates that the message couldn't be processed because 18 | // none of the processing conditions matched 19 | var ErrNoProcessingConditionMatch = errors.New("isosim: no processing conditions matched") 20 | 21 | // ProcessMsg processes a the incoming message using server definition and 22 | // returns the response as a []byte 23 | func processMsg(data []byte, pServerDef *data.ServerDef) ([]byte, error) { 24 | 25 | //var processed bool= false 26 | for _, msgSelectionConfig := range pServerDef.MsgSelectionConfigs { 27 | 28 | msgSelectorData := data[msgSelectionConfig.BytesFrom:msgSelectionConfig.BytesTo] 29 | msgSelector := strings.ToUpper(hex.EncodeToString(msgSelectorData)) 30 | expectedVal := strings.ToUpper(msgSelectionConfig.BytesValue) 31 | log.Debugln("Comparing ", msgSelector, " to ", expectedVal) 32 | if msgSelector == expectedVal { 33 | responseData, processed, err := processInternal(data, pServerDef, msgSelectionConfig) 34 | if processed && err == nil { 35 | return responseData, nil 36 | } 37 | if err != nil { 38 | return nil, err 39 | } 40 | } 41 | 42 | } 43 | 44 | return nil, ErrNoMessageSelected 45 | 46 | } 47 | 48 | func processInternal(data []byte, pServerDef *data.ServerDef, msgSelConfig data.MsgSelectionConfig) ([]byte, bool, error) { 49 | 50 | var isoSpec = iso.SpecByID(pServerDef.SpecId) 51 | log.Trace("Selected Spec", pServerDef.SpecId, "Selected Message - ", msgSelConfig.Msg) 52 | msg := isoSpec.MessageByID(msgSelConfig.Msg) 53 | parsedMsg, err := msg.Parse(data) 54 | if err != nil { 55 | return nil, false, fmt.Errorf("isosim: parsing error :%w", err) 56 | } 57 | 58 | isoMsg := iso.FromParsedMsg(parsedMsg) 59 | isoMsg.Bitmap() 60 | 61 | for _, pc := range msgSelConfig.ProcessingConditions { 62 | 63 | fieldData := parsedMsg.GetById(pc.FieldId) 64 | if fieldData == nil { 65 | log.Debugln("Processing Condition failed. Field not present - ", pc.FieldId) 66 | return nil, false, nil 67 | } 68 | 69 | log.Debugln("[", pc.MatchConditionType, "] ", " Comparing field value ..", fieldData.Value(), " to ", pc.FieldValue) 70 | 71 | switch pc.MatchConditionType { 72 | 73 | case "Any": 74 | 75 | log.Debugln("[", pc.MatchConditionType+"] Processing condition matched.") 76 | buildResponse(isoMsg, &pc) 77 | response, _, err := isoMsg.Assemble() 78 | return response, true, err 79 | 80 | case "StringEquals": 81 | { 82 | 83 | if fieldData.Value() == pc.FieldValue { 84 | log.Debugln("[", pc.MatchConditionType+"] Processing condition matched.") 85 | //set the response fields 86 | buildResponse(isoMsg, &pc) 87 | response, _, err := isoMsg.Assemble() 88 | return response, true, err 89 | } 90 | 91 | } 92 | 93 | case "IntEquals": 94 | fallthrough 95 | case "IntGt": 96 | fallthrough 97 | case "IntLt": 98 | 99 | { 100 | 101 | compareTo, err := strconv.Atoi(pc.FieldValue) 102 | if err != nil { 103 | log.Print("Processing condition for field ", fieldData.Field.Name, " should be integer!") 104 | return nil, false, err 105 | } 106 | compareFrom, err := strconv.Atoi(fieldData.Value()) 107 | if err != nil { 108 | log.Print("field ", fieldData.Field.Name, " should be integer!") 109 | return nil, false, err 110 | } 111 | 112 | log.Debugln("[", pc.MatchConditionType, "] ", " Comparing int field value ..", compareFrom, " to ", compareTo) 113 | 114 | matched := false 115 | if pc.MatchConditionType == "IntEquals" { 116 | if compareFrom == compareTo { 117 | matched = true 118 | } 119 | } 120 | if pc.MatchConditionType == "IntGt" { 121 | if compareFrom > compareTo { 122 | matched = true 123 | } 124 | } 125 | if pc.MatchConditionType == "IntLt" { 126 | if compareFrom < compareTo { 127 | matched = true 128 | } 129 | } 130 | 131 | if matched { 132 | log.Debugln(pc.MatchConditionType + "] Processing condition matched.") 133 | //set the response fields 134 | buildResponse(isoMsg, &pc) 135 | response, _, err := isoMsg.Assemble() 136 | return response, true, err 137 | } 138 | 139 | } 140 | 141 | } 142 | 143 | } 144 | 145 | return nil, false, ErrNoProcessingConditionMatch 146 | 147 | } 148 | -------------------------------------------------------------------------------- /internal/services/handlers/isoserver_handlers.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 6 | log "github.com/sirupsen/logrus" 7 | "io/ioutil" 8 | "isosim/internal/db" 9 | "isosim/internal/iso" 10 | "isosim/internal/iso/server" 11 | "net/http" 12 | "os" 13 | "path/filepath" 14 | "regexp" 15 | "strconv" 16 | ) 17 | 18 | func AddAll() { 19 | 20 | addIsoServerHandlers() 21 | addIsoServerSaveDefHandler() 22 | fetchDefHandler() 23 | startServerHandler() 24 | addGetActiveServersHandler() 25 | stopServerHandler() 26 | 27 | } 28 | 29 | func addIsoServerHandlers() { 30 | 31 | log.Print("Adding ISO server handler .. ") 32 | http.HandleFunc("/iso/v0/server", func(rw http.ResponseWriter, req *http.Request) { 33 | 34 | pattern := "/iso/v0/server" 35 | log.Debugf("Pattern: %s . Requested URI = %s", pattern, req.RequestURI) 36 | 37 | file := filepath.Join(iso.HTMLDir, "iso_server.html") 38 | log.Debugln("Serving file = " + file) 39 | http.ServeFile(rw, req, file) 40 | 41 | }) 42 | 43 | } 44 | 45 | //This function will register a handler that will save incoming server definitions into a file 46 | 47 | func fetchDefHandler() { 48 | 49 | http.HandleFunc("/iso/v0/server/defs/fetch", func(rw http.ResponseWriter, req *http.Request) { 50 | 51 | if err := req.ParseForm(); err != nil { 52 | sendError(rw, err.Error()) 53 | return 54 | } 55 | 56 | strSpecId := req.Form.Get("specId") 57 | if len(strSpecId) == 0 { 58 | sendError(rw, "Invalid or missing parameter 'specId'") 59 | return 60 | } 61 | 62 | serverDefs, err := db.DataSetManager().ServerDefinitions(strSpecId) 63 | if err != nil { 64 | log.Debugln("Server Defs = ", len(serverDefs), serverDefs) 65 | if _, ok := err.(*os.PathError); ok { 66 | specId, err2 := strconv.Atoi(strSpecId) 67 | if sp := isov2.SpecByID(specId); err2 == nil && sp != nil { 68 | sendError(rw, "No definitions for spec - "+sp.Name) 69 | } else { 70 | sendError(rw, "No such spec (specId) - "+strSpecId) 71 | } 72 | return 73 | } 74 | sendError(rw, err.Error()) 75 | return 76 | } 77 | _ = json.NewEncoder(rw).Encode(serverDefs) 78 | 79 | }) 80 | 81 | http.HandleFunc("/iso/v0/server/defs/get", func(rw http.ResponseWriter, req *http.Request) { 82 | 83 | if err := req.ParseForm(); err != nil { 84 | sendError(rw, err.Error()) 85 | return 86 | } 87 | 88 | strSpecId := req.Form.Get("specId") 89 | fileName := req.Form.Get("name") 90 | if len(strSpecId) == 0 || len(fileName) == 0 { 91 | sendError(rw, "Invalid or missing parameter 'specId' or 'name'") 92 | return 93 | } 94 | 95 | serverDef, err := db.DataSetManager().ServerDef(strSpecId, fileName) 96 | log.Debugln("Def = " + string(serverDef)) 97 | 98 | if err != nil { 99 | sendError(rw, err.Error()) 100 | return 101 | } 102 | _, _ = rw.Write(serverDef) 103 | 104 | }) 105 | } 106 | 107 | func addIsoServerSaveDefHandler() { 108 | 109 | http.HandleFunc("/iso/v0/server/defs/save", func(rw http.ResponseWriter, req *http.Request) { 110 | 111 | def, err := ioutil.ReadAll(req.Body) 112 | if err != nil { 113 | sendError(rw, "Error reading data. "+err.Error()) 114 | return 115 | } 116 | 117 | serverDef, err := db.DataSetManager().AddServerDef(string(def)) 118 | if err != nil { 119 | sendError(rw, "Failed. = "+err.Error()) 120 | return 121 | } 122 | _, _ = rw.Write([]byte(serverDef)) 123 | 124 | }) 125 | } 126 | 127 | func startServerHandler() { 128 | 129 | http.HandleFunc("/iso/v0/server/start", func(rw http.ResponseWriter, req *http.Request) { 130 | 131 | log.Debugf("Requested URI = %s\n", req.RequestURI) 132 | 133 | if err := req.ParseForm(); err != nil { 134 | sendError(rw, err.Error()) 135 | return 136 | } 137 | 138 | specId, def, port, mliType := req.Form.Get("specId"), req.Form.Get("def"), req.Form.Get("port"), req.Form.Get("mliType") 139 | matched, _ := regexp.MatchString("^[0-9]+$", port) 140 | if len(port) == 0 || !matched { 141 | sendError(rw, "Invalid Port - "+port) 142 | return 143 | 144 | } 145 | 146 | port_, _ := strconv.Atoi(port) 147 | err := server.Start(specId, def, port_, mliType) 148 | if err != nil { 149 | sendError(rw, err.Error()) 150 | return 151 | } 152 | log.Infof("Server [%s] has been started @ port %s", def, port) 153 | rw.WriteHeader(http.StatusOK) 154 | 155 | }) 156 | } 157 | 158 | func stopServerHandler() { 159 | 160 | http.HandleFunc("/iso/v0/server/stop", func(rw http.ResponseWriter, req *http.Request) { 161 | 162 | log.Debugf("Requested URI = %s\n", req.RequestURI) 163 | 164 | if err := req.ParseForm(); err != nil { 165 | sendError(rw, err.Error()) 166 | return 167 | } 168 | 169 | name := req.Form.Get("name") 170 | 171 | if name == "" { 172 | sendError(rw, "Invalid Server Name - "+name) 173 | return 174 | 175 | } 176 | err := server.Stop(name) 177 | if err != nil { 178 | sendError(rw, err.Error()) 179 | return 180 | } 181 | log.Infof("Server [%s] has been stopped\n", name) 182 | 183 | }) 184 | } 185 | 186 | func addGetActiveServersHandler() { 187 | 188 | http.HandleFunc("/iso/v0/server/active", func(rw http.ResponseWriter, req *http.Request) { 189 | 190 | data := server.ActiveServers() 191 | _, _ = rw.Write([]byte(data)) 192 | 193 | }) 194 | } 195 | 196 | func sendError(rw http.ResponseWriter, errorMsg string) { 197 | log.Debugln("isosim: ISO-Server Error. Error = " + errorMsg) 198 | rw.Header().Set("X-IsoSim-ErrorText", errorMsg) 199 | rw.WriteHeader(http.StatusBadRequest) 200 | _, _ = rw.Write([]byte(errorMsg)) 201 | 202 | } 203 | -------------------------------------------------------------------------------- /web/isosim_styles.css: -------------------------------------------------------------------------------- 1 | #overlay { 2 | / / display: none; 3 | position: absolute; 4 | z-index: 80; 5 | opacity: 0.8; 6 | left: 0px; 7 | top: 0px; 8 | width: 100%; 9 | height: 100% 10 | } 11 | 12 | #navDiv { 13 | position: relative; 14 | / / top: 0 px; 15 | left: 0px; 16 | width: 10%; 17 | height: 100%; 18 | float: left; 19 | / / border-style: groove; 20 | / / border-color: blue; 21 | padding: 2px; 22 | margin: 0px; 23 | z-index: 90; 24 | } 25 | 26 | #headerDiv { 27 | position: relative; 28 | top: 0px; 29 | left: 0px; 30 | height: 15%; 31 | width: 100%; 32 | float: left; 33 | / / border-style: dashed; 34 | / / border-color: red; 35 | z-index: 90; 36 | } 37 | 38 | #bodyDiv { 39 | position: relative; 40 | / / top: 200 px; 41 | / / left: 30 px; 42 | / / height: 80 px; 43 | width: 85%; 44 | float: left; 45 | / / border-style: double; 46 | overflow: hidden; 47 | z-index: 90; 48 | } 49 | 50 | .cl_cursive_heading { 51 | font-family: "Gloria Hallelujah", serif; 52 | font-size: 35px; 53 | } 54 | 55 | .cl_small_text_new { 56 | font-family: 'Lora', serif; 57 | font-size: 14px; 58 | } 59 | 60 | .cl_small_btn { 61 | font-family: 'Lora', serif; 62 | font-size: 10pt; 63 | color: black; 64 | background: #33d6ff; 65 | width: 120px; 66 | } 67 | 68 | .cl_small_btn_disabled { 69 | font-family: 'Lora', serif; 70 | font-size: 10pt; 71 | color: black; 72 | background: #f0dbdb; 73 | width: 120px; 74 | } 75 | 76 | .cl_vsmall_btn_edit { 77 | font-family: Lora; 78 | font-size: 10pt; 79 | color: black; 80 | background: #bfbb53; 81 | width: 50px; 82 | } 83 | 84 | .cl_vsmall_btn_view { 85 | font-family: 'Lora', serif; 86 | font-size: 10pt; 87 | color: black; 88 | background: #bfbb53; 89 | width: 50px; 90 | } 91 | 92 | .cl_small_text { 93 | font-family: Lora, serif; 94 | font-size: 10pt; 95 | color: black; 96 | } 97 | 98 | .cl_field_name { 99 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 100 | font-size: 15px; 101 | color: #25235a; 102 | } 103 | 104 | .cl_user_input_text { 105 | font-family: 'Lora', 'Times', serif; 106 | font-size: 14px; 107 | color: #25235a; 108 | } 109 | 110 | .cl_table_heading { 111 | font-family: 'Cormorant Garamond', 'Lora', serif; 112 | font-size: 19px; 113 | color: #221441; 114 | } 115 | 116 | .cl_segment_header_req { 117 | font-family: Lora, serif; 118 | font-size: 12pt; 119 | font-weight: 200; 120 | background: #ccff99; 121 | } 122 | 123 | .cl_segment_header_resp { 124 | font-family: Lora, serif; 125 | font-size: 12pt; 126 | font-weight: 200; 127 | background: #cacadd; 128 | } 129 | 130 | .cl_field_value { 131 | font-family: 'Courier New', monospace; 132 | font-size: 11pt; 133 | color: #090933; 134 | width: 150px; 135 | } 136 | 137 | .cl_field_pos { 138 | font-family: Lora, serif; 139 | font-size: 11pt; 140 | color: #e64a0a; 141 | } 142 | 143 | .cl_trace_value { 144 | font-family: 'Courier New', monospace; 145 | font-size: 10pt; 146 | color: black; 147 | } 148 | 149 | .cl_req_field_sel_cb { 150 | background: grey; 151 | } 152 | 153 | .cl_resp_field_sel_cb { 154 | background: grey; 155 | } 156 | 157 | .cl_val_error { 158 | color: red; 159 | font-family: Lora, serif; 160 | font-size: 9pt; 161 | } 162 | 163 | .cl_text_label { 164 | color: black; 165 | font-family: Lora, serif; 166 | font-size: 11pt; 167 | height: 25px; 168 | width: 150px; 169 | / / align-content: center; 170 | background: #bbcad1; 171 | } 172 | 173 | .cl_std_text_box { 174 | background-color: #ededdd; 175 | border: 1px solid #848484; 176 | height: 25px; 177 | width: 200px; 178 | outline: 0; 179 | font-family: Lora, serif; 180 | font-size: 11pt; 181 | } 182 | 183 | .cl_std_cbox { 184 | background-color: #ededdd; 185 | border: 1px solid #848484; 186 | height: 25px; 187 | width: 200px; 188 | outline: 0; 189 | font-family: Lora, serif; 190 | font-size: 11pt; 191 | z-index: 90; 192 | } 193 | 194 | .cl_trace_dlg { 195 | margin-left: 10px; 196 | margin-right: 10px; 197 | width: 455px; 198 | height: 230px; 199 | display: none; 200 | background: white; 201 | border-style: solid; 202 | border-width: 1px; 203 | border-color: blue; 204 | z-index: 100; 205 | / / opacity: 1; 206 | position: absolute; 207 | } 208 | 209 | .cl_about_dlg { 210 | margin-left: 10px; 211 | margin-right: 10px; 212 | width: 455px; 213 | height: 300px; 214 | display: none; 215 | background: white; 216 | border-style: solid; 217 | border-width: 1px; 218 | border-color: blue; 219 | z-index: 100; 220 | / / opacity: 1; 221 | position: absolute; 222 | } 223 | 224 | .cl_cb_opt { 225 | font-family: SourceCodePro-Medium, serif; 226 | font-size: 11pt; 227 | } 228 | 229 | .cl_dlg_header { 230 | font-family: Lora, serif; 231 | width: 455px; 232 | background: gainsboro; 233 | } 234 | 235 | .cl_msg_selector { 236 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 237 | font-size: 15px; 238 | color: #25235a; 239 | } 240 | 241 | .cl_spec_msg { 242 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 243 | font-size: 15px; 244 | color: #25235a; 245 | } 246 | 247 | .cl_bytes_value { 248 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 249 | font-size: 15px; 250 | color: #25235a; 251 | } 252 | 253 | .cl_field_match_condition { 254 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 255 | font-size: 15px; 256 | color: #25235a; 257 | } 258 | 259 | .cl_proc_condition { 260 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 261 | font-size: 15px; 262 | } 263 | 264 | .cl_bytes_from { 265 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 266 | font-size: 15px; 267 | color: #25235a; 268 | width: 40px; 269 | } 270 | 271 | .cl_bytes_to { 272 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 273 | font-size: 15px; 274 | color: #25235a; 275 | width: 40px; 276 | } 277 | 278 | .cl_field_val { 279 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 280 | font-size: 15px; 281 | color: #25235a; 282 | } 283 | 284 | .cl_field_off { 285 | font-family: 'Lora', 'Cormorant Garamond', 'Lora', 'Times', serif; 286 | font-size: 15px; 287 | color: #25235a; 288 | } -------------------------------------------------------------------------------- /test/testdata/specs/sample_spec.yaml: -------------------------------------------------------------------------------- 1 | specs: 2 | - name: SampleSpec 3 | id: 4 4 | header_fields: 5 | - name: "hdr_msg_type" 6 | id: 1 7 | type: Fixed 8 | size: 4 9 | data_encoding: ASCII 10 | children: [] 11 | messages: 12 | - name: "1100 - Authorization" 13 | header_match: 14 | - "1100" 15 | - "1110" 16 | id: 1 17 | fields: 18 | - name: "Message Type" 19 | id: 1 20 | type: Fixed 21 | size: 4 22 | data_encoding: ASCII 23 | 24 | - name: "Bitmap" 25 | id: 2 26 | type: Bitmapped 27 | data_encoding: BINARY 28 | children: 29 | - name: "pan" 30 | id: 16 31 | type: Variable 32 | length_indicator_size: 2 33 | length_indicator_encoding: ASCII 34 | data_encoding: ASCII 35 | position: 2 36 | 37 | - name: "proc_code" 38 | id: 3 39 | type: Fixed 40 | size: 6 41 | data_encoding: ASCII 42 | position: 3 43 | 44 | - name: "amount" 45 | id: 4 46 | type: Fixed 47 | size: 12 48 | data_encoding: ASCII 49 | position: 4 50 | 51 | - name: "stan" 52 | id: 5 53 | type: Fixed 54 | size: 6 55 | data_encoding: ASCII 56 | key: true 57 | position: 11 58 | 59 | - name: "expiration_date" 60 | id: 6 61 | type: Fixed 62 | size: 4 63 | data_encoding: ASCII 64 | position: 14 65 | 66 | - name: "country_code" 67 | id: 7 68 | type: Fixed 69 | size: 3 70 | data_encoding: EBCDIC 71 | position: 19 72 | 73 | - name: "approval_code" 74 | id: 8 75 | type: Fixed 76 | size: 6 77 | data_encoding: ASCII 78 | position: 38 79 | 80 | - name: "action_code" 81 | id: 9 82 | type: Fixed 83 | size: 3 84 | data_encoding: ASCII 85 | position: 39 86 | 87 | - name: "pin_data" 88 | id: 10 89 | type: Fixed 90 | size: 8 91 | data_encoding: BINARY 92 | position: 52 93 | 94 | - name: "private_1" 95 | id: 11 96 | type: Variable 97 | length_indicator_size: 2 98 | length_indicator_encoding: BCD 99 | data_encoding: ASCII 100 | position: 61 101 | 102 | - name: "private_2" 103 | id: 12 104 | type: Variable 105 | length_indicator_size: 1 106 | length_indicator_encoding: BINARY 107 | data_encoding: EBCDIC 108 | position: 62 109 | 110 | - name: "private_3" 111 | id: 13 112 | type: Variable 113 | length_indicator_size: 3 114 | length_indicator_encoding: EBCDIC 115 | data_encoding: ASCII 116 | position: 63 117 | 118 | - name: "mac_1" 119 | id: 17 120 | type: Fixed 121 | size: 8 122 | data_encoding: BINARY 123 | position: 64 124 | 125 | - name: "key_mgmt_data" 126 | id: 14 127 | type: Fixed 128 | size: 4 129 | data_encoding: ASCII 130 | position: 96 131 | 132 | - name: "mac_2" 133 | id: 18 134 | type: Fixed 135 | size: 8 136 | data_encoding: BINARY 137 | position: 128 138 | 139 | 140 | - name: "reserved_data" 141 | id: 15 142 | type: Fixed 143 | size: 4 144 | data_encoding: ASCII 145 | position: 160 146 | 147 | - name: "1420 - Reversal" 148 | header_match: 149 | - "1420" 150 | - "1430" 151 | id: 2 152 | fields: 153 | - name: "Message Type" 154 | id: 1 155 | type: Fixed 156 | size: 4 157 | data_encoding: ASCII 158 | 159 | - name: "Bitmap" 160 | id: 2 161 | type: Bitmapped 162 | size: 0 163 | data_encoding: BINARY 164 | children: 165 | - name: "pan" 166 | id: 3 167 | type: Variable 168 | length_indicator_size: 2 169 | length_indicator_encoding: ASCII 170 | data_encoding: ASCII 171 | position: 2 172 | 173 | - name: "proc_code" 174 | id: 4 175 | type: Fixed 176 | size: 6 177 | data_encoding: ASCII 178 | position: 3 179 | 180 | - name: "amount" 181 | id: 8 182 | type: Fixed 183 | size: 12 184 | data_encoding: ASCII 185 | position: 4 186 | 187 | - name: "stan" 188 | id: 9 189 | type: Fixed 190 | size: 6 191 | data_encoding: ASCII 192 | key: true 193 | position: 11 194 | 195 | - name: "expiration_date" 196 | id: 16 197 | type: Fixed 198 | size: 4 199 | data_encoding: ASCII 200 | position: 14 201 | 202 | - name: "country_code" 203 | id: 17 204 | type: Fixed 205 | size: 3 206 | data_encoding: EBCDIC 207 | position: 19 208 | 209 | - name: "approval_code" 210 | id: 10 211 | type: Fixed 212 | size: 6 213 | data_encoding: ASCII 214 | position: 38 215 | 216 | - name: "action_code" 217 | id: 11 218 | type: Fixed 219 | size: 3 220 | data_encoding: ASCII 221 | position: 39 222 | 223 | - name: "private_3" 224 | id: 20 225 | type: Variable 226 | length_indicator_size: 3 227 | length_indicator_encoding: EBCDIC 228 | data_encoding: ASCII 229 | position: 63 230 | 231 | - name: "key_mgmt_data" 232 | id: 14 233 | type: Fixed 234 | size: 4 235 | data_encoding: ASCII 236 | position: 96 237 | 238 | - name: "reserved_data" 239 | id: 15 240 | type: Fixed 241 | size: 4 242 | data_encoding: ASCII 243 | position: 160 244 | -------------------------------------------------------------------------------- /web/isosim_common.js: -------------------------------------------------------------------------------- 1 | //globals 2 | 3 | var reqFieldsPrefixId0 = 'idJsReqFieldSelectedId_'; 4 | var reqFieldsPrefixId1 = 'idJsReqFieldId_'; 5 | var reqFieldsPrefixId2 = 'idJsReqFieldValueId_'; 6 | var reqFieldsValErrorIdPrefix = 'idJsFieldValErr_'; 7 | 8 | var respFieldsPrefixId0 = 'idJsRespFieldSelectedId_'; 9 | var respFieldsPrefixId1 = 'idJsRespFieldId_'; 10 | var respFieldsPrefixId2 = 'idJsRespFieldValueId_'; 11 | 12 | var alphaRE = /^[a-zA-Z]+$/; 13 | var alphaNumericRE = /^[0-9a-zA-Z]+$/; 14 | var numericRE = /^[0-9]+$/; 15 | 16 | var gPageState = { 17 | layoutLoaded: false 18 | }; 19 | 20 | //functions 21 | 22 | function enableOverlay() { 23 | document.getElementById('overlay').style['z-index'] = 99; 24 | } 25 | 26 | function disableOverlay() { 27 | document.getElementById('overlay').style['z-index'] = 80; 28 | } 29 | 30 | function getRequestFieldValElem(fieldId) { 31 | //alert(document.getElementById(reqFieldsPrefixId2 + fieldId)); 32 | return document.getElementById(reqFieldsPrefixId2 + fieldId); 33 | } 34 | 35 | function getResponseFieldValElem(fieldId) { 36 | return document.getElementById(respFieldsPrefixId2 + fieldId); 37 | } 38 | 39 | //functions related to dealing with user input - 40 | 41 | function jsSaveReqData() { 42 | 43 | enableOverlay(); 44 | document.getElementById('idJsUserInput').value = ''; 45 | 46 | var elem = document.getElementById('idJsUserInputDiv'); 47 | elem.style.display = "block"; 48 | elem.style.position = "fixed"; 49 | elem.style.left = "35%"; 50 | elem.style.top = "50%"; 51 | } 52 | 53 | function jsProcessUserInput() { 54 | 55 | var dataSetName = document.getElementById('idJsUserInput').value; 56 | if (dataSetName) { 57 | 58 | if (dataSetName === "") { 59 | showErrorMessage('Please provide a valid data set name.'); 60 | return; 61 | } else { 62 | 63 | var postData = ''; 64 | postData += 'specId=' + getCurrentSpecId() + '&'; 65 | postData += 'msgId=' + getCurrentSpecMsgId() + '&'; 66 | postData += 'dataSetName=' + dataSetName + '&'; 67 | 68 | var reqObj = constructReqObj(); 69 | if (reqObj.length === 0) { 70 | //document.getElementById('idJsUserInputDiv').style.display = "none"; 71 | showErrorMessage( 72 | 'There is no data to save, please construct a message.'); 73 | 74 | return; 75 | } 76 | 77 | postData += 'msg=' + JSON.stringify(reqObj); 78 | //alert(postData); 79 | 80 | doAjaxCall('/iso/v0/save', function (response) { 81 | jsShowUserMsg('Message [' + dataSetName + '] has been saved.'); 82 | updateCurrentDs(dataSetName) 83 | 84 | }, postData, 'POST', 'application/x-www-form-urlencoded'); 85 | 86 | } 87 | 88 | } else { 89 | showErrorMessage('Please provide a valid data set name.'); 90 | return; 91 | 92 | } 93 | 94 | jsCancelUserInputDialog(); 95 | 96 | } 97 | 98 | function jsCancelUserInputDialog() { 99 | document.getElementById('idJsUserInputDiv').style.display = "none"; 100 | disableOverlay(); 101 | 102 | } 103 | 104 | function getCurrentSpecId() { 105 | 106 | var specsElem = document.getElementById('idJsSpec'); 107 | var selectedOption = specsElem.options[specsElem.selectedIndex]; 108 | return selectedOption.id; 109 | 110 | } 111 | 112 | function getCurrentSpecMsgId() { 113 | 114 | 115 | //var specsElem = document.getElementById('idJsSpec'); 116 | //var selectedOption = specsElem.options[specsElem.selectedIndex]; 117 | //return selectedOption.id; 118 | var elem = document.getElementById('idJsSpecMsgs'); 119 | var msgId = elem.options[elem.selectedIndex].id; 120 | return msgId; 121 | 122 | } 123 | 124 | function jsLoadReqData() { 125 | 126 | doAjaxCall('/iso/v0/loadmsg?specId=' + getCurrentSpecId() + '&msgId=' 127 | + getCurrentSpecMsgId(), function (response) { 128 | 129 | //alert(response); 130 | 131 | var dataSets = JSON.parse(response); 132 | var htmlContent = ''; 133 | for (var i = 0; i < dataSets.length; i++) { 134 | htmlContent += '"; 136 | 137 | } 138 | 139 | var selectElem = document.getElementById('idJsUserSelection'); 140 | selectElem.innerHTML = htmlContent; 141 | selectElem.selectedIndex = 0; 142 | 143 | enableOverlay(); 144 | var elem = document.getElementById('idJsUserSelectionDiv'); 145 | elem.style.display = "block"; 146 | elem.style.position = "fixed"; 147 | elem.style.left = "35%"; 148 | elem.style.top = "50%"; 149 | 150 | }, 'GET'); 151 | 152 | } 153 | 154 | function getCurrentDs() { 155 | return document.getElementById('idJsCurrentDs').value; 156 | } 157 | 158 | function updateCurrentDs(dsName) { 159 | 160 | if (dsName) { 161 | gPageState.currentDataSet = dsName; 162 | document.getElementById('idJsCurrentDs').value = dsName; 163 | document.getElementById('idJsUpdateMsgBtn').disabled = false; 164 | document.getElementById('idJsUpdateMsgBtn').className = 'cl_small_btn'; 165 | 166 | } else { 167 | document.getElementById('idJsCurrentDs').value = ""; 168 | document.getElementById('idJsUpdateMsgBtn').disabled = true; 169 | document.getElementById( 170 | 'idJsUpdateMsgBtn').className = 'cl_small_btn_disabled'; 171 | } 172 | 173 | } 174 | 175 | function jsProcessUserSelection() { 176 | 177 | var selectElem = document.getElementById('idJsUserSelection'); 178 | var dsName = selectElem.options[selectElem.selectedIndex].value; 179 | 180 | //alert('Fetching ds - ' + dsName); 181 | 182 | doAjaxCall('/iso/v0/loadmsg?specId=' + getCurrentSpecId() + '&msgId=' 183 | + getCurrentSpecMsgId() + '&dsName=' + dsName, function (response) { 184 | 185 | //alert(response); 186 | var dataSet = JSON.parse(response); 187 | updateCurrentDs(dsName); 188 | 189 | jsLoadTemplate(dataSet, function () { 190 | jsCancelUserSelectionDialog(); 191 | }); 192 | 193 | }, 'GET'); 194 | 195 | } 196 | 197 | function jsCancelUserSelectionDialog() { 198 | document.getElementById('idJsUserSelectionDiv').style.display = "none"; 199 | disableOverlay(); 200 | } 201 | 202 | function jsShowUserMsg(msg) { 203 | 204 | enableOverlay(); 205 | document.getElementById('idJsUserMsg').innerHTML = msg; 206 | 207 | var elem = document.getElementById('idJsUserMsgDiv'); 208 | elem.style.display = "block"; 209 | elem.style.position = "fixed"; 210 | elem.style.left = "35%"; 211 | elem.style.top = "50%"; 212 | } 213 | 214 | function jsCloseUserMsgDialog() { 215 | document.getElementById('idJsUserMsgDiv').style.display = "none"; 216 | disableOverlay(); 217 | } 218 | 219 | function jsUpdateMessage() { 220 | 221 | var postData = ''; 222 | postData += 'specId=' + getCurrentSpecId() + '&'; 223 | postData += 'msgId=' + getCurrentSpecMsgId() + '&'; 224 | postData += 'updateMsg=' + 'true' + '&'; 225 | var ds = getCurrentDs(); 226 | postData += 'dataSetName=' + ds + '&'; 227 | 228 | var reqObj = constructReqObj(); 229 | if (reqObj.length === 0) { 230 | showErrorMessage('There is no data to save, please construct a message.'); 231 | 232 | return; 233 | } 234 | 235 | postData += 'msg=' + JSON.stringify(reqObj); 236 | //alert(postData); 237 | 238 | doAjaxCall('/iso/v0/save', function (response) { 239 | jsShowUserMsg('Message [' + ds + '] has been updated.'); 240 | }, postData, 'POST', 'application/x-www-form-urlencoded'); 241 | 242 | } 243 | -------------------------------------------------------------------------------- /internal/iso/server/iso_server.go: -------------------------------------------------------------------------------- 1 | // Package server has types to help define and serve ISO specs, build responses 2 | // based on server definitions etc 3 | package server //github.com/rkbalgi/isosim/server 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "encoding/hex" 9 | "encoding/json" 10 | "errors" 11 | "fmt" 12 | net2 "github.com/rkbalgi/libiso/net" 13 | log "github.com/sirupsen/logrus" 14 | "io" 15 | "isosim/internal/services/data" 16 | "net" 17 | "strconv" 18 | "sync" 19 | ) 20 | 21 | //The list of servers that are currently running 22 | var activeServers map[string]*serverInstance 23 | 24 | //The lock to protect concurrent access to activeServers map 25 | var activeServersLock sync.Mutex 26 | 27 | type serverInstance struct { 28 | name string 29 | port int 30 | listener net.Listener 31 | } 32 | 33 | func init() { 34 | activeServers = make(map[string]*serverInstance) 35 | activeServersLock = sync.Mutex{} 36 | 37 | } 38 | 39 | type activeServer struct { 40 | Name string 41 | Port int 42 | } 43 | 44 | //ActiveServers returns a list of running servers along with listener port info 45 | //To be used while displaying information o UI 46 | func ActiveServers() string { 47 | 48 | if len(activeServers) == 0 { 49 | return "{\"msg\": \"No server instances running.\"}" 50 | } 51 | result := make([]activeServer, 0, len(activeServers)) 52 | for _, si := range activeServers { 53 | result = append(result, activeServer{si.name, si.port}) 54 | } 55 | jsonRep := bytes.NewBufferString("") 56 | json.NewEncoder(jsonRep).Encode(result) 57 | return jsonRep.String() 58 | 59 | } 60 | 61 | // addServer a server to the list of active servers 62 | func addServer(serverName string, port int, listener net.Listener) { 63 | 64 | activeServersLock.Lock() 65 | defer activeServersLock.Unlock() 66 | serverID := serverName + strconv.Itoa(port) 67 | activeServers[serverID] = &serverInstance{serverName, 68 | port, listener} 69 | 70 | } 71 | 72 | // Stop stops a running server given its name 73 | func Stop(serverName string) error { 74 | 75 | activeServersLock.Lock() 76 | defer activeServersLock.Unlock() 77 | var si *serverInstance 78 | var ok bool 79 | if si, ok = activeServers[serverName]; !ok { 80 | return errors.New("No such server running ..- " + serverName) 81 | } 82 | err := si.listener.Close() 83 | if err == nil { 84 | delete(activeServers, serverName) 85 | } 86 | return err 87 | 88 | } 89 | 90 | // Start starts a ISO server at port, the behaviour of which is defined by the server definition 91 | func Start(specId string, serverDefName string, port int, mliType string) error { 92 | 93 | vServerDef, err := getDef(specId, serverDefName) 94 | if err != nil { 95 | log.Errorln("Failed to get server definition", err) 96 | } 97 | //override the MLI type and port 98 | vServerDef.MliType = mliType 99 | vServerDef.ServerPort = port 100 | 101 | return StartWithDef(&vServerDef, serverDefName, 0) 102 | 103 | } 104 | 105 | // StartWithDef starts the server with a provided def, name and the port 106 | func StartWithDef(def *data.ServerDef, defName string, port int) error { 107 | 108 | actualPort := port 109 | if actualPort == 0 { 110 | actualPort = def.ServerPort 111 | } 112 | 113 | log.Infoln("Starting ISO Server @ Port = ", actualPort, "MLI-Type = ", def.MliType) 114 | listener, err := net.Listen("tcp", ":"+strconv.Itoa(actualPort)) 115 | if err != nil { 116 | return err 117 | } 118 | addServer(defName, port, listener) 119 | 120 | go func(vServerDef *data.ServerDef) { 121 | 122 | for { 123 | connection, err := listener.Accept() 124 | if err != nil { 125 | log.Print("Error on server. Error = ", err) 126 | return 127 | } 128 | log.Debugf("New connection accepted: - %v->%v", connection.RemoteAddr(), connection.RemoteAddr()) 129 | go handleConnection(connection, vServerDef) 130 | } 131 | }(def) 132 | 133 | return nil 134 | } 135 | 136 | func closeOnError(connection net.Conn, err error) { 137 | 138 | if err != io.EOF { 139 | log.Errorln("Error on connection.. Error = " + err.Error() + " Remote Addr =" + connection.RemoteAddr().String()) 140 | } else { 141 | log.Infof("iso-server: A remote client closed the connection. Addr: %s: LocalAddr: %s", connection.RemoteAddr().String(), connection.LocalAddr().String()) 142 | } 143 | if err := connection.Close(); err != nil { 144 | log.Errorln("Error closing connection ", err) 145 | } 146 | 147 | } 148 | 149 | func handleConnection(connection net.Conn, def *data.ServerDef) { 150 | 151 | slog := log.WithFields(log.Fields{"type": "server"}) 152 | 153 | buf := new(bytes.Buffer) 154 | 155 | mliType, err := getMliFromString(def.MliType) 156 | if err != nil { 157 | log.Errorf("isosim: Invalid MLIType %s specified", def.MliType) 158 | return 159 | } 160 | var mliLen uint32 = 2 161 | if mliType == net2.Mli4e || mliType == net2.Mli4i { 162 | mliLen = 4 163 | } 164 | 165 | mli := make([]byte, mliLen) 166 | tmp := make([]byte, 256) 167 | 168 | for { 169 | slog.Traceln("Reading MLI .. ") 170 | n, err := connection.Read(mli) 171 | 172 | if err != nil { 173 | log.Traceln("Unexpected error while reading MLI : ", err) 174 | } 175 | if n > 0 { 176 | slog.Traceln("MLI Data = " + hex.EncodeToString(mli)) 177 | } 178 | var msgLen uint32 = 0 179 | 180 | switch mliType { 181 | case net2.Mli2i, net2.Mli2e: 182 | msgLen = uint32(binary.BigEndian.Uint16(mli)) 183 | if mliType == net2.Mli2i { 184 | msgLen -= mliLen 185 | } 186 | case net2.Mli4i, net2.Mli4e: 187 | msgLen = binary.BigEndian.Uint32(mli) 188 | if mliType == net2.Mli4i { 189 | msgLen -= mliLen 190 | } 191 | } 192 | 193 | if err != nil { 194 | closeOnError(connection, err) 195 | return 196 | } 197 | slog.Debugf("Expected msgLen: %d", msgLen) 198 | 199 | complete := false 200 | for !complete { 201 | n := 0 202 | if n, err = connection.Read(tmp); err != nil { 203 | closeOnError(connection, err) 204 | return 205 | 206 | } 207 | 208 | if n > 0 { 209 | 210 | slog.Traceln("Read = " + hex.EncodeToString(tmp[0:n])) 211 | buf.Write(tmp[0:n]) 212 | slog.Traceln("msgLen = ", msgLen, " Read = ", n) 213 | if uint32(len(buf.Bytes())) == msgLen { 214 | //we have a complete msg 215 | complete = true 216 | var msgData = make([]byte, msgLen) 217 | copy(msgData, buf.Bytes()) 218 | slog.Debugf("Received Request, \n%s\n", hex.Dump(msgData)) 219 | buf.Reset() 220 | go handleRequest(connection, msgData, def, mliType) 221 | 222 | } 223 | } 224 | 225 | } 226 | 227 | } 228 | 229 | } 230 | 231 | func getMliFromString(mliType string) (net2.MliType, error) { 232 | 233 | switch mliType { 234 | case "2e", "2E": 235 | return net2.Mli2e, nil 236 | case "2i", "2I": 237 | return net2.Mli2i, nil 238 | case "4e", "4E": 239 | return net2.Mli4e, nil 240 | case "4i", "4I": 241 | return net2.Mli4i, nil 242 | 243 | default: 244 | return "", fmt.Errorf("isosim: (server) Invalid MLI-Type - %s", mliType) 245 | 246 | } 247 | } 248 | 249 | func handleRequest(connection net.Conn, msgData []byte, pServerDef *data.ServerDef, mliType net2.MliType) { 250 | 251 | responseData, err := processMsg(msgData, pServerDef) 252 | if err != nil { 253 | log.Errorln("Failed to process message . Error = ", err.Error()) 254 | return 255 | } 256 | 257 | finalData := net2.AddMLI(mliType, responseData) 258 | 259 | buf := new(bytes.Buffer) 260 | buf.Write(finalData) 261 | log.WithFields(log.Fields{"type": "server"}).Debugln("Writing Response. Data = " + hex.EncodeToString(buf.Bytes())) 262 | _, err = connection.Write(buf.Bytes()) 263 | if err != nil { 264 | log.Errorln("Error writing response to client ", err) 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /internal/services/websim/endpoints.go: -------------------------------------------------------------------------------- 1 | package websim 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-kit/kit/endpoint" 7 | "github.com/go-kit/kit/log" 8 | netutil "github.com/rkbalgi/libiso/net" 9 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 10 | "isosim/internal/services/data" 11 | ) 12 | 13 | type GetAllSpecsRequest struct{} 14 | 15 | type GetAllSpecResponse struct { 16 | Specs []UISpec `json:"specs"` 17 | Err error `json:"-"` 18 | } 19 | 20 | type GetMessages4SpecRequest struct{ specId int } 21 | type GetMessages4SpecResponse struct { 22 | Messages []*isov2.Message `json:"messages"` 23 | Err error `json:"-"` 24 | } 25 | 26 | type GetMessageTemplateRequest struct { 27 | specId int 28 | msgId int 29 | } 30 | 31 | type GetMessageTemplateResponse struct { 32 | Fields []*data.JsonFieldInfoRep `json:"fields"` 33 | Err error `json:"-"` 34 | } 35 | 36 | type LoadOrFetchSavedMessagesRequest struct { 37 | specId int 38 | msgId int 39 | dsName string 40 | } 41 | 42 | type LoadOrFetchSavedMessagesResponse struct { 43 | SavedMsg *SavedMsg `json:"saved_message,omitempty"` 44 | SavedMessages []string `json:"saved_messages,omitempty"` 45 | Err error `json:"-"` 46 | } 47 | 48 | //parse msg 49 | 50 | type ParseTraceRequest struct { 51 | specId int 52 | msgId int 53 | msgTrace string 54 | } 55 | 56 | type ParseTraceResponse struct { 57 | ParsedFields *[]data.JsonFieldDataRep `json:"parsed_fields"` 58 | Err error `json:"-"` 59 | } 60 | 61 | type ParseTraceExtRequest struct { 62 | specName string 63 | msgName string 64 | msgTrace string 65 | } 66 | 67 | type ParseTraceExtResponse struct { 68 | ParsedFields *[]data.JsonFieldDataRep `json:"parsed_fields"` 69 | Err error `json:"-"` 70 | } 71 | 72 | //save msg 73 | 74 | type SaveMsgRequest struct { 75 | specId int 76 | msgId int 77 | msgName string 78 | msgData string 79 | responseMsgData string 80 | isUpdate bool 81 | } 82 | 83 | type SaveMsgResponse struct { 84 | Err error `json:"-"` 85 | } 86 | 87 | //send to host 88 | 89 | type SendToHostRequest struct { 90 | specId int 91 | msgId int 92 | msgData string 93 | HostIP string 94 | Port int 95 | MLI string 96 | } 97 | 98 | type SendToHostResponse struct { 99 | ResponseFields *[]data.JsonFieldDataRep `json:"response_fields"` 100 | Err error `json:"-"` 101 | } 102 | 103 | func (r GetAllSpecResponse) Failed() error { 104 | return r.Err 105 | } 106 | 107 | func (r GetMessages4SpecResponse) Failed() error { 108 | return r.Err 109 | } 110 | 111 | func (r GetMessageTemplateResponse) Failed() error { 112 | return r.Err 113 | } 114 | func (r LoadOrFetchSavedMessagesResponse) Failed() error { 115 | return r.Err 116 | } 117 | 118 | func (r ParseTraceExtResponse) Failed() error { 119 | return r.Err 120 | } 121 | 122 | func (r ParseTraceResponse) Failed() error { 123 | return r.Err 124 | } 125 | 126 | func (r SaveMsgResponse) Failed() error { 127 | return r.Err 128 | } 129 | 130 | func (r SendToHostResponse) Failed() error { 131 | return r.Err 132 | } 133 | 134 | func allSpecsEndpoint(s Service) endpoint.Endpoint { 135 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 136 | resp, err := s.GetAllSpecs(ctx) 137 | if err != nil { 138 | return GetAllSpecResponse{Specs: nil, Err: err}, nil 139 | } 140 | return GetAllSpecResponse{Specs: resp}, nil 141 | 142 | } 143 | } 144 | 145 | func messages4SpecEndpoint(s Service) endpoint.Endpoint { 146 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 147 | req := request.(GetMessages4SpecRequest) 148 | resp, err := s.GetMessages4Spec(ctx, req.specId) 149 | if err != nil { 150 | return GetMessages4SpecResponse{Messages: nil, Err: err}, nil 151 | } 152 | return GetMessages4SpecResponse{Messages: resp}, nil 153 | 154 | } 155 | } 156 | 157 | func messageTemplateEndpoint(s Service) endpoint.Endpoint { 158 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 159 | req := request.(GetMessageTemplateRequest) 160 | jsonMsgTemplate, err := s.GetMessageTemplate(ctx, req.specId, req.msgId) 161 | if err != nil { 162 | return GetMessageTemplateResponse{Fields: nil, Err: err}, nil 163 | } 164 | return GetMessageTemplateResponse{Fields: jsonMsgTemplate.Fields}, nil 165 | 166 | } 167 | } 168 | 169 | func loadOrFetchSavedMessagesEndpoint(s Service) endpoint.Endpoint { 170 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 171 | req := request.(LoadOrFetchSavedMessagesRequest) 172 | sm, sms, err := s.LoadOrFetchSavedMessages(ctx, req.specId, req.msgId, req.dsName) 173 | if err != nil { 174 | return LoadOrFetchSavedMessagesResponse{Err: err}, nil 175 | } 176 | return LoadOrFetchSavedMessagesResponse{SavedMsg: sm, SavedMessages: sms, Err: err}, nil 177 | 178 | } 179 | } 180 | 181 | func parseTraceEndpoint(s Service) endpoint.Endpoint { 182 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 183 | req := request.(ParseTraceRequest) 184 | 185 | parsedResponse, err := s.ParseTrace(ctx, req.specId, req.msgId, req.msgTrace) 186 | if err != nil { 187 | return ParseTraceResponse{Err: err}, nil 188 | } 189 | return ParseTraceResponse{ParsedFields: parsedResponse, Err: err}, nil 190 | 191 | } 192 | 193 | } 194 | 195 | func parseTraceExternalEndpoint(s Service) endpoint.Endpoint { 196 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 197 | req := request.(ParseTraceExtRequest) 198 | parsedResponse, err := s.ParseTraceExternal(ctx, req.specName, req.msgName, req.msgTrace) 199 | if err != nil { 200 | return ParseTraceExtResponse{Err: err}, nil 201 | } 202 | return ParseTraceExtResponse{ParsedFields: parsedResponse, Err: err}, nil 203 | 204 | } 205 | } 206 | 207 | func saveMsgEndpoint(s Service) endpoint.Endpoint { 208 | return func(ctx context.Context, request interface{}) (response interface{}, err error) { 209 | req := request.(SaveMsgRequest) 210 | err = s.SaveMessage(ctx, req.specId, req.msgId, req.msgName, req.msgData, req.responseMsgData, req.isUpdate) 211 | if err != nil { 212 | return SaveMsgResponse{Err: err}, nil 213 | } 214 | return SaveMsgResponse{Err: err}, nil 215 | 216 | } 217 | } 218 | 219 | func sendToHostEndpoint(s Service) endpoint.Endpoint { 220 | return func(ctx context.Context, request interface{}) (interface{}, error) { 221 | req := request.(SendToHostRequest) 222 | 223 | var mli netutil.MliType 224 | switch req.MLI { 225 | case "2I", "2i": 226 | mli = netutil.Mli2i 227 | case "2E", "2e": 228 | mli = netutil.Mli2e 229 | case "4I", "4i": 230 | mli = netutil.Mli4i 231 | case "4E", "4e": 232 | mli = netutil.Mli4e 233 | 234 | default: 235 | return nil, fmt.Errorf("isosim: Invalid MLI-Type %s in request", req.MLI) 236 | 237 | } 238 | 239 | isoResponse, err := s.SendToHost(ctx, req.specId, req.msgId, NetOptions{Host: req.HostIP, Port: req.Port, MLIType: mli}, req.msgData) 240 | if err != nil { 241 | return SendToHostResponse{Err: err}, nil 242 | } 243 | return SendToHostResponse{ResponseFields: isoResponse}, nil 244 | 245 | } 246 | } 247 | 248 | func loggingMiddleware(logger log.Logger) endpoint.Middleware { 249 | return func(next endpoint.Endpoint) endpoint.Endpoint { 250 | return func(ctx context.Context, request interface{}) (interface{}, error) { 251 | logger.Log("calling endpoint ") 252 | defer logger.Log("called endpoint ") 253 | return next(ctx, request) 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /web/react-fe/build/static/js/runtime-main.d1dafa31.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../webpack/bootstrap"],"names":["webpackJsonpCallback","data","moduleId","chunkId","chunkIds","moreModules","executeModules","i","resolves","length","Object","prototype","hasOwnProperty","call","installedChunks","push","modules","parentJsonpFunction","shift","deferredModules","apply","checkDeferredModules","result","deferredModule","fulfilled","j","depId","splice","__webpack_require__","s","installedModules","1","exports","module","l","m","c","d","name","getter","o","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","p","jsonpArray","this","oldJsonpFunction","slice"],"mappings":"aACE,SAASA,EAAqBC,GAQ7B,IAPA,IAMIC,EAAUC,EANVC,EAAWH,EAAK,GAChBI,EAAcJ,EAAK,GACnBK,EAAiBL,EAAK,GAIHM,EAAI,EAAGC,EAAW,GACpCD,EAAIH,EAASK,OAAQF,IACzBJ,EAAUC,EAASG,GAChBG,OAAOC,UAAUC,eAAeC,KAAKC,EAAiBX,IAAYW,EAAgBX,IACpFK,EAASO,KAAKD,EAAgBX,GAAS,IAExCW,EAAgBX,GAAW,EAE5B,IAAID,KAAYG,EACZK,OAAOC,UAAUC,eAAeC,KAAKR,EAAaH,KACpDc,EAAQd,GAAYG,EAAYH,IAKlC,IAFGe,GAAqBA,EAAoBhB,GAEtCO,EAASC,QACdD,EAASU,OAATV,GAOD,OAHAW,EAAgBJ,KAAKK,MAAMD,EAAiBb,GAAkB,IAGvDe,IAER,SAASA,IAER,IADA,IAAIC,EACIf,EAAI,EAAGA,EAAIY,EAAgBV,OAAQF,IAAK,CAG/C,IAFA,IAAIgB,EAAiBJ,EAAgBZ,GACjCiB,GAAY,EACRC,EAAI,EAAGA,EAAIF,EAAed,OAAQgB,IAAK,CAC9C,IAAIC,EAAQH,EAAeE,GACG,IAA3BX,EAAgBY,KAAcF,GAAY,GAE3CA,IACFL,EAAgBQ,OAAOpB,IAAK,GAC5Be,EAASM,EAAoBA,EAAoBC,EAAIN,EAAe,KAItE,OAAOD,EAIR,IAAIQ,EAAmB,GAKnBhB,EAAkB,CACrBiB,EAAG,GAGAZ,EAAkB,GAGtB,SAASS,EAAoB1B,GAG5B,GAAG4B,EAAiB5B,GACnB,OAAO4B,EAAiB5B,GAAU8B,QAGnC,IAAIC,EAASH,EAAiB5B,GAAY,CACzCK,EAAGL,EACHgC,GAAG,EACHF,QAAS,IAUV,OANAhB,EAAQd,GAAUW,KAAKoB,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAG/DK,EAAOC,GAAI,EAGJD,EAAOD,QAKfJ,EAAoBO,EAAInB,EAGxBY,EAAoBQ,EAAIN,EAGxBF,EAAoBS,EAAI,SAASL,EAASM,EAAMC,GAC3CX,EAAoBY,EAAER,EAASM,IAClC5B,OAAO+B,eAAeT,EAASM,EAAM,CAAEI,YAAY,EAAMC,IAAKJ,KAKhEX,EAAoBgB,EAAI,SAASZ,GACX,qBAAXa,QAA0BA,OAAOC,aAC1CpC,OAAO+B,eAAeT,EAASa,OAAOC,YAAa,CAAEC,MAAO,WAE7DrC,OAAO+B,eAAeT,EAAS,aAAc,CAAEe,OAAO,KAQvDnB,EAAoBoB,EAAI,SAASD,EAAOE,GAEvC,GADU,EAAPA,IAAUF,EAAQnB,EAAoBmB,IAC/B,EAAPE,EAAU,OAAOF,EACpB,GAAW,EAAPE,GAA8B,kBAAVF,GAAsBA,GAASA,EAAMG,WAAY,OAAOH,EAChF,IAAII,EAAKzC,OAAO0C,OAAO,MAGvB,GAFAxB,EAAoBgB,EAAEO,GACtBzC,OAAO+B,eAAeU,EAAI,UAAW,CAAET,YAAY,EAAMK,MAAOA,IACtD,EAAPE,GAA4B,iBAATF,EAAmB,IAAI,IAAIM,KAAON,EAAOnB,EAAoBS,EAAEc,EAAIE,EAAK,SAASA,GAAO,OAAON,EAAMM,IAAQC,KAAK,KAAMD,IAC9I,OAAOF,GAIRvB,EAAoB2B,EAAI,SAAStB,GAChC,IAAIM,EAASN,GAAUA,EAAOiB,WAC7B,WAAwB,OAAOjB,EAAgB,SAC/C,WAA8B,OAAOA,GAEtC,OADAL,EAAoBS,EAAEE,EAAQ,IAAKA,GAC5BA,GAIRX,EAAoBY,EAAI,SAASgB,EAAQC,GAAY,OAAO/C,OAAOC,UAAUC,eAAeC,KAAK2C,EAAQC,IAGzG7B,EAAoB8B,EAAI,IAExB,IAAIC,EAAaC,KAAK,8BAAgCA,KAAK,+BAAiC,GACxFC,EAAmBF,EAAW5C,KAAKuC,KAAKK,GAC5CA,EAAW5C,KAAOf,EAClB2D,EAAaA,EAAWG,QACxB,IAAI,IAAIvD,EAAI,EAAGA,EAAIoD,EAAWlD,OAAQF,IAAKP,EAAqB2D,EAAWpD,IAC3E,IAAIU,EAAsB4C,EAI1BxC,I","file":"static/js/runtime-main.d1dafa31.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t1: 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \tvar jsonpArray = this[\"webpackJsonpisosim-reactjs\"] = this[\"webpackJsonpisosim-reactjs\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":""} -------------------------------------------------------------------------------- /internal/db/data_set_manager.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import bolt "go.etcd.io/bbolt" 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "errors" 9 | log "github.com/sirupsen/logrus" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | "strings" 15 | "sync" 16 | 17 | "isosim/internal/services/data" 18 | ) 19 | 20 | type dataSetManager struct{} 21 | 22 | var instance *dataSetManager 23 | var dataDir string 24 | var bdb *bolt.DB 25 | 26 | const defFileSuffix = ".srvdef.json" 27 | const respFileSuffix = "_response_ref.json" 28 | 29 | var initDS = sync.Once{} 30 | 31 | // ErrDataSetExists is an error that indicates that the dataset by the provided 32 | // name already exists 33 | var ErrDataSetExists = errors.New("isosim: data set exists") 34 | 35 | // Init verifies and initializes the dataDir passed in during in 36 | // initialization 37 | func Init(dirname string) error { 38 | dir, err := os.Open(dirname) 39 | if err != nil { 40 | return err 41 | } 42 | _ = dir.Close() 43 | dataDir = dirname 44 | 45 | //we'll use bolt db to store transaction history 46 | if bdb, err = bolt.Open(filepath.Join(dataDir, "isosim.bdb"), 0666, nil); err != nil { 47 | return err 48 | } 49 | log.Infoln("Opened Bolt DB .. db-file: isosim.bdb") 50 | 51 | return nil 52 | } 53 | 54 | // DataSetManager returns the singleton instance of the DataSetManager 55 | func DataSetManager() *dataSetManager { 56 | 57 | initDS.Do(func() { 58 | instance = new(dataSetManager) 59 | 60 | }) 61 | return instance 62 | } 63 | 64 | // GetAll returns a list of all data sets (names only) for the given specId 65 | // and msgId 66 | func (dsm *dataSetManager) GetAll(specId string, msgId string) ([]string, error) { 67 | 68 | dir, err := os.Open(filepath.Join(dataDir, specId, msgId)) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | if dirContents, err := dir.Readdir(-1); err != nil { 74 | return nil, err 75 | } else { 76 | var dataSets = make([]string, 0, 10) 77 | for _, ds := range dirContents { 78 | if !ds.IsDir() && !strings.HasSuffix(ds.Name(), respFileSuffix) { 79 | dataSets = append(dataSets, ds.Name()) 80 | } 81 | } 82 | 83 | return dataSets, nil 84 | 85 | } 86 | } 87 | 88 | // TestCase is a specialization of a Message that also contains a reference response and affiliated data 89 | type TestCase struct { 90 | ReqData []*data.JsonFieldDataRep `json:"req_data"` 91 | RespData []*data.TCResponseFieldDataRep `json:"resp_data"` 92 | } 93 | 94 | // Get returns the content of a specific data set 95 | func (dsm *dataSetManager) Get(specId string, msgId string, dsName string) ([]byte, error) { 96 | 97 | dsData, err := ioutil.ReadFile(filepath.Join(dataDir, specId, msgId, dsName)) 98 | if err != nil { 99 | return nil, err 100 | 101 | } 102 | 103 | testCase := TestCase{} 104 | 105 | if err := json.Unmarshal(dsData, &testCase.ReqData); err != nil { 106 | return nil, err 107 | } 108 | 109 | if respData, err := ioutil.ReadFile(filepath.Join(dataDir, specId, msgId, dsName+respFileSuffix)); err != nil { 110 | log.Debugf("No response ref data found for %q, probably not an error", dsName) 111 | } else { 112 | if err := json.Unmarshal(respData, &testCase.RespData); err != nil { 113 | return nil, err 114 | } 115 | } 116 | 117 | return json.Marshal(testCase) 118 | 119 | } 120 | 121 | // Add adds a new data-set for the given spec and msg 122 | func (dsm *dataSetManager) Add(specId string, msgId string, name string, reqData string, respData string) error { 123 | 124 | log.Traceln("Adding msgData set - " + name + " reqData = " + reqData + " respData = " + respData) 125 | exists, err := checkIfExists(specId, msgId, name) 126 | if err != nil { 127 | return err 128 | } 129 | if exists { 130 | return ErrDataSetExists 131 | } 132 | 133 | err = ioutil.WriteFile(filepath.Join(dataDir, specId, msgId, name), []byte(reqData), 0755) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | err = ioutil.WriteFile(filepath.Join(dataDir, specId, msgId, name+respFileSuffix), []byte(respData), 0755) 139 | if err != nil { 140 | return err 141 | } 142 | return nil 143 | } 144 | 145 | // AddServerDef adds a new server definition 146 | func (dsm *dataSetManager) AddServerDef(defString string) (string, error) { 147 | 148 | log.Traceln("Adding server definition - .. JSON = " + defString) 149 | 150 | serverDef := &data.ServerDef{} 151 | err := json.NewDecoder(bytes.NewBufferString(defString)).Decode(serverDef) 152 | if err != nil { 153 | return "", err 154 | } 155 | 156 | strSpecId := strconv.Itoa(serverDef.SpecId) 157 | dir, err := os.Open(filepath.Join(dataDir, strSpecId)) 158 | if err != nil && os.IsNotExist(err) { 159 | //create dir if one doesn't exist 160 | os.Mkdir(filepath.Join(dataDir, strSpecId), 0755) 161 | } else { 162 | if err != nil { 163 | return "", err 164 | } 165 | } 166 | dir.Close() 167 | 168 | fileName := serverDef.ServerName 169 | fileName = strings.Replace(fileName, " ", "", -1) 170 | fileName = strings.Replace(fileName, ",", "", -1) 171 | fileName = fileName + defFileSuffix 172 | 173 | log.Debugln("Writing spec def to file = " + fileName) 174 | 175 | defFile, err := os.Open(filepath.Join(dataDir, strSpecId, fileName)) 176 | if err == nil { 177 | return "", errors.New("isosim: server-def file exists") 178 | } 179 | defFile.Close() 180 | 181 | err = ioutil.WriteFile(filepath.Join(dataDir, strSpecId, fileName), []byte(defString), 0755) 182 | if err != nil { 183 | return "", err 184 | } 185 | return fileName, nil 186 | } 187 | 188 | // ServerDefinitions returns all existing server definitions 189 | func (dsm *dataSetManager) ServerDefinitions(specId string) ([]string, error) { 190 | 191 | dir, err := os.Open(filepath.Join(dataDir, specId)) 192 | if err != nil { 193 | return nil, err 194 | } 195 | dirContents, err := dir.Readdir(-1) 196 | res := make([]string, 0) 197 | 198 | for _, fileInfo := range dirContents { 199 | if strings.HasSuffix(fileInfo.Name(), defFileSuffix) { 200 | res = append(res, fileInfo.Name()) 201 | } 202 | } 203 | 204 | return res, nil 205 | 206 | } 207 | 208 | // ServerDef returns a server definition by its name 209 | func (dsm *dataSetManager) ServerDef(specId string, name string) ([]byte, error) { 210 | 211 | file, err := os.Open(filepath.Join(dataDir, specId, name)) 212 | if err != nil { 213 | return nil, err 214 | } 215 | return ioutil.ReadAll(file) 216 | 217 | } 218 | 219 | // Update updates a data set 220 | func (dsm *dataSetManager) Update(specId string, msgId string, name string, reqData string, respData string) error { 221 | 222 | log.Debugln("Updating data set - " + name + " data = " + reqData + " respData = " + respData) 223 | 224 | var err error 225 | 226 | if reqData == "" && respData == "" { 227 | return errors.New("A request or a response should be present in an update request") 228 | } 229 | 230 | if reqData != "" { 231 | err = ioutil.WriteFile(filepath.Join(dataDir, specId, msgId, name), []byte(reqData), 0755) 232 | if err != nil { 233 | return err 234 | } 235 | } 236 | 237 | if respData != "" { 238 | err = ioutil.WriteFile(filepath.Join(dataDir, specId, msgId, name+respFileSuffix), []byte(respData), 0755) 239 | if err != nil { 240 | return err 241 | } 242 | } 243 | 244 | return nil 245 | } 246 | 247 | func checkIfExists(specId string, msgId string, name string) (bool, error) { 248 | 249 | //check if the dir exists for this spec and msg 250 | //and if not create one first 251 | 252 | dir, err := os.Open(filepath.Join(dataDir, specId, msgId)) 253 | if err != nil && os.IsNotExist(err) { 254 | err = os.MkdirAll(filepath.Join(dataDir, specId, msgId), 0755) 255 | if err != nil { 256 | return false, err 257 | } 258 | dir, err = os.Open(filepath.Join(dataDir, specId, msgId)) 259 | if err != nil { 260 | return false, err 261 | } 262 | 263 | } 264 | 265 | dirContents, err := dir.Readdir(-1) 266 | if err != nil { 267 | return false, err 268 | } 269 | for _, fileInfo := range dirContents { 270 | if fileInfo.Name() == name { 271 | return true, nil 272 | } 273 | } 274 | 275 | return false, nil 276 | 277 | } 278 | -------------------------------------------------------------------------------- /internal/services/websim/http_transport.go: -------------------------------------------------------------------------------- 1 | package websim 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "github.com/go-kit/kit/endpoint" 8 | loggk "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/log/logrus" 10 | "github.com/go-kit/kit/transport" 11 | httptransport "github.com/go-kit/kit/transport/http" 12 | log "github.com/sirupsen/logrus" 13 | "io/ioutil" 14 | "net/http" 15 | "os" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | type errorWrapper struct { 21 | Error string `json:"error"` 22 | } 23 | 24 | func errorEncoder(_ context.Context, err error, w http.ResponseWriter) { 25 | //TODO:: construct specific error types based on err 26 | w.Header().Add("Access-Control-Allow-Origin", "*") 27 | w.WriteHeader(http.StatusBadRequest) 28 | _ = json.NewEncoder(w).Encode(errorWrapper{Error: err.Error()}) 29 | } 30 | 31 | // encode the http request into a request object 32 | func specsReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 33 | return GetAllSpecsRequest{}, nil 34 | } 35 | 36 | func messages4SpecReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 37 | 38 | reqUri := req.RequestURI 39 | p := strings.LastIndex(reqUri, "/") 40 | specIdParam := reqUri[p+1:] 41 | specId, err := strconv.ParseInt(specIdParam, 10, 0) 42 | return GetMessages4SpecRequest{specId: int(specId)}, err 43 | } 44 | 45 | func getMessageTemplateReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 46 | 47 | reqUri := req.RequestURI 48 | ids := strings.Split(reqUri[len(URLGetMessageTemplate):], "/") 49 | specIdParam := ids[0] 50 | specId, err := strconv.ParseInt(specIdParam, 10, 0) 51 | if err != nil { 52 | return nil, err 53 | } 54 | msgIdParam := ids[1] 55 | msgId, err := strconv.ParseInt(msgIdParam, 10, 0) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return GetMessageTemplateRequest{specId: int(specId), msgId: int(msgId)}, err 60 | } 61 | 62 | func loadOrFetchSavedMessagesReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 63 | 64 | if err := req.ParseForm(); err != nil { 65 | return nil, err 66 | } 67 | 68 | specIdParam := req.Form.Get("specId") 69 | msgIdParam := req.Form.Get("msgId") 70 | dsName := req.Form.Get("dsName") 71 | 72 | if specIdParam == "" || msgIdParam == "" { 73 | return nil, errors.New("isosim: specId and msgId missing in request to -" + req.RequestURI) 74 | } 75 | 76 | specId, err := strconv.Atoi(specIdParam) 77 | if err != nil { 78 | return nil, errors.New("isosim: Invalid specId in request to -" + req.RequestURI) 79 | } 80 | msgId, err := strconv.Atoi(msgIdParam) 81 | if err != nil { 82 | return nil, errors.New("isosim: Invalid msgId in request to -" + req.RequestURI) 83 | } 84 | 85 | return LoadOrFetchSavedMessagesRequest{specId: specId, msgId: msgId, dsName: dsName}, err 86 | } 87 | 88 | func parseTraceReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 89 | 90 | reqUri := req.RequestURI 91 | ids := strings.Split(reqUri[len(URLParseTrace):], "/") 92 | 93 | specIdParam := ids[0] 94 | specId, err := strconv.Atoi(specIdParam) 95 | if err != nil { 96 | return nil, err 97 | } 98 | msgIdParam := ids[1] 99 | msgId, err := strconv.Atoi(msgIdParam) 100 | reqBody, err := ioutil.ReadAll(req.Body) 101 | if err != nil { 102 | return nil, err 103 | } 104 | defer req.Body.Close() 105 | 106 | serviceReq := ParseTraceRequest{ 107 | specId: specId, 108 | msgId: msgId, 109 | msgTrace: string(reqBody), 110 | } 111 | 112 | return serviceReq, nil 113 | 114 | } 115 | 116 | type parseTraceExtReq struct { 117 | SpecName string `json:"spec_name"` 118 | MsgName string `json:"msg_name"` 119 | Data string `json:"trace_data"` 120 | } 121 | 122 | func parseTraceExternalReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 123 | 124 | reqObj := parseTraceExtReq{} 125 | 126 | defer req.Body.Close() 127 | if err := json.NewDecoder(req.Body).Decode(&reqObj); err != nil { 128 | return nil, err 129 | } 130 | 131 | return ParseTraceExtRequest{ 132 | specName: reqObj.SpecName, 133 | msgName: reqObj.MsgName, 134 | msgTrace: reqObj.Data, 135 | }, nil 136 | 137 | } 138 | 139 | func saveMsgReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 140 | 141 | if err := req.ParseForm(); err != nil { 142 | return nil, err 143 | } 144 | 145 | specIdParam := req.PostForm.Get("specId") 146 | msgIdParam := req.PostForm.Get("msgId") 147 | dsName := req.PostForm.Get("dsName") 148 | msgData := req.PostForm.Get("msg") 149 | respMsgData := req.PostForm.Get("response_msg") 150 | updateMsg := req.PostForm.Get("updateMsg") 151 | 152 | if specIdParam == "" || msgIdParam == "" { 153 | return nil, errors.New("isosim: specId and msgId missing in request to -" + req.RequestURI) 154 | } 155 | 156 | specId, err := strconv.Atoi(specIdParam) 157 | if err != nil { 158 | return nil, errors.New("isosim: Invalid specId in request to -" + req.RequestURI) 159 | } 160 | msgId, err := strconv.Atoi(msgIdParam) 161 | if err != nil { 162 | return nil, errors.New("isosim: Invalid msgId in request to -" + req.RequestURI) 163 | } 164 | 165 | defer req.Body.Close() 166 | 167 | var update bool 168 | update, err = strconv.ParseBool(updateMsg) 169 | if err != nil { 170 | update = false 171 | } 172 | 173 | return SaveMsgRequest{ 174 | specId: specId, 175 | msgId: msgId, 176 | msgName: dsName, 177 | msgData: msgData, 178 | responseMsgData: respMsgData, 179 | isUpdate: update, 180 | }, nil 181 | 182 | } 183 | 184 | func sendToHostReqDecoder(ctx context.Context, req *http.Request) (request interface{}, err error) { 185 | 186 | if err := req.ParseForm(); err != nil { 187 | return nil, err 188 | } 189 | 190 | specIdParam := req.PostForm.Get("specId") 191 | msgIdParam := req.Form.Get("msgId") 192 | msg := req.Form.Get("msg") 193 | host := req.Form.Get("host") 194 | port := req.Form.Get("port") 195 | mli := req.Form.Get("mli") 196 | 197 | specId, err := strconv.Atoi(specIdParam) 198 | if err != nil { 199 | return nil, errors.New("isosim: Invalid specId in request to -" + req.RequestURI) 200 | } 201 | msgId, err := strconv.Atoi(msgIdParam) 202 | if err != nil { 203 | return nil, errors.New("isosim: Invalid msgId in request to -" + req.RequestURI) 204 | } 205 | 206 | defer req.Body.Close() 207 | 208 | iPort, err := strconv.Atoi(port) 209 | if err != nil { 210 | return nil, errors.New("isosim: Invalid port in request to -" + req.RequestURI + " port supplied = " + port) 211 | } 212 | 213 | return SendToHostRequest{ 214 | specId: specId, 215 | msgId: msgId, 216 | msgData: msg, 217 | HostIP: host, 218 | Port: iPort, 219 | MLI: mli, 220 | }, nil 221 | 222 | } 223 | 224 | // decode the response into JSON - generic decoder 225 | func respEncoder(ctx context.Context, rw http.ResponseWriter, response interface{}) error { 226 | if f, ok := response.(endpoint.Failer); ok && f.Failed() != nil { 227 | errorEncoder(ctx, f.Failed(), rw) 228 | return nil 229 | } 230 | rw.Header().Add("Access-Control-Allow-Origin", "*") 231 | rw.Header().Add("Content-Type", "application/json; charset=utf-8") 232 | return json.NewEncoder(rw).Encode(response) 233 | } 234 | 235 | func RegisterHTTPTransport() { 236 | 237 | options := []httptransport.ServerOption{ 238 | httptransport.ServerErrorEncoder(errorEncoder), 239 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logrus.NewLogrusLogger(log.New()))), 240 | } 241 | 242 | logger := loggk.NewLogfmtLogger(os.Stderr) 243 | service := New() 244 | 245 | http.Handle(URLAllSpecs, httptransport.NewServer(allSpecsEndpoint(service), specsReqDecoder, respEncoder, options...)) 246 | http.Handle(URLMessages4Spec, httptransport.NewServer(messages4SpecEndpoint(service), messages4SpecReqDecoder, respEncoder, options...)) 247 | http.Handle(URLGetMessageTemplate, httptransport.NewServer(messageTemplateEndpoint(service), getMessageTemplateReqDecoder, respEncoder, options...)) 248 | http.Handle(URLLoadMsg, httptransport.NewServer(loadOrFetchSavedMessagesEndpoint(service), loadOrFetchSavedMessagesReqDecoder, respEncoder, options...)) 249 | 250 | http.Handle(URLParseTraceExternal, httptransport.NewServer(loggingMiddleware(loggk.With(logger, "method", "parseTraceExternal"))(parseTraceExternalEndpoint(service)), 251 | parseTraceExternalReqDecoder, respEncoder, options...)) 252 | 253 | http.Handle(URLParseTrace, httptransport.NewServer(loggingMiddleware(loggk.With(logger, "method", "parseTrace"))(parseTraceEndpoint(service)), parseTraceReqDecoder, respEncoder, options...)) 254 | http.Handle(URLSaveMsg, httptransport.NewServer(saveMsgEndpoint(service), saveMsgReqDecoder, respEncoder, options...)) 255 | 256 | http.Handle(URLSendMessageToHost, httptransport.NewServer(loggingMiddleware(loggk.With(logger, "method", "send2Host"))(sendToHostEndpoint(service)), sendToHostReqDecoder, respEncoder, options...)) 257 | 258 | } 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /internal/services/websim/http_transport_test.go: -------------------------------------------------------------------------------- 1 | package websim 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/go-kit/kit/log/logrus" 8 | "github.com/go-kit/kit/transport" 9 | httptransport "github.com/go-kit/kit/transport/http" 10 | isov2 "github.com/rkbalgi/libiso/v2/iso8583" 11 | log "github.com/sirupsen/logrus" 12 | "github.com/stretchr/testify/assert" 13 | "io/ioutil" 14 | "isosim/internal/db" 15 | "net/http" 16 | "net/http/httptest" 17 | "strconv" 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func init() { 23 | log.SetLevel(log.DebugLevel) 24 | } 25 | 26 | type testHttpHandler struct{} 27 | 28 | func (testHttpHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 29 | options := []httptransport.ServerOption{ 30 | httptransport.ServerErrorEncoder(errorEncoder), 31 | httptransport.ServerErrorHandler(transport.NewLogErrorHandler(logrus.NewLogrusLogger(log.New()))), 32 | } 33 | 34 | s := New() 35 | switch { 36 | case strings.HasPrefix(req.URL.Path, URLAllSpecs): 37 | httptransport.NewServer(allSpecsEndpoint(s), specsReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 38 | 39 | case strings.HasPrefix(req.URL.Path, URLMessages4Spec): 40 | httptransport.NewServer(messages4SpecEndpoint(s), messages4SpecReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 41 | 42 | case strings.HasPrefix(req.URL.Path, URLGetMessageTemplate): 43 | httptransport.NewServer(messageTemplateEndpoint(s), getMessageTemplateReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 44 | 45 | case strings.HasPrefix(req.URL.Path, URLLoadMsg): 46 | httptransport.NewServer(loadOrFetchSavedMessagesEndpoint(s), loadOrFetchSavedMessagesReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 47 | case strings.HasPrefix(req.URL.Path, URLParseTraceExternal): 48 | httptransport.NewServer(parseTraceExternalEndpoint(s), parseTraceExternalReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 49 | 50 | case strings.HasPrefix(req.URL.Path, URLParseTrace): 51 | httptransport.NewServer(parseTraceEndpoint(s), parseTraceReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 52 | case strings.HasPrefix(req.URL.Path, URLSaveMsg): 53 | httptransport.NewServer(saveMsgEndpoint(s), saveMsgReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 54 | 55 | case strings.HasPrefix(req.URL.Path, URLSendMessageToHost): 56 | httptransport.NewServer(sendToHostEndpoint(s), sendToHostReqDecoder, respEncoder, options...).ServeHTTP(rw, req) 57 | 58 | } 59 | 60 | } 61 | 62 | func Test_WebsimHttpService(t *testing.T) { 63 | 64 | if err := db.Init("../../../test/testdata/appdata"); err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | if err := isov2.ReadSpecs("../../../test/testdata/specs"); err != nil { 69 | t.Fatal(err) 70 | } 71 | s := httptest.NewServer(testHttpHandler{}) 72 | defer s.Close() 73 | 74 | t.Run("Get all specs - success", func(t *testing.T) { 75 | req, err := http.NewRequest(http.MethodGet, s.URL+URLAllSpecs, nil) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | resp, err := http.DefaultClient.Do(req) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | var data []byte 84 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 85 | t.Fatal(err) 86 | } 87 | defer resp.Body.Close() 88 | t.Log(string(data)) 89 | allSpecsResponse := &GetAllSpecResponse{} 90 | if err = json.Unmarshal(data, allSpecsResponse); err != nil { 91 | t.Fatal(err) 92 | } 93 | 94 | assert.Condition(t, func() (success bool) { 95 | return len(allSpecsResponse.Specs) > 0 96 | }) 97 | assert.Nil(t, allSpecsResponse.Err) 98 | 99 | }) 100 | 101 | t.Run("Get all message for spec - Success", func(t *testing.T) { 102 | spec := isov2.SpecByName("Iso8583-MiniSpec") 103 | t.Log(spec.ID) 104 | req, err := http.NewRequest(http.MethodGet, s.URL+URLMessages4Spec+"/"+strconv.Itoa(spec.ID), nil) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | resp, err := http.DefaultClient.Do(req) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | var data []byte 113 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 114 | t.Fatal(err) 115 | } 116 | t.Log(string(data)) 117 | defer resp.Body.Close() 118 | 119 | msgs4SpecResponse := &GetMessages4SpecResponse{} 120 | if err = json.Unmarshal(data, msgs4SpecResponse); err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | assert.Equal(t, 2, len(msgs4SpecResponse.Messages)) 125 | assert.Nil(t, msgs4SpecResponse.Err) 126 | 127 | }) 128 | 129 | t.Run("Get all message for spec - Unknown spec - Failure", func(t *testing.T) { 130 | 131 | req, err := http.NewRequest(http.MethodGet, s.URL+URLMessages4Spec+"/0", nil) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | resp, err := http.DefaultClient.Do(req) 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | var data []byte 140 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 141 | t.Fatal(err) 142 | } 143 | t.Log(string(data)) 144 | defer resp.Body.Close() 145 | 146 | msgs4SpecResponse := &GetMessages4SpecResponse{} 147 | if err = json.Unmarshal(data, msgs4SpecResponse); err != nil { 148 | t.Fatal(err) 149 | } 150 | t.Log(msgs4SpecResponse.Err) 151 | assert.Equal(t, http.StatusBadRequest, resp.StatusCode) 152 | 153 | }) 154 | 155 | t.Run("Get MessageTemplate", func(t *testing.T) { 156 | 157 | req, err := http.NewRequest(http.MethodGet, s.URL+URLGetMessageTemplate+"1/1", nil) 158 | if err != nil { 159 | t.Fatal(err) 160 | } 161 | resp, err := http.DefaultClient.Do(req) 162 | if err != nil { 163 | t.Fatal(err) 164 | } 165 | var data []byte 166 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 167 | t.Fatal(err) 168 | } 169 | t.Log(string(data)) 170 | defer resp.Body.Close() 171 | 172 | response := &GetMessageTemplateResponse{} 173 | if err = json.Unmarshal(data, response); err != nil { 174 | t.Fatal(err) 175 | } 176 | 177 | assert.True(t, response.Fields != nil && len(response.Fields) > 0) 178 | 179 | }) 180 | 181 | t.Run("Get Saved Message - Specific", func(t *testing.T) { 182 | 183 | req, err := http.NewRequest(http.MethodGet, s.URL+URLLoadMsg+"/?specId=1&msgId=1&dsName=TC_000_Approved", nil) 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | resp, err := http.DefaultClient.Do(req) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | var data []byte 192 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 193 | t.Fatal(err) 194 | } 195 | t.Log(string(data)) 196 | defer resp.Body.Close() 197 | 198 | response := &LoadOrFetchSavedMessagesResponse{} 199 | if err = json.Unmarshal(data, response); err != nil { 200 | t.Fatal(err) 201 | } 202 | 203 | assert.True(t, response.SavedMsg != nil && response.SavedMessages == nil) 204 | 205 | }) 206 | 207 | t.Run("Get Saved Message - All", func(t *testing.T) { 208 | 209 | req, err := http.NewRequest(http.MethodGet, s.URL+URLLoadMsg+"/?specId=1&msgId=1", nil) 210 | if err != nil { 211 | t.Fatal(err) 212 | } 213 | resp, err := http.DefaultClient.Do(req) 214 | if err != nil { 215 | t.Fatal(err) 216 | } 217 | var data []byte 218 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 219 | t.Fatal(err) 220 | } 221 | t.Log(string(data)) 222 | defer resp.Body.Close() 223 | 224 | response := &LoadOrFetchSavedMessagesResponse{} 225 | if err = json.Unmarshal(data, response); err != nil { 226 | t.Fatal(err) 227 | } 228 | 229 | assert.True(t, response.SavedMsg == nil && response.SavedMessages != nil && len(response.SavedMessages) > 0) 230 | 231 | }) 232 | 233 | t.Run("Get Saved Message - Invalid Spec", func(t *testing.T) { 234 | 235 | req, err := http.NewRequest(http.MethodGet, s.URL+URLLoadMsg+"/?specId=0&msgId=1", nil) 236 | if err != nil { 237 | t.Fatal(err) 238 | } 239 | resp, err := http.DefaultClient.Do(req) 240 | if err != nil { 241 | t.Fatal(err) 242 | } 243 | 244 | assert.Equal(t, 400, resp.StatusCode) 245 | 246 | }) 247 | 248 | t.Run("Parse Trace", func(t *testing.T) { 249 | 250 | trace := "313130303730323030303030323030303130303031343536353534343333333637373736303034303030303030303030303030303930313233343536313356554433367776f200302020201234567890abcd11" 251 | req, err := http.NewRequest(http.MethodPost, s.URL+URLParseTrace+"3/3", bytes.NewReader([]byte(trace))) 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | req.Header.Set("Content-Type", "text/plain") 256 | resp, err := http.DefaultClient.Do(req) 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | var data []byte 261 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 262 | t.Fatal(err) 263 | } 264 | t.Log(string(data), resp.Header) 265 | defer resp.Body.Close() 266 | 267 | assert.Equal(t, 200, resp.StatusCode) 268 | 269 | }) 270 | 271 | t.Run("Parse Trace External", func(t *testing.T) { 272 | 273 | trace := "313130303730323030303030323030303130303031343536353534343333333637373736303034303030303030303030303030303930313233343536313356554433367776f200302020201234567890abcd11" 274 | reqObj := parseTraceExtReq{ 275 | SpecName: "ISO8583-Test", 276 | MsgName: "1100(A) - Authorization", 277 | Data: trace, 278 | } 279 | 280 | jsonReq, err := json.Marshal(reqObj) 281 | if err != nil { 282 | t.Fatal(err) 283 | } 284 | 285 | req, err := http.NewRequest(http.MethodPost, s.URL+URLParseTraceExternal, bytes.NewReader(jsonReq)) 286 | if err != nil { 287 | t.Fatal(err) 288 | } 289 | resp, err := http.DefaultClient.Do(req) 290 | if err != nil { 291 | t.Fatal(err) 292 | } 293 | var data []byte 294 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 295 | t.Fatal(err) 296 | } 297 | t.Log(string(data)) 298 | defer resp.Body.Close() 299 | 300 | assert.Equal(t, 200, resp.StatusCode) 301 | 302 | }) 303 | 304 | t.Run("Test save message with update=TRUE", func(t *testing.T) { 305 | 306 | requestObj := `[{"ID":3,"Name":"PAN","Value":"56554433367776"},{"ID":8,"Name":"Amount","Value":"000000000090"},{"ID":2,"Name":"Bitmap","Value":"0111000000100000000000000000000000100000000000000001000000000000"},{"ID":9,"Name":"STAN","Value":"123456"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":10,"Name":"Track 2","Value":"56554433367776f20030202020"},{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":14,"Name":"PIN Data","Value":"1234567890abcd11"}]` 307 | 308 | requestData := fmt.Sprintf("specId=%d&msgId=%d&dsName=save_test_01&updateMsg=true&msg=%s", 3, 3, requestObj) 309 | 310 | t.Log("requestData in SaveMsg request", requestData) 311 | req, err := http.NewRequest(http.MethodPost, s.URL+URLSaveMsg, bytes.NewReader([]byte(requestData))) 312 | if err != nil { 313 | t.Fatal(err) 314 | } 315 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 316 | resp, err := http.DefaultClient.Do(req) 317 | if err != nil { 318 | t.Fatal(err) 319 | } 320 | var data []byte 321 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 322 | t.Fatal(err) 323 | } 324 | t.Log(string(data)) 325 | defer resp.Body.Close() 326 | 327 | assert.Equal(t, 200, resp.StatusCode) 328 | 329 | }) 330 | 331 | t.Run("Test send to host", func(t *testing.T) { 332 | 333 | t.SkipNow() 334 | 335 | requestObj := `[{"ID":1,"Name":"Message Type","Value":"1100"},{"ID":2,"Name":"Bitmap","Value":"0111000000100000000000000000000000100000000000000001000000000000"},{"ID":8,"Name":"Amount","Value":"000000000090"},{"ID":3,"Name":"PAN","Value":"56554433367776"},{"ID":14,"Name":"PIN Data","Value":"1234567890abcd11"},{"ID":4,"Name":"Processing Code","Value":"004000"},{"ID":9,"Name":"STAN","Value":"123456"},{"ID":10,"Name":"Track 2","Value":"56554433367776f20030202020"}]` 336 | 337 | requestData := fmt.Sprintf("specId=%d&msgId=%d&mli=2I&host=%s&port=%d&msg=%s", 3, 3, "localhost", 7777, requestObj) 338 | 339 | t.Log("requestData in SendMessageToHst request", requestData) 340 | 341 | req, err := http.NewRequest(http.MethodPost, s.URL+URLSendMessageToHost, bytes.NewReader([]byte(requestData))) 342 | if err != nil { 343 | t.Fatal(err) 344 | } 345 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 346 | resp, err := http.DefaultClient.Do(req) 347 | if err != nil { 348 | t.Fatal(err) 349 | } 350 | var data []byte 351 | if data, err = ioutil.ReadAll(resp.Body); err != nil { 352 | t.Fatal(err) 353 | } 354 | t.Log(string(data), resp.Header) 355 | defer resp.Body.Close() 356 | 357 | assert.Equal(t, 200, resp.StatusCode) 358 | 359 | }) 360 | 361 | } 362 | --------------------------------------------------------------------------------