feat(mobs): opensource message enc/dec generator

This commit is contained in:
ShiKhu 2022-07-22 13:06:52 +02:00
parent 705eec29fe
commit 1860655ab0
16 changed files with 1178 additions and 0 deletions

16
mobs/README.md Normal file
View file

@ -0,0 +1,16 @@
# Message Object Binary Schema and Code Generator from Templates
To generate all necessary files for the project:
```sh
ruby run.rb
```
In order generated .go file to fit the go formatting style:
```sh
gofmt -w ../backend/pkg/messages/messages.go
```
(Otherwise there will be changes in stage)

172
mobs/ios_messages.rb Normal file
View file

@ -0,0 +1,172 @@
message 107, 'IOSBatchMeta', :replayer => false do
uint 'Timestamp'
uint 'Length'
uint 'FirstIndex'
end
message 90, 'IOSSessionStart', :replayer => true do
uint 'Timestamp'
# uint 'Length'
uint 'ProjectID'
string 'TrackerVersion'
string 'RevID'
string 'UserUUID'
# string 'UserAgent'
string 'UserOS'
string 'UserOSVersion'
# string 'UserBrowser'
# string 'UserBrowserVersion'
string 'UserDevice'
string 'UserDeviceType'
# uint 'UserDeviceMemorySize'
# uint 'UserDeviceHeapSize'
string 'UserCountry'
end
message 91, 'IOSSessionEnd' do
uint 'Timestamp'
end
message 92, 'IOSMetadata' do
uint 'Timestamp'
uint 'Length'
string 'Key'
string 'Value'
end
message 93, 'IOSCustomEvent', :seq_index => true, :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Name'
string 'Payload'
end
message 94, 'IOSUserID' do
uint 'Timestamp'
uint 'Length'
string 'Value'
end
message 95, 'IOSUserAnonymousID' do
uint 'Timestamp'
uint 'Length'
string 'Value'
end
message 96, 'IOSScreenChanges', :replayer => true do
uint 'Timestamp'
uint 'Length'
uint 'X'
uint 'Y'
uint 'Width'
uint 'Height'
end
message 97, 'IOSCrash', :seq_index => true do
uint 'Timestamp'
uint 'Length'
string 'Name'
string 'Reason'
string 'Stacktrace'
end
message 98, 'IOSScreenEnter', :seq_index => true do
uint 'Timestamp'
uint 'Length'
string 'Title'
string 'ViewName'
end
message 99, 'IOSScreenLeave' do
uint 'Timestamp'
uint 'Length'
string 'Title'
string 'ViewName'
end
message 100, 'IOSClickEvent', :seq_index => true, :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Label'
uint 'X'
uint 'Y'
end
message 101, 'IOSInputEvent', :seq_index => true do
uint 'Timestamp'
uint 'Length'
string 'Value'
boolean 'ValueMasked'
string 'Label'
end
=begin
Name/Value may be :
"physicalMemory": Total memory in bytes
"processorCount": Total processors in device
?"activeProcessorCount": Number of currently used processors
"systemUptime": Elapsed time (in seconds) since last boot
?"isLowPowerModeEnabled": Possible values (1 or 0)
2/3!"thermalState": Possible values (0:nominal 1:fair 2:serious 3:critical)
!"batteryLevel": Possible values (0 .. 100)
"batteryState": Possible values (0:unknown 1:unplugged 2:charging 3:full)
"orientation": Possible values (0unknown 1:portrait 2:portraitUpsideDown 3:landscapeLeft 4:landscapeRight 5:faceUp 6:faceDown)
"mainThreadCPU": Possible values (0 .. 100)
"memoryUsage": Used memory in bytes
=end
message 102, 'IOSPerformanceEvent', :replayer => true, :seq_index => true do
uint 'Timestamp'
uint 'Length'
string 'Name'
uint 'Value'
end
message 103, 'IOSLog', :replayer => true do
uint 'Timestamp'
uint 'Length'
string 'Severity' # Possible values ("info", "error")
string 'Content'
end
message 104, 'IOSInternalError' do
uint 'Timestamp'
uint 'Length'
string 'Content'
end
message 105, 'IOSNetworkCall', :replayer => true, :seq_index => true do
uint 'Timestamp'
uint 'Length'
uint 'Duration'
string 'Headers'
string 'Body'
string 'URL'
boolean 'Success'
string 'Method'
uint 'Status'
end
message 110, 'IOSPerformanceAggregated', :swift => false do
uint 'TimestampStart'
uint 'TimestampEnd'
uint 'MinFPS'
uint 'AvgFPS'
uint 'MaxFPS'
uint 'MinCPU'
uint 'AvgCPU'
uint 'MaxCPU'
uint 'MinMemory'
uint 'AvgMemory'
uint 'MaxMemory'
uint 'MinBattery'
uint 'AvgBattery'
uint 'MaxBattery'
end
message 111, 'IOSIssueEvent', :seq_index => true do
uint 'Timestamp'
string 'Type'
string 'ContextString'
string 'Context'
string 'Payload'
end

