init commit
This commit is contained in:
161
internal/network/http_server.go
Normal file
161
internal/network/http_server.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"customServer/internal/state"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func StartHTTPServer(addr string) error {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/main/v2/oauth/token", handleToken)
|
||||
mux.HandleFunc("/main/v1/user/me", handleUserMe)
|
||||
mux.HandleFunc("/main/v1/user/me/link", handleLink)
|
||||
mux.HandleFunc("/main/v1/users", handleUserSearch)
|
||||
mux.HandleFunc("/main/v3/showcase/games/steam/es", handleShowcase)
|
||||
mux.HandleFunc("/main/v1/user/me/buddies", handleBuddies)
|
||||
mux.HandleFunc("/main/v1/user/me/lastopponents/GOTDBG", handleLastOpponents)
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] [UNKNOWN] Request to %s (%s) from %s - Full URI: %s\n", r.URL.Path, r.Method, r.RemoteAddr, r.RequestURI)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte(`{"error":true,"status":404,"message":"Not found"}`))
|
||||
})
|
||||
|
||||
fmt.Printf("[HTTP] REST API: http://localhost%s\n", addr)
|
||||
return http.ListenAndServe(addr, mux)
|
||||
}
|
||||
|
||||
func handleToken(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||
response := map[string]interface{}{
|
||||
"access_token": "mock_access_token_12345",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600,
|
||||
"refresh_token": "mock_refresh_token_67890",
|
||||
"scope": "public private boardgames onlinegames partners features",
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleUserMe(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"error": false,
|
||||
"status": 200,
|
||||
"data": map[string]interface{}{
|
||||
"user": map[string]interface{}{
|
||||
"user_id": state.MockUserID,
|
||||
"login_name": state.MockUserName,
|
||||
"email": "player@customserver.local",
|
||||
"name": state.MockUserName,
|
||||
"email_valid": true,
|
||||
"validated": true,
|
||||
"country": "US",
|
||||
"language": "en",
|
||||
"time_zone": "UTC",
|
||||
"posted_msg_count": 42,
|
||||
"features": []string{"online_play", "all_expansions", "community", "profile", "userpages"},
|
||||
"partners": []interface{}{
|
||||
map[string]interface{}{
|
||||
"partner_id": 12, // Steam
|
||||
"partner_user_id": "76561198084728812",
|
||||
"created_at": "2026-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
"boardgames": []interface{}{},
|
||||
"onlinegames": []interface{}{},
|
||||
"avatar": "https://uploads.asmodee.net/builtin/avatar-neutral.jpg",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleLink(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s (%s) from %s\n", r.URL.Path, r.Method, r.RemoteAddr)
|
||||
response := map[string]interface{}{
|
||||
"error": false,
|
||||
"status": 200,
|
||||
"data": map[string]interface{}{},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleUserSearch(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||
|
||||
response := map[string]interface{}{
|
||||
"error": false,
|
||||
"status": 200,
|
||||
"data": map[string]interface{}{
|
||||
"total": 1,
|
||||
"_links": map[string]interface{}{},
|
||||
"users": []interface{}{
|
||||
map[string]interface{}{
|
||||
"user_id": state.MockUserID,
|
||||
"login_name": state.MockUserName,
|
||||
"avatar": "https://uploads.asmodee.net/builtin/avatar-neutral.jpg",
|
||||
"features": []string{"online_play", "all_expansions"},
|
||||
"boardgames": []interface{}{},
|
||||
"onlinegames": []interface{}{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleShowcase(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s (%s) from %s\n", r.URL.Path, r.Method, r.RemoteAddr)
|
||||
response := map[string]interface{}{
|
||||
"error": false,
|
||||
"status": 200,
|
||||
"data": []interface{}{},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleBuddies(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||
response := map[string]interface{}{
|
||||
"error": false,
|
||||
"status": 200,
|
||||
"data": map[string]interface{}{
|
||||
"total": 0,
|
||||
"buddies": []interface{}{},
|
||||
"_links": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
|
||||
func handleLastOpponents(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||
response := map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"opponents": []interface{}{},
|
||||
},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
data, _ := json.Marshal(response)
|
||||
w.Write(data)
|
||||
}
|
||||
180
internal/network/tcp_server.go
Normal file
180
internal/network/tcp_server.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"customServer/internal/handlers"
|
||||
"customServer/internal/protocol"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func StartTCPServer(addr string) error {
|
||||
tlsConfig, err := generateSelfSignedCert()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate self-signed cert: %v", err)
|
||||
}
|
||||
|
||||
listener, err := tls.Listen("tcp", addr, tlsConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TCP TLS listener failed: %v", err)
|
||||
}
|
||||
fmt.Printf("[TCP] Scalable Server (TLS): localhost%s\n", addr)
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Printf("Accept error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
go handleTCPConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func handleTCPConnection(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
fmt.Printf("[TCP] New TLS connection from %s\n", conn.RemoteAddr())
|
||||
|
||||
for {
|
||||
// Read length (4 bytes, Big Endian)
|
||||
length, err := protocol.ReadPacketLength(conn)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Printf("[TCP] Read length error: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read packet
|
||||
data := make([]byte, length)
|
||||
_, err = io.ReadFull(conn, data)
|
||||
if err != nil {
|
||||
fmt.Printf("[TCP] Read data error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("[TCP] Received packet of %d bytes\n", length)
|
||||
|
||||
packetID := int64(0)
|
||||
requestNumber := int32(0)
|
||||
var payloadBytes []byte
|
||||
|
||||
reader := bytes.NewReader(data)
|
||||
for {
|
||||
tag, err := protocol.ReadVarint(reader)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fieldNum := tag >> 3
|
||||
wireType := tag & 0x7
|
||||
|
||||
if fieldNum == 1 && wireType == 0 { // Packet.id
|
||||
packetID, _ = protocol.ReadVarintInt64(reader)
|
||||
} else if fieldNum == 3 && wireType == 2 { // Packet.payload (Message)
|
||||
payloadLen, _ := protocol.ReadVarint(reader)
|
||||
payloadBytes = make([]byte, payloadLen)
|
||||
reader.Read(payloadBytes)
|
||||
|
||||
payloadReader := bytes.NewReader(payloadBytes)
|
||||
for {
|
||||
pTag, err := protocol.ReadVarint(payloadReader)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
pFieldNum := pTag >> 3
|
||||
pWireType := pTag & 0x7
|
||||
if pFieldNum == 1 && pWireType == 0 { // Message.request_number
|
||||
reqNum64, _ := protocol.ReadVarintInt64(payloadReader)
|
||||
requestNumber = int32(reqNum64)
|
||||
} else {
|
||||
protocol.SkipField(payloadReader, pWireType)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
protocol.SkipField(reader, wireType)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("[TCP] Got Request ID: %d, Number: %d\n", packetID, requestNumber)
|
||||
|
||||
responsePayload, responseFieldNum := handlers.Dispatch(conn, packetID, requestNumber, payloadBytes)
|
||||
if responsePayload != nil {
|
||||
sendTCPResponse(conn, packetID, responseFieldNum, responsePayload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendTCPResponse(conn net.Conn, packetID int64, fieldNum int, payload []byte) {
|
||||
// Construct Message wrapper
|
||||
message := make([]byte, 0)
|
||||
// Field 1: request_number
|
||||
message = append(message, 0x08)
|
||||
message = append(message, protocol.EncodeVarint(uint64(fieldNum))...)
|
||||
|
||||
// Field [fieldNum]: payload (WireType 2)
|
||||
message = append(message, protocol.EncodeVarint(uint64(fieldNum<<3|2))...)
|
||||
message = append(message, protocol.EncodeVarint(uint64(len(payload)))...)
|
||||
message = append(message, payload...)
|
||||
|
||||
// Construct Packet wrapper
|
||||
packet := make([]byte, 0)
|
||||
// Field 1: Id
|
||||
packet = append(packet, 0x08)
|
||||
packet = append(packet, protocol.EncodeVarint(uint64(packetID))...)
|
||||
// Field 3: Payload
|
||||
packet = append(packet, 0x1a)
|
||||
packet = append(packet, protocol.EncodeVarint(uint64(len(message)))...)
|
||||
packet = append(packet, message...)
|
||||
|
||||
// Send length + packet
|
||||
lengthBuf := make([]byte, 4)
|
||||
length := uint32(len(packet))
|
||||
lengthBuf[0] = byte(length >> 24)
|
||||
lengthBuf[1] = byte(length >> 16)
|
||||
lengthBuf[2] = byte(length >> 8)
|
||||
lengthBuf[3] = byte(length)
|
||||
|
||||
conn.Write(lengthBuf)
|
||||
conn.Write(packet)
|
||||
}
|
||||
|
||||
func generateSelfSignedCert() (*tls.Config, error) {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Custom Server Mod"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||
DNSNames: []string{"localhost"},
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert := tls.Certificate{
|
||||
Certificate: [][]byte{derBytes},
|
||||
PrivateKey: priv,
|
||||
}
|
||||
|
||||
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user