From 2e3bc3fd0f9019fdabb96a3d67369aadedd85808 Mon Sep 17 00:00:00 2001 From: Sean Murphy Date: Sat, 7 Apr 2018 18:42:52 -0600 Subject: [PATCH] Adding in context checking for playerID, method to read any byte array to an event --- events.go | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++ filter.go | 4 +++ game.go | 51 ++++++++++++++++++++++++++++---- validate.go | 32 +++++++++++++++++++++ 4 files changed, 164 insertions(+), 6 deletions(-) diff --git a/events.go b/events.go index 88e53ee..33bbc99 100644 --- a/events.go +++ b/events.go @@ -1,6 +1,8 @@ package sh import ( + "encoding/json" + "errors" "time" ) @@ -30,6 +32,87 @@ type Event interface { GetType() string } +func UnmarshalEvent(b []byte) (Event, error) { + //read it as a base event + bt := BaseEvent{} + err := json.Unmarshal(b, &bt) + if err != nil { + return nil, err + } + //finally read it as it's real event + switch bt.GetType() { + case TypePlayerJoin: + fallthrough + case TypePlayerReady: + fallthrough + case TypePlayerAcknowledge: + e := PlayerEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypePlayerNominate: + fallthrough + case TypePlayerSpecialElection: + fallthrough + case TypePlayerExecute: + fallthrough + case TypePlayerInvestigate: + e := PlayerPlayerEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypePlayerVote: + e := PlayerVoteEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypePlayerLegislate: + e := PlayerLegislateEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypeRequestAcknowledge: + fallthrough + case TypeRequestVote: + fallthrough + case TypeRequestNominate: + fallthrough + case TypeRequestLegislate: + fallthrough + case TypeRequestExecutiveAction: + e := RequestEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypeGameInformation: + e := InformationEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + case TypeGameUpdate: + e := GameEvent{} + err = json.Unmarshal(b, &e) + if err != nil { + return bt, err + } + return e, nil + default: + return bt, errors.New("Unknown Event Type") + } +} + type BaseEvent struct { ID int `json:"id"` Type string `json:"type"` diff --git a/filter.go b/filter.go index a66a2e7..975c691 100644 --- a/filter.go +++ b/filter.go @@ -26,6 +26,10 @@ func (g Game) Filter(ctx context.Context) Game { InvestigatedBy: p.InvestigatedBy, ExecutedBy: p.ExecutedBy, } + if me.ID == p.ID { + np.Party = p.Party + np.Role = p.Role + } if me.ID == p.InvestigatedBy { np.Party = p.Party } diff --git a/game.go b/game.go index 255fb01..a5d398a 100644 --- a/game.go +++ b/game.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "fmt" - "os" + "io" "sync" ) @@ -45,24 +45,40 @@ func NewSecretHitler() *SecretHitler { ret := new(SecretHitler) ret.subscribers = make(map[string]chan<- Event) ec := make(chan Event) + //Make the engine a subscriber ret.subscribers["engine"] = ec go func() { for { select { case e := <-ec: - //TODO If the game is over, then return + if e == nil { + fmt.Println("Exiting game engine loop via nil read") + return + } if nes, err := ret.Engine(e); err == nil { fmt.Println("engine: Produced:", nes) for _, ne := range nes { - err = ret.SubmitEvent(context.TODO(), ne) + ctx := context.Background() + ctx = context.WithValue(ctx, "playerID", "engine") + err = ret.SubmitEvent(ctx, ne) if err != nil { fmt.Println("Apply Error:", err) } } } + //If the game is over, then return + if ret.Game.State == GameStateFinished { + ret.m.Lock() + for k, v := range ret.subscribers { + delete(ret.subscribers, k) + close(v) + } + ret.m.Unlock() + break + } } } - fmt.Println("Exiting game engine loop") + fmt.Println("Exiting game engine loop via loop break") }() return ret } @@ -70,10 +86,9 @@ func NewSecretHitler() *SecretHitler { type SecretHitler struct { Game - Log *os.File + Log io.Writer m sync.RWMutex - //Make the engine a subscriber subscribers map[string]chan<- Event } @@ -105,6 +120,9 @@ func (sh *SecretHitler) SubmitEvent(ctx context.Context, e Event) error { } func (sh *SecretHitler) AddSubscriber(key string, channel chan<- Event) { + if sh.Game.State == GameStateFinished { + return + } sh.m.Lock() sh.subscribers[key] = channel sh.m.Unlock() @@ -124,6 +142,27 @@ func (sh *SecretHitler) BroadcastEvent(e Event) { } } +//ReadEventLog will read the associated event log and publish all the events to the included channel +func ReadEventLog(r io.Reader, c chan<- Event) error { + //Read in a byte slice + d := json.NewDecoder(r) + var err error + for err == nil { + var rm json.RawMessage + err = d.Decode(&rm) + if err != nil { + return err + } + e, err := UnmarshalEvent(rm) + if err != nil { + return err + } + c <- e + } + close(c) + return nil +} + type Game struct { EventID int `json:"eventID,omitempty"` State string `json:"state,omitempty"` diff --git a/validate.go b/validate.go index 4ce161f..a011fb7 100644 --- a/validate.go +++ b/validate.go @@ -8,10 +8,14 @@ import ( //Validate ensures that an event is consistent with the current state and then //sends it to the event log. func (g Game) Validate(ctx context.Context, e Event) error { + pid := ctx.Value("playerID").(string) //Players must all be ready for game to start switch e.GetType() { case TypePlayerJoin: pje := e.(PlayerEvent) + if pje.Player.ID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.State != GameStateLobby { return errors.New("Players can only join while the game is in the lobby state") } @@ -25,6 +29,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerReady: pre := e.(PlayerEvent) + if pre.Player.ID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.State != GameStateLobby { return errors.New("Players can only ready while the game is in the lobby state") } @@ -40,6 +47,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { return errors.New("No player found with matching ID") case TypePlayerAcknowledge: pae := e.(PlayerEvent) + if pae.Player.ID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.State != "init" { return errors.New("Players can only ack while the game is in the init state") } @@ -63,6 +73,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { return errors.New("No player found with matching ID") case TypePlayerNominate: ope := e.(PlayerPlayerEvent) + if ope.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateNominating { return errors.New("Players can only vote while the round is in the nominating state") } @@ -84,6 +97,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerVote: pve := e.(PlayerVoteEvent) + if pve.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateVoting { return errors.New("Players can only vote while the round is in the voting state") } @@ -106,6 +122,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerLegislate: ple := e.(PlayerLegislateEvent) + if ple.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateLegislating { return errors.New("Players can only legislate while the round is in the legislating state") } @@ -132,6 +151,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerInvestigate: ope := e.(PlayerPlayerEvent) + if ope.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateExecutiveAction { return errors.New("Players can only investigate while the round is in the executive_action state") } @@ -150,6 +172,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerSpecialElection: ope := e.(PlayerPlayerEvent) + if ope.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateExecutiveAction { return errors.New("Players can only call a special election while the round is in the executive_action state") } @@ -168,6 +193,9 @@ func (g Game) Validate(ctx context.Context, e Event) error { } case TypePlayerExecute: ope := e.(PlayerPlayerEvent) + if ope.PlayerID != pid { + return errors.New("PlayerID must match currently authenticated user") + } if g.Round.State != RoundStateExecutiveAction { return errors.New("Players can only execute while the round is in the executive_action state") } @@ -184,6 +212,10 @@ func (g Game) Validate(ctx context.Context, e Event) error { } } } + default: + if pid != "admin" || pid != "engine" { + return errors.New("Not Authorized") + } } return nil