409
mobs/messages.rb Normal file
View file

@ -0,0 +1,409 @@
# Special one for Batch Meta. Message id could define the version
message 80, 'BatchMeta', :replayer => false do
uint 'PageNo'
uint 'FirstIndex'
int 'Timestamp'
end
message 0, 'Timestamp' do
uint 'Timestamp'
end
message 1, 'SessionStart', :js => false, :replayer => false do
uint 'Timestamp'
uint 'ProjectID'
string 'TrackerVersion'
string 'RevID'
string 'UserUUID'
string 'UserAgent'
string 'UserOS'
string 'UserOSVersion'
string 'UserBrowser'
string 'UserBrowserVersion'
string 'UserDevice'
string 'UserDeviceType'
uint 'UserDeviceMemorySize'
uint 'UserDeviceHeapSize'
string 'UserCountry'
string 'UserID'
end
# Depricated (not used) since OpenReplay tracker 3.0.0
message 2, 'SessionDisconnect', :js => false do
uint 'Timestamp'
end
message 3, 'SessionEnd', :js => false, :replayer => false do
uint 'Timestamp'
end
message 4, 'SetPageLocation' do
string 'URL'
string 'Referrer'
uint 'NavigationStart'
end
message 5, 'SetViewportSize' do
uint 'Width'
uint 'Height'
end
message 6, 'SetViewportScroll' do
int 'X'
int 'Y'
end
message 7, 'CreateDocument' do
end
message 8, 'CreateElementNode' do
uint 'ID'
uint 'ParentID'
uint 'index'
string 'Tag'
boolean 'SVG'
end
message 9, 'CreateTextNode' do
uint 'ID'
uint 'ParentID'
uint 'Index'
end
message 10, 'MoveNode' do
uint 'ID'
uint 'ParentID'
uint 'Index'
end
message 11, 'RemoveNode' do
uint 'ID'
end
message 12, 'SetNodeAttribute' do
uint 'ID'
string 'Name'
string 'Value'
end
message 13, 'RemoveNodeAttribute' do
uint 'ID'
string 'Name'
end
message 14, 'SetNodeData' do
uint 'ID'
string 'Data'
end
# Depricated starting from 5.5.11 in favor of SetStyleData
message 15, 'SetCSSData', :js => false do
uint 'ID'
string 'Data'
end
message 16, 'SetNodeScroll' do
uint 'ID'
int 'X'
int 'Y'
end
message 17, 'SetInputTarget', :replayer => false do
uint 'ID'
string 'Label'
end
message 18, 'SetInputValue' do
uint 'ID'
string 'Value'
int 'Mask'
end
message 19, 'SetInputChecked' do
uint 'ID'
boolean 'Checked'
end
message 20, 'MouseMove' do
uint 'X'
uint 'Y'
end
# Depricated since OpenReplay 1.2.0
message 21, 'MouseClickDepricated', :js => false, :replayer => false do
uint 'ID'
uint 'HesitationTime'
string 'Label'
end
message 22, 'ConsoleLog' do
string 'Level'
string 'Value'
end
message 23, 'PageLoadTiming', :replayer => false do
uint 'RequestStart'
uint 'ResponseStart'
uint 'ResponseEnd'
uint 'DomContentLoadedEventStart'
uint 'DomContentLoadedEventEnd'
uint 'LoadEventStart'
uint 'LoadEventEnd'
uint 'FirstPaint'
uint 'FirstContentfulPaint'
end
message 24, 'PageRenderTiming', :replayer => false do
uint 'SpeedIndex'
uint 'VisuallyComplete'
uint 'TimeToInteractive'
end
message 25, 'JSException', :replayer => false do
string 'Name'
string 'Message'
string 'Payload'
end
message 26, 'IntegrationEvent', :js => false, :replayer => false do
uint 'Timestamp'
string 'Source'
string 'Name'
string 'Message'
string 'Payload'
end
message 27, 'RawCustomEvent', :replayer => false do
string 'Name'
string 'Payload'
end
message 28, 'UserID', :replayer => false do
string 'ID'
end
message 29, 'UserAnonymousID', :replayer => false do
string 'ID'
end
message 30, 'Metadata', :replayer => false do
string 'Key'
string 'Value'
end
message 31, 'PageEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'URL'
string 'Referrer'
boolean 'Loaded'
uint 'RequestStart'
uint 'ResponseStart'
uint 'ResponseEnd'
uint 'DomContentLoadedEventStart'
uint 'DomContentLoadedEventEnd'
uint 'LoadEventStart'
uint 'LoadEventEnd'
uint 'FirstPaint'
uint 'FirstContentfulPaint'
uint 'SpeedIndex'
uint 'VisuallyComplete'
uint 'TimeToInteractive'
end
message 32, 'InputEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'Value'
boolean 'ValueMasked'
string 'Label'
end
message 33, 'ClickEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
uint 'HesitationTime'
string 'Label'
string 'Selector'
end
message 34, 'ErrorEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'Source'
string 'Name'
string 'Message'
string 'Payload'
end
message 35, 'ResourceEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
uint 'Duration'
uint 'TTFB'
uint 'HeaderSize'
uint 'EncodedBodySize'
uint 'DecodedBodySize'
string 'URL'
string 'Type'
boolean 'Success'
string 'Method'
uint 'Status'
end
message 36, 'CustomEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'Name'
string 'Payload'
end
message 37, 'CSSInsertRule' do
uint 'ID'
string 'Rule'
uint 'Index'
end
message 38, 'CSSDeleteRule' do
uint 'ID'
uint 'Index'
end
message 39, 'Fetch' do
string 'Method'
string 'URL'
string 'Request'
string 'Response'
uint 'Status'
uint 'Timestamp'
uint 'Duration'
end
message 40, 'Profiler' do
string 'Name'
uint 'Duration'
string 'Args'
string 'Result'
end
message 41, 'OTable' do
string 'Key'
string 'Value'
end
message 42, 'StateAction', :replayer => false do
string 'Type'
end
message 43, 'StateActionEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'Type'
end
message 44, 'Redux' do
string 'Action'
string 'State'
uint 'Duration'
end
message 45, 'Vuex' do
string 'Mutation'
string 'State'
end
message 46, 'MobX' do
string 'Type'
string 'Payload'
end
message 47, 'NgRx' do
string 'Action'
string 'State'
uint 'Duration'
end
message 48, 'GraphQL' do
string 'OperationKind'
string 'OperationName'
string 'Variables'
string 'Response'
end
message 49, 'PerformanceTrack' do
int 'Frames'
int 'Ticks'
uint 'TotalJSHeapSize'
uint 'UsedJSHeapSize'
end
message 50, 'GraphQLEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'OperationKind'
string 'OperationName'
string 'Variables'
string 'Response'
end
message 51, 'FetchEvent', :js => false, :replayer => false do
uint 'MessageID'
uint 'Timestamp'
string 'Method'
string 'URL'
string 'Request'
string 'Response'
uint 'Status'
uint 'Duration'
end
message 52, 'DOMDrop', :js => false, :replayer => false do
uint 'Timestamp'
end
message 53, 'ResourceTiming', :replayer => false do
uint 'Timestamp'
uint 'Duration'
uint 'TTFB'
uint 'HeaderSize'
uint 'EncodedBodySize'
uint 'DecodedBodySize'
string 'URL'
string 'Initiator'
end
message 54, 'ConnectionInformation' do
uint 'Downlink'
string 'Type'
end
message 55, 'SetPageVisibility' do
boolean 'hidden'
end
message 56, 'PerformanceTrackAggr', :js => false, :replayer => false do
uint 'TimestampStart'
uint 'TimestampEnd'
uint 'MinFPS'
uint 'AvgFPS'
uint 'MaxFPS'
uint 'MinCPU'
uint 'AvgCPU'
uint 'MaxCPU'
uint 'MinTotalJSHeapSize'
uint 'AvgTotalJSHeapSize'
uint 'MaxTotalJSHeapSize'
uint 'MinUsedJSHeapSize'
uint 'AvgUsedJSHeapSize'
uint 'MaxUsedJSHeapSize'
end
message 59, 'LongTask' do
uint 'Timestamp'
uint 'Duration'
uint 'Context'
uint 'ContainerType'
string 'ContainerSrc'
string 'ContainerId'
string 'ContainerName'
end
message 60, 'SetNodeAttributeURLBased', :replayer => false do
uint 'ID'
string 'Name'
string 'Value'
string 'BaseURL'
end
# Might replace SetCSSData (although BaseURL is useless after rewriting)
message 61, 'SetCSSDataURLBased', :replayer => false do
uint 'ID'
string 'Data'
string 'BaseURL'
end
message 62, 'IssueEvent', :replayer => false, :js => false do
uint 'MessageID'
uint 'Timestamp'
string 'Type'
string 'ContextString'
string 'Context'
string 'Payload'
end
message 63, 'TechnicalInfo', :replayer => false do
string 'Type'
string 'Value'
end
message 64, 'CustomIssue', :replayer => false do
string 'Name'
string 'Payload'
end
# Since 5.6.6; only for websocket (might be probably replaced with ws.close())
# Depricated
message 65, 'PageClose', :replayer => false do
end
message 66, 'AssetCache', :replayer => false, :js => false do
string 'URL'
end
message 67, 'CSSInsertRuleURLBased', :replayer => false do
uint 'ID'
string 'Rule'
uint 'Index'
string 'BaseURL'
end
message 69, 'MouseClick' do
uint 'ID'
uint 'HesitationTime'
string 'Label'
string 'Selector'
end
# Since 3.4.0
message 70, 'CreateIFrameDocument' do
uint 'FrameID'
uint 'ID'
end

