github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/device_clone_state.go (about)

     1  // Copyright 2018 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"fmt"
     8  )
     9  
    10  const (
    11  	DefaultCloneTokenValue string = "00000000000000000000000000000000"
    12  )
    13  
    14  type DeviceCloneState struct {
    15  	Prior  string
    16  	Stage  string
    17  	Clones int
    18  }
    19  
    20  type DeviceCloneStateJSONFile struct {
    21  	*JSONFile
    22  }
    23  
    24  type cloneDetectionResponse struct {
    25  	AppStatusEmbed
    26  	Token  string `json:"token"`
    27  	Clones int    `json:"clones"`
    28  }
    29  
    30  func (d DeviceCloneState) IsClone() bool {
    31  	return d.Clones > 1
    32  }
    33  
    34  func UpdateDeviceCloneState(m MetaContext) (before, after int, err error) {
    35  	d, err := GetDeviceCloneState(m)
    36  	if err != nil {
    37  		return 0, 0, err
    38  	}
    39  	before = d.Clones
    40  
    41  	prior, stage := d.Prior, d.Stage
    42  	if prior == "" {
    43  		// first run
    44  		prior = DefaultCloneTokenValue
    45  	}
    46  	if stage == "" {
    47  		stage, err = RandHexString("", 16)
    48  		if err != nil {
    49  			return 0, 0, err
    50  		}
    51  		if err = SetDeviceCloneState(m, DeviceCloneState{Prior: prior, Stage: stage, Clones: d.Clones}); err != nil {
    52  			return 0, 0, err
    53  		}
    54  	}
    55  
    56  	// POST these tokens to the server
    57  	arg := APIArg{
    58  		Endpoint:    "device/clone_detection_token",
    59  		SessionType: APISessionTypeREQUIRED,
    60  		Args: HTTPArgs{
    61  			"device_id": m.G().ActiveDevice.DeviceID(),
    62  			"prior":     S{Val: prior},
    63  			"stage":     S{Val: stage},
    64  		},
    65  	}
    66  	var res cloneDetectionResponse
    67  	if err = m.G().API.PostDecode(m, arg, &res); err != nil {
    68  		return 0, 0, err
    69  	}
    70  
    71  	after = res.Clones
    72  	err = SetDeviceCloneState(m, DeviceCloneState{Prior: res.Token, Stage: "", Clones: after})
    73  	return before, after, err
    74  }
    75  
    76  func deviceCloneJSONPaths(m MetaContext) (string, string, string) {
    77  	deviceID := m.G().ActiveDevice.DeviceID()
    78  	p := fmt.Sprintf("%s.prior", deviceID)
    79  	s := fmt.Sprintf("%s.stage", deviceID)
    80  	c := fmt.Sprintf("%s.clones", deviceID)
    81  	return p, s, c
    82  }
    83  
    84  func GetDeviceCloneState(m MetaContext) (state DeviceCloneState, err error) {
    85  	reader, err := newDeviceCloneStateReader(m)
    86  	if err != nil {
    87  		return state, err
    88  	}
    89  	pPath, sPath, cPath := deviceCloneJSONPaths(m)
    90  	p, _ := reader.GetStringAtPath(pPath)
    91  	s, _ := reader.GetStringAtPath(sPath)
    92  	c, _ := reader.GetIntAtPath(cPath)
    93  	if c < 1 {
    94  		c = 1
    95  	}
    96  	state = DeviceCloneState{Prior: p, Stage: s, Clones: c}
    97  	m.Debug("GetDeviceCloneState: %+v", state)
    98  	return state, nil
    99  }
   100  
   101  func SetDeviceCloneState(m MetaContext, d DeviceCloneState) error {
   102  	m.Debug("SetDeviceCloneState: %+v", d)
   103  	writer, err := newDeviceCloneStateWriter(m)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	tx, err := writer.BeginTransaction()
   108  	if err != nil {
   109  		return err
   110  	}
   111  	// From this point on, if there's an error, we abort the
   112  	// transaction.
   113  	defer func() {
   114  		if tx != nil {
   115  			err := tx.Rollback()
   116  			if err != nil {
   117  				m.Debug("SetDeviceCloneState: rollback error: %+v", err)
   118  			}
   119  			err = tx.Abort()
   120  			if err != nil {
   121  				m.Debug("SetDeviceCloneState: abort error: %+v", err)
   122  			}
   123  		}
   124  	}()
   125  
   126  	pPath, sPath, cPath := deviceCloneJSONPaths(m)
   127  	if err = writer.SetStringAtPath(pPath, d.Prior); err != nil {
   128  		return err
   129  	}
   130  	if err = writer.SetStringAtPath(sPath, d.Stage); err != nil {
   131  		return err
   132  	}
   133  	if err = writer.SetIntAtPath(cPath, d.Clones); err != nil {
   134  		return err
   135  	}
   136  	if err = tx.Commit(); err != nil {
   137  		return err
   138  	}
   139  
   140  	// Zero out the TX so that we don't abort it in the defer()
   141  	tx = nil
   142  	return nil
   143  }
   144  
   145  func newDeviceCloneStateReader(m MetaContext) (*DeviceCloneStateJSONFile, error) {
   146  	f := DeviceCloneStateJSONFile{NewJSONFile(m.G(), m.G().Env.GetDeviceCloneStateFilename(), "device clone state")}
   147  	err := f.Load(false)
   148  	return &f, err
   149  }
   150  
   151  func newDeviceCloneStateWriter(m MetaContext) (*DeviceCloneStateJSONFile, error) {
   152  	f := DeviceCloneStateJSONFile{NewJSONFile(m.G(), m.G().Env.GetDeviceCloneStateFilename(), "device clone state")}
   153  	err := f.Load(false)
   154  	return &f, err
   155  }