From cea34bd8082784051542131ddf8a353f5656ac88 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 9 May 2022 17:19:01 +1200 Subject: Add basic binary operators (and, or, xor, not) to `IO::Buffer`. (#5893) --- io_buffer.c | 405 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 405 insertions(+) (limited to 'io_buffer.c') diff --git a/io_buffer.c b/io_buffer.c index 38ec76b599..d9a9f40bd7 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -20,6 +20,7 @@ VALUE rb_eIOBufferLockedError; VALUE rb_eIOBufferAllocationError; VALUE rb_eIOBufferAccessError; VALUE rb_eIOBufferInvalidatedError; +VALUE rb_eIOBufferMaskError; size_t RUBY_IO_BUFFER_PAGE_SIZE; size_t RUBY_IO_BUFFER_DEFAULT_SIZE; @@ -1692,6 +1693,40 @@ io_buffer_copy_from(struct rb_io_buffer *data, const void *source_base, size_t s return SIZET2NUM(length); } +/* + * call-seq: + * dup -> io_buffer + * clone -> io_buffer + * + * Make an internal copy of the source buffer. Updates to the copy will not + * affect the source buffer. + * + * source = IO::Buffer.for("Hello World") + * # => + * # # + * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World + * buffer = source.dup + * # => + * # # + * # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World + * + */ +static VALUE +rb_io_buffer_initialize_copy(VALUE self, VALUE source) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + const void *source_base; + size_t source_size; + + rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size); + + io_buffer_initialize(data, NULL, source_size, io_flags_for_size(source_size), Qnil); + + return io_buffer_copy_from(data, source_base, source_size, 0, NULL); +} + /* * call-seq: * copy(source, [offset, [length, [source_offset]]]) -> size @@ -2120,6 +2155,363 @@ io_buffer_pwrite(VALUE self, VALUE io, VALUE length, VALUE offset) return rb_io_buffer_pwrite(self, io, RB_NUM2SIZE(length), NUM2OFFT(offset)); } +static inline void +io_buffer_check_mask(const struct rb_io_buffer *buffer) +{ + if (buffer->size == 0) + rb_raise(rb_eIOBufferMaskError, "Zero-length mask given!"); +} + +static void +memory_and(unsigned char * restrict output, unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + output[offset] = base[offset] & mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source & mask -> io_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") + * # => + * # # + * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89. + */ +static VALUE +io_buffer_and(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + + VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); + struct rb_io_buffer *output_data = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + + memory_and(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + + return output; +} + +static void +memory_or(unsigned char * restrict output, unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + output[offset] = base[offset] | mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source | mask -> io_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") + * # => + * # # + * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0 + */ +static VALUE +io_buffer_or(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + + VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); + struct rb_io_buffer *output_data = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + + memory_or(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + + return output; +} + +static void +memory_xor(unsigned char * restrict output, unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + output[offset] = base[offset] ^ mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source ^ mask -> io_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") + * # => + * # # + * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0 + */ +static VALUE +io_buffer_xor(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + + VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); + struct rb_io_buffer *output_data = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + + memory_xor(output_data->base, data->base, data->size, mask_data->base, mask_data->size); + + return output; +} + +static void +memory_not(unsigned char * restrict output, unsigned char * restrict base, size_t size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + output[offset] = ~base[offset]; + } +} + +/* + * call-seq: + * ~source -> io_buffer + * + * Generate a new buffer the same size as the source by applying the binary NOT + * operation to the source. + * + * ~IO::Buffer.for("1234567890") + * # => + * # # + * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf .......... + */ +static VALUE +io_buffer_not(VALUE self) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + VALUE output = rb_io_buffer_new(NULL, data->size, io_flags_for_size(data->size)); + struct rb_io_buffer *output_data = NULL; + TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_data); + + memory_not(output_data->base, data->base, data->size); + + return output; +} + +static inline int +io_buffer_overlaps(const struct rb_io_buffer *a, const struct rb_io_buffer *b) +{ + if (a->base > b->base) { + return io_buffer_overlaps(b, a); + } + + return (b->base >= a->base) && (b->base <= (void*)((unsigned char *)a->base + a->size)); +} + +static inline void +io_buffer_check_overlaps(struct rb_io_buffer *a, struct rb_io_buffer *b) +{ + if (io_buffer_overlaps(a, b)) + rb_raise(rb_eIOBufferMaskError, "Mask overlaps source data!"); +} + +static void +memory_and_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + base[offset] &= mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source.and!(mask) -> io_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. + * # => + * # # + * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 + * + * source.and!(IO::Buffer.for("\xFF\x00\x00\xFF")) + * # => + * # # + * # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89. + */ +static VALUE +io_buffer_and_inplace(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + io_buffer_check_overlaps(data, mask_data); + + void *base; + size_t size; + io_buffer_get_bytes_for_writing(data, &base, &size); + + memory_and_inplace(base, size, mask_data->base, mask_data->size); + + return self; +} + +static void +memory_or_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + base[offset] |= mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source.or!(mask) -> io_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. + * # => + * # # + * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 + * + * source.or!(IO::Buffer.for("\xFF\x00\x00\xFF")) + * # => + * # # + * # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0 + */ +static VALUE +io_buffer_or_inplace(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + io_buffer_check_overlaps(data, mask_data); + + void *base; + size_t size; + io_buffer_get_bytes_for_writing(data, &base, &size); + + memory_or_inplace(base, size, mask_data->base, mask_data->size); + + return self; +} + +static void +memory_xor_inplace(unsigned char * restrict base, size_t size, unsigned char * restrict mask, size_t mask_size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + base[offset] ^= mask[offset % mask_size]; + } +} + +/* + * call-seq: + * source.xor!(mask) -> io_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. + * # => + * # # + * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 + * + * source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF")) + * # => + * # # + * # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0 + */ +static VALUE +io_buffer_xor_inplace(VALUE self, VALUE mask) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + struct rb_io_buffer *mask_data = NULL; + TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_data); + + io_buffer_check_mask(mask_data); + io_buffer_check_overlaps(data, mask_data); + + void *base; + size_t size; + io_buffer_get_bytes_for_writing(data, &base, &size); + + memory_xor_inplace(base, size, mask_data->base, mask_data->size); + + return self; +} + +static void +memory_not_inplace(unsigned char * restrict base, size_t size) +{ + for (size_t offset = 0; offset < size; offset += 1) { + base[offset] = ~base[offset]; + } +} + +/* + * call-seq: + * source.not! -> io_buffer + * + * 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. + * # => + * # # + * # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 + * + * source.not! + * # => + * # # + * # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf .......... + */ +static VALUE +io_buffer_not_inplace(VALUE self) +{ + struct rb_io_buffer *data = NULL; + TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, data); + + void *base; + size_t size; + io_buffer_get_bytes_for_writing(data, &base, &size); + + memory_not_inplace(base, size); + + return self; +} + /* * Document-class: IO::Buffer * @@ -2211,6 +2603,7 @@ Init_IO_Buffer(void) rb_eIOBufferAllocationError = rb_define_class_under(rb_cIOBuffer, "AllocationError", rb_eRuntimeError); rb_eIOBufferAccessError = rb_define_class_under(rb_cIOBuffer, "AccessError", rb_eRuntimeError); rb_eIOBufferInvalidatedError = rb_define_class_under(rb_cIOBuffer, "InvalidatedError", rb_eRuntimeError); + rb_eIOBufferMaskError = rb_define_class_under(rb_cIOBuffer, "MaskError", rb_eArgError); rb_define_alloc_func(rb_cIOBuffer, rb_io_buffer_type_allocate); rb_define_singleton_method(rb_cIOBuffer, "for", rb_io_buffer_type_for, 1); @@ -2233,6 +2626,7 @@ Init_IO_Buffer(void) // General use: rb_define_method(rb_cIOBuffer, "initialize", rb_io_buffer_initialize, -1); + rb_define_method(rb_cIOBuffer, "initialize_copy", rb_io_buffer_initialize_copy, 1); rb_define_method(rb_cIOBuffer, "inspect", rb_io_buffer_inspect, 0); rb_define_method(rb_cIOBuffer, "hexdump", rb_io_buffer_hexdump, 0); rb_define_method(rb_cIOBuffer, "to_s", rb_io_buffer_to_s, 0); @@ -2295,6 +2689,17 @@ Init_IO_Buffer(void) rb_define_method(rb_cIOBuffer, "get_string", io_buffer_get_string, -1); rb_define_method(rb_cIOBuffer, "set_string", io_buffer_set_string, -1); + // Binary data manipulations: + rb_define_method(rb_cIOBuffer, "&", io_buffer_and, 1); + rb_define_method(rb_cIOBuffer, "|", io_buffer_or, 1); + rb_define_method(rb_cIOBuffer, "^", io_buffer_xor, 1); + rb_define_method(rb_cIOBuffer, "~", io_buffer_not, 0); + + rb_define_method(rb_cIOBuffer, "and!", io_buffer_and_inplace, 1); + rb_define_method(rb_cIOBuffer, "or!", io_buffer_or_inplace, 1); + rb_define_method(rb_cIOBuffer, "xor!", io_buffer_xor_inplace, 1); + rb_define_method(rb_cIOBuffer, "not!", io_buffer_not_inplace, 0); + // IO operations: rb_define_method(rb_cIOBuffer, "read", io_buffer_read, 2); rb_define_method(rb_cIOBuffer, "pread", io_buffer_pread, 3); -- cgit v1.2.1