1
use crate::{
2
  abi,
3
  drop_wrapped_c_void_ptr,
4
  str_to_module_buffer,
5
  strs_to_module_buffers,
6
  wrap_into_c_void_ptr,
7
  EnvoyCounterId,
8
  EnvoyCounterVecId,
9
  EnvoyGaugeId,
10
  EnvoyGaugeVecId,
11
  EnvoyHistogramId,
12
  EnvoyHistogramVecId,
13
  NEW_LOAD_BALANCER_CONFIG_FUNCTION,
14
};
15
use mockall::*;
16
use std::sync::Arc;
17

            
18
/// Trait for interacting with the Envoy load balancer and its context.
19
///
20
/// This trait provides access to both cluster/host information and request context.
21
/// The cluster/host methods are always available, while the context methods are only
22
/// valid during the [`LoadBalancer::choose_host`] callback.
23
#[automock]
24
pub trait EnvoyLoadBalancer {
25
  /// Returns the cluster name.
26
  fn get_cluster_name(&self) -> String;
27

            
28
  /// Returns the number of all hosts at a given priority.
29
  fn get_hosts_count(&self, priority: u32) -> usize;
30

            
31
  /// Returns the number of healthy hosts at a given priority.
32
  fn get_healthy_hosts_count(&self, priority: u32) -> usize;
33

            
34
  /// Returns the number of degraded hosts at a given priority.
35
  fn get_degraded_hosts_count(&self, priority: u32) -> usize;
36

            
37
  /// Returns the number of priority levels.
38
  fn get_priority_set_size(&self) -> usize;
39

            
40
  /// Returns the address of a healthy host by index at a given priority.
41
  fn get_healthy_host_address(&self, priority: u32, index: usize) -> Option<String>;
42

            
43
  /// Returns the weight of a healthy host by index at a given priority.
44
  fn get_healthy_host_weight(&self, priority: u32, index: usize) -> u32;
45

            
46
  /// Returns the health status of a host by index within all hosts at a given priority.
47
  fn get_host_health(
48
    &self,
49
    priority: u32,
50
    index: usize,
51
  ) -> abi::envoy_dynamic_module_type_host_health;
52

            
53
  /// Looks up a host by its address string across all priorities and returns its health status.
54
  /// This provides O(1) lookup by address using the cross-priority host map, instead of requiring
55
  /// iteration through all hosts by index.
56
  ///
57
  /// The address must match the format "ip:port" (e.g., "10.0.0.1:8080").
58
  fn get_host_health_by_address(
59
    &self,
60
    address: &str,
61
  ) -> Option<abi::envoy_dynamic_module_type_host_health>;
62

            
63
  /// Returns the address of a host by index within all hosts at a given priority.
64
  fn get_host_address(&self, priority: u32, index: usize) -> Option<String>;
65

            
66
  /// Returns the weight of a host by index within all hosts at a given priority.
67
  fn get_host_weight(&self, priority: u32, index: usize) -> u32;
68

            
69
  /// Returns the value of a per-host stat. This provides access to host-level counters and gauges
70
  /// such as total connections, request errors, active requests, and active connections.
71
  fn get_host_stat(
72
    &self,
73
    priority: u32,
74
    index: usize,
75
    stat: abi::envoy_dynamic_module_type_host_stat,
76
  ) -> u64;
77

            
78
  /// Returns the locality information (region, zone, sub_zone) for a host by index within all
79
  /// hosts at a given priority. This enables zone-aware and locality-aware load balancing.
80
  fn get_host_locality(&self, priority: u32, index: usize) -> Option<(String, String, String)>;
81

            
82
  /// Stores an opaque value on a host identified by priority and index. This data is stored per
83
  /// load balancer instance (per worker thread) and can be used for per-host state such as moving
84
  /// averages or request tracking. Use 0 to clear the data.
85
  fn set_host_data(&self, priority: u32, index: usize, data: usize) -> bool;
86

            
87
  /// Retrieves a previously stored opaque value for a host. Returns `None` if the host was not
88
  /// found. Returns `Some(0)` if the host exists but no data was stored.
89
  fn get_host_data(&self, priority: u32, index: usize) -> Option<usize>;
90

            
91
  /// Returns the string metadata value for a host by looking up the given filter name and key in
92
  /// the host's endpoint metadata. Returns `None` if the key was not found or the value is not a
93
  /// string.
94
  fn get_host_metadata_string(
95
    &self,
96
    priority: u32,
97
    index: usize,
98
    filter_name: &str,
99
    key: &str,
100
  ) -> Option<String>;
101

            
102
  /// Returns the number metadata value for a host by looking up the given filter name and key in
103
  /// the host's endpoint metadata. Returns `None` if the key was not found or the value is not a
104
  /// number.
105
  fn get_host_metadata_number(
106
    &self,
107
    priority: u32,
108
    index: usize,
109
    filter_name: &str,
110
    key: &str,
111
  ) -> Option<f64>;
112

            
113
  /// Returns the bool metadata value for a host by looking up the given filter name and key in
114
  /// the host's endpoint metadata. Returns `None` if the key was not found or the value is not a
115
  /// bool.
116
  fn get_host_metadata_bool(
117
    &self,
118
    priority: u32,
119
    index: usize,
120
    filter_name: &str,
121
    key: &str,
122
  ) -> Option<bool>;
123

            
124
  /// Returns the number of locality buckets for the healthy hosts at a given priority.
125
  fn get_locality_count(&self, priority: u32) -> usize;
126

            
127
  /// Returns the number of healthy hosts in a specific locality bucket at a given priority.
128
  fn get_locality_host_count(&self, priority: u32, locality_index: usize) -> usize;
129

            
130
  /// Returns the address of a host within a specific locality bucket at a given priority.
131
  fn get_locality_host_address(
132
    &self,
133
    priority: u32,
134
    locality_index: usize,
135
    host_index: usize,
136
  ) -> Option<String>;
137

            
138
  /// Returns the weight of a locality bucket at a given priority.
139
  fn get_locality_weight(&self, priority: u32, locality_index: usize) -> u32;
140

            
141
  // -------------------------------------------------------------------------
142
  // Member update methods are only valid during on_host_membership_update callback.
143
  // -------------------------------------------------------------------------
144

            
145
  /// Returns the address of an added or removed host during on_host_membership_update.
146
  /// If `is_added` is true, returns the address of the added host at the given index.
147
  /// If `is_added` is false, returns the address of the removed host at the given index.
148
  /// Only valid during on_host_membership_update callback.
149
  fn get_member_update_host_address(&self, index: usize, is_added: bool) -> Option<String>;
150

            
151
  // -------------------------------------------------------------------------
152
  // Context methods are only valid during choose_host callback.
153
  // -------------------------------------------------------------------------
154

            
155
  /// Returns whether the context is available.
156
  /// Context methods will return default values if this returns false.
157
  fn has_context(&self) -> bool;
158

            
159
  /// Computes a hash key for consistent hashing from the request context.
160
  /// Only valid during choose_host callback.
161
  fn context_compute_hash_key(&self) -> Option<u64>;
162

            
163
  /// Returns the number of downstream request headers.
164
  /// Only valid during choose_host callback.
165
  fn context_get_downstream_headers_size(&self) -> usize;
166

            
167
  /// Returns all downstream request headers as a vector of (key, value) pairs.
168
  /// Only valid during choose_host callback.
169
  fn context_get_downstream_headers(&self) -> Option<Vec<(String, String)>>;
170

            
171
  /// Returns a downstream request header value by key and index.
172
  /// Since a header can have multiple values, the index is used to get the specific value.
173
  /// Returns the value and optionally the total number of values for the key.
174
  /// Only valid during choose_host callback.
175
  fn context_get_downstream_header(&self, key: &str, index: usize) -> Option<(String, usize)>;
176

            
177
  /// Returns the maximum number of times host selection should be retried if the chosen host
178
  /// is rejected by [`context_should_select_another_host`]. Built-in load balancers use this
179
  /// value as the upper bound of a retry loop during host selection.
180
  /// Only valid during choose_host callback.
181
  fn context_get_host_selection_retry_count(&self) -> u32;
182

            
183
  /// Checks whether the load balancer should reject the given host and retry selection.
184
  /// This is used during retries to avoid selecting hosts that were already attempted.
185
  /// The host is identified by priority and index within all hosts at that priority.
186
  /// Only valid during choose_host callback.
187
  fn context_should_select_another_host(&self, priority: u32, index: usize) -> bool;
188

            
189
  /// Returns the override host address and strict mode flag from the context. Override host
190
  /// allows upstream filters to direct the load balancer to prefer a specific host by address.
191
  /// Returns `Some((address, strict))` if an override host is set, `None` otherwise. When
192
  /// `strict` is true, the load balancer should return no host if the override is not valid.
193
  /// Only valid during choose_host callback.
194
  fn context_get_override_host(&self) -> Option<(String, bool)>;
195
}
196

            
197
/// Implementation of EnvoyLoadBalancer that calls into the Envoy ABI.
198
pub struct EnvoyLoadBalancerImpl {
199
  lb_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr,
200
  context_ptr: abi::envoy_dynamic_module_type_lb_context_envoy_ptr,
201
}
202

            
203
impl EnvoyLoadBalancerImpl {
204
  /// Creates a new EnvoyLoadBalancerImpl with both LB and context pointers.
205
  pub fn new(
206
    lb_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr,
207
    context_ptr: abi::envoy_dynamic_module_type_lb_context_envoy_ptr,
208
  ) -> Self {
209
    Self {
210
      lb_ptr,
211
      context_ptr,
212
    }
213
  }
214
}
215

            
216
impl EnvoyLoadBalancer for EnvoyLoadBalancerImpl {
217
  fn get_cluster_name(&self) -> String {
218
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
219
      ptr: std::ptr::null(),
220
      length: 0,
221
    };
222
    unsafe {
223
      abi::envoy_dynamic_module_callback_lb_get_cluster_name(self.lb_ptr, &mut result);
224
      if !result.ptr.is_null() && result.length > 0 {
225
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
226
          result.ptr as *const _,
227
          result.length,
228
        ))
229
        .to_string()
230
      } else {
231
        String::new()
232
      }
