Class: IO::Buffer

Inherits:
Object show all
Includes:
Comparable
Defined in:
io_buffer.c,
io_buffer.c

Overview

IO::Buffer is a efficient zero-copy buffer for input/output. There are typical use cases:

  • Create an empty buffer with ::new, fill it with buffer using #copy or #set_value, #set_string, get buffer with #get_string or write it directly to some file with #write.

  • Create a buffer mapped to some string with ::for, then it could be used both for reading with #get_string or #get_value, and writing (writing will change the source string, too).

  • Create a buffer mapped to some file with ::map, then it could be used for reading and writing the underlying file.

  • Create a string of a fixed size with ::string, then #read into it, or modify it using #set_value.

Interaction with string and file memory is performed by efficient low-level C mechanisms like ‘memcpy`.

The class is meant to be an utility for implementing more high-level mechanisms like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary protocols.

Examples of Usage

Empty buffer:

buffer = IO::Buffer.new(8)  # create empty 8-byte buffer
# =>
# #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL>
# ...
buffer
# =>
# <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL>
# 0x00000000  00 00 00 00 00 00 00 00
buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2
# => 4
buffer.get_string  # get the result
# => "\x00\x00test\x00\x00"

Buffer from string:

string = 'buffer'
buffer = IO::Buffer.for(string)
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# ...
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000  64 61 74 61                                     buffer

buffer.get_string(2)  # read content starting from offset 2
# => "ta"
buffer.set_string('---', 1) # write content, starting from offset 1
# => 3
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000  64 2d 2d 2d                                     d---
string  # original string changed, too
# => "d---"

Buffer from file:

File.write('test.txt', 'test buffer')
# => 9
buffer = IO::Buffer.map(File.open('test.txt'))
# =>
# #<IO::Buffer 0x00007f3f0768c000+9 MAPPED IMMUTABLE>
# ...
buffer.get_string(5, 2) # read 2 bytes, starting from offset 5
# => "da"
buffer.set_string('---', 1) # attempt to write
# in `set_string': Buffer is not writable! (IO::Buffer::AccessError)

# To create writable file-mapped buffer
# Open file for read-write, pass size, offset, and flags=0
buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0)
buffer.set_string('---', 1)
# => 3 -- bytes written
File.read('test.txt')
# => "t--- buffer"

The class is experimental and the interface is subject to change, this is especially true of file mappings which may be removed entirely in the future.

Defined Under Namespace

Classes: AccessError, AllocationError, InvalidatedError, LockedError, MaskError

Constant Summary collapse

PAGE_SIZE =

The operating system page size. Used for efficient page-aligned memory allocations.

SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE)
DEFAULT_SIZE =

The default buffer size, typically a (small) multiple of the PAGE_SIZE.

Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE
environment variable.
SIZET2NUM(RUBY_IO_BUFFER_DEFAULT_SIZE)
EXTERNAL =

Indicates that the memory in the buffer is owned by someone else. See #external? for more details.

RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)
INTERNAL =

Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details.

RB_INT2NUM(RB_IO_BUFFER_INTERNAL)
MAPPED =

Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details.

RB_INT2NUM(RB_IO_BUFFER_MAPPED)
SHARED =

Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details.

RB_INT2NUM(RB_IO_BUFFER_SHARED)
LOCKED =

Indicates that the memory in the buffer is locked and cannot be resized or freed. See #locked? and #locked for more details.

RB_INT2NUM(RB_IO_BUFFER_LOCKED)
PRIVATE =

Indicates that the memory in the buffer is mapped privately and changes won’t be replicated to the underlying file. See #private? for more details.

RB_INT2NUM(RB_IO_BUFFER_PRIVATE)
READONLY =

Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See #readonly? for more details.

RB_INT2NUM(RB_IO_BUFFER_READONLY)
LITTLE_ENDIAN =

Refers to little endian byte order, where the least significant byte is stored first. See #get_value for more details.

RB_INT2NUM(RB_IO_BUFFER_LITTLE_ENDIAN)
BIG_ENDIAN =

