Files
Erupe/server/entranceserver/entrance_server.go
Houmgaor 82b967b715 refactor: replace raw SQL with repository interfaces in entranceserver and API server
Extract all direct database calls from entranceserver (2 calls) and
API server (17 calls) into typed repository interfaces with PostgreSQL
implementations, matching the pattern established in signserver and
channelserver.

Entranceserver: EntranceServerRepo, EntranceSessionRepo
API server: APIUserRepo, APICharacterRepo, APISessionRepo

Also fix the 3 remaining fmt.Sprintf calls inside logger invocations
in handlers_commands.go and handlers_stage.go, replacing them with
structured zap fields.

Unskip 5 TestNewAuthData* tests that previously required a real
database — they now run with mock repos.
2026-02-22 17:04:58 +01:00

132 lines
3.0 KiB
Go

package entranceserver
import (
"encoding/hex"
"fmt"
"io"
"net"
"strings"
"sync"
cfg "erupe-ce/config"
"erupe-ce/network"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
// Server is a MHF entrance server.
type Server struct {
sync.Mutex
logger *zap.Logger
erupeConfig *cfg.Config
serverRepo EntranceServerRepo
sessionRepo EntranceSessionRepo
listener net.Listener
isShuttingDown bool
}
// Config struct allows configuring the server.
type Config struct {
Logger *zap.Logger
DB *sqlx.DB
ErupeConfig *cfg.Config
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
logger: config.Logger,
erupeConfig: config.ErupeConfig,
}
if config.DB != nil {
s.serverRepo = NewEntranceServerRepository(config.DB)
s.sessionRepo = NewEntranceSessionRepository(config.DB)
}
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start() error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.erupeConfig.Entrance.Port))
if err != nil {
return err
}
s.listener = l
go s.acceptClients()
return nil
}
// Shutdown exits the server gracefully.
func (s *Server) Shutdown() {
s.logger.Debug("Shutting down...")
s.Lock()
s.isShuttingDown = true
s.Unlock()
// This will cause the acceptor goroutine to error and exit gracefully.
_ = s.listener.Close()
}
// acceptClients handles accepting new clients in a loop.
func (s *Server) acceptClients() {
for {
conn, err := s.listener.Accept()
if err != nil {
// Check if we are shutting down and exit gracefully if so.
s.Lock()
shutdown := s.isShuttingDown
s.Unlock()
if shutdown {
break
} else {
continue
}
}
// Start a new goroutine for the connection so that we don't block other incoming connections.
go s.handleEntranceServerConnection(conn)
}
}
func (s *Server) handleEntranceServerConnection(conn net.Conn) {
defer func() { _ = conn.Close() }()
// Client initalizes the connection with a one-time buffer of 8 NULL bytes.
nullInit := make([]byte, 8)
n, err := io.ReadFull(conn, nullInit)
if err != nil {
s.logger.Warn("Failed to read 8 NULL init", zap.Error(err))
return
} else if n != len(nullInit) {
s.logger.Warn("io.ReadFull couldn't read the full 8 byte init.")
return
}
// Create a new encrypted connection handler and read a packet from it.
cc := network.NewCryptConn(conn, s.erupeConfig.RealClientMode, s.logger)
pkt, err := cc.ReadPacket()
if err != nil {
s.logger.Warn("Error reading packet", zap.Error(err))
return
}
if s.erupeConfig.DebugOptions.LogInboundMessages {
s.logger.Debug("Inbound packet", zap.Int("bytes", len(pkt)), zap.String("data", hex.Dump(pkt)))
}
local := strings.Split(conn.RemoteAddr().String(), ":")[0] == "127.0.0.1"
data := makeSv2Resp(s.erupeConfig, s, local)
if len(pkt) > 5 {
data = append(data, makeUsrResp(pkt, s)...)
}
_ = cc.SendPacket(data)
// Close because we only need to send the response once.
// Any further requests from the client will come from a new connection.
}