# Field 52 Binary Data Fix - October 13, 2025

## Issue Summary

**Problem:** Field 52 (PIN Data) was being incorrectly converted from binary to hex string representation, causing the field to double in size and become unparseable.

**Symptom:**
- **Expected:** 8-byte binary data `A102EBF1AF3BA715` (hex representation)
- **Actual Output:** 16-byte ASCII string `61313032656266316166336261373135` (hex of "a102ebf1af3ba715")

**Impact:** Upstream systems could not parse messages because Field 52 was 16 bytes instead of 8 bytes.

---

## Root Cause

In `base_field_packager.ex`, the `pack/1` function was converting **all** non-UTF8 binary data to hex strings:

```elixir
# BEFORE (INCORRECT)
is_binary(value) and not String.valid?(value) ->
  # Handle binary data - convert to hex string like jPOS
  Base.encode16(value, case: :lower)
```

This caused IFB_BINARY fields (like Field 52 PIN data) to be incorrectly converted:
1. Binary `<<0xA1, 0x02, 0xEB, 0xF1, 0xAF, 0x3B, 0xA7, 0x15>>` → 8 bytes
2. Converted to hex string `"a102ebf1af3ba715"` → 16 characters
3. Encoded as ASCII `<<0x61, 0x31, 0x30, 0x32, ...>>` → 16 bytes ❌

**Result:** Field length doubled from 8 to 16 bytes, breaking message parsing.

---

## Solution

Modified `pack/1` to check the packager's interpreter type and preserve raw binary data for IFB_BINARY fields:

```elixir
# AFTER (CORRECT)
# For binary interpreter fields (IFB_BINARY), keep raw binary data
# This matches jPOS behavior: c.getBytes() returns raw bytes
packager.interpreter == {:binary_interpreter} and is_binary(value) ->
  value
```

**File Changed:**
- `lib/da_product_app/mercury_iso8583/packagers/field_packagers/base_field_packager.ex` (lines 62-64)

---

## Technical Details

### Java jPOS Behavior (Correct)
```java
// ISOBinaryFieldPackager.pack()
byte[] data = c.getBytes();  // Gets raw bytes directly
interpreter.interpret(data, ret, packedLength);  // System.arraycopy (1:1 copy)
```

### Elixir Behavior (Fixed)
```elixir
# Now matches Java behavior for binary fields
data = if packager.interpreter == {:binary_interpreter} do
  value  # Keep raw binary
else
  # Handle other field types
end
```

---

## Verification

**Test Case:**
```
Input:  Field 52 = <<0xA1, 0x02, 0xEB, 0xF1, 0xAF, 0x3B, 0xA7, 0x15>>
Output: A102EBF1AF3BA715 (8 bytes)
Status: ✅ Verified with actual packet
```

**Log Evidence:**
```
Incoming (correct):  A102EBF1AF3BA715
Outgoing (before):   61313032656266316166336261373135 ❌
Outgoing (after):    A102EBF1AF3BA715 ✅
```

---

## Affected Fields

This fix applies to all IFB_BINARY fields in ISO87BPackager:

| Field | Description | Length | Status |
|-------|-------------|--------|--------|
| 52 | PIN Data | 8 bytes | ✅ Fixed & Verified |
| 64 | Message Authentication Code | 8 bytes | ✅ Fixed |
| 65 | Bitmap Extended | 1 byte | ✅ Fixed |
| 96 | Message Security Code | 16 bytes | ✅ Fixed |
| 128 | MAC 2 | 8 bytes | ✅ Fixed |

---

## Related Issues

This fix also resolves potential issues with:
- **Field 64:** Message Authentication Code (MAC)
- **Field 128:** Secondary MAC
- **Field 96:** Message Security Code
- Any custom binary fields using IFB_BINARY

---

## Testing

**Manual Testing:**
- ✅ Verified with actual ISO8583 message packets
- ✅ Field 52 now correctly maintains 8-byte binary format
- ✅ Upstream parsing successful

**Automated Testing:**
- Test script created: `test_field52_fix.exs`
- Round-trip test: Pack → Unpack → Verify data preservation

---

## Code Changes

### File: `lib/da_product_app/mercury_iso8583/packagers/field_packagers/base_field_packager.ex`

**Lines 62-77 (Modified):**

```elixir
def pack(%{__struct__: DaProductApp.MercuryISO8583.Packagers.ISOComponent, value: value, packager: packager}) when not is_nil(packager) do
  # Handle different value types based on the packager's interpreter type
  data = cond do
    # For binary interpreter fields (IFB_BINARY), keep raw binary data
    # This matches jPOS behavior: c.getBytes() returns raw bytes
    packager.interpreter == {:binary_interpreter} and is_binary(value) ->
      value
    
    # For non-binary fields with binary content, convert to hex string
    is_binary(value) and not String.valid?(value) ->
      Base.encode16(value, case: :lower)
    
    is_binary(value) ->
      # Handle string data directly
      value
      
    true ->
      # Convert other types to string
      to_string(value)
  end
  
  pack_with_packager(data, packager)
end
```

---

## Compatibility

**jPOS Compatibility:** ✅ Now matches Java jPOS behavior exactly
- Java: `c.getBytes()` → raw bytes
- Elixir: `value` → raw bytes (for binary fields)

**Backward Compatibility:** ✅ No breaking changes for non-binary fields

---

## Recommendations

1. ✅ **Deploy Fix:** Ready for production deployment
2. ✅ **Monitor:** Watch for any Field 52/64/128 parsing errors in logs
3. 📋 **Future:** Consider adding unit tests for all IFB_BINARY fields
4. 📋 **Documentation:** Update ISO8583 field handling documentation

---

## Related Documentation

- `docs/IFB_BINARY_COMPARISON.md` - Detailed Java vs Elixir comparison
- `docs/2025-09-26_ysp_config_consolidation_summary.md` - YSP configuration
- `iso/IFB_BINARY.java` - Java reference implementation

---

**Issue Resolved:** ✅ October 13, 2025  
**Verified By:** User with actual packet data  
**Status:** Production Ready
