{"id":16116719,"url":"https://github.com/vitiral/trfl","last_synced_at":"2026-02-24T01:34:37.510Z","repository":{"id":66158015,"uuid":"498490663","full_name":"vitiral/trfl","owner":"vitiral","description":"filesystem for fngi kernel","archived":false,"fork":false,"pushed_at":"2022-06-03T17:35:56.000Z","size":26,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-29T03:35:22.449Z","etag":null,"topics":["civboot","filesystem"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vitiral.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-31T20:38:51.000Z","updated_at":"2024-04-23T03:44:19.000Z","dependencies_parsed_at":"2023-03-10T07:00:51.114Z","dependency_job_id":null,"html_url":"https://github.com/vitiral/trfl","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/vitiral/trfl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitiral%2Ftrfl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitiral%2Ftrfl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitiral%2Ftrfl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitiral%2Ftrfl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vitiral","download_url":"https://codeload.github.com/vitiral/trfl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vitiral%2Ftrfl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29766742,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T01:28:30.166Z","status":"ssl_error","status_checked_at":"2026-02-24T01:28:27.518Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["civboot","filesystem"],"created_at":"2024-10-09T20:25:28.408Z","updated_at":"2026-02-24T01:34:37.495Z","avatar_url":"https://github.com/vitiral.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# trfl FS: a filesystem for fngi\n\nThis is an in-development (**WARNING: currently conceptual**) filesystem for the\n[fngi] kernel, intended to be used on a microcontroller or simple operating\nsystem for [Civboot], but robust enough as a general purpose file system.\n\nTrfl stores a Binary Search Tree of directories, files and a rope structure of\ntheir data with the ability to efficiently create, delete, write, append, read\nand modify in `O(log N)` time.\n\nThe filesystem is designed to wear-level for solid state drives (SD Cards, SSD\ndrives, etc) but can also be used for [Other Media](#other-media) with only\nminor changes.\n\n\u003e **Note:** The name is pronounced \"truffle\". The truffle is the reproductive\n\u003e structure of a mycorrhizal fungi which lives in a symbiotic relationship with\n\u003e the root structure of plants. The name was chosen since filesystems are trees\n\u003e which contain a root node, and this filesystem is intended to be used by the\n\u003e [fngi] kernel.\n\n## Basics\n\n**Stages of the Write Head (WH) and the Garbage Collector (GC) Head**\n```\n\n   Filesystem start, disk entirly free (` `)\n\n     WH\n     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n     |---------------------------------------------------------------|\n     |                                                               |\n     |                                                               |\n     |                                                               |\n     |                                                               |\n     |                                                               |\n     |---------------------------------------------------------------|\n\n   Filesystem starts being written, some data is used (`U`), some deleted (`.`)\n   Write Head (WH) writes data from `left-\u003eright`, as received by the operating\n   system (user).\n\n             WH\n     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n     |---------------------------------------------------------------|\n     |UUU.UUUU                                                       |\n     |U.UUUUUU                                                       |\n     |UUU.UUU                                                        |\n     |UUUUUU.                                                        |\n     |UUUUU.U                                                        |\n     |---------------------------------------------------------------|\n\n   Write Head (WH) nears end of SD Card and Garbage Collector (GC) Head starts.\n   Note that much of initial data is now deleted (`.`) due to normal filesystem\n   operation (moving files, changing contents, etc).\n\n   GC sends still used (U) data to the WH, which writes it alongside the new\n   data from the OS (user). Whenever the GC moves data, it updates the parent\n   references. Once all used data has been moved and references updated, the\n   block can be freed (` `).\n\n     GC -----------------------------------------------\u003e WH\n     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n     |---------------------------------------------------------------|\n     |U...U..UU...UUUU...UUUU...UUUU..UU..UUUU..UUUU..UUU            |\n     |U.U......UUU...UUUU...UUUUUUUUUU..UUUU..U..UUUUUU..            |\n     |..U.U...UU.U...UU...UUUUU...UU..UUUUU..UU..UUU..U.U            |\n     |U.U.....U.UUUU...UUUUUUUUUUU...UUUUUU..UUU..UUU.U.U            |\n     |U...U.UUUU...UUUU...UUUUUUUUUU...UUUUUU..UUUUUU.UU             |\n     |---------------------------------------------------------------|\n\n   GC has erased data and WH has wrapped around. The cycle count increases. GC\n   continues to send used data to the WH and erase blocks. There is no memory\n   fragmentation because any still used data is continuously re-packed by the\n   write head on each cycle.\n\n         WH \u003c------- GC\n     |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |   |\n     |---------------------------------------------------------------|\n     |UUUU           U...U..U...UUUU..UU..UUUU..UUUU...UUU..UUU.U..UU|\n     |U.U            U..U...U..U..UUUU..UUUU..U..U..U...UUU...UU.U.UU|\n     |UUU            .U...U.U.....UU..UUU.U..UU...UU....U..UUU.UUU.UU|\n     |UUU            ..UU....UUU.....UU..U...UUU..U.U.U.U..UUU..U..UU|\n     |U.u            U....UU..UUUUUU...UU.U.U.........UU.U.UU...UU.UU|\n     |---------------------------------------------------------------|\n\n   The system can continue cycling in this way until the drive is too full for\n   the GC to operate efficiently (unknown limit, probably 80-90% full?)\n```\n\nThe system is inherently \"wear leveling\" since it only erases sectors once per\ncycle. The design of the system follows the block erasure principles required by\nFlash Memory, read about them\n[on Wikipedia](https://en.wikipedia.org/wiki/Flash_memory#Block_erasure). In\nsummary:\n\n- You can set a single bit of data at any time\n- You can only erase data in the size of an [Erasure Block] (between 16KiB and\n  512KiB). Erasing causes wear on the device and most solid state devices can\n  only handle a certain number of erasures (ten thousand to a few million\n  typically). This is why wear-leveling (erasing blocks evenly) is so important.\n\n\u003e **Note**: the actual value of bits on erasure is determined by the vendor.\n\u003e However, we use \"erased\" to mean `0`, and can therefore set any bit at any\n\u003e time. The physical layer implementation will invert the data on transport\n\u003e as needed by the specific device.  See 4.3.5.1 of [Physical Layer Spec]\n\u003e for more details.\n\nTo take this into account, the following principles are followed:\n- Every data structure starts with some flags/bits \"reserved\" and set to `0`\n  (\"erased\"). They may later be set to 1 by the Garbage Collector (GC),\n  indicating that data has moved/etc. More on the GC later.\n  - Note: the limitation of flash memory allows any single _bit_ to be set\n    to `1` at any time. Erasing to `0` requires a \"block erasure\" (these values\n    may be reversed, but we use this mnemonic for simplicity).\n- When data is \"deleted\" it simply has a single bit (flag) set. It will\n  later be cleaned by the GC\n- Data \"Write Head\": data is written sequentially from low-memory to high\n  memory, then cycles back to low memory. Every 64KiB sector has metadata\n  indicating which 64 byte \"slots\" have been used.\n- At least 2+ \"erasure blocks\" in front of the Write Head is the Garbage\n  Collector (GC). It finds non-deleted data and tells the write-head to write\n  it, moving the data. Once all non-deleted data has been moved, it performs a\n  block erasure and updates by setting the relevant reserved bits (i.e.\n  reference pointers) of the parents of any moved data.\n  - For example: if you have a binary search tree, and your left node has moved.\n    You will have a flag indicating this fact, and you will have a \"new\"\n    reference set aside for the new data.\n\n## Core Data Structures\n\nThere are a few core data structures used throughout the design. Note that all\nof these represent tightly packed data (with no alignment requirements) with\nbig-endian (aka network-endian, highest byte first) byte ordering.\n\n### References\nFirst we have the `Ref` and `GCRef`, which are methods to refer to other areas\nof the filesystem at a certain byte address. `GCRef` is simply two `Ref`s, which\nallows the GC to \"move\" data (Recall that data can only be set once, so in order\nto move data you have to have the space set aside and a flag to indicate\nmovement).\n\n```\n\\ A reference to another area in the SDCard. The number of bytes is always 5,\n\\ which is enough for 1 TiB of data.\ntypedef Ref Arr[U1; 5]\n\n\\ A Ref that can be updated by the GC\n\\ The flags contain the following bits:\n\\   del: if set, this ref is no longer active. Used for versioned data.\n\\   firstInit: if set, first data has been successfully initialized\n\\   secondInit: if set, second data has been successfully initialized\nstruct GCRef[A] {\n  flags: U1, \\ del | firstInit | secondInit | _unused_ ...\n  first: Ref[A],\n  second: Ref[A],\n}\n```\n\nGCRef allows for changes to a reference by the GC, which is necessary because\nthe GC will move all data exactly once before collecting it. However, we need a\nway to specify arbitrary changes to data (i.e. change to file contents), which\nis what `VersionedRef` is used for.\n\n### Versioned Data\n\nThe VersionedRef is simply an array of the past and (possibly future and\ncurrent) version of some data. If `next` is set, then look there for the current\nversion.  Otherwise, the last initialized version in `versions` the current one.\n\nWhy do we do this? Recall, we can only set bits, never clear them... unless we\nclear a whole sector, which is what the GC does. We have to reserve space for\nfuture changes ahead of time.\n\nWhen `versions` are exhausted, a new set of versions with twice the length are\nreserved at `next`. Therefore the time to find a version is the same as\nperforming a binary search: `O(log v)` where v is the number of versions.\n\n```\nstruct VersionedRef[A] {\n  next: GCRef[VersionedRef], \\ if set, then use instead of these versions\n  len: U2, \\ the number of possible versions\n  versions: Arr[GCRef[A], len], \\ references to relevant data/nodes\n}\n\n\\ A compressed version of VersionedRef with known size, used in other structs.\nstruct VersionedRefStart[A] {\n  start: GCRef[A]\n  next: GCRef[VersionedRef[A]],  \\ if set, then use instead of start\n}\n```\n\n### Ropes for Byte Data\nFinally, we have data storage in the RawData and Rope structs. These are\nreferenced by `VersionedRef`'s above, which are themselves used in the `File`\nobject seen later.\n\n\u003e **Why Ropes?**: Ropes enable both fast appends _and_ fast and efficient\n\u003e mutation of data. Along with binary search trees they are pretty much the\n\u003e rockstars of filesystems.\n\n```\nstruct RawData {\n  flag: U1, \\ del | initialized | type=RawData\n  parent: GCRef,\n  crc32: U4, \\ CRC32 checksum on len + data\n  len: U2,\n  data: Arr[U1, len],\n}\n\n\\ Versioned rope length\nstruct RopeLen {\n  flag: U1, \\ del | initialized | type=RopeLen\n  parent: GCRef[RopeLen],\n  next: GCRef[RopeLen], \\ if set, use instead of this\n  len: U2, \\ number of versions\n  lengthVersions: Arr[U2, len], \\ length values at versions\n}\n\nstruct Rope {\n  flag: U1, \\ del | initialized | type=Rope\n  parent: GCRef[Node], \\ Filesystem Node\n  up:    VersionedRefStart[Rope | RawData | SectorData],\n  left:  VersionedRefStart[Rope | RawData | SectorData],\n  right: VersionedRefStart[Rope | RawData | SectorData],\n  len: RopeLen, \\ note: uses the remainder of the slot\n}\n```\n\nTo see how ropes work, let's look at an example. `R[len]` represents a rope node\nand `\"some string\"` represents RawData.\n\n```\n  We start writing some data, which goes on the left of the Rope:\n\n   /----------------R[12]\n   |\n \"hello world!\"\n\n  Now we want to append \" And Hello Universe!\" to it. To do so we add a new\n  version to the right and update the Rope:\n\n   /----------R[32] -----\\\n   |                     |\n \"hello world!\"  \" And Hello Universe!\"\n\n Now, we want to append \" Now Goodbye!\", we reserve an \"up\" node, which adds the\n data to its right:\n\n               /--------------R[45]-----\\\n               |                        |\n   /----------R[32] -----\\         \" Now Goodbye!\"\n   |                     |\n \"hello world!\"  \" And Hello Universe!\"\n\n Now, we wish to change \"hello world\" to \"Hello Amazing World\" since we love\n capitalizing amazing things. We simply update to a new version of the data and\n update the lengths:\n\n               /--------------R[53]-----\\\n               |                        |\n   /----------R[40] -----\\         \"Now Goodbye!\"\n   |                     |\n \"Hello Amazing World!\"  \" And Hello Universe!\"\n\n We decide against \"Amazing\" and want to remove it. If the data is small we\n simply replace the data. If the data is large, we could instead decide to split\n it in order to avoid future large mutations (shown here for demonstration):\n\n               /--------------R[45]-----\\\n               |                        |\n   /----------R[32] -----\\         \"Now Goodbye!\"\n   |                     |\n /-R[12]----\\            |\n |          |            |\n \"Hello \"  \"World!\"  \" And Hello Universe!\"\n```\n\nA few notes on ropes:\n- For a balanced rope, the time for most operations (seek, insert, etc) is `O(log v + log n)`.\n  Streaming operations like streaming read/write is `O(1)` after the initial\n  seek since it involves just traversing/building the tree from a known\n  location.\n- We can choose to re-balance the rope at any time, which will also reduce the\n  number of versions. We simply combine the RawData's and create a new rope,\n  then update the version of the parent node.\n- Ropes can start out using small RawData chunks (say maximum ~500 bytes), then\n  the GC can \"graduate\" them to larger chunks or even SectorData if they were\n  not mutated during the cycle. The GC can tell they were not mutated by reading\n  their version structs.\n\n## Sector\nThe size of a sector is the size of an [Erasure Block]. It contains some\nmetadata at the beginning of every erasure block:\n\n```\nstruct Sector {\n  \\ del | initialized | gcDone | type=Sector\n  flags: U1,\n  cycle: U4,  \\ 4 byte cycle count\n  root: GCRef,\n  allocBitmap: Arr[U1, sectorLen / 8],\n}\n```\n\nThe sector contains the flags `del` and `initialized`. If the sector is not\n`del` and it is `initialized` then this sector contains the reference to the\n\"root\" node (i.e. `/` in a Linux filesystem). At filesystem startup, a binary\nsearch (time `O(log n)`) is performed over all sectors to find this sector. The\nbinary search uses the `cycle` count, which increments every time the GC\ncollects a sector. This also finds the current GC location, since it is a known\nnumber of \"erasure blocks\" in front of the root sector. After this, the\nfilesystem will keep track of where the root node is stored.\n\nThe allocBitmap has each bit set as the relevant 64 byte slot is used. This\nkeeps track of the currently allocated data for the event of power loss. This is\nchecked against any data which is written to determine if there was critical\npower loss (corruption) and help in recovery.\n\n### SectorData for large Blocks of Data\n\nIf the RawData would be nearly the size of a single sector/[Erasure Block], it\ncan be the type SectorData. This _replaces_ the normal sector metadata and has\ntype=SectorData.\n\n```\nstruct SectorData {\n  flags: U1, \\ del | initialized | type=SectorData\n  crc32: U4, \\ CRC32 checksum of len + data\n  len: U3,   \\ space up to the remaining \"Erasure Block\" size.\n  data: Arr[U1, len],\n}\n```\n\nWhen the GC encounters SectorData which has not been deleted, it doesn't move\nit. This drastically reduces churn on the GC for large files.\n\nSince the data is not moved the GC does not need to update the parent reference;\ntherefore no parent reference is stored. Similarly, when the SectorData's parent\nis moved, it doesn't need to update that fact since there is no reference to\nupdate. This allows the sector data to essentially be \"frozen\" and reduces the\nnumber of erasures on blocks where sector data is stored.\n\n## Node Struct\n\nThe filesystem is composed of a binary search tree of \"nodes\" which have names\nand associated data, which is a versioned reference to either a new node-tree or\ndata.\n\n```\n\\ Signed time in Milliseconds since/before epoch.\ntypedef TimeMilliseconds I8;\n\nstruct Owner {\n  owner: U4,        \\ user id\n  group: U4,        \\ group id\n  permissions: U1,  \\ i.e. linux chmod\n}\n\nstruct Node {\n  flag: U1, \\ del | initialized | type=Node\n  created: TimeMilliseconds,\n  owner: Owner,\n  name: GCRef[RawData]\n  left: GCRef[Node],\n  right: GCRef[Node],\n  parent: GCRef[Node],\n  data: VersionedRefStart[Node | Rope],\n}\n```\n\n#### Deletion, Re-naming and Cleanup\nA node is considered deleted if all of its used versions have the `del` flag\nset. If the node-name is ever re-used, then the new data will be added to the\nVersionedRef. Note that a node's parent and left/right nodes _cannot_ be\nupdated by anyone except the GC, so even though the node is \"deleted\" it is\nstill used for the BST search until the GC collects it.\n\nRenaming can be accomplished with a deletion then an add. Any data held by the\nnode must be re-written (unless that data is SectorData) since there is no way\nto update the `Parent` reference.\n\nThe OS or user may also occasionally decide to rewrite a directory structure in\norder to cleanup any large VersionedRef's or re-balance binary search trees,\nhowever note that this is an extremely expensive operation requiring rewriting\nalmost all files (and sub-files) in a directory -- so this is only useful in\nspecific cases where there are an extremely large number of nodes in a\ndirectory.\n\n## Write Head (WH)\nBoth the GC and data writing are designed to be byte atomic, meaning that power\ncan be lost at any time and when the system reboots it will still be able to\nrecover the filesystem (although some space may be lost on that cycle).\n\nData is written, filling up one sector at a time. The sector starts off as\n\"cleared\" (all bits set to 0), and therefore has flags `del=0, initialized=0,\nfirstGCDone=0`. Likewise, the sector's `allocBitmap` is all\nset to 0, indicating all 64 byte slots are \"free\".\n\nBasic operation:\n- Before the sector is reserved, the GC has set `gcDone=1`, saying the sector\n  has been GC'd.\n- The sector is \"reserved\" by updating the current cycle, setting the current\n  root and marking the slots used by the sector as allocated. `inintialized` is\n  then set to `1`, leaving `del=0` (this sector is now root). The previous\n  sector's `del` is then set to 1.\n- The WH then receives data to write equal to or less than the remaining size of\n  the sector. The WH does not need to check types or do any other special\n  operation. It simply updates it's `allocBitmap` as data is written.\n  - Startup will check for non-agreement. If the final slot is marked as \"free\"\n    but is not set to all `0`'s, then it will assume power loss and mark it as\n    non-free as well as attempt other recovery.\n\n## The Garbage Collector (GC)\nThe GC runs more than 1 (probably 2 or more) sectors in front of the WH, moving\nnon-deleted data to the write head and erasing blocks. After all data in a\nsector has been moved, it clears the `finalGCRunning` flag.\n\nThe GC knows how to determine the size of a node. It simply scans the sector for\nnon-deleted nodes, knowing where they are by using their lengths/etc.\n\n## Data Corruption\nTODO: Data corruption is a real problem for storage systems. This section should\ndetail how this will be solved for.\n\nPossible Solutions:\n\n1. CRC checksum for all non-mutable data (i.e. the `Data` structs), which can\n   both find issues and fix them.\n2. Double or even triple duplicate data for mutable data (i.e. flags, refs,\n   etc).\n3. Bad-block bitmaps\n4. Other solutions?\n\n## Physical Write Layer API\nOn most SDCards (i.e. non \"Standard Capacity\" cards) both reads and writes can\nonly be to a fixed 512 byte \"block\", which I'll call a block512 (see [Physical\nLayer Spec] section 7.2). Also, from 7.1, SDUC cards are simply not supported in\nSPI mode.\n\nTo accommodate this, but still allow byte-level control from an API standpoint,\nthe write interface will use a 512 byte buffer. To write to a byte at ref:\n\n```\nBlock512 bl512 = asBlock512(ref);\nreadBlock512(\u0026buf, bl512); // read 512 bytes of data\nU2 offset = asOffset512(ref);\nbuf[offset] |= 0x84;  // set some bits (note that only set is allowed)\nwriteBlock512(\u0026buf, bl512); // write 512 bytes of data.\n```\n\nMost loops/etc will work as if against a normal buffer, then flush the buffer\nwhen the bl512 changes.\n\nFor \"erasing\" data (setting to 0) you send `CMD32` then `CMD33` to set the\nstart/end address of the erasure. These will use `Block512` indexes. You then\nsend `CMD38:ERASE`.\n\nNote in actual fact that erasure may set bits to 1. For those devices, all\ncommunication will be inverted before sent. However, from the client's\nperspective all operations will be \"normal.\"\n\n## Other Media\nSSD drives are essentially large SD cards with some extra features. Therefore,\nthere should be essentially no conceptual changes (although obviously the\nphysical layer would have some changes), except for possibly supporing multiple\nread/write heads, as some support 16 or more. For this, it would likely make\nsense to allow multiple sectors to be written (and GC'd) simultaniously.\nHowever, this would be a performance optimization and not a fundamental change.\n\nFor Spinning Disk or Floppy, there are at least two major changes:\n- Everything is now byte mutable -- it is not required that you erase a whole\n  sector at once. Therefore GCRef can be replaced with simply Ref and Versioned\n  objects can be replaced with their basic form (i.e. VersionedLen -\u003e Len).\n- There is a concept of \"spindles\", which is a single rotation at a specific\n  radius. Possibly replace the `Sector` objects with `Spindle` objects. More\n  investigation needed.\n- GC would only move data and mark the \"spindle\" as \"deleted.\" It would not\n  actually \"erase\" the data, as there is no need.\n\nFor spinning disk/floppy, this would remove any `v` (i.e. `O(log v)` ) from the\ntime calculations, as well as saving some write time and reserved space.\n\nAnother change that might make sense for spinning disk is to semi-frequently\n(weekly? monthly?) re-write the filesystem to keep related directories and file\ndata close together, as seek performance is slower on spinning disk vs solid\nstate drives. More investigation with real-world use is required.\n\n## Bibliography\n\n- [Reversing CRC - Theory and\n  Practice](http://stigge.org/martin/pub/SAR-PR-2006-05.pdf) is a complete\n  description of CRC in 24 pages according to [this\n  response](https://stackoverflow.com/a/4812571)\n  - [rosseta code](https://rosettacode.org/wiki/CRC-32#C) example CRC-32 is only\n    ~20 lines of C. It does require a 256 length U4, aka a **1024 byte table**.\n- SD Card [Physical Layer Spec] for any specifics about SD Card SPI\n  communication.\n\n### Some notes\n\n\"Polynomial mapping\" in the literature is fairly confusing. These two examples\nshould help explain what the literature is talking about.\n\nNote that the literature writes the polynomial in \"reverse\" order of\nmost-significant bit first (i.e. it writes x^2 + 1 instead of 1 + x^2).\n```\nRepr     | Mapping                                             | Final\nPoly     | 1^0 + 0^1 + x^2                                     | x^2 + 1\nBinary   | 1     0     1                                       | 0xA\n\nPoly     | 1^0 + 0^1 + 0^2 + x^3 + 0^4 + x^5 + 0^6 + 0^7 + x^8 | x^8 + x^5 + x^3 + 1\nBinary   | 1     0     0     1     0     1     0     0     1   | 0x94\n```\n\n[fngi]: http://github.com/civboot/fngi\n[Civboot]: http://civboot.org\n[Erasure Block]: https://en.wikipedia.org/wiki/Flash_memory#NAND_memories\n[Physical Layer Spec]: https://www.sdcard.org/downloads/pls/\n\n## LICENSING\n\nThis work is part of the Civboot project and therefore primarily exists for\neducational purposes. Attribution to the authors and project is appreciated but\nnot necessary.\n\nTherefore this body of work is licensed using the [UNLICENSE](./UNLICENSE),\nunless otherwise specified at the beginning of the source file.\n\nIf for any reason the UNLICENSE is not valid in your jurisdiction or project,\nthis work can be singly or dual licensed at your discretion with the MIT\nlicense below.\n\n```text\nCopyright 2022 Garrett Berg\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitiral%2Ftrfl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvitiral%2Ftrfl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvitiral%2Ftrfl/lists"}