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
|
# frozen_string_literal: true
# Copyright (C) 2016-2023 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.
module Mongo
class Server
class AppMetadata
# Implements the metadata truncation logic described in the handshake
# spec.
#
# @api private
class Truncator
# @return [ BSON::Document ] the document being truncated.
attr_reader :document
# The max application metadata document byte size.
MAX_DOCUMENT_SIZE = 512
# Creates a new Truncator instance and tries enforcing the maximum
# document size on the given document.
#
# @param [ BSON::Document] document The document to (potentially)
# truncate.
#
# @note The document is modified in-place; if you wish to keep the
# original unchanged, you must deep-clone it prior to sending it to
# a truncator.
def initialize(document)
@document = document
try_truncate!
end
# The current size of the document, in bytes, as a serialized BSON
# document.
#
# @return [ Integer ] the size of the document
def size
@document.to_bson.to_s.length
end
# Whether the document fits within the required maximum document size.
#
# @return [ true | false ] if the document is okay or not.
def ok?
size <= MAX_DOCUMENT_SIZE
end
private
# How many extra bytes must be trimmed before the document may be
# considered #ok?.
#
# @return [ Integer ] how many bytes larger the document is than the
# maximum document size.
def excess
size - MAX_DOCUMENT_SIZE
end
# Attempt to truncate the document using the documented metadata
# priorities (see the handshake specification).
def try_truncate!
%i[ env_fields os_fields env platform ].each do |target|
break if ok?
send(:"try_truncate_#{target}!")
end
end
# Attempt to truncate or remove the {{:platform}} key from the
# document.
def try_truncate_platform!
@document.delete(:platform) unless try_truncate_string(@document[:platform])
end
# Attempt to truncate the keys in the {{:env}} subdocument.
def try_truncate_env_fields!
try_truncate_hash(@document[:env], reserved: %w[ name ])
end
# Attempt to truncate the keys in the {{:os}} subdocument.
def try_truncate_os_fields!
try_truncate_hash(@document[:os], reserved: %w[ type ])
end
# Remove the {{:env}} key from the document.
def try_truncate_env!
@document.delete(:env)
end
# A helper method for truncating a string (in-place) by whatever
# {{#excess}} is required.
#
# @param [ String ] string the string value to truncate.
#
# @note the parameter is modified in-place.
def try_truncate_string(string)
length = string&.length || 0
return false if excess > length
string[(length - excess)..-1] = ''
end
# A helper method for removing the keys of a Hash (in-place) until
# the document is the necessary size. The keys are considered in order
# (using the Hash's native key ordering), and each will be removed from
# the hash in turn, until the document is the necessary size.
#
# Any keys in the {{reserved}} list will be ignored.
#
# @param [ Hash | nil ] hash the Hash instance to consider.
# @param [ Array ] reserved the list of keys to ignore in the hash.
#
# @note the hash parameter is modified in-place.
def try_truncate_hash(hash, reserved: [])
return false unless hash
keys = hash.keys - reserved
keys.each do |key|
hash.delete(key)
return true if ok?
end
false
end
end
end
end
end
|