#!/usr/bin/env elixir # Mock Upstream Server for testing EnhancedProtocol # Run with: elixir mock_upstream_server.exs Mix.install([]) defmodule MockUpstreamServer do use Bitwise @moduledoc """ Simple mock upstream server that receives ISO8583 messages and returns proper responses for testing the EnhancedProtocol. """ require Logger @port 9583 @timeout 30_000 def start do IO.puts("πŸš€ Starting Mock Upstream Server on port #{@port}") {:ok, listen_socket} = :gen_tcp.listen(@port, [ :binary, active: false, reuseaddr: true, packet: 0 ]) IO.puts("βœ… Mock upstream server listening on port #{@port}") IO.puts("πŸ“ Ready to receive ISO8583 messages from EnhancedProtocol") accept_loop(listen_socket) end defp accept_loop(listen_socket) do case :gen_tcp.accept(listen_socket, @timeout) do {:ok, client_socket} -> IO.puts("\nπŸ”— New upstream connection accepted") # Handle client in separate process spawn(fn -> handle_client(client_socket) end) # Continue accepting connections accept_loop(listen_socket) {:error, reason} -> IO.puts("❌ Accept failed: #{inspect(reason)}") accept_loop(listen_socket) end end defp handle_client(socket) do case :gen_tcp.recv(socket, 2, @timeout) do {:ok, <>} -> IO.puts("πŸ“ Receiving message of #{length} bytes") case :gen_tcp.recv(socket, length, @timeout) do {:ok, message_data} -> IO.puts("πŸ“¦ Received message: #{byte_size(message_data)} bytes") IO.puts(" Raw: #{inspect(message_data, limit: 50)}") # Process the message and create response response = create_response(message_data) # Send response back send_response(socket, response) # Keep connection open for more messages handle_client(socket) {:error, reason} -> IO.puts("❌ Failed to receive message data: #{inspect(reason)}") :gen_tcp.close(socket) end {:error, reason} -> IO.puts("πŸ”Œ Connection closed: #{inspect(reason)}") :gen_tcp.close(socket) end end defp create_response(message_data) when is_binary(message_data) do try do # Parse incoming message if byte_size(message_data) >= 4 do <> = message_data IO.puts(" πŸ“‹ Request MTI: #{mti}") # Convert request MTI to response MTI response_mti = case mti do "0200" -> "0210" # Authorization Request -> Authorization Response "0400" -> "0410" # Reversal Request -> Reversal Response "0800" -> "0810" # Network Management -> Network Management Response other -> # Default conversion: 0xxx -> 1xxx, 2xxx -> 3xxx case String.at(other, 0) do "0" -> "1" <> String.slice(other, 1, 3) "2" -> "3" <> String.slice(other, 1, 3) _ -> other end end IO.puts(" πŸ“€ Response MTI: #{response_mti}") if byte_size(rest) >= 8 do <> = rest IO.puts(" πŸ—ΊοΈ Request Bitmap: #{inspect(bitmap)}") # Parse request fields request_fields = parse_fields(bitmap, fields) IO.puts(" πŸ“ Request Fields: #{inspect(request_fields, limit: 10)}") # Create response fields response_fields = create_response_fields(request_fields) # Build response message response_bitmap = create_response_bitmap(response_fields) response_field_data = pack_response_fields(response_fields) response_message = response_mti <> response_bitmap <> response_field_data IO.puts(" βœ… Response created: #{byte_size(response_message)} bytes") response_message else # Minimal response if bitmap parsing fails response_mti <> <<0, 0, 0, 0, 0, 0, 0, 0>> end else # Invalid message, return error response "0110" <> <<0, 0, 0, 0, 0, 0, 0, 0>> end rescue exception -> IO.puts(" ❌ Error creating response: #{inspect(exception)}") # Return system error response "0110" <> <<0, 0, 0, 0, 0, 0, 0, 0>> end end defp parse_fields(bitmap, field_data) when byte_size(bitmap) == 8 do # Simple field parsing - this is a mock, so we'll do basic parsing field_numbers = parse_bitmap_to_fields(bitmap) # For this mock, we'll just store the field numbers we detected # In a real implementation, you'd parse each field according to its type %{ detected_fields: field_numbers, raw_data: field_data } end defp parse_bitmap_to_fields(bitmap) do bitmap |> :binary.bin_to_list() |> Enum.with_index() |> Enum.flat_map(fn {byte, byte_index} -> 0..7 |> Enum.filter(fn bit -> Bitwise.band(byte, Bitwise.bsl(1, (7 - bit))) != 0 end) |> Enum.map(fn bit -> byte_index * 8 + bit + 1 end) end) end defp create_response_fields(request_fields) do # Create typical response fields response_fields = %{ 39 => "00", # Response Code - Approved 11 => "123456", # STAN (echo from request) 37 => "123456789012", # RRN (echo from request) 41 => "TERM0001", # Terminal ID (echo from request) 42 => "MERCHANT000001" # Merchant ID (echo from request) } # Add detected request fields that should be echoed detected = Map.get(request_fields, :detected_fields, []) # Echo common fields if they were in the request echoed_fields = Enum.reduce(detected, response_fields, fn field_num, acc -> case field_num do 2 -> Map.put(acc, 2, "4111111111111111") # Echo PAN (masked) 3 -> Map.put(acc, 3, "000000") # Echo Processing Code 4 -> Map.put(acc, 4, "000001000000") # Echo Amount _ -> acc end end) echoed_fields end defp create_response_bitmap(response_fields) do field_numbers = Map.keys(response_fields) # Create 8-byte bitmap bitmap = <<0, 0, 0, 0, 0, 0, 0, 0>> Enum.reduce(field_numbers, bitmap, fn field_num, acc -> if field_num <= 64 do # Primary bitmap only byte_pos = div(field_num - 1, 8) bit_pos = rem(field_num - 1, 8) <> = acc new_byte = Bitwise.bor(byte, Bitwise.bsl(1, (7 - bit_pos))) pre <> <> <> post else acc end end) end defp pack_response_fields(response_fields) do # Pack fields in order field_numbers = Map.keys(response_fields) |> Enum.sort() Enum.reduce(field_numbers, <<>>, fn field_num, acc -> value = Map.get(response_fields, field_num) packed_field = pack_response_field(field_num, value) acc <> packed_field end) end defp pack_response_field(field_number, value) do case field_number do 2 -> # PAN - LLVAR length = byte_size(value) <> <> value 39 -> # Response Code - Fixed 2 chars String.pad_trailing(value, 2) _ -> # Default: use value as-is to_string(value) end end defp send_response(socket, response_data) do # Send with length prefix length = byte_size(response_data) message = <> <> response_data case :gen_tcp.send(socket, message) do :ok -> IO.puts(" πŸ“€ Response sent: #{byte_size(response_data)} bytes") :ok {:error, reason} -> IO.puts(" ❌ Failed to send response: #{inspect(reason)}") {:error, reason} end end end # Start the mock server IO.puts("=== Mock Upstream Server ===") IO.puts("This server will:") IO.puts("1. Listen on port 9583") IO.puts("2. Receive ISO8583 messages from EnhancedProtocol") IO.puts("3. Return proper responses for testing") IO.puts("4. Log all message details") IO.puts("") MockUpstreamServer.start()