Skip to content

[BUG] bookId is a 16-bit title checksum, so different books collide and overwrite each other's notes #345

Open

Description

@TsutomuZ

Summary

bookId is a Fletcher-16 checksum of the lower-cased book title (id: hash(title) in src/scraper/scrapeBooks.ts, src/utils/hash.ts). With only 16 bits, different books end up with the same bookId once a library reaches a few hundred books.
Sync looks up the note by bookId alone (getKindleFile in src/fileManager/index.ts), so when two books collide, the metadata and highlights of one book are written into the other book's note.

Environment

  • Plugin: 2.3.1 (plus a local fix for the Amazon login)
  • Obsidian: 1.15.0
  • OS: macOS
  • Amazon region: amazon.co.jp (Japan)
  • Library size: about 730 books

What happened

  • The kindle-* frontmatter of one note (title, asin, bookId, ...) was overwritten with another book's values.
  • Highlights of the other book were added to that note (up to 108 lines).
  • 2 pairs of books out of about 730 were confirmed to share a bookId (reproduced below), and a third pair was observed during sync. (If the checksum were uniform, about 4 pairs would be expected, so this is not a rare accident.)

Colliding books

Titles are shown exactly as they appear in the Kindle notebook on amazon.co.jp. They are not translated, because bookId is computed from the exact string.

bookId Book A Book B
6714 Maps & Civilization: Cartography in Culture and Society (English Edition) 脳がわかれば心がわかるか──脳科学リテラシー養成講座
33774 知的生産の技術 (岩波新書) 難解な本を読む技術 (光文社新書)

How to reproduce the collision

Same computation as the plugin's hash(), in Node.js:

const fletcher16 = require('fletcher');
const hash = (s) => fletcher16(Buffer.from(s.toLowerCase())).toString();
hash('Maps & Civilization: Cartography in Culture and Society (English Edition)'); // '6714'
hash('脳がわかれば心がわかるか──脳科学リテラシー養成講座'); // '6714'
hash('知的生産の技術 (岩波新書)'); // '33774'
hash('難解な本を読む技術 (光文社新書)'); // '33774'

Cause

  1. The hash has only 16 bits, so collisions are expected in libraries of this size.
  2. Matching uses bookId only, so two books with the same value cannot be told apart.
  3. If a book's title changes on Amazon, its bookId changes too, and the existing note is no longer found. This may also explain duplicate notes (possibly related to [BUG] Flooded with duplicate entries #334 and [BUG] Book title differs from MyClippings.txt and cloud sync, creating duplicate notes #315; I have not confirmed that).

Suggested fix

  • Match books by ASIN, and fall back to bookId only when an ASIN is missing:
    • getKindleFile in src/fileManager/index.ts
    • isEqual in src/sync/syncManager/diffBooks.ts
    • resyncBook in src/sync/syncAmazon/index.ts
  • Stop writing kindle-bookId, and treat a note that has only kindle-asin as a synced note.
  • Add a test with two books that share a bookId but have different ASINs.

In my fork, this change synced all ~730 books without the colliding books mixing again. I can open a PR if that would be useful.

Notes

The Amazon login problem (#337, #339) is a separate issue, which PR #338 addresses.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions