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
// Copyright 2015 The etcd Authors
// Copyright 2026 Leo Cheng
//
// 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.

///|
/// The sentinel used for an unbounded size cap: no `entries`/`slice` request is
/// ever limited by size. Mirrors etcd's `noLimit = math.MaxUint64`.
pub let no_limit : UInt64 = 18446744073709551615

///|
/// The number of bytes a base-128 varint of `v` occupies.
fn varint_len(v : UInt64) -> UInt64 {
  let mut n : UInt64 = 1
  let mut x = v
  while x >= 128 {
    x = x / 128
    n = n + 1
  }
  n
}

///|
/// The protobuf wire size of one entry, following etcd's `proto.Size`: each
/// non-zero field costs a tag byte plus its varint/length-delimited encoding.
/// Only the byte total matters — it is what `limit_size` and `Storage.Entries`
/// budget against.
pub fn entry_encoding_size(e : Entry) -> UInt64 {
  // The entry type is a first-class field of every entry here (unlike etcd's
  // optional proto field), so its tag + one-byte enum value is always counted.
  let mut size : UInt64 = 2
  if e.term != 0 {
    size = size + 1 + varint_len(e.term)
  }
  if e.index != 0 {
    size = size + 1 + varint_len(e.index)
  }
  let dl = e.command.length().to_uint64()
  if dl > 0 {
    size = size + 1 + varint_len(dl) + dl
  }
  size
}

///|
/// The total encoding size of a run of entries.
pub fn ents_size(entries : ArrayView[Entry]) -> UInt64 {
  let mut size : UInt64 = 0
  for e in entries {
    size = size + entry_encoding_size(e)
  }
  size
}

///|
/// The longest prefix of `entries` whose total encoding size does not exceed
/// `max_size`. Always returns at least one entry when the input is non-empty —
/// so a single oversized entry is still returned — matching etcd's `limitSize`.
pub fn limit_size(
  entries : ArrayView[Entry],
  max_size : UInt64,
) -> Array[Entry] {
  if entries.is_empty() {
    return []
  }
  let out : Array[Entry] = [entries[0]]
  let mut size = entry_encoding_size(entries[0])
  let mut i = 1
  while i < entries.length() {
    size = size + entry_encoding_size(entries[i])
    if size > max_size {
      break
    }
    out.push(entries[i])
    i = i + 1
  }
  out
}

///|
/// The size of an entry's payload: its command bytes only, independent of term
/// and index. Empty-payload entries (like the no-op a new leader appends) are
/// zero size, so they do not count against the uncommitted-log quota. Mirrors
/// etcd's `payloadSize`.
pub fn payload_size(e : Entry) -> UInt64 {
  e.command.length().to_uint64()
}

///|
/// The total payload size of a run of entries (etcd's `payloadsSize`).
pub fn payloads_size(entries : ArrayView[Entry]) -> UInt64 {
  let mut s : UInt64 = 0
  for e in entries {
    s = s + payload_size(e)
  }
  s
}