Refers to big endian byte order, where the most significant byte is stored first. See #get_value for more details.

RB_INT2NUM(RB_IO_BUFFER_BIG_ENDIAN)
HOST_ENDIAN =

Refers to the byte order of the host machine. See #get_value for more details.

RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN)
NETWORK_ENDIAN =

Refers to network byte order, which is the same as big endian. See #get_value for more details.

RB_INT2NUM(RB_IO_BUFFER_NETWORK_ENDIAN)

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Comparable

#<, #<=, #==, #>, #>=, #between?, #clamp

Constructor Details

#IO::Buffer.new([size = DEFAULT_SIZE, [flags = 0]]) ⇒ Object

Create a new zero-filled IO::Buffer of size bytes. By default, the buffer will be internal: directly allocated chunk of the memory. But if the requested size is more than OS-specific IO::Buffer::PAGE_SIZE, the buffer would be allocated using the virtual memory mechanism (anonymous mmap on Unix, VirtualAlloc on Windows). The behavior can be forced by passing IO::Buffer::MAPPED as a second parameter.

buffer = IO::Buffer.new(4)
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000  00 00 00 00                                     ....

buffer.get_string(0, 1) # => "\x00"

buffer.set_string("test")
buffer
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000  74 65 73 74                                     test


749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# File 'io_buffer.c', line 749

VALUE
rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
{
    io_buffer_experimental();

    rb_check_arity(argc, 0, 2);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    size_t size;
    if (argc > 0) {
        size = io_buffer_extract_size(argv[0]);
    }
    else {
        size = RUBY_IO_BUFFER_DEFAULT_SIZE;
    }

    enum rb_io_buffer_flags flags = 0;
    if (argc >= 2) {
        flags = io_buffer_extract_flags(argv[1]);
    }
    else {
        flags |= io_flags_for_size(size);
    }

    io_buffer_initialize(self, buffer, NULL, size, flags, Qnil);

    return self;
}

Class Method Details

.IO::Buffer.for(string) ⇒ Object .IO::Buffer.for(string) {|io_buffer| ... } ⇒ Object

Creates a zero-copy IO::Buffer from the given string’s memory. Without a block a frozen internal copy of the string is created efficiently and used as the buffer source. When a block is provided, the buffer is associated directly with the string’s internal buffer and updating the buffer will update the string.

Until #free is invoked on the buffer, either explicitly or via the garbage collector, the source string will be locked and cannot be modified.

If the string is frozen, it will create a read-only buffer which cannot be modified. If the string is shared, it may trigger a copy-on-write when using the block form.

string = 'test'
buffer = IO::Buffer.for(string)
buffer.external? #=> true

buffer.get_string(0, 1)
# => "t"
string
# => "best"

buffer.resize(100)
# in `resize': Cannot resize external buffer! (IO::Buffer::AccessError)

IO::Buffer.for(string) do |buffer|
  buffer.set_string("T")
  string
  # => "Test"
end

Overloads:

  • .IO::Buffer.for(string) {|io_buffer| ... } ⇒ Object

    Yields:

    • (io_buffer)


551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'io_buffer.c', line 551

VALUE
rb_io_buffer_type_for(VALUE klass, VALUE string)
{
    StringValue(string);

    // If the string is frozen, both code paths are okay.
    // If the string is not frozen, if a block is not given, it must be frozen.
    if (rb_block_given_p()) {
        struct io_buffer_for_yield_instance_arguments arguments = {
            .klass = klass,
            .string = string,
            .instance = Qnil,
            .flags = 0,
        };

        return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);
    }
    else {
        // This internally returns the source string if it's already frozen.
        string = rb_str_tmp_frozen_acquire(string);
        return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY);
    }
}

.IO::Buffer.map(file, [size, [offset, [flags]]]) ⇒ Object

Create an IO::Buffer for reading from file by memory-mapping the file. file_io should be a File instance, opened for reading.

Optional size and offset of mapping can be specified.

