1// Copyright 2018 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
7	"fmt"
8)
9
10const (
11	DefaultCloneTokenValue string = "00000000000000000000000000000000"
12)
13
14type DeviceCloneState struct {
15	Prior  string
16	Stage  string
17	Clones int
18}
19
20type DeviceCloneStateJSONFile struct {
21	*JSONFile
22}
23
24type cloneDetectionResponse struct {
25	AppStatusEmbed
26	Token  string `json:"token"`
27	Clones int    `json:"clones"`
28}
29
30func (d DeviceCloneState) IsClone() bool {
31	return d.Clones > 1
32}
33
34func 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
76func 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
84func 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
101func 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
145func 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
151func 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}
156