A place to cache linked articles (think custom and personal wayback machine)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>How Dat Works (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="https://datprotocol.github.io/how-dat-works/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. How Dat Works (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="https://datprotocol.github.io/how-dat-works/">Source originale du contenu</a></h3>
  445. <p id="introduction"><strong>Dat is a protocol for sharing data between computers.</strong> Dat’s strengths are that data is hosted and distributed by many computers on the network, that it can work offline or with poor connectivity, that the original uploader can add or modify data while keeping a full history and that it can handle large amounts of data.</p>
  446. <div>
  447. <p>Dat is compelling because the people working on it have a dedication to user experience and ease-of-use. The software around Dat brings publishing within reach for people with a wide range of skills, not just technical. Although first designed with scientific data in mind, the Dat community is testing the waters and has begun to use it for websites, art, music releases, peer-to-peer chat programs and many other experiments.</p>
  448. <p>This guide is an in-depth tour through the bits and bytes of the Dat protocol, starting from a blank slate and ending with being able to download and share files with other peers running Dat. There will be enough detail for readers who are considering writing their own implementation of Dat, but if you are just curious how it works or want to learn from Dat’s design then I hope you will find this guide useful too!</p>
  449. </div>
  450. <aside class="basealign">
  451. <h2>More documentation about Dat</h2>
  452. </aside>
  453. <div class="feedback">
  454. <div>
  455. <h2>Feedback</h2>
  456. <p>This guide was published only a few days ago! There are probably lots of little improvements that will be obvious to you as you read through it but weren’t obvious to me as I wrote it. Please <strong><a href="https://goo.gl/forms/R22N7C3e0trxNn9h1">send feedback</a></strong> about what you liked, found confusing or would change.</p>
  457. <p><strong>New Zealanders:</strong> Would you be willing to read through this guide together, in person, with me the author? <a href="https://goo.gl/forms/VT1UcGgMPTziILxH2">More info.</a></p>
  458. </div>
  459. </div>
  460. <h1 id="urls" class="section"><span>URLs</span></h1>
  461. <p>To fetch a file in Dat you need to know its URL. Here is an example:</p>
  462. <p><svg class="smallmargin pagewidth">
  463. <text class="code" x="0" y="18"><tspan>dat://</tspan><tspan fill="#007fff">778f8d955175c92e4ced5e4f5563f69bfec0c86cc6f670352c457943666fe639</tspan><tspan>/dat_intro.gif</tspan></text>
  464. <path stroke="#93a0b6" strokewidth="1" fill="none" d="M0.5,24 v4.5 h60 v-4.5 m0,4.5 h640 v-4.5 m0,4.5 h140 v-4.5"/>
  465. <text y="44" text-anchor="middle"><tspan x="30">protocol</tspan><tspan x="30" dy="1.2em">identifier</tspan></text>
  466. <text y="44" text-anchor="middle"><tspan x="380">ed25519 public key</tspan><tspan x="380" dy="1.2em">(hexadecimal)</tspan></text>
  467. <text y="44" text-anchor="middle"><tspan x="770">optional suffix</tspan><tspan x="770" dy="1.2em">path to data within Dat</tspan></text>
  468. </svg>
  469. <ul>
  470. <li><strong>Protocol identifier.</strong> Makes Dat URLs easily recognizable. Dat-capable applications can register with the operating system to handle <code>dat://</code> links, like <a href="https://beakerbrowser.com/">Beaker</a> does. In Dat-specific applications the protocol identifier can be left off.</li>
  471. <li><strong>Public key.</strong> An ed25519 public key unique to this Dat, used by the author to create and update data within it. The public key enables you to discover other peers who have the data and is also verify that the data was not corrupted or tampered with as it passed through the network.</li>
  472. <li><strong>Suffix.</strong> Identifies specific data within this Dat. For most Dats which contain a directory of files, the suffix is a slash-separated file path. Dats can also contain data in structures that don’t use the concept of files or directories, in which case the suffix would use some other format as understood by the applications that handle that sort of data.</li>
  473. </ul>
  474. <aside class="impl">
  475. <img class="icon" src="png/spanner_icon.png"/>
  476. <h3>Implementations</h3>
  477. <p class="lang">JS</p></p>
  478. </aside>
  479. <h1 id="discovery" class="section"><span>Discovery</span></h1>
  480. <p>Dat clients use several different methods for discovering peers who they can download data from. Each discovery method has strengths and weaknesses, but combined they form a reasonably robust way of finding peers.</p>
  481. <h2 id="discovery-keys">Discovery keys</h2>
  482. <div>
  483. <p>Discovery keys are used for finding other peers who are interested in the same Dat as you.</p>
  484. <p>If you know a Dat’s public key then you can calculate the discovery key easily, however if you only know a discovery key you cannot work backwards to find the corresponding public key. This prevents eavesdroppers learning of Dat URLs (and therefore being able to read their contents) by observing network traffic.</p>
  485. </div>
  486. <aside class="impl">
  487. <img class="icon" src="png/spanner_icon.png"/>
  488. <h3>Implementations</h3>
  489. <p class="lang">JS</p>
  490. </aside>
  491. <p>However eavesdroppers can confirm that peers are talking about a specific Dat and read all communications between those peers if they know its public key already. Eavesdroppers who do not know the public key can still get an idea of how many Dats are popular on the network, their approximate sizes, which IP addresses are interested in them and potentially the IP address of the creator by observing handshakes, traffic timing and volumes. Dat makes no attempt to hide IP addresses.</p>
  492. <p>Calculate a Dat’s discovery key using the <strong>BLAKE2b</strong> hashing function, keyed with the public key (as 32 bytes, not 64 hexadecimal characters), to hash the word “hypercore”:</p>
  493. <aside class="basealign">Dat uses the BLAKE2b variant that accepts both a key and input to be hashed, returning 256 bits (32 bytes) of output.</aside>
  494. <p><img id="blake2b" src="png/blake2b.png"/>
  495. <aside>
  496. <h3>Byte notation</h3>
  497. <img src="png/notation_byte.png"/>
  498. <p>Throughout this guide bytes are shown as a number inside a square. The number is always in decimal (base‑10) and can range from 0 to 255.</p>
  499. </aside></p>
  500. <h2 id="local-network-discovery">Local network discovery</h2>
  501. <div>
  502. <p>Peers broadcast which Dats they are interested in via their local network.</p>
  503. <ul>
  504. <li><strong>Strengths.</strong> Fast, finds physically nearby peers, doesn’t need special infrastructure, works offline.</li>
  505. <li><strong>Weaknesses.</strong> Limited reach.</li>
  506. <li><strong>Deployment status.</strong> Currently in use, will be replaced by <a href="https://pfrazee.hashbase.io/blog/hyperswarm">Hyperswarm</a> in the future.</li>
  507. </ul>
  508. </div>
  509. <aside class="impl">
  510. <img class="icon" src="png/spanner_icon.png"/>
  511. <h3>Implementations</h3>
  512. <p class="lang">JS</p>
  513. </aside>
  514. <p>Local network discovery uses multicast DNS, which is like a regular DNS query except instead of sending queries to a nameserver they are broadcast to the local network with the hope that someone else on the network sees it and responds.</p>
  515. <figure id="mdns-request" class="pagewidth">
  516. <figcaption>Client asking for peers</figcaption>
  517. <img src="png/mdns_request.png"/>
  518. </figure>
  519. <p>Multicast DNS packets are sent to the special broadcast MAC and IP addresses shown above. Both the source and destination ports are 5353.</p>
  520. <p>Essentially the computer is asking “Does anybody have any TXT records for the domain name <em>25a78aa81615847eba00995df29dd41d7ee30f3b.dat.local</em>?” Other Dat clients on the network will recognize requests following this pattern and know that the client who sent it is looking for peers.</p>
  521. <figure id="mdns-response" class="pagewidth">
  522. <figcaption>Peer reporting that they are also interested in this Dat</figcaption>
  523. <img src="png/mdns_response.png"/>
  524. </figure>
  525. <p id="mdns-peers-record">Responses contain two TXT records:</p>
  526. <ul>
  527. <li>The <strong>token</strong> record is a random value that makes it easier for clients to avoid connecting to themselves. If a client sees a response with the same token as a response they just sent out, they will know it came from them and ignore it.</li>
  528. <li>The <strong>peers</strong> record is a base64-encoded list of IP addresses and ports of peers interested in this Dat:</li>
  529. </ul>
  530. <aside class="impl">
  531. <img class="icon" src="png/spanner_icon.png"/>
  532. <h3>Implementations</h3>
  533. <p class="lang">JS</p>
  534. </aside>
  535. <div>
  536. <img src="png/mdns_peers_record.png"/>
  537. <p>The special IP address 0.0.0.0 means “use the address this mDNS response came from”. When discovering peers on the local network all mDNS responses will contain only one peer and will use the 0.0.0.0 address.</p>
  538. </div>
  539. <aside>
  540. <p>Base64 encoding in Dat uses the variant with plus <code>+</code> and slash <code>/</code> characters. Padding equals <code>=</code> characters are required.</p>
  541. <p>Only IPv4 addresses are supported by this discovery mechanism.</p>
  542. <h3>Multi-byte numbers</h3>
  543. <p>Port numbers go from 0 to 65,535 which is larger than can fit inside a single byte, so in this case two bytes are used.</p>
  544. <p>The first byte is how many 256’s there are and the second byte is how many ones there are:</p>
  545. <img src="png/notation_multi_byte_number.png"/>
  546. <p>In the Dat protocol multi-byte numbers are big-endian meaning the most significant byte comes first.</p>
  547. </aside>
  548. <h2 id="centralized-dns-discovery">Centralized DNS discovery</h2>
  549. <p>Peers ask a server on the internet for other peers using a DNS-based protocol.</p>
  550. <ul>
  551. <li><strong>Strengths.</strong> Fast, global reach.</li>
  552. <li><strong>Weaknesses.</strong> Must be online, centralized point of failure, one server sees everyone’s metadata.</li>
  553. <li><strong>Deployment status.</strong> Currently in use, will be replaced by <a href="https://pfrazee.hashbase.io/blog/hyperswarm">Hyperswarm</a> in the future.</li>
  554. </ul>
  555. <p>Currently the server running this is <strong>discovery1.datprotocol.com</strong>. If that goes offline then <strong>discovery2.datprotocol.com</strong> can be used as a fallback.</p>
  556. <p>Here is a typical message flow between a Dat peer and the DNS discovery server:</p>
  557. <p><img class="pagewidth" src="png/centralized_dns_conversation.png"/>
  558. <p>To stay subscribed, peers should re-announce themselves every 60 seconds. The discovery server will also cycle its tokens periodically so peers should remember the token they last received and update it when the receive a new one.</p>
  559. <p id="centralized-dns-peers-record">The <em>peers</em> record returned by the discovery server uses the same structure as in mDNS:</p>
  560. <img src="png/mdns_peers_record_multi.png"/>
  561. <aside>In this case the server sent back a list of five peers. DNS TXT records are limited to 255 characters so the server is limited to sending back 31 peers at a time. If the server knows more than this it will have to choose which to send, for example the most recent, longest lived or by picking at random.</aside>
  562. <p>Following are three examples showing how these DNS requests appear as bytes sent over the network:</p>
  563. <figure id="centralized-dns-request">
  564. <figcaption>Peer announce request to discovery server</figcaption>
  565. <img src="png/centralized_dns_request.png"/>
  566. </figure>
  567. <figure id="centralized-dns-response">
  568. <figcaption>Discovery server response to announce</figcaption>
  569. <img src="png/centralized_dns_response.png"/>
  570. </figure>
  571. <figure id="centralized-dns-srv">
  572. <figcaption>Discovery server SRV push notification</figcaption>
  573. <img src="png/centralized_dns_srv.png"/>
  574. </figure></p>
  575. <h1 id="wire-protocol" class="section"><span>Wire protocol</span></h1>
  576. <p>Once a peer has discovered another peer’s IP address and port number it will open a TCP connection to the other peer. Each half of the conversation has this structure which repeats until the end of the connection:</p>
  577. <aside class="impl">
  578. <img class="icon" src="png/spanner_icon.png"/>
  579. <h3>Implementations</h3>
  580. <p class="lang">JS</p>
  581. </aside>
  582. <p><img class="pagewidth" src="png/wire_protocol_structure.png"/>
  583. <ul>
  584. <li><strong>Length.</strong> Number of bytes until the start of the next length field.</li>
  585. <li><strong>Channel and type.</strong> A single number (up to 11 bits long) that encodes two sub-fields as:</li>
  586. <img src="png/channel_type_field.png"/>
  587. <ul>
  588. <li><strong>Channel.</strong> Peers can talk about multiple Dats using the same TCP connection. The channel number is 0 for the first Dat talked about, 1 for the next Dat and so on.</li>
  589. <li id="message-type"><strong>Type.</strong> Number that says what the purpose of the message is.</li>
  590. <table>
  591. <thead>
  592. <tr>
  593. <th>Type</th>
  594. <th>Name</th>
  595. <th>Meaning</th>
  596. </tr>
  597. </thead>
  598. <tbody>
  599. <tr>
  600. <td>0</td>
  601. <td><strong>Feed</strong></td>
  602. <td>I want to talk to you about this particular Dat</td>
  603. </tr>
  604. <tr>
  605. <td>1</td>
  606. <td><strong>Handshake</strong></td>
  607. <td>I want to negotiate how we will communicate on this TCP connection</td>
  608. </tr>
  609. <tr>
  610. <td>2</td>
  611. <td><strong>Info</strong></td>
  612. <td>I am either starting or stopping uploading or downloading</td>
  613. </tr>
  614. <tr>
  615. <td>3</td>
  616. <td><strong>Have</strong></td>
  617. <td>I have some data that you said you wanted</td>
  618. </tr>
  619. <tr>
  620. <td>4</td>
  621. <td><strong>Unhave</strong></td>
  622. <td>I no longer have some data that I previously said I had (alternatively: I didn’t store that data you just sent, please stop sending me data preemptively)</td>
  623. </tr>
  624. <tr>
  625. <td>5</td>
  626. <td><strong>Want</strong></td>
  627. <td>This is what data I want</td>
  628. </tr>
  629. <tr>
  630. <td>6</td>
  631. <td><strong>Unwant</strong></td>
  632. <td>I no longer want this data</td>
  633. </tr>
  634. <tr>
  635. <td>7</td>
  636. <td><strong>Request</strong></td>
  637. <td>Please send me this data now</td>
  638. </tr>
  639. <tr>
  640. <td>8</td>
  641. <td><strong>Cancel</strong></td>
  642. <td>Actually, don’t send me that data</td>
  643. </tr>
  644. <tr>
  645. <td>9</td>
  646. <td><strong>Data</strong></td>
  647. <td>Here is the data you requested</td>
  648. </tr>
  649. <tr>
  650. <td>10–14</td>
  651. <td/>
  652. <td><em>(Unused)</em></td>
  653. </tr>
  654. <tr>
  655. <td>15</td>
  656. <td><strong>Extension</strong></td>
  657. <td>Some other message that is not part of the core protocol</td>
  658. </tr>
  659. </tbody>
  660. </table>
  661. </ul>
  662. <li><strong>Body.</strong> Contents of the message.</li>
  663. </ul>
  664. <aside>
  665. <h3>Bit notation</h3>
  666. <p>In several parts of the Dat protocol multiple fields are packed into a single number. It helps to look at the number as a sequence of bits because this makes the fields visible.</p>
  667. <img src="png/notation_bits.png"/>
  668. <p>Throughout this guide bit sequences are shown as 1’s and 0’s in a box, grouped into fields. The most significant bit is always on the left.</p>
  669. <p>Eight bits make up a byte, however this number and many others are varints which can be up to 64 bits long. The fields on the right are always a fixed number of bits but the leftmost field can take up as many of the remaining 64 bits as it needs.</p>
  670. </aside></p>
  671. <h2 id="varints">Varints</h2>
  672. <div>
  673. <p>The first two fields are encoded as variable-length integers and therefore do not have a fixed size. You must read each field starting from the beginning to determine how long the field is and where the next field starts.</p>
  674. <p>The advantage of varints is that they only require a few bytes to represent small numbers, while still being able to represent large numbers by using more bytes. The disadvantage of varints is that they take more work to encode and decode compared to regular integers.</p>
  675. </div>
  676. <aside>
  677. <div class="impl">
  678. <img class="icon" src="png/spanner_icon.png"/>
  679. <h3>Implementations</h3>
  680. <p class="lang">JS</p>
  681. <p class="lang">Rust</p>
  682. </div>
  683. <p>In Dat, varints are between 1 and 10 bytes long and represent integers from 0 to 2<sup>64</sup> - 1. Negative numbers aren’t used.</p>
  684. </aside>
  685. <p>Here’s how to decode a varint:</p>
  686. <p><img class="pagewidth" src="png/varint.png"/></p>
  687. <h2 id="keepalive">Keepalive</h2>
  688. <p>Keepalive messages are empty messages containing no channel number, type or body. They are discarded upon being received. Sending keepalives is necessary when there is a network middlebox that kills TCP connections which haven’t sent any data in a while. In these cases each peer periodically sends keepalive messages when no other data is being sent.</p>
  689. <aside>How frequently to send keepalive messages is essentially a guess based on what types of middleboxes are commonly used on the internet today. Other TCP-based protocols typically send keepalives every 30 to 120 seconds.</aside>
  690. <p>Here’s an example of several keepalive messages interleaved with messages containing actual data. Each keepalive message is a single byte of zero:</p>
  691. <p><img class="pagewidth" src="png/wire_protocol_keepalive.png"/></p>
  692. <h2 id="message-structure">Message structure</h2>
  693. <p>Within each message body is a series of field tags and values:</p>
  694. <p><img class="pagewidth" src="png/wire_protocol_field_value_structure.png"/>
  695. <p id="message-structure-field-tag">The field tag is a varint. The most significant bits indicate which field within the message this is, for example: 1 = <em>discovery key</em>, 2 = <em>nonce</em>. This is needed because messages can have missing or repeated fields. The 3 least significant bits are the type of field.</p>
  696. <img src="png/field_type_field.png"/>
  697. <p id="message-field-types">The two types of field are:</p>
  698. <ul>
  699. <li><strong>Varint.</strong> The field tag followed by a varint value. Used for simple numeric values and booleans.</li>
  700. <img src="png/field_structure_varint.png"/>
  701. <li><strong>Length-prefixed.</strong> The field tag followed by a varint to say how many bytes the field contains, followed by the bytes themselves. Used for strings, bytes and embedded messages.</li>
  702. <img src="png/field_structure_length_prefixed.png"/>
  703. </ul>
  704. <aside class="basealign">
  705. <p>If you are familiar with the <a href="https://developers.google.com/protocol-buffers/">Protocol Buffers</a> data serialization format you might recognize that this message structure adheres to it. Dat only uses a small subset of the features available with Protocol Buffers, however if you are writing an implementation you may be interested in the <a href="https://github.com/mafintosh/hypercore-protocol/blob/master/schema.proto">.proto file</a> that describes the wire protocol.</p>
  706. <p>Over time new fields could be added to messages in the wire protocol. Field tags enable this to happen in a backwards-compatible way. If your implementation sees a field number that it doesn’t know about it can use the field type to find out how long the unknown field is and skip over it.</p>
  707. <div class="impl">
  708. <img class="icon" src="png/spanner_icon.png"/>
  709. <h3>Implementations</h3>
  710. <p class="lang">JS</p></p>
  711. <p></div>
  712. </aside></p>
  713. <h2 id="feed-message">Feed message</h2>
  714. <p>After opening the TCP connection the first message is always a <strong>feed</strong> message.</p>
  715. <p>Feed messages have two fields:</p>
  716. <div class="pagewidth msgfields">
  717. <table>
  718. <thead>
  719. <tr>
  720. <th>No.</th>
  721. <th>Name</th>
  722. <th>Type</th>
  723. <th>Description</th>
  724. </tr>
  725. </thead>
  726. <tbody>
  727. <tr>
  728. <td>1</td>
  729. <td><strong>Discovery key</strong></td>
  730. <td>Length-prefixed</td>
  731. <td>32-byte discovery key for this Dat.</td>
  732. </tr>
  733. <tr>
  734. <td>2</td>
  735. <td><strong>Nonce</strong></td>
  736. <td>Length-prefixed</td>
  737. <td>24-byte random nonce generated for this TCP connection. Only present for the first feed message.</td>
  738. </tr>
  739. </tbody>
  740. </table>
  741. </div>
  742. <p>Putting everything together, this is how each side of the TCP connection begins:</p>
  743. <p><img class="pagewidth" src="png/feed_message.png"/>
  744. <p>Or, as the bytes actually sent over the wire:</p>
  745. <img id="feed-message-bytes" class="pagewidth" src="png/feed_message_bytes.png"/></p>
  746. <h2 id="encryption">Encryption</h2>
  747. <div>
  748. <p>Each side of the TCP connection is encrypted starting from the second message and continuing until the end of the connection. This prevents network eavesdroppers from finding out what data a Dat contains unless they already know its public key.</p>
  749. <p><strong>XSalsa20</strong> is the encryption cipher used. Given a 32-byte key and a 24-byte nonce, XSalsa20 produces a never-ending stream of pseudorandom bytes called the keystream.</p>
  750. </div>
  751. <aside class="basealign">The encryption scheme is not authenticated, meaning a man-in-the-middle can flip bits to disrupt the connection. This is mitigated because data integrity in a Dat is verified using a separate mechanism. If there is an attacker who is in a position to modify bits on the network they have lots of other options for disrupting connections, for example by simply dropping packets.</aside>
  752. <p><img class="pagewidth" src="png/encryption.png"/>
  753. <p id="encryption-sender">The <strong>sender</strong> generates a random 24-byte value for the nonce and includes it in their first message (which is always a feed message). The Dat’s 32-byte public key is used as the XSalsa20 key. From the second message onwards all bytes they send are XORed with the keystream.</p>
  754. <aside class="impl">
  755. <img class="icon" src="png/spanner_icon.png"/>
  756. <h3>Implementations</h3>
  757. <p class="lang">JS</p></p>
  758. <p></aside>
  759. <img class="pagewidth" src="png/encryption_send.png"/>
  760. <p id="encryption-receiver">The <strong>receiver</strong> reads the nonce from the sender’s first message and uses this with their knowledge of the Dat’s public key to set up an identical XSalsa20 keystream. Then they XOR the keystream with the bytes received to decrypt the stream.</p>
  761. <img class="pagewidth" src="png/encryption_receive.png"/></p>
  762. <h2 id="handshake-message">Handshake message</h2>
  763. <p>After the initial feed message, the second message sent on each side of the TCP connection is always a <strong>handshake</strong> message.</p>
  764. <div class="pagewidth msgfields">
  765. <table>
  766. <thead>
  767. <tr>
  768. <th>No.</th>
  769. <th>Name</th>
  770. <th>Type</th>
  771. <th>Description</th>
  772. </tr>
  773. </thead>
  774. <tbody>
  775. <tr>
  776. <td>1</td>
  777. <td><strong>ID</strong></td>
  778. <td>Length-prefixed</td>
  779. <td>Random ID generated by this peer upon starting, 32 bytes long. Used to detect multiple connections to the same peer or accidental connections to itself.</td>
  780. </tr>
  781. <tr>
  782. <td>2</td>
  783. <td><strong>Live</strong></td>
  784. <td>Varint</td>
  785. <td>0 = End the connection when neither peer is downloading, 1 = Keep the connection open indefinitely (only takes effect if both peers set to 1).</td>
  786. </tr>
  787. <tr>
  788. <td>3</td>
  789. <td><strong>User data</strong></td>
  790. <td>Length-prefixed</td>
  791. <td>Arbitrary bytes that can be used by higher-level applications for any purpose. Remember that any data put in here is not protected from tampering as it passes through the network. Additionally, any eavesdropper who knows the Dat’s public key can read this.</td>
  792. </tr>
  793. <tr>
  794. <td>4</td>
  795. <td><strong>Extensions</strong></td>
  796. <td>Length-prefixed</td>
  797. <td>Names of any extensions the peer wants to use, for example: “session-data”. This field can appear multiple times, one for each extension. Both peers need to request an extension in their handshake messages for it to become active.</td>
  798. </tr>
  799. <tr>
  800. <td>5</td>
  801. <td><strong>Acknowledge</strong></td>
  802. <td>Varint</td>
  803. <td>0 = No need to acknowledge each chunk of data received, 1 = Must acknowledge each chunk of data received.</td>
  804. </tr>
  805. </tbody>
  806. </table>
  807. </div>
  808. <h1 id="data-model" class="section"><span>Data model</span></h1>
  809. <p>After completing the handshake peers begin requesting data from each other. Dats contain a list of variable-sized chunks of bytes. New chunks can be added to the end by the Dat’s author, but existing chunks can’t be deleted or modified.</p>
  810. <aside>Boundaries between chunks are preserved. This can be useful for dividing up the Dat into a series of messages, for example one message per chunk.</aside>
  811. <p><img src="png/chunk_structure.png"/>
  812. <p>Hashes are used to verify the integrity of data within a Dat. Each chunk of data has a corresponding hash. There are also parent hashes which verify the integrity of two other hashes. Parent hashes form a tree structure. In this example the integrity of all the data can be verified if you know hash number 3:</p>
  813. <img id="chunk-structure-hashes" src="png/chunk_structure_hashes.png"/>
  814. <aside>
  815. <p>Chunk hashes are even-numbered. Parent hashes are odd-numbered.</p>
  816. <p>Hash trees allow downloaders to download and verify a specific chunk without needing to also download hashes of every other chunk.</p>
  817. </aside>
  818. <p>Each time the author adds new chunks they calculate a root hash and sign it with the Dat’s secret key. Downloaders can use the Dat’s public key to verify the signature, which in turn verifies the integrity of all the other hashes and chunks.</p>
  819. <img id="chunk-structure-signature" src="png/chunk_structure_signature.png"/>
  820. <p>Depending on the number of chunks, the root hash can have more than one input. The root hash combines as many parent or chunk hashes as necessary to cover all the chunks. Here is how the hash tree looks with different numbers of chunks:</p>
  821. <img id="hash-tree-1" class="pagewidth" src="png/hash_tree_1.png"/>
  822. <img id="hash-tree-2" class="pagewidth" src="png/hash_tree_2.png"/></p>
  823. <h2 id="hashes-and-signatures">Hashes and signatures</h2>
  824. <div>
  825. <p>The three types of hash seen in the hash tree are:</p>
  826. <ul>
  827. <li><strong>Chunk hashes</strong>, which hash the contents of a single chunk.</li>
  828. <li><strong>Parent hashes</strong>, which hash two other hashes forming a tree structure.</li>
  829. <li><strong>Root hashes</strong>, which sit at the root of the tree and are signed by the Dat’s author.</li>
  830. </ul>
  831. </div>
  832. <aside class="impl">
  833. <img class="icon" src="png/spanner_icon.png"/>
  834. <h3>Implementations</h3>
  835. <p class="lang">JS</p>
  836. <p class="lang">Rust</p>
  837. </aside>
  838. <p>Each type of hash has a specific way to construct it:</p>
  839. <h1 id="exchanging-data" class="section"><span>Exchanging data</span></h1>
  840. <p>Peers exchange chunks in multi-step process where the downloader and uploader negotiate what chunks they want and have:</p>
  841. <p><img src="png/chunk_conversation.png"/>
  842. <aside>
  843. <p>In this example one peer is a downloader and the other is an uploader. In other cases both peers will be uploading and downloading at the same time as they each have some of the chunks but not all of them.</p>
  844. <p>Each request message can only request a single chunk per message. Downloaders may want to queue up several request messages at a time. When the uploader finishes sending a chunk this lets them immediately start sending the next one without waiting for a round-trip.</p>
  845. </aside></p>
  846. <h2 id="want-and-have">Want and have</h2>
  847. <p>Each peer remembers which chunks the other peer wants and has.</p>
  848. <ul>
  849. <li><strong>Wanting</strong> a chunk means “I want to download this chunk, please tell me if you have it”.</li>
  850. <li><strong>Having</strong> a chunk means “I know you want this chunk and I will send it if you ask for it”. If a peer tells you they are only interested in a small range of chunks then you only have to tell them about chunks within that range.</li>
  851. </ul>
  852. <p>As peers download (or even delete) data, the list of chunks they want and have will change. This state is communicated with four message types: <strong>want</strong>, <strong>unwant</strong>, <strong>have</strong> and <strong>unhave</strong>. Each of these four messages has the same structure which indicates a contiguous range of chunks:</p>
  853. <div class="pagewidth msgfields">
  854. <table>
  855. <thead>
  856. <tr>
  857. <th>No.</th>
  858. <th>Name</th>
  859. <th>Type</th>
  860. <th>Description</th>
  861. </tr>
  862. </thead>
  863. <tbody>
  864. <tr>
  865. <td>1</td>
  866. <td><strong>Start</strong></td>
  867. <td>Varint</td>
  868. <td>Number of the first chunk you want/unwant/have/unhave. Chunk numbering starts at 0.</td>
  869. </tr>
  870. <tr>
  871. <td>2</td>
  872. <td><strong>Length</strong></td>
  873. <td>Varint</td>
  874. <td>1 = Just the <em>start</em> chunk, 2 = The <em>start</em> chunk and the next one, and so on. Omit this field to select all following chunks to the end of the Dat, including new chunks as they are added.</td>
  875. </tr>
  876. </tbody>
  877. </table>
  878. </div>
  879. <p>Here is an example showing typical use of have and want messages between two peers:</p>
  880. <p><img id="want-have-conversation-1" class="pagewidth" src="png/want_have_1.png"/>
  881. <img id="want-have-conversation-2" class="pagewidth" src="png/want_have_2.png"/></p>
  882. <h2 id="have-bitfield">Have bitfield</h2>
  883. <div>
  884. <p>If you have lots of little, non-contiguous ranges of data it can take a lot of have messages to tell your peer exactly what you have. There is an alternate form of the have message for this purpose. It is efficient at representing both contiguous and non-contiguous ranges of data.</p>
  885. <p>This form of the have message only has one field:</p>
  886. </div>
  887. <aside class="impl">
  888. <img class="icon" src="png/spanner_icon.png"/>
  889. <h3>Implementations</h3>
  890. <p class="lang">JS</p>
  891. <p class="lang">Rust</p>
  892. </aside>
  893. <div class="pagewidth msgfields">
  894. <table>
  895. <thead>
  896. <tr>
  897. <th>No.</th>
  898. <th>Name</th>
  899. <th>Type</th>
  900. <th>Description</th>
  901. </tr>
  902. </thead>
  903. <tbody>
  904. <tr>
  905. <td>3</td>
  906. <td><strong>Bitfield</strong></td>
  907. <td>Length-prefixed</td>
  908. <td>A sequence of contiguous and non-contiguous chunk ranges.</td>
  909. </tr>
  910. </tbody>
  911. </table>
  912. </div>
  913. <p>For example, let’s look at the chunks this peer has. Normally this would take 11 messages to represent:</p>
  914. <p><img class="pagewidth" src="png/run_length_encoding_bitfield.png"/>
  915. <p>Instead, divide the chunks into ranges where each range is either contiguous (all chunks present or none present), or non-contiguous (some chunks present). The ranges must be multiples of 8 chunks long.</p>
  916. <aside>In this case the ranges alternate between contiguous and non-contiguous, but this does not always happen. It is possible for contiguous ranges to be next to each other if one has all chunks present and the other has no chunks present.</aside>
  917. <img class="pagewidth" src="png/run_length_encoding_bitfield_segment.png"/>
  918. <p id="have-bitfield-range-types">Ranges are encoded as:</p>
  919. <ul>
  920. <li><strong>Contiguous.</strong> A single varint that contains sub-fields saying how many 8-chunk spans there are and whether all the chunks are present or absent.</li>
  921. <img src="png/run_length_encoding_contiguous_range.png"/>
  922. <li><strong>Non-contiguous.</strong> A varint saying how many 8-chunk spans the following bitfield represents (which is also its length in bytes), followed by the bitfield.</li>
  923. <img src="png/run_length_encoding_non_contiguous_range.png"/>
  924. </ul>
  925. <aside>The first byte in the bitfield represents the first 8-chunk span. The most significant bit of each byte represents the first chunk of each 8-chunk span.</aside>
  926. <p>Putting everything together, here are the bits used to encode the chunks this peer has:</p>
  927. <img id="have-bitfield-bits" class="pagewidth" src="png/run_length_encoding_bitfield_bits.png"/>
  928. <p>And here is how the final have message would appear on the wire:</p>
  929. <img id="have-bitfield-bytes" class="pagewidth" src="png/have_message_bytes.png"/></p>
  930. <h2 id="requesting-data">Requesting data</h2>
  931. <p>Once your peer has told you that they have a chunk you want, send a <strong>request</strong> message to ask them for it:
  932. </p>
  933. <div class="pagewidth msgfields">
  934. <table>
  935. <thead>
  936. <tr>
  937. <th>No.</th>
  938. <th>Name</th>
  939. <th>Type</th>
  940. <th>Description</th>
  941. </tr>
  942. </thead>
  943. <tbody>
  944. <tr>
  945. <td>1</td>
  946. <td><strong>Index</strong></td>
  947. <td>Varint</td>
  948. <td>Number of the chunk to send back. This field must be present even when using the <em>bytes</em> field below.</td>
  949. </tr>
  950. <tr>
  951. <td>2</td>
  952. <td><strong>Bytes</strong></td>
  953. <td>Varint</td>
  954. <td>If this field is present, ignore the <em>index</em> field and send back the chunk containing this byte. Useful if you don’t know how big each chunk is but you want to seek to a specific byte.</td>
  955. </tr>
  956. <tr>
  957. <td>3</td>
  958. <td><strong>Hash</strong></td>
  959. <td>Varint</td>
  960. <td>0 = Send back the data in this chunk as well as hashes needed to verify it, 1 = Don’t send back the data in this chunk, only send the hashes.</td>
  961. </tr>
  962. <tr>
  963. <td>4</td>
  964. <td><strong>Nodes</strong></td>
  965. <td>Varint</td>
  966. <td>Used to request additional hashes needed to verify the integrity of this chunk. 0 = Send back all hashes needed to verify this chunk, 1 = Just send the data, no hashes. For other values that can be used to request specific hashes from the hash tree, see the <a href="https://github.com/pfrazee/DEPs/blob/dep-wire-protocol/proposals/0000-wire-protocol.md#block-tree-digest">Wire Protocol specification</a>.</td>
  967. </tr>
  968. </tbody>
  969. </table>
  970. </div>
  971. <p id="cancel-message">If you no longer want a chunk you requested, send a <strong>cancel</strong> message:</p>
  972. <div class="pagewidth msgfields">
  973. <table>
  974. <thead>
  975. <tr>
  976. <th>No.</th>
  977. <th>Name</th>
  978. <th>Type</th>
  979. <th>Description</th>
  980. </tr>
  981. </thead>
  982. <tbody>
  983. <tr>
  984. <td>1</td>
  985. <td><strong>Index</strong></td>
  986. <td>Varint</td>
  987. <td>Number of the chunk to cancel. This field must be present even when using the <em>bytes</em> field below.</td>
  988. </tr>
  989. <tr>
  990. <td>2</td>
  991. <td><strong>Bytes</strong></td>
  992. <td>Varint</td>
  993. <td>If this field is present, ignore the <em>index</em> field and cancel the request for the chunk containing this byte.</td>
  994. </tr>
  995. <tr>
  996. <td>3</td>
  997. <td><strong>Hash</strong></td>
  998. <td>Varint</td>
  999. <td>Set to the same value as the <em>hash</em> field of the request you want to cancel.</td>
  1000. </tr>
  1001. </tbody>
  1002. </table>
  1003. </div>
  1004. <p>Cancel messages can be used if you preemptively requested a chunk from multiple peers at the same time. Upon receiving the chunk from the fastest peer, send cancel messages to the others.</p>
  1005. <p id="data-message">When a peer has requested a chunk from you, send it to them with a <strong>data</strong> message:</p>
  1006. <div class="pagewidth msgfields">
  1007. <table>
  1008. <thead>
  1009. <tr>
  1010. <th>No.</th>
  1011. <th>Name</th>
  1012. <th>Type</th>
  1013. <th>Description</th>
  1014. </tr>
  1015. </thead>
  1016. <tbody>
  1017. <tr>
  1018. <td>1</td>
  1019. <td><strong>Index</strong></td>
  1020. <td>Varint</td>
  1021. <td>Chunk number.</td>
  1022. </tr>
  1023. <tr>
  1024. <td>2</td>
  1025. <td><strong>Value</strong></td>
  1026. <td>Length-prefixed</td>
  1027. <td>Contents of the chunk. Do not set this field if the request had <em>hash</em> = 1.</td>
  1028. </tr>
  1029. <tr>
  1030. <td>3</td>
  1031. <td><strong>Nodes</strong></td>
  1032. <td>Length-prefixed</td>
  1033. <td>
  1034. <p>This field is repeated for each hash that the requester needs to verify the chunk’s integrity.</p>
  1035. <table>
  1036. <thead>
  1037. <tr>
  1038. <th>No.</th>
  1039. <th>Name</th>
  1040. <th>Type</th>
  1041. <th>Description</th>
  1042. </tr>
  1043. </thead>
  1044. <tbody>
  1045. <tr>
  1046. <td>1</td>
  1047. <td><strong>Index</strong></td>
  1048. <td>Varint</td>
  1049. <td>Hash number.</td>
  1050. </tr>
  1051. <tr>
  1052. <td>2</td>
  1053. <td><strong>Hash</strong></td>
  1054. <td>Length-prefixed</td>
  1055. <td>32-byte <a href="#chunk-hash">chunk hash</a> or <a href="#parent-hash">parent hash</a>.</td>
  1056. </tr>
  1057. <tr>
  1058. <td>3</td>
  1059. <td><strong>Size</strong></td>
  1060. <td>Varint</td>
  1061. <td>Total length of data in chunks covered by this hash.</td>
  1062. </tr>
  1063. </tbody>
  1064. </table>
  1065. </td>
  1066. </tr>
  1067. <tr>
  1068. <td>4</td>
  1069. <td><strong>Signature</strong></td>
  1070. <td>Length-prefixed</td>
  1071. <td>64-byte ed25519 signature of the root hash corresponding to this chunk.</td>
  1072. </tr>
  1073. </tbody>
  1074. </table>
  1075. </div>
  1076. <h1 id="files-and-folders" class="section"><span>Files and folders</span></h1>
  1077. <p>Dat uses two coupled feeds to represent files and folders. The <strong>metadata</strong> feed contains the names, sizes and other metadata for each file, and its typically quite small even when the Dat contains a lot of data. The <strong>content</strong> feed contains the actual file contents. The metadata feed points to where in the content feed each file is located, so you only need to fetch the contents of files you are interested in.</p>
  1078. <aside>Folders aren’t created explicitly. Instead, files have slash-separated names and all but the last path segment represents the folders that file is inside. Dat can’t represent an empty folder or remember UIDs, permission modes or modification dates on folders.</aside>
  1079. <p><img class="pagewidth" src="png/files_folders_overview.png"/>
  1080. <p>The first chunk of the metadata feed is always an <strong>index</strong> chunk. Check that the <em>type</em> field contains the word “hyperdrive”. If so, the <em>content</em> field is the public key of the content feed.</p>
  1081. <div class="pagewidth msgfields">
  1082. <table>
  1083. <thead>
  1084. <tr>
  1085. <th>No.</th>
  1086. <th>Name</th>
  1087. <th>Type</th>
  1088. <th>Description</th>
  1089. </tr>
  1090. </thead>
  1091. <tbody>
  1092. <tr>
  1093. <td>1</td>
  1094. <td><strong>Type</strong></td>
  1095. <td>Length-prefixed</td>
  1096. <td>What sort of data is contained in this Dat. For Dats using the concept of files and folders this is “hyperdrive”.</td>
  1097. </tr>
  1098. <tr>
  1099. <td>2</td>
  1100. <td><strong>Content</strong></td>
  1101. <td>Length-prefixed</td>
  1102. <td>32-byte public key of the content feed.</td>
  1103. </tr>
  1104. </tbody>
  1105. </table>
  1106. </div>
  1107. <p>After the index all following chunks in the metadata feed are <strong>nodes</strong>, which store file metadata. Nodes have these fields:</p>
  1108. <div id="node-fields" class="pagewidth msgfields">
  1109. <table>
  1110. <thead>
  1111. <tr>
  1112. <th>No.</th>
  1113. <th>Name</th>
  1114. <th>Type</th>
  1115. <th>Description</th>
  1116. </tr>
  1117. </thead>
  1118. <tbody>
  1119. <tr>
  1120. <td>1</td>
  1121. <td><strong>Name</strong></td>
  1122. <td>Length-prefixed</td>
  1123. <td>Slash-separated path and filename. Always begins with a slash. For example: “/src/main.c”</td>
  1124. </tr>
  1125. <tr>
  1126. <td>2</td>
  1127. <td><strong>Value</strong></td>
  1128. <td>Length-prefixed</td>
  1129. <td>
  1130. <p>If this field is present the file is being created or updated. These sub-fields give the details of the new or updated file:</p>
  1131. <table>
  1132. <thead>
  1133. <tr>
  1134. <th>No.</th>
  1135. <th>Name</th>
  1136. <th>Type</th>
  1137. <th>Description</th>
  1138. </tr>
  1139. </thead>
  1140. <tbody>
  1141. <tr>
  1142. <td>1</td>
  1143. <td><strong>Mode</strong></td>
  1144. <td>Varint</td>
  1145. <td>
  1146. <p>Unix permissions. In practice one of these two common values depending on whether the file is executable or not:</p>
  1147. <img src="png/files_folders_mode_flags.png"/>
  1148. <p>Security-sensitive bits such as setuid and setgid might also be set. When extracting files from a Dat to the filesystem you might consider not honoring these bits.</p>
  1149. </td>
  1150. </tr>
  1151. <tr>
  1152. <td>2</td>
  1153. <td><strong>UID</strong></td>
  1154. <td>Varint</td>
  1155. <td>Unix user ID. Alternatively, set to 0 to not expose the user’s ID.</td>
  1156. </tr>
  1157. <tr>
  1158. <td>3</td>
  1159. <td><strong>GID</strong></td>
  1160. <td>Varint</td>
  1161. <td>Unix group ID. Alternatively, set to 0 to not expose the user’s group ID.</td>
  1162. </tr>
  1163. <tr>
  1164. <td>4</td>
  1165. <td><strong>Size</strong></td>
  1166. <td>Varint</td>
  1167. <td>Size of the file in bytes.</td>
  1168. </tr>
  1169. <tr>
  1170. <td>5</td>
  1171. <td><strong>Blocks</strong></td>
  1172. <td>Varint</td>
  1173. <td>Number of chunks the file occupies in the content feed.</td>
  1174. </tr>
  1175. <tr>
  1176. <td>6</td>
  1177. <td><strong>Offset</strong></td>
  1178. <td>Varint</td>
  1179. <td>Chunk number of the first chunk in the content feed.</td>
  1180. </tr>
  1181. <tr>
  1182. <td>7</td>
  1183. <td><strong>Byte offset</strong></td>
  1184. <td>Varint</td>
  1185. <td>Size in bytes of all chunks in the content feed before this file. 0 for the first file.</td>
  1186. </tr>
  1187. <tr>
  1188. <td>8</td>
  1189. <td><strong>Mtime</strong></td>
  1190. <td>Varint</td>
  1191. <td>Time the file was last modified. Number of milliseconds since 1 January 1970 00:00:00 UTC.</td>
  1192. </tr>
  1193. <tr>
  1194. <td>9</td>
  1195. <td><strong>Ctime</strong></td>
  1196. <td>Varint</td>
  1197. <td>Time the file was created. Number of milliseconds since 1 January 1970 00:00:00 UTC.</td>
  1198. </tr>
  1199. </tbody>
  1200. </table>
  1201. <p>If the <em>value</em> field is absent (and therefore none of the sub-fields above are set) the file previously existing with this <em>name</em> is now deleted.</p>
  1202. </td>
  1203. </tr>
  1204. <tr>
  1205. <td>3</td>
  1206. <td><strong>Paths</strong></td>
  1207. <td>Length-prefixed</td>
  1208. <td>Index that helps to traverse folders more efficiently. <a href="#paths-index">See below</a>.</td>
  1209. </tr>
  1210. </tbody>
  1211. </table>
  1212. </div>
  1213. <p>To find the latest version of a file, start from the end of the metadata feed and work backwards until you find a node with that file’s name. Even though files can be modified and deleted, previous versions can still be retrieved by searching back in the metadata feed.</p>
  1214. <p>Here is an example showing how the <em>offset</em> and <em>blocks</em> fields refer to chunks in the content feed:</p>
  1215. <img id="files-folders-feeds" class="pagewidth" src="png/files_folders_feeds.png"/></p>
  1216. <h2 id="paths-index">Paths index</h2>
  1217. <p>Scanning through the list of all files added, modified or deleted would be slow for Dats that contain lots of files or a long history. To make this faster, every node in the metadata feed contains extra information in the <strong>paths</strong> field to help traverse folders.</p>
  1218. <aside class="impl">
  1219. <img class="icon" src="png/spanner_icon.png"/>
  1220. <h3>Implementations</h3>
  1221. <p class="lang">JS</p>
  1222. </aside>
  1223. <p><img class="pagewidth" src="png/paths_index_storage.png"/>
  1224. <p>To calculate a <em>paths</em> field, start by constructing a file hierarchy from the metadata feed:</p>
  1225. <img id="paths-index-tree" src="png/paths_index_tree.png"/>
  1226. <aside>
  1227. <p>The first node is called node 0, which is stored in chunk 1 of the metadata feed.</p>
  1228. <p>Node number = chunk number - 1</p>
  1229. </aside>
  1230. <p>For each file in the hierarchy, find the most recent entry for it in the metadata feed and remember the node number. For each folder, remember the highest node number among its children:</p>
  1231. <img id="paths-index-versions" src="png/paths_index_versions.png"/>
  1232. <p>Locate the file that was just added. Select that file and all its parent folders up to the root folder. Also select files and folders within those folders, but not their descendants. Ignore everything else.</p>
  1233. <img id="paths-index-select" src="png/paths_index_select.png"/>
  1234. <aside>In this example we are calculating the <em>paths</em> field for <strong>node 8</strong>, however the same calculation will have already been done for all the previous nodes as they were added.</aside>
  1235. <p>Next, follow these steps to process the node numbers into bytes:</p>
  1236. <img id="paths-index-encode" class="pagewidth" src="png/paths_index_encode.png"/>
  1237. <p>So node 8 would appear in the metadata feed as:</p>
  1238. <img src="png/paths_index_result.png"/>
  1239. <p>The process for calculating the <em>paths</em> field after deleting a file is mostly the same as when adding a file:</p>
  1240. <img id="paths-index-delete" class="pagewidth" src="png/paths_index_delete.png"/></p>
  1241. <h1 id="future-of-dat" class="section"><span>Future of Dat</span></h1>
  1242. <p>Dat was first released in 2013, which in terms of internet infrastructure is very recent. Parts of the protocol are still changing today to enable Dat to handle bigger datasets, more hostile network conditions and support new types of applications.</p>
  1243. <p>This guide has described the Dat protocol as of January 2019. Here’s a brief summary of upcoming proposals to modify the Dat protocol in the near future:</p>
  1244. <ul>
  1245. <li>
  1246. <p><strong><a href="https://github.com/datproject/planning#protocol-performance-and-features">Hyperdrive</a></strong> is the technical name of the files and folders system. Hyperdrive is going to be completely overhauled to make it faster for datasets containing millions of files. The new version will use a data structure called a prefix tree. There will also be refactoring changes so that files/folders and key/value databases use the same underlying storage system.</p>
  1247. </li>
  1248. <li>
  1249. <p><strong><a href="https://pfrazee.hashbase.io/blog/hyperswarm">Hyperswarm</a></strong> is a new set of discovery mechanisms for finding other peers. It will replace the local network discovery and centralized DNS discovery mechanisms. It will also replace the BitTorrent distributed hash table mechanism that is in use today (but not documented in this guide).</p>
  1250. <p>Hyperswarm is able to hole-punch through NAT devices on networks. This helps users on residential or mobile internet connections directly connect to each other despite not having dedicated IP addresses or being able to accept incoming TCP connections.</p>
  1251. </li>
  1252. <li>
  1253. <p><strong><a href="https://www.datprotocol.com/deps/0008-multiwriter/">Multi-writer</a></strong> will allow Dats to be modified by multiple devices and multiple authors at the same time. Each author will have their own secret key and publish a Dat with their data in it. Multi-writer fuses all of these separate Dats into one “meta-Dat” that is a view of everyone’s data combined.</p>
  1254. </li>
  1255. <li>
  1256. <p><strong><a href="https://noiseprotocol.org/">NOISE protocol</a></strong> is a cryptographic framework for setting up secure connections between computers on the internet. This will replace how handshakes and encryption currently work in the wire protocol.</p>
  1257. <p>NOISE will fix weaknesses such as connections being readable by any eavesdropper who knows the Dat’s public key. It will allow connections to be authenticated to prevent tampering and also support forward secrecy so that past connections cannot be decrypted if the key is later stolen.</p>
  1258. </li>
  1259. </ul>
  1260. <hr/>
  1261. <p id="conclusion">This is the end of <em>How Dat Works</em>. We’ve seen all the steps necessary to download and share files using Dat. If you’d like to write an implementation then check out the <em><a href="https://datprotocol.github.io/book/">Dat Protocol Book</a></em> which offers guidance about implementation details.</p>
  1262. <p>The focus of this guide has been on storing files, but this is just one use of Dat. The protocol is flexible enough to store arbitrary data that does not use the concept of files or folders. One example is <a href="https://www.datprotocol.com/deps/0004-hyperdb/">Hyperdb</a> which is a key/value database, but Dat can be extended to support completely different use cases too. Take a look at the formal <a href="https://www.datprotocol.com/deps/">protocol specifications</a> for more information.</p>
  1263. </article>
  1264. </section>
  1265. <nav id="jumpto">
  1266. <p>
  1267. <a href="/david/blog/">Accueil du blog</a> |
  1268. <a href="https://datprotocol.github.io/how-dat-works/">Source originale</a> |
  1269. <a href="/david/stream/2019/">Accueil du flux</a>
  1270. </p>
  1271. </nav>
  1272. <footer>
  1273. <div>
  1274. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  1275. <p>
  1276. Bonjour/Hi!
  1277. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  1278. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  1279. </p>
  1280. <p>
  1281. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  1282. </p>
  1283. <p>
  1284. Voici quelques articles choisis :
  1285. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  1286. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  1287. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  1288. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  1289. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  1290. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  1291. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  1292. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  1293. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  1294. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  1295. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  1296. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  1297. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  1298. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  1299. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  1300. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  1301. </p>
  1302. <p>
  1303. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  1304. </p>
  1305. <p>
  1306. Je ne traque pas ta navigation mais mon
  1307. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  1308. conserve des logs d’accès.
  1309. </p>
  1310. </div>
  1311. </footer>
  1312. <script type="text/javascript">
  1313. ;(_ => {
  1314. const jumper = document.getElementById('jumper')
  1315. jumper.addEventListener('click', e => {
  1316. e.preventDefault()
  1317. const anchor = e.target.getAttribute('href')
  1318. const targetEl = document.getElementById(anchor.substring(1))
  1319. targetEl.scrollIntoView({behavior: 'smooth'})
  1320. })
  1321. })()
  1322. </script>