blob: 9e8bf4a19431a1f684f17873ef544484c6836f83 [file]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you 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.
package data
import (
"fmt"
"sync/atomic"
"google.golang.org/protobuf/proto"
measurev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/measure/v1"
)
// RawFrameMagicLeadingByte is the mandatory first byte of every raw vec
// columnar frame body. It is a complete varint tag decoding to field number 0,
// which protowire.ConsumeTag rejects with errCodeFieldNumber and proto/decode.go
// converts into a hard errDecode before any unknown-field skip (verified against
// google.golang.org/protobuf@v1.36.11). Emitting this leading byte
// deterministically forces a flag-off node's proto.Unmarshal of a raw frame
// into measurev1.InternalQueryResponse{} to return a non-nil error, collapsing
// the "garbage-but-parsed silently-empty" outcome into the already-loud decode
// outcome (G9f spec Principle 3). The full frame magic/wire-version body is
// produced and consumed by G9f.2/G9f.3; G9f.0 only wires the passthrough plus
// this contract byte.
const RawFrameMagicLeadingByte byte = 0x00
// ResponseCodec encodes and decodes the clusterv1.SendResponse.body for a
// single topic. The ~18 proto topics use a behavior-preserving ProtoCodec;
// TopicInternalMeasureQuery uses a wire-mode-dispatching codec that selects
// RawFrameCodec when the process is flag-on and ProtoCodec when flag-off
// (topic-AND-process-wire-mode selection — one static supplier serves both
// flag-on raw bodies and flag-off proto bodies on the same topic).
type ResponseCodec interface {
// Marshal encodes a response value into the SendResponse.body bytes.
Marshal(any) ([]byte, error)
// Unmarshal decodes the SendResponse.body bytes into a response value.
Unmarshal([]byte) (any, error)
}
// ProtoCodec is the behavior-preserving codec for every proto-bodied topic. It
// marshals and unmarshals byte-identically to the pre-G9f.0
// proto.Marshal/proto.Unmarshal path: Marshal calls proto.Marshal on the
// supplied proto.Message; Unmarshal allocates a fresh message via the supplier
// and proto.Unmarshal-s into it.
type ProtoCodec struct {
newMessage func() proto.Message
}
// NewProtoCodec wraps a proto message supplier in a ProtoCodec.
func NewProtoCodec(newMessage func() proto.Message) *ProtoCodec {
return &ProtoCodec{newMessage: newMessage}
}
// Marshal proto-marshals the value, byte-identical to the pre-refactor path.
func (c *ProtoCodec) Marshal(v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, fmt.Errorf("ProtoCodec: expected proto.Message, got %T", v)
}
return proto.Marshal(m)
}
// Unmarshal proto-unmarshals into a fresh message from the supplier,
// byte-identical to the pre-refactor path.
func (c *ProtoCodec) Unmarshal(body []byte) (any, error) {
m := c.newMessage()
if err := proto.Unmarshal(body, m); err != nil {
return nil, err
}
return m, nil
}
// RawFrameCodec passes the SendResponse.body through as opaque bytes — the
// future vec columnar frame. It applies no proto encoding. A nil/empty body is
// a legitimate empty distributed result and is NOT magic-validated (sub.go
// sends a body-less SendResponse for an empty result and pub.go returns before
// codec dispatch on a nil body — magic-validating those legitimate empties
// would fail loud incorrectly). A non-empty body MUST begin with
// RawFrameMagicLeadingByte; the full magic/wire-version validation lands in
// G9f.3, but the leading-byte contract is enforced here so the G9f.0
// passthrough is the SOLE place the contract is asserted on decode.
type RawFrameCodec struct{}
// NewRawFrameCodec returns the opaque-bytes passthrough codec.
func NewRawFrameCodec() *RawFrameCodec {
return &RawFrameCodec{}
}
// Marshal returns the raw frame bytes unchanged. A nil/empty value encodes to
// a nil body (a legitimate empty distributed result).
func (c *RawFrameCodec) Marshal(v any) ([]byte, error) {
if v == nil {
return nil, nil
}
b, ok := v.([]byte)
if !ok {
return nil, fmt.Errorf("RawFrameCodec: expected []byte raw frame, got %T", v)
}
return b, nil
}
// Unmarshal returns the raw frame bytes unchanged. A nil/empty body is a valid
// empty result and is NOT magic-validated. A non-empty body MUST begin with
// RawFrameMagicLeadingByte or it fails loud.
func (c *RawFrameCodec) Unmarshal(body []byte) (any, error) {
if len(body) == 0 {
return []byte(nil), nil
}
if body[0] != RawFrameMagicLeadingByte {
return nil, fmt.Errorf("RawFrameCodec: invalid raw frame magic: leading byte 0x%02x, want 0x%02x",
body[0], RawFrameMagicLeadingByte)
}
return body, nil
}
// measureWireModeRaw is the per-process wire mode for TopicInternalMeasureQuery.
// It is set once at measure-service startup from --measure-vectorized-enabled
// (a per-process CLI bool with zero cluster propagation — G9f spec Principle 2).
// Default false (flag-off / proto) so any process that never sets it keeps the
// pre-G9f.0 proto behavior.
var measureWireModeRaw atomic.Bool
// SetMeasureWireModeRaw publishes the per-process wire mode for
// TopicInternalMeasureQuery. raw==true selects RawFrameCodec (flag-on,
// vec columnar frame body); raw==false selects ProtoCodec (flag-off, proto
// body). Called at measure-service startup after flags are parsed.
func SetMeasureWireModeRaw(raw bool) {
measureWireModeRaw.Store(raw)
}
// MeasureWireModeRaw reports whether this process is flag-on (raw vec frame
// body) for TopicInternalMeasureQuery.
func MeasureWireModeRaw() bool {
return measureWireModeRaw.Load()
}
// traceWireModeRaw is the per-process wire mode for TopicTraceQuery. Set once at
// trace-service startup; raw==true selects the native columnar trace frame body,
// raw==false keeps the proto body. Default false so any process that never sets
// it keeps the pre-existing proto behavior.
var traceWireModeRaw atomic.Bool
// SetTraceWireModeRaw publishes the per-process wire mode for TopicTraceQuery.
func SetTraceWireModeRaw(raw bool) {
traceWireModeRaw.Store(raw)
}
// TraceWireModeRaw reports whether this process emits/decodes native trace frame
// bodies for TopicTraceQuery.
func TraceWireModeRaw() bool {
return traceWireModeRaw.Load()
}
// measureQueryResponseCodec dispatches TopicInternalMeasureQuery encode/decode
// by the per-process wire mode: flag-on → RawFrameCodec, flag-off → ProtoCodec.
// One static supplier serves both flag-on raw bodies and flag-off proto bodies
// on the same topic, so the codec is selected by topic-AND-process-wire-mode,
// not topic alone (G9f spec G9f.0 topic-AND-mode guard).
type measureQueryResponseCodec struct {
proto *ProtoCodec
raw *RawFrameCodec
}
func (c *measureQueryResponseCodec) Marshal(v any) ([]byte, error) {
if !MeasureWireModeRaw() {
return c.proto.Marshal(v)
}
switch value := v.(type) {
case []byte:
return c.raw.Marshal(value)
case *measurev1.InternalQueryResponse:
return c.proto.Marshal(value)
default:
return nil, fmt.Errorf("measureQueryResponseCodec: raw wire mode expects []byte or *measurev1.InternalQueryResponse, got %T", v)
}
}
func (c *measureQueryResponseCodec) Unmarshal(body []byte) (any, error) {
if !MeasureWireModeRaw() {
return c.proto.Unmarshal(body)
}
if len(body) == 0 || body[0] == RawFrameMagicLeadingByte {
return c.raw.Unmarshal(body)
}
return c.proto.Unmarshal(body)
}