client: O(n log n) block sort, O(n) block concat, ranged read stops at range end
ober
d948eff63c932c095ce709715e2fb80c4b9a574b
--- 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)])