File: binary.rb

package info (click to toggle)
ruby-mongo 2.21.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 14,764 kB
  • sloc: ruby: 108,806; makefile: 5; sh: 2
file content (158 lines) | stat: -rw-r--r-- 5,604 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# frozen_string_literal: true
# rubocop:todo all

# Copyright (C) 2019-2020 MongoDB Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

require 'ffi'

module Mongo
  module Crypt

    # A wrapper around mongocrypt_binary_t, a non-owning buffer of
    # uint-8 byte data. Each Binary instance keeps a copy of the data
    # passed to it in order to keep that data alive.
    #
    # @api private
    class Binary
      # Create a new Binary object that wraps a byte string
      #
      # @param [ String ] data The data string wrapped by the
      #   byte buffer (optional)
      # @param [ FFI::Pointer ] pointer A pointer to an existing
      #   mongocrypt_binary_t object
      #
      # @note When initializing a Binary object with a string or a pointer,
      # it is recommended that you use #self.from_pointer or #self.from_data
      # methods
      def initialize(data: nil, pointer: nil)
        if data
          # Represent data string as array of uint-8 bytes
          bytes = data.unpack('C*')

          # FFI::MemoryPointer automatically frees memory when it goes out of scope
          @data_p = FFI::MemoryPointer.new(bytes.length)
                    .write_array_of_uint8(bytes)

          # FFI::AutoPointer uses a custom release strategy to automatically free
          # the pointer once this object goes out of scope
          @bin = FFI::AutoPointer.new(
            Binding.mongocrypt_binary_new_from_data(@data_p, bytes.length),
            Binding.method(:mongocrypt_binary_destroy)
          )
        elsif pointer
          # If the Binary class is used this way, it means that the pointer
          # for the underlying mongocrypt_binary_t object is allocated somewhere
          # else. It is not the responsibility of this class to de-allocate data.
          @bin = pointer
        else
          # FFI::AutoPointer uses a custom release strategy to automatically free
          # the pointer once this object goes out of scope
          @bin = FFI::AutoPointer.new(
            Binding.mongocrypt_binary_new,
            Binding.method(:mongocrypt_binary_destroy)
          )
        end
      end

      # Initialize a Binary object from an existing pointer to a mongocrypt_binary_t
      # object.
      #
      # @param [ FFI::Pointer ] pointer A pointer to an existing
      #   mongocrypt_binary_t object
      #
      # @return [ Mongo::Crypt::Binary ] A new binary object
      def self.from_pointer(pointer)
        self.new(pointer: pointer)
      end

      # Initialize a Binary object with a string. The Binary object will store a
      # copy of the specified string and destroy the allocated memory when
      # it goes out of scope.
      #
      # @param [ String ] data A string to be wrapped by the Binary object
      #
      # @return [ Mongo::Crypt::Binary ] A new binary object
      def self.from_data(data)
        self.new(data: data)
      end

      # Overwrite the existing data wrapped by this Binary object
      #
      # @note The data passed in must not take up more memory than the
      # original memory allocated to the underlying mongocrypt_binary_t
      # object. Do NOT use this method unless required to do so by libmongocrypt.
      #
      # @param [ String ] data The new string data to be wrapped by this binary object
      #
      # @return [ true ] Always true
      #
      # @raise [ ArgumentError ] Raises when trying to write more data
      # than was originally allocated or when writing to an object that
      # already owns data.
      def write(data)
        if @data
          raise ArgumentError, 'Cannot write to an owned Binary'
        end

        # Cannot write a string that's longer than the space currently allocated
        # by the mongocrypt_binary_t object
        str_p = Binding.get_binary_data_direct(ref)
        len = Binding.get_binary_len_direct(ref)

        if len < data.bytesize
          raise ArgumentError.new(
            "Cannot write #{data.bytesize} bytes of data to a Binary object " +
            "that was initialized with #{Binding.get_binary_len_direct(@bin)} bytes."
          )
        end

        str_p.put_bytes(0, data)

        true
      end

      # Returns the data stored as a string
      #
      # @return [ String ] Data stored in the mongocrypt_binary_t as a string
      def to_s
        str_p = Binding.get_binary_data_direct(ref)
        len = Binding.get_binary_len_direct(ref)
        str_p.read_string(len)
      end

      # Returns the reference to the underlying mongocrypt_binary_t
      # object
      #
      # @return [ FFI::Pointer ] The underlying mongocrypt_binary_t object
      def ref
        @bin
      end

      # Wraps a String with a mongocrypt_binary_t, yielding an FFI::Pointer
      # to the wrapped struct.
      def self.wrap_string(str)
        binary_p = Binding.mongocrypt_binary_new_from_data(
          FFI::MemoryPointer.from_string(str),
          str.bytesize,
        )
        begin
          yield binary_p
        ensure
          Binding.mongocrypt_binary_destroy(binary_p)
        end
      end
    end
  end
end