forked from Cookies_Github_mirror/AquaDX
[billing] Add billing server
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package icu.samnyan.aqua.sega.billing;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import icu.samnyan.aqua.sega.billing.model.response.BillingResponse;
|
||||
import icu.samnyan.aqua.sega.billing.util.Decoder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.Signature;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
public class BillingController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BillingController.class);
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
public BillingController(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
}
|
||||
|
||||
@PostMapping(value = "/request", produces = "text/plain")
|
||||
public String powerOn(InputStream dataStream, HttpServletRequest req) throws IOException {
|
||||
|
||||
RSAPrivateKey key = loadBillingKey();
|
||||
|
||||
byte[] bytes = dataStream.readAllBytes();
|
||||
Map<String, String> reqMap = Decoder.decode(bytes);
|
||||
|
||||
logger.info("Request: Billing, " + mapper.writeValueAsString(reqMap));
|
||||
|
||||
String keychipId = reqMap.getOrDefault("keychipid", "");
|
||||
|
||||
BillingResponse resp = new BillingResponse(
|
||||
0,
|
||||
100,
|
||||
1,
|
||||
"",
|
||||
1024,
|
||||
signWithKey(key, keychipId, 1024),
|
||||
"1.000",
|
||||
66048, // 0x00010200
|
||||
signWithKey(key, keychipId, 66048),
|
||||
0,
|
||||
5,
|
||||
"000000/0:000000/0:000000/0");
|
||||
logger.info("Response: " + mapper.writeValueAsString(resp));
|
||||
return resp.toString().concat("\n");
|
||||
}
|
||||
|
||||
private String signWithKey(RSAPrivateKey key, String keychipId, int val) {
|
||||
String result = "";
|
||||
|
||||
ByteBuffer sigbytes = ByteBuffer.allocate(15);
|
||||
sigbytes.order(ByteOrder.LITTLE_ENDIAN);
|
||||
sigbytes.putInt(0, val);
|
||||
sigbytes.put(4, keychipId.getBytes());
|
||||
|
||||
Signature sig;
|
||||
try {
|
||||
sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initSign(key);
|
||||
sig.update(sigbytes);
|
||||
byte[] signedData = sig.sign();
|
||||
result = bytesToHex(signedData);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to sign with billing key, " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private RSAPrivateKey loadBillingKey() {
|
||||
RSAPrivateKey billingKey = null;
|
||||
|
||||
Resource keyRes = resourceLoader.getResource("classpath:billing.der");
|
||||
byte[] key;
|
||||
try {
|
||||
key = FileCopyUtils.copyToByteArray(keyRes.getInputStream());
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
KeySpec keySpec = new PKCS8EncodedKeySpec(key);
|
||||
billingKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to load billing key file, " + e.getMessage());
|
||||
}
|
||||
|
||||
return billingKey;
|
||||
}
|
||||
|
||||
private String bytesToHex(byte[] in) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for(byte b : in) {
|
||||
builder.append(String.format("%02x", b));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package icu.samnyan.aqua.sega.billing.model.response;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class BillingResponse {
|
||||
private int result;
|
||||
private int waittime;
|
||||
private int linelimit;
|
||||
private String message;
|
||||
private int playlimit;
|
||||
private String playlimitsig;
|
||||
private String protocolver;
|
||||
private int nearfull;
|
||||
private String nearfullsig;
|
||||
private int fixlogcnt;
|
||||
private int fixinterval;
|
||||
private String playhistory;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "result=" + result +
|
||||
"&waittime=" + waittime +
|
||||
"&linelimit=" + linelimit +
|
||||
"&message=" + message +
|
||||
"&playlimit=" + playlimit +
|
||||
"&playlimitsig=" + playlimitsig +
|
||||
"&protocolver=" + protocolver +
|
||||
"&nearfull=" + nearfull +
|
||||
"&nearfullsig=" + nearfullsig +
|
||||
"&fixlogcnt=" + fixlogcnt +
|
||||
"&fixinterval=" + fixinterval +
|
||||
"&playhistory=" + playhistory;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package icu.samnyan.aqua.sega.billing.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
public class Decoder {
|
||||
|
||||
public static Map<String, String> decode(byte[] src) {
|
||||
//byte[] bytes = Base64.getMimeDecoder().decode(src);
|
||||
|
||||
byte[] output = RawCompression.decompress(src);
|
||||
|
||||
String outputString = new String(output, StandardCharsets.UTF_8).trim();
|
||||
String[] split = outputString.split("&");
|
||||
Map<String, String> resultMap = new HashMap<>();
|
||||
for (String s :
|
||||
split) {
|
||||
String[] kv = s.split("=");
|
||||
resultMap.put(kv[0], kv[1]);
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package icu.samnyan.aqua.sega.billing.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.Inflater;
|
||||
|
||||
import icu.samnyan.aqua.sega.util.ByteBufUtil;
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
public class RawCompression {
|
||||
|
||||
public static byte[] decompress(byte[] src) {
|
||||
ByteBuf result = Unpooled.buffer();
|
||||
byte[] buffer = new byte[100];
|
||||
Inflater decompressor = new Inflater(true); // Enable no wrap option
|
||||
decompressor.setInput(src);
|
||||
|
||||
try {
|
||||
while (!decompressor.finished()) {
|
||||
int count = decompressor.inflate(buffer);
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
result.writeBytes(buffer, result.readerIndex(), count);
|
||||
}
|
||||
decompressor.end();
|
||||
|
||||
return ByteBufUtil.toBytes(result);
|
||||
} catch (DataFormatException e) {
|
||||
e.printStackTrace();
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user