View file

@ -0,0 +1,124 @@
package messages
import (
"errors"
"io"
)
func ReadByte(reader io.Reader) (byte, error) {
p := make([]byte, 1)
_, err := io.ReadFull(reader, p)
if err != nil {
return 0, err
}
return p[0], nil
}
func SkipBytes(reader io.ReadSeeker) error {
n, err := ReadUint(reader)
if err != nil {
return err
}
_, err := reader.Seek(n, io.SeekCurrent);
return err
}
func ReadData(reader io.Reader) ([]byte, error) {
n, err := ReadUint(reader)
if err != nil {
return nil, err
}
p := make([]byte, n)
_, err := io.ReadFull(reader, p)
if err != nil {
return nil, err
}
return p, nil
}
func ReadUint(reader io.Reader) (uint64, error) {
var x uint64
var s uint
i := 0
for {
b, err := ReadByte(reader)
if err != nil {
return x, err
}
if b < 0x80 {
if i > 9 || i == 9 && b > 1 {
return x, errors.New("overflow")
}
return x | uint64(b)<<s, nil
}
x |= uint64(b&0x7f) << s
s += 7
i++
}
}
func ReadInt(reader io.Reader) (int64, error) {
ux, err := ReadUint(reader)
x := int64(ux >> 1)
if err != nil {
return x, err
}
if ux&1 != 0 {
x = ^x
}
return x, err
}
func ReadBoolean(reader io.Reader) (bool, error) {
p := make([]byte, 1)
_, err := io.ReadFull(reader, p)
if err != nil {
return false, err
}
return p[0] == 1, nil
}
func ReadString(reader io.Reader) (string, error) {
l, err := ReadUint(reader)
if err != nil {
return "", err
}
buf := make([]byte, l)
_, err = io.ReadFull(reader, buf)
if err != nil {
return "", err
}
return string(buf), nil
}
func WriteUint(v uint64, buf []byte, p int) int {
for v >= 0x80 {
buf[p] = byte(v) | 0x80
v >>= 7
p++
}
buf[p] = byte(v)
return p + 1
}
func WriteInt(v int64, buf []byte, p int) int {
uv := uint64(v) << 1
if v < 0 {
uv = ^uv
}
return WriteUint(uv, buf, p)
}
func WriteBoolean(v bool, buf []byte, p int) int {
if v {
buf[p] = 1
} else {
buf[p] = 0
}
return p + 1
}
func WriteString(str string, buf []byte, p int) int {
p = WriteUint(uint64(len(str)), buf, p)
return p + copy(buf[p:], str)
}