233
    }
234
  }
235

            
236
  fn get_hosts_count(&self, priority: u32) -> usize {
237
    unsafe { abi::envoy_dynamic_module_callback_lb_get_hosts_count(self.lb_ptr, priority) }
238
  }
239

            
240
  fn get_healthy_hosts_count(&self, priority: u32) -> usize {
241
    unsafe { abi::envoy_dynamic_module_callback_lb_get_healthy_hosts_count(self.lb_ptr, priority) }
242
  }
243

            
244
  fn get_degraded_hosts_count(&self, priority: u32) -> usize {
245
    unsafe { abi::envoy_dynamic_module_callback_lb_get_degraded_hosts_count(self.lb_ptr, priority) }
246
  }
247

            
248
  fn get_priority_set_size(&self) -> usize {
249
    unsafe { abi::envoy_dynamic_module_callback_lb_get_priority_set_size(self.lb_ptr) }
250
  }
251

            
252
  fn get_healthy_host_address(&self, priority: u32, index: usize) -> Option<String> {
253
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
254
      ptr: std::ptr::null(),
255
      length: 0,
256
    };
257
    let found = unsafe {
258
      abi::envoy_dynamic_module_callback_lb_get_healthy_host_address(
259
        self.lb_ptr,
260
        priority,
261
        index,
262
        &mut result,
263
      )
264
    };
