feat(mobs): opensource message enc/dec generator
This commit is contained in:
parent
705eec29fe
commit
1860655ab0
16 changed files with 1178 additions and 0 deletions
16
mobs/README.md
Normal file
16
mobs/README.md
Normal 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
172
mobs/ios_messages.rb
Normal 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
409
mobs/messages.rb
Normal 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
|
||||
124
mobs/primitives/primitives.go
Normal file
124
mobs/primitives/primitives.go
Normal 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)
|
||||
}
|
||||
62
mobs/primitives/primitives.py
Normal file
62
mobs/primitives/primitives.py
Normal 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
|
||||
60
mobs/primitives/primitives.swift
Normal file
60
mobs/primitives/primitives.swift
Normal 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
136
mobs/run.rb
Normal 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
|
||||
10
mobs/templates/backend~pkg~messages~filters.go.erb
Normal file
10
mobs/templates/backend~pkg~messages~filters.go.erb
Normal 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(' || ') %>
|
||||
}
|
||||
12
mobs/templates/backend~pkg~messages~get-timestamp.go.erb
Normal file
12
mobs/templates/backend~pkg~messages~get-timestamp.go.erb
Normal 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)
|
||||
}
|
||||
22
mobs/templates/backend~pkg~messages~messages.go.erb
Normal file
22
mobs/templates/backend~pkg~messages~messages.go.erb
Normal 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 %>
|
||||
26
mobs/templates/backend~pkg~messages~read-message.go.erb
Normal file
26
mobs/templates/backend~pkg~messages~read-message.go.erb
Normal 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)
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 %>
|
||||
|
|
@ -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 " | " %>;
|
||||
36
mobs/templates/ios/ASMessage.swift
Normal file
36
mobs/templates/ios/ASMessage.swift
Normal 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 %>
|
||||
31
mobs/templates/tracker~tracker~src~common~messages.ts.erb
Normal file
31
mobs/templates/tracker~tracker~src~common~messages.ts.erb
Normal 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 %>
|
||||
Loading…
Add table
Reference in a new issue