By default, the buffer would be immutable (read only); to create a writable mapping, you need to open a file in read-write mode, and explicitly pass flags argument without IO::Buffer::IMMUTABLE.

File.write('test.txt', 'test')

buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY>

buffer.readonly?   # => true

buffer.get_string
# => "test"

buffer.set_string('b', 0)
# `set_string': Buffer is not writable! (IO::Buffer::AccessError)

# create read/write mapping: length 4 bytes, offset 0, flags 0
buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0)
buffer.set_string('b', 0)
# => 1

# Check it
File.read('test.txt')
# => "best"

Note that some operating systems may not have cache coherency between mapped buffers and file reads.



671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
# File 'io_buffer.c', line 671

static VALUE
io_buffer_map(int argc, VALUE *argv, VALUE klass)
{
    rb_check_arity(argc, 1, 4);

    // We might like to handle a string path?
    VALUE io = argv[0];

    size_t size;
    if (argc >= 2 && !RB_NIL_P(argv[1])) {
        size = io_buffer_extract_size(argv[1]);
    }
    else {
        rb_off_t file_size = rb_file_size(io);

        // Compiler can confirm that we handled file_size < 0 case:
        if (file_size < 0) {
            rb_raise(rb_eArgError, "Invalid negative file size!");
        }
        // Here, we assume that file_size is positive:
        else if ((uintmax_t)file_size > SIZE_MAX) {
            rb_raise(rb_eArgError, "File larger than address space!");
        }
        else {
            // This conversion should be safe:
            size = (size_t)file_size;
        }
    }

    // This is the file offset, not the buffer offset:
    rb_off_t offset = 0;
    if (argc >= 3) {
        offset = NUM2OFFT(argv[2]);
    }

    enum rb_io_buffer_flags flags = 0;
    if (argc >= 4) {
        flags = io_buffer_extract_flags(argv[3]);
    }

    return rb_io_buffer_map(io, size, offset, flags);
}

.size_of(buffer_type) ⇒ Object .size_of(arrayofbuffer_type) ⇒ Object

Returns the size of the given buffer type(s) in bytes.

IO::Buffer.size_of(:u32) # => 4
IO::Buffer.size_of([:u32, :u32]) # => 8


1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
# File 'io_buffer.c', line 1935

static VALUE
io_buffer_size_of(VALUE klass, VALUE buffer_type)
{
    if (RB_TYPE_P(buffer_type, T_ARRAY)) {
        size_t total = 0;
        for (long i = 0; i < RARRAY_LEN(buffer_type); i++) {
            total += io_buffer_buffer_type_size(RB_SYM2ID(RARRAY_AREF(buffer_type, i)));
        }
        return SIZET2NUM(total);
    }
    else {
        return SIZET2NUM(io_buffer_buffer_type_size(RB_SYM2ID(buffer_type)));
    }
}

.IO::Buffer.string(length) {|io_buffer| ... } ⇒ String

Creates a new string of the given length and yields a zero-copy IO::Buffer instance to the block which uses the string as a source. The block is expected to write to the buffer and the string will be returned.

IO::Buffer.string(4) do |buffer|
  buffer.set_string("Ruby")
end
# => "Ruby"

Yields:

  • (io_buffer)

Returns:



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'io_buffer.c', line 588

VALUE
rb_io_buffer_type_string(VALUE klass, VALUE length)
{
    VALUE string = rb_str_new(NULL, RB_NUM2LONG(length));

    struct io_buffer_for_yield_instance_arguments arguments = {
        .klass = klass,
        .string = string,
        .instance = Qnil,
    };

    rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);

    return string;
}

Instance Method Details

#&(mask) ⇒ Object

Generate a new buffer the same size as the source by applying the binary AND operation to the source, using the mask, repeating as necessary.

IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x00005589b2758480+4 INTERNAL>
# 0x00000000  31 00 00 34 35 00 00 38 39 00                   1..45..89.


3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
# File 'io_buffer.c', line 3203