265
    if found && !result.ptr.is_null() && result.length > 0 {
266
      unsafe {
267
        Some(
268
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
269
            result.ptr as *const _,
270
            result.length,
271
          ))
272
          .to_string(),
273
        )
274
      }
275
    } else {
276
      None
277
    }
278
  }
279

            
280
  fn get_healthy_host_weight(&self, priority: u32, index: usize) -> u32 {
281
    unsafe {
282
      abi::envoy_dynamic_module_callback_lb_get_healthy_host_weight(self.lb_ptr, priority, index)
283
    }
284
  }
285

            
286
  fn get_host_health(
287
    &self,
288
    priority: u32,
289
    index: usize,
290
  ) -> abi::envoy_dynamic_module_type_host_health {
291
    unsafe { abi::envoy_dynamic_module_callback_lb_get_host_health(self.lb_ptr, priority, index) }
292
  }
293

            
294
  fn get_host_health_by_address(
295
    &self,
296
    address: &str,
297
  ) -> Option<abi::envoy_dynamic_module_type_host_health> {
298
    let address_buf = abi::envoy_dynamic_module_type_module_buffer {
299
      ptr: address.as_ptr() as *const _,
300
      length: address.len(),
301
    };
302
    let mut health: abi::envoy_dynamic_module_type_host_health =
303
      abi::envoy_dynamic_module_type_host_health::Unhealthy;
304
    let found = unsafe {
305
      abi::envoy_dynamic_module_callback_lb_get_host_health_by_address(
306
        self.lb_ptr,
307
        address_buf,
308
        &mut health,
309
      )
310
    };
311
    if found {
312
      Some(health)
313
    } else {
314
      None
315
    }
316
  }
317

            
318
  fn get_host_address(&self, priority: u32, index: usize) -> Option<String> {
319
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
320
      ptr: std::ptr::null(),
321
      length: 0,
322
    };
323
    let found = unsafe {
324
      abi::envoy_dynamic_module_callback_lb_get_host_address(
325
        self.lb_ptr,
326
        priority,
327
        index,
328
        &mut result,
329
      )
330
    };
331
    if found && !result.ptr.is_null() && result.length > 0 {
332
      unsafe {
333
        Some(
334
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
335
            result.ptr as *const _,
336
            result.length,
337
          ))
338
          .to_string(),
339
        )
340
      }
341
    } else {
342
      None
343
    }
344
  }
345

            
346
  fn get_host_weight(&self, priority: u32, index: usize) -> u32 {
347
    unsafe { abi::envoy_dynamic_module_callback_lb_get_host_weight(self.lb_ptr, priority, index) }
348
  }
349

            
350
  fn get_host_stat(
351
    &self,
352
    priority: u32,
353
    index: usize,
354
    stat: abi::envoy_dynamic_module_type_host_stat,
355
  ) -> u64 {
356
    unsafe {
357
      abi::envoy_dynamic_module_callback_lb_get_host_stat(self.lb_ptr, priority, index, stat)
358
    }
359
  }
360

            
361
  fn get_host_locality(&self, priority: u32, index: usize) -> Option<(String, String, String)> {
362
    let mut region = abi::envoy_dynamic_module_type_envoy_buffer {
363
      ptr: std::ptr::null(),
364
      length: 0,
365
    };
366
    let mut zone = abi::envoy_dynamic_module_type_envoy_buffer {
367
      ptr: std::ptr::null(),
368
      length: 0,
369
    };
370
    let mut sub_zone = abi::envoy_dynamic_module_type_envoy_buffer {
371
      ptr: std::ptr::null(),
372
      length: 0,
373
    };
374
    let found = unsafe {
375
      abi::envoy_dynamic_module_callback_lb_get_host_locality(
376
        self.lb_ptr,
377
        priority,
378
        index,
379
        &mut region,
380
        &mut zone,
381
        &mut sub_zone,
382
      )
383
    };
384
    if !found {
385
      return None;
386
    }
387
    unsafe {
388
      let region_str = if !region.ptr.is_null() && region.length > 0 {
389
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
390
          region.ptr as *const _,
391
          region.length,
392
        ))
393
        .to_string()
394
      } else {
395
        String::new()
396
      };
397
      let zone_str = if !zone.ptr.is_null() && zone.length > 0 {
398
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
399
          zone.ptr as *const _,
400
          zone.length,
401
        ))
402
        .to_string()
403
      } else {
404
        String::new()
405
      };
406
      let sub_zone_str = if !sub_zone.ptr.is_null() && sub_zone.length > 0 {
407
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
408
          sub_zone.ptr as *const _,
409
          sub_zone.length,
410
        ))
411
        .to_string()
412
      } else {
413
        String::new()
414
      };
415
      Some((region_str, zone_str, sub_zone_str))
416
    }
417
  }
418

            
419
  fn set_host_data(&self, priority: u32, index: usize, data: usize) -> bool {
420
    unsafe {
421
      abi::envoy_dynamic_module_callback_lb_set_host_data(self.lb_ptr, priority, index, data)
422
    }
423
  }
424

            
425
  fn get_host_data(&self, priority: u32, index: usize) -> Option<usize> {
426
    let mut data: usize = 0;
427
    let found = unsafe {
428
      abi::envoy_dynamic_module_callback_lb_get_host_data(self.lb_ptr, priority, index, &mut data)
429
    };
430
    if found {
431
      Some(data)
432
    } else {
433
      None
434
    }
435
  }
