github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/flip/replay.go (about) 1 package flip 2 3 import ( 4 "context" 5 "io" 6 "time" 7 ) 8 9 type GameMessageReplayed struct { 10 GameMessageWrappedEncoded 11 Time time.Time 12 } 13 14 type GameHistory []GameMessageReplayed 15 16 type GameHistoryPlayer struct { 17 Device UserDevice 18 Commitment Commitment 19 Reveal *Secret 20 } 21 22 type GameSummary struct { 23 Err error 24 Players []GameHistoryPlayer 25 Result Result 26 } 27 28 func (g GameHistory) start(rh ReplayHelper) (game *Game, rest GameHistory, err error) { 29 if len(g) == 0 { 30 return nil, nil, NewReplayError("cannot reply 0-length game") 31 } 32 first := &g[0] 33 rest = g[1:] 34 gmw, err := first.Decode() 35 if err != nil { 36 return nil, nil, err 37 } 38 t, err := gmw.Msg.Body.T() 39 if err != nil { 40 return nil, nil, err 41 } 42 if t != MessageType_START { 43 return nil, nil, NewReplayError("expected first message to be of type START") 44 } 45 start := gmw.Msg.Body.Start() 46 47 md := gmw.Msg.Md 48 if !md.Initiator.Eq(gmw.Sender) { 49 return nil, nil, NewReplayError("bad initiator; didn't match sender") 50 } 51 52 if !first.FirstInConversation { 53 return nil, nil, GameReplayError{md.GameID} 54 } 55 56 _, err = computeClockSkew(md, first.Time, start.StartTime.Time(), first.Time) 57 if err != nil { 58 return nil, nil, err 59 } 60 61 game = &Game{ 62 md: md, 63 isLeader: false, 64 start: first.Time, 65 key: md.ToKey(), 66 params: start, 67 gameUpdateCh: make(chan GameStateUpdateMessage), 68 players: make(map[UserDeviceKey]*GamePlayerState), 69 commitments: make(map[string]bool), 70 stage: Stage_ROUND1, 71 clogf: rh.CLogf, 72 } 73 74 return game, rest, nil 75 } 76 77 func runReplayLoop(ctx context.Context, game *Game, gh GameHistory) (err error) { 78 for _, m := range gh { 79 gmw, err := m.GameMessageWrappedEncoded.Decode() 80 if err != nil { 81 return err 82 } 83 err = game.handleMessage(ctx, gmw, m.Time) 84 if err == io.EOF { 85 return nil 86 } 87 if err != nil { 88 return err 89 } 90 } 91 return nil 92 } 93 94 func Replay(ctx context.Context, rh ReplayHelper, gh GameHistory) (*GameSummary, error) { 95 ret, err := replay(ctx, rh, gh) 96 if err != nil { 97 rh.CLogf(ctx, "Replay failure (%s); game dump: %+v", err, gh) 98 } 99 return ret, err 100 } 101 102 func replay(ctx context.Context, rh ReplayHelper, gh GameHistory) (*GameSummary, error) { 103 104 var game *Game 105 var err error 106 game, gh, err = gh.start(rh) 107 if err != nil { 108 return nil, err 109 } 110 111 errCh := make(chan error) 112 go func() { 113 err := runReplayLoop(ctx, game, gh) 114 close(game.gameUpdateCh) 115 errCh <- err 116 }() 117 118 summaryCh := make(chan GameSummary) 119 go func() { 120 var ret GameSummary 121 found := false 122 players := make(map[UserDeviceKey]*GameHistoryPlayer) 123 for msg := range game.gameUpdateCh { 124 switch { 125 case msg.Err != nil: 126 ret.Err = msg.Err 127 case msg.CommitmentComplete != nil: 128 for _, p := range msg.CommitmentComplete.Players { 129 ret.Players = append(ret.Players, GameHistoryPlayer{ 130 Device: p.Ud, 131 Commitment: p.C, 132 }) 133 } 134 for index, p := range ret.Players { 135 players[p.Device.ToKey()] = &ret.Players[index] 136 } 137 case msg.Reveal != nil: 138 if p, ok := players[msg.Reveal.User.ToKey()]; ok { 139 p.Reveal = &msg.Reveal.Reveal 140 delete(players, msg.Reveal.User.ToKey()) 141 } 142 case msg.Result != nil: 143 ret.Result = *msg.Result 144 found = true 145 } 146 } 147 if !found && ret.Err == nil { 148 var absentees []UserDevice 149 for _, v := range players { 150 absentees = append(absentees, v.Device) 151 } 152 var err error 153 if len(absentees) > 0 { 154 err = AbsenteesError{Absentees: absentees} 155 } else { 156 err = GameAbortedError{} 157 } 158 ret.Err = err 159 } 160 summaryCh <- ret 161 }() 162 163 err = <-errCh 164 if err != nil { 165 return nil, err 166 } 167 ret := <-summaryCh 168 if ret.Err != nil { 169 return nil, ret.Err 170 } 171 172 return &ret, nil 173 }