View file

@ -0,0 +1,62 @@
import io
class Codec:
"""
Implements encode/decode primitives
"""
@staticmethod
def read_boolean(reader: io.BytesIO):
b = reader.read(1)
return b == 1
@staticmethod
def read_uint(reader: io.BytesIO):
"""
The ending "big" doesn't play any role here,
since we're dealing with data per one byte
"""
x = 0 # the result
s = 0 # the shift (our result is big-ending)
i = 0 # n of byte (max 9 for uint64)
while True:
b = reader.read(1)
num = int.from_bytes(b, "big", signed=False)
# print(i, x)
if num < 0x80:
if i > 9 | i == 9 & num > 1:
raise OverflowError()
return int(x | num << s)
x |= (num & 0x7f) << s
s += 7
i += 1
@staticmethod
def read_int(reader: io.BytesIO) -> int:
"""
ux, err := ReadUint(reader)
x := int64(ux >> 1)
if err != nil {
return x, err
}
if ux&1 != 0 {
x = ^x
}
return x, err
"""
ux = Codec.read_uint(reader)
x = int(ux >> 1)
if ux & 1 != 0:
x = - x - 1
return x
@staticmethod
def read_string(reader: io.BytesIO) -> str:
length = Codec.read_uint(reader)
s = reader.read(length)
try:
return s.decode("utf-8", errors="replace").replace("\x00", "\uFFFD")
except UnicodeDecodeError:
return None

