client: O(n log n) block sort, O(n) block concat, ranged read stops at range end

ober

d948eff63c932c095ce709715e2fb80c4b9a574b

diff --git a/protonstorage/drive/client.ss b/protonstorage/drive/client.ss
index bca1b4a..1366909 100644
--- a/protonstorage/drive/client.ss
+++ b/protonstorage/drive/client.ss
@@ -336,18 +336,8 @@
   (def (block<? a b)
     (< (block-index a) (block-index b)))
 
-  (def (insert-block block sorted)
-    (cond
-      [(null? sorted) (list block)]
-      [(block<? block (car sorted)) (cons block sorted)]
-      [else (cons (car sorted)
-                  (insert-block block (cdr sorted)))]))
-
   (def (sort-blocks blocks)
-    (let loop ([xs blocks] [acc '()])
-      (if (null? xs)
-          acc
-          (loop (cdr xs) (insert-block (car xs) acc)))))
+    (list-sort block<? blocks))
 
   (def (block-field who block key)
     (let ([value (jmaybe block key #f)])
@@ -368,9 +358,18 @@
         address-key)))
 
   (def (concat-bytevectors bvs)
-    (if (null? bvs)
-        (make-bytevector 0)
-        (apply bytevector-append bvs)))
+    (let* ([total
+            (let loop ([xs bvs] [n 0])
+              (if (null? xs)
+                  n
+                  (loop (cdr xs) (+ n (bytevector-length (car xs))))))]
+           [out (make-bytevector total 0)])
+      (let loop ([xs bvs] [off 0])
+        (unless (null? xs)
+          (let* ([bv (car xs)] [len (bytevector-length bv)])
+            (bytevector-copy! bv 0 out off len)
+            (loop (cdr xs) (+ off len)))))
+      out))
 
   (def (bv-slice bv start end)
     (let* ([n (bytevector-length bv)]
@@ -400,15 +399,46 @@
 
   (def (proton-drive-read-file-range session share-id parent-key file-link size offset
                                      address-key)
-    (let* ([all (proton-drive-read-file-bytes
-                  session
-                  share-id
-                  parent-key
-                  file-link
-                  address-key)]
-           [start (if (and (number? offset) (> offset 0)) offset 0)]
-           [count (if (and (number? size) (>= size 0)) size (bytevector-length all))])
-       (bv-slice all start (+ start count))))
+    (unless (proton-drive-link-file? file-link)
+      (error 'proton-drive-read-file-range "link is not a file"))
+    (let* ([file-key
+            (proton-drive-unlocked-node-key parent-key file-link)]
+           [content-key
+            (proton-drive-decrypt-content-session-key file-key file-link address-key)]
+           [revision
+            (proton-drive-active-file-revision session share-id file-link)]
+           [blocks (sort-blocks (revision-blocks revision))])
+      (proton-drive-verify-manifest-signature! address-key revision blocks)
+      (let* ([total (link-size file-link)]
+             [start (if (and (number? offset) (> offset 0)) offset 0)]
+             [count (if (and (number? size) (>= size 0)) size total)]
+             [end (min (+ start count) total)])
+        ;; Revision blocks carry no plaintext size, so blocks before `start`
+        ;; cannot be skipped; decrypt in index order but stop once past `end`,
+        ;; keeping only the portion overlapping [start,end).
+        (if (>= start end)
+            (make-bytevector 0)
+            (let loop ([bs blocks] [pos 0] [acc '()])
+              (cond
+                [(or (null? bs) (>= pos end))
+                 (concat-bytevectors (reverse acc))]
+                [else
+                 (let* ([block (car bs)]
+                        [plain
+                         (download-decrypt-block
+                           session content-key block file-key address-key)]
+                        [blen (bytevector-length plain)]
+                        [block-end (+ pos blen)])
+                   (if (<= block-end start)
+                       (loop (cdr bs) block-end acc)
+                       (let* ([sel-start (max 0 (- start pos))]
+                              [sel-end (min blen (- end pos))]
+                              [sel (bv-slice plain sel-start sel-end)])
+                         (loop (cdr bs)
+                               block-end
+                               (if (> (bytevector-length sel) 0)
+                                   (cons sel acc)
+                                   acc)))))]))))))
 
   (def (link-state link)
     (let ([value (jmaybe link "State" 0)])