Compare commits

..

11 Commits

Author SHA1 Message Date
2bd61dc571 login_test: more stale comments lol 2023-12-06 20:23:29 -06:00
cba01a877d login_test: fix sendAndRecv
removed the stale typecast lol
2023-12-06 20:17:36 -06:00
e1b9fa5d99 login_test: refactor, abstracted send and recv
validation
2023-12-06 20:15:06 -06:00
77751a2aa0 login_tests: annotate tests 2023-12-06 17:35:02 -06:00
8e84f0c7b2 more better README
haven't decided if showing an asciinema of the docker compose
environment is really useful or informative. maybe i'll
record a GIF of some gameplay in the future
2023-12-06 17:20:58 -06:00
c902559eac LICENSE: finally added! 2023-12-06 17:19:32 -06:00
1c40998cb6 README: oops, wrong link 2023-12-06 17:12:39 -06:00
988368c307 added unit tests workflow badge to README 2023-12-06 17:10:39 -06:00
01ebf4499f login_test: added TestCharacterSequence
tests the complete account/character creation sequence of packets
2023-12-06 17:08:59 -06:00
3a14d807d2 login: minor refactoring 2023-12-06 17:08:05 -06:00
141858d6c3 db/account: removed debug log 2023-12-06 16:40:06 -06:00
5 changed files with 130 additions and 33 deletions

7
LICENSE.md Normal file
View File

@ -0,0 +1,7 @@
Copyright © 2022 Gopenfusion Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,5 +1,12 @@
# gopenfusion # gopenfusion
<p align="center">
<a href="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml"><img src="https://github.com/CPunch/gopenfusion/actions/workflows/tests.yaml/badge.svg?branch=main" alt="Workflow"></a>
<a href="https://github.com/CPunch/gopenfusion/blob/main/LICENSE.md"><img src="https://img.shields.io/github/license/CPunch/gopenfusion" alt="License"></a>
<br>
<a href="https://asciinema.org/a/625524" target="_blank"><img src="https://asciinema.org/a/625524.svg" /></a>
</p>
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) (see: `cnet/`) and accompanying services, written in Go. A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) (see: `cnet/`) and accompanying services, written in Go.
## Landwalker demo ## Landwalker demo

View File