436

            
437
  fn get_host_metadata_string(
438
    &self,
439
    priority: u32,
440
    index: usize,
441
    filter_name: &str,
442
    key: &str,
443
  ) -> Option<String> {
444
    let filter_buf = abi::envoy_dynamic_module_type_module_buffer {
445
      ptr: filter_name.as_ptr() as *const _,
446
      length: filter_name.len(),
447
    };
448
    let key_buf = abi::envoy_dynamic_module_type_module_buffer {
449
      ptr: key.as_ptr() as *const _,
450
      length: key.len(),
451
    };
452
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
453
      ptr: std::ptr::null(),
454
      length: 0,
455
    };
456
    let found = unsafe {
457
      abi::envoy_dynamic_module_callback_lb_get_host_metadata_string(
458
        self.lb_ptr,
459
        priority,
460
        index,
461
        filter_buf,
462
        key_buf,
463
        &mut result,
464
      )
465
    };
466
    if found && !result.ptr.is_null() && result.length > 0 {
467
      unsafe {
468
        Some(
469
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
470
            result.ptr as *const _,
471
            result.length,
472
          ))
473
          .to_string(),
474
        )
475
      }
476
    } else {
477
      None
478
    }
479
  }
480

            
481
  fn get_host_metadata_number(
482
    &self,
483
    priority: u32,
484
    index: usize,
485
    filter_name: &str,
486
    key: &str,
487
  ) -> Option<f64> {
488
    let filter_buf = abi::envoy_dynamic_module_type_module_buffer {
489
      ptr: filter_name.as_ptr() as *const _,
490
      length: filter_name.len(),
491
    };
492
    let key_buf = abi::envoy_dynamic_module_type_module_buffer {
493
      ptr: key.as_ptr() as *const _,
494
      length: key.len(),
495
    };
496
    let mut result: f64 = 0.0;
497
    let found = unsafe {
498
      abi::envoy_dynamic_module_callback_lb_get_host_metadata_number(
499
        self.lb_ptr,
500
        priority,
501
        index,
502
        filter_buf,
503
        key_buf,
504
        &mut result,
505
      )
506
    };
507
    if found {
508
      Some(result)
509
    } else {
510
      None
511
    }
512
  }
513

            
514
  fn get_host_metadata_bool(
515
    &self,
516
    priority: u32,
517
    index: usize,
518
    filter_name: &str,
519
    key: &str,
520
  ) -> Option<bool> {
521
    let filter_buf = abi::envoy_dynamic_module_type_module_buffer {
522
      ptr: filter_name.as_ptr() as *const _,
523
      length: filter_name.len(),
524
    };
525
    let key_buf = abi::envoy_dynamic_module_type_module_buffer {
526
      ptr: key.as_ptr() as *const _,
527
      length: key.len(),
528
    };
529
    let mut result: bool = false;
530
    let found = unsafe {
531
      abi::envoy_dynamic_module_callback_lb_get_host_metadata_bool(
532
        self.lb_ptr,
533
        priority,
534
        index,
535
        filter_buf,
536
        key_buf,
537
        &mut result,
538
      )
539
    };
540
    if found {
541
      Some(result)
542
    } else {
543
      None
544
    }
545
  }
546

            
547
  fn get_locality_count(&self, priority: u32) -> usize {
548
    unsafe { abi::envoy_dynamic_module_callback_lb_get_locality_count(self.lb_ptr, priority) }
549
  }
550

            
551
  fn get_locality_host_count(&self, priority: u32, locality_index: usize) -> usize {
552
    unsafe {
553
      abi::envoy_dynamic_module_callback_lb_get_locality_host_count(
554
        self.lb_ptr,
555
        priority,
556
        locality_index,
557
      )
558
    }
559
  }
560

            
561
  fn get_locality_host_address(
562
    &self,
563
    priority: u32,
564
    locality_index: usize,
565
    host_index: usize,
566
  ) -> Option<String> {
567
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
568
      ptr: std::ptr::null(),
569
      length: 0,
570
    };
571
    let found = unsafe {
572
      abi::envoy_dynamic_module_callback_lb_get_locality_host_address(
573
        self.lb_ptr,
574
        priority,
575
        locality_index,
576
        host_index,
577
        &mut result,
578
      )
579
    };
580
    if found && !result.ptr.is_null() && result.length > 0 {
581
      unsafe {
582
        Some(
583
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
584
            result.ptr as *const _,
585
            result.length,
586
          ))
587
          .to_string(),
588
        )
589
      }
590
    } else {
591
      None
592
    }
593
  }
594

            
595
  fn get_locality_weight(&self, priority: u32, locality_index: usize) -> u32 {
596
    unsafe {
597
      abi::envoy_dynamic_module_callback_lb_get_locality_weight(
598
        self.lb_ptr,
599
        priority,
600
        locality_index,
601
      )
602
    }
603
  }
604

            
605
  fn get_member_update_host_address(&self, index: usize, is_added: bool) -> Option<String> {
606
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
607
      ptr: std::ptr::null(),
608
      length: 0,
609
    };
610
    let found = unsafe {
611
      abi::envoy_dynamic_module_callback_lb_get_member_update_host_address(
612
        self.lb_ptr,
613
        index,
614
        is_added,
615
        &mut result,
616
      )
617
    };
618
    if found && !result.ptr.is_null() && result.length > 0 {
619
      Some(unsafe {
620
        std::str::from_utf8_unchecked(std::slice::from_raw_parts(
621
          result.ptr as *const _,
622
          result.length,
623
        ))
624
        .to_string()
625
      })
626
    } else {
627
      None
628
    }
629
  }
630

            
631
  fn has_context(&self) -> bool {
632
    !self.context_ptr.is_null()
633
  }
634

            
635
  fn context_compute_hash_key(&self) -> Option<u64> {
636
    if self.context_ptr.is_null() {
637
      return None;
638
    }
639
    let mut hash: u64 = 0;
640
    let found = unsafe {
641
      abi::envoy_dynamic_module_callback_lb_context_compute_hash_key(self.context_ptr, &mut hash)
642
    };
643
    if found {
644
      Some(hash)
645
    } else {
646
      None
647
    }
648
  }
