CARファイルの内容について
ヘッダーについて
CARファイル内のData部のブロックの中で基準になるブロックのCIDが格納されている。
{
"roots": [
{
"$link": "bafyreia5is2dgfsipq3qdj4bya7kb7cv32534sfwutiffh6kwgle4p26je"
}
],
"version": 1
}
CARファイルにはATPのレコード以外にデータ構造を示すアドレスブロック(仮称)やCARファイルの持ち主(アカウント)の情報も含まれ、ヘッダーが指している先はアカウント情報である(プロフィールとは別)。
前の記事で説明したとおりすべてのブロックにはCIDが付与されておりCIDでブロックを識別するが、ATPのレコードを納めているブロックのCIDはcom.atproto.repo.listRecords
やapp.bsky.feed.getTimeline
などで取得できるCIDと同じ値になる。しかし、アドレスブロックなどは別で付与されている(形式は同じ)。
アカウント情報
ヘッダーが指している先のブロックにはアカウント情報(と言ってもdidくらい)とレコードが保存されているブロックへの参照を含むアドレスブロックへのCIDが記述されている。
{
"data": {
"$link": "bafyreihke6kqud3ahlmldkr3snlnvrdbwcux6gu6e2s7awhtjpvm6t7i6i"
},
"did": "did:plc:mqxsuw5b5rhpwo4lw6iwlid5",
"prev": null,
"rev": "3krixkpky7p2u",
"sig": <binary data>,
"version": 3
}
アドレスブロック
アドレスブロックは木構造になって各レコードが保存されているブロックのCIDが含まれる。
{
"e": [
{
"k": "app.bsky.feed.post/3kqddm55vy22s",
"p": 0,
"t": {
"$link": "bafyreids7ivuirx3e7cqd3izw3i5rcrjdeisqx4kege3xxqhguakcvo5b4"
},
"v": {
"$link": "bafyreiafmh467ghcs3wztcsb32beap3k76rlchjfdiee5e2emsjssyq27a"
}
}
],
"l": {
"$link": "bafyreidmb6yn2jknpg7zlsrozji6wrfcnqntqjypz26ge3gu5mbgbkop44"
}
}
- e : 参照情報
- k : URIの後半(ATPのAPIで取得できるURIのdidから前半を除くレコード固有の部分)
- p : 前の要素のkと共通している範囲(eの中は配列になっているので前のkの使い回せる範囲を示すことでデータサイズを可能な限り小さくできるようにしていると思われる。データサイズにLEB128を使っているようにとにかく節約しようとしているっぽい)
- t : 子供になるアドレスブロックのCID
- v : ATPのレコードが保存されているブロックのCID(APTにおけるURIに対応するCID。APIで取得できるものと同じ)
- l : 次のアドレスブロックのCID(最後はnull)
eの要素が複数ある例
{
"e": [
{
"k": "app.bsky.feed.post/3k7w6vdtki32z",
"p": 0,
"t": {
"$link": "bafyreicseabcai6hbpeqxuofng2jovktjnj7cpo7ljczvne4vb7m3oc7eu"
},
"v": {
"$link": "bafyreiacr3d2mjkjfwh5zdldp2ucoh6ly4ihipweueoaapsxnpk4onxocu"
}
},
{
"k": "a3a4ban6z2w",
"p": 21,
"t": {
"$link": "bafyreib36dylftsjexozpa4o4qwcxnync25g2u5uj3wv5axnnpwvmkpn4y"
},
"v": {
"$link": "bafyreic2wmzushwy5botw34lxhf4s4g3opt6q6zn4e4zsnl7piee2m6tja"
}
},
{
"k": "gy7moh4pb2s",
"p": 21,
"t": {
"$link": "bafyreigw4ojiiceunwgns5e3ieosivduveapauevomhpumg5lcpvpslbfa"
},
"v": {
"$link": "bafyreigdbb64jbdh2wzo3tbttrwd77sqmfckxcp66vecg5a7ubucwsuwxm"
}
},
{
"k": "jwuk7uxcn2f",
"p": 21,
"t": {
"$link": "bafyreicnu2yt6ige24a3v2nfmlbjrruvmm3lvifc6t5b47hgm2m46bdus4"
},
"v": {
"$link": "bafyreiby7cjhpwdskguiutdvh64dzbqvupsdhpjaa77oroky6qltpko7ve"
}
}
],
"l": {
"$link": "bafyreifje7cx7scbr52a2lbbjwoghqramym4mb363wel32trcficxu32fa"
}
}
上記の例の2つ目は21文字分(app.bsky.feed.post/3k
)を使い回して下記のようにURIの後半を再構成する。
- 1つ目
k : app.bsky.feed.post/3k7w6vdtki32z p : 0 => app.bsky.feed.post/3k7w6vdtki32z ==> at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.feed.post/3k7w6vdtki32z
- 2つ目
k : a3a4ban6z2w p : 21 => app.bsky.feed.post/3ka3a4ban6z2w ==> at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.feed.post/3ka3a4ban6z2w
注意点としては使い回す範囲は毎要素で変わるので直前の要素の復元後URIを基準に再構成する。 使い回せる文字列が1文字も無ければ途中でもp=0となることがある。
レコードブロック
下記はレコードブロックの例です。
{
"$type": "app.bsky.feed.post",
"createdAt": "2023-07-13T14:36:19.294Z",
"langs": [
"ja"
],
"text": "てすてす"
}
このデータに対応するcom.atproto.repo.listRecords
で取得できるデータは下記になる。
{
"records": [
{
"uri": "at://did:plc:mqxsuw5b5rhpwo4lw6iwlid5/app.bsky.feed.post/3k2fx2rz5zy2m",
"cid": "bafyreid6qom3un3e2yw5rzwqcythkjeago2zrmvgecnvglq2og6hqoz54m",
"value": {
"text": "てすてす",
"$type": "app.bsky.feed.post",
"langs": [
"ja"
],
"createdAt": "2023-07-13T14:36:19.294Z"
}
}
]
}
この情報を復元するには各データの[ varint | CID | block ]
の形式で保存されているCID
とblock
とアドレスブロックに保存されているURI
を組み合わせる必要がある。
その他
アドレスブロックに親子関係があったり、ある程度の個数で分割されている理由は不明である。