static VALUE
io_buffer_and(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#<=>Object

#^(mask) ⇒ Object

Generate a new buffer the same size as the source by applying the binary XOR operation to the source, using the mask, repeating as necessary.

IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x000055a2d5d10480+10 INTERNAL>
# 0x00000000  ce 32 33 cb ca 36 37 c7 c6 30                   .23..67..0


3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
# File 'io_buffer.c', line 3283

static VALUE
io_buffer_xor(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_xor(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#and!(mask) ⇒ Object

Modify the source buffer in place by applying the binary AND operation to the source, using the mask, repeating as necessary.

source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
# 0x00000000  31 32 33 34 35 36 37 38 39 30                   1234567890

source.and!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL>
# 0x00000000  31 00 00 34 35 00 00 38 39 00                   1..45..89.


3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
# File 'io_buffer.c', line 3380

static VALUE
io_buffer_and_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#clear(value = 0, [offset, [length]]) ⇒ self

Fill buffer with value, starting with offset and going for length bytes.

buffer = IO::Buffer.for('test')
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  74 65 73 74         test

buffer.clear
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  00 00 00 00         ....

buf.clear(1) # fill with 1
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 01 01 01         ....

buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 02 02 01         ....

buffer.clear(2, 1) # fill with 2, starting from offset 1
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 02 02 02         ....

Returns:

  • (self)


2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
# File 'io_buffer.c', line 2618

static VALUE
io_buffer_clear(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 0, 3);

    uint8_t value = 0;
    if (argc >= 1) {
        value = NUM2UINT(argv[0]);
    }

    size_t offset, length;
    io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);

    rb_io_buffer_clear(self, value, offset, length);

    return self;
}

#copy(source, [offset, [length, [source_offset]]]) ⇒ Object

Efficiently copy from a source IO::Buffer into the buffer, at offset using memcpy. For copying String instances, see #set_string.

buffer = IO::Buffer.new(32)
# =>
# #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL>
# 0x00000000  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
# 0x00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................  *

buffer.copy(IO::Buffer.for("test"), 8)
# => 4 -- size of buffer copied
buffer
# =>
# #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL>
# 0x00000000  00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test....
# 0x00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *

#copy can be used to put buffer into strings associated with buffer:

string= "buffer:    "
# => "buffer:    "
buffer = IO::Buffer.for(string)
buffer.copy(IO::Buffer.for("test"), 5)
# => 4
string
# => "buffer:test"

Attempt to copy into a read-only buffer will fail:

File.write('test.txt', 'test')
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
buffer.copy(IO::Buffer.for("test"), 8)
# in `copy': Buffer is not writable! (IO::Buffer::AccessError)

See ::map for details of creation of mutable file mappings, this will work:

buffer = IO::Buffer.map(File.open('test.txt', 'r+'))
buffer.copy(IO::Buffer.for("boom"), 0)
# => 4
File.read('test.txt')
# => "boom"

Attempt to copy the buffer which will need place outside of buffer’s bounds will fail:

buffer = IO::Buffer.new(2)
buffer.copy(IO::Buffer.for('test'), 0)
# in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)


2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
# File 'io_buffer.c', line 2477

static VALUE
io_buffer_copy(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 4);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE source = argv[0];
    const void *source_base;
    size_t source_size;

    rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size);

    return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
}

#each(buffer_type, [offset, [count]]) {|offset, value| ... } ⇒ self #each(buffer_type, [offset, [count]]) ⇒ Object

Iterates over the buffer, yielding each value of buffer_type starting from offset.

If count is given, only count values will be yielded.

IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value|
  puts "#{offset}: #{value}"
end
# 2: 108
# 3: 108

Overloads:

  • #each(buffer_type, [offset, [count]]) {|offset, value| ... } ⇒ self

    Yields:

    • (offset, value)

    Returns:

    • (self)


2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
# File 'io_buffer.c', line 2111