@ -2,7 +2,6 @@ package db
import ( import (
"errors" "errors"
"log"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -62,7 +61,6 @@ func (db *DBHandler) TryLogin(Login, Password string) (*Account, error) {
var account Account var account Account
row.Next() row.Next()
if err := sqlscan.ScanRow(&account, row); err != nil { if err := sqlscan.ScanRow(&account, row); err != nil {
log.Printf("Error scanning row: %v", err)
return nil, ErrLoginInvalidID return nil, ErrLoginInvalidID
} }

View File

@ -280,11 +280,6 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
return fmt.Errorf("loginServer has found no linked shards") return fmt.Errorf("loginServer has found no linked shards")
} }
key, err := protocol.GenSerialKey()
if err != nil {
return err
}
// TODO: better shard selection logic pls // TODO: better shard selection logic pls
// for now, pick random shard // for now, pick random shard
shard := shards[rand.Intn(len(shards))] shard := shards[rand.Intn(len(shards))]
@ -302,6 +297,11 @@ func (server *LoginServer) ShardSelect(peer *cnet.Peer, pkt protocol.Packet) err
return SendFail(peer) return SendFail(peer)
} }
key, err := protocol.GenSerialKey()
if err != nil {
return err
}
// share the login attempt // share the login attempt
server.redisHndlr.QueueLogin(key, redis.LoginMetadata{ server.redisHndlr.QueueLogin(key, redis.LoginMetadata{
FEKey: peer.FE_key, FEKey: peer.FE_key,

View File

@ -2,6 +2,7 @@ package login_test
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"net" "net"
"os" "os"
@ -24,6 +25,25 @@ var (
rh *redis.RedisHandler rh *redis.RedisHandler
) )
var (
testCharCreate = protocol.SP_CL2LS_REQ_CHAR_CREATE{
PCStyle: protocol.SPCStyle{
INameCheck: 1, SzFirstName: "Hector",
SzLastName: "Bannonvenom", IGender: 1, IFaceStyle: 1,
IHairStyle: 17, IHairColor: 11, ISkinColor: 10, IEyeColor: 2,
IHeight: 1, IBody: 0, IClass: 0,
},
SOn_Item: protocol.SOnItem{
IEquipHandID: 0, IEquipUBID: 53, IEquipLBID: 17, IEquipFootID: 58,
IEquipHeadID: 0, IEquipFaceID: 0, IEquipBackID: 0,
},
SOn_Item_Index: protocol.SOnItem_Index{
IEquipUBID_index: 15, IEquipLBID_index: 12, IEquipFootID_index: 17,
IFaceStyle: 2, IHairStyle: 18,
},
}
)
func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent) *cnet.Peer { func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent) *cnet.Peer {
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", loginPort)) conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", loginPort))
is.NoErr(err) is.NoErr(err)
@ -36,6 +56,18 @@ func makeDummyPeer(ctx context.Context, is *is.I, recv chan<- *cnet.PacketEvent)
return peer return peer
} }
func sendAndRecv(peer *cnet.Peer, recv chan *cnet.PacketEvent, is *is.I, sID, rID uint32, out, in interface{}) {
// send out packet
err := peer.Send(sID, out)
is.NoErr(err) // peer.Send() should not return an error
// receive response
evnt := <-recv
is.Equal(evnt.PktID, rID) // should receive expected type
is.NoErr(protocol.NewPacket(evnt.Pkt).Decode(in)) // packet.Decode() should not return an error
}
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -95,6 +127,7 @@ func TestMain(m *testing.M) {
os.Exit(ret) os.Exit(ret)
} }
// This test tries a typical login sequence.
func TestLoginSuccSequence(t *testing.T) { func TestLoginSuccSequence(t *testing.T) {
is := is.New(t) is := is.New(t)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -105,29 +138,23 @@ func TestLoginSuccSequence(t *testing.T) {
defer peer.Kill() defer peer.Kill()
// send login request (this should create an account) // send login request (this should create an account)
err := peer.Send(protocol.P_CL2LS_REQ_LOGIN, protocol.SP_CL2LS_REQ_LOGIN{
SzID: "testLoginSequence",
SzPassword: "test",
})
is.NoErr(err) // peer.Send() should not return an error
// receive login response
evnt := <-recv
is.Equal(int(evnt.PktID), protocol.P_LS2CL_REP_LOGIN_SUCC) // should receive P_LS2CL_REP_LOGIN_SUCC
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
err = protocol.NewPacket(evnt.Pkt).Decode(&resp) sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC,
is.NoErr(err) // packet.Decode() should not return an error protocol.SP_CL2LS_REQ_LOGIN{
SzID: "testLoginSequence",
SzPassword: "test",
}, &resp)
// verify response // verify response
is.Equal(resp.SzID, "testLoginSequence") // should have the same ID is.Equal(resp.SzID, "testLoginSequence") // should have the same ID
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
// verify account was created // verify account was created
_, err = testDB.TryLogin("testLoginSequence", "test") _, err := testDB.TryLogin("testLoginSequence", "test")
is.NoErr(err) // TryLogin() should not return an error is.NoErr(err) // TryLogin() should not return an error
} }
// This test tries a typical login sequence, but with an invalid password.
func TestLoginFailSequence(t *testing.T) { func TestLoginFailSequence(t *testing.T) {
is := is.New(t) is := is.New(t)
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
@ -137,22 +164,80 @@ func TestLoginFailSequence(t *testing.T) {
peer := makeDummyPeer(ctx, is, recv) peer := makeDummyPeer(ctx, is, recv)
defer peer.Kill() defer peer.Kill()
// send login request (this should create an account) // send login request (this should not create an account)
err := peer.Send(protocol.P_CL2LS_REQ_LOGIN, protocol.SP_CL2LS_REQ_LOGIN{
SzID: "",
SzPassword: "",
})
is.NoErr(err) // peer.Send() should not return an error
// receive login response
evnt := <-recv
is.Equal(int(evnt.PktID), protocol.P_LS2CL_REP_LOGIN_FAIL) // should receive P_LS2CL_REP_LOGIN_FAIL
var resp protocol.SP_LS2CL_REP_LOGIN_FAIL var resp protocol.SP_LS2CL_REP_LOGIN_FAIL
err = protocol.NewPacket(evnt.Pkt).Decode(&resp) sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_FAIL,
is.NoErr(err) // packet.Decode() should not return an error protocol.SP_CL2LS_REQ_LOGIN{
SzID: "",
SzPassword: "",
}, &resp)
// verify response // verify response
is.Equal(resp.SzID, "") // should have the same ID is.Equal(resp.SzID, "") // should have the same ID
is.Equal(resp.IErrorCode, int32(login.LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)) // should respond with LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH is.Equal(resp.IErrorCode, int32(login.LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)) // should respond with LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH
} }
// This test tries a typical login sequence, and creates a character
func TestCharacterSequence(t *testing.T) {
is := is.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
recv := make(chan *cnet.PacketEvent)
peer := makeDummyPeer(ctx, is, recv)
defer peer.Kill()
// send login request (this should create an account)
var resp protocol.SP_LS2CL_REP_LOGIN_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_LOGIN, protocol.P_LS2CL_REP_LOGIN_SUCC,
protocol.SP_CL2LS_REQ_LOGIN{
SzID: "testCharacterSequence",
SzPassword: "test",
}, &resp)
// verify response
is.Equal(resp.SzID, "testCharacterSequence") // should have the same ID
is.Equal(resp.ICharCount, int8(0)) // should have 0 characters
// perform key swap
peer.E_key = protocol.CreateNewKey(
resp.UiSvrTime,
uint64(resp.ICharCount+1),
uint64(resp.ISlotNum+1),
)
peer.FE_key = protocol.CreateNewKey(
binary.LittleEndian.Uint64([]byte(protocol.DEFAULT_KEY)),
0,
1,
)
// send character name check request
var charResp protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_SAVE_CHAR_NAME, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC,
protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME{
ISlotNum: 1,
IGender: 1,
IFNCode: 260,
ILNCode: 551,
IMNCode: 33,
SzFirstName: testCharCreate.PCStyle.SzFirstName,
SzLastName: testCharCreate.PCStyle.SzLastName,
}, &charResp)
// verify response
is.Equal(charResp.ISlotNum, int8(1)) // should have the same slot number
is.Equal(charResp.IGender, int8(1)) // should have the same gender
is.Equal(charResp.SzFirstName, testCharCreate.PCStyle.SzFirstName) // should have the same first name
is.Equal(charResp.SzLastName, testCharCreate.PCStyle.SzLastName) // should have the same last name
// send character create request
charCreate := testCharCreate
charCreate.PCStyle.IPC_UID = charResp.IPC_UID
var charCreateResp protocol.SP_LS2CL_REP_CHAR_CREATE_SUCC
sendAndRecv(peer, recv, is, protocol.P_CL2LS_REQ_CHAR_CREATE, protocol.P_LS2CL_REP_CHAR_CREATE_SUCC,
charCreate, &charCreateResp)
// verify response
is.Equal(charCreate.PCStyle, charCreateResp.SPC_Style) // should have the same PCStyle
is.Equal(charCreate.SOn_Item, charCreateResp.SOn_Item) // should have the same SOn_Item
}