aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/api0/api.go4
-rw-r--r--pkg/api/api0/server.go346
2 files changed, 225 insertions, 125 deletions
diff --git a/pkg/api/api0/api.go b/pkg/api/api0/api.go
index 3f84238..d17018f 100644
--- a/pkg/api/api0/api.go
+++ b/pkg/api/api0/api.go
@@ -88,8 +88,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.handleClientAuthWithSelf(w, r)
case "/client/servers":
h.handleClientServers(w, r)
- case "/server/add_server":
- h.handleServerAddServer(w, r)
+ case "/server/add_server", "/server/update_values", "/server/heartbeat":
+ h.handleServerUpsert(w, r)
case "/accounts/write_persistence":
h.handleAccountsWritePersistence(w, r)
case "/accounts/get_username":
diff --git a/pkg/api/api0/server.go b/pkg/api/api0/server.go
index 29cbcda..1efc5c2 100644
--- a/pkg/api/api0/server.go
+++ b/pkg/api/api0/server.go
@@ -17,15 +17,30 @@ import (
)
/*
- /server/heartbeat:
- POST:
- /server/update_values:
- POST:
/server/remove_server:
DELETE:
*/
-func (h *Handler) handleServerAddServer(w http.ResponseWriter, r *http.Request) {
+func (h *Handler) handleServerUpsert(w http.ResponseWriter, r *http.Request) {
+ // note: if the API is confusing, see:
+ // - https://github.com/R2Northstar/NorthstarLauncher/commit/753dda6231bbb2adf585bbc916c0b220e816fcdc
+ // - https://github.com/R2Northstar/NorthstarLauncher/blob/v1.9.7/NorthstarDLL/masterserver.cpp
+
+ var isCreate, canCreate, isUpdate, canUpdate bool
+ switch r.URL.Path {
+ case "/server/add_server":
+ isCreate = true
+ canCreate = true
+ case "/server/update_values":
+ canCreate = true
+ fallthrough
+ case "/server/heartbeat":
+ isUpdate = true
+ canUpdate = true
+ default:
+ panic("unhandled path")
+ }
+
if r.Method != http.MethodOptions && r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
@@ -73,153 +88,237 @@ func (h *Handler) handleServerAddServer(w http.ResponseWriter, r *http.Request)
}
}
- var s Server
-
- if v := r.URL.Query().Get("port"); v == "" {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("port param is required"),
- })
- return
- } else if n, err := strconv.ParseUint(v, 10, 16); err != nil {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("port param is invalid: %v", err),
- })
- return
- } else {
- s.Addr = netip.AddrPortFrom(raddr.Addr(), uint16(n))
+ var l ServerListLimit
+ if n := h.MaxServers; n > 0 {
+ l.MaxServers = n
+ } else if n == 0 {
+ l.MaxServers = 1000
+ }
+ if n := h.MaxServersPerIP; n > 0 {
+ l.MaxServersPerIP = n
+ } else if n == 0 {
+ l.MaxServersPerIP = 50
}
- if v := r.URL.Query().Get("authPort"); v == "" {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is required"),
- })
- return
- } else if n, err := strconv.ParseUint(v, 10, 16); err != nil {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is invalid: %v", err),
- })
- return
- } else {
- s.AuthPort = uint16(n)
+ var s *Server
+ if canCreate {
+ s = &Server{}
}
- if v := r.URL.Query().Get("name"); v == "" {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("name param must not be empty"),
- })
- return
- } else {
- // TODO: bad word censoring
- if n := 256; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 63
- v = v[:n]
+ var u *ServerUpdate
+ if canUpdate {
+ u = &ServerUpdate{
+ Heartbeat: true,
+ ExpectIP: raddr.Addr(),
}
- s.Name = v
}
- if v := r.URL.Query().Get("description"); v != "" {
- // TODO: bad word censoring
- if n := 1024; len(v) > n { // NorthstarLauncher@v1.9.7 doesn't have a limit
- v = v[:n]
+ if canUpdate {
+ if v := r.URL.Query().Get("id"); v == "" {
+ if isUpdate {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("id param is required"),
+ })
+ return
+ }
+ } else {
+ u.ID = v
}
- s.Description = v
}
- if v := r.URL.Query().Get("map"); v != "" {
- if n := 64; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 31
- v = v[:n]
+ if canCreate {
+ if v := r.URL.Query().Get("port"); v == "" {
+ if isCreate {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("port param is required"),
+ })
+ return
+ }
+ } else if n, err := strconv.ParseUint(v, 10, 16); err != nil {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("port param is invalid: %v", err),
+ })
+ return
+ } else {
+ s.Addr = netip.AddrPortFrom(raddr.Addr(), uint16(n))
}
- s.Map = v
- }
- if v := r.URL.Query().Get("playlist"); v != "" {
- if n := 64; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 15
- v = v[:n]
+ if v := r.URL.Query().Get("authPort"); v == "" {
+ if isCreate {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is required"),
+ })
+ }
+ return
+ } else if n, err := strconv.ParseUint(v, 10, 16); err != nil {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("authPort param is invalid: %v", err),
+ })
+ return
+ } else {
+ s.AuthPort = uint16(n)
}
- s.Playlist = v
- }
- if n, err := strconv.ParseUint(r.URL.Query().Get("maxPlayers"), 10, 8); err == nil {
- s.MaxPlayers = int(n)
+ if v := r.URL.Query().Get("password"); len(v) > 128 {
+ if isCreate {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("password is too long"),
+ })
+ return
+ }
+ } else {
+ s.Password = v
+ }
}
- if v := r.URL.Query().Get("password"); len(v) > 128 {
- respJSON(w, r, http.StatusBadRequest, map[string]any{
- "success": false,
- "error": ErrorCode_BAD_REQUEST,
- "msg": ErrorCode_BAD_REQUEST.Messagef("password is too long"),
- })
- return
- } else {
- s.Password = v
+ if canCreate || canUpdate {
+ if v := r.URL.Query().Get("name"); v == "" {
+ if isCreate {
+ respJSON(w, r, http.StatusBadRequest, map[string]any{
+ "success": false,
+ "error": ErrorCode_BAD_REQUEST,
+ "msg": ErrorCode_BAD_REQUEST.Messagef("name param must not be empty"),
+ })
+ return
+ }
+ } else {
+ // TODO: bad word censoring
+ if n := 256; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 63
+ v = v[:n]
+ }
+ if canCreate {
+ s.Name = v
+ }
+ if canUpdate {
+ u.Name = &v
+ }
+ }
+
+ if v := r.URL.Query().Get("description"); v != "" {
+ // TODO: bad word censoring
+ if n := 1024; len(v) > n { // NorthstarLauncher@v1.9.7 doesn't have a limit
+ v = v[:n]
+ }
+ if canCreate {
+ s.Description = v
+ }
+ if canUpdate {
+ u.Description = &v
+ }
+ }
+
+ if v := r.URL.Query().Get("map"); v != "" {
+ if n := 64; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 31
+ v = v[:n]
+ }
+ if canCreate {
+ s.Map = v
+ }
+ if canUpdate {
+ u.Map = &v
+ }
+ }
+
+ if v := r.URL.Query().Get("playlist"); v != "" {
+ if n := 64; len(v) > n { // NorthstarLauncher@v1.9.7 limits it to 15
+ v = v[:n]
+ }
+ if canCreate {
+ s.Playlist = v
+ }
+ if canUpdate {
+ u.Playlist = &v
+ }
+ }
+
+ if n, err := strconv.ParseUint(r.URL.Query().Get("playerCount"), 10, 8); err == nil {
+ if canCreate {
+ s.MaxPlayers = int(n)
+ }
+ if canUpdate {
+ s.MaxPlayers = int(n)
+ }
+ }
+
+ if n, err := strconv.ParseUint(r.URL.Query().Get("maxPlayers"), 10, 8); err == nil {
+ if canCreate {
+ s.MaxPlayers = int(n)
+ }
+ if canUpdate {
+ s.MaxPlayers = int(n)
+ }
+ }
}
- var modInfoErr error
- if err := r.ParseMultipartForm(1 << 18 /*.25 MB*/); err == nil {
- if mf, mfHdr, err := r.FormFile("modinfo"); err == nil {
- if mfHdr.Size < 1<<18 {
- var obj struct {
- Mods []struct {
- Name string `json:"Name"`
- Version string `json:"Version"`
- RequiredOnClient bool `json:"RequiredOnClient"`
- } `json:"Mods"`
- }
- if err := json.NewDecoder(mf).Decode(&obj); err == nil {
- for _, m := range obj.Mods {
- if m.Name != "" {
- if m.Version == "" {
- m.Version = "0.0.0"
+ if canCreate {
+ var modInfoErr error
+ if err := r.ParseMultipartForm(1 << 18 /*.25 MB*/); err == nil {
+ if mf, mfHdr, err := r.FormFile("modinfo"); err == nil {
+ if mfHdr.Size < 1<<18 {
+ var obj struct {
+ Mods []struct {
+ Name string `json:"Name"`
+ Version string `json:"Version"`
+ RequiredOnClient bool `json:"RequiredOnClient"`
+ } `json:"Mods"`
+ }
+ if err := json.NewDecoder(mf).Decode(&obj); err == nil {
+ for _, m := range obj.Mods {
+ if m.Name != "" {
+ if m.Version == "" {
+ m.Version = "0.0.0"
+ }
+ s.ModInfo = append(s.ModInfo, ServerModInfo{
+ Name: m.Name,
+ Version: m.Version,
+ RequiredOnClient: m.RequiredOnClient,
+ })
}
- s.ModInfo = append(s.ModInfo, ServerModInfo{
- Name: m.Name,
- Version: m.Version,
- RequiredOnClient: m.RequiredOnClient,
- })
}
+ } else {
+ modInfoErr = fmt.Errorf("parse modinfo file: %w", err)
}
} else {
- modInfoErr = fmt.Errorf("parse modinfo file: %w", err)
+ modInfoErr = fmt.Errorf("get modinfo file: too large (size %d)", mfHdr.Size)
}
+ mf.Close()
} else {
- modInfoErr = fmt.Errorf("get modinfo file: too large (size %d)", mfHdr.Size)
+ modInfoErr = fmt.Errorf("get modinfo file: %w", err)
}
- mf.Close()
} else {
- modInfoErr = fmt.Errorf("get modinfo file: %w", err)
+ if isCreate {
+ modInfoErr = fmt.Errorf("parse multipart form: %w", err)
+ }
+ }
+ if modInfoErr != nil {
+ hlog.FromRequest(r).Warn().
+ Err(err).
+ Msgf("failed to parse modinfo")
}
- } else {
- modInfoErr = fmt.Errorf("parse multipart form: %w", err)
- }
- if modInfoErr != nil {
- hlog.FromRequest(r).Warn().
- Err(err).
- Msgf("failed to parse modinfo")
- }
-
- var l ServerListLimit
- if n := h.MaxServers; n > 0 {
- l.MaxServers = n
- } else if n == 0 {
- l.MaxServers = 1000
- }
- if n := h.MaxServersPerIP; n > 0 {
- l.MaxServersPerIP = n
- } else if n == 0 {
- l.MaxServersPerIP = 50
}
- nsrv, err := h.ServerList.ServerHybridUpdatePut(nil, &s, l)
+ nsrv, err := h.ServerList.ServerHybridUpdatePut(u, s, l)
if err != nil {
+ if errors.Is(err, ErrServerListUpdateWrongIP) {
+ respJSON(w, r, http.StatusForbidden, map[string]any{
+ "success": false,
+ "error": ErrorCode_UNAUTHORIZED_GAMESERVER,
+ "msg": ErrorCode_UNAUTHORIZED_GAMESERVER.Messagef("%v", err),
+ })
+ return
+ }
if errors.Is(err, ErrServerListDuplicateAuthAddr) {
respJSON(w, r, http.StatusForbidden, map[string]any{
"success": false,
@@ -238,7 +337,7 @@ func (h *Handler) handleServerAddServer(w http.ResponseWriter, r *http.Request)
}
hlog.FromRequest(r).Error().
Err(err).
- Msgf("failed to add server to list")
+ Msgf("failed to update server list")
respJSON(w, r, http.StatusInternalServerError, map[string]any{
"success": false,
"error": ErrorCode_INTERNAL_SERVER_ERROR,
@@ -303,6 +402,7 @@ func (h *Handler) handleServerAddServer(w http.ResponseWriter, r *http.Request)
"error": ErrorCode_NO_GAMESERVER_RESPONSE,
"msg": ErrorCode_NO_GAMESERVER_RESPONSE.Messagef("verification timed out"),
})
+ return
}
}