static VALUE
io_buffer_each(int argc, VALUE *argv, VALUE self)
{
    RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);

    const void *base;
    size_t size;

    rb_io_buffer_get_bytes_for_reading(self, &base, &size);

    ID buffer_type;
    if (argc >= 1) {
        buffer_type = RB_SYM2ID(argv[0]);
    }
    else {
        buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
    }

    size_t offset, count;
    io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);

    for (size_t i = 0; i < count; i++) {
        size_t current_offset = offset;
        VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
        rb_yield_values(2, SIZET2NUM(current_offset), value);
    }

    return self;
}

#each_byte([offset, [count]]) {|offset, byte| ... } ⇒ self #each_byte([offset, [count]]) ⇒ Object

Iterates over the buffer, yielding each byte starting from offset.

If count is given, only count bytes will be yielded.

IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte|
  puts "#{offset}: #{byte}"
end
# 2: 108
# 3: 108

Overloads:

  • #each_byte([offset, [count]]) {|offset, byte| ... } ⇒ self

    Yields:

    • (offset, byte)

    Returns:

    • (self)


2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
# File 'io_buffer.c', line 2195

static VALUE
io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
{
    RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);

    const void *base;
    size_t size;

    rb_io_buffer_get_bytes_for_reading(self, &base, &size);

    size_t offset, count;
    io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc-1, argv+1, &offset, &count);

    for (size_t i = 0; i < count; i++) {
        unsigned char *value = (unsigned char *)base + i + offset;
        rb_yield(RB_INT2FIX(*value));
    }

    return self;
}

#empty?Boolean

Returns:

  • (Boolean)

#external?Boolean

Returns:

  • (Boolean)

#freeObject

#get_string([offset, [length, [encoding]]]) ⇒ String

Read a chunk or all of the buffer into a string, in the specified encoding. If no encoding is provided Encoding::BINARY is used.

buffer = IO::Buffer.for('test')
buffer.get_string
# => "test"
buffer.get_string(2)
# => "st"
buffer.get_string(2, 1)
# => "s"

Returns:



2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
# File 'io_buffer.c', line 2508

static VALUE
io_buffer_get_string(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 0, 3);

    size_t offset, length;
    struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);

    const void *base;
    size_t size;
    io_buffer_get_bytes_for_reading(buffer, &base, &size);

    rb_encoding *encoding;
    if (argc >= 3) {
        encoding = rb_find_encoding(argv[2]);
    }
    else {
        encoding = rb_ascii8bit_encoding();
    }

    io_buffer_validate_range(buffer, offset, length);

    return rb_enc_str_new((const char*)base + offset, length, encoding);
}

#get_value(buffer_type, offset) ⇒ Numeric

Read from buffer a value of type at offset. buffer_type should be one of symbols:

  • :U8: unsigned integer, 1 byte

  • :S8: signed integer, 1 byte

  • :u16: unsigned integer, 2 bytes, little-endian

  • :U16: unsigned integer, 2 bytes, big-endian

  • :s16: signed integer, 2 bytes, little-endian

  • :S16: signed integer, 2 bytes, big-endian

  • :u32: unsigned integer, 4 bytes, little-endian

  • :U32: unsigned integer, 4 bytes, big-endian

  • :s32: signed integer, 4 bytes, little-endian

  • :S32: signed integer, 4 bytes, big-endian

  • :u64: unsigned integer, 8 bytes, little-endian

  • :U64: unsigned integer, 8 bytes, big-endian

  • :s64: signed integer, 8 bytes, little-endian

  • :S64: signed integer, 8 bytes, big-endian

  • :f32: float, 4 bytes, little-endian

  • :F32: float, 4 bytes, big-endian

  • :f64: double, 8 bytes, little-endian

  • :F64: double, 8 bytes, big-endian

A buffer type refers specifically to the type of binary buffer that is stored in the buffer. For example, a :u32 buffer type is a 32-bit unsigned integer in little-endian format.

string = [1.5].pack('f')
# => "\x00\x00\xC0?"
IO::Buffer.for(string).get_value(:f32, 0)
# => 1.5

Returns:



