ABS polling
This commit is contained in:
parent
4c23c00df7
commit
55b226e729
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.cezenBTC.DTO.CenteralServerConect;
|
||||||
|
|
||||||
|
public record ApiResponse(
|
||||||
|
boolean ok,
|
||||||
|
String target,
|
||||||
|
String sentHeaderHex,
|
||||||
|
String sentBodyHex,
|
||||||
|
int replyBytes,
|
||||||
|
String replyHexFirst64,
|
||||||
|
RcvHeaderRaw rcvHeaderRaw,
|
||||||
|
boolean success,
|
||||||
|
Encryption encryption,
|
||||||
|
Log log
|
||||||
|
) {}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.example.cezenBTC.DTO.CenteralServerConect;
|
||||||
|
|
||||||
|
public record Encryption(
|
||||||
|
boolean used,
|
||||||
|
int utcSeconds,
|
||||||
|
int key,
|
||||||
|
String encPasswd
|
||||||
|
) {}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.example.cezenBTC.DTO.CenteralServerConect;
|
||||||
|
|
||||||
|
public record Log(
|
||||||
|
String cDateTime,
|
||||||
|
String cUsrNm,
|
||||||
|
String cUsrId,
|
||||||
|
String cSupNm,
|
||||||
|
String cSupId,
|
||||||
|
String cUsrTyp,
|
||||||
|
|
||||||
|
double fOpenBal,
|
||||||
|
double fTktSalesByVoucher,
|
||||||
|
double fTktSalesByCash,
|
||||||
|
double fTktSalesByMemCard,
|
||||||
|
|
||||||
|
int nTktSalesByVoucherCount,
|
||||||
|
int nTktSalesByCashCount,
|
||||||
|
int nTktSalesByMemCardCount,
|
||||||
|
|
||||||
|
double fPayoutByVoucher,
|
||||||
|
double fPayoutByCash,
|
||||||
|
double fPayoutByMemCard,
|
||||||
|
|
||||||
|
int nPayoutByVoucherCount,
|
||||||
|
int nPayoutByCashCount,
|
||||||
|
int nPayoutByMemCardCount,
|
||||||
|
|
||||||
|
double fCancelByVoucher,
|
||||||
|
double fCancelByCash,
|
||||||
|
double fCancelByMemCard,
|
||||||
|
|
||||||
|
int nCancelByVoucherCount,
|
||||||
|
int nCancelByCashCount,
|
||||||
|
int nCancelByMemCardCount,
|
||||||
|
|
||||||
|
double fDeposit,
|
||||||
|
double fWithdrawAmt,
|
||||||
|
double fVoucherSales,
|
||||||
|
double fVoucherEncash,
|
||||||
|
double fCloseBal,
|
||||||
|
double fSaleTarget,
|
||||||
|
|
||||||
|
int size
|
||||||
|
) {}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.example.cezenBTC.DTO.CenteralServerConect;
|
||||||
|
|
||||||
|
public record RcvHeaderRaw(
|
||||||
|
int nTxnCd,
|
||||||
|
int nRetCd,
|
||||||
|
int nNumRecs,
|
||||||
|
int nTxnId,
|
||||||
|
int cBtMake,
|
||||||
|
int size
|
||||||
|
) {}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.example.cezenBTC.DTO.bridge;
|
||||||
|
|
||||||
|
public record RcvHeader(
|
||||||
|
int nTxnCd, int nRetCd, int nNumRecs, int nTxnId, int cBtMake, int size
|
||||||
|
) {}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.cezenBTC.DTO.bridge;
|
||||||
|
|
||||||
|
public record RcvLog(
|
||||||
|
String cDateTime, String cUsrNm, String cUsrId, String cSupNm, String cSupId, String cUsrTyp,
|
||||||
|
float fOpenBal, float fTktSalesByVoucher, float fTktSalesByCash, float fTktSalesByMemCard,
|
||||||
|
int nTktSalesByVoucherCount, int nTktSalesByCashCount, int nTktSalesByMemCardCount,
|
||||||
|
float fPayoutByVoucher, float fPayoutByCash, float fPayoutByMemCard,
|
||||||
|
int nPayoutByVoucherCount, int nPayoutByCashCount, int nPayoutByMemCardCount,
|
||||||
|
float fCancelByVoucher, float fCancelByCash, float fCancelByMemCard,
|
||||||
|
int nCancelByVoucherCount, int nCancelByCashCount, int nCancelByMemCardCount,
|
||||||
|
float fDeposit, float fWithdrawAmt, float fVoucherSales, float fVoucherEncash,
|
||||||
|
float fCloseBal, float fSaleTarget,
|
||||||
|
int size
|
||||||
|
) {}
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
package com.example.cezenBTC.absbridge.core;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import com.example.cezenBTC.DTO.bridge.RcvHeader;
|
||||||
|
import com.example.cezenBTC.DTO.bridge.RcvLog;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public final class AbsProtocol {
|
||||||
|
private AbsProtocol() {}
|
||||||
|
|
||||||
|
// ---- constants ----
|
||||||
|
public static final int ABS_POLL = 178;
|
||||||
|
public static final int LOG = 100;
|
||||||
|
public static final int LOGBT = 6013;
|
||||||
|
public static final int SUCCESS = 0;
|
||||||
|
|
||||||
|
// sSndHeader (24 bytes)
|
||||||
|
public static byte[] packSndHeader(int nTxnCd, int nOpCd, int nNumRecsSent,
|
||||||
|
int nNumRecsRqrd, int nTxnId, int cBtMake) {
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate(24).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bb.putInt(nTxnCd);
|
||||||
|
bb.putInt(nOpCd);
|
||||||
|
bb.putInt(nNumRecsSent);
|
||||||
|
bb.putInt(nNumRecsRqrd);
|
||||||
|
bb.putInt(nTxnId);
|
||||||
|
bb.put((byte)(cBtMake & 0xFF));
|
||||||
|
return bb.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize to 18‑byte header (for display/compat)
|
||||||
|
public static byte[] buildRcvHeader18(RcvHeader h) {
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate(18).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
bb.putInt(h.nTxnCd());
|
||||||
|
bb.putInt(h.nRetCd());
|
||||||
|
bb.putInt(h.nNumRecs());
|
||||||
|
bb.putInt(h.nTxnId());
|
||||||
|
bb.put((byte)(h.cBtMake() & 0xFF));
|
||||||
|
return bb.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// flexible parser: 18 or 24 bytes present
|
||||||
|
public static RcvHeader parseRcvHeaderFlexible(byte[] reply) throws Exception {
|
||||||
|
if (reply.length < 18) throw new Exception("Reply too short for RcvHeader: " + reply.length);
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(reply).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
int nTxnCd = bb.getInt();
|
||||||
|
int nRetCd = bb.getInt();
|
||||||
|
int nNumRecs = bb.getInt();
|
||||||
|
int nTxnId = bb.getInt();
|
||||||
|
int cBtMake = Byte.toUnsignedInt(bb.get());
|
||||||
|
int size = reply.length >= 24 ? 24 : 18;
|
||||||
|
return new RcvHeader(nTxnCd, nRetCd, nNumRecs, nTxnId, cBtMake, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sSndLog (38 bytes): cUsrId[5], cOpCardCd[17], cPasswd[11], cBtId[5]
|
||||||
|
public static byte[] packSndLog(String usrId, String opCard, String passwd, String btId) {
|
||||||
|
ByteBuffer bb = ByteBuffer.allocate(38).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
FixedAscii.writeFixedAscii(bb, usrId, 5);
|
||||||
|
FixedAscii.writeFixedAscii(bb, opCard, 17);
|
||||||
|
FixedAscii.writeFixedAscii(bb, passwd, 11);
|
||||||
|
FixedAscii.writeFixedAscii(bb, btId, 5);
|
||||||
|
return bb.array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MFC-like password "encryption"
|
||||||
|
public static Enc encryptPasswordLikeMFC(String plain) {
|
||||||
|
int sec = Instant.now().getEpochSecond() % 60 == 0 ? 0 : (int)(Instant.now().getEpochSecond() % 60);
|
||||||
|
// key = first digit of UTC seconds
|
||||||
|
String secStr = String.valueOf((int)(Instant.now().getEpochSecond() % 60));
|
||||||
|
int key = (secStr.length() > 0) ? (secStr.charAt(0) - '0') : 0;
|
||||||
|
String s = plain == null ? "" : plain;
|
||||||
|
StringBuilder out = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char ch = s.charAt(i);
|
||||||
|
int d = (ch >= '0' && ch <= '9') ? (ch - '0') : 0;
|
||||||
|
out.append((d + key) % 10);
|
||||||
|
}
|
||||||
|
out.append(key);
|
||||||
|
return new Enc(out.toString(), key, (int)(Instant.now().getEpochSecond() % 60));
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Enc(String enc, int key, int utcSeconds) {}
|
||||||
|
|
||||||
|
// parse sRcvLog body starting at offset
|
||||||
|
public static RcvLog parseRcvLog(byte[] buf, int offset) {
|
||||||
|
int o = offset;
|
||||||
|
|
||||||
|
String cDateTime = FixedAscii.readFixedAscii(buf, o, 20); o += 20;
|
||||||
|
String cUsrNm = FixedAscii.readFixedAscii(buf, o, 31); o += 31;
|
||||||
|
String cUsrId = FixedAscii.readFixedAscii(buf, o, 5); o += 5;
|
||||||
|
String cSupNm = FixedAscii.readFixedAscii(buf, o, 31); o += 31;
|
||||||
|
String cSupId = FixedAscii.readFixedAscii(buf, o, 5); o += 5;
|
||||||
|
String cUsrTyp = FixedAscii.readFixedAscii(buf, o, 5); o += 5;
|
||||||
|
|
||||||
|
ByteBuffer bb = ByteBuffer.wrap(buf, o, buf.length - o).order(ByteOrder.LITTLE_ENDIAN);
|
||||||
|
|
||||||
|
float fOpenBal = bb.getFloat();
|
||||||
|
float fTktSalesByVoucher = bb.getFloat();
|
||||||
|
float fTktSalesByCash = bb.getFloat();
|
||||||
|
float fTktSalesByMemCard = bb.getFloat();
|
||||||
|
int nTktSalesByVoucherCount = bb.getInt();
|
||||||
|
int nTktSalesByCashCount = bb.getInt();
|
||||||
|
int nTktSalesByMemCardCount = bb.getInt();
|
||||||
|
float fPayoutByVoucher = bb.getFloat();
|
||||||
|
float fPayoutByCash = bb.getFloat();
|
||||||
|
float fPayoutByMemCard = bb.getFloat();
|
||||||
|
int nPayoutByVoucherCount = bb.getInt();
|
||||||
|
int nPayoutByCashCount = bb.getInt();
|
||||||
|
int nPayoutByMemCardCount = bb.getInt();
|
||||||
|
float fCancelByVoucher = bb.getFloat();
|
||||||
|
float fCancelByCash = bb.getFloat();
|
||||||
|
float fCancelByMemCard = bb.getFloat();
|
||||||
|
int nCancelByVoucherCount = bb.getInt();
|
||||||
|
int nCancelByCashCount = bb.getInt();
|
||||||
|
int nCancelByMemCardCount = bb.getInt();
|
||||||
|
float fDeposit = bb.getFloat();
|
||||||
|
float fWithdrawAmt = bb.getFloat();
|
||||||
|
float fVoucherSales = bb.getFloat();
|
||||||
|
float fVoucherEncash = bb.getFloat();
|
||||||
|
float fCloseBal = bb.getFloat();
|
||||||
|
float fSaleTarget = bb.getFloat();
|
||||||
|
|
||||||
|
int consumed = 20+31+5+31+5+5 + ( // strings
|
||||||
|
4*3 + // first 3 floats already? (we counted below anyway)
|
||||||
|
0);
|
||||||
|
|
||||||
|
// compute actual bytes consumed via buffer:
|
||||||
|
int size = (20+31+5+31+5+5) + ( // strings
|
||||||
|
4 * 16 // floats count (16 floats)
|
||||||
|
+ 4 * 9 // ints count (9 ints)
|
||||||
|
);
|
||||||
|
|
||||||
|
int totalOffset = (buf.length - bb.remaining());
|
||||||
|
size = totalOffset - offset;
|
||||||
|
|
||||||
|
return new RcvLog(
|
||||||
|
cDateTime, cUsrNm, cUsrId, cSupNm, cSupId, cUsrTyp,
|
||||||
|
fOpenBal, fTktSalesByVoucher, fTktSalesByCash, fTktSalesByMemCard,
|
||||||
|
nTktSalesByVoucherCount, nTktSalesByCashCount, nTktSalesByMemCardCount,
|
||||||
|
fPayoutByVoucher, fPayoutByCash, fPayoutByMemCard,
|
||||||
|
nPayoutByVoucherCount, nPayoutByCashCount, nPayoutByMemCardCount,
|
||||||
|
fCancelByVoucher, fCancelByCash, fCancelByMemCard,
|
||||||
|
nCancelByVoucherCount, nCancelByCashCount, nCancelByMemCardCount,
|
||||||
|
fDeposit, fWithdrawAmt, fVoucherSales, fVoucherEncash,
|
||||||
|
fCloseBal, fSaleTarget,
|
||||||
|
size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
package com.example.cezenBTC.absbridge.core;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public final class FixedAscii {
|
||||||
|
private FixedAscii() {}
|
||||||
|
|
||||||
|
public static void writeFixedAscii(ByteBuffer bb, String s, int len) {
|
||||||
|
String str = s == null ? "" : s;
|
||||||
|
byte[] src = str.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
int toCopy = Math.min(src.length, len);
|
||||||
|
bb.put(src, 0, toCopy);
|
||||||
|
for (int i = toCopy; i < len; i++) bb.put((byte)0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String readFixedAscii(byte[] buf, int offset, int len) {
|
||||||
|
int end = offset;
|
||||||
|
int max = offset + len;
|
||||||
|
while (end < max && buf[end] != 0) end++;
|
||||||
|
return new String(buf, offset, end - offset, StandardCharsets.US_ASCII).stripTrailing();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
package com.example.cezenBTC.absbridge.model;
|
||||||
|
|
||||||
|
public record HealthResponse(boolean ok, String target, String error) {}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.example.cezenBTC.absbridge.model;
|
||||||
|
|
||||||
|
public class LoginRequest {
|
||||||
|
public String opCard;
|
||||||
|
public String btId;
|
||||||
|
public String usrId;
|
||||||
|
public String password;
|
||||||
|
public String passwordEnc;
|
||||||
|
public Object btMake; // number or single-char string accepted
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.example.cezenBTC.absbridge.model;
|
||||||
|
|
||||||
|
import com.example.cezenBTC.DTO.bridge.RcvHeader;
|
||||||
|
|
||||||
|
public record PollResponse(
|
||||||
|
boolean ok,
|
||||||
|
String target,
|
||||||
|
String sentHeaderHex,
|
||||||
|
int replyBytes,
|
||||||
|
String replyHexFirst64,
|
||||||
|
RcvHeader parsedRcvHeaderRaw,
|
||||||
|
RcvHeader normalizedRcvHeader,
|
||||||
|
String normalizedHeaderHex,
|
||||||
|
boolean success
|
||||||
|
) {}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
package com.example.cezenBTC.config;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AbsClient {
|
||||||
|
|
||||||
|
public static final int PKT_SIZE = 512;
|
||||||
|
public static final int PAYLOAD_PER_PKT = PKT_SIZE - 1;
|
||||||
|
|
||||||
|
private final String host;
|
||||||
|
private final int port;
|
||||||
|
|
||||||
|
public AbsClient(
|
||||||
|
@Value("${abs.host:192.0.0.14}") String host,
|
||||||
|
@Value("${abs.port:7000}") int port
|
||||||
|
) {
|
||||||
|
this.host = host;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String target() { return host + ":" + port; }
|
||||||
|
|
||||||
|
public byte[] roundtrip(byte[] payload, int timeoutMs) throws Exception {
|
||||||
|
try (Socket sock = new Socket()) {
|
||||||
|
sock.connect(new InetSocketAddress(host, port), timeoutMs);
|
||||||
|
sock.setSoTimeout(timeoutMs);
|
||||||
|
|
||||||
|
try (OutputStream out = sock.getOutputStream();
|
||||||
|
InputStream in = sock.getInputStream()) {
|
||||||
|
|
||||||
|
send(out, payload);
|
||||||
|
return recv(in, timeoutMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void send(OutputStream out, byte[] payload) throws Exception {
|
||||||
|
int off = 0;
|
||||||
|
while (off < payload.length) {
|
||||||
|
int remaining = payload.length - off;
|
||||||
|
int toCopy = Math.min(PAYLOAD_PER_PKT, remaining);
|
||||||
|
byte[] pkt = new byte[PKT_SIZE];
|
||||||
|
pkt[0] = (byte)((off + toCopy >= payload.length) ? '0' : '1');
|
||||||
|
if (toCopy > 0) {
|
||||||
|
System.arraycopy(payload, off, pkt, 1, toCopy);
|
||||||
|
off += toCopy;
|
||||||
|
}
|
||||||
|
out.write(pkt);
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] recv(InputStream in, int timeoutMs) throws Exception {
|
||||||
|
List<byte[]> chunks = new ArrayList<>();
|
||||||
|
byte[] buf = new byte[PKT_SIZE];
|
||||||
|
while (true) {
|
||||||
|
int read = 0;
|
||||||
|
while (read < PKT_SIZE) {
|
||||||
|
int n = in.read(buf, read, PKT_SIZE - read);
|
||||||
|
if (n < 0) throw new Exception("Socket ended before final '0' packet");
|
||||||
|
read += n;
|
||||||
|
}
|
||||||
|
byte flag = buf[0];
|
||||||
|
byte[] body = new byte[PKT_SIZE - 1];
|
||||||
|
System.arraycopy(buf, 1, body, 0, body.length);
|
||||||
|
chunks.add(body);
|
||||||
|
if (flag == '0') break;
|
||||||
|
}
|
||||||
|
int total = chunks.stream().mapToInt(a -> a.length).sum();
|
||||||
|
byte[] out = new byte[total];
|
||||||
|
int o = 0;
|
||||||
|
for (byte[] c : chunks) {
|
||||||
|
System.arraycopy(c, 0, out, o, c.length);
|
||||||
|
o += c.length;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -93,7 +93,7 @@ public class CezenRoutsSecurityChain {
|
|||||||
//.csrf(AbstractHttpConfigurer::disable)
|
//.csrf(AbstractHttpConfigurer::disable)
|
||||||
.csrf((csrf) ->
|
.csrf((csrf) ->
|
||||||
csrf.csrfTokenRequestHandler(requestHandler).
|
csrf.csrfTokenRequestHandler(requestHandler).
|
||||||
ignoringRequestMatchers("/open/signup","/user/getXSRfToken","/user/ping")
|
ignoringRequestMatchers("/open/signup","/user/getXSRfToken","/user/ping", "/abs/*")
|
||||||
//.csrfTokenRepository(new CookieCsrfTokenRepository())
|
//.csrfTokenRepository(new CookieCsrfTokenRepository())
|
||||||
.csrfTokenRepository(cookieCsrfTokenRepo)
|
.csrfTokenRepository(cookieCsrfTokenRepo)
|
||||||
)
|
)
|
||||||
@ -113,7 +113,8 @@ public class CezenRoutsSecurityChain {
|
|||||||
"/btc/get_race_card",
|
"/btc/get_race_card",
|
||||||
"/cezen/set_aors",
|
"/cezen/set_aors",
|
||||||
"/cezen/set_password",
|
"/cezen/set_password",
|
||||||
"/cezen/add_extension"
|
"/cezen/add_extension",
|
||||||
|
"/abs/*"
|
||||||
).hasAnyRole("admin")
|
).hasAnyRole("admin")
|
||||||
//any one who is authenticated can access /logout
|
//any one who is authenticated can access /logout
|
||||||
.requestMatchers("/user/getXSRfToken","/user/ping", "/logout").authenticated()
|
.requestMatchers("/user/getXSRfToken","/user/ping", "/logout").authenticated()
|
||||||
|
|||||||
@ -0,0 +1,233 @@
|
|||||||
|
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 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test")
|
||||||
|
public String root() {
|
||||||
|
return "ABS bridge is up";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping(value = "/abs/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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
spring.application.name=cezenHorse
|
spring.application.name=cezenHorse
|
||||||
#server.port=8083
|
#server.port=8083
|
||||||
#server.port=8083
|
server.port=8081
|
||||||
server.address=0.0.0.0
|
server.address=0.0.0.0
|
||||||
|
|
||||||
spring.datasource.url = jdbc:postgresql://localhost:5434/horse
|
spring.datasource.url = jdbc:postgresql://localhost:5434/horse
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user