chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
204
internal/compat/layerwire/layout.go
Normal file
204
internal/compat/layerwire/layout.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue