owpengram-server/internal/compat/layerwire/layout.go

204 lines
6.2 KiB
Go

// Package layerwire downgrades canonical (Layer 227, the bytes gotd actually
// emits) TL objects to the wire shape expected by older clients (down to
// Layer 220), and is the runtime half of docs/layer-compat-220-227-design.md.
//
// The package is schema-driven: at init it parses the embedded canonical-227
// schema into a per-constructor field layout used by a generic walker; the
// generate-time tables (tables_gen.go, produced by ./gen) describe the
// per-layer downgrade rules. Business handlers and gotd are never touched —
// they always produce Layer 227, and transcoding happens only at the edge and
// only when the negotiated client layer is below 227.
package layerwire
import (
_ "embed"
"fmt"
"strings"
"github.com/gotd/tl"
)
// CanonicalLayer is the layer telesrv's pinned gotd emits.
const CanonicalLayer = 227
// SupportedFloor is the oldest client layer the transcoder targets.
const SupportedFloor = 220
// vectorTypeID is the boxed Vector constructor id.
const vectorTypeID = 0x1cb5c415
//go:embed schema/canonical-227.tl
var canonicalSchema string
// wireKind is the on-wire representation of a single TL value.
type wireKind uint8
const (
kindInt wireKind = iota // 4 bytes
kindLong // 8 bytes
kindDouble // 8 bytes
kindInt128 // 16 bytes
kindInt256 // 32 bytes
kindBytes // TL bytes (length-prefixed, padded)
kindString // TL string (same wire as bytes)
kindBool // boxed Bool (4-byte CRC)
kindTrue // flag-only pseudo value, 0 bytes
kindVector // boxed Vector<elem> (0x1cb5c415 + n + elems)
kindVectorBare // bare vector<elem> (n + elems, no id)
kindObject // boxed object (4-byte CRC + body)
kindBareObject // bare object (body only, resolved by typeName)
)
// fieldLayout is one parameter of a constructor with its decoded wire shape.
type fieldLayout struct {
name string
kind wireKind
isFlags bool // this is a `#` flags integer
flagName string // when conditional: which flags integer gates it
flagBit int // when conditional: bit index; -1 otherwise
elem *fieldLayout // vector element layout
typeName string // object/bareObject: (qualified) referenced type name
}
func (f fieldLayout) conditional() bool { return f.flagBit >= 0 }
// ctorLayout is the decoded field layout of a single constructor.
type ctorLayout struct {
crc uint32
name string // qualified TL name, e.g. "messages.dialogs" / "message"
result string // qualified result (abstract) type name
fields []fieldLayout
isFunc bool
}
// schemaModel is the parsed canonical schema indexed for the walker.
type schemaModel struct {
byCRC map[uint32]*ctorLayout
byName map[string]*ctorLayout // qualified ctor name -> layout
bareByT map[string]*ctorLayout // bare type name -> its single constructor
ctorsOfT map[string][]*ctorLayout // abstract result type -> constructors
}
// canonical is the parsed Layer 227 model, built once at init.
var canonical = mustLoadCanonical()
func mustLoadCanonical() *schemaModel {
m, err := parseSchemaModel(canonicalSchema)
if err != nil {
panic("layerwire: parse canonical schema: " + err.Error())
}
return m
}
func qualifyName(ns []string, name string) string {
if len(ns) == 0 {
return name
}
return strings.Join(ns, ".") + "." + name
}
func qualifyType(t tl.Type) string {
return qualifyName(t.Namespace, t.Name)
}
func parseSchemaModel(src string) (*schemaModel, error) {
parsed, err := tl.Parse(strings.NewReader(src))
if err != nil {
return nil, err
}
m := &schemaModel{
byCRC: make(map[uint32]*ctorLayout),
byName: make(map[string]*ctorLayout),
bareByT: make(map[string]*ctorLayout),
ctorsOfT: make(map[string][]*ctorLayout),
}
for i := range parsed.Definitions {
sd := parsed.Definitions[i]
d := sd.Definition
name := qualifyName(d.Namespace, d.Name)
if name == "vector" {
continue // implicit Vector pseudo-definition
}
cl := &ctorLayout{
crc: d.ID,
name: name,
result: qualifyType(d.Type),
isFunc: sd.Category == tl.CategoryFunction,
}
for _, p := range d.Params {
fl, err := toFieldLayout(p)
if err != nil {
return nil, fmt.Errorf("%s field %q: %w", name, p.Name, err)
}
cl.fields = append(cl.fields, fl)
}
if prev, ok := m.byCRC[cl.crc]; ok && prev.name != cl.name {
return nil, fmt.Errorf("crc collision %#08x: %s vs %s", cl.crc, prev.name, cl.name)
}
m.byCRC[cl.crc] = cl
m.byName[cl.name] = cl
if !cl.isFunc {
m.ctorsOfT[cl.result] = append(m.ctorsOfT[cl.result], cl)
// A bare type name is the lowercase constructor name itself.
m.bareByT[cl.name] = cl
}
}
return m, nil
}
func toFieldLayout(p tl.Parameter) (fieldLayout, error) {
if p.Flags {
return fieldLayout{name: p.Name, kind: kindInt, isFlags: true, flagBit: -1}, nil
}
fl := fieldLayout{name: p.Name, flagBit: -1}
if p.Flag != nil {
fl.flagName = p.Flag.Name
fl.flagBit = p.Flag.Index
}
kind, typeName, elem, err := resolveType(p.Type)
if err != nil {
return fieldLayout{}, err
}
fl.kind = kind
fl.typeName = typeName
fl.elem = elem
return fl, nil
}
func resolveType(t tl.Type) (kind wireKind, typeName string, elem *fieldLayout, err error) {
if t.GenericArg != nil {
ek, etn, eel, eerr := resolveType(*t.GenericArg)
if eerr != nil {
return 0, "", nil, eerr
}
el := &fieldLayout{kind: ek, typeName: etn, elem: eel, flagBit: -1}
if t.Name == "vector" { // bare vector
return kindVectorBare, "", el, nil
}
return kindVector, "", el, nil
}
switch t.Name {
case "int":
return kindInt, "", nil, nil
case "long":
return kindLong, "", nil, nil
case "double":
return kindDouble, "", nil, nil
case "int128":
return kindInt128, "", nil, nil
case "int256":
return kindInt256, "", nil, nil
case "bytes":
return kindBytes, "", nil, nil
case "string":
return kindString, "", nil, nil
case "Bool":
return kindBool, "", nil, nil
case "true":
return kindTrue, "", nil, nil
}
if t.Bare {
return kindBareObject, qualifyType(t), nil, nil
}
return kindObject, qualifyType(t), nil, nil
}