2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
# File 'io_buffer.c', line 2015

static VALUE
io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
{
    const void *base;
    size_t size;
    size_t offset = io_buffer_extract_offset(_offset);

    rb_io_buffer_get_bytes_for_reading(self, &base, &size);

    return rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset);
}

#get_values(buffer_types, offset) ⇒ Array

Similar to #get_value, except that it can handle multiple buffer types and returns an array of values.

string = [1.5, 2.5].pack('ff')
IO::Buffer.for(string).get_values([:f32, :f32], 0)
# => [1.5, 2.5]

Returns:



2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
# File 'io_buffer.c', line 2037

static VALUE
io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset)
{
    size_t offset = io_buffer_extract_offset(_offset);

    const void *base;
    size_t size;
    rb_io_buffer_get_bytes_for_reading(self, &base, &size);

    if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
        rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
    }

    VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types));

    for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
        VALUE type = rb_ary_entry(buffer_types, i);
        VALUE value = rb_io_buffer_get_value(base, size, RB_SYM2ID(type), &offset);
        rb_ary_push(array, value);
    }

    return array;
}

#hexdumpObject

#dupObject #cloneObject

Make an internal copy of the source buffer. Updates to the copy will not affect the source buffer.

source = IO::Buffer.for("Hello World")
# =>
# #<IO::Buffer 0x00007fd598466830+11 EXTERNAL READONLY SLICE>
# 0x00000000  48 65 6c 6c 6f 20 57 6f 72 6c 64                Hello World
buffer = source.dup
# =>
# #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
# 0x00000000  48 65 6c 6c 6f 20 57 6f 72 6c 64                Hello World


2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
# File 'io_buffer.c', line 2407

static VALUE
rb_io_buffer_initialize_copy(VALUE self, VALUE source)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    const void *source_base;
    size_t source_size;

    rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size);

    io_buffer_initialize(self, buffer, NULL, source_size, io_flags_for_size(source_size), Qnil);

    return io_buffer_copy_from(buffer, source_base, source_size, 0, NULL);
}

#inspectObject

#internal?Boolean

Returns:

  • (Boolean)

#lockedObject

rb_define_method(rb_cIOBuffer, “unlock”, rb_io_buffer_unlock, 0);

#locked?Boolean

Returns:

  • (Boolean)

#mapped?Boolean

Returns:

  • (Boolean)

#not!Object

Modify the source buffer in place by applying the binary NOT operation to the source.

source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
# 0x00000000  31 32 33 34 35 36 37 38 39 30                   1234567890

source.not!
# =>
# #<IO::Buffer 0x000056307a33a450+10 INTERNAL>
# 0x00000000  ce cd cc cb ca c9 c8 c7 c6 cf                   ..........


3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
# File 'io_buffer.c', line 3518

static VALUE
io_buffer_not_inplace(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_not_inplace(base, size);

    return self;
}

#null?Boolean

Returns:

  • (Boolean)

#or!(mask) ⇒ Object

Modify the source buffer in place by applying the binary OR operation to the source, using the mask, repeating as necessary.

source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a272350+10 INTERNAL>
# 0x00000000  31 32 33 34 35 36 37 38 39 30                   1234567890

source.or!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a272350+10 INTERNAL>
# 0x00000000  ff 32 33 ff ff 36 37 ff ff 30                   .23..67..0


3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
# File 'io_buffer.c', line 3426

static VALUE
io_buffer_or_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#pread(io, from, [length, [offset]]) ⇒ Object

Read at least length bytes from the io starting at the specified from position, into the buffer starting at offset. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one pread operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

IO::Buffer.for('test') do |buffer|
  p buffer
  # =>
  # <IO::Buffer 0x00007fca40087c38+4 SLICE>
  # 0x00000000  74 65 73 74         test

  # take 2 bytes from the beginning of urandom,
  # put them in buffer starting from position 2
  buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2)
  p buffer
  # =>
  # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
  # 0x00000000  05 35 73 74         te.5
end


