chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
350
internal/compat/layerwire/tables.go
Normal file
350
internal/compat/layerwire/tables.go
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
package layerwire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gotd/td/bin"
|
||||
)
|
||||
|
||||
// ruleRaw is the generated, compact form of a single changed-constructor
|
||||
// downgrade (see tables_gen.go). Mechanical rules carry the target CRC plus the
|
||||
// canonical field names retained at the target layer; structural rules name a
|
||||
// hand-written transform registered in fallback.go.
|
||||
type ruleRaw struct {
|
||||
target uint32
|
||||
keep []string
|
||||
structural string
|
||||
}
|
||||
|
||||
// layerRaw is the generated downgrade table for one target layer.
|
||||
type layerRaw struct {
|
||||
rules map[uint32]ruleRaw
|
||||
newTypes []uint32 // canonical CRCs that do not exist at this layer
|
||||
}
|
||||
|
||||
// downgradeRule is the runtime form of ruleRaw with keep as a set.
|
||||
type downgradeRule struct {
|
||||
target uint32
|
||||
keep map[string]bool
|
||||
structural string
|
||||
}
|
||||
|
||||
// layerTables is the runtime downgrade model for one target layer.
|
||||
type layerTables struct {
|
||||
rules map[uint32]*downgradeRule
|
||||
newTypes map[uint32]bool
|
||||
dirty map[uint32]bool // ctor CRC needs a deep walk
|
||||
dirtyT map[string]bool // abstract/bare type name reaches a dirty ctor
|
||||
}
|
||||
|
||||
// tables holds the runtime model per supported layer, built lazily.
|
||||
var tables = func() map[int]*layerTables {
|
||||
out := make(map[int]*layerTables, len(generatedTables))
|
||||
for layer, raw := range generatedTables {
|
||||
out[layer] = buildLayerTables(raw)
|
||||
}
|
||||
return out
|
||||
}()
|
||||
|
||||
func buildLayerTables(raw layerRaw) *layerTables {
|
||||
lt := &layerTables{
|
||||
rules: make(map[uint32]*downgradeRule, len(raw.rules)),
|
||||
newTypes: make(map[uint32]bool, len(raw.newTypes)),
|
||||
}
|
||||
for crc, r := range raw.rules {
|
||||
dr := &downgradeRule{target: r.target, structural: r.structural}
|
||||
if r.structural == "" {
|
||||
dr.keep = make(map[string]bool, len(r.keep))
|
||||
for _, n := range r.keep {
|
||||
dr.keep[n] = true
|
||||
}
|
||||
}
|
||||
lt.rules[crc] = dr
|
||||
}
|
||||
for _, crc := range raw.newTypes {
|
||||
lt.newTypes[crc] = true
|
||||
}
|
||||
lt.computeDirty()
|
||||
return lt
|
||||
}
|
||||
|
||||
// computeDirty marks every constructor (and abstract/bare type) that can
|
||||
// transitively contain a changed, structural, or layer-absent constructor, so
|
||||
// the transcoder can byte-copy the ~86% of the type graph that is unaffected.
|
||||
func (lt *layerTables) computeDirty() {
|
||||
lt.dirty = make(map[uint32]bool)
|
||||
lt.dirtyT = make(map[string]bool)
|
||||
// Seed: rules + new types are themselves dirty.
|
||||
for crc := range lt.rules {
|
||||
lt.dirty[crc] = true
|
||||
}
|
||||
for crc := range lt.newTypes {
|
||||
lt.dirty[crc] = true
|
||||
}
|
||||
markType := func(name string) {
|
||||
if name != "" && !lt.dirtyT[name] {
|
||||
lt.dirtyT[name] = true
|
||||
}
|
||||
}
|
||||
// Seed dirty types from seeded dirty ctors.
|
||||
for crc := range lt.dirty {
|
||||
if cl := canonical.byCRC[crc]; cl != nil {
|
||||
markType(cl.result)
|
||||
markType(cl.name) // bare reference
|
||||
}
|
||||
}
|
||||
// Fixpoint: a ctor is dirty if any field's type is dirty; a type is dirty
|
||||
// if any of its constructors is dirty.
|
||||
for changed := true; changed; {
|
||||
changed = false
|
||||
for crc, cl := range canonical.byCRC {
|
||||
if lt.dirty[crc] {
|
||||
continue
|
||||
}
|
||||
if lt.ctorHasDirtyField(cl) {
|
||||
lt.dirty[crc] = true
|
||||
if !lt.dirtyT[cl.result] {
|
||||
lt.dirtyT[cl.result] = true
|
||||
}
|
||||
if !lt.dirtyT[cl.name] {
|
||||
lt.dirtyT[cl.name] = true
|
||||
}
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (lt *layerTables) ctorHasDirtyField(cl *ctorLayout) bool {
|
||||
for i := range cl.fields {
|
||||
if lt.fieldDirty(&cl.fields[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (lt *layerTables) fieldDirty(f *fieldLayout) bool {
|
||||
switch f.kind {
|
||||
case kindObject, kindBareObject:
|
||||
return lt.dirtyT[f.typeName]
|
||||
case kindVector, kindVectorBare:
|
||||
return lt.fieldDirty(f.elem)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// structuralFunc transforms a changed constructor whose downgrade is not a pure
|
||||
// field drop. The leading CRC has already been consumed from in; the transform
|
||||
// reads the canonical body from in and writes the target-layer object (whose
|
||||
// constructor id is target) to out.
|
||||
type structuralFunc func(cl *ctorLayout, target uint32, in, out *bin.Buffer, layer int) error
|
||||
|
||||
// fallbackFunc replaces a layer-absent (227-only) constructor with an
|
||||
// equivalent the target layer understands. The leading CRC is NOT yet consumed.
|
||||
type fallbackFunc func(cl *ctorLayout, in, out *bin.Buffer, layer int) error
|
||||
|
||||
// structuralTransforms and the newType fallback registries are populated in
|
||||
// fallback.go. newTypeFallbacks is keyed by canonical CRC (specific override);
|
||||
// newTypeFallbacksByType is keyed by the canonical abstract result type and
|
||||
// covers every 227-only constructor of that class (e.g. any new MessageAction).
|
||||
var (
|
||||
structuralTransforms = map[string]structuralFunc{}
|
||||
newTypeFallbacks = map[uint32]fallbackFunc{}
|
||||
newTypeFallbacksByType = map[string]fallbackFunc{}
|
||||
)
|
||||
|
||||
// Transcode downgrades a single canonical (Layer 227) boxed object to the wire
|
||||
// shape of layer. layer >= CanonicalLayer (or unsupported) returns in verbatim.
|
||||
// On any transform gap it returns an error so the edge can fall back to sending
|
||||
// the canonical bytes rather than corrupting the stream.
|
||||
func Transcode(canonicalBytes []byte, layer int) ([]byte, error) {
|
||||
if layer >= CanonicalLayer {
|
||||
return canonicalBytes, nil
|
||||
}
|
||||
lt := tables[layer]
|
||||
if lt == nil {
|
||||
return canonicalBytes, nil // unsupported floor: best-effort passthrough
|
||||
}
|
||||
// Top-level constructors that are not in the canonical tg schema are MTProto
|
||||
// control/error objects (mt.*, e.g. rpc_error) — layer-invariant, so pass
|
||||
// them through. A nested unknown id is still a hard error (real gap).
|
||||
if id, err := (&bin.Buffer{Buf: canonicalBytes}).PeekID(); err != nil || canonical.byCRC[id] == nil {
|
||||
return canonicalBytes, nil
|
||||
}
|
||||
in := &bin.Buffer{Buf: canonicalBytes}
|
||||
out := &bin.Buffer{}
|
||||
if err := lt.transcodeObject(in, out, layer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if in.Len() != 0 {
|
||||
return nil, fmt.Errorf("layerwire: %d trailing bytes after transcode to layer %d", in.Len(), layer)
|
||||
}
|
||||
return out.Buf, nil
|
||||
}
|
||||
|
||||
// UpgradeMethodCRC maps an old client's method constructor id to the canonical
|
||||
// (227) id when the request body is byte-compatible — i.e. swapping the leading
|
||||
// 4-byte id yields a valid 227 request. It unifies two sources: generated
|
||||
// official layer drift (inboundMethodUpgrades) and hand-maintained client
|
||||
// constructor drift (clientMethodAliases). Returns ok=false for unchanged
|
||||
// methods and for changes that need a real decode (those stay as rpc handlers).
|
||||
func UpgradeMethodCRC(oldID uint32) (uint32, bool) {
|
||||
if newID, ok := inboundMethodUpgrades[oldID]; ok {
|
||||
return newID, true
|
||||
}
|
||||
newID, ok := clientMethodAliases[oldID]
|
||||
return newID, ok
|
||||
}
|
||||
|
||||
func (lt *layerTables) transcodeObject(in, out *bin.Buffer, layer int) error {
|
||||
id, err := in.PeekID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cl, ok := canonical.byCRC[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("layerwire: unknown constructor %#08x", id)
|
||||
}
|
||||
if rule := lt.rules[id]; rule != nil {
|
||||
if err := in.ConsumeID(id); err != nil {
|
||||
return err
|
||||
}
|
||||
if rule.structural != "" {
|
||||
fn := structuralTransforms[rule.structural]
|
||||
if fn == nil {
|
||||
return fmt.Errorf("layerwire: no structural transform %q for %s@%d", rule.structural, cl.name, layer)
|
||||
}
|
||||
return fn(cl, rule.target, in, out, layer)
|
||||
}
|
||||
out.PutID(rule.target)
|
||||
return lt.transcodeBody(in, out, cl, rule.keep, layer)
|
||||
}
|
||||
if lt.newTypes[id] {
|
||||
fn := newTypeFallbacks[id]
|
||||
if fn == nil {
|
||||
fn = newTypeFallbacksByType[cl.result]
|
||||
}
|
||||
if fn == nil {
|
||||
return fmt.Errorf("layerwire: %s (%#08x) absent at layer %d and no fallback", cl.name, id, layer)
|
||||
}
|
||||
return fn(cl, in, out, layer)
|
||||
}
|
||||
if !lt.dirty[id] {
|
||||
// Unaffected subtree: byte-for-byte copy.
|
||||
pre := in.Buf
|
||||
if err := canonical.skipObject(in); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Put(pre[:len(pre)-len(in.Buf)])
|
||||
return nil
|
||||
}
|
||||
// Unchanged at this level but a descendant is dirty: keep CRC, recurse.
|
||||
if err := in.ConsumeID(id); err != nil {
|
||||
return err
|
||||
}
|
||||
out.PutID(id)
|
||||
return lt.transcodeBody(in, out, cl, nil, layer)
|
||||
}
|
||||
|
||||
// transcodeBody re-encodes a constructor body. keep==nil means retain every
|
||||
// field (recursing into dirty descendants); otherwise only the named canonical
|
||||
// fields are written, flag integers are remasked to the retained bits, and
|
||||
// dropped fields are read-and-discarded.
|
||||
func (lt *layerTables) transcodeBody(in, out *bin.Buffer, cl *ctorLayout, keep map[string]bool, layer int) error {
|
||||
kept := func(name string) bool { return keep == nil || keep[name] }
|
||||
var flags map[string]uint32
|
||||
for i := range cl.fields {
|
||||
f := &cl.fields[i]
|
||||
if f.isFlags {
|
||||
v, err := in.Uint32()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s.%s: %w", cl.name, f.name, err)
|
||||
}
|
||||
if flags == nil {
|
||||
flags = make(map[string]uint32, 2)
|
||||
}
|
||||
flags[f.name] = v
|
||||
if kept(f.name) {
|
||||
out.PutUint32(v & lt.keptMask(cl, f.name, kept))
|
||||
}
|
||||
continue
|
||||
}
|
||||
present := !f.conditional() || flags[f.flagName]&(1<<uint(f.flagBit)) != 0
|
||||
if !present {
|
||||
continue
|
||||
}
|
||||
if kept(f.name) {
|
||||
if err := lt.transcodeValue(in, out, f, layer); err != nil {
|
||||
return fmt.Errorf("%s.%s: %w", cl.name, f.name, err)
|
||||
}
|
||||
} else if err := canonical.skipValue(in, f); err != nil {
|
||||
return fmt.Errorf("%s.%s (drop): %w", cl.name, f.name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// keptMask is the OR of bits for retained conditional fields gated by flagName,
|
||||
// clearing bits whose fields are dropped at the target layer.
|
||||
func (lt *layerTables) keptMask(cl *ctorLayout, flagName string, kept func(string) bool) uint32 {
|
||||
var mask uint32
|
||||
for i := range cl.fields {
|
||||
g := &cl.fields[i]
|
||||
if g.conditional() && g.flagName == flagName && kept(g.name) {
|
||||
mask |= 1 << uint(g.flagBit)
|
||||
}
|
||||
}
|
||||
return mask
|
||||
}
|
||||
|
||||
// transcodeValue writes one present field value, recursing only into dirty
|
||||
// subtrees and byte-copying everything else.
|
||||
func (lt *layerTables) transcodeValue(in, out *bin.Buffer, f *fieldLayout, layer int) error {
|
||||
if !lt.fieldDirty(f) {
|
||||
pre := in.Buf
|
||||
if err := canonical.skipValue(in, f); err != nil {
|
||||
return err
|
||||
}
|
||||
out.Put(pre[:len(pre)-len(in.Buf)])
|
||||
return nil
|
||||
}
|
||||
switch f.kind {
|
||||
case kindVector, kindVectorBare:
|
||||
if f.kind == kindVector {
|
||||
id, err := in.Uint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id != vectorTypeID {
|
||||
return fmt.Errorf("expected vector id, got %#08x", id)
|
||||
}
|
||||
out.PutUint32(vectorTypeID)
|
||||
}
|
||||
n, err := in.Int()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.PutInt(n)
|
||||
for i := 0; i < n; i++ {
|
||||
if err := lt.transcodeValue(in, out, f.elem, layer); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case kindObject:
|
||||
return lt.transcodeObject(in, out, layer)
|
||||
case kindBareObject:
|
||||
cl, ok := canonical.bareByT[f.typeName]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown bare type %q", f.typeName)
|
||||
}
|
||||
// Bare objects have no CRC and (within 220..227) no changed bare ctor;
|
||||
// recurse all-kept to reach any dirty descendants.
|
||||
return lt.transcodeBody(in, out, cl, nil, layer)
|
||||
default:
|
||||
// Primitive marked dirty should be impossible.
|
||||
return fmt.Errorf("unexpected dirty primitive kind %d", f.kind)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue