// SPDX-FileCopyrightText: 2025 Free Mobile // SPDX-License-Identifier: AGPL-3.0-only package schema import ( "net/netip" "github.com/ClickHouse/ch-go/proto" "github.com/bits-and-blooms/bitset" ) // FlowMessage is the abstract representation of a flow through various subsystems. type FlowMessage struct { TimeReceived uint32 SamplingRate uint64 // For exporter classifier ExporterAddress netip.Addr // For interface classifier InIf uint32 OutIf uint32 SrcVlan uint16 DstVlan uint16 // For routing component SrcAddr netip.Addr DstAddr netip.Addr NextHop netip.Addr // Core component may override them SrcAS uint32 DstAS uint32 SrcNetMask uint8 DstNetMask uint8 // Only for tests OtherColumns map[ColumnKey]any reversed bool batch clickhouseBatch schema *Schema } // clickhouseBatch stores columns for efficient streaming. It is embedded // inside a FlowMessage. type clickhouseBatch struct { columns []proto.Column // Indexed by ColumnKey columnSet bitset.BitSet // Track which columns have been set rowCount int // Number of rows accumulated input proto.Input // Input including all columns to stream to ClickHouse } // reset resets a flow message. All public fields are set to 0, // but the current ClickHouse batch is left untouched. func (bf *FlowMessage) reset() { *bf = FlowMessage{ batch: bf.batch, schema: bf.schema, } bf.batch.columnSet.ClearAll() } // Clear clears all column data. func (bf *FlowMessage) Clear() { bf.reset() bf.batch.input.Reset() bf.batch.rowCount = 0 } // ClickHouseProtoInput returns the proto.Input that can be used to stream results // to ClickHouse. func (bf *FlowMessage) ClickHouseProtoInput() proto.Input { return bf.batch.input } // NewFlowMessage creates a new FlowMessage for the given schema with ClickHouse batch initialized. func (schema *Schema) NewFlowMessage() *FlowMessage { bf := &FlowMessage{ schema: schema, } maxKey := ColumnKey(0) for _, column := range bf.schema.columns { if column.Key > maxKey { maxKey = column.Key } } bf.batch.columns = make([]proto.Column, maxKey+1) bf.batch.columnSet = *bitset.New(uint(maxKey + 1)) bf.batch.rowCount = 0 for _, column := range bf.schema.columns { if !column.Disabled && column.shouldProvideValue() { bf.batch.columns[column.Key] = column.newProtoColumn() bf.batch.input = append(bf.batch.input, proto.InputColumn{ Name: column.Name, Data: column.wrapProtoColumn(bf.batch.columns[column.Key]), }) } } return bf } // FlowCount return the number of flows batched func (bf *FlowMessage) FlowCount() int { return bf.batch.rowCount } // Reverse reverses the direction of the next calls to Append*(). func (bf *FlowMessage) Reverse() { bf.reversed = !bf.reversed bf.InIf, bf.OutIf = bf.OutIf, bf.InIf bf.SrcVlan, bf.DstVlan = bf.DstVlan, bf.SrcVlan bf.SrcAddr, bf.DstAddr = bf.DstAddr, bf.SrcAddr bf.SrcAS, bf.DstAS = bf.DstAS, bf.SrcAS bf.SrcNetMask, bf.DstNetMask = bf.DstNetMask, bf.SrcNetMask }