View file

@ -0,0 +1,60 @@
extension Data {
func readByte(offset: inout Int) -> UInt8 {
if offset >= self.count {
fatalError(">>> Error reading Byte")
}
let b = self[offset]
offset += 1
return b
}
func readUint(offset: inout Int) -> UInt64 {
var x: UInt64 = 0
var s: Int = 0
var i: Int = 0
while true {
let b = readByte(offset: &offset)
if b < 0x80 {
if i > 9 || i == 9 && b > 1 {
fatalError(">>> Error reading UInt")
}
return x | UInt64(b)<<s
}
x |= UInt64(b&0x7f) << s
s += 7
i += 1
}
}
func readInt(offset: inout Int) -> Int64 {
let ux = readUint(offset: &offset)
var x = Int64(ux >> 1)
if ux&1 != 0 {
x = ~x
}
return x
}
func readBoolean(offset: inout Int) -> Bool {
return readByte(offset: &offset) == 1
}
mutating func writeUint(_ input: UInt64) {
var v = input
while v >= 0x80 {
append(UInt8(v.littleEndian & 0x7F) | 0x80) // v.littleEndian ?
v >>= 7
}
append(UInt8(v))
}
mutating func writeInt(_ v: Int64) {
var uv = UInt64(v) << 1
if v < 0 {
uv = ~uv
}
writeUint(uv)
}
mutating func writeBoolean(_ v: Bool) {
if v {
append(1)
} else {
append(0)
}
}
}

136
mobs/run.rb Normal file
View file

@ -0,0 +1,136 @@
require 'erb'
# TODO: change method names to correct (CapitalCase and camelCase, not CamalCase and firstLower)
class String
def camel_case
return self if self !~ /_/ && self =~ /[A-Z]+.*/
split('_').map{|e| e.capitalize}.join.upperize
end
def camel_case_lower
self.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join.upperize
end
def upperize
self.sub('Id', 'ID').sub('Url', 'URL')
end
def first_lower
self.sub(/^[A-Z]+/) {|f| f.downcase }
end
def underscore
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end
class Attribute
attr_reader :name, :type
def initialize(name:, type:)
@name = name
@type = type
end
def type_js
case @type
when :int
"number"
when :uint
"number"
when :json
# TODO
# raise "Unexpected attribute type: data type attribute #{@name} found in JS template"
"string"
when :data
raise "Unexpected attribute type: data type attribute #{@name} found in JS template"
else
@type
end
end
def type_go
case @type
when :int
'int64'
when :uint
'uint64'
when :string
'string'
when :data
'[]byte'
when :boolean
'bool'
when :json
'interface{}'
end
end
def lengh_encoded
case @type
when :string, :data
true
else
false
end
end
end
$context = :web
class Message
attr_reader :id, :name, :js, :replayer, :swift, :seq_index, :attributes, :context
def initialize(name:, id:, js: $context == :web, replayer: $context == :web, swift: $context == :ios, seq_index: false, &block)
@id = id
@name = name
@js = js
@replayer = replayer
@swift = swift
@seq_index = seq_index
@context = $context
@attributes = []
instance_eval &block
end
%i(int uint boolean string data).each do |type|
define_method type do |name, opts = {}|
opts.merge!(
name: name,
type: type,
)
@attributes << Attribute.new(opts)
end
end
end
$ids = []
$messages = []
def message(id, name, opts = {}, &block)
raise "id duplicated #{name}" if $ids.include? id
raise "id is too big #{name}" if id > 120
$ids << id
opts[:id] = id
opts[:name] = name
msg = Message.new(opts, &block)
$messages << msg
end
require './messages.rb'
$context = :ios
require './ios_messages.rb'
Dir["templates/*.erb"].each do |tpl|
e = ERB.new(File.read(tpl))
path = tpl.split '/'
t = '../' + path[1].gsub('~', '/')
t = t[0..-5]
File.write(t, e.result)
puts tpl + ' --> ' + t
end

