ABS service

This commit is contained in:
MathewFrancis 2025-08-23 12:35:58 +05:30
parent e9992575dc
commit f716457c1f
2 changed files with 238 additions and 198 deletions

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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);
return absServiceForLogIn.loginRew(req);
}
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<String, Object> 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<String, Object> 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(); }
}

View File

@ -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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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(); }
}