1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <drm_rights_manager.h> 18 #include <drm_inner.h> 19 #include <drm_file.h> 20 #include <drm_i18n.h> 21 22 static int32_t drm_getString(uint8_t* string, int32_t len, int32_t handle) 23 { 24 int32_t i; 25 26 for (i = 0; i < len; i++) { 27 if (DRM_FILE_FAILURE == DRM_file_read(handle, &string[i], 1)) 28 return FALSE; 29 if (string[i] == '\n') { 30 string[i + 1] = '\0'; 31 break; 32 } 33 } 34 return TRUE; 35 } 36 37 static int32_t drm_putString(uint8_t* string, int32_t handle) 38 { 39 int32_t i = 0; 40 41 for (i = 0;; i++) { 42 if (string[i] == '\0') 43 break; 44 if (DRM_FILE_FAILURE == DRM_file_write(handle, &string[i], 1)) 45 return FALSE; 46 } 47 return TRUE; 48 } 49 50 static int32_t drm_writeToUidTxt(uint8_t* Uid, int32_t* id) 51 { 52 int32_t length; 53 int32_t i; 54 uint8_t idStr[8]; 55 int32_t idMax; 56 uint8_t(*uidStr)[256]; 57 uint16_t nameUcs2[MAX_FILENAME_LEN]; 58 int32_t nameLen; 59 int32_t bytesConsumed; 60 int32_t handle; 61 int32_t fileRes; 62 63 if (*id < 1) 64 return FALSE; 65 66 /* convert in ucs2 */ 67 nameLen = strlen(DRM_UID_FILE_PATH); 68 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 69 (uint8_t *)DRM_UID_FILE_PATH, 70 nameLen, 71 nameUcs2, 72 MAX_FILENAME_LEN, 73 &bytesConsumed); 74 fileRes = DRM_file_open(nameUcs2, 75 nameLen, 76 DRM_FILE_MODE_READ, 77 &handle); 78 if (DRM_FILE_SUCCESS != fileRes) { 79 DRM_file_open(nameUcs2, 80 nameLen, 81 DRM_FILE_MODE_WRITE, 82 &handle); 83 DRM_file_write(handle, (uint8_t *)"0\n", 2); 84 DRM_file_close(handle); 85 DRM_file_open(nameUcs2, 86 nameLen, 87 DRM_FILE_MODE_READ, 88 &handle); 89 } 90 91 if (!drm_getString(idStr, 8, handle)) { 92 DRM_file_close(handle); 93 return FALSE; 94 } 95 idMax = atoi((char *)idStr); 96 97 if (idMax < *id) 98 uidStr = malloc((idMax + 1) * 256); 99 else 100 uidStr = malloc(idMax * 256); 101 102 for (i = 0; i < idMax; i++) { 103 if (!drm_getString(uidStr[i], 256, handle)) { 104 DRM_file_close(handle); 105 free(uidStr); 106 return FALSE; 107 } 108 } 109 length = strlen((char *)Uid); 110 strcpy((char *)uidStr[*id - 1], (char *)Uid); 111 uidStr[*id - 1][length] = '\n'; 112 uidStr[*id - 1][length + 1] = '\0'; 113 if (idMax < (*id)) 114 idMax++; 115 DRM_file_close(handle); 116 117 DRM_file_open(nameUcs2, 118 nameLen, 119 DRM_FILE_MODE_WRITE, 120 &handle); 121 sprintf((char *)idStr, "%d", idMax); 122 123 if (!drm_putString(idStr, handle)) { 124 DRM_file_close(handle); 125 free(uidStr); 126 return FALSE; 127 } 128 if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { 129 DRM_file_close(handle); 130 free(uidStr); 131 return FALSE; 132 } 133 for (i = 0; i < idMax; i++) { 134 if (!drm_putString(uidStr[i], handle)) { 135 DRM_file_close(handle); 136 free(uidStr); 137 return FALSE; 138 } 139 } 140 if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t *)"\n", 1)) { 141 DRM_file_close(handle); 142 free(uidStr); 143 return FALSE; 144 } 145 DRM_file_close(handle); 146 free(uidStr); 147 return TRUE; 148 } 149 150 /* See objmng_files.h */ 151 int32_t drm_readFromUidTxt(uint8_t* Uid, int32_t* id, int32_t option) 152 { 153 int32_t i; 154 uint8_t p[256] = { 0 }; 155 uint8_t idStr[8]; 156 int32_t idMax = 0; 157 uint16_t nameUcs2[MAX_FILENAME_LEN]; 158 int32_t nameLen = 0; 159 int32_t bytesConsumed; 160 int32_t handle; 161 int32_t fileRes; 162 163 if (NULL == id || NULL == Uid) 164 return FALSE; 165 166 DRM_file_startup(); 167 168 /* convert in ucs2 */ 169 nameLen = strlen(DRM_UID_FILE_PATH); 170 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 171 (uint8_t *)DRM_UID_FILE_PATH, 172 nameLen, 173 nameUcs2, 174 MAX_FILENAME_LEN, 175 &bytesConsumed); 176 fileRes = DRM_file_open(nameUcs2, 177 nameLen, 178 DRM_FILE_MODE_READ, 179 &handle); 180 if (DRM_FILE_SUCCESS != fileRes) { 181 DRM_file_open(nameUcs2, 182 nameLen, 183 DRM_FILE_MODE_WRITE, 184 &handle); 185 DRM_file_write(handle, (uint8_t *)"0\n", 2); 186 DRM_file_close(handle); 187 DRM_file_open(nameUcs2, 188 nameLen, 189 DRM_FILE_MODE_READ, 190 &handle); 191 } 192 193 if (!drm_getString(idStr, 8, handle)) { 194 DRM_file_close(handle); 195 return FALSE; 196 } 197 idMax = atoi((char *)idStr); 198 199 if (option == GET_UID) { 200 if (*id < 1 || *id > idMax) { 201 DRM_file_close(handle); 202 return FALSE; 203 } 204 for (i = 1; i <= *id; i++) { 205 if (!drm_getString(Uid, 256, handle)) { 206 DRM_file_close(handle); 207 return FALSE; 208 } 209 } 210 DRM_file_close(handle); 211 return TRUE; 212 } 213 if (option == GET_ID) { 214 *id = -1; 215 for (i = 1; i <= idMax; i++) { 216 if (!drm_getString(p, 256, handle)) { 217 DRM_file_close(handle); 218 return FALSE; 219 } 220 if (strstr((char *)p, (char *)Uid) != NULL 221 && strlen((char *)p) == strlen((char *)Uid) + 1) { 222 *id = i; 223 DRM_file_close(handle); 224 return TRUE; 225 } 226 if ((*id == -1) && (strlen((char *)p) < 3)) 227 *id = i; 228 } 229 if (*id != -1) { 230 DRM_file_close(handle); 231 return FALSE; 232 } 233 *id = idMax + 1; 234 DRM_file_close(handle); 235 return FALSE; 236 } 237 DRM_file_close(handle); 238 return FALSE; 239 } 240 241 static int32_t drm_acquireId(uint8_t* uid, int32_t* id) 242 { 243 if (TRUE == drm_readFromUidTxt(uid, id, GET_ID)) 244 return TRUE; 245 246 drm_writeToUidTxt(uid, id); 247 248 return FALSE; /* The Uid is not exit, then return FALSE indicate it */ 249 } 250 251 int32_t drm_writeOrReadInfo(int32_t id, T_DRM_Rights* Ro, int32_t* RoAmount, int32_t option) 252 { 253 uint8_t fullname[MAX_FILENAME_LEN] = {0}; 254 int32_t tmpRoAmount; 255 uint16_t nameUcs2[MAX_FILENAME_LEN]; 256 int32_t nameLen = 0; 257 int32_t bytesConsumed; 258 int32_t handle; 259 int32_t fileRes; 260 261 sprintf((char *)fullname, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); 262 263 /* convert in ucs2 */ 264 nameLen = strlen((char *)fullname); 265 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 266 fullname, 267 nameLen, 268 nameUcs2, 269 MAX_FILENAME_LEN, 270 &bytesConsumed); 271 fileRes = DRM_file_open(nameUcs2, 272 nameLen, 273 DRM_FILE_MODE_READ, 274 &handle); 275 if (DRM_FILE_SUCCESS != fileRes) { 276 if (GET_ALL_RO == option || GET_A_RO == option) 277 return FALSE; 278 279 if (GET_ROAMOUNT == option) { 280 *RoAmount = -1; 281 return TRUE; 282 } 283 } 284 285 DRM_file_close(handle); 286 DRM_file_open(nameUcs2, 287 nameLen, 288 DRM_FILE_MODE_READ | DRM_FILE_MODE_WRITE, 289 &handle); 290 291 switch(option) { 292 case GET_ROAMOUNT: 293 if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { 294 DRM_file_close(handle); 295 return FALSE; 296 } 297 break; 298 case GET_ALL_RO: 299 DRM_file_setPosition(handle, sizeof(int32_t)); 300 301 if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { 302 DRM_file_close(handle); 303 return FALSE; 304 } 305 break; 306 case SAVE_ALL_RO: 307 if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t))) { 308 DRM_file_close(handle); 309 return FALSE; 310 } 311 312 if (NULL != Ro && *RoAmount >= 1) { 313 if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*) Ro, (*RoAmount) * sizeof(T_DRM_Rights))) { 314 DRM_file_close(handle); 315 return FALSE; 316 } 317 } 318 break; 319 case GET_A_RO: 320 DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); 321 322 if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { 323 DRM_file_close(handle); 324 return FALSE; 325 } 326 break; 327 case SAVE_A_RO: 328 DRM_file_setPosition(handle, sizeof(int32_t) + (*RoAmount - 1) * sizeof(T_DRM_Rights)); 329 330 if (DRM_FILE_FAILURE == DRM_file_write(handle, (uint8_t*)Ro, sizeof(T_DRM_Rights))) { 331 DRM_file_close(handle); 332 return FALSE; 333 } 334 335 DRM_file_setPosition(handle, 0); 336 if (DRM_FILE_FAILURE == DRM_file_read(handle, (uint8_t*)&tmpRoAmount, sizeof(int32_t))) { 337 DRM_file_close(handle); 338 return FALSE; 339 } 340 if (tmpRoAmount < *RoAmount) { 341 DRM_file_setPosition(handle, 0); 342 DRM_file_write(handle, (uint8_t*)RoAmount, sizeof(int32_t)); 343 } 344 break; 345 default: 346 DRM_file_close(handle); 347 return FALSE; 348 } 349 350 DRM_file_close(handle); 351 return TRUE; 352 } 353 354 int32_t drm_appendRightsInfo(T_DRM_Rights* rights) 355 { 356 int32_t id; 357 int32_t roAmount; 358 359 if (NULL == rights) 360 return FALSE; 361 362 drm_acquireId(rights->uid, &id); 363 364 if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) 365 return FALSE; 366 367 if (-1 == roAmount) 368 roAmount = 0; 369 370 /* The RO amount increase */ 371 roAmount++; 372 373 /* Save the rights information */ 374 if (FALSE == drm_writeOrReadInfo(id, rights, &roAmount, SAVE_A_RO)) 375 return FALSE; 376 377 return TRUE; 378 } 379 380 int32_t drm_getMaxIdFromUidTxt() 381 { 382 uint8_t idStr[8]; 383 int32_t idMax = 0; 384 uint16_t nameUcs2[MAX_FILENAME_LEN] = {0}; 385 int32_t nameLen = 0; 386 int32_t bytesConsumed; 387 int32_t handle; 388 int32_t fileRes; 389 390 /* convert in ucs2 */ 391 nameLen = strlen(DRM_UID_FILE_PATH); 392 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 393 (uint8_t *)DRM_UID_FILE_PATH, 394 nameLen, 395 nameUcs2, 396 MAX_FILENAME_LEN, 397 &bytesConsumed); 398 fileRes = DRM_file_open(nameUcs2, 399 nameLen, 400 DRM_FILE_MODE_READ, 401 &handle); 402 403 /* this means the uid.txt file is not exist, so there is not any DRM object */ 404 if (DRM_FILE_SUCCESS != fileRes) 405 return 0; 406 407 if (!drm_getString(idStr, 8, handle)) { 408 DRM_file_close(handle); 409 return -1; 410 } 411 DRM_file_close(handle); 412 413 idMax = atoi((char *)idStr); 414 return idMax; 415 } 416 417 int32_t drm_removeIdInfoFile(int32_t id) 418 { 419 uint8_t filename[MAX_FILENAME_LEN] = {0}; 420 uint16_t nameUcs2[MAX_FILENAME_LEN]; 421 int32_t nameLen = 0; 422 int32_t bytesConsumed; 423 424 if (id <= 0) 425 return FALSE; 426 427 sprintf((char *)filename, ANDROID_DRM_CORE_PATH"%d"EXTENSION_NAME_INFO, id); 428 429 /* convert in ucs2 */ 430 nameLen = strlen((char *)filename); 431 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 432 filename, 433 nameLen, 434 nameUcs2, 435 MAX_FILENAME_LEN, 436 &bytesConsumed); 437 if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) 438 return FALSE; 439 440 return TRUE; 441 } 442 443 int32_t drm_updateUidTxtWhenDelete(int32_t id) 444 { 445 uint16_t nameUcs2[MAX_FILENAME_LEN]; 446 int32_t nameLen = 0; 447 int32_t bytesConsumed; 448 int32_t handle; 449 int32_t fileRes; 450 int32_t bufferLen; 451 uint8_t *buffer; 452 uint8_t idStr[8]; 453 int32_t idMax; 454 455 if (id <= 0) 456 return FALSE; 457 458 nameLen = strlen(DRM_UID_FILE_PATH); 459 nameLen = DRM_i18n_mbsToWcs(DRM_CHARSET_UTF8, 460 (uint8_t *)DRM_UID_FILE_PATH, 461 nameLen, 462 nameUcs2, 463 MAX_FILENAME_LEN, 464 &bytesConsumed); 465 bufferLen = DRM_file_getFileLength(nameUcs2, nameLen); 466 if (bufferLen <= 0) 467 return FALSE; 468 469 buffer = (uint8_t *)malloc(bufferLen); 470 if (NULL == buffer) 471 return FALSE; 472 473 fileRes = DRM_file_open(nameUcs2, 474 nameLen, 475 DRM_FILE_MODE_READ, 476 &handle); 477 if (DRM_FILE_SUCCESS != fileRes) { 478 free(buffer); 479 return FALSE; 480 } 481 482 drm_getString(idStr, 8, handle); 483 idMax = atoi((char *)idStr); 484 485 bufferLen -= strlen((char *)idStr); 486 fileRes = DRM_file_read(handle, buffer, bufferLen); 487 buffer[bufferLen] = '\0'; 488 DRM_file_close(handle); 489 490 /* handle this buffer */ 491 { 492 uint8_t *pStart, *pEnd; 493 int32_t i, movLen; 494 495 pStart = buffer; 496 pEnd = pStart; 497 for (i = 0; i < id; i++) { 498 if (pEnd != pStart) 499 pStart = ++pEnd; 500 while ('\n' != *pEnd) 501 pEnd++; 502 if (pStart == pEnd) 503 pStart--; 504 } 505 movLen = bufferLen - (pEnd - buffer); 506 memmove(pStart, pEnd, movLen); 507 bufferLen -= (pEnd - pStart); 508 } 509 510 if (DRM_FILE_SUCCESS != DRM_file_delete(nameUcs2, nameLen)) { 511 free(buffer); 512 return FALSE; 513 } 514 515 fileRes = DRM_file_open(nameUcs2, 516 nameLen, 517 DRM_FILE_MODE_WRITE, 518 &handle); 519 if (DRM_FILE_SUCCESS != fileRes) { 520 free(buffer); 521 return FALSE; 522 } 523 sprintf((char *)idStr, "%d", idMax); 524 drm_putString(idStr, handle); 525 DRM_file_write(handle, (uint8_t*)"\n", 1); 526 DRM_file_write(handle, buffer, bufferLen); 527 free(buffer); 528 DRM_file_close(handle); 529 return TRUE; 530 } 531 532 int32_t drm_getKey(uint8_t* uid, uint8_t* KeyValue) 533 { 534 T_DRM_Rights ro; 535 int32_t id, roAmount; 536 537 if (NULL == uid || NULL == KeyValue) 538 return FALSE; 539 540 if (FALSE == drm_readFromUidTxt(uid, &id, GET_ID)) 541 return FALSE; 542 543 if (FALSE == drm_writeOrReadInfo(id, NULL, &roAmount, GET_ROAMOUNT)) 544 return FALSE; 545 546 if (roAmount <= 0) 547 return FALSE; 548 549 memset(&ro, 0, sizeof(T_DRM_Rights)); 550 roAmount = 1; 551 if (FALSE == drm_writeOrReadInfo(id, &ro, &roAmount, GET_A_RO)) 552 return FALSE; 553 554 memcpy(KeyValue, ro.KeyValue, DRM_KEY_LEN); 555 return TRUE; 556 } 557 558 void drm_discardPaddingByte(uint8_t *decryptedBuf, int32_t *decryptedBufLen) 559 { 560 int32_t tmpLen = *decryptedBufLen; 561 int32_t i; 562 563 if (NULL == decryptedBuf || *decryptedBufLen < 0) 564 return; 565 566 /* Check whether the last several bytes are padding or not */ 567 for (i = 1; i < decryptedBuf[tmpLen - 1]; i++) { 568 if (decryptedBuf[tmpLen - 1 - i] != decryptedBuf[tmpLen - 1]) 569 break; /* Not the padding bytes */ 570 } 571 if (i == decryptedBuf[tmpLen - 1]) /* They are padding bytes */ 572 *decryptedBufLen = tmpLen - i; 573 return; 574 } 575 576 int32_t drm_aesDecBuffer(uint8_t * Buffer, int32_t * BufferLen, AES_KEY *key) 577 { 578 uint8_t dbuf[3 * DRM_ONE_AES_BLOCK_LEN], buf[DRM_ONE_AES_BLOCK_LEN]; 579 uint64_t i, len, wlen = DRM_ONE_AES_BLOCK_LEN, curLen, restLen; 580 uint8_t *pTarget, *pTargetHead; 581 582 pTargetHead = Buffer; 583 pTarget = Buffer; 584 curLen = 0; 585 restLen = *BufferLen; 586 587 if (restLen > 2 * DRM_ONE_AES_BLOCK_LEN) { 588 len = 2 * DRM_ONE_AES_BLOCK_LEN; 589 } else { 590 len = restLen; 591 } 592 memcpy(dbuf, Buffer, (size_t)len); 593 restLen -= len; 594 Buffer += len; 595 596 if (len < 2 * DRM_ONE_AES_BLOCK_LEN) { /* The original file is less than one block in length */ 597 len -= DRM_ONE_AES_BLOCK_LEN; 598 /* Decrypt from position len to position len + DRM_ONE_AES_BLOCK_LEN */ 599 AES_decrypt((dbuf + len), (dbuf + len), key); 600 601 /* Undo the CBC chaining */ 602 for (i = 0; i < len; ++i) 603 dbuf[i] ^= dbuf[i + DRM_ONE_AES_BLOCK_LEN]; 604 605 /* Output the decrypted bytes */ 606 memcpy(pTarget, dbuf, (size_t)len); 607 pTarget += len; 608 } else { 609 uint8_t *b1 = dbuf, *b2 = b1 + DRM_ONE_AES_BLOCK_LEN, *b3 = b2 + DRM_ONE_AES_BLOCK_LEN, *bt; 610 611 for (;;) { /* While some ciphertext remains, prepare to decrypt block b2 */ 612 /* Read in the next block to see if ciphertext stealing is needed */ 613 b3 = Buffer; 614 if (restLen > DRM_ONE_AES_BLOCK_LEN) { 615 len = DRM_ONE_AES_BLOCK_LEN; 616 } else { 617 len = restLen; 618 } 619 restLen -= len; 620 Buffer += len; 621 622 /* Decrypt the b2 block */ 623 AES_decrypt((uint8_t *)b2, buf, key); 624 625 if (len == 0 || len == DRM_ONE_AES_BLOCK_LEN) { /* No ciphertext stealing */ 626 /* Unchain CBC using the previous ciphertext block in b1 */ 627 for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) 628 buf[i] ^= b1[i]; 629 } else { /* Partial last block - use ciphertext stealing */ 630 wlen = len; 631 /* Produce last 'len' bytes of plaintext by xoring with */ 632 /* The lowest 'len' bytes of next block b3 - C[N-1] */ 633 for (i = 0; i < len; ++i) 634 buf[i] ^= b3[i]; 635 636 /* Reconstruct the C[N-1] block in b3 by adding in the */ 637 /* Last (DRM_ONE_AES_BLOCK_LEN - len) bytes of C[N-2] in b2 */ 638 for (i = len; i < DRM_ONE_AES_BLOCK_LEN; ++i) 639 b3[i] = buf[i]; 640 641 /* Decrypt the C[N-1] block in b3 */ 642 AES_decrypt((uint8_t *)b3, (uint8_t *)b3, key); 643 644 /* Produce the last but one plaintext block by xoring with */ 645 /* The last but two ciphertext block */ 646 for (i = 0; i < DRM_ONE_AES_BLOCK_LEN; ++i) 647 b3[i] ^= b1[i]; 648 649 /* Write decrypted plaintext blocks */ 650 memcpy(pTarget, b3, DRM_ONE_AES_BLOCK_LEN); 651 pTarget += DRM_ONE_AES_BLOCK_LEN; 652 } 653 654 /* Write the decrypted plaintext block */ 655 memcpy(pTarget, buf, (size_t)wlen); 656 pTarget += wlen; 657 658 if (len != DRM_ONE_AES_BLOCK_LEN) { 659 *BufferLen = pTarget - pTargetHead; 660 return 0; 661 } 662 663 /* Advance the buffer pointers */ 664 bt = b1, b1 = b2, b2 = b3, b3 = bt; 665 } 666 } 667 return 0; 668 } 669 670 int32_t drm_updateDcfDataLen(uint8_t* pDcfLastData, uint8_t* keyValue, int32_t* moreBytes) 671 { 672 AES_KEY key; 673 int32_t len = DRM_TWO_AES_BLOCK_LEN; 674 675 if (NULL == pDcfLastData || NULL == keyValue) 676 return FALSE; 677 678 AES_set_decrypt_key(keyValue, DRM_KEY_LEN * 8, &key); 679 680 if (drm_aesDecBuffer(pDcfLastData, &len, &key) < 0) 681 return FALSE; 682 683 drm_discardPaddingByte(pDcfLastData, &len); 684 685 *moreBytes = DRM_TWO_AES_BLOCK_LEN - len; 686 687 return TRUE; 688 } 689