View file

@ -0,0 +1,10 @@
// Auto-generated, do not edit
package messages
func IsReplayerType(id int) bool {
return <%= $messages.select { |msg| msg.replayer }.map{ |msg| "#{msg.id} == id" }.join(' || ') %>
}
func IsIOSType(id int) bool {
return <%= $messages.select { |msg| msg.context == :ios }.map{ |msg| "#{msg.id} == id"}.join(' || ') %>
}

View file

@ -0,0 +1,12 @@
// Auto-generated, do not edit
package messages
func GetTimestamp(message Message) uint64 {
switch msg := message.(type) {
<% $messages.select { |msg| msg.swift }.each do |msg| %>
case *<%= msg.name %>:
return msg.Timestamp
<% end %>
}
return uint64(message.Meta().Timestamp)
}

View file

@ -0,0 +1,22 @@
// Auto-generated, do not edit
package messages
<% $messages.each do |msg| %>
type <%= msg.name %> struct {
message
<%= msg.attributes.map { |attr|
" #{attr.name} #{attr.type_go}" }.join "\n" %>
}
func (msg *<%= msg.name %>) Encode() []byte {
buf := make([]byte, <%= msg.attributes.count * 10 + 1 %><%= msg.attributes.map { |attr| "+len(msg.#{attr.name})" if attr.lengh_encoded }.compact.join %>)
buf[0] = <%= msg.id %>
p := 1
<%= msg.attributes.map { |attr|
" p = Write#{attr.type.to_s.camel_case}(msg.#{attr.name}, buf, p)" }.join "\n" %>
return buf[:p]
}
func (msg *<%= msg.name %>) TypeID() int {
return <%= msg.id %>
}
<% end %>

View file

@ -0,0 +1,26 @@
// Auto-generated, do not edit
package messages
import (
"fmt"
"io"
)
func ReadMessage(reader io.Reader) (Message, error) {
t, err := ReadUint(reader)
if err != nil {
return nil, err
}
switch t {
<% $messages.each do |msg| %>
case <%= msg.id %>:
msg := &<%= msg.name %>{}
<%= msg.attributes.map { |attr|
" if msg.#{attr.name}, err = Read#{attr.type.to_s.camel_case}(reader); err != nil {
return nil, err
}" }.join "\n" %>
return msg, nil
<% end %>
}
return nil, fmt.Errorf("Unknown message code: %v", t)
}

View file

@ -0,0 +1,35 @@
// Auto-generated, do not edit
import PrimitiveReader from './PrimitiveReader'
import type { RawMessage } from './raw'
export default class RawMessageReader extends PrimitiveReader {
readMessage(): RawMessage | null {
const p = this.p
const resetPointer = () => {
this.p = p
return null
}
const tp = this.readUint()
if (tp === null) { return resetPointer() }
switch (tp) {
<% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %>
case <%= msg.id %>: {
<%= msg.attributes.map { |attr|
" const #{attr.name.first_lower} = this.read#{attr.type.to_s.camel_case}(); if (#{attr.name.first_lower} === null) { return resetPointer() }" }.join "\n" %>
return {
tp: "<%= msg.name.underscore %>",
<%= msg.attributes.map { |attr|
" #{attr.name.first_lower}," }.join "\n" %>
};
}
<% end %>
default:
throw new Error(`Unrecognizable message type: ${ tp }; Pointer at the position ${this.p} of ${this.buf.length}`)
return null;
}
}
}

