ABS service
This commit is contained in:
parent
e9992575dc
commit
f716457c1f
@ -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(); }
|
||||
}
|
||||
@ -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(); }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user