Class: IO::Buffer
| Relationships & Source Files | |
| Namespace Children | |
|
Exceptions:
| |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Instance Chain:
self,
::Comparable
|
|
| Inherits: | Object |
| Defined in: | io_buffer.c, io_buffer.c |
Overview
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.
MemoryView Support
Buffer supports the C-level MemoryView protocol, so C
extensions can use rb_memory_view_get() to access the buffer's
memory directly (zero-copy) as a 1-dimensional contiguous array of
bytes. The memory view is writable if the buffer is not
#readonly? and RUBY_MEMORY_VIEW_WRITABLE is specified.
While a MemoryView is exported, the buffer is locked.
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 = 'data'
IO::Buffer.for(string) do |buffer|
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000 64 61 74 61 data
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---"
end
Buffer from file:
File.write('test.txt', 'test data')
# => 9
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# =>
# #<IO::Buffer 0x00007f3f0768c000+9 EXTERNAL MAPPED FILE SHARED READONLY>
# ...
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--- data"
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.
Constant Summary
-
BIG_ENDIAN =
# File 'io_buffer.c', line 4503
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)
-
DEFAULT_SIZE =
# File 'io_buffer.c', line 4467
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 =
# File 'io_buffer.c', line 4482
Indicates that the memory in the buffer is owned by someone else. See #external? for more details.
RB_INT2NUM(RB_IO_BUFFER_EXTERNAL)
-
HOST_ENDIAN =
# File 'io_buffer.c', line 4506
Refers to the byte order of the host machine. See #get_value for more details.
RB_INT2NUM(RB_IO_BUFFER_HOST_ENDIAN)
-
INTERNAL =
# File 'io_buffer.c', line 4485
Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details.
RB_INT2NUM(RB_IO_BUFFER_INTERNAL)
-
LITTLE_ENDIAN =
# File 'io_buffer.c', line 4500
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)
-
MAPPED =
# File 'io_buffer.c', line 4488
Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details.
RB_INT2NUM(RB_IO_BUFFER_MAPPED)
-
MAP_ALIGNMENT =
# File 'io_buffer.c', line 4462
The alignment required for file mapping offsets. Mapping sizes do not need to be aligned.
SIZET2NUM(RUBY_IO_BUFFER_MAP_ALIGNMENT)
-
NETWORK_ENDIAN =
# File 'io_buffer.c', line 4509
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)
-
PAGE_SIZE =
# File 'io_buffer.c', line 4459
The operating system page size. Used for efficient page-aligned memory allocations.
SIZET2NUM(RUBY_IO_BUFFER_PAGE_SIZE)
-
PRIVATE =
# File 'io_buffer.c', line 4494
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 =
# File 'io_buffer.c', line 4497
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)
-
SHARED =
# File 'io_buffer.c', line 4491
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)
-
VERSION =
# File 'io_buffer.c', line 4456
:Bufferinterface version.The IO
Class Method Summary
-
.for(string) ⇒ Buffer
Creates a zero-copy
Bufferfrom the given string's memory. -
.map(file, [size, [offset, [flags]]]) ⇒ Buffer
Create an
Bufferfor reading fromfileby memory-mapping the file. -
.new([size = DEFAULT_SIZE, [flags]]) ⇒ Buffer
constructor
Create a new zero-filled
Bufferof #size bytes. -
.size_of(buffer_type) ⇒ byte size
Returns the size of the given buffer type(s) in bytes.
-
.string(length) {|io_buffer| ... } ⇒ String
Creates a new string of the given length and yields a zero-copy
Bufferinstance to the block which uses the string as a source.
Instance Attribute Summary
- #empty? ⇒ Boolean readonly
- #external? ⇒ Boolean readonly
- #internal? ⇒ Boolean readonly
-
#locked
readonly
Prevents the buffer or its buffer source from being moved or freed while the block is executing.
- #locked? ⇒ Boolean readonly
- #mapped? ⇒ Boolean readonly
- #null? ⇒ Boolean readonly
- #private? ⇒ Boolean readonly
- #readonly? ⇒ Boolean readonly
- #shared? ⇒ Boolean readonly
- #valid? ⇒ Boolean readonly
Instance Method Summary
-
#&(mask) ⇒ Buffer
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.
-
#<=>(other) ⇒ Integer
Returns a negative integer, zero, or a positive integer if the receiver is less than, equal to, or greater than
other, respectively. -
#^(mask) ⇒ Buffer
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.
-
#and!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary AND operation to the source, using the mask, repeating as necessary.
-
#bit_count([offset, [length]]) ⇒ Integer
Returns the number of set bits (1s) in the buffer, also known as the Hamming weight or population count.
-
#clear(value = 0, [offset, [length]]) ⇒ self
Fill buffer with
value, starting withoffsetand going forlengthbytes. -
#copy(source, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source
Bufferinto the buffer, atoffsetusingmemmove. -
#free ⇒ self
If the buffer references memory, release it back to the operating system.
-
#get_string([offset, [length, [encoding]]]) ⇒ String
Read a chunk or all of the buffer into a string, in the specified
encoding. -
#get_value(buffer_type, offset) ⇒ Numeric
Read from buffer a value of
typeatoffset. -
#get_values(buffer_types, offset) ⇒ Array
Similar to #get_value, except that it can handle multiple buffer types and returns an array of values.
-
#hexdump([offset, [length, [width]]]) ⇒ String?
Returns a human-readable string representation of the buffer.
-
#not! ⇒ Buffer
Modify the source buffer in place by applying the unary NOT operation to the source.
-
#or!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary OR operation to the source, using the mask, repeating as necessary.
-
#read(io, [offset, [length]]) ⇒ read length, -errno
Perform one read operation of at most
lengthbytes fromiointo the buffer starting atoffset. -
#resize(new_size) ⇒ self
Resizes a buffer to a
new_sizebytes, preserving its content. -
#set_string(string, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source
::Stringinto the buffer, atoffsetusingmemmove. - #set_value(type, _offset, value)
-
#set_values(buffer_types, offset, values) ⇒ offset
Write #values of
buffer_typesatoffsetto the buffer. - #size
-
#slice([offset, [length]]) ⇒ Buffer
Produce another
Bufferwhich is a slice (or view into) the current one starting atoffsetbytes and going forlengthbytes. - #to_s
-
#transfer ⇒ Buffer
Transfers ownership of the underlying memory to a new buffer, causing the current buffer to become uninitialized.
-
#values(buffer_type, [offset, [count]]) ⇒ Array
Returns an array of values of
buffer_typestarting fromoffset. -
#write(io, [offset, [length]]) ⇒ written length, -errno
Perform one write operation of at most
lengthbytes toiofrom the buffer starting atoffset. -
#xor!(mask) ⇒ Buffer
Modify the source buffer in place by applying the binary XOR operation to the source, using the mask, repeating as necessary.
-
#|(mask) ⇒ Buffer
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.
-
#~ ⇒ Buffer
Generate a new buffer the same size as the source by applying the unary NOT operation to the source.
::Comparable - Included
| #< | Returns whether |
| #<= | Returns whether |
| #== | Compares two objects based on the receiver's #<=> method, returning true if it returns 0. |
| #> | Returns whether |
| #>= | Returns whether |
| #between? | |
| #clamp |
Constructor Details
.new([size = DEFAULT_SIZE, [flags]]) ⇒ Buffer
Create a new zero-filled 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
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 MAPPED
as a second parameter.
SHARED and PRIVATE imply MAPPED and are
mutually exclusive. Otherwise, if flags do not include an allocation
mode, INTERNAL or MAPPED is inferred from the
requested size. The two allocation modes are mutually exclusive.
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
# File 'io_buffer.c', line 1055
VALUE
rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
struct rb_io_buffer *buffer = get_io_buffer(self);
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]);
}
flags = io_buffer_flags_for_new(flags, size);
io_buffer_initialize(self, buffer, NULL, size, flags, Qnil);
return self;
}
Class Method Details
.for(string) ⇒ Buffer
.for(string) {|io_buffer| ... }
Buffer
.for(string) {|io_buffer| ... }
Creates a zero-copy 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
# => "test"
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
# File 'io_buffer.c', line 742
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);
}
}
.map(file, [size, [offset, [flags]]]) ⇒ Buffer
Create an Buffer for reading from file by memory-mapping the file.
file should be a ::File instance, opened for reading or reading and writing.
Optional #size and offset of mapping can be specified. The offset must
be a multiple of MAP_ALIGNMENT. The #size does not need to be
aligned. Trying to map an empty file or specify #size of 0 will raise an
error.
By default, the buffer is writable and expects the file to be writable. It is also shared, so several processes can use the same mapping.
The mapping mode may be explicitly selected with SHARED or PRIVATE, but the two flags are mutually exclusive. MAPPED is accepted but redundant because this method always creates a mapped buffer. INTERNAL and EXTERNAL cannot be specified.
You can pass READONLY in flags argument to make a read-only buffer;
this allows to work with files opened only for reading.
Specifying PRIVATE in flags creates a private mapping,
which will not impact other processes or the underlying file.
It also allows updating a buffer created from a read-only file.
File.write('test.txt', 'test')
buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# => #<IO::Buffer 0x00000001014a0000+4 EXTERNAL MAPPED FILE SHARED READONLY>
buffer.readonly? # => true
buffer.get_string
# => "test"
buffer.set_string('b', 0)
# 'IO::Buffer#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.
# File 'io_buffer.c', line 895
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];
rb_off_t file_size = rb_file_size(io);
// Compiler can confirm that we handled file_size <= 0 case:
if (UNLIKELY(file_size <= 0)) {
rb_raise(rb_eArgError, "Invalid negative or zero file size!");
}
// Here, we assume that file_size is positive:
else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
rb_raise(rb_eArgError, "File larger than address space!");
}
size_t size;
if (argc >= 2 && !RB_NIL_P(argv[1])) {
size = io_buffer_extract_size(argv[1]);
if (UNLIKELY(size == 0)) {
rb_raise(rb_eArgError, "Size can't be zero!");
}
if (UNLIKELY(size > (size_t)file_size)) {
rb_raise(rb_eArgError,
"Size (%" PRIuSIZE ") can't be larger than "
"file size (%" PRIuSIZE ")",
size,
(size_t)file_size);
}
}
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]);
if (UNLIKELY(offset < 0)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be negative!",
argv[2]);
}
if (UNLIKELY(offset >= file_size)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"file size (%" PRIuSIZE ")",
argv[2],
(size_t)file_size);
}
if (RB_NIL_P(argv[1])) {
// Decrease size if it's set from the actual file size:
size = (size_t)(file_size - offset);
}
else if (UNLIKELY((size_t)(file_size - offset) < size)) {
size_t maximum_offset =
(file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT *
RUBY_IO_BUFFER_MAP_ALIGNMENT;
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"%" PRIuSIZE " for requested size (%" PRIuSIZE ")",
argv[2],
maximum_offset,
size);
}
}
enum rb_io_buffer_flags flags = 0;
if (argc >= 4) {
flags = io_buffer_extract_flags(argv[3]);
}
flags = io_buffer_flags_for_map(flags);
return rb_io_buffer_map(io, size, offset, flags);
}
# File 'io_buffer.c', line 2502
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(TYPE_ID(RARRAY_AREF(buffer_type, i)));
}
return SIZET2NUM(total);
}
else {
return SIZET2NUM(io_buffer_buffer_type_size(TYPE_ID(buffer_type)));
}
}
.string(length) {|io_buffer| ... } ⇒ String
Creates a new string of the given length and yields a zero-copy 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"
# File 'io_buffer.c', line 779
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 Attribute Details
#empty? ⇒ Boolean (readonly)
[ GitHub ]
#external? ⇒ Boolean (readonly)
[ GitHub ]
#internal? ⇒ Boolean (readonly)
[ GitHub ]
#locked (readonly)
Prevents the buffer or its buffer source from being moved or freed while the block is executing. Locks are nested and shared with slices backed by the same buffer source. The source remains locked until every nested lock has been released.
Locking protects allocation lifetime; it does not serialize access to the bytes. Code that shares mutable buffer contents between threads must still use appropriate synchronization.
buffer = IO::Buffer.new(4)
buffer.locked? #=> false
Fiber.schedule do
buffer.locked do
buffer.write(io) # theoretical system call interface
end
end
Fiber.schedule do
buffer.locked do
buffer.set_string("test", 0) # Nested locking is allowed.
end
end
# File 'io_buffer.c', line 1799
VALUE
rb_io_buffer_locked(VALUE self)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
// Only yield the block for a currently valid view. In particular, an
// invalid slice should not lock its source.
io_buffer_validate_for_reading(buffer);
io_buffer_lock(buffer);
return rb_ensure(rb_yield, self, rb_io_buffer_locked_ensure, self);
}
#locked? ⇒ Boolean (readonly)
[ GitHub ]
#mapped? ⇒ Boolean (readonly)
[ GitHub ]
#null? ⇒ Boolean (readonly)
[ GitHub ]
#private? ⇒ Boolean (readonly)
[ GitHub ]
#readonly? ⇒ Boolean (readonly)
[ GitHub ]
#valid? ⇒ Boolean (readonly)
[ GitHub ]
Instance Method Details
#&(mask) ⇒ Buffer
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+10 INTERNAL>
# 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
# File 'io_buffer.c', line 3838
static VALUE
io_buffer_and(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = get_io_buffer(output);
memory_and(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
#<=>(other) ⇒ Integer
Returns a negative integer, zero, or a positive integer if the receiver is
less than, equal to, or greater than other, respectively.
Buffers are compared by size first, and if the sizes are equal, by the exact
contents of the memory they are referencing using memcmp. Only the sign of
the returned integer is meaningful; the result of memcmp is returned as is.
IO::Buffer.for("abc") <=> IO::Buffer.for("abc") # => 0
IO::Buffer.for("abc") <=> IO::Buffer.for("ab") # => 1
IO::Buffer.for("abc") <=> IO::Buffer.for("abd") # => -1
# File 'io_buffer.c', line 2266
static VALUE
rb_io_buffer_compare(VALUE self, VALUE other)
{
const void *ptr1, *ptr2;
size_t size1, size2;
rb_io_buffer_get_bytes_for_reading(self, &ptr1, &size1);
rb_io_buffer_get_bytes_for_reading(other, &ptr2, &size2);
if (size1 < size2) {
return RB_INT2NUM(-1);
}
if (size1 > size2) {
return RB_INT2NUM(1);
}
if (size1 == 0) {
return RB_INT2NUM(0);
}
RUBY_ASSERT(ptr1 != NULL);
RUBY_ASSERT(ptr2 != NULL);
return RB_INT2NUM(memcmp(ptr1, ptr2, size1));
}
#^(mask) ⇒ Buffer
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
# File 'io_buffer.c', line 3928
static VALUE
io_buffer_xor(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = get_io_buffer(output);
memory_xor(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
#and!(mask) ⇒ Buffer
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.
# File 'io_buffer.c', line 4032
static VALUE
io_buffer_and_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size);
return self;
}
#bit_count([offset, [length]]) ⇒ Integer
# File 'io_buffer.c', line 4225
static VALUE
io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
size_t offset, length;
struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
io_buffer_validate_range(buffer, offset, length);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
if (length == 0) return SIZET2NUM(0);
RUBY_ASSERT(base != NULL);
size_t count = memory_bit_count((const unsigned char *)base + offset, length);
return SIZET2NUM(count);
}
#clear(value = 0, [offset, [length]]) ⇒ self
Fill buffer with value, starting with offset and going for length
bytes.
buffer = IO::Buffer.for('test').dup
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 74 65 73 74 test
buffer.clear
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 00 00 00 00 ....
buf.clear(1) # fill with 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 01 01 01 ....
buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 02 02 01 ....
buffer.clear(2, 1) # fill with 2, starting from offset 1
# =>
# <IO::Buffer 0x00007fca40087c38+4 INTERNAL>
# 0x00000000 01 02 02 02 ....
# File 'io_buffer.c', line 3365
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]]]) ⇒ size
Efficiently copy from a source Buffer into the buffer, at offset
using memmove. 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 = "data: "
# => "data: "
buffer = IO::Buffer.for(string) do |buffer|
buffer.copy(IO::Buffer.for("test"), 5)
end
# => 4
string
# => "data: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)
It is safe to copy between memory regions that overlaps each other. In such case, the data is copied as if the data was first copied from the source buffer to a temporary buffer, and then copied from the temporary buffer to the destination buffer.
buffer = IO::Buffer.new(10)
buffer.set_string("0123456789")
buffer.copy(buffer, 3, 7)
# => 7
buffer
# =>
# #<IO::Buffer 0x000056494f8ce440+10 INTERNAL>
# 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456
# File 'io_buffer.c', line 3216
static VALUE
io_buffer_copy(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 4);
VALUE source = argv[0];
struct io_buffer_copy_arguments arguments = {
.destination = self,
.argc = argc-1,
.argv = argv+1,
};
// Lock the source first, then io_buffer_copy_from_readable nests the
// destination lock. The scoped helpers use rb_ensure, so the destination
// is unlocked before the source on both normal and exceptional returns.
// If both buffers share an allocation, its reference-counted lock is
// acquired and released twice.
return rb_io_buffer_locked_for_reading(source, io_buffer_copy_from_readable, (VALUE)&arguments);
}
#free ⇒ self
If the buffer references memory, release it back to the operating system.
- for a mapped buffer (e.g. from file): unmap.
- for a buffer created from scratch: free memory.
- for a buffer created from string: undo the association.
After releasing any referenced memory, the buffer is reset to a valid, empty, null state. It has no backing storage and its size is zero. Zero-length operations remain valid, while operations requiring bytes fail normal bounds checking.
You can resize the buffer to allocate new storage.
buffer = IO::Buffer.for('test')
buffer.free
# => #<IO::Buffer 0x0000000000000000+0 NULL>
buffer.null? # => true
buffer.empty? # => true
buffer.valid? # => true
buffer.get_string # => ""
buffer.get_value(:U8, 0) # raises ArgumentError
A frozen buffer cannot be freed, as that would release the memory its contents live in:
buffer = IO::Buffer.for('test').freeze
buffer.free
# in `free': can't modify frozen IO::Buffer (FrozenError)
# File 'io_buffer.c', line 1860
static VALUE
io_buffer_free(VALUE self)
{
rb_check_frozen(self);
return rb_io_buffer_free(self);
}
#get_string([offset, [length, [encoding]]]) ⇒ String
# File 'io_buffer.c', line 3250
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);
const char *data = base ? (const char*)base + offset : NULL;
return rb_enc_str_new(data, 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:u128: unsigned integer, 16 bytes, little-endian:U128: unsigned integer, 16 bytes, big-endian:s128: signed integer, 16 bytes, little-endian:S128: signed integer, 16 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
# File 'io_buffer.c', line 2591
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, TYPE_ID(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]
# File 'io_buffer.c', line 2613
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, TYPE_ID(type), &offset);
rb_ary_push(array, value);
}
return array;
}
#hexdump([offset, [length, [width]]]) ⇒ String?
Returns a human-readable string representation of the buffer. The exact format is subject to change.
Returns nil if the buffer does not reference any memory, that is, if
#null? returns true (for example after #free or #transfer).
buffer = IO::Buffer.for("Hello World")
puts buffer.hexdump
# 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
As buffers are usually fairly big, you may want to limit the output by specifying the offset and length:
puts buffer.hexdump(6, 5)
# 0x00000006 57 6f 72 6c 64 World
# File 'io_buffer.c', line 1924
static VALUE
rb_io_buffer_hexdump(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);
size_t width = RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH;
if (argc >= 3) {
width = io_buffer_extract_width(argv[2], 1);
}
// This may raise an exception if the offset/length is invalid:
io_buffer_validate_range(buffer, offset, length);
VALUE result = Qnil;
if (io_buffer_validate(buffer) && buffer->base) {
result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1));
io_buffer_hexdump(result, width, buffer->base, offset+length, offset, 1);
}
return result;
}
#not! ⇒ Buffer
Modify the source buffer in place by applying the unary 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 ..........
# File 'io_buffer.c', line 4176
static VALUE
io_buffer_not_inplace(VALUE self)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
memory_not_inplace(base, size);
return self;
}
#or!(mask) ⇒ Buffer
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
# File 'io_buffer.c', line 4080
static VALUE
io_buffer_or_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size);
return self;
}
#read(io, [offset, [length]]) ⇒ read length, -errno
Perform one read operation of at most length bytes from io into the
buffer starting at offset. A short read is a normal result. If an error
occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the
buffer. If length is not given, it defaults to the size of the buffer
minus the offset. A zero length is a no-op.
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'), 0, 2)
p buffer
# =>
# <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
# 0x00000000 05 35 73 74 .5st
end
# File 'io_buffer.c', line 3530
static VALUE
io_buffer_read(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 3);
VALUE io = argv[0];
size_t offset, length;
io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
return rb_io_buffer_read(self, io, offset, length);
}
#resize(new_size) ⇒ self
Resizes a buffer to a new_size bytes, preserving its content.
Depending on the old and new size, the memory area associated with
the buffer might be either extended, or rellocated at different
address with content being copied.
buffer = IO::Buffer.new(4)
buffer.set_string("test", 0)
buffer.resize(8) # resize to 8 bytes
# =>
# #<IO::Buffer 0x0000555f5d1a1630+8 INTERNAL>
# 0x00000000 74 65 73 74 00 00 00 00 test....
When the buffer is a slice, resizing changes the size of the view without modifying the source buffer or allocating new storage. The resized view must remain within the source buffer. Growing the view exposes the existing bytes in the source; they are not cleared. Because the source allocation does not change, a slice can be resized while its source is locked.
External owning buffers (created with .for), and locked owning buffers cannot be resized. Frozen buffers cannot be resized.
# File 'io_buffer.c', line 2242
static VALUE
io_buffer_resize(VALUE self, VALUE size)
{
rb_check_frozen(self);
rb_io_buffer_resize(self, io_buffer_extract_size(size));
return self;
}
#set_string(string, [offset, [length, [source_offset]]]) ⇒ size
Efficiently copy from a source ::String into the buffer, at offset using
memmove.
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.
# File 'io_buffer.c', line 3300
static VALUE
io_buffer_set_string(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 4);
struct rb_io_buffer *buffer = get_io_buffer(self);
VALUE string = rb_str_to_str(argv[0]);
const void *source_base = RSTRING_PTR(string);
size_t source_size = RSTRING_LEN(string);
VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
RB_GC_GUARD(string);
return result;
}
#set_value(type, _offset, value)
[ GitHub ]# File 'io_buffer.c', line 2912
static VALUE
io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
{
struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
size_t offset = io_buffer_extract_offset(_offset);
rb_io_buffer_set_value(buffer, type, &offset, value);
return SIZET2NUM(offset);
}
#set_values(buffer_types, offset, values) ⇒ offset
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. Returns the offset just after the last
written value.
buffer = IO::Buffer.new(8)
buffer.set_values([:U8, :U16], 0, [1, 2])
# => 3
buffer
# =>
# #<IO::Buffer 0x696f717561746978+8 INTERNAL>
# 0x00000000 01 00 02 00 00 00 00 00 ........
# File 'io_buffer.c', line 2937
static VALUE
io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
{
struct rb_io_buffer *buffer = get_io_buffer_for_writing(self);
if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
}
size_t offset = io_buffer_extract_offset(_offset);
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!");
}
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(buffer, type, &offset, value);
}
return SIZET2NUM(offset);
}
#size
[ GitHub ]
#slice([offset, [length]]) ⇒ Buffer
Produce another Buffer which is a slice (or view into) the current one
starting at offset bytes and going for length bytes.
The slicing happens without copying memory. The slice retains its root buffer and becomes invalid if that root is freed, transferred, resized so that the slice is outside its bounds, or otherwise invalidated.
If the offset is not given, it will be zero. If the offset is negative, it
will raise an ::ArgumentError.
If the length is not given, the slice will be as long as the original
buffer minus the specified offset. If the length is negative, it will raise
an ::ArgumentError.
Raises RuntimeError if the offsetlength is out of the current
buffer's bounds.
string = 'test'
buffer = IO::Buffer.for(string).dup
slice = buffer.slice
# =>
# #<IO::Buffer 0x0000000108338e68+4 SLICE>
# 0x00000000 74 65 73 74 test
buffer.slice(2)
# =>
# #<IO::Buffer 0x0000000108338e6a+2 SLICE>
# 0x00000000 73 74 st
slice = buffer.slice(1, 2)
# =>
# #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
# 0x00000000 65 73 es
# Put "o" into 0s position of the slice
slice.set_string('o', 0)
slice
# =>
# #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE>
# 0x00000000 6f 73 os
# it is also visible at position 1 of the original buffer
buffer
# =>
# #<IO::Buffer 0x00007fc3d31e2d80+4 INTERNAL>
# 0x00000000 74 6f 73 74 tost
# File 'io_buffer.c', line 2026
static VALUE
io_buffer_slice(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
size_t offset, length;
struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
return rb_io_buffer_slice(buffer, self, offset, length);
}
#to_s
[ GitHub ]
#transfer ⇒ Buffer
Transfers ownership of the underlying memory to a new buffer, causing the current buffer to become uninitialized.
buffer = IO::Buffer.for('test')
other = buffer.transfer
other
# =>
# #<IO::Buffer 0x00007f136a15f7b0+4 EXTERNAL READONLY SLICE>
# 0x00000000 74 65 73 74 test
buffer
# =>
# #<IO::Buffer 0x0000000000000000+0 NULL EXTERNAL READONLY>
buffer.null?
# => true
A frozen buffer cannot transfer ownership, as that would leave it uninitialized:
buffer = IO::Buffer.for('test').freeze
buffer.transfer
# in `transfer': can't modify frozen IO::Buffer (FrozenError)
# File 'io_buffer.c', line 2081
static VALUE
io_buffer_transfer(VALUE self)
{
rb_check_frozen(self);
return rb_io_buffer_transfer(self);
}
#values(buffer_type, [offset, [count]]) ⇒ Array
# File 'io_buffer.c', line 2751
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 = TYPE_ID(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, [offset, [length]]) ⇒ written length, -errno
Perform one write operation of at most length bytes to io from the
buffer starting at offset. A short write is a normal result. If an error
occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the
buffer. If length is not given, it defaults to the size of the buffer
minus the offset. A zero length is a no-op.
out = File.open('output.txt', 'wb')
IO::Buffer.for('1234567').write(out, 0, 3)
This leads to 123 being written into output.txt
# File 'io_buffer.c', line 3708
static VALUE
io_buffer_write(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 3);
VALUE io = argv[0];
size_t offset, length;
io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
return rb_io_buffer_write(self, io, offset, length);
}
#xor!(mask) ⇒ Buffer
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
# File 'io_buffer.c', line 4128
static VALUE
io_buffer_xor_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size);
return self;
}
#|(mask) ⇒ Buffer
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
# File 'io_buffer.c', line 3883
static VALUE
io_buffer_or(VALUE self, VALUE mask)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
struct rb_io_buffer *mask_buffer = get_io_buffer(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = get_io_buffer(output);
memory_or(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
#~ ⇒ Buffer
# File 'io_buffer.c', line 3973
static VALUE
io_buffer_not(VALUE self)
{
struct rb_io_buffer *buffer = get_io_buffer(self);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer *output_buffer = get_io_buffer(output);
memory_not(output_buffer->base, base, size);
return output;
}