From f716457c1fa99a9acf8416694f90662a083b1ad4 Mon Sep 17 00:00:00 2001 From: MathewFrancis Date: Sat, 23 Aug 2025 12:35:58 +0530 Subject: [PATCH] ABS service --- .../cezenBTC/controller/AbsController.java | 205 +--------------- .../service/ABS/ABSServiceForLogIn.java | 231 ++++++++++++++++++ 2 files changed, 238 insertions(+), 198 deletions(-) create mode 100644 springHorse/src/main/java/com/example/cezenBTC/service/ABS/ABSServiceForLogIn.java diff --git a/springHorse/src/main/java/com/example/cezenBTC/controller/AbsController.java b/springHorse/src/main/java/com/example/cezenBTC/controller/AbsController.java index e42170f..68815dc 100644 --- a/springHorse/src/main/java/com/example/cezenBTC/controller/AbsController.java +++ b/springHorse/src/main/java/com/example/cezenBTC/controller/AbsController.java @@ -1,36 +1,23 @@ package com.example.cezenBTC.controller; -import com.example.cezenBTC.DTO.bridge.RcvHeader; -import com.example.cezenBTC.DTO.bridge.RcvLog; -import com.example.cezenBTC.absbridge.core.AbsProtocol; import com.example.cezenBTC.absbridge.model.HealthResponse; import com.example.cezenBTC.absbridge.model.LoginRequest; import com.example.cezenBTC.config.AbsClient; +import com.example.cezenBTC.service.ABS.ABSServiceForLogIn; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; -import java.util.HexFormat; -import java.util.LinkedHashMap; import java.util.Map; @RestController @RequestMapping("/abs") public class AbsController { - private final AbsClient client; - private final String target; - @Value("${server.port}") - private int httpPort; - - public AbsController(AbsClient client) { - this.client = client; - this.target = client.target(); - } + @Autowired + ABSServiceForLogIn absServiceForLogIn; @GetMapping("/test") public String root() { @@ -39,195 +26,17 @@ public class AbsController { @GetMapping("/health") public HealthResponse health() { - try { - byte[] header = AbsProtocol.packSndHeader(AbsProtocol.ABS_POLL, 0, 0, 0, 0, 0); - byte[] reply = client.roundtrip(header, 2000); // 2s tight timeout - var hdr = AbsProtocol.parseRcvHeaderFlexible( - reply.length >= 24 ? java.util.Arrays.copyOf(reply, 24) - : java.util.Arrays.copyOf(reply, reply.length) - ); - boolean ok = (hdr.nRetCd() == AbsProtocol.SUCCESS); - return new HealthResponse(ok, client.target(), ok ? null : ("ABS poll retCd=" + hdr.nRetCd())); - } catch (Exception e) { - return new HealthResponse(false, client.target(), "ABS poll failed: " + e.getMessage()); - } + return this.absServiceForLogIn.healthData(); } @PostMapping(value = "/poll", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public Object poll(@RequestBody(required = false) Map body) { - try { - int btMake = extractBtMake(body); - - byte[] header = AbsProtocol.packSndHeader( - AbsProtocol.ABS_POLL, 0, 0, 0, 0, btMake - ); - - byte[] reply = client.roundtrip(header, 7000); - byte[] hdrSlice = slice(reply, 0, Math.min(reply.length, 24)); - RcvHeader parsed = AbsProtocol.parseRcvHeaderFlexible(hdrSlice); - - RcvHeader normalized = new RcvHeader( - parsed.nTxnCd() == 0 ? AbsProtocol.ABS_POLL : parsed.nTxnCd(), - parsed.nRetCd(), - parsed.nNumRecs(), - parsed.nTxnId(), - parsed.cBtMake(), - 18 - ); - - Map out = new LinkedHashMap<>(); - out.put("ok", true); - out.put("target", target); - out.put("sentHeaderHex", toHex(header)); - out.put("replyBytes", reply.length); - out.put("replyHexFirst64", toHex(slice(reply, 0, Math.min(64, reply.length)))); - out.put("parsedRcvHeaderRaw", parsed); - out.put("normalizedRcvHeader", normalized); - out.put("normalizedHeaderHex", toHex(AbsProtocol.buildRcvHeader18(normalized))); - out.put("success", parsed.nRetCd() == AbsProtocol.SUCCESS); - return out; - } catch (Exception e) { - return Map.of("ok", false, "error", e.toString()); - } + return this.absServiceForLogIn.poling(body); } @PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public Object login(@RequestBody LoginRequest req) { - try { - if (isEmpty(req.opCard) || isEmpty(req.btId) || (isEmpty(req.password) && isEmpty(req.passwordEnc))) { - return Map.of("ok", false, "error", "Missing opCard, btId, and either password or passwordEnc"); - } - String passwd; - Map encMeta = new LinkedHashMap<>(); - if (req.passwordEnc != null && !req.passwordEnc.isBlank()) { - passwd = req.passwordEnc; - encMeta.put("used", false); - encMeta.put("providedPasswordEnc", true); - } else { - AbsProtocol.Enc enc = AbsProtocol.encryptPasswordLikeMFC(req.password); - passwd = enc.enc(); - encMeta.put("used", true); - encMeta.put("utcSeconds", enc.utcSeconds()); - encMeta.put("key", enc.key()); - encMeta.put("encPasswd", passwd); - } - - int btMake = extractBtMake(req.btMake); - - byte[] sndHeader = AbsProtocol.packSndHeader( - AbsProtocol.LOG, AbsProtocol.LOGBT, 1, 1, 0, btMake - ); - byte[] sndLog = AbsProtocol.packSndLog( - safe(req.usrId), safe(req.opCard), passwd, safe(req.btId) - ); - - byte[] sendBuf = concat(sndHeader, sndLog); - byte[] reply = client.roundtrip(sendBuf, 10_000); - - byte[] hdrSlice = slice(reply, 0, Math.min(reply.length, 24)); - RcvHeader rcvHeader = AbsProtocol.parseRcvHeaderFlexible(hdrSlice); - - Map json = new LinkedHashMap<>(); - json.put("ok", true); - json.put("target", target); - json.put("sentHeaderHex", toHex(sndHeader)); - json.put("sentBodyHex", toHex(sndLog)); - json.put("replyBytes", reply.length); - json.put("replyHexFirst64", toHex(slice(reply, 0, Math.min(64, reply.length)))); - json.put("rcvHeaderRaw", rcvHeader); - json.put("success", rcvHeader.nRetCd() == AbsProtocol.SUCCESS); - json.put("encryption", encMeta); - - // ---- body offset logic (like Node) ---- - int bodyOffset = rcvHeader.size(); - if (reply.length >= bodyOffset + 4) { - int maybeOp = leInt(reply, bodyOffset); - if (maybeOp == AbsProtocol.LOGBT || maybeOp == AbsProtocol.LOG || maybeOp == 0 - || (maybeOp >= 1000 && maybeOp <= 10000)) { - bodyOffset += 4; - json.put("rcvExtraOpCd", maybeOp); - } else { - boolean looksYear = looksYear(reply, bodyOffset); - boolean looksYearFwd = looksYear(reply, bodyOffset + 4); - if (!looksYear && looksYearFwd) bodyOffset += 4; - } - } - if (bodyOffset >= 4 && byteAt(reply, bodyOffset) == (byte)'/') { - if (byteAt(reply, bodyOffset - 4) == '2' && byteAt(reply, bodyOffset - 3) == '0') { - bodyOffset -= 4; - } - } - - json.put("offsetProbe", Map.of( - "bodyOffset", bodyOffset, - "around", new String(slice(reply, Math.max(0, bodyOffset - 8), - Math.min(reply.length, bodyOffset + 16)), StandardCharsets.US_ASCII) - )); - - if ((boolean) json.get("success") && reply.length >= bodyOffset + 20) { - try { - RcvLog log = AbsProtocol.parseRcvLog(reply, bodyOffset); - json.put("log", log); - } catch (Exception ex) { - json.put("parseLogError", ex.toString()); - } - } - - return json; - } catch (Exception e) { - return Map.of("ok", false, "error", e.toString()); - } + return absServiceForLogIn.loginRew(req); } - - // ---------- helpers ---------- - private static byte[] slice(byte[] a, int off, int end) { - int n = Math.max(0, end - off); - byte[] out = new byte[n]; - System.arraycopy(a, Math.max(0, off), out, 0, n); - return out; - } - - private static String toHex(byte[] a) { - return HexFormat.of().formatHex(a); - } - - private static byte[] concat(byte[] a, byte[] b) { - byte[] out = new byte[a.length + b.length]; - System.arraycopy(a, 0, out, 0, a.length); - System.arraycopy(b, 0, out, a.length, b.length); - return out; - } - - private static int leInt(byte[] a, int off) { - ByteBuffer bb = ByteBuffer.wrap(a, off, 4).order(ByteOrder.LITTLE_ENDIAN); - return bb.getInt(); - } - - private static boolean looksYear(byte[] a, int off) { - if (off + 1 >= a.length) return false; - return a[off] == '2' && a[off + 1] == '0'; - } - - private static byte byteAt(byte[] a, int off) { - return off >= 0 && off < a.length ? a[off] : 0; - } - - private static int extractBtMake(Object btMakeAny) { - if (btMakeAny == null) return 0; - if (btMakeAny instanceof Number n) return n.intValue(); - if (btMakeAny instanceof String s) { - return s.isEmpty() ? 0 : (int) s.charAt(0); - } - return 0; - } - - private static int extractBtMake(Map body) { - if (body == null) return 0; - Object v = body.get("btMake"); - return extractBtMake(v); - } - - private static String safe(String s) { return s == null ? "" : s; } - private static boolean isEmpty(String s) { return s == null || s.isBlank(); } } \ No newline at end of file diff --git a/springHorse/src/main/java/com/example/cezenBTC/service/ABS/ABSServiceForLogIn.java b/springHorse/src/main/java/com/example/cezenBTC/service/ABS/ABSServiceForLogIn.java new file mode 100644 index 0000000..3b9ee7b --- /dev/null +++ b/springHorse/src/main/java/com/example/cezenBTC/service/ABS/ABSServiceForLogIn.java @@ -0,0 +1,231 @@ +package com.example.cezenBTC.service.ABS; + +import com.example.cezenBTC.DTO.bridge.RcvHeader; +import com.example.cezenBTC.DTO.bridge.RcvLog; +import com.example.cezenBTC.absbridge.core.AbsProtocol; +import com.example.cezenBTC.absbridge.model.HealthResponse; +import com.example.cezenBTC.absbridge.model.LoginRequest; +import com.example.cezenBTC.config.AbsClient; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; +import java.util.LinkedHashMap; +import java.util.Map; + +@Service +public class ABSServiceForLogIn { + + + @Value("${server.port}") + private int httpPort; + + + private final AbsClient client; + private final String target; + + public ABSServiceForLogIn(AbsClient client) { + this.client = client; + this.target = client.target(); + } + + + public HealthResponse healthData(){ + try { + byte[] header = AbsProtocol.packSndHeader(AbsProtocol.ABS_POLL, 0, 0, 0, 0, 0); + byte[] reply = client.roundtrip(header, 2000); // 2s tight timeout + var hdr = AbsProtocol.parseRcvHeaderFlexible( + reply.length >= 24 ? java.util.Arrays.copyOf(reply, 24) + : java.util.Arrays.copyOf(reply, reply.length) + ); + boolean ok = (hdr.nRetCd() == AbsProtocol.SUCCESS); + return new HealthResponse(ok, client.target(), ok ? null : ("ABS poll retCd=" + hdr.nRetCd())); + } catch (Exception e) { + return new HealthResponse(false, client.target(), "ABS poll failed: " + e.getMessage()); + } + } + + public Object poling(Map body){ + + try { + int btMake = extractBtMake(body); + + byte[] header = AbsProtocol.packSndHeader( + AbsProtocol.ABS_POLL, 0, 0, 0, 0, btMake + ); + + byte[] reply = client.roundtrip(header, 7000); + byte[] hdrSlice = slice(reply, 0, Math.min(reply.length, 24)); + RcvHeader parsed = AbsProtocol.parseRcvHeaderFlexible(hdrSlice); + + RcvHeader normalized = new RcvHeader( + parsed.nTxnCd() == 0 ? AbsProtocol.ABS_POLL : parsed.nTxnCd(), + parsed.nRetCd(), + parsed.nNumRecs(), + parsed.nTxnId(), + parsed.cBtMake(), + 18 + ); + + Map out = new LinkedHashMap<>(); + out.put("ok", true); + out.put("target", target); + out.put("sentHeaderHex", toHex(header)); + out.put("replyBytes", reply.length); + out.put("replyHexFirst64", toHex(slice(reply, 0, Math.min(64, reply.length)))); + out.put("parsedRcvHeaderRaw", parsed); + out.put("normalizedRcvHeader", normalized); + out.put("normalizedHeaderHex", toHex(AbsProtocol.buildRcvHeader18(normalized))); + out.put("success", parsed.nRetCd() == AbsProtocol.SUCCESS); + return out; + } catch (Exception e) { + return Map.of("ok", false, "error", e.toString()); + } + } + + + public Object loginRew(LoginRequest req){ + + try { + if (isEmpty(req.opCard) || isEmpty(req.btId) || (isEmpty(req.password) && isEmpty(req.passwordEnc))) { + return Map.of("ok", false, "error", "Missing opCard, btId, and either password or passwordEnc"); + } + + String passwd; + Map encMeta = new LinkedHashMap<>(); + if (req.passwordEnc != null && !req.passwordEnc.isBlank()) { + passwd = req.passwordEnc; + encMeta.put("used", false); + encMeta.put("providedPasswordEnc", true); + } else { + AbsProtocol.Enc enc = AbsProtocol.encryptPasswordLikeMFC(req.password); + passwd = enc.enc(); + encMeta.put("used", true); + encMeta.put("utcSeconds", enc.utcSeconds()); + encMeta.put("key", enc.key()); + encMeta.put("encPasswd", passwd); + } + + int btMake = extractBtMake(req.btMake); + + byte[] sndHeader = AbsProtocol.packSndHeader( + AbsProtocol.LOG, AbsProtocol.LOGBT, 1, 1, 0, btMake + ); + byte[] sndLog = AbsProtocol.packSndLog( + safe(req.usrId), safe(req.opCard), passwd, safe(req.btId) + ); + + byte[] sendBuf = concat(sndHeader, sndLog); + byte[] reply = client.roundtrip(sendBuf, 10_000); + + byte[] hdrSlice = slice(reply, 0, Math.min(reply.length, 24)); + RcvHeader rcvHeader = AbsProtocol.parseRcvHeaderFlexible(hdrSlice); + + Map json = new LinkedHashMap<>(); + json.put("ok", true); + json.put("target", target); + json.put("sentHeaderHex", toHex(sndHeader)); + json.put("sentBodyHex", toHex(sndLog)); + json.put("replyBytes", reply.length); + json.put("replyHexFirst64", toHex(slice(reply, 0, Math.min(64, reply.length)))); + json.put("rcvHeaderRaw", rcvHeader); + json.put("success", rcvHeader.nRetCd() == AbsProtocol.SUCCESS); + json.put("encryption", encMeta); + + // ---- body offset logic (like Node) ---- + int bodyOffset = rcvHeader.size(); + if (reply.length >= bodyOffset + 4) { + int maybeOp = leInt(reply, bodyOffset); + if (maybeOp == AbsProtocol.LOGBT || maybeOp == AbsProtocol.LOG || maybeOp == 0 + || (maybeOp >= 1000 && maybeOp <= 10000)) { + bodyOffset += 4; + json.put("rcvExtraOpCd", maybeOp); + } else { + boolean looksYear = looksYear(reply, bodyOffset); + boolean looksYearFwd = looksYear(reply, bodyOffset + 4); + if (!looksYear && looksYearFwd) bodyOffset += 4; + } + } + if (bodyOffset >= 4 && byteAt(reply, bodyOffset) == (byte)'/') { + if (byteAt(reply, bodyOffset - 4) == '2' && byteAt(reply, bodyOffset - 3) == '0') { + bodyOffset -= 4; + } + } + + json.put("offsetProbe", Map.of( + "bodyOffset", bodyOffset, + "around", new String(slice(reply, Math.max(0, bodyOffset - 8), + Math.min(reply.length, bodyOffset + 16)), StandardCharsets.US_ASCII) + )); + + if ((boolean) json.get("success") && reply.length >= bodyOffset + 20) { + try { + RcvLog log = AbsProtocol.parseRcvLog(reply, bodyOffset); + json.put("log", log); + } catch (Exception ex) { + json.put("parseLogError", ex.toString()); + } + } + + return json; + } catch (Exception e) { + return Map.of("ok", false, "error", e.toString()); + } + } + + + + // ---------- helpers ---------- + private static byte[] slice(byte[] a, int off, int end) { + int n = Math.max(0, end - off); + byte[] out = new byte[n]; + System.arraycopy(a, Math.max(0, off), out, 0, n); + return out; + } + + private static String toHex(byte[] a) { + return HexFormat.of().formatHex(a); + } + + private static byte[] concat(byte[] a, byte[] b) { + byte[] out = new byte[a.length + b.length]; + System.arraycopy(a, 0, out, 0, a.length); + System.arraycopy(b, 0, out, a.length, b.length); + return out; + } + + private static int leInt(byte[] a, int off) { + ByteBuffer bb = ByteBuffer.wrap(a, off, 4).order(ByteOrder.LITTLE_ENDIAN); + return bb.getInt(); + } + + private static boolean looksYear(byte[] a, int off) { + if (off + 1 >= a.length) return false; + return a[off] == '2' && a[off + 1] == '0'; + } + + private static byte byteAt(byte[] a, int off) { + return off >= 0 && off < a.length ? a[off] : 0; + } + + private static int extractBtMake(Object btMakeAny) { + if (btMakeAny == null) return 0; + if (btMakeAny instanceof Number n) return n.intValue(); + if (btMakeAny instanceof String s) { + return s.isEmpty() ? 0 : (int) s.charAt(0); + } + return 0; + } + + private static int extractBtMake(Map body) { + if (body == null) return 0; + Object v = body.get("btMake"); + return extractBtMake(v); + } + + private static String safe(String s) { return s == null ? "" : s; } + private static boolean isEmpty(String s) { return s == null || s.isBlank(); } +}