649

            
650
  fn context_get_downstream_headers_size(&self) -> usize {
651
    if self.context_ptr.is_null() {
652
      return 0;
653
    }
654
    unsafe {
655
      abi::envoy_dynamic_module_callback_lb_context_get_downstream_headers_size(self.context_ptr)
656
    }
657
  }
658

            
659
  fn context_get_downstream_headers(&self) -> Option<Vec<(String, String)>> {
660
    if self.context_ptr.is_null() {
661
      return None;
662
    }
663
    let size = self.context_get_downstream_headers_size();
664
    if size == 0 {
665
      return Some(Vec::new());
666
    }
667
    let mut headers = vec![
668
      abi::envoy_dynamic_module_type_envoy_http_header {
669
        key_ptr: std::ptr::null(),
670
        key_length: 0,
671
        value_ptr: std::ptr::null(),
672
        value_length: 0,
673
      };
674
      size
675
    ];
676
    let success = unsafe {
677
      abi::envoy_dynamic_module_callback_lb_context_get_downstream_headers(
678
        self.context_ptr,
679
        headers.as_mut_ptr(),
680
      )
681
    };
682
    if !success {
683
      return None;
684
    }
685
    Some(
686
      headers
687
        .iter()
688
        .map(|h| unsafe {
689
          (
690
            std::str::from_utf8_unchecked(std::slice::from_raw_parts(
691
              h.key_ptr as *const _,
692
              h.key_length,
693
            ))
694
            .to_string(),
695
            std::str::from_utf8_unchecked(std::slice::from_raw_parts(
696
              h.value_ptr as *const _,
697
              h.value_length,
698
            ))
699
            .to_string(),
700
          )
701
        })
702
        .collect(),
703
    )
704
  }
705

            
706
  fn context_get_downstream_header(&self, key: &str, index: usize) -> Option<(String, usize)> {
707
    if self.context_ptr.is_null() {
708
      return None;
709
    }
710
    let key_buf = abi::envoy_dynamic_module_type_module_buffer {
711
      ptr: key.as_ptr() as *const _,
712
      length: key.len(),
713
    };
714
    let mut result = abi::envoy_dynamic_module_type_envoy_buffer {
715
      ptr: std::ptr::null(),
716
      length: 0,
717
    };
718
    let mut count: usize = 0;
719
    let found = unsafe {
720
      abi::envoy_dynamic_module_callback_lb_context_get_downstream_header(
721
        self.context_ptr,
722
        key_buf,
723
        &mut result,
724
        index,
725
        &mut count,
726
      )
727
    };
728
    if found {
729
      unsafe {
730
        Some((
731
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
732
            result.ptr as *const _,
733
            result.length,
734
          ))
735
          .to_string(),
736
          count,
737
        ))
738
      }
739
    } else {
740
      None
741
    }
742
  }
743

            
744
  fn context_get_host_selection_retry_count(&self) -> u32 {
745
    if self.context_ptr.is_null() {
746
      return 0;
747
    }
748
    unsafe {
749
      abi::envoy_dynamic_module_callback_lb_context_get_host_selection_retry_count(self.context_ptr)
750
    }
751
  }
752

            
753
  fn context_should_select_another_host(&self, priority: u32, index: usize) -> bool {
754
    if self.context_ptr.is_null() || self.lb_ptr.is_null() {
755
      return false;
756
    }
757
    unsafe {
758
      abi::envoy_dynamic_module_callback_lb_context_should_select_another_host(
759
        self.lb_ptr,
760
        self.context_ptr,
761
        priority,
762
        index,
763
      )
764
    }
765
  }
766

            
767
  fn context_get_override_host(&self) -> Option<(String, bool)> {
768
    if self.context_ptr.is_null() {
769
      return None;
770
    }
771
    let mut address = abi::envoy_dynamic_module_type_envoy_buffer {
772
      ptr: std::ptr::null(),
773
      length: 0,
774
    };
775
    let mut strict = false;
776
    let found = unsafe {
777
      abi::envoy_dynamic_module_callback_lb_context_get_override_host(
778
        self.context_ptr,
779
        &mut address,
780
        &mut strict,
781
      )
782
    };
783
    if found {
784
      unsafe {
785
        Some((
786
          std::str::from_utf8_unchecked(std::slice::from_raw_parts(
787
            address.ptr as *const _,
788
            address.length,
789
          ))
790
          .to_string(),
791
          strict,
792
        ))
793
      }
794
    } else {
795
      None
796
    }
797
  }