2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
# File 'io_buffer.c', line 2931

static VALUE
io_buffer_pread(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 2, 4);

    VALUE io = argv[0];
    rb_off_t from = NUM2OFFT(argv[1]);

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);

    return rb_io_buffer_pread(self, io, from, length, offset);
}

#private?Boolean

Returns:

  • (Boolean)

#pwrite(io, from, [length, [offset]]) ⇒ Object

Write at least length bytes from the buffer starting at offset, into the io starting at the specified from position. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one pwrite operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

If the from position is beyond the end of the file, the gap will be filled with null (0 value) bytes.

out = File.open('output.txt', File::RDWR) # open for read/write, no truncation
IO::Buffer.for('1234567').pwrite(out, 2, 3, 1)

This leads to 234 (3 bytes, starting from position 1) being written into output.txt, starting from file position 2.



3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
# File 'io_buffer.c', line 3162

static VALUE
io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 2, 4);

    VALUE io = argv[0];
    rb_off_t from = NUM2OFFT(argv[1]);

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);

    return rb_io_buffer_pwrite(self, io, from, length, offset);
}

#read(io, [length, [offset]]) ⇒ Object

Read at least length bytes from the io, into the buffer starting at offset. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one read operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

IO::Buffer.for('test') do |buffer|
  p buffer
  # =>
  # <IO::Buffer 0x00007fca40087c38+4 SLICE>
  # 0x00000000  74 65 73 74         test
  buffer.read(File.open('/dev/urandom', 'rb'), 2)
  p buffer
  # =>
  # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
  # 0x00000000  05 35 73 74         .5st
end


2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
# File 'io_buffer.c', line 2809

static VALUE
io_buffer_read(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 3);

    VALUE io = argv[0];

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset);

    return rb_io_buffer_read(self, io, length, offset);
}

#readonly?Boolean

Returns:

  • (Boolean)

#resizeObject

#set_string(string, [offset, [length, [source_offset]]]) ⇒ Object

Efficiently copy from a source String into the buffer, at offset using memcpy.

buf = IO::Buffer.new(8)
# =>
# #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
# 0x00000000  00 00 00 00 00 00 00 00                         ........

# set buffer starting from offset 1, take 2 bytes starting from string's
# second
buf.set_string('test', 1, 2, 1)
# => 2
buf
# =>
# #<IO::Buffer 0x0000557412714a20+8 INTERNAL>
# 0x00000000  00 65 73 00 00 00 00 00                         .es.....

See also #copy for examples of how buffer writing might be used for changing associated strings and files.



2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
# File 'io_buffer.c', line 2556

static VALUE
io_buffer_set_string(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 4);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE string = rb_str_to_str(argv[0]);

    const void *source_base = RSTRING_PTR(string);
    size_t source_size = RSTRING_LEN(string);

    return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
}

#set_value(type, offset, value) ⇒ Object

Write to a buffer a value of type at offset. type should be one of symbols described in #get_value.

buffer = IO::Buffer.new(8)
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000  00 00 00 00 00 00 00 00

buffer.set_value(:U8, 1, 111)
# => 1

buffer
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000  00 6f 00 00 00 00 00 00                         .o......

Note that if the type is integer and value is Float, the implicit truncation is performed:

buffer = IO::Buffer.new(8)
buffer.set_value(:U32, 0, 2.5)

buffer
# =>
# #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL>
# 0x00000000  00 00 00 02 00 00 00 00
#                      ^^ the same as if we'd pass just integer 2


2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
# File 'io_buffer.c', line 2277

static VALUE
io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
{
    void *base;
    size_t size;
    size_t offset = io_buffer_extract_offset(_offset);

    rb_io_buffer_get_bytes_for_writing(self, &base, &size);

    rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value);

    return SIZET2NUM(offset);
}

#set_values(buffer_types, offset, values) ⇒ Object

Write values of buffer_types at offset to the buffer. buffer_types should be an array of symbols as described in #get_value. values should be an array of values to write.

