use super::{
CandidateTraceQualificationError, CandidateTraceQualificationStatus,
QualifiedCandidateTraceRunProvenance,
};
pub const CANDIDATE_TRACE_FORMAT_VERSION: u32 = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CandidateTraceDigest {
pub first: u64,
pub second: u64,
}
impl CandidateTraceDigest {
const FIRST_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FIRST_PRIME: u64 = 0x0000_0100_0000_01b3;
const SECOND_OFFSET: u64 = 0x9e37_79b9_7f4a_7c15;
const SECOND_MULTIPLIER: u64 = 0xd6e8_feb8_6659_fd93;
pub const fn empty() -> Self {
Self {
first: Self::FIRST_OFFSET,
second: Self::SECOND_OFFSET,
}
}
pub fn of_bytes(bytes: &[u8]) -> Self {
let mut digest = Self::empty();
digest.update(bytes);
digest
}
pub fn update(&mut self, bytes: &[u8]) {
for &byte in bytes {
self.first ^= u64::from(byte);
self.first = self.first.wrapping_mul(Self::FIRST_PRIME);
self.second ^= u64::from(byte).wrapping_add(0x9d);
self.second = self
.second
.rotate_left(13)
.wrapping_mul(Self::SECOND_MULTIPLIER)
.wrapping_add(0x9e37_79b9);
}
}
}
impl Default for CandidateTraceDigest {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTracePhasePlan {
pub kind: String,
pub attributes: Vec<CandidateTracePhaseAttribute>,
pub opaque: bool,
pub children: Vec<Self>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct CandidateTracePhaseAttribute {
pub key: String,
pub value: String,
}
impl CandidateTracePhasePlan {
pub fn known<K, V, I>(kind: impl Into<String>, attributes: I, children: Vec<Self>) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
let mut attributes = attributes
.into_iter()
.map(|(key, value)| CandidateTracePhaseAttribute {
key: key.into(),
value: value.into(),
})
.collect::<Vec<_>>();
attributes.sort();
assert!(
attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
"candidate trace phase-plan attributes must have unique keys"
);
Self {
kind: kind.into(),
attributes,
opaque: false,
children,
}
}
pub fn opaque(kind: impl Into<String>) -> Self {
Self {
kind: kind.into(),
attributes: Vec::new(),
opaque: true,
children: Vec::new(),
}
}
pub fn is_complete(&self) -> bool {
!self.opaque && self.children.iter().all(Self::is_complete)
}
pub fn canonical_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.append_canonical_bytes(&mut out);
out
}
fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
out.push(0x50);
append_string(out, &self.kind);
append_bool(out, self.opaque);
append_len(out, self.attributes.len());
for attribute in &self.attributes {
append_string(out, &attribute.key);
append_string(out, &attribute.value);
}
append_len(out, self.children.len());
for child in &self.children {
child.append_canonical_bytes(out);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceExecutionPolicy {
pub kind: String,
pub attributes: Vec<CandidateTracePhaseAttribute>,
pub opaque: bool,
}
impl CandidateTraceExecutionPolicy {
pub fn known<K, V, I>(kind: impl Into<String>, attributes: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
let mut attributes = attributes
.into_iter()
.map(|(key, value)| CandidateTracePhaseAttribute {
key: key.into(),
value: value.into(),
})
.collect::<Vec<_>>();
attributes.sort();
assert!(
attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
"candidate trace execution-policy attributes must have unique keys"
);
Self {
kind: kind.into(),
attributes,
opaque: false,
}
}
pub fn opaque(kind: impl Into<String>) -> Self {
Self::opaque_with_attributes(kind, std::iter::empty::<(String, String)>())
}
pub fn opaque_with_attributes<K, V, I>(kind: impl Into<String>, attributes: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
let mut attributes = attributes
.into_iter()
.map(|(key, value)| CandidateTracePhaseAttribute {
key: key.into(),
value: value.into(),
})
.collect::<Vec<_>>();
attributes.sort();
assert!(
attributes.windows(2).all(|pair| pair[0].key != pair[1].key),
"candidate trace execution-policy attributes must have unique keys"
);
Self {
kind: kind.into(),
attributes,
opaque: true,
}
}
pub const fn is_complete(&self) -> bool {
!self.opaque
}
pub fn canonical_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
out.push(0x58);
append_string(&mut out, &self.kind);
append_bool(&mut out, self.opaque);
append_len(&mut out, self.attributes.len());
for attribute in &self.attributes {
append_string(&mut out, &attribute.key);
append_string(&mut out, &attribute.value);
}
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CandidateTraceExternalDigest {
pub bytes: [u8; 32],
}
impl CandidateTraceExternalDigest {
pub const fn sha256(bytes: [u8; 32]) -> Self {
Self { bytes }
}
fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
out.push(1);
out.extend_from_slice(&self.bytes);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceInputAttestation {
producer: String,
}
impl CandidateTraceInputAttestation {
pub fn external(producer: impl Into<String>) -> Self {
let producer = producer.into();
assert!(
!producer.is_empty(),
"candidate trace external provenance producer must not be empty"
);
Self { producer }
}
pub fn external_producer(&self) -> &str {
&self.producer
}
fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
out.push(1);
append_string(out, &self.producer);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceInputProvenance {
pub schema_digest: CandidateTraceExternalDigest,
pub instance_digest: CandidateTraceExternalDigest,
pub initial_state_digest: CandidateTraceExternalDigest,
pub core_tree_digest: Option<CandidateTraceExternalDigest>,
pub build_digest: Option<CandidateTraceExternalDigest>,
pub attestation: CandidateTraceInputAttestation,
}
impl CandidateTraceInputProvenance {
pub fn externally_attested(
schema_digest: CandidateTraceExternalDigest,
instance_digest: CandidateTraceExternalDigest,
initial_state_digest: CandidateTraceExternalDigest,
producer: impl Into<String>,
) -> Self {
Self {
schema_digest,
instance_digest,
initial_state_digest,
core_tree_digest: None,
build_digest: None,
attestation: CandidateTraceInputAttestation::external(producer),
}
}
pub fn with_core_tree_digest(mut self, digest: CandidateTraceExternalDigest) -> Self {
self.core_tree_digest = Some(digest);
self
}
pub fn with_build_digest(mut self, digest: CandidateTraceExternalDigest) -> Self {
self.build_digest = Some(digest);
self
}
fn canonical_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
out.push(0x49);
self.schema_digest.append_canonical_bytes(&mut out);
self.instance_digest.append_canonical_bytes(&mut out);
self.initial_state_digest.append_canonical_bytes(&mut out);
append_optional_external_digest(&mut out, self.core_tree_digest);
append_optional_external_digest(&mut out, self.build_digest);
self.attestation.append_canonical_bytes(&mut out);
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CandidateTraceInputProvenanceStatus {
Absent,
ExternallyAttested,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CandidateTraceProvenanceStatus {
pub execution_policy_complete: bool,
pub resolved_phase_plan_complete: bool,
pub input_provenance: CandidateTraceInputProvenanceStatus,
pub qualification: CandidateTraceQualificationStatus,
}
impl CandidateTraceProvenanceStatus {
pub const fn has_complete_execution_plan(self) -> bool {
self.execution_policy_complete && self.resolved_phase_plan_complete
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceHeader {
pub format_version: u32,
pub configured_input: String,
pub configured_input_digest: CandidateTraceDigest,
pub execution_policy: CandidateTraceExecutionPolicy,
pub execution_policy_digest: CandidateTraceDigest,
pub execution_policy_complete: bool,
pub input_provenance: Option<CandidateTraceInputProvenance>,
pub input_provenance_digest: Option<CandidateTraceDigest>,
pub qualified_run_provenance: Option<QualifiedCandidateTraceRunProvenance>,
pub resolved_phase_plan: CandidateTracePhasePlan,
pub resolved_phase_plan_digest: CandidateTraceDigest,
pub resolved_phase_plan_complete: bool,
}
impl CandidateTraceHeader {
pub fn new(
configured_input: String,
execution_policy: CandidateTraceExecutionPolicy,
resolved_phase_plan: CandidateTracePhasePlan,
input_provenance: Option<CandidateTraceInputProvenance>,
) -> Self {
let configured_input_digest = CandidateTraceDigest::of_bytes(configured_input.as_bytes());
let execution_policy_digest =
CandidateTraceDigest::of_bytes(&execution_policy.canonical_bytes());
let execution_policy_complete = execution_policy.is_complete();
let input_provenance_digest = input_provenance
.as_ref()
.map(|provenance| CandidateTraceDigest::of_bytes(&provenance.canonical_bytes()));
let resolved_phase_plan_digest =
CandidateTraceDigest::of_bytes(&resolved_phase_plan.canonical_bytes());
let resolved_phase_plan_complete = resolved_phase_plan.is_complete();
Self {
format_version: CANDIDATE_TRACE_FORMAT_VERSION,
configured_input,
configured_input_digest,
execution_policy,
execution_policy_digest,
execution_policy_complete,
input_provenance,
input_provenance_digest,
qualified_run_provenance: None,
resolved_phase_plan,
resolved_phase_plan_digest,
resolved_phase_plan_complete,
}
}
pub fn new_qualified(
configured_input: String,
execution_policy: CandidateTraceExecutionPolicy,
resolved_phase_plan: CandidateTracePhasePlan,
qualified_run_provenance: QualifiedCandidateTraceRunProvenance,
) -> Self {
let mut header = Self::new(
configured_input,
execution_policy,
resolved_phase_plan,
Some(qualified_run_provenance.input_provenance().clone()),
);
header.qualified_run_provenance = Some(qualified_run_provenance);
header
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CandidateTraceSource {
Construction,
LocalSearch,
VariableNeighborhoodDescent,
KOpt,
ListRoundRobinConstruction,
ListCheapestInsertionTrial,
ListRegretInsertionTrial,
ListClarkeWrightSavings,
ListClarkeWrightMerge,
ListClarkeWrightCompletionInsertion,
ListKOptReconnection,
ListRegretOwnerAppend,
}
impl CandidateTraceSource {
const fn code(self) -> u8 {
match self {
Self::Construction => 1,
Self::LocalSearch => 2,
Self::VariableNeighborhoodDescent => 3,
Self::KOpt => 4,
Self::ListRoundRobinConstruction => 5,
Self::ListCheapestInsertionTrial => 6,
Self::ListRegretInsertionTrial => 7,
Self::ListClarkeWrightSavings => 8,
Self::ListClarkeWrightMerge => 9,
Self::ListClarkeWrightCompletionInsertion => 10,
Self::ListKOptReconnection => 11,
Self::ListRegretOwnerAppend => 12,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CandidateTraceDisposition {
InterruptedBeforeEvaluation,
Evaluated,
NotDoable,
RejectedByHardImprovement,
RejectedByScoreImprovement,
AcceptorRejected,
ForagerIgnored,
Selected,
Applied,
}
impl CandidateTraceDisposition {
const fn code(self) -> u8 {
match self {
Self::InterruptedBeforeEvaluation => 1,
Self::Evaluated => 2,
Self::NotDoable => 3,
Self::RejectedByHardImprovement => 4,
Self::RejectedByScoreImprovement => 5,
Self::AcceptorRejected => 6,
Self::ForagerIgnored => 7,
Self::Selected => 8,
Self::Applied => 9,
}
}
const fn is_terminal(self) -> bool {
matches!(
self,
Self::InterruptedBeforeEvaluation
| Self::NotDoable
| Self::RejectedByHardImprovement
| Self::RejectedByScoreImprovement
| Self::AcceptorRejected
| Self::ForagerIgnored
| Self::Applied
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct CandidateTracePullToken {
ordinal: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CandidateTraceConstructionTarget {
pub descriptor_index: usize,
pub entity_index: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CandidateTraceIdentity {
Operation(CandidateTraceOperationIdentity),
Composite(CandidateTraceCompositeIdentity),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceOperationIdentity {
pub descriptor_index: usize,
pub variable_name: Option<String>,
pub operation: String,
pub components: Vec<CandidateTraceCoordinate>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceCompositeIdentity {
pub operation: String,
pub children: Vec<CandidateTraceIdentity>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CandidateTraceCoordinate {
Unsigned(u64),
Absent,
Text(String),
Bytes(Vec<u8>),
}
impl From<u64> for CandidateTraceCoordinate {
fn from(value: u64) -> Self {
Self::Unsigned(value)
}
}
impl From<usize> for CandidateTraceCoordinate {
fn from(value: usize) -> Self {
Self::Unsigned(value as u64)
}
}
impl From<Option<usize>> for CandidateTraceCoordinate {
fn from(value: Option<usize>) -> Self {
value.map_or(Self::Absent, Self::from)
}
}
impl From<String> for CandidateTraceCoordinate {
fn from(value: String) -> Self {
Self::Text(value)
}
}
impl From<&str> for CandidateTraceCoordinate {
fn from(value: &str) -> Self {
Self::Text(value.to_owned())
}
}
impl CandidateTraceIdentity {
pub fn operation<I, T>(
descriptor_index: usize,
operation: impl Into<String>,
components: I,
) -> Self
where
I: IntoIterator<Item = T>,
T: Into<CandidateTraceCoordinate>,
{
Self::Operation(CandidateTraceOperationIdentity {
descriptor_index,
variable_name: None,
operation: operation.into(),
components: components.into_iter().map(Into::into).collect(),
})
}
pub fn logical_move<I, T>(
descriptor_index: usize,
variable_name: impl Into<String>,
family: impl Into<String>,
coordinates: I,
) -> Self
where
I: IntoIterator<Item = T>,
T: Into<CandidateTraceCoordinate>,
{
Self::Operation(CandidateTraceOperationIdentity {
descriptor_index,
variable_name: Some(variable_name.into()),
operation: family.into(),
components: coordinates.into_iter().map(Into::into).collect(),
})
}
pub fn composite(
operation: impl Into<String>,
children: impl IntoIterator<Item = CandidateTraceIdentity>,
) -> Self {
Self::Composite(CandidateTraceCompositeIdentity {
operation: operation.into(),
children: children.into_iter().collect(),
})
}
pub fn canonical_bytes(&self) -> Vec<u8> {
let mut out = Vec::new();
self.append_canonical_bytes(&mut out);
out
}
fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
match self {
Self::Operation(identity) => {
out.push(0x4f);
append_usize(out, identity.descriptor_index);
match &identity.variable_name {
Some(variable_name) => {
append_bool(out, true);
append_string(out, variable_name);
}
None => append_bool(out, false),
}
append_string(out, &identity.operation);
append_coordinate_list(out, &identity.components);
}
Self::Composite(identity) => {
out.push(0x43);
append_string(out, &identity.operation);
append_len(out, identity.children.len());
for child in &identity.children {
child.append_canonical_bytes(out);
}
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidatePullTelemetry {
pub ordinal: u64,
pub source: CandidateTraceSource,
pub phase_index: usize,
pub phase_type: String,
pub step_index: u64,
pub selector_index: Option<usize>,
pub candidate_index: usize,
pub construction_target: Option<CandidateTraceConstructionTarget>,
pub identity: Option<CandidateTraceIdentity>,
pub dispositions: Vec<CandidateTraceDisposition>,
}
impl CandidatePullTelemetry {
fn append_canonical_bytes(&self, out: &mut Vec<u8>) {
out.push(0x45);
append_u64(out, self.ordinal);
out.push(self.source.code());
append_usize(out, self.phase_index);
append_string(out, &self.phase_type);
append_u64(out, self.step_index);
append_option_usize(out, self.selector_index);
append_usize(out, self.candidate_index);
match self.construction_target {
Some(target) => {
append_bool(out, true);
append_usize(out, target.descriptor_index);
append_usize(out, target.entity_index);
}
None => append_bool(out, false),
}
match &self.identity {
Some(identity) => {
append_bool(out, true);
identity.append_canonical_bytes(out);
}
None => append_bool(out, false),
}
append_len(out, self.dispositions.len());
for disposition in &self.dispositions {
out.push(disposition.code());
}
}
fn has_terminal_disposition(&self) -> bool {
self.dispositions
.last()
.is_some_and(|disposition| disposition.is_terminal())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CandidateTraceTelemetry {
pub header: CandidateTraceHeader,
pub max_entries: usize,
pub total_pulls: u64,
pub pulls: Vec<CandidatePullTelemetry>,
pub truncated: bool,
pub prefix_digest: CandidateTraceDigest,
pub unencoded_identity_count: u64,
resolved_phase_plan_finalized: bool,
next_phase_index: usize,
}
impl CandidateTraceTelemetry {
pub(crate) fn new(header: CandidateTraceHeader, max_entries: usize) -> Self {
Self {
header,
max_entries,
total_pulls: 0,
pulls: Vec::new(),
truncated: false,
prefix_digest: CandidateTraceDigest::empty(),
unencoded_identity_count: 0,
resolved_phase_plan_finalized: false,
next_phase_index: 0,
}
}
pub(crate) fn finalize_resolved_phase_plan(
&mut self,
resolved_phase_plan: CandidateTracePhasePlan,
) {
assert!(
!self.resolved_phase_plan_finalized,
"candidate trace resolved phase plan may be finalized only once"
);
let resolved_phase_plan_digest =
CandidateTraceDigest::of_bytes(&resolved_phase_plan.canonical_bytes());
let resolved_phase_plan_complete = resolved_phase_plan.is_complete();
self.header.resolved_phase_plan = resolved_phase_plan;
self.header.resolved_phase_plan_digest = resolved_phase_plan_digest;
self.header.resolved_phase_plan_complete = resolved_phase_plan_complete;
self.resolved_phase_plan_finalized = true;
}
pub fn is_complete(&self) -> bool {
!self.truncated
&& self.total_pulls == self.pulls.len() as u64
&& self.unencoded_identity_count == 0
&& self
.pulls
.iter()
.all(CandidatePullTelemetry::has_terminal_disposition)
}
pub fn has_complete_execution_provenance(&self) -> bool {
self.header.execution_policy_complete && self.header.resolved_phase_plan_complete
}
pub fn provenance_status(&self) -> CandidateTraceProvenanceStatus {
let input_provenance = match self.header.input_provenance.as_ref() {
None => CandidateTraceInputProvenanceStatus::Absent,
Some(_) => CandidateTraceInputProvenanceStatus::ExternallyAttested,
};
CandidateTraceProvenanceStatus {
execution_policy_complete: self.header.execution_policy_complete,
resolved_phase_plan_complete: self.header.resolved_phase_plan_complete,
input_provenance,
qualification: self
.header
.qualified_run_provenance
.as_ref()
.map_or(CandidateTraceQualificationStatus::NotRequested, |_| {
CandidateTraceQualificationStatus::Qualified
}),
}
}
pub fn require_qualified_run_provenance(
&self,
) -> Result<&QualifiedCandidateTraceRunProvenance, CandidateTraceQualificationError> {
self.header
.qualified_run_provenance
.as_ref()
.ok_or(CandidateTraceQualificationError::QualificationNotRequested)
}
pub(crate) fn prepare_pull(&mut self) -> CandidateTraceRecordDecision {
let ordinal = self.total_pulls;
self.total_pulls = self.total_pulls.saturating_add(1);
if self.pulls.len() < self.max_entries {
CandidateTraceRecordDecision::Capture { ordinal }
} else {
self.truncated = true;
CandidateTraceRecordDecision::Overflow
}
}
pub(crate) fn begin_phase(&mut self) -> usize {
let phase_index = self.next_phase_index;
self.next_phase_index = self.next_phase_index.saturating_add(1);
phase_index
}
pub(crate) fn push_prepared(
&mut self,
pull: CandidatePullTelemetry,
) -> CandidateTracePullToken {
debug_assert!(self.pulls.len() < self.max_entries);
debug_assert_eq!(pull.ordinal, self.total_pulls.saturating_sub(1));
if pull.identity.is_none() {
self.unencoded_identity_count = self.unencoded_identity_count.saturating_add(1);
}
let token = CandidateTracePullToken {
ordinal: pull.ordinal,
};
self.pulls.push(pull);
token
}
pub(crate) fn record_disposition(
&mut self,
token: CandidateTracePullToken,
disposition: CandidateTraceDisposition,
) {
let Some(pull) = self.pulls.get_mut(token.ordinal as usize) else {
debug_assert!(false, "candidate trace token must point at a retained pull");
return;
};
debug_assert_eq!(pull.ordinal, token.ordinal);
pull.dispositions.push(disposition);
}
pub(crate) fn snapshot(&self) -> Self {
let mut snapshot = self.clone();
snapshot.prefix_digest = CandidateTraceDigest::empty();
for pull in &snapshot.pulls {
let mut bytes = Vec::new();
pull.append_canonical_bytes(&mut bytes);
snapshot.prefix_digest.update(&bytes);
}
snapshot
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CandidateTraceRecordDecision {
Disabled,
Capture { ordinal: u64 },
Overflow,
}
fn append_bool(out: &mut Vec<u8>, value: bool) {
out.push(u8::from(value));
}
fn append_len(out: &mut Vec<u8>, value: usize) {
append_u64(
out,
u64::try_from(value).expect("candidate trace lengths must fit in u64"),
);
}
fn append_usize(out: &mut Vec<u8>, value: usize) {
append_len(out, value);
}
fn append_u64(out: &mut Vec<u8>, value: u64) {
out.extend_from_slice(&value.to_le_bytes());
}
fn append_option_usize(out: &mut Vec<u8>, value: Option<usize>) {
match value {
Some(value) => {
append_bool(out, true);
append_usize(out, value);
}
None => append_bool(out, false),
}
}
fn append_optional_external_digest(out: &mut Vec<u8>, value: Option<CandidateTraceExternalDigest>) {
match value {
Some(value) => {
append_bool(out, true);
value.append_canonical_bytes(out);
}
None => append_bool(out, false),
}
}
fn append_string(out: &mut Vec<u8>, value: &str) {
append_len(out, value.len());
out.extend_from_slice(value.as_bytes());
}
fn append_coordinate_list(out: &mut Vec<u8>, values: &[CandidateTraceCoordinate]) {
append_len(out, values.len());
for value in values {
match value {
CandidateTraceCoordinate::Unsigned(value) => {
out.push(1);
append_u64(out, *value);
}
CandidateTraceCoordinate::Absent => out.push(2),
CandidateTraceCoordinate::Text(value) => {
out.push(3);
append_string(out, value);
}
CandidateTraceCoordinate::Bytes(value) => {
out.push(4);
append_len(out, value.len());
out.extend_from_slice(value);
}
}
}
}