798
}
799

            
800
/// Trait for defining and recording custom metrics for load balancer modules.
801
///
802
/// An implementation of this trait is passed to the user's config creation function for defining
803
/// metrics. It can also be stored by the user and used at runtime (e.g., during host selection)
804
/// to record metric values. The raw pointer is safe to store and use from any thread because the
805
/// underlying C++ `DynamicModuleLbConfig` is thread-safe for metric operations.
806
#[automock]
807
#[allow(clippy::needless_lifetimes)]
808
pub trait EnvoyLbConfig: Send + Sync {
809
  // -------------------------------------------------------------------------
810
  // Define metrics (call during config creation).
811
  // -------------------------------------------------------------------------
812

            
813
  /// Define a new counter with the given name and no labels.
814
  fn define_counter(
815
    &self,
816
    name: &str,
817
  ) -> Result<EnvoyCounterId, abi::envoy_dynamic_module_type_metrics_result>;
818

            
819
  /// Define a new counter vec with the given name and label names.
820
  fn define_counter_vec<'a>(
821
    &self,
822
    name: &str,
823
    labels: &[&'a str],
824
  ) -> Result<EnvoyCounterVecId, abi::envoy_dynamic_module_type_metrics_result>;
825

            
826
  /// Define a new gauge with the given name and no labels.
827
  fn define_gauge(
828
    &self,
829
    name: &str,
830
  ) -> Result<EnvoyGaugeId, abi::envoy_dynamic_module_type_metrics_result>;
831

            
832
  /// Define a new gauge vec with the given name and label names.
833
  fn define_gauge_vec<'a>(
834
    &self,
835
    name: &str,
836
    labels: &[&'a str],
837
  ) -> Result<EnvoyGaugeVecId, abi::envoy_dynamic_module_type_metrics_result>;
838

            
839
  /// Define a new histogram with the given name and no labels.
840
  fn define_histogram(
841
    &self,
842
    name: &str,
843
  ) -> Result<EnvoyHistogramId, abi::envoy_dynamic_module_type_metrics_result>;
844

            
845
  /// Define a new histogram vec with the given name and label names.
846
  fn define_histogram_vec<'a>(
847
    &self,
848
    name: &str,
849
    labels: &[&'a str],
850
  ) -> Result<EnvoyHistogramVecId, abi::envoy_dynamic_module_type_metrics_result>;
851

            
852
  // -------------------------------------------------------------------------
853
  // Record metrics (call at runtime, e.g., during choose_host).
854
  // -------------------------------------------------------------------------
855

            
856
  /// Increment a previously defined counter by the given value.
857
  fn increment_counter(
858
    &self,
859
    id: EnvoyCounterId,
860
    value: u64,
861
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
862

            
863
  /// Increment a previously defined counter vec by the given value with label values.
864
  fn increment_counter_vec<'a>(
865
    &self,
866
    id: EnvoyCounterVecId,
867
    labels: &[&'a str],
868
    value: u64,
869
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
870

            
871
  /// Set the value of a previously defined gauge.
872
  fn set_gauge(
873
    &self,
874
    id: EnvoyGaugeId,
875
    value: u64,
876
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
877

            
878
  /// Set the value of a previously defined gauge vec with label values.
879
  fn set_gauge_vec<'a>(
880
    &self,
881
    id: EnvoyGaugeVecId,
882
    labels: &[&'a str],
883
    value: u64,
884
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
885

            
886
  /// Increase a previously defined gauge by the given value.
887
  fn increase_gauge(
888
    &self,
889
    id: EnvoyGaugeId,
890
    value: u64,
891
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
892

            
893
  /// Increase a previously defined gauge vec by the given value with label values.
894
  fn increase_gauge_vec<'a>(
895
    &self,
896
    id: EnvoyGaugeVecId,
897
    labels: &[&'a str],
898
    value: u64,
899
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
900

            
901
  /// Decrease a previously defined gauge by the given value.
902
  fn decrease_gauge(
903
    &self,
904
    id: EnvoyGaugeId,
905
    value: u64,
906
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
907

            
908
  /// Decrease a previously defined gauge vec by the given value with label values.
909
  fn decrease_gauge_vec<'a>(
910
    &self,
911
    id: EnvoyGaugeVecId,
912
    labels: &[&'a str],
913
    value: u64,
914
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
915

            
916
  /// Record a value in a previously defined histogram.
917
  fn record_histogram_value(
918
    &self,
919
    id: EnvoyHistogramId,
920
    value: u64,
921
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
922

            
923
  /// Record a value in a previously defined histogram vec with label values.
924
  fn record_histogram_value_vec<'a>(
925
    &self,
926
    id: EnvoyHistogramVecId,
927
    labels: &[&'a str],
928
    value: u64,
929
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result>;
930
}
931

            
932
/// Implementation of [`EnvoyLbConfig`] that calls into the Envoy ABI.
933
pub struct EnvoyLbConfigImpl {
934
  raw: abi::envoy_dynamic_module_type_lb_config_envoy_ptr,
935
}
936

            
937
// The raw pointer references C++ DynamicModuleLbConfig which is safe for metric operations
938
// from any thread.
939
unsafe impl Send for EnvoyLbConfigImpl {}
940
unsafe impl Sync for EnvoyLbConfigImpl {}
941

            
942
/// Converts an ABI metrics result to a Rust Result for recording operations.
943
fn metric_result_to_rust(
944
  res: abi::envoy_dynamic_module_type_metrics_result,
945
) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
946
  if res == abi::envoy_dynamic_module_type_metrics_result::Success {
947
    Ok(())
948
  } else {
949
    Err(res)
950
  }
951
}
952

            
953
impl EnvoyLbConfig for EnvoyLbConfigImpl {
954
  fn define_counter(
955
    &self,
956
    name: &str,
957
  ) -> Result<EnvoyCounterId, abi::envoy_dynamic_module_type_metrics_result> {
958
    let mut id: usize = 0;
959
    Result::from(unsafe {
960
      abi::envoy_dynamic_module_callback_lb_config_define_counter(
961
        self.raw,
962
        str_to_module_buffer(name),
963
        std::ptr::null_mut(),
964
        0,
965
        &mut id,
966
      )
967
    })?;
968
    Ok(EnvoyCounterId(id))
969
  }
970

            
971
  fn define_counter_vec(
972
    &self,
973
    name: &str,
974
    labels: &[&str],
975
  ) -> Result<EnvoyCounterVecId, abi::envoy_dynamic_module_type_metrics_result> {
976
    let mut label_bufs = strs_to_module_buffers(labels);
977
    let mut id: usize = 0;
978
    Result::from(unsafe {
979
      abi::envoy_dynamic_module_callback_lb_config_define_counter(
980
        self.raw,
981
        str_to_module_buffer(name),
982
        label_bufs.as_mut_ptr(),
983
        labels.len(),
984
        &mut id,
985
      )
986
    })?;
987
    Ok(EnvoyCounterVecId(id))
988
  }
989

            
990
  fn define_gauge(
991
    &self,
992
    name: &str,
993
  ) -> Result<EnvoyGaugeId, abi::envoy_dynamic_module_type_metrics_result> {
994
    let mut id: usize = 0;
995
    Result::from(unsafe {
996
      abi::envoy_dynamic_module_callback_lb_config_define_gauge(
997
        self.raw,
998
        str_to_module_buffer(name),
999
        std::ptr::null_mut(),
        0,
        &mut id,
      )
    })?;
    Ok(EnvoyGaugeId(id))
  }
  fn define_gauge_vec(
    &self,
    name: &str,
    labels: &[&str],
  ) -> Result<EnvoyGaugeVecId, abi::envoy_dynamic_module_type_metrics_result> {
    let mut label_bufs = strs_to_module_buffers(labels);
    let mut id: usize = 0;
    Result::from(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_define_gauge(
        self.raw,
        str_to_module_buffer(name),
        label_bufs.as_mut_ptr(),
        labels.len(),
        &mut id,
      )
    })?;
    Ok(EnvoyGaugeVecId(id))
  }
  fn define_histogram(
    &self,
    name: &str,
  ) -> Result<EnvoyHistogramId, abi::envoy_dynamic_module_type_metrics_result> {
    let mut id: usize = 0;
    Result::from(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_define_histogram(
        self.raw,
        str_to_module_buffer(name),
        std::ptr::null_mut(),
        0,
        &mut id,
      )
    })?;
    Ok(EnvoyHistogramId(id))
  }
  fn define_histogram_vec(
    &self,
    name: &str,
    labels: &[&str],
  ) -> Result<EnvoyHistogramVecId, abi::envoy_dynamic_module_type_metrics_result> {
    let mut label_bufs = strs_to_module_buffers(labels);
    let mut id: usize = 0;
    Result::from(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_define_histogram(
        self.raw,
        str_to_module_buffer(name),
        label_bufs.as_mut_ptr(),
        labels.len(),
        &mut id,
      )
    })?;
    Ok(EnvoyHistogramVecId(id))
  }
  fn increment_counter(
    &self,
    id: EnvoyCounterId,
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyCounterId(id) = id;
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_increment_counter(
        self.raw,
        id,
        std::ptr::null_mut(),
        0,
        value,
      )
    })
  }
  fn increment_counter_vec(
    &self,
    id: EnvoyCounterVecId,
    labels: &[&str],
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyCounterVecId(id) = id;
    let mut label_bufs = strs_to_module_buffers(labels);
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_increment_counter(
        self.raw,
        id,
        label_bufs.as_mut_ptr(),
        labels.len(),
        value,
      )
    })
  }
  fn set_gauge(
    &self,
    id: EnvoyGaugeId,
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeId(id) = id;
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_set_gauge(
        self.raw,
        id,
        std::ptr::null_mut(),
        0,
        value,
      )
    })
  }
  fn set_gauge_vec(
    &self,
    id: EnvoyGaugeVecId,
    labels: &[&str],
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeVecId(id) = id;
    let mut label_bufs = strs_to_module_buffers(labels);
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_set_gauge(
        self.raw,
        id,
        label_bufs.as_mut_ptr(),
        labels.len(),
        value,
      )
    })
  }
  fn increase_gauge(
    &self,
    id: EnvoyGaugeId,
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeId(id) = id;
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_increment_gauge(
        self.raw,
        id,
        std::ptr::null_mut(),
        0,
        value,
      )
    })
  }
  fn increase_gauge_vec(
    &self,
    id: EnvoyGaugeVecId,
    labels: &[&str],
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeVecId(id) = id;
    let mut label_bufs = strs_to_module_buffers(labels);
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_increment_gauge(
        self.raw,
        id,
        label_bufs.as_mut_ptr(),
        labels.len(),
        value,
      )
    })
  }
  fn decrease_gauge(
    &self,
    id: EnvoyGaugeId,
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeId(id) = id;
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_decrement_gauge(
        self.raw,
        id,
        std::ptr::null_mut(),
        0,
        value,
      )
    })
  }
  fn decrease_gauge_vec(
    &self,
    id: EnvoyGaugeVecId,
    labels: &[&str],
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyGaugeVecId(id) = id;
    let mut label_bufs = strs_to_module_buffers(labels);
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_decrement_gauge(
        self.raw,
        id,
        label_bufs.as_mut_ptr(),
        labels.len(),
        value,
      )
    })
  }
  fn record_histogram_value(
    &self,
    id: EnvoyHistogramId,
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyHistogramId(id) = id;
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_record_histogram_value(
        self.raw,
        id,
        std::ptr::null_mut(),
        0,
        value,
      )
    })
  }
  fn record_histogram_value_vec(
    &self,
    id: EnvoyHistogramVecId,
    labels: &[&str],
    value: u64,
  ) -> Result<(), abi::envoy_dynamic_module_type_metrics_result> {
    let EnvoyHistogramVecId(id) = id;
    let mut label_bufs = strs_to_module_buffers(labels);
    metric_result_to_rust(unsafe {
      abi::envoy_dynamic_module_callback_lb_config_record_histogram_value(
        self.raw,
        id,
        label_bufs.as_mut_ptr(),
        labels.len(),
        value,
      )
    })
  }
}
/// Trait for the load balancer configuration.
///
/// This is created once when the load balancer policy is configured and shared across all
/// worker threads. Implementations must be `Sync` since they are accessed from worker threads.
pub trait LoadBalancerConfig: Sync {
  /// Creates a new load balancer instance for each worker thread.
  ///
  /// This is called once per worker thread when the thread is initialized.
  /// The `envoy_lb` provides access to cluster information (context methods are not available).
  fn new_load_balancer(&self, envoy_lb: &dyn EnvoyLoadBalancer) -> Box<dyn LoadBalancer>;
}
/// Represents the result of a host selection decision, containing the priority level
/// and the host index within the healthy hosts at that priority.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HostSelection {
  /// The priority level of the selected host.
  pub priority: u32,
  /// The index of the selected host within the healthy hosts at the given priority.
  pub index: u32,
}
impl HostSelection {
  /// Creates a new host selection at the given priority and index.
  pub fn new(priority: u32, index: u32) -> Self {
    Self { priority, index }
  }
  /// Creates a new host selection at priority 0 with the given index.
  /// This is a convenience for the common case of single-priority clusters.
  pub fn at_default_priority(index: u32) -> Self {
    Self { priority: 0, index }
  }
}
/// Trait for a load balancer instance.
///
/// All the event hooks are called on the same thread as the one that the [`LoadBalancer`] is
/// created via the [`LoadBalancerConfig::new_load_balancer`] method. In other words, the
/// [`LoadBalancer`] object is thread-local.
pub trait LoadBalancer {
  /// Chooses a host for an upstream request.
  ///
  /// The `envoy_lb` provides access to both cluster/host information and request context.
  /// Context methods (those starting with `context_`) are only valid during this callback.
  ///
  /// Returns a [`HostSelection`] containing the priority and index of the selected host
  /// in the healthy hosts list at that priority, or `None` if no host should be selected
  /// (which will result in no upstream connection).
  fn choose_host(&mut self, envoy_lb: &dyn EnvoyLoadBalancer) -> Option<HostSelection>;
  /// Called when the set of hosts in the cluster changes (hosts added or removed).
  ///
  /// The `envoy_lb` provides access to cluster/host information. During this callback,
  /// [`EnvoyLoadBalancer::get_member_update_host_address`] can be used to get the addresses
  /// of the added or removed hosts.
  ///
  /// The default implementation is a no-op.
  fn on_host_membership_update(
    &mut self,
    _envoy_lb: &dyn EnvoyLoadBalancer,
    _num_hosts_added: usize,
    _num_hosts_removed: usize,
  ) {
  }
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_new(
  lb_config_envoy_ptr: abi::envoy_dynamic_module_type_lb_config_envoy_ptr,
  name: abi::envoy_dynamic_module_type_envoy_buffer,
  config: abi::envoy_dynamic_module_type_envoy_buffer,
) -> abi::envoy_dynamic_module_type_lb_config_module_ptr {
  let name_str = std::str::from_utf8_unchecked(std::slice::from_raw_parts(
    name.ptr as *const _,
    name.length,
  ));
  let config_slice = std::slice::from_raw_parts(config.ptr as *const _, config.length);
  let new_config_fn = NEW_LOAD_BALANCER_CONFIG_FUNCTION
    .get()
    .expect("NEW_LOAD_BALANCER_CONFIG_FUNCTION must be set");
  let envoy_lb_config: Arc<dyn EnvoyLbConfig> = Arc::new(EnvoyLbConfigImpl {
    raw: lb_config_envoy_ptr,
  });
  match new_config_fn(name_str, config_slice, envoy_lb_config) {
    Some(config) => wrap_into_c_void_ptr!(config),
    None => std::ptr::null(),
  }
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_config_destroy(
  config_ptr: abi::envoy_dynamic_module_type_lb_config_module_ptr,
) {
  drop_wrapped_c_void_ptr!(config_ptr, LoadBalancerConfig);
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_new(
  config_ptr: abi::envoy_dynamic_module_type_lb_config_module_ptr,
  lb_envoy_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr,
) -> abi::envoy_dynamic_module_type_lb_module_ptr {
  // During new_load_balancer, context is not available.
  let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut());
  let lb_config = {
    let raw = config_ptr as *const *const dyn LoadBalancerConfig;
    &**raw
  };
  let lb = lb_config.new_load_balancer(&envoy_lb);
  wrap_into_c_void_ptr!(lb)
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_choose_host(
  lb_envoy_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr,
  lb_module_ptr: abi::envoy_dynamic_module_type_lb_module_ptr,
  context_envoy_ptr: abi::envoy_dynamic_module_type_lb_context_envoy_ptr,
  result_priority: *mut u32,
  result_index: *mut u32,
) -> bool {
  let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, context_envoy_ptr);
  let lb = {
    let raw = lb_module_ptr as *mut *mut dyn LoadBalancer;
    &mut **raw
  };
  match lb.choose_host(&envoy_lb) {
    Some(selection) => {
      *result_priority = selection.priority;
      *result_index = selection.index;
      true
    },
    None => false,
  }
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_on_host_membership_update(
  lb_envoy_ptr: abi::envoy_dynamic_module_type_lb_envoy_ptr,
  lb_module_ptr: abi::envoy_dynamic_module_type_lb_module_ptr,
  num_hosts_added: usize,
  num_hosts_removed: usize,
) {
  let envoy_lb = EnvoyLoadBalancerImpl::new(lb_envoy_ptr, std::ptr::null_mut());
  let lb = {
    let raw = lb_module_ptr as *mut *mut dyn LoadBalancer;
    &mut **raw
  };
  lb.on_host_membership_update(&envoy_lb, num_hosts_added, num_hosts_removed);
}
/// # Safety
///
/// This is an FFI function called by Envoy. All pointer arguments must be valid as guaranteed
/// by the Envoy dynamic module ABI.
#[no_mangle]
pub unsafe extern "C" fn envoy_dynamic_module_on_lb_destroy(
  lb_module_ptr: abi::envoy_dynamic_module_type_lb_module_ptr,
) {
  drop_wrapped_c_void_ptr!(lb_module_ptr, LoadBalancer);
}