diff --git a/.markdownlint.json b/.markdownlint.json
index 4197e44b0..fb66d6e5c 100644
--- a/.markdownlint.json
+++ b/.markdownlint.json
@@ -8,5 +8,6 @@
"blanks-around-lists": false,
"single-trailing-newline": false,
"link-fragments": false,
- "line-length": false
+ "line-length": false,
+ "blanks-around-fences": false
}
diff --git a/UNIXFS.md b/UNIXFS.md
index a53c7af2c..95e346ca4 100644
--- a/UNIXFS.md
+++ b/UNIXFS.md
@@ -1,233 +1,3 @@
-#  UnixFS
+# UnixFS
-**Author(s)**:
-- NA
-
-* * *
-
-**Abstract**
-
-UnixFS is a [protocol-buffers](https://developers.google.com/protocol-buffers/) based format for describing files, directories, and symlinks in IPFS. The current implementation of UnixFS has grown organically and does not have a clear specification document. See [“implementations”](#implementations) below for reference implementations you can examine to understand the format.
-
-Draft work and discussion on a specification for the upcoming version 2 of the UnixFS format is happening in the [`ipfs/unixfs-v2` repo](https://github.com/ipfs/unixfs-v2). Please see the issues there for discussion and PRs for drafts. When the specification is completed there, it will be copied back to this repo and replace this document.
-
-## Table of Contents
-
-- [Implementations](#implementations)
-- [Data Format](#data-format)
-- [Metadata](#metadata)
- - [Deduplication and inlining](#deduplication-and-inlining)
-- [Importing](#importing)
- - [Chunking](#chunking)
- - [Layout](#layout)
-- [Exporting](#exporting)
-- [Design decision rationale](#design-decision-rationale)
- - [Metadata](#metadata-1)
- - [Separate Metadata node](#separate-metadata-node)
- - [Metadata in the directory](#metadata-in-the-directory)
- - [Metadata in the file](#metadata-in-the-file)
- - [Side trees](#side-trees)
- - [Side database](#side-database)
-
-## Implementations
-
-- JavaScript
- - Data Formats - [unixfs](https://github.com/ipfs/js-ipfs-unixfs)
- - Importer - [unixfs-importer](https://github.com/ipfs/js-ipfs-unixfs-importer)
- - Exporter - [unixfs-exporter](https://github.com/ipfs/js-ipfs-unixfs-exporter)
-- Go
- - [`ipfs/go-ipfs/unixfs`](https://github.com/ipfs/go-ipfs/tree/b3faaad1310bcc32dc3dd24e1919e9edf51edba8/unixfs)
- - Protocol Buffer Definitions - [`ipfs/go-ipfs/unixfs/pb`](https://github.com/ipfs/go-ipfs/blob/b3faaad1310bcc32dc3dd24e1919e9edf51edba8/unixfs/pb/unixfs.proto)
-
-## Data Format
-
-The UnixfsV1 data format is represented by this protobuf:
-
-```protobuf
-message Data {
- enum DataType {
- Raw = 0;
- Directory = 1;
- File = 2;
- Metadata = 3;
- Symlink = 4;
- HAMTShard = 5;
- }
-
- required DataType Type = 1;
- optional bytes Data = 2;
- optional uint64 filesize = 3;
- repeated uint64 blocksizes = 4;
- optional uint64 hashType = 5;
- optional uint64 fanout = 6;
- optional uint32 mode = 7;
- optional UnixTime mtime = 8;
-}
-
-message Metadata {
- optional string MimeType = 1;
-}
-
-message UnixTime {
- required int64 Seconds = 1;
- optional fixed32 FractionalNanoseconds = 2;
-}
-```
-
-This `Data` object is used for all non-leaf nodes in Unixfs.
-
-For files that are comprised of more than a single block, the 'Type' field will be set to 'File', the 'filesize' field will be set to the total number of bytes in the file (not the graph structure) represented by this node, and 'blocksizes' will contain a list of the filesizes of each child node.
-
-This data is serialized and placed inside the 'Data' field of the outer merkledag protobuf, which also contains the actual links to the child nodes of this object.
-
-For files comprised of a single block, the 'Type' field will be set to 'File', 'filesize' will be set to the total number of bytes in the file and the file data will be stored in the 'Data' field.
-
-## Metadata
-
-UnixFS currently supports two optional metadata fields:
-
-* `mode` -- The `mode` is for persisting the file permissions in [numeric notation](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation) \[[spec](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html)\].
- - If unspecified this defaults to
- - `0755` for directories/HAMT shards
- - `0644` for all other types where applicable
- - The nine least significant bits represent `ugo-rwx`
- - The next three least significant bits represent `setuid`, `setgid` and the `sticky bit`
- - The remaining 20 bits are reserved for future use, and are subject to change. Spec implementations **MUST** handle bits they do not expect as follows:
- - For future-proofing the (de)serialization layer must preserve the entire uint32 value during clone/copy operations, modifying only bit values that have a well defined meaning: `clonedValue = ( modifiedBits & 07777 ) | ( originalValue & 0xFFFFF000 )`
- - Implementations of this spec must proactively mask off bits without a defined meaning in the implemented version of the spec: `interpretedValue = originalValue & 07777`
-
-* `mtime` -- A two-element structure ( `Seconds`, `FractionalNanoseconds` ) representing the modification time in seconds relative to the unix epoch `1970-01-01T00:00:00Z`.
- - The two fields are:
- 1. `Seconds` ( always present, signed 64bit integer ): represents the amount of seconds after **or before** the epoch.
- 2. `FractionalNanoseconds` ( optional, 32bit unsigned integer ): when specified represents the fractional part of the mtime as the amount of nanoseconds. The valid range for this value are the integers `[1, 999999999]`.
-
- - Implementations encoding or decoding wire-representations must observe the following:
- - An `mtime` structure with `FractionalNanoseconds` outside of the on-wire range `[1, 999999999]` is **not** valid. This includes a fractional value of `0`. Implementations encountering such values should consider the entire enclosing metadata block malformed and abort processing the corresponding DAG.
- - The `mtime` structure is optional - its absence implies `unspecified`, rather than `0`
- - For ergonomic reasons a surface API of an encoder must allow fractional 0 as input, while at the same time must ensure it is stripped from the final structure before encoding, satisfying the above constraints.
-
- - Implementations interpreting the mtime metadata in order to apply it within a non-IPFS target must observe the following:
- - If the target supports a distinction between `unspecified` and `0`/`1970-01-01T00:00:00Z`, the distinction must be preserved within the target. E.g. if no `mtime` structure is available, a web gateway must **not** render a `Last-Modified:` header.
- - If the target requires an mtime ( e.g. a FUSE interface ) and no `mtime` is supplied OR the supplied `mtime` falls outside of the targets accepted range:
- - When no `mtime` is specified or the resulting `UnixTime` is negative: implementations must assume `0`/`1970-01-01T00:00:00Z` ( note that such values are not merely academic: e.g. the OpenVMS epoch is `1858-11-17T00:00:00Z` )
- - When the resulting `UnixTime` is larger than the targets range ( e.g. 32bit vs 64bit mismatch ) implementations must assume the highest possible value in the targets range ( in most cases that would be `2038-01-19T03:14:07Z` )
-
-### Deduplication and inlining
-
-Where the file data is small it would normally be stored in the `Data` field of the UnixFS `File` node.
-
-To aid in deduplication of data even for small files, file data can be stored in a separate node linked to from the `File` node in order for the data to have a constant [CID] regardless of the metadata associated with it.
-
-As a further optimization, if the `File` node's serialized size is small, it may be inlined into its v1 [CID] by using the [`identity`](https://github.com/multiformats/multicodec/blob/master/table.csv) [multihash].
-
-## Importing
-
-Importing a file into unixfs is split up into two parts. The first is chunking, the second is layout.
-
-### Chunking
-
-Chunking has two main parameters, chunking strategy and leaf format.
-
-Leaf format should always be set to 'raw', this is mainly configurable for backwards compatibility with earlier formats that used a Unixfs Data object with type 'Raw'. Raw leaves means that the nodes output from chunking will be just raw data from the file with a CID type of 'raw'.
-
-Chunking strategy currently has two different options, 'fixed size' and 'rabin'. Fixed size chunking will chunk the input data into pieces of a given size. Rabin chunking will chunk the input data using rabin fingerprinting to determine the boundaries between chunks.
-
-
-### Layout
-
-Layout defines the shape of the tree that gets built from the chunks of the input file.
-
-There are currently two options for layout, balanced, and trickle.
-Additionally, a 'max width' must be specified. The default max width is 174.
-
-The balanced layout creates a balanced tree of width 'max width'. The tree is formed by taking up to 'max width' chunks from the chunk stream, and creating a unixfs file node that links to all of them. This is repeated until 'max width' unixfs file nodes are created, at which point a unixfs file node is created to hold all of those nodes, recursively. The root node of the resultant tree is returned as the handle to the newly imported file.
-
-If there is only a single chunk, no intermediate unixfs file nodes are created, and the single chunk is returned as the handle to the file.
-
-## Exporting
-
-To read the file data out of the unixfs graph, perform an in order traversal, emitting the data contained in each of the leaves.
-
-## Design decision rationale
-
-### Metadata
-
-Metadata support in UnixFSv1.5 has been expanded to increase the number of possible use cases. These include rsync and filesystem based package managers.
-
-Several metadata systems were evaluated:
-
-#### Separate Metadata node
-
-In this scheme, the existing `Metadata` message is expanded to include additional metadata types (`mtime`, `mode`, etc). It then contains links to the actual file data but never the file data itself.
-
-This was ultimately rejected for a number of reasons:
-
-1. You would always need to retrieve an additional node to access file data which limits the kind of optimizations that are possible.
-
- For example many files are under the 256KiB block size limit, so we tend to inline them into the describing UnixFS `File` node. This would not be possible with an intermediate `Metadata` node.
-
-2. The `File` node already contains some metadata (e.g. the file size) so metadata would be stored in multiple places which complicates forwards compatibility with UnixFSv2 as to map between metadata formats potentially requires multiple fetch operations
-
-#### Metadata in the directory
-
-Repeated `Metadata` messages are added to UnixFS `Directory` and `HAMTShard` nodes, the index of which indicates which entry they are to be applied to.
-
-Where entries are `HAMTShard`s, an empty message is added.
-
-One advantage of this method is that if we expand stored metadata to include entry types and sizes we can perform directory listings without needing to fetch further entry nodes (excepting `HAMTShard` nodes), though without removing the storage of these datums elsewhere in the spec we run the risk of having non-canonical data locations and perhaps conflicting data as we traverse through trees containing both UnixFS v1 and v1.5 nodes.
-
-This was rejected for the following reasons:
-
-1. When creating a UnixFS node there's no way to record metadata without wrapping it in a directory.
-
-2. If you access any UnixFS node directly by its [CID], there is no way of recreating the metadata which limits flexibility.
-
-3. In order to list the contents of a directory including entry types and sizes, you have to fetch the root node of each entry anyway so the performance benefit of including some metadata in the containing directory is negligible in this use case.
-
-#### Metadata in the file
-
-This adds new fields to the UnixFS `Data` message to represent the various metadata fields.
-
-It has the advantage of being simple to implement, metadata is maintained whether the file is accessed directly via its [CID] or via an IPFS path that includes a containing directory, and by keeping the metadata small enough we can inline root UnixFS nodes into their CIDs so we can end up fetching the same number of nodes if we decide to keep file data in a leaf node for deduplication reasons.
-
-Downsides to this approach are:
-
-1. Two users adding the same file to IPFS at different times will have different [CID]s due to the `mtime`s being different.
-
- If the content is stored in another node, its [CID] will be constant between the two users but you can't navigate to it unless you have the parent node which will be less available due to the proliferation of [CID]s.
-
-2. Metadata is also impossible to remove without changing the [CID], so metadata becomes part of the content.
-
-3. Performance may be impacted as well as if we don't inline UnixFS root nodes into [CID]s, additional fetches will be required to load a given UnixFS entry.
-
-#### Side trees
-
-With this approach we would maintain a separate data structure outside of the UnixFS tree to hold metadata.
-
-This was rejected due to concerns about added complexity, recovery after system crashes while writing, and having to make extra requests to fetch metadata nodes when resolving [CID]s from peers.
-
-#### Side database
-
-This scheme would see metadata stored in an external database.
-
-The downsides to this are that metadata would not be transferred from one node to another when syncing as [Bitswap] is not aware of the database, and in-tree metadata
-
-### UnixTime protobuf datatype rationale
-
-#### Seconds
-
-The integer portion of UnixTime is represented on the wire using a varint encoding. While this is
-inefficient for negative values, it avoids introducing zig-zag encoding. Values before the year 1970
-will be exceedingly rare, and it would be handy having such cases stand out, while at the same keeping
-the "usual" positive values easy to eyeball. The varint representing the time of writing this text is
-5 bytes long. It will remain so until October 26, 3058 ( 34,359,738,367 )
-
-#### FractionalNanoseconds
-Fractional values are effectively a random number in the range 1 ~ 999,999,999. Such values will exceed
-2^28 nanoseconds ( 268,435,456 ) in most cases. Therefore, the fractional part is represented as a 4-byte
-`fixed32`, [as per Google's recommendation](https://developers.google.com/protocol-buffers/docs/proto#scalar).
-
-[multihash]: https://tools.ietf.org/html/draft-multiformats-multihash-00
-[CID]: https://docs.ipfs.io/guides/concepts/cid/
-[Bitswap]: https://github.com/ipfs/specs/blob/master/BITSWAP.md
-[MFS]: https://docs.ipfs.io/guides/concepts/mfs/
+Moved to https://specs.ipfs.tech/unixfs/
diff --git a/package-lock.json b/package-lock.json
index 6ab7de863..51d624711 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,7 @@
"name": "ipfs-specs-website",
"version": "1.0.0",
"devDependencies": {
- "spec-generator": "^1.5.0"
+ "spec-generator": "^1.6.0"
}
},
"node_modules/@11ty/dependency-tree": {
@@ -10029,9 +10029,9 @@
"dev": true
},
"node_modules/spec-generator": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/spec-generator/-/spec-generator-1.5.0.tgz",
- "integrity": "sha512-HbthFfgF3MAFIcOmrmWvOw2p44dXTXEftKNtp6hdY7d4gHYE/QWzSmoyMY2YNQQffY4Rz9twnaWqjPJ6ZruSGQ==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/spec-generator/-/spec-generator-1.6.0.tgz",
+ "integrity": "sha512-l93u7rXioKFNroBvi0Q0f3uvBO6X7CWu2oh2+uRquAMB0Opi3CBYvC66B9qugoLzG4eI6NjzZTct88RdrZZHMA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index c014efe07..dde7432f7 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,6 @@
"license": "",
"private": true,
"devDependencies": {
- "spec-generator": "^1.5.0"
+ "spec-generator": "^1.6.0"
}
}
diff --git a/src/data-formats/index.html b/src/data-formats/index.html
new file mode 100644
index 000000000..c34ea9654
--- /dev/null
+++ b/src/data-formats/index.html
@@ -0,0 +1,13 @@
+---
+title: Data formats
+description: |
+ IPFS basic primitive is an opaque block of bytes identified by a CID. CID includes codec that informs IPFS System about data format: how to parse the block, and how to link from one block to another.
+---
+
+{% include 'header.html' %}
+
+
+ IPFS basic primitive is an opaque block of bytes identified by a CID. CID includes codec that informs IPFS System about data format: how to parse the block, and how to link from one block to another. +
++ The most popular data formats used by IPFS Systems are RAW (an opaque block), UnixFS (filesystem abstraction built with DAG-PB and RAW codecs), DAG-CBOR/DAG-JSON, however IPFS ecosystem is not limited to them, and IPFS systems are free to choose the level of interoperability, or even implement support for own, additional formats. A complimentary CAR is a codec-agnostic archive format for transporting multiple opaque blocks. +
++ Specifications: +
+ {% include 'list.html', posts: collections.data-formats %} +
diff --git a/src/unixfs.md b/src/unixfs.md
new file mode 100644
index 000000000..ede3e5524
--- /dev/null
+++ b/src/unixfs.md
@@ -0,0 +1,1281 @@
+---
+title: UnixFS
+description: >
+ UnixFS is a Protocol Buffers-based format for describing files, directories,
+ and symlinks as dag-pb DAGs and raw blocks in IPFS.
+date: 2025-08-23
+maturity: draft
+editors:
+ - name: Marcin Rataj
+ github: lidel
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+former_editors:
+ - name: Hugo Valtier
+ github: Jorropo
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+contributors:
+thanks:
+ - name: David Dias
+ github: daviddias
+ - name: Łukasz Magiera
+ github: magik6k
+ - name: Jeromy Johnson
+ github: whyrusleeping
+ - name: Alex Potsides
+ github: achingbrain
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+ - name: Peter Rabbitson
+ github: ribasushi
+ - name: Steven Allen
+ github: Stebalien
+ - name: Hector Sanjuan
+ github: hsanjuan
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+ - name: Henrique Dias
+ github: hacdias
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+ - name: Marten Seemann
+ github: marten-seemann
+ - name: Adin Schmahmann
+ github: aschmahmann
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+ - name: Will Scott
+ github: willscott
+ - name: John Turpish
+ github: John-LittleBearLabs
+ - name: Alan Shaw
+ github: alanshaw
+ - name: Andrew Gillis
+ github: gammazero
+ affiliation:
+ name: Interplanetary Shipyard
+ url: https://ipshipyard.com/
+ - name: bumblefudge
+ github: bumblefudge
+
+tags: ['data-formats']
+order: 1
+---
+
+# Node Types
+
+A :dfn[Node] is the smallest unit present in a graph, and it comes from graph
+theory. In UnixFS, there is a 1-to-1 mapping between nodes and blocks. Therefore,
+they are used interchangeably in this document.
+
+A node is addressed by a [CID]. In order to be able to read a node, its [CID] is
+required. A [CID] includes two important pieces of information:
+
+1. A [multicodec], simply known as a codec.
+2. A [multihash] used to specify the hashing algorithm, the hash parameters and
+ the hash digest.
+
+Thus, when a block is retrieved and its bytes are hashed using the
+hash function specified in the multihash, this gives the same multihash value contained in the CID.
+
+In UnixFS, a node can be encoded using two different multicodecs, listed below. More details are provided in the following sections:
+
+- [`raw`](#raw-node) (`0x55`), which are single block files without any metadata.
+- [`dag-pb`](#dag-pb-node) (`0x70`), which can be of any other type.
+
+# `raw` Node
+
+The simplest nodes use `raw` encoding and are implicitly a [File](#dag-pb-file). They can
+be recognized because their [CIDs](https://github.com/multiformats/cid) are encoded using the `raw` (`0x55`) codec:
+
+- The block is the file data. There is no protobuf envelope or metadata.
+- They never have any children nodes, and thus are also known as single block files.
+- Their size is the length of the block body (`Tsize` in parent is equal to `blocksize`).
+
+:::warning
+**Important**: Do not confuse `raw` codec blocks (`0x55`) with the deprecated `Raw` DataType (enum value `0`):
+- **`raw` codec** - Modern way to store data without protobuf wrapper (used for small files and leaves)
+- **`Raw` DataType** - Legacy UnixFS type that wrapped raw data in dag-pb protobuf (implementations MUST NOT produce, MAY read for compatibility)
+:::
+
+# `dag-pb` Node
+
+More complex nodes use the `dag-pb` (`0x70`) encoding. These nodes require two steps of
+decoding. The first step is to decode the outer container of the block. This is encoded using the [`dag-pb`][ipld-dag-pb] specification, which uses [Protocol Buffers][protobuf] and can be
+summarized as follows:
+
+```protobuf
+message PBLink {
+ // Binary representation of CID (https://github.com/multiformats/cid) of the target object.
+ // This contains raw CID bytes (either CIDv0 or CIDv1) with no multibase prefix.
+ // CIDv1 is a binary format composed of unsigned varints, while CIDv0 is a raw multihash.
+ // In both cases, the bytes are stored directly without any additional prefix.
+ bytes Hash = 1;
+
+ // UTF-8 string name
+ string Name = 2;
+
+ // cumulative size of target object
+ uint64 Tsize = 3;
+}
+
+message PBNode {
+ // refs to other objects
+ repeated PBLink Links = 2;
+
+ // opaque user data
+ bytes Data = 1;
+}
+```
+
+After decoding the node, we obtain a `PBNode`. This `PBNode` contains a field
+`Data` that contains the bytes that require the second decoding. This will also be
+a protobuf message specified in the UnixFSV1 format:
+
+```protobuf
+message Data {
+ enum DataType {
+ Raw = 0; // deprecated, use raw codec blocks without dag-pb instead
+ Directory = 1;
+ File = 2;
+ Metadata = 3; // reserved for future use
+ Symlink = 4;
+ HAMTShard = 5;
+ }
+
+ DataType Type = 1; // MUST be present - validate at application layer
+ bytes Data = 2; // file content (File), symlink target (Symlink), bitmap (HAMTShard), unused (Directory)
+ uint64 filesize = 3; // mandatory for Type=File and Type=Raw, defaults to 0 if omitted
+ repeated uint64 blocksizes = 4; // required for multi-block files (Type=File) with Links
+ uint64 hashType = 5; // required for Type=HAMTShard (currently always murmur3-x64-64)
+ uint64 fanout = 6; // required for Type=HAMTShard (power of 2, max 1024)
+ uint32 mode = 7; // opt-in, AKA UnixFS 1.5
+ UnixTime mtime = 8; // opt-in, AKA UnixFS 1.5
+}
+
+message Metadata {
+ string MimeType = 1; // reserved for future use
+}
+
+message UnixTime {
+ int64 Seconds = 1; // MUST be present when UnixTime is used
+ fixed32 FractionalNanoseconds = 2;
+}
+```
+
+Summarizing, a `dag-pb` UnixFS node is a [`dag-pb`][ipld-dag-pb] protobuf,
+whose `Data` field is a UnixFSV1 Protobuf message. For clarity, the specification
+document may represent these nested Protobufs as one object. In this representation,
+it is implied that the `PBNode.Data` field is protobuf-encoded.
+
+## `dag-pb` Types
+
+A `dag-pb` UnixFS node supports different types, which are defined in
+`decode(PBNode.Data).Type`. Every type is handled differently.
+
+### `dag-pb` `File`
+
+A :dfn[File] is a container over an arbitrary sized amount of bytes. Files are either
+single block or multi-block. A multi-block file is a concatenation of multiple child files.
+
+:::note
+Single-block files SHOULD prefer the `raw` codec (0x55) over `dag-pb` for the canonical CID,
+as it's more efficient and avoids the protobuf overhead. The `raw` encoding is described
+in the [`raw` Node](#raw-node) section.
+:::
+
+#### The _sister-lists_ `PBNode.Links` and `decode(PBNode.Data).blocksizes`
+
+The _sister-lists_ are the key point of why `dag-pb` is important for files. They
+allow us to concatenate smaller files together.
+
+Linked files would be loaded recursively with the same process following a DFS
+(Depth-First-Search) order.
+
+Child nodes must be of type File; either a `dag-pb` [File](#dag-pb-file), or a
+[`raw` block](#raw-blocks).
+
+For example, consider this pseudo-json block:
+
+```json
+{
+ "Links": [{"Hash":"Qmfoo"}, {"Hash":"Qmbar"}],
+ "Data": {
+ "Type": "File",
+ "blocksizes": [20, 30]
+ }
+}
+```
+
+This indicates that this file is the concatenation of the `Qmfoo` and `Qmbar` files.
+
+When reading a file represented with `dag-pb`, the `blocksizes` array gives us the
+size in bytes of the partial file content present in children DAGs. Each index in
+`PBNode.Links` MUST have a corresponding chunk size stored at the same index
+in `decode(PBNode.Data).blocksizes`.
+
+The child blocks containing the partial file data can be either:
+- `raw` blocks (0x55): Direct file data without protobuf wrapper
+- `dag-pb` blocks (0x70): File data wrapped in protobuf, potentially with further children
+
+:::warning
+Implementers need to be extra careful to ensure the values in `Data.blocksizes`
+are calculated by following the definition from [`Blocksize`](#decodepbnodedatablocksize).
+:::
+
+This allows for fast indexing into the file. For example, if someone is trying
+to read bytes 25 to 35, we can compute an offset list by summing all previous
+indexes in `blocksizes`, then do a search to find which indexes contain the
+range we are interested in.
+
+In the example above, the offset list would be `[0, 20]`. Thus, we know we only need to download `Qmbar` to get the range we are interested in.
+
+A UnixFS parser MUST reject the node and halt processing if the `blocksizes` array and
+`Links` array contain different numbers of elements. Implementations SHOULD return a
+descriptive error indicating the array length mismatch rather than silently failing or
+attempting to process partial data.
+
+#### `decode(PBNode.Data).Data`
+
+An array of bytes that is the file content and is appended before
+the links. This must be taken into account when doing offset calculations; that is,
+the length of `decode(PBNode.Data).Data` defines the value of the zeroth element
+of the offset list when computing offsets.
+
+#### `PBNode.Links[].Name`
+
+The `Name` field is primarily used in directories to identify child entries.
+
+**For internal file chunks:**
+- Implementations SHOULD NOT produce `Name` fields (the field should be absent in the protobuf, not an empty string)
+- For compatibility with historical data, implementations SHOULD treat empty string values ("") the same as absent when parsing
+- If a non-empty `Name` is present in an internal file chunk, the parser MUST reject the file and halt processing as this indicates an invalid file structure
+
+#### `decode(PBNode.Data).Blocksize`
+
+This field is not directly present in the block, but rather a computable property
+of a `dag-pb`, which would be used in the parent node in `decode(PBNode.Data).blocksizes`.
+
+**Important:** `blocksize` represents only the raw file data size, NOT including the protobuf envelope overhead.
+
+It is calculated as:
+- For `dag-pb` blocks: the length of `decode(PBNode.Data).Data` field plus the sum of all child `blocksizes`
+- For `raw` blocks (small files, raw leaves): the length of the entire raw block
+
+:::note
+
+Examples of where `blocksize` is useful:
+
+- Seeking and range requests (e.g., HTTP Range headers for video streaming). The `blocksizes` array allows calculating byte offsets (see [Offset List](#offset-list)) to determine which blocks contain the requested range without downloading unnecessary blocks.
+
+:::
+
+#### `decode(PBNode.Data).filesize`
+
+For `Type=File` (0) and `Type=Raw` (2), this field is mandatory. While marked as "optional"
+in the protobuf schema (for compatibility with other types like Directory), implementations:
+- MUST include this field when creating File or Raw nodes
+- When reading, if this field is absent, MUST interpret it as 0 (zero-length file)
+- If present, this field MUST be equal to the `Blocksize` computation above, otherwise the file is invalid
+
+#### `dag-pb` `File` Path Resolution
+
+A file terminates a UnixFS content path. Any attempt to resolve a path past a
+file MUST be rejected with an error indicating that UnixFS files cannot have children.
+
+### `dag-pb` `Directory`
+
+A :dfn[Directory], also known as folder, is a named collection of child [Nodes](#dag-pb-node):
+
+- Every link in `PBNode.Links` is an entry (child) of the directory, and
+ `PBNode.Links[].Name` gives you the name of that child.
+- Duplicate names are not allowed. Therefore, two elements of `PBNode.Link` CANNOT
+ have the same `Name`. Names are considered identical if they are byte-for-byte
+ equal (not just semantically equivalent). If two identical names are present in
+ a directory, the decoder MUST fail.
+- Implementations SHOULD detect when a directory becomes too big to fit in a single
+ `Directory` block and use [`HAMTDirectory`] type instead.
+
+The `PBNode.Data` field MUST contain valid UnixFS protobuf data for all UnixFS nodes.
+For directories (DataType==1), the minimum valid `PBNode.Data` field is as follows:
+
+```json
+{
+ "Type": "Directory"
+}
+```
+
+For historical compatibility, implementations MAY encounter dag-pb nodes with empty or
+missing Data fields from older IPFS versions, but MUST NOT produce such nodes.
+
+#### `dag-pb` `Directory` Link Ordering
+
+Directory links SHOULD be sorted lexicographically by the `Name` field when creating
+new directories. This ensures consistent, deterministic directory structures across
+implementations.
+
+While decoders MUST accept directories with any link ordering, encoders SHOULD use
+lexicographic sorting for better interoperability and deterministic CIDs.
+
+A decoder SHOULD, if it can, preserve the order of the original files. This "sort on write,
+not on read" approach maintains DAG stability - existing unsorted directories remain unchanged
+when accessed or traversed, preventing unintentional mutations of intermediate nodes that could
+alter their CIDs.
+
+Note: Lexicographic sorting was chosen as the standard because it provides a universal,
+locale-independent ordering that works consistently across all implementations and languages.
+Sorting on write (when the Links list is modified) helps with deduplication detection and enables more
+efficient directory traversal algorithms in some implementations.
+
+#### `dag-pb` `Directory` Path Resolution
+
+Pop the left-most component of the path, and match it to the `Name` of
+a child under `PBNode.Links`.
+
+Duplicate names are not allowed in UnixFS directories. However, when reading
+third-party data that contains duplicates, implementations MUST always return
+the first matching entry and ignore subsequent ones (following the
+[Robustness Principle](https://specs.ipfs.tech/architecture/principles/#robustness)).
+Similarly, when writers mutate a UnixFS directory that has duplicate
+names, they MUST drop the redundant entries and only keep the first occurrence
+of each name.
+
+Assuming no errors were raised, you can continue to the path resolution on the
+remaining components and on the CID you popped.
+
+### `dag-pb` `HAMTDirectory`
+
+A :dfn[HAMT Directory] is a [Hashed-Array-Mapped-Trie](https://en.wikipedia.org/wiki/Hash_array_mapped_trie)
+data structure representing a [Directory](#dag-pb-directory). It is generally used to represent
+directories that cannot fit inside a single block. These are also known as "sharded
+directories", since they allow you to split large directories into multiple blocks, known as "shards".
+
+#### HAMT Structure and Parameters
+
+The HAMT directory is configured through the UnixFS metadata in `PBNode.Data`:
+
+- `decode(PBNode.Data).Type` MUST be `HAMTShard` (value `5`)
+- `decode(PBNode.Data).hashType` indicates the [multihash] function to use to digest
+ the path components for sharding. Currently, all HAMT implementations use `murmur3-x64-64` (`0x22`),
+ and this value MUST be consistent across all shards within the same HAMT structure
+- `decode(PBNode.Data).fanout` is REQUIRED for HAMTShard nodes (though marked optional in the
+ protobuf schema). The value MUST be a power of two, a multiple of 8 (for byte-aligned
+ bitfields), and at most 1024.
+
+ This determines the number of possible bucket indices (permutations) at each level of the trie.
+ For example, fanout=256 provides 256 possible buckets (0x00 to 0xFF), requiring 8 bits from the hash.
+ The hex prefix length is `log2(fanout)/4` characters (since each hex character represents 4 bits).
+ The same fanout value is used throughout all levels of a single HAMT structure
+
+ :::note
+ Implementations that onboard user data to create new HAMTDirectory structures are free to choose a `fanout` value or allow users to configure it based on their use case:
+ - **256**: Balanced tree depth and node size, suitable for most use cases
+ - **1024**: Creates wider, shallower DAGs with fewer levels
+ - Advantages: Minimizes tree depth for faster lookups, reduces number of intermediate nodes to traverse
+ - Trade-offs: Larger blocks mean higher latency on cold cache reads and more data
+ rewritten when modifying directories (each change affects a larger block)
+ :::
+
+ :::warning
+ Implementations MUST limit the `fanout` parameter to a maximum of 1024 to prevent
+ denial-of-service attacks. Excessively large fanout values can cause memory exhaustion
+ when allocating bucket arrays. See [CVE-2023-23625](https://nvd.nist.gov/vuln/detail/CVE-2023-23625) and
+ [GHSA-q264-w97q-q778](https://github.com/advisories/GHSA-q264-w97q-q778) for details
+ on this vulnerability.
+ :::
+- `decode(PBNode.Data).Data` contains a bitfield indicating which buckets contain entries.
+ Each bit corresponds to one bucket (0 to fanout-1), with bit value 1 indicating the bucket
+ is occupied. The bitfield is stored in little-endian byte order. The bitfield size in bytes
+ is `fanout/8`, which is why fanout MUST be a multiple of 8.
+ - Implementations MUST write this bitfield when creating HAMT nodes
+ - Implementations SHOULD use this bitfield for efficient traversal (checking which buckets
+ exist without examining all links)
+ - Note: Some implementations derive bucket occupancy from link names instead of reading
+ the bitfield, but this is less efficient
+
+The field `Name` of an element of `PBNode.Links` for a HAMT uses a
+hex-encoded prefix corresponding to the bucket index, zero-padded to a width
+of `log2(fanout)/4` characters.
+
+To illustrate the HAMT structure with a concrete example:
+
+```protobuf
+// Root HAMT shard (bafybeidbclfqleg2uojchspzd4bob56dqetqjsj27gy2cq3klkkgxtpn4i)
+// This shard contains 1000 files distributed across buckets
+message PBNode {
+ // UnixFS metadata in Data field
+ Data = {
+ Type = HAMTShard // Type = 5
+ Data = 0xffffff... // Bitmap: bits set for populated buckets
+ hashType = 0x22 // murmur3-x64-64
+ fanout = 256 // 256 buckets (8-bit width)
+ }
+
+ // Links to sub-shards or entries
+ Links = [
+ {
+ Hash = bafybeiaebmuestgbpqhkkbrwl2qtjtvs3whkmp2trkbkimuod4yv7oygni
+ Name = "00" // Bucket 0x00
+ Tsize = 2693 // Cumulative size of this subtree
+ },
+ {
+ Hash = bafybeia322onepwqofne3l3ptwltzns52fgapeauhmyynvoojmcvchxptu
+ Name = "01" // Bucket 0x01
+ Tsize = 7977
+ },
+ // ... more buckets as needed up to "FF"
+ ]
+}
+
+// Sub-shard for bucket "00" (multiple files hash to 00 at first level)
+message PBNode {
+ Data = {
+ Type = HAMTShard // Still a HAMT at second level
+ Data = 0x800000... // Bitmap for this sub-level
+ hashType = 0x22 // murmur3-x64-64
+ fanout = 256 // Same fanout throughout
+ }
+
+ Links = [
+ {
+ Hash = bafybeigcisqd7m5nf3qmuvjdbakl5bdnh4ocrmacaqkpuh77qjvggmt2sa
+ Name = "6E470.txt" // Bucket 0x6E + filename
+ Tsize = 1271
+ },
+ {
+ Hash = bafybeigcisqd7m5nf3qmuvjdbakl5bdnh4ocrmacaqkpuh77qjvggmt2sa
+ Name = "FF742.txt" // Bucket 0xFF + filename
+ Tsize = 1271
+ }
+ ]
+}
+```
+
+#### `dag-pb` `HAMTDirectory` Path Resolution
+
+To resolve a path inside a HAMT:
+
+1. Hash the filename using the hash function specified in `decode(PBNode.Data).hashType`
+2. Pop `log2(fanout)` bits from the hash digest (lowest/least significant bits first),
+ then hex encode those bits using little endian to form the bucket prefix. The prefix MUST use uppercase hex characters (00-FF, not 00-ff)
+3. Find the link whose `Name` starts with this hex prefix:
+ - If `Name` equals the prefix exactly → this is a sub-shard, follow the link and repeat from step 2
+ - If `Name` equals prefix + filename → target found
+ - If no matching prefix → file not in directory
+4. When following to a sub-shard, continue consuming bits from the same hash
+
+Note: Empty intermediate shards are typically collapsed during deletion operations to maintain consistency
+and avoid having HAMT structures that differ based on insertion/deletion history.
+
+:::note
+**Example: Finding "470.txt" in a HAMT with fanout=256** (see [HAMT Sharded Directory test vector](#hamt-sharded-directory))
+
+Given a HAMT-sharded directory containing 1000 files:
+
+1. Hash the filename "470.txt" using murmur3-x64-64 (multihash `0x22`)
+2. With fanout=256, we consume 8 bits at a time from the hash:
+ - First 8 bits determine root bucket → `0x00` → link name "00"
+ - Follow link "00" to sub-shard (`bafybeiaebmuestgbpqhkkbrwl2qtjtvs3whkmp2trkbkimuod4yv7oygni`)
+3. The sub-shard is also a HAMT (has Type=HAMTShard):
+ - Next 8 bits from hash → `0x6E`
+ - Find entry with name "6E470.txt" (prefix + original filename)
+4. Link name format at leaf level: `[hex_prefix][original_filename]`
+ - "6E470.txt" means: file "470.txt" that hashed to bucket 6E at this level
+ - "FF742.txt" means: file "742.txt" that hashed to bucket FF at this level
+:::
+
+#### When to Use HAMT Sharding
+
+Implementations typically convert regular directories to HAMT when the serialized directory
+node exceeds a size threshold between 256 KiB and 1 MiB. This threshold:
+- Prevents directories from exceeding block size limits
+- Is implementation-specific and may be configurable
+- Common values range from 256 KiB (conservative) to 1 MiB (modern)
+
+See [Block Size Considerations](#block-size-considerations) for details on block size limits and conventions.
+
+### `dag-pb` `Symlink`
+
+A :dfn[Symlink] represents a POSIX [symbolic link](https://pubs.opengroup.org/onlinepubs/9699919799/functions/symlink.html).
+A symlink MUST NOT have children in `PBNode.Links`.
+
+The `PBNode.Data.Data` field is a POSIX path that MAY be inserted in front of the
+currently remaining path component stack.
+
+#### `dag-pb` `Symlink` Path Resolution
+
+Symlink path resolution SHOULD follow the POSIX specification, over the current UnixFS path context, as much as is applicable.
+
+:::warning
+
+There is no current consensus on how pathing over symlinks should behave. Some
+implementations return symlink objects and fail if a consumer tries to follow them
+through.
+
+:::
+
+### `dag-pb` `TSize` (cumulative DAG size)
+
+`Tsize` is an optional field in `PBNode.Links[]` which represents the cumulative size of the entire DAG rooted at that link, including all protobuf encoding overhead.
+
+While optional in the protobuf schema, implementations SHOULD include `Tsize` for:
+- All directory entries (enables fast directory size display)
+- Multi-block files (enables parallel downloading and progress tracking)
+- HAMT shard links (enables efficient traversal decisions)
+
+**Key distinction from blocksize:**
+- **`blocksize`**: Only the raw file data (no protobuf overhead)
+- **`Tsize`**: Total size of all serialized blocks in the DAG (includes protobuf overhead)
+
+To compute `Tsize`: sum the serialized size of the current dag-pb block and the Tsize values of all child links.
+
+:::note
+
+**Example: Directory with multi-block file**
+
+Consider the [Simple Directory fixture](#simple-directory) (`bafybeihchr7vmgjaasntayyatmp5sv6xza57iy2h4xj7g46bpjij6yhrmy`):
+
+The directory has a total `Tsize` of 1572 bytes:
+- Directory block itself: 227 bytes when serialized
+- Child entries with Tsizes: 31 + 31 + 12 + 1271 = 1345 bytes
+
+The `multiblock.txt` file within this directory demonstrates how `Tsize` accumulates:
+- Raw file content: 1026 bytes (blocksizes: [256, 256, 256, 256, 2])
+- Root dag-pb block: 245 bytes when serialized
+- Total `Tsize`: 245 + 1026 = 1271 bytes
+
+This shows how `Tsize` includes both the protobuf overhead and all child data, while `blocksize` only counts the raw file data.
+
+:::
+
+:::note
+
+Examples of where `Tsize` is useful:
+
+- User interfaces, where total size of a DAG needs to be displayed immediately, without having to do the full DAG walk.
+- Smart download clients, downloading a file concurrently from two sources that have radically different speeds. It may be more efficient to parallelize and download bigger
+links from the fastest source, and smaller ones from the slower sources.
+
+:::
+
+:::warning
+
+An implementation SHOULD NOT assume the `TSize` values are correct. The value is only a hint that provides performance optimization for better UX.
+
+Following the [Robustness Principle](https://specs.ipfs.tech/architecture/principles/#robustness), implementation SHOULD be
+able to decode nodes where the `Tsize` field is wrong (not matching the sizes of sub-DAGs), or
+partially or completely missing.
+
+:::
+
+:::warning
+
+When total data size is needed for important purposes such as accounting, billing, and cost estimation, the `Tsize` SHOULD NOT be used, and instead a full DAG walk SHOULD to be performed.
+
+:::
+
+### `dag-pb` Optional Metadata
+
+UnixFS defines the following optional metadata fields.
+
+#### `mode` Field
+
+The `mode` (introduced in UnixFS v1.5) is for persisting the file permissions in [numeric notation](https://en.wikipedia.org/wiki/File_system_permissions#Numeric_notation)
+\[[spec](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html)\].
+
+- If unspecified, implementations MAY default to
+ - `0755` for directories/HAMT shards
+ - `0644` for all other types where applicable
+- The nine least significant bits represent `ugo-rwx`
+- The next three least significant bits represent `setuid`, `setgid` and the `sticky bit`
+- The remaining 20 bits are reserved for future use, and are subject to change. Spec implementations **MUST** handle bits they do not expect as follows:
+ - For future-proofing, the (de)serialization layer must preserve the entire uint32 value during clone/copy operations, modifying only bit values that have a well defined meaning: `clonedValue = ( modifiedBits & 07777 ) | ( originalValue & 0xFFFFF000 )`
+ - Implementations of this spec must proactively mask off bits without a defined meaning in the implemented version of the spec: `interpretedValue = originalValue & 07777`
+
+**Implementation guidance:**
+- When importing new data, implementations SHOULD NOT include the mode field unless the user explicitly requests preserving permissions
+ - Including mode changes the root CID, causing unnecessary deduplication failures when permission differences are irrelevant
+- Implementations MUST be able to parse UnixFS nodes both with and without this field
+- When present during operations like copying, implementations SHOULD preserve this field
+
+#### `mtime` Field
+
+The `mtime` (introduced in UnixFS v1.5) is a two-element structure ( `Seconds`, `FractionalNanoseconds` ) representing the
+modification time in seconds relative to the unix epoch `1970-01-01T00:00:00Z`.
+
+The two fields are:
+
+1. `Seconds` ( always present, signed 64bit integer ): represents the amount of seconds after **or before** the epoch.
+2. `FractionalNanoseconds` ( optional, 32bit unsigned integer ): when specified, represents the fractional part of the `mtime` as the amount of nanoseconds. The valid range for this value are the integers `[1, 999999999]`.
+
+Implementations encoding or decoding wire-representations MUST observe the following:
+
+- An `mtime` structure with `FractionalNanoseconds` outside of the on-wire range
+ `[1, 999999999]` is **not** valid. This includes a fractional value of `0`.
+ Implementations encountering such values should consider the entire enclosing
+ metadata block malformed and abort the processing of the corresponding DAG.
+- The `mtime` structure is optional. Its absence implies `unspecified` rather
+ than `0`.
+- For ergonomic reasons, a surface API of an encoder MUST allow fractional `0` as
+ input, while at the same time MUST ensure it is stripped from the final structure
+ before encoding, satisfying the above constraints.
+
+Implementations interpreting the `mtime` metadata in order to apply it within a
+non-IPFS target MUST observe the following:
+
+- If the target supports a distinction between `unspecified` and `0`/`1970-01-01T00:00:00Z`,
+ the distinction must be preserved within the target. For example, if no `mtime` structure
+ is available, a web gateway must **not** render a `Last-Modified:` header.
+- If the target requires an `mtime` ( e.g. a FUSE interface ) and no `mtime` is
+ supplied OR the supplied `mtime` falls outside of the targets accepted range:
+ - When no `mtime` is specified or the resulting `UnixTime` is negative:
+ implementations must assume `0`/`1970-01-01T00:00:00Z` (note that such values
+ are not merely academic: e.g. the OpenVMS epoch is `1858-11-17T00:00:00Z`)
+ - When the resulting `UnixTime` is larger than the targets range ( e.g. 32bit
+ vs 64bit mismatch), implementations must assume the highest possible value
+ in the targets range. In most cases, this would be `2038-01-19T03:14:07Z`.
+
+**Implementation guidance:**
+- When importing new data, implementations SHOULD NOT include the mtime field unless the user explicitly requests preserving timestamps
+ - Including mtime changes the root CID, causing unnecessary deduplication failures when timestamp differences are irrelevant
+- Implementations MUST be able to parse UnixFS nodes both with and without this field
+- When present during operations like copying, implementations SHOULD preserve this field
+
+## UnixFS Paths
+
+:::note
+Path resolution describes how IPFS systems traverse UnixFS DAGs. While path resolution
+behavior is mostly IPFS semantics layered over UnixFS data structures, certain UnixFS
+types (notably HAMTDirectory) define specific resolution algorithms as part of their
+data structure specification. Each UnixFS type includes a "Path Resolution" subsection
+documenting its specific requirements.
+:::
+
+Paths begin with a `