1 #include "nugget_tools.h"
2
3 #include <app_nugget.h>
4 #include <nos/NuggetClient.h>
5
6 #include <chrono>
7 #include <cinttypes>
8 #include <cstring>
9 #include <iostream>
10 #include <thread>
11 #include <vector>
12
13 #ifdef ANDROID
14 #include <android-base/endian.h>
15 #include "nos/CitadeldProxyClient.h"
16 #else
17 #include "gflags/gflags.h"
18
19 DEFINE_string(nos_core_serial, "", "USB device serial number to open");
20 #endif // ANDROID
21
22 #ifndef LOG
23 #define LOG(x) std::cerr << __FILE__ << ":" << __LINE__ << " " << #x << ": "
24 #endif // LOG
25
26 using std::chrono::duration;
27 using std::chrono::duration_cast;
28 using std::chrono::high_resolution_clock;
29 using std::chrono::microseconds;
30 using std::string;
31
32 namespace nugget_tools {
33
IsDirectDeviceClient()34 bool IsDirectDeviceClient() {
35 #ifdef ANDROID
36 nos::NuggetClient client;
37 client.Open();
38 return client.IsOpen();
39 #else
40 return true;
41 #endif
42 }
43
GetCitadelUSBSerialNo()44 std::string GetCitadelUSBSerialNo() {
45 #ifdef ANDROID
46 return "";
47 #else
48 if (FLAGS_nos_core_serial.empty()) {
49 const char *env_default = secure_getenv("CITADEL_DEVICE");
50 if (env_default && *env_default) {
51 FLAGS_nos_core_serial.assign(env_default);
52 std::cerr << "Using CITADEL_DEVICE=" << FLAGS_nos_core_serial << "\n";
53 }
54 }
55 return FLAGS_nos_core_serial;
56 #endif
57 }
58
MakeNuggetClient()59 std::unique_ptr<nos::NuggetClientInterface> MakeNuggetClient() {
60 #ifdef ANDROID
61 std::unique_ptr<nos::NuggetClientInterface> client =
62 std::unique_ptr<nos::NuggetClientInterface>(new nos::NuggetClient());
63 client->Open();
64 if (!client->IsOpen()) {
65 client = std::unique_ptr<nos::NuggetClientInterface>(
66 new nos::CitadeldProxyClient());
67 }
68 return client;
69 #else
70 return std::unique_ptr<nos::NuggetClientInterface>(
71 new nos::NuggetClient(GetCitadelUSBSerialNo()));
72 #endif
73 }
74
MakeDirectNuggetClient()75 std::unique_ptr<nos::NuggetClient> MakeDirectNuggetClient() {
76 #ifdef ANDROID
77 std::unique_ptr<nos::NuggetClient> client =
78 std::unique_ptr<nos::NuggetClient>(new nos::NuggetClient());
79 return client;
80 #else
81 return std::unique_ptr<nos::NuggetClient>(
82 new nos::NuggetClient(GetCitadelUSBSerialNo()));
83 #endif
84 }
85
CyclesSinceBoot(nos::NuggetClientInterface * client,uint32_t * cycles)86 bool CyclesSinceBoot(nos::NuggetClientInterface *client, uint32_t *cycles) {
87 std::vector<uint8_t> buffer;
88 buffer.reserve(sizeof(uint32_t));
89 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_CYCLES_SINCE_BOOT,
90 buffer, &buffer) != app_status::APP_SUCCESS) {
91 perror("test");
92 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_CYCLES_SINCE_BOOT, ...) failed!\n";
93 return false;
94 };
95 if (buffer.size() != sizeof(uint32_t)) {
96 LOG(ERROR) << "Unexpected size of cycle count!\n";
97 return false;
98 }
99 *cycles = le32toh(*reinterpret_cast<uint32_t *>(buffer.data()));
100 return true;
101 }
102
ShowStats(const char * msg,const struct nugget_app_low_power_stats & stats)103 static void ShowStats(const char *msg,
104 const struct nugget_app_low_power_stats& stats) {
105 printf("%s\n", msg);
106 printf(" hard_reset_count %" PRIu64 "\n", stats.hard_reset_count);
107 printf(" time_since_hard_reset %" PRIu64 "\n",
108 stats.time_since_hard_reset);
109 printf(" wake_count %" PRIu64 "\n", stats.wake_count);
110 printf(" time_at_last_wake %" PRIu64 "\n", stats.time_at_last_wake);
111 printf(" time_spent_awake %" PRIu64 "\n", stats.time_spent_awake);
112 printf(" deep_sleep_count %" PRIu64 "\n", stats.deep_sleep_count);
113 printf(" time_at_last_deep_sleep %" PRIu64 "\n",
114 stats.time_at_last_deep_sleep);
115 printf(" time_spent_in_deep_sleep %" PRIu64 "\n",
116 stats.time_spent_in_deep_sleep);
117 }
118
RebootNuggetUnchecked(nos::NuggetClientInterface * client)119 bool RebootNuggetUnchecked(nos::NuggetClientInterface *client) {
120 std::vector<uint8_t> ignored;
121 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_REBOOT, ignored,
122 nullptr) != app_status::APP_SUCCESS) {
123 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_REBOOT, ...) failed!\n";
124 return false;
125 }
126 return true;
127 }
128
RebootNugget(nos::NuggetClientInterface * client)129 bool RebootNugget(nos::NuggetClientInterface *client) {
130 struct nugget_app_low_power_stats stats0;
131 struct nugget_app_low_power_stats stats1;
132 std::vector<uint8_t> buffer;
133
134 // Grab stats before sleeping
135 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
136 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
137 buffer, &buffer) != app_status::APP_SUCCESS) {
138 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
139 return false;
140 }
141 memcpy(&stats0, buffer.data(), sizeof(stats0));
142
143 // Capture the time here to allow for some tolerance on the reported time.
144 auto start = high_resolution_clock::now();
145
146 // Tell Nugget OS to reboot
147 if (!RebootNuggetUnchecked(client)) return false;
148
149 // Grab stats after sleeping
150 buffer.empty();
151 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
152 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
153 buffer, &buffer) != app_status::APP_SUCCESS) {
154 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
155 return false;
156 }
157 memcpy(&stats1, buffer.data(), sizeof(stats1));
158
159 // Figure a max elapsed time that Nugget OS should see (our time + 5%).
160 auto max_usecs =
161 duration_cast<microseconds>(high_resolution_clock::now() - start) *
162 105 / 100;
163
164 // Verify that Citadel rebooted
165 if (stats1.hard_reset_count == stats0.hard_reset_count + 1 &&
166 stats1.time_at_last_wake == 0 &&
167 stats1.deep_sleep_count == 0 &&
168 std::chrono::microseconds(stats1.time_since_hard_reset) < max_usecs) {
169 return true;
170 }
171
172 LOG(ERROR) << "Citadel didn't reboot within "
173 << max_usecs.count() << " microseconds\n";
174 ShowStats("stats before waiting", stats0);
175 ShowStats("stats after waiting", stats1);
176
177 return false;
178 }
179
WaitForSleep(nos::NuggetClientInterface * client,uint32_t * seconds_waited)180 bool WaitForSleep(nos::NuggetClientInterface *client, uint32_t *seconds_waited) {
181 struct nugget_app_low_power_stats stats0;
182 struct nugget_app_low_power_stats stats1;
183 std::vector<uint8_t> buffer;
184
185 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
186 // Grab stats before sleeping
187 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
188 buffer, &buffer) != app_status::APP_SUCCESS) {
189 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
190 return false;
191 }
192 memcpy(&stats0, buffer.data(), sizeof(stats0));
193
194 // Wait for Citadel to fall asleep
195 constexpr uint32_t wait_seconds = 4;
196 std::this_thread::sleep_for(std::chrono::seconds(wait_seconds));
197
198 // Grab stats after sleeping
199 buffer.empty();
200 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
201 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
202 buffer, &buffer) != app_status::APP_SUCCESS) {
203 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
204 return false;
205 }
206 memcpy(&stats1, buffer.data(), sizeof(stats1));
207
208 // Verify that Citadel went to sleep but didn't reboot
209 if (stats1.hard_reset_count == stats0.hard_reset_count &&
210 stats1.deep_sleep_count == stats0.deep_sleep_count + 1 &&
211 stats1.wake_count == stats0.wake_count + 1 &&
212 stats1.time_spent_in_deep_sleep > stats0.time_spent_in_deep_sleep) {
213 // Yep, looks good
214 if (seconds_waited) {
215 *seconds_waited = wait_seconds;
216 }
217 return true;
218 }
219
220 LOG(ERROR) << "Citadel didn't sleep\n";
221 ShowStats("stats before waiting", stats0);
222 ShowStats("stats after waiting", stats1);
223
224 return false;
225 }
226
WipeUserData(nos::NuggetClientInterface * client)227 bool WipeUserData(nos::NuggetClientInterface *client) {
228 struct nugget_app_low_power_stats stats0;
229 struct nugget_app_low_power_stats stats1;
230 std::vector<uint8_t> buffer;
231
232 // Grab stats before sleeping
233 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
234 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
235 buffer, &buffer) != app_status::APP_SUCCESS) {
236 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
237 return false;
238 }
239 memcpy(&stats0, buffer.data(), sizeof(stats0));
240
241 // Request wipe of user data which should hard reboot
242 buffer.resize(4);
243 *reinterpret_cast<uint32_t *>(buffer.data()) = htole32(ERASE_CONFIRMATION);
244 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_NUKE_FROM_ORBIT,
245 buffer, nullptr) != app_status::APP_SUCCESS) {
246 return false;
247 }
248
249 // Grab stats after sleeping
250 buffer.empty();
251 buffer.reserve(sizeof(struct nugget_app_low_power_stats));
252 if (client->CallApp(APP_ID_NUGGET, NUGGET_PARAM_GET_LOW_POWER_STATS,
253 buffer, &buffer) != app_status::APP_SUCCESS) {
254 LOG(ERROR) << "CallApp(..., NUGGET_PARAM_GET_LOW_POWER_STATS, ...) failed!\n";
255 return false;
256 }
257 memcpy(&stats1, buffer.data(), sizeof(stats1));
258
259 // Verify that Citadel didn't reset
260 const bool ret = stats1.hard_reset_count == stats0.hard_reset_count;
261 if (!ret) {
262 LOG(ERROR) << "Citadel reset while wiping user data\n";
263 }
264 return ret;
265 }
266
267 } // namespace nugget_tools
268