View file

@ -0,0 +1,13 @@
// Auto-generated, do not edit
import type { Timed } from './timed'
import type { RawMessage } from './raw'
import type {
<%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| " Raw#{msg.name.underscore.camel_case}," }.join "\n" %>
} from './raw'
export type Message = RawMessage & Timed
<% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %>
export type <%= msg.name.underscore.camel_case %> = Raw<%= msg.name.underscore.camel_case %> & Timed
<% end %>

View file

@ -0,0 +1,14 @@
// Auto-generated, do not edit
export const TP_MAP = {
<%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| " #{msg.id}: \"#{msg.name.underscore}\"," }.join "\n" %>
}
<% $messages.select { |msg| msg.js || msg.replayer }.each do |msg| %>
export interface Raw<%= msg.name.underscore.camel_case %> {
tp: "<%= msg.name.underscore %>",
<%= msg.attributes.map { |attr| " #{attr.name.first_lower}: #{attr.type_js}," }.join "\n" %>
}
<% end %>
export type RawMessage = <%= $messages.select { |msg| msg.js || msg.replayer }.map { |msg| "Raw#{msg.name.underscore.camel_case}" }.join " | " %>;

View file

@ -0,0 +1,36 @@
// Auto-generated, do not edit
import UIKit
enum ASMessageType: UInt64 {
<%= $messages.map { |msg| " case #{msg.name.first_lower} = #{msg.id}" }.join "\n" %>
}
<% $messages.each do |msg| %>
class AS<%= msg.name.to_s.camel_case %>: ASMessage {
<%= msg.attributes[2..-1].map { |attr| " let #{attr.property}: #{attr.type_swift}" }.join "\n" %>
init(<%= msg.attributes[2..-1].map { |attr| "#{attr.property}: #{attr.type_swift}" }.join ", " %>) {
<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = #{attr.property}" }.join "\n" %>
super.init(messageType: .<%= "#{msg.name.first_lower}" %>)
}
override init?(genericMessage: GenericMessage) {
<% if msg.attributes.length > 2 %> do {
var offset = 0
<%= msg.attributes[2..-1].map { |attr| " self.#{attr.property} = try genericMessage.body.read#{attr.type_swift_read}(offset: &offset)" }.join "\n" %>
super.init(genericMessage: genericMessage)
} catch {
return nil
}
<% else %>
super.init(genericMessage: genericMessage)
<% end %>}
override func contentData() -> Data {
return Data(values: UInt64(<%= "#{msg.id}"%>), timestamp<% if msg.attributes.length > 2 %>, Data(values: <%= msg.attributes[2..-1].map { |attr| attr.property }.join ", "%>)<% end %>)
}
override var description: String {
return "-->> <%= msg.name.to_s.camel_case %>(<%= "#{msg.id}"%>): timestamp:\(timestamp) <%= msg.attributes[2..-1].map { |attr| "#{attr.property}:\\(#{attr.property})" }.join " "%>";
}
}
<% end %>

View file

@ -0,0 +1,31 @@
// Auto-generated, do not edit
import type { Writer, Message }from "./types.js";
export default Message
function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
Class: C & { new(...args: A): T }
): C & ((...args: A) => T) {
function _Class(...args: A) {
return new Class(...args);
}
_Class.prototype = Class.prototype;
return <C & ((...args: A) => T)>_Class;
}
export const classes: Map<number, Function> = new Map();
<% $messages.select { |msg| msg.js }.each do |msg| %>
class _<%= msg.name %> implements Message {
readonly _id: number = <%= msg.id %>;
constructor(
<%= msg.attributes.map { |attr| "public #{attr.name.first_lower}: #{attr.type_js}" }.join ",\n " %>
) {}
encode(writer: Writer): boolean {
return writer.uint(<%= msg.id %>)<%= " &&" if msg.attributes.length() > 0 %>
<%= msg.attributes.map { |attr| "writer.#{attr.type}(this.#{attr.name.first_lower})" }.join " &&\n " %>;
}
}
export const <%= msg.name %> = bindNew(_<%= msg.name %>);
classes.set(<%= msg.id %>, <%= msg.name %>);
<% end %>