buffer = IO::Buffer.new(8)
buffer.set_values([:U8, :U16], 0, [1, 2])
buffer
# =>
# #<IO::Buffer 0x696f717561746978+8 INTERNAL>
# 0x00000000  01 00 02 00 00 00 00 00                         ........


2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
# File 'io_buffer.c', line 2305

static VALUE
io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
{
    if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
        rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
    }

    if (!RB_TYPE_P(values, T_ARRAY)) {
        rb_raise(rb_eArgError, "Argument values should be an array!");
    }

    if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) {
        rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!");
    }

    size_t offset = io_buffer_extract_offset(_offset);

    void *base;
    size_t size;
    rb_io_buffer_get_bytes_for_writing(self, &base, &size);

    for (long i = 0; i < RARRAY_LEN(buffer_types); i++) {
        VALUE type = rb_ary_entry(buffer_types, i);
        VALUE value = rb_ary_entry(values, i);
        rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value);
    }

    return SIZET2NUM(offset);
}

#shared?Boolean

Returns:

  • (Boolean)

#sizeObject

#sliceObject

Manipulation:

#to_sObject

#transferObject

#valid?Boolean

Returns:

  • (Boolean)

#values(buffer_type, [offset, [count]]) ⇒ Array

Returns an array of values of buffer_type starting from offset.

If count is given, only count values will be returned.

IO::Buffer.for("Hello World").values(:U8, 2, 2)
# => [108, 108]

Returns:



2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
# File 'io_buffer.c', line 2151

static VALUE
io_buffer_values(int argc, VALUE *argv, VALUE self)
{
    const void *base;
    size_t size;

    rb_io_buffer_get_bytes_for_reading(self, &base, &size);

    ID buffer_type;
    if (argc >= 1) {
        buffer_type = RB_SYM2ID(argv[0]);
    }
    else {
        buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
    }

    size_t offset, count;
    io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);

    VALUE array = rb_ary_new_capa(count);

    for (size_t i = 0; i < count; i++) {
        VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
        rb_ary_push(array, value);
    }

    return array;
}

#write(io, [length, [offset]]) ⇒ Object

Write at least length bytes from the buffer starting at offset, into the io. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one write operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

out = File.open('output.txt', 'wb')
IO::Buffer.for('1234567').write(out, 3)

This leads to 123 being written into output.txt



3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
# File 'io_buffer.c', line 3039

static VALUE
io_buffer_write(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 3);

    VALUE io = argv[0];

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset);

    return rb_io_buffer_write(self, io, length, offset);
}

#xor!(mask) ⇒ Object

Modify the source buffer in place by applying the binary XOR operation to the source, using the mask, repeating as necessary.

source = IO::Buffer.for("1234567890").dup # Make a read/write copy.
# =>
# #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
# 0x00000000  31 32 33 34 35 36 37 38 39 30                   1234567890

source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF"))
# =>
# #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL>
# 0x00000000  ce 32 33 cb ca 36 37 c7 c6 30                   .23..67..0


3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
# File 'io_buffer.c', line 3472

static VALUE
io_buffer_xor_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#|(mask) ⇒ Object

Generate a new buffer the same size as the source by applying the binary OR operation to the source, using the mask, repeating as necessary.

IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF")
# =>
# #<IO::Buffer 0x0000561785ae3480+10 INTERNAL>
# 0x00000000  ff 32 33 ff ff 36 37 ff ff 30                   .23..67..0


3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
# File 'io_buffer.c', line 3243

static VALUE
io_buffer_or(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#~Object

Generate a new buffer the same size as the source by applying the binary NOT operation to the source.

~IO::Buffer.for("1234567890")
# =>
# #<IO::Buffer 0x000055a5ac42f120+10 INTERNAL>
# 0x00000000  ce cd cc cb ca c9 c8 c7 c6 cf                   ..........


3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
# File 'io_buffer.c', line 3323

static VALUE
io_buffer_not(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_not(output_buffer->base, buffer->base, buffer->size);

    return output;
}