KissModem.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. #include "KissModem.h"
  2. #include <CayenneLPP.h>
  3. KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng,
  4. mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors)
  5. : _serial(serial), _identity(identity), _rng(rng), _radio(radio), _board(board), _sensors(sensors) {
  6. _rx_len = 0;
  7. _rx_escaped = false;
  8. _rx_active = false;
  9. _has_pending_tx = false;
  10. _pending_tx_len = 0;
  11. _txdelay = KISS_DEFAULT_TXDELAY;
  12. _persistence = KISS_DEFAULT_PERSISTENCE;
  13. _slottime = KISS_DEFAULT_SLOTTIME;
  14. _txtail = 0;
  15. _fullduplex = 0;
  16. _tx_state = TX_IDLE;
  17. _tx_timer = 0;
  18. _setRadioCallback = nullptr;
  19. _setTxPowerCallback = nullptr;
  20. _getCurrentRssiCallback = nullptr;
  21. _getStatsCallback = nullptr;
  22. _config = {0, 0, 0, 0, 0};
  23. _signal_report_enabled = true;
  24. }
  25. void KissModem::begin() {
  26. _rx_len = 0;
  27. _rx_escaped = false;
  28. _rx_active = false;
  29. _has_pending_tx = false;
  30. _tx_state = TX_IDLE;
  31. }
  32. void KissModem::writeByte(uint8_t b) {
  33. if (b == KISS_FEND) {
  34. _serial.write(KISS_FESC);
  35. _serial.write(KISS_TFEND);
  36. } else if (b == KISS_FESC) {
  37. _serial.write(KISS_FESC);
  38. _serial.write(KISS_TFESC);
  39. } else {
  40. _serial.write(b);
  41. }
  42. }
  43. void KissModem::writeFrame(uint8_t type, const uint8_t* data, uint16_t len) {
  44. _serial.write(KISS_FEND);
  45. writeByte(type);
  46. for (uint16_t i = 0; i < len; i++) {
  47. writeByte(data[i]);
  48. }
  49. _serial.write(KISS_FEND);
  50. }
  51. void KissModem::writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len) {
  52. _serial.write(KISS_FEND);
  53. writeByte(KISS_CMD_SETHARDWARE);
  54. writeByte(sub_cmd);
  55. for (uint16_t i = 0; i < len; i++) {
  56. writeByte(data[i]);
  57. }
  58. _serial.write(KISS_FEND);
  59. }
  60. void KissModem::writeHardwareError(uint8_t error_code) {
  61. writeHardwareFrame(HW_RESP_ERROR, &error_code, 1);
  62. }
  63. void KissModem::loop() {
  64. while (_serial.available()) {
  65. uint8_t b = _serial.read();
  66. if (b == KISS_FEND) {
  67. if (_rx_active && _rx_len > 0) {
  68. processFrame();
  69. }
  70. _rx_len = 0;
  71. _rx_escaped = false;
  72. _rx_active = true;
  73. continue;
  74. }
  75. if (!_rx_active) continue;
  76. if (b == KISS_FESC) {
  77. _rx_escaped = true;
  78. continue;
  79. }
  80. if (_rx_escaped) {
  81. _rx_escaped = false;
  82. if (b == KISS_TFEND) b = KISS_FEND;
  83. else if (b == KISS_TFESC) b = KISS_FESC;
  84. else continue;
  85. }
  86. if (_rx_len < KISS_MAX_FRAME_SIZE) {
  87. _rx_buf[_rx_len++] = b;
  88. } else {
  89. /* Buffer full with no FEND; reset so we don't stay stuck ignoring input. */
  90. _rx_len = 0;
  91. _rx_escaped = false;
  92. _rx_active = false;
  93. }
  94. }
  95. processTx();
  96. }
  97. void KissModem::processFrame() {
  98. if (_rx_len < 1) return;
  99. uint8_t type_byte = _rx_buf[0];
  100. if (type_byte == KISS_CMD_RETURN) return;
  101. uint8_t port = (type_byte >> 4) & 0x0F;
  102. uint8_t cmd = type_byte & 0x0F;
  103. if (port != 0) return;
  104. const uint8_t* data = &_rx_buf[1];
  105. uint16_t data_len = _rx_len - 1;
  106. switch (cmd) {
  107. case KISS_CMD_DATA:
  108. if (data_len > 0 && data_len <= KISS_MAX_PACKET_SIZE && !_has_pending_tx) {
  109. memcpy(_pending_tx, data, data_len);
  110. _pending_tx_len = data_len;
  111. _has_pending_tx = true;
  112. } else if (_has_pending_tx) {
  113. uint8_t result = 0x00;
  114. writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
  115. }
  116. break;
  117. case KISS_CMD_TXDELAY:
  118. if (data_len >= 1) _txdelay = data[0];
  119. break;
  120. case KISS_CMD_PERSISTENCE:
  121. if (data_len >= 1) _persistence = data[0];
  122. break;
  123. case KISS_CMD_SLOTTIME:
  124. if (data_len >= 1) _slottime = data[0];
  125. break;
  126. case KISS_CMD_TXTAIL:
  127. if (data_len >= 1) _txtail = data[0];
  128. break;
  129. case KISS_CMD_FULLDUPLEX:
  130. if (data_len >= 1) _fullduplex = data[0];
  131. break;
  132. case KISS_CMD_SETHARDWARE:
  133. if (data_len >= 1) {
  134. handleHardwareCommand(data[0], data + 1, data_len - 1);
  135. }
  136. break;
  137. default:
  138. break;
  139. }
  140. }
  141. void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len) {
  142. switch (sub_cmd) {
  143. case HW_CMD_GET_IDENTITY:
  144. handleGetIdentity();
  145. break;
  146. case HW_CMD_GET_RANDOM:
  147. handleGetRandom(data, len);
  148. break;
  149. case HW_CMD_VERIFY_SIGNATURE:
  150. handleVerifySignature(data, len);
  151. break;
  152. case HW_CMD_SIGN_DATA:
  153. handleSignData(data, len);
  154. break;
  155. case HW_CMD_ENCRYPT_DATA:
  156. handleEncryptData(data, len);
  157. break;
  158. case HW_CMD_DECRYPT_DATA:
  159. handleDecryptData(data, len);
  160. break;
  161. case HW_CMD_KEY_EXCHANGE:
  162. handleKeyExchange(data, len);
  163. break;
  164. case HW_CMD_HASH:
  165. handleHash(data, len);
  166. break;
  167. case HW_CMD_SET_RADIO:
  168. handleSetRadio(data, len);
  169. break;
  170. case HW_CMD_SET_TX_POWER:
  171. handleSetTxPower(data, len);
  172. break;
  173. case HW_CMD_GET_RADIO:
  174. handleGetRadio();
  175. break;
  176. case HW_CMD_GET_TX_POWER:
  177. handleGetTxPower();
  178. break;
  179. case HW_CMD_GET_VERSION:
  180. handleGetVersion();
  181. break;
  182. case HW_CMD_GET_CURRENT_RSSI:
  183. handleGetCurrentRssi();
  184. break;
  185. case HW_CMD_IS_CHANNEL_BUSY:
  186. handleIsChannelBusy();
  187. break;
  188. case HW_CMD_GET_AIRTIME:
  189. handleGetAirtime(data, len);
  190. break;
  191. case HW_CMD_GET_NOISE_FLOOR:
  192. handleGetNoiseFloor();
  193. break;
  194. case HW_CMD_GET_STATS:
  195. handleGetStats();
  196. break;
  197. case HW_CMD_GET_BATTERY:
  198. handleGetBattery();
  199. break;
  200. case HW_CMD_PING:
  201. handlePing();
  202. break;
  203. case HW_CMD_GET_SENSORS:
  204. handleGetSensors(data, len);
  205. break;
  206. case HW_CMD_GET_MCU_TEMP:
  207. handleGetMCUTemp();
  208. break;
  209. case HW_CMD_REBOOT:
  210. handleReboot();
  211. break;
  212. case HW_CMD_GET_DEVICE_NAME:
  213. handleGetDeviceName();
  214. break;
  215. case HW_CMD_SET_SIGNAL_REPORT:
  216. handleSetSignalReport(data, len);
  217. break;
  218. case HW_CMD_GET_SIGNAL_REPORT:
  219. handleGetSignalReport();
  220. break;
  221. default:
  222. writeHardwareError(HW_ERR_UNKNOWN_CMD);
  223. break;
  224. }
  225. }
  226. void KissModem::processTx() {
  227. switch (_tx_state) {
  228. case TX_IDLE:
  229. if (_has_pending_tx) {
  230. if (_fullduplex) {
  231. _tx_timer = millis();
  232. _tx_state = TX_DELAY;
  233. } else {
  234. _tx_timer = millis();
  235. _tx_state = TX_WAIT_CLEAR;
  236. }
  237. }
  238. break;
  239. case TX_WAIT_CLEAR:
  240. if (!_radio.isReceiving()) {
  241. uint8_t rand_val;
  242. _rng.random(&rand_val, 1);
  243. if (rand_val <= _persistence) {
  244. _tx_timer = millis();
  245. _tx_state = TX_DELAY;
  246. } else {
  247. _tx_timer = millis();
  248. _tx_state = TX_SLOT_WAIT;
  249. }
  250. } else if (millis() - _tx_timer >= _radio.getEstAirtimeFor(KISS_MAX_PACKET_SIZE) * KISS_TX_TIMEOUT_FACTOR) {
  251. _tx_timer = millis();
  252. _tx_state = TX_DELAY;
  253. }
  254. break;
  255. case TX_SLOT_WAIT:
  256. if (millis() - _tx_timer >= (uint32_t)_slottime * 10) {
  257. _tx_timer = millis();
  258. _tx_state = TX_WAIT_CLEAR;
  259. }
  260. break;
  261. case TX_DELAY:
  262. if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) {
  263. if (_radio.startSendRaw(_pending_tx, _pending_tx_len)) {
  264. _tx_timer = millis();
  265. _tx_state = TX_SENDING;
  266. } else {
  267. _has_pending_tx = false;
  268. _tx_state = TX_IDLE;
  269. }
  270. }
  271. break;
  272. case TX_SENDING:
  273. if (_radio.isSendComplete()) {
  274. _radio.onSendFinished();
  275. uint8_t result = 0x01;
  276. writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
  277. _has_pending_tx = false;
  278. _tx_state = TX_IDLE;
  279. } else if (millis() - _tx_timer >= _radio.getEstAirtimeFor(_pending_tx_len) * KISS_TX_TIMEOUT_FACTOR) {
  280. _radio.onSendFinished();
  281. uint8_t result = 0x00;
  282. writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
  283. _has_pending_tx = false;
  284. _tx_state = TX_IDLE;
  285. }
  286. break;
  287. }
  288. }
  289. void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) {
  290. writeFrame(KISS_CMD_DATA, packet, len);
  291. if (_signal_report_enabled) {
  292. uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi };
  293. writeHardwareFrame(HW_RESP_RX_META, meta, 2);
  294. }
  295. }
  296. void KissModem::handleGetIdentity() {
  297. writeHardwareFrame(HW_RESP(HW_CMD_GET_IDENTITY), _identity.pub_key, PUB_KEY_SIZE);
  298. }
  299. void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) {
  300. if (len < 1) {
  301. writeHardwareError(HW_ERR_INVALID_LENGTH);
  302. return;
  303. }
  304. uint8_t requested = data[0];
  305. if (requested < 1 || requested > 64) {
  306. writeHardwareError(HW_ERR_INVALID_PARAM);
  307. return;
  308. }
  309. uint8_t buf[64];
  310. _rng.random(buf, requested);
  311. writeHardwareFrame(HW_RESP(HW_CMD_GET_RANDOM), buf, requested);
  312. }
  313. void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) {
  314. if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) {
  315. writeHardwareError(HW_ERR_INVALID_LENGTH);
  316. return;
  317. }
  318. mesh::Identity signer(data);
  319. const uint8_t* signature = data + PUB_KEY_SIZE;
  320. const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE;
  321. uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE;
  322. uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00;
  323. writeHardwareFrame(HW_RESP(HW_CMD_VERIFY_SIGNATURE), &result, 1);
  324. }
  325. void KissModem::handleSignData(const uint8_t* data, uint16_t len) {
  326. if (len < 1) {
  327. writeHardwareError(HW_ERR_INVALID_LENGTH);
  328. return;
  329. }
  330. uint8_t signature[SIGNATURE_SIZE];
  331. _identity.sign(signature, data, len);
  332. writeHardwareFrame(HW_RESP(HW_CMD_SIGN_DATA), signature, SIGNATURE_SIZE);
  333. }
  334. void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) {
  335. if (len < PUB_KEY_SIZE + 1) {
  336. writeHardwareError(HW_ERR_INVALID_LENGTH);
  337. return;
  338. }
  339. const uint8_t* key = data;
  340. const uint8_t* plaintext = data + PUB_KEY_SIZE;
  341. uint16_t plaintext_len = len - PUB_KEY_SIZE;
  342. uint8_t buf[KISS_MAX_FRAME_SIZE];
  343. int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len);
  344. if (encrypted_len > 0) {
  345. writeHardwareFrame(HW_RESP(HW_CMD_ENCRYPT_DATA), buf, encrypted_len);
  346. } else {
  347. writeHardwareError(HW_ERR_ENCRYPT_FAILED);
  348. }
  349. }
  350. void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) {
  351. if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) {
  352. writeHardwareError(HW_ERR_INVALID_LENGTH);
  353. return;
  354. }
  355. const uint8_t* key = data;
  356. const uint8_t* ciphertext = data + PUB_KEY_SIZE;
  357. uint16_t ciphertext_len = len - PUB_KEY_SIZE;
  358. uint8_t buf[KISS_MAX_FRAME_SIZE];
  359. int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len);
  360. if (decrypted_len > 0) {
  361. writeHardwareFrame(HW_RESP(HW_CMD_DECRYPT_DATA), buf, decrypted_len);
  362. } else {
  363. writeHardwareError(HW_ERR_MAC_FAILED);
  364. }
  365. }
  366. void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) {
  367. if (len < PUB_KEY_SIZE) {
  368. writeHardwareError(HW_ERR_INVALID_LENGTH);
  369. return;
  370. }
  371. uint8_t shared_secret[PUB_KEY_SIZE];
  372. _identity.calcSharedSecret(shared_secret, data);
  373. writeHardwareFrame(HW_RESP(HW_CMD_KEY_EXCHANGE), shared_secret, PUB_KEY_SIZE);
  374. }
  375. void KissModem::handleHash(const uint8_t* data, uint16_t len) {
  376. if (len < 1) {
  377. writeHardwareError(HW_ERR_INVALID_LENGTH);
  378. return;
  379. }
  380. uint8_t hash[32];
  381. mesh::Utils::sha256(hash, 32, data, len);
  382. writeHardwareFrame(HW_RESP(HW_CMD_HASH), hash, 32);
  383. }
  384. void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) {
  385. if (len < 10) {
  386. writeHardwareError(HW_ERR_INVALID_LENGTH);
  387. return;
  388. }
  389. if (!_setRadioCallback) {
  390. writeHardwareError(HW_ERR_NO_CALLBACK);
  391. return;
  392. }
  393. memcpy(&_config.freq_hz, data, 4);
  394. memcpy(&_config.bw_hz, data + 4, 4);
  395. _config.sf = data[8];
  396. _config.cr = data[9];
  397. _setRadioCallback(_config.freq_hz / 1000000.0f, _config.bw_hz / 1000.0f, _config.sf, _config.cr);
  398. writeHardwareFrame(HW_RESP_OK, nullptr, 0);
  399. }
  400. void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) {
  401. if (len < 1) {
  402. writeHardwareError(HW_ERR_INVALID_LENGTH);
  403. return;
  404. }
  405. if (!_setTxPowerCallback) {
  406. writeHardwareError(HW_ERR_NO_CALLBACK);
  407. return;
  408. }
  409. _config.tx_power = data[0];
  410. _setTxPowerCallback(data[0]);
  411. writeHardwareFrame(HW_RESP_OK, nullptr, 0);
  412. }
  413. void KissModem::handleGetRadio() {
  414. uint8_t buf[10];
  415. memcpy(buf, &_config.freq_hz, 4);
  416. memcpy(buf + 4, &_config.bw_hz, 4);
  417. buf[8] = _config.sf;
  418. buf[9] = _config.cr;
  419. writeHardwareFrame(HW_RESP(HW_CMD_GET_RADIO), buf, 10);
  420. }
  421. void KissModem::handleGetTxPower() {
  422. writeHardwareFrame(HW_RESP(HW_CMD_GET_TX_POWER), &_config.tx_power, 1);
  423. }
  424. void KissModem::handleGetVersion() {
  425. uint8_t buf[2];
  426. buf[0] = KISS_FIRMWARE_VERSION;
  427. buf[1] = 0;
  428. writeHardwareFrame(HW_RESP(HW_CMD_GET_VERSION), buf, 2);
  429. }
  430. void KissModem::handleGetCurrentRssi() {
  431. if (!_getCurrentRssiCallback) {
  432. writeHardwareError(HW_ERR_NO_CALLBACK);
  433. return;
  434. }
  435. float rssi = _getCurrentRssiCallback();
  436. int8_t rssi_byte = (int8_t)rssi;
  437. writeHardwareFrame(HW_RESP(HW_CMD_GET_CURRENT_RSSI), (uint8_t*)&rssi_byte, 1);
  438. }
  439. void KissModem::handleIsChannelBusy() {
  440. uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00;
  441. writeHardwareFrame(HW_RESP(HW_CMD_IS_CHANNEL_BUSY), &busy, 1);
  442. }
  443. void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) {
  444. if (len < 1) {
  445. writeHardwareError(HW_ERR_INVALID_LENGTH);
  446. return;
  447. }
  448. uint8_t packet_len = data[0];
  449. uint32_t airtime = _radio.getEstAirtimeFor(packet_len);
  450. writeHardwareFrame(HW_RESP(HW_CMD_GET_AIRTIME), (uint8_t*)&airtime, 4);
  451. }
  452. void KissModem::handleGetNoiseFloor() {
  453. int16_t noise_floor = _radio.getNoiseFloor();
  454. writeHardwareFrame(HW_RESP(HW_CMD_GET_NOISE_FLOOR), (uint8_t*)&noise_floor, 2);
  455. }
  456. void KissModem::handleGetStats() {
  457. if (!_getStatsCallback) {
  458. writeHardwareError(HW_ERR_NO_CALLBACK);
  459. return;
  460. }
  461. uint32_t rx, tx, errors;
  462. _getStatsCallback(&rx, &tx, &errors);
  463. uint8_t buf[12];
  464. memcpy(buf, &rx, 4);
  465. memcpy(buf + 4, &tx, 4);
  466. memcpy(buf + 8, &errors, 4);
  467. writeHardwareFrame(HW_RESP(HW_CMD_GET_STATS), buf, 12);
  468. }
  469. void KissModem::handleGetBattery() {
  470. uint16_t mv = _board.getBattMilliVolts();
  471. writeHardwareFrame(HW_RESP(HW_CMD_GET_BATTERY), (uint8_t*)&mv, 2);
  472. }
  473. void KissModem::handlePing() {
  474. writeHardwareFrame(HW_RESP(HW_CMD_PING), nullptr, 0);
  475. }
  476. void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) {
  477. if (len < 1) {
  478. writeHardwareError(HW_ERR_INVALID_LENGTH);
  479. return;
  480. }
  481. uint8_t permissions = data[0];
  482. CayenneLPP telemetry(255);
  483. if (_sensors.querySensors(permissions, telemetry)) {
  484. writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), telemetry.getBuffer(), telemetry.getSize());
  485. } else {
  486. writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), nullptr, 0);
  487. }
  488. }
  489. void KissModem::handleGetMCUTemp() {
  490. float temp = _board.getMCUTemperature();
  491. if (isnan(temp)) {
  492. writeHardwareError(HW_ERR_NO_CALLBACK);
  493. return;
  494. }
  495. int16_t temp_tenths = (int16_t)(temp * 10.0f);
  496. writeHardwareFrame(HW_RESP(HW_CMD_GET_MCU_TEMP), (uint8_t*)&temp_tenths, 2);
  497. }
  498. void KissModem::handleReboot() {
  499. writeHardwareFrame(HW_RESP_OK, nullptr, 0);
  500. _serial.flush();
  501. delay(50);
  502. _board.reboot();
  503. }
  504. void KissModem::handleGetDeviceName() {
  505. const char* name = _board.getManufacturerName();
  506. writeHardwareFrame(HW_RESP(HW_CMD_GET_DEVICE_NAME), (const uint8_t*)name, strlen(name));
  507. }
  508. void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) {
  509. if (len < 1) {
  510. writeHardwareError(HW_ERR_INVALID_LENGTH);
  511. return;
  512. }
  513. _signal_report_enabled = (data[0] != 0x00);
  514. uint8_t val = _signal_report_enabled ? 0x01 : 0x00;
  515. writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1);
  516. }
  517. void KissModem::handleGetSignalReport() {
  518. uint8_t val = _signal_report_enabled ? 0x01 : 0x00;
  519. writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1);
  520. }