an atproto pds written in F# (.NET 9) 馃
pds
fsharp
giraffe
dotnet
atproto
1namespace PDSharp.Core
2
3open System
4open System.Collections.Generic
5open System.Formats.Cbor
6
7module Mst =
8 type MstEntry = {
9 PrefixLen : int
10 KeySuffix : string
11 Value : Cid
12 Tree : Cid option
13 }
14
15 type MstNode = { Left : Cid option; Entries : MstEntry list }
16
17 /// Layer Calculation
18 let layer (key : string) : int =
19 let hash = Crypto.sha256Str key
20 let mutable zeros = 0
21 let mutable i = 0
22 let mutable found = false
23
24 while i < hash.Length && not found do
25 let b = hash.[i]
26
27 if b = 0uy then
28 zeros <- zeros + 8
29 i <- i + 1
30 else
31 let mutable mask = 0x80uy
32
33 while b &&& mask = 0uy do
34 zeros <- zeros + 1
35 mask <- mask >>> 1
36
37 found <- true
38
39 zeros / 2
40
41 let entryToCborObj (e : MstEntry) : obj =
42 let arr = Array.zeroCreate<obj> 4
43 arr.[0] <- e.PrefixLen :> obj
44 arr.[1] <- e.KeySuffix :> obj
45 arr.[2] <- e.Value :> obj
46
47 arr.[3] <-
48 match e.Tree with
49 | Some c -> c :> obj
50 | None -> null
51
52 arr :> obj
53
54 let nodeToCborObj (node : MstNode) : obj =
55 let arr = Array.zeroCreate<obj> 2
56
57 arr.[0] <-
58 match node.Left with
59 | Some c -> c :> obj
60 | None -> null
61
62 let entriesArr = node.Entries |> List.map entryToCborObj |> Seq.toArray
63 arr.[1] <- entriesArr :> obj
64 arr :> obj
65
66 let serialize (node : MstNode) : byte[] = nodeToCborObj node |> DagCbor.encode
67
68 let readCid (reader : CborReader) =
69 let tag = reader.ReadTag()
70 let tag42 = LanguagePrimitives.EnumOfValue<uint64, CborTag>(42UL)
71
72 if tag <> tag42 then
73 failwith "Expected CID tag 42"
74
75 let bytes = reader.ReadByteString()
76
77 if bytes.Length > 0 && bytes.[0] = 0x00uy then
78 let raw = Array.zeroCreate<byte> (bytes.Length - 1)
79 Array.Copy(bytes, 1, raw, 0, raw.Length)
80 Cid raw
81 else
82 Cid bytes
83
84 let deserialize (data : byte[]) : MstNode =
85 let reader = new CborReader(data.AsMemory(), CborConformanceMode.Strict)
86 let len = reader.ReadStartArray()
87
88 if len.HasValue && len.Value <> 2 then
89 failwith "MST node must be array of length 2"
90
91 let left =
92 match reader.PeekState() with
93 | CborReaderState.Null ->
94 reader.ReadNull()
95 None
96 | _ -> Some(readCid reader)
97
98 let entriesLen = reader.ReadStartArray()
99 let mutable entries = []
100
101 let count =
102 if entriesLen.HasValue then
103 entriesLen.Value
104 else
105 Int32.MaxValue
106
107 let mutable i = 0
108
109 while i < count && reader.PeekState() <> CborReaderState.EndArray do
110 let entryLen = reader.ReadStartArray()
111
112 if entryLen.HasValue && entryLen.Value <> 4 then
113 failwith "MST entry must be array of length 4"
114
115 let p = reader.ReadInt32()
116 let k = reader.ReadTextString()
117 let v = readCid reader
118
119 let t =
120 match reader.PeekState() with
121 | CborReaderState.Null ->
122 reader.ReadNull()
123 None
124 | _ -> Some(readCid reader)
125
126 reader.ReadEndArray()
127
128 entries <- entries @ [ { PrefixLen = p; KeySuffix = k; Value = v; Tree = t } ]
129 i <- i + 1
130
131 reader.ReadEndArray()
132 reader.ReadEndArray()
133 { Left = left; Entries = entries }
134
135 let compareKeys (a : string) (b : string) : int =
136 let bytesA = System.Text.Encoding.UTF8.GetBytes(a)
137 let bytesB = System.Text.Encoding.UTF8.GetBytes(b)
138 let len = min bytesA.Length bytesB.Length
139 let mutable res = 0
140 let mutable i = 0
141
142 while res = 0 && i < len do
143 res <- bytesA.[i].CompareTo(bytesB.[i])
144 i <- i + 1
145
146 if res <> 0 then
147 res
148 else
149 bytesA.Length.CompareTo bytesB.Length
150
151 type NodeLoader = Cid -> Async<MstNode option>
152 type NodePersister = MstNode -> Async<Cid>
153
154 let storeNode (persister : NodePersister) (node : MstNode) : Async<Cid> = persister node
155
156 let rec get (loader : NodeLoader) (node : MstNode) (key : string) (prevKey : string) : Async<Cid option> = async {
157 let mutable currentKey = prevKey
158 let mutable foundEntry : MstEntry option = None
159 let mutable nextTree : Cid option = node.Left
160 let mutable stop = false
161 let mutable i = 0
162
163 while not stop && i < node.Entries.Length do
164 let e = node.Entries.[i]
165
166 let prefix =
167 if e.PrefixLen > currentKey.Length then
168 currentKey
169 else
170 currentKey.Substring(0, e.PrefixLen)
171
172 let fullKey = prefix + e.KeySuffix
173 let cmp = compareKeys key fullKey
174
175 if cmp = 0 then
176 foundEntry <- Some e
177 stop <- true
178 elif cmp < 0 then
179 stop <- true
180 else
181 nextTree <- e.Tree
182 currentKey <- fullKey
183 i <- i + 1
184
185 match foundEntry with
186 | Some e -> return Some e.Value
187 | None ->
188 match nextTree with
189 | None -> return None
190 | Some cid ->
191 let! child = loader cid
192
193 match child with
194 | Some cNode -> return! get loader cNode key currentKey
195 | None -> return None
196 }
197
198 let sharedPrefixLen (a : string) (b : string) =
199 let len = min a.Length b.Length
200 let mutable i = 0
201
202 while i < len && a.[i] = b.[i] do
203 i <- i + 1
204
205 i
206
207 let nodeLayer (node : MstNode) (prevKey : string) =
208 match node.Entries with
209 | [] -> -1
210 | first :: _ ->
211 let prefix =
212 if first.PrefixLen > prevKey.Length then
213 prevKey
214 else
215 prevKey.Substring(0, first.PrefixLen)
216
217 let fullKey = prefix + first.KeySuffix
218 layer fullKey
219
220 /// Splits a node around a key (which is assumed to be higher layer than any in the node).
221 ///
222 /// Returns (LeftNode, RightNode).
223 let rec split
224 (loader : NodeLoader)
225 (persister : NodePersister)
226 (node : MstNode)
227 (key : string)
228 (prevKey : string)
229 : Async<MstNode * MstNode> =
230 async {
231 let mutable splitIdx = -1
232 let mutable found = false
233 let mutable currentKey = prevKey
234 let mutable i = 0
235 let entries = node.Entries
236 let mutable splitPrevKey = prevKey
237
238 while i < entries.Length && not found do
239 let e = entries.[i]
240
241 let prefix =
242 if e.PrefixLen > currentKey.Length then
243 currentKey
244 else
245 currentKey.Substring(0, e.PrefixLen)
246
247 let fullKey = prefix + e.KeySuffix
248
249 if compareKeys key fullKey < 0 then
250 splitIdx <- i
251 found <- true
252 else
253 currentKey <- fullKey
254 splitPrevKey <- currentKey
255 i <- i + 1
256
257 let childCidToSplit =
258 if found then
259 if splitIdx = 0 then
260 node.Left
261 else
262 entries.[splitIdx - 1].Tree
263 else if entries.Length = 0 then
264 node.Left
265 else
266 entries.[entries.Length - 1].Tree
267
268 let! (lChild, rChild) = async {
269 match childCidToSplit with
270 | None -> return ({ Left = None; Entries = [] }, { Left = None; Entries = [] })
271 | Some cid ->
272 let! childNodeOpt = loader cid
273
274 match childNodeOpt with
275 | None -> return ({ Left = None; Entries = [] }, { Left = None; Entries = [] })
276 | Some childNode -> return! split loader persister childNode key currentKey
277 }
278
279 let persistOrNone (n : MstNode) = async {
280 if n.Entries.IsEmpty && n.Left.IsNone then
281 return None
282 else
283 let! c = persister n
284 return Some c
285 }
286
287 let! lCid = persistOrNone lChild
288 let! rCid = persistOrNone rChild
289 let leftEntries = if found then entries |> List.take splitIdx else entries
290
291 let newLeftEntries =
292 if leftEntries.IsEmpty then
293 []
294 else
295 let last = leftEntries.[leftEntries.Length - 1]
296 let newLast = { last with Tree = lCid }
297 (leftEntries |> List.take (leftEntries.Length - 1)) @ [ newLast ]
298
299 let leftNode =
300 if leftEntries.IsEmpty then
301 { Left = lCid; Entries = [] }
302 else
303 { Left = node.Left; Entries = newLeftEntries }
304
305 let rightEntries = if found then entries |> List.skip splitIdx else []
306
307 let newRightEntries =
308 match rightEntries with
309 | [] -> []
310 | first :: rest ->
311 let firstFullKey =
312 let prefix =
313 if first.PrefixLen > currentKey.Length then
314 currentKey
315 else
316 currentKey.Substring(0, first.PrefixLen)
317
318 prefix + first.KeySuffix
319
320 let newP = sharedPrefixLen key firstFullKey
321 let newSuffix = firstFullKey.Substring(newP)
322 let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
323 newFirst :: rest
324
325 let rightNode = { Left = rCid; Entries = newRightEntries }
326 return (leftNode, rightNode)
327 }
328
329 let rec put
330 (loader : NodeLoader)
331 (persister : NodePersister)
332 (node : MstNode)
333 (key : string)
334 (value : Cid)
335 (prevKey : string)
336 : Async<MstNode> =
337 async {
338 let kLayer = layer key
339
340 let nLayer =
341 match node.Entries with
342 | [] -> -1
343 | first :: _ ->
344 let prefix =
345 if first.PrefixLen > prevKey.Length then
346 prevKey
347 else
348 prevKey.Substring(0, first.PrefixLen)
349
350 let fullKey = prefix + first.KeySuffix
351 layer fullKey
352
353 if kLayer > nLayer then
354 let! (lNode, rNode) = split loader persister node key prevKey
355
356 let persistOrNone (n : MstNode) = async {
357 if n.Entries.IsEmpty && n.Left.IsNone then
358 return None
359 else
360 let! c = persister n
361 return Some c
362 }
363
364 let! lCid = persistOrNone lNode
365 let! rCid = persistOrNone rNode
366
367 let p = sharedPrefixLen prevKey key
368 let suffix = key.Substring(p)
369
370 return {
371 Left = lCid
372 Entries = [
373 {
374 PrefixLen = p
375 KeySuffix = suffix
376 Value = value
377 Tree = rCid
378 }
379 ]
380 }
381
382 elif kLayer < nLayer then
383 let mutable nextCid = node.Left
384 let mutable currentKey = prevKey
385 let mutable found = false
386 let mutable i = 0
387
388 let entries = node.Entries
389 let mutable childIdx = -1
390
391 while i < entries.Length && not found do
392 let e = entries.[i]
393
394 let prefix =
395 if e.PrefixLen > currentKey.Length then
396 currentKey
397 else
398 currentKey.Substring(0, e.PrefixLen)
399
400 let fullKey = prefix + e.KeySuffix
401
402 if compareKeys key fullKey < 0 then
403 found <- true
404 else
405 childIdx <- i
406 nextCid <- e.Tree
407 currentKey <- fullKey
408 i <- i + 1
409
410 let! childNode =
411 match nextCid with
412 | Some c -> loader c
413 | None -> async { return Some { Left = None; Entries = [] } }
414
415 match childNode with
416 | None -> return failwith "Failed to load child node"
417 | Some cn ->
418 let! newChildNode = put loader persister cn key value currentKey
419 let! newChildCid = persister newChildNode
420
421 if childIdx = -1 then
422 return { node with Left = Some newChildCid }
423 else
424 return {
425 node with
426 Entries =
427 entries
428 |> List.mapi (fun idx x ->
429 if idx = childIdx then
430 { entries.[childIdx] with Tree = Some newChildCid }
431 else
432 x)
433 }
434
435 else
436 let mutable insertIdx = -1
437 let mutable found = false
438 let mutable currentKey = prevKey
439 let mutable i = 0
440 let mutable targetChildCid = None
441 let mutable childPrevKey = prevKey
442
443 let fullKeysCache = new List<string>()
444
445 while i < node.Entries.Length && not found do
446 let e = node.Entries.[i]
447
448 let prefix =
449 if e.PrefixLen > currentKey.Length then
450 currentKey
451 else
452 currentKey.Substring(0, e.PrefixLen)
453
454 let fullKey = prefix + e.KeySuffix
455 fullKeysCache.Add(fullKey)
456
457 let cmp = compareKeys key fullKey
458
459 if cmp = 0 then
460 insertIdx <- i
461 found <- true
462 elif cmp < 0 then
463 insertIdx <- i
464 targetChildCid <- if i = 0 then node.Left else node.Entries.[i - 1].Tree
465 childPrevKey <- currentKey
466 found <- true
467 else
468 currentKey <- fullKey
469 i <- i + 1
470
471 if not found then
472 insertIdx <- node.Entries.Length
473
474 targetChildCid <-
475 if node.Entries.Length = 0 then
476 node.Left
477 else
478 node.Entries.[node.Entries.Length - 1].Tree
479
480 childPrevKey <- currentKey
481
482 let entries = node.Entries
483
484 if found && compareKeys key fullKeysCache.[insertIdx] = 0 then
485 let e = entries.[insertIdx]
486 let newE = { e with Value = value }
487
488 let newEntries =
489 entries |> List.mapi (fun idx x -> if idx = insertIdx then newE else x)
490
491 return { node with Entries = newEntries }
492 else
493 let! (lChild, rChild) = async {
494 match targetChildCid with
495 | None -> return { Left = None; Entries = [] }, { Left = None; Entries = [] }
496 | Some cid ->
497 let! cOpt = loader cid
498
499 match cOpt with
500 | None -> return { Left = None; Entries = [] }, { Left = None; Entries = [] }
501 | Some c -> return! split loader persister c key childPrevKey
502 }
503
504 let persistOrNone (n : MstNode) = async {
505 if n.Entries.IsEmpty && n.Left.IsNone then
506 return None
507 else
508 let! c = persister n
509 return Some c
510 }
511
512 let! lCid = persistOrNone lChild
513 let! rCid = persistOrNone rChild
514
515 let beforeEntries =
516 if insertIdx = 0 then
517 []
518 else
519 let prevE = entries.[insertIdx - 1]
520 let newPrevE = { prevE with Tree = lCid }
521 (entries |> List.take (insertIdx - 1)) @ [ newPrevE ]
522
523 let newLeft = if insertIdx = 0 then lCid else node.Left
524 let getFullKey idx = fullKeysCache.[idx]
525 let p = sharedPrefixLen childPrevKey key
526 let suffix = key.Substring(p)
527
528 let newEntry = {
529 PrefixLen = p
530 KeySuffix = suffix
531 Value = value
532 Tree = rCid
533 }
534
535 let afterEntries =
536 if insertIdx >= entries.Length then
537 []
538 else
539 let first = entries.[insertIdx]
540 let firstFullKey = getFullKey insertIdx
541
542 let newP = sharedPrefixLen key firstFullKey
543 let newS = firstFullKey.Substring(newP)
544 let newFirst = { first with PrefixLen = newP; KeySuffix = newS }
545
546 [ newFirst ] @ (entries |> List.skip (insertIdx + 1))
547
548 let newEntries = beforeEntries @ [ newEntry ] @ afterEntries
549
550 return { Left = newLeft; Entries = newEntries }
551 }
552
553 // --- Merge Operation ---
554 let rec merge
555 (loader : NodeLoader)
556 (persister : NodePersister)
557 (leftCid : Cid option)
558 (rightCid : Cid option)
559 (prevKey : string)
560 : Async<MstNode> =
561 async {
562 match leftCid, rightCid with
563 | None, None -> return { Left = None; Entries = [] }
564 | Some l, None ->
565 let! n = loader l
566 return n |> Option.defaultValue { Left = None; Entries = [] }
567 | None, Some r ->
568 let! n = loader r
569 return n |> Option.defaultValue { Left = None; Entries = [] }
570 | Some l, Some r ->
571 let! lNodeOpt = loader l
572 let! rNodeOpt = loader r
573 let lNode = lNodeOpt |> Option.defaultValue { Left = None; Entries = [] }
574 let rNode = rNodeOpt |> Option.defaultValue { Left = None; Entries = [] }
575
576 let lLayer = nodeLayer lNode prevKey
577 let mutable current = prevKey
578
579 for e in lNode.Entries do
580 let p =
581 if e.PrefixLen > current.Length then
582 current
583 else
584 current.Substring(0, e.PrefixLen)
585
586 current <- p + e.KeySuffix
587
588 let rightPrevKey = current
589
590 let realLLayer = nodeLayer lNode prevKey
591 let realRLayer = nodeLayer rNode rightPrevKey
592
593 if realLLayer > realRLayer then
594 match lNode.Entries with
595 | [] -> return! merge loader persister lNode.Left rightCid prevKey
596 | entries ->
597 let lastIdx = entries.Length - 1
598 let lastEntry = entries.[lastIdx]
599
600 let! mergedChild = merge loader persister lastEntry.Tree rightCid rightPrevKey
601 let! mergedCid = persister mergedChild
602
603 let newEntry = { lastEntry with Tree = Some mergedCid }
604 let newEntries = (entries |> List.take lastIdx) @ [ newEntry ]
605 return { lNode with Entries = newEntries }
606
607 elif realRLayer > realLLayer then
608 match rNode.Entries with
609 | [] -> return! merge loader persister leftCid rNode.Left prevKey
610 | _ ->
611 let! mergedChild = merge loader persister leftCid rNode.Left prevKey
612 let! mergedCid = persister mergedChild
613
614 return { rNode with Left = Some mergedCid }
615
616 else
617 let boundaryL =
618 match lNode.Entries with
619 | [] -> lNode.Left
620 | es -> es.[es.Length - 1].Tree
621
622 let boundaryR = rNode.Left
623
624 let! mergedBoundaryNode = merge loader persister boundaryL boundaryR rightPrevKey
625 let! mergedBoundaryCid = persister mergedBoundaryNode
626
627 let newEntries =
628 match lNode.Entries with
629 | [] -> rNode.Entries
630 | lEntries ->
631 let lastIdx = lEntries.Length - 1
632 let lastE = lEntries.[lastIdx]
633 let newLastE = { lastE with Tree = Some mergedBoundaryCid }
634 (lEntries |> List.take lastIdx) @ [ newLastE ] @ rNode.Entries
635
636 let newLeft =
637 if lNode.Entries.IsEmpty then
638 Some mergedBoundaryCid
639 else
640 lNode.Left
641
642 return { Left = newLeft; Entries = newEntries }
643 }
644
645 let rec delete
646 (loader : NodeLoader)
647 (persister : NodePersister)
648 (node : MstNode)
649 (key : string)
650 (prevKey : string)
651 : Async<MstNode option> =
652 async {
653 let mutable currentKey = prevKey
654 let mutable foundIdx = -1
655 let mutable nextTreeIdx = -1
656 let mutable i = 0
657 let mutable found = false
658
659 while i < node.Entries.Length && not found do
660 let e = node.Entries.[i]
661
662 let prefix =
663 if e.PrefixLen > currentKey.Length then
664 currentKey
665 else
666 currentKey.Substring(0, e.PrefixLen)
667
668 let fullKey = prefix + e.KeySuffix
669
670 let cmp = compareKeys key fullKey
671
672 if cmp = 0 then
673 foundIdx <- i
674 found <- true
675 elif cmp < 0 then
676 found <- true
677 nextTreeIdx <- i - 1
678 else
679 currentKey <- fullKey
680 i <- i + 1
681
682 if not found then
683 nextTreeIdx <- node.Entries.Length - 1
684
685 if foundIdx <> -1 then
686 let e = node.Entries.[foundIdx]
687
688 let leftChildCid =
689 if foundIdx = 0 then
690 node.Left
691 else
692 node.Entries.[foundIdx - 1].Tree
693
694 let rightChildCid = e.Tree
695
696 let mergePrevKey = if foundIdx = 0 then prevKey else currentKey
697
698 let! mergedChildNode = merge loader persister leftChildCid rightChildCid mergePrevKey
699 let! mergedChildCid = persister mergedChildNode
700
701 let newEntries =
702 if foundIdx = 0 then
703 let rest = node.Entries |> List.skip 1
704
705 match rest with
706 | [] -> []
707 | first :: rs ->
708 let firstFullKey =
709 let oldP =
710 if first.PrefixLen > key.Length then
711 key
712 else
713 key.Substring(0, first.PrefixLen)
714
715 oldP + first.KeySuffix
716
717 let newP = sharedPrefixLen prevKey firstFullKey
718 let newSuffix = firstFullKey.Substring(newP)
719 let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
720 newFirst :: rs
721 else
722 let before = node.Entries |> List.take (foundIdx - 1)
723 let prevE = node.Entries.[foundIdx - 1]
724 let newPrevE = { prevE with Tree = Some mergedChildCid }
725
726 let rest = node.Entries |> List.skip (foundIdx + 1)
727
728 let newRest =
729 match rest with
730 | [] -> []
731 | first :: rs ->
732 let firstFullKey =
733 let oldP =
734 if first.PrefixLen > key.Length then
735 key
736 else
737 key.Substring(0, first.PrefixLen)
738
739 oldP + first.KeySuffix
740
741 let newP = sharedPrefixLen mergePrevKey firstFullKey
742 let newSuffix = firstFullKey.Substring(newP)
743 let newFirst = { first with PrefixLen = newP; KeySuffix = newSuffix }
744 newFirst :: rs
745
746 before @ [ newPrevE ] @ newRest
747
748 let newLeft = if foundIdx = 0 then Some mergedChildCid else node.Left
749
750 let newNode = { Left = newLeft; Entries = newEntries }
751
752 if newNode.Entries.IsEmpty && newNode.Left.IsNone then
753 return None
754 else
755 return Some newNode
756
757 else
758 let childCid =
759 if nextTreeIdx = -1 then
760 node.Left
761 else
762 node.Entries.[nextTreeIdx].Tree
763
764 match childCid with
765 | None -> return Some node
766 | Some cid ->
767 let! childNodeOpt = loader cid
768
769 match childNodeOpt with
770 | None -> return Some node
771 | Some childNode ->
772 let! newChildOpt = delete loader persister childNode key currentKey
773
774 match newChildOpt with
775 | None ->
776 if nextTreeIdx = -1 then
777 return Some { node with Left = None }
778 else
779 let prevE = node.Entries.[nextTreeIdx]
780 let newPrevE = { prevE with Tree = None }
781
782 let newEntries =
783 node.Entries
784 |> List.mapi (fun idx x -> if idx = nextTreeIdx then newPrevE else x)
785
786 return Some { node with Entries = newEntries }
787 | Some newChild ->
788 let! newChildCid = persister newChild
789
790 if nextTreeIdx = -1 then
791 return Some { node with Left = Some newChildCid }
792 else
793 let prevE = node.Entries.[nextTreeIdx]
794 let newPrevE = { prevE with Tree = Some newChildCid }
795
796 let newEntries =
797 node.Entries
798 |> List.mapi (fun idx x -> if idx = nextTreeIdx then newPrevE else x)
799
800 return Some { node with Entries = newEntries }
801 }
802
803 let fromEntries (loader : NodeLoader) (persister : NodePersister) (entries : (string * Cid) list) : Async<MstNode> = async {
804 let mutable root = { Left = None; Entries = [] }
805 let sortedEntries = entries |> List.sortBy fst
806
807 for k, v in sortedEntries do
808 let! newRoot = put loader persister root k v ""
809 root <- newRoot
810
811 return root
812 }