···11-ISC License
22-33-Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
44-55-Permission to use, copy, modify, and distribute this software for any
66-purpose with or without fee is hereby granted, provided that the above
77-copyright notice and this permission notice appear in all copies.
88-99-THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1010-WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1111-MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1212-ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1313-WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1414-ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1515-OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-53
ocaml-bytesrw-eio/README.md
···11-# bytesrw-eio - OCaml Bytesrw adapters for Eio
22-33-This OCaml library provides adapters to create `Bytesrw.Bytes.Reader.t` and
44-`Bytesrw.Bytes.Writer.t` from Eio flows, mirroring the API of `Bytesrw_unix`
55-for Eio's effect-based I/O.
66-77-## Usage
88-99-```ocaml
1010-open Eio.Std
1111-1212-(* Create a reader from an Eio flow *)
1313-let read_from_flow flow =
1414- let reader = Bytesrw_eio.bytes_reader_of_flow flow in
1515- (* Use reader with Bytesrw decoders *)
1616- reader
1717-1818-(* Create a writer to an Eio flow *)
1919-let write_to_flow flow =
2020- let writer = Bytesrw_eio.bytes_writer_of_flow flow in
2121- (* Use writer with Bytesrw encoders *)
2222- writer
2323-```
2424-2525-For custom slice sizes:
2626-2727-```ocaml
2828-(* Specify custom slice length for reading *)
2929-let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length:4096 flow in
3030-3131-(* Specify custom slice length for writing *)
3232-let writer = Bytesrw_eio.bytes_writer_of_flow ~slice_length:4096 flow in
3333-()
3434-```
3535-3636-## Installation
3737-3838-```
3939-opam install bytesrw-eio
4040-```
4141-4242-## Documentation
4343-4444-API documentation is available via:
4545-4646-```
4747-opam install bytesrw-eio
4848-odig doc bytesrw-eio
4949-```
5050-5151-## License
5252-5353-ISC
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Bytesrw adapters for Eio
77-88- This module provides adapters to create {!Bytesrw.Bytes.Reader.t} and
99- {!Bytesrw.Bytes.Writer.t} from Eio flows, mirroring the API of
1010- {!Bytesrw_unix} for Eio's effect-based I/O. *)
1111-1212-open Bytesrw
1313-1414-(** Create a [Bytes.Reader.t] from an Eio source flow.
1515-1616- Reads directly from the flow without intermediate buffering.
1717-1818- @param slice_length
1919- Maximum bytes per slice (default: 65536, which is
2020- {!Bytes.Slice.unix_io_buffer_size}) *)
2121-let bytes_reader_of_flow ?(slice_length = Bytes.Slice.unix_io_buffer_size)
2222- (flow : _ Eio.Flow.source) : Bytes.Reader.t =
2323- let buf_size = Bytes.Slice.check_length slice_length in
2424- let read () =
2525- let cstruct = Cstruct.create buf_size in
2626- match Eio.Flow.single_read flow cstruct with
2727- | 0 -> Bytes.Slice.eod
2828- | count ->
2929- let data_cs = Cstruct.sub cstruct 0 count in
3030- let buf = Cstruct.to_bytes data_cs in
3131- Bytes.Slice.make buf ~first:0 ~length:count
3232- | exception End_of_file -> Bytes.Slice.eod
3333- in
3434- Bytes.Reader.make ~slice_length read
3535-3636-(** Create a [Bytes.Writer.t] from an Eio sink flow.
3737-3838- Writes directly to the flow without intermediate buffering.
3939-4040- @param slice_length
4141- Suggested slice length for upstream (default: 65536, which is
4242- {!Bytes.Slice.unix_io_buffer_size}) *)
4343-let bytes_writer_of_flow ?(slice_length = Bytes.Slice.unix_io_buffer_size)
4444- (flow : _ Eio.Flow.sink) : Bytes.Writer.t =
4545- let rec write slice =
4646- if Bytes.Slice.is_eod slice then ()
4747- else begin
4848- let bytes = Bytes.Slice.bytes slice in
4949- let first = Bytes.Slice.first slice in
5050- let length = Bytes.Slice.length slice in
5151- let cstruct = Cstruct.of_bytes ~off:first ~len:length bytes in
5252- match Eio.Flow.single_write flow [ cstruct ] with
5353- | count when count = length -> ()
5454- | count -> write (Option.get (Bytes.Slice.drop count slice))
5555- end
5656- in
5757- Bytes.Writer.make ~slice_length write
-33
ocaml-bytesrw-eio/src/bytesrw_eio.mli
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(** Bytesrw adapters for Eio
77-88- This module provides adapters to create {!Bytesrw.Bytes.Reader.t} and
99- {!Bytesrw.Bytes.Writer.t} from Eio flows, mirroring the API of
1010- [Bytesrw_unix] for Eio's effect-based I/O.
1111-1212- Unlike the Buf_read/Buf_write wrappers, these adapters read and write
1313- directly to the flow, allowing bytesrw to handle its own buffering. *)
1414-1515-(** {1 Readers} *)
1616-1717-val bytes_reader_of_flow :
1818- ?slice_length:int -> _ Eio.Flow.source -> Bytesrw.Bytes.Reader.t
1919-(** [bytes_reader_of_flow flow] creates a reader from an Eio source flow.
2020-2121- Reads directly from the flow without intermediate buffering.
2222-2323- @param slice_length Maximum bytes per slice (default: 65536) *)
2424-2525-(** {1 Writers} *)
2626-2727-val bytes_writer_of_flow :
2828- ?slice_length:int -> _ Eio.Flow.sink -> Bytesrw.Bytes.Writer.t
2929-(** [bytes_writer_of_flow flow] creates a writer from an Eio sink flow.
3030-3131- Writes directly to the flow without intermediate buffering.
3232-3333- @param slice_length Suggested slice length for upstream (default: 65536) *)
···11-(*---------------------------------------------------------------------------
22- Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33- SPDX-License-Identifier: ISC
44- ---------------------------------------------------------------------------*)
55-66-(* Test reading from a mock flow *)
77-let test_reader_basic () =
88- Eio_main.run @@ fun _env ->
99- let test_data = "Hello, World!" in
1010- let flow = Eio.Flow.string_source test_data in
1111- let reader = Bytesrw_eio.bytes_reader_of_flow flow in
1212-1313- (* Read first slice *)
1414- let slice1 = Bytesrw.Bytes.Reader.read reader in
1515- Alcotest.(check bool)
1616- "slice is not eod" false
1717- (Bytesrw.Bytes.Slice.is_eod slice1);
1818-1919- let read_data =
2020- Bytes.sub_string
2121- (Bytesrw.Bytes.Slice.bytes slice1)
2222- (Bytesrw.Bytes.Slice.first slice1)
2323- (Bytesrw.Bytes.Slice.length slice1)
2424- in
2525- Alcotest.(check string) "data matches" test_data read_data;
2626-2727- (* Next read should be eod *)
2828- let slice2 = Bytesrw.Bytes.Reader.read reader in
2929- Alcotest.(check bool)
3030- "second read is eod" true
3131- (Bytesrw.Bytes.Slice.is_eod slice2)
3232-3333-(* Test reading with custom slice length *)
3434-let test_reader_custom_slice_length () =
3535- Eio_main.run @@ fun _env ->
3636- let test_data = "Hello, World!" in
3737- let flow = Eio.Flow.string_source test_data in
3838- let slice_length = 5 in
3939- let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length flow in
4040-4141- (* Read should respect slice_length as maximum *)
4242- let slice = Bytesrw.Bytes.Reader.read reader in
4343- Alcotest.(check bool)
4444- "slice length <= custom size" true
4545- (Bytesrw.Bytes.Slice.length slice <= slice_length)
4646-4747-(* Test reading empty flow *)
4848-let test_reader_empty () =
4949- Eio_main.run @@ fun _env ->
5050- let flow = Eio.Flow.string_source "" in
5151- let reader = Bytesrw_eio.bytes_reader_of_flow flow in
5252-5353- let slice = Bytesrw.Bytes.Reader.read reader in
5454- Alcotest.(check bool)
5555- "empty flow returns eod" true
5656- (Bytesrw.Bytes.Slice.is_eod slice)
5757-5858-(* Test writing to a mock flow *)
5959-let test_writer_basic () =
6060- Eio_main.run @@ fun _env ->
6161- let buf = Buffer.create 100 in
6262- let flow = Eio.Flow.buffer_sink buf in
6363- let writer = Bytesrw_eio.bytes_writer_of_flow flow in
6464-6565- let test_data = "Hello, World!" in
6666- let bytes = Bytes.of_string test_data in
6767- let slice =
6868- Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes)
6969- in
7070-7171- Bytesrw.Bytes.Writer.write writer slice;
7272-7373- let written = Buffer.contents buf in
7474- Alcotest.(check string) "written data matches" test_data written
7575-7676-(* Test writing with custom slice length *)
7777-let test_writer_custom_slice_length () =
7878- Eio_main.run @@ fun _env ->
7979- let buf = Buffer.create 100 in
8080- let flow = Eio.Flow.buffer_sink buf in
8181- let slice_length = 8 in
8282- let writer = Bytesrw_eio.bytes_writer_of_flow ~slice_length flow in
8383-8484- let test_data = "Hello, World!" in
8585- let bytes = Bytes.of_string test_data in
8686- let slice =
8787- Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes)
8888- in
8989-9090- Bytesrw.Bytes.Writer.write writer slice;
9191-9292- let written = Buffer.contents buf in
9393- Alcotest.(check string)
9494- "written data matches regardless of slice_length" test_data written
9595-9696-(* Test writing eod slice (should be no-op) *)
9797-let test_writer_eod () =
9898- Eio_main.run @@ fun _env ->
9999- let buf = Buffer.create 100 in
100100- let flow = Eio.Flow.buffer_sink buf in
101101- let writer = Bytesrw_eio.bytes_writer_of_flow flow in
102102-103103- Bytesrw.Bytes.Writer.write writer Bytesrw.Bytes.Slice.eod;
104104-105105- let written = Buffer.contents buf in
106106- Alcotest.(check string) "eod writes nothing" "" written
107107-108108-(* Test writing partial slice *)
109109-let test_writer_partial_slice () =
110110- Eio_main.run @@ fun _env ->
111111- let buf = Buffer.create 100 in
112112- let flow = Eio.Flow.buffer_sink buf in
113113- let writer = Bytesrw_eio.bytes_writer_of_flow flow in
114114-115115- let test_data = "Hello, World!" in
116116- let bytes = Bytes.of_string test_data in
117117- (* Write only "World" *)
118118- let slice = Bytesrw.Bytes.Slice.make bytes ~first:7 ~length:5 in
119119-120120- Bytesrw.Bytes.Writer.write writer slice;
121121-122122- let written = Buffer.contents buf in
123123- Alcotest.(check string) "partial slice written" "World" written
124124-125125-(* Test multiple reads to ensure data isolation - buffers from previous reads
126126- should not be corrupted by subsequent reads *)
127127-let test_reader_multiple_reads () =
128128- Eio_main.run @@ fun _env ->
129129- let test_data = "ABCDEFGHIJ" in
130130- (* 10 bytes *)
131131- let flow = Eio.Flow.string_source test_data in
132132- let reader = Bytesrw_eio.bytes_reader_of_flow ~slice_length:5 flow in
133133-134134- (* Read first 5 bytes *)
135135- let slice1 = Bytesrw.Bytes.Reader.read reader in
136136- let bytes1 = Bytesrw.Bytes.Slice.bytes slice1 in
137137- let data1 =
138138- Bytes.sub_string bytes1
139139- (Bytesrw.Bytes.Slice.first slice1)
140140- (Bytesrw.Bytes.Slice.length slice1)
141141- in
142142-143143- (* Read next 5 bytes *)
144144- let slice2 = Bytesrw.Bytes.Reader.read reader in
145145- let data2 =
146146- Bytes.sub_string
147147- (Bytesrw.Bytes.Slice.bytes slice2)
148148- (Bytesrw.Bytes.Slice.first slice2)
149149- (Bytesrw.Bytes.Slice.length slice2)
150150- in
151151-152152- (* Critical test: verify first read's data is STILL intact after second read
153153- This would fail if we were reusing buffers or if Cstruct.to_bytes created a view *)
154154- let data1_check =
155155- Bytes.sub_string bytes1
156156- (Bytesrw.Bytes.Slice.first slice1)
157157- (Bytesrw.Bytes.Slice.length slice1)
158158- in
159159-160160- Alcotest.(check string) "first read" "ABCDE" data1;
161161- Alcotest.(check string) "second read" "FGHIJ" data2;
162162- Alcotest.(check string)
163163- "first read still intact after second" "ABCDE" data1_check
164164-165165-(* Test round-trip: write then read *)
166166-let test_roundtrip () =
167167- Eio_main.run @@ fun _env ->
168168- let test_data = "Round-trip test data" in
169169-170170- (* Write to buffer *)
171171- let buf = Buffer.create 100 in
172172- let write_flow = Eio.Flow.buffer_sink buf in
173173- let writer = Bytesrw_eio.bytes_writer_of_flow write_flow in
174174-175175- let bytes = Bytes.of_string test_data in
176176- let slice =
177177- Bytesrw.Bytes.Slice.make bytes ~first:0 ~length:(Bytes.length bytes)
178178- in
179179- Bytesrw.Bytes.Writer.write writer slice;
180180-181181- (* Read back from buffer *)
182182- let read_flow = Eio.Flow.string_source (Buffer.contents buf) in
183183- let reader = Bytesrw_eio.bytes_reader_of_flow read_flow in
184184-185185- let read_slice = Bytesrw.Bytes.Reader.read reader in
186186- let read_data =
187187- Bytes.sub_string
188188- (Bytesrw.Bytes.Slice.bytes read_slice)
189189- (Bytesrw.Bytes.Slice.first read_slice)
190190- (Bytesrw.Bytes.Slice.length read_slice)
191191- in
192192-193193- Alcotest.(check string) "round-trip data matches" test_data read_data
194194-195195-let () =
196196- Alcotest.run "Bytesrw_eio"
197197- [
198198- ( "reader",
199199- [
200200- Alcotest.test_case "basic read" `Quick test_reader_basic;
201201- Alcotest.test_case "custom slice length" `Quick
202202- test_reader_custom_slice_length;
203203- Alcotest.test_case "empty flow" `Quick test_reader_empty;
204204- Alcotest.test_case "multiple reads data isolation" `Quick
205205- test_reader_multiple_reads;
206206- ] );
207207- ( "writer",
208208- [
209209- Alcotest.test_case "basic write" `Quick test_writer_basic;
210210- Alcotest.test_case "custom slice length" `Quick
211211- test_writer_custom_slice_length;
212212- Alcotest.test_case "eod write" `Quick test_writer_eod;
213213- Alcotest.test_case "partial slice" `Quick test_writer_partial_slice;
214214- ] );
215215- ("integration", [ Alcotest.test_case "round-trip" `Quick test_roundtrip ]);
216216- ]