github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/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  }