-
Notifications
You must be signed in to change notification settings - Fork 71
/
class_BSON.ahk
243 lines (199 loc) · 5.48 KB
/
class_BSON.ahk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
class BSONTypes {
static None := 0x00
static Double := 0x01
static String := 0x02
static Object := 0x03
static Boolean := 0x08
static Null := 0x0A
static Int := 0x10
static Int64 := 0x12
}
class BSON {
class Functor {
; Allows for BSON.Method() alongside BSON.Method.SubMethod()
__Call(Key, Params*) {
if (Key.__Class = "BSON") {
return this.Call(Params*)
}
}
}
class Load extends BSON.Functor {
FromHexString(String) {
Bytes := StrSplit(String, " ")
return this.Call(Bytes)
}
FromFile(FilePath) {
Scanner := FileOpen(FilePath, "r")
Object := this.FromScanner(Scanner)
Scanner.Close()
return Object
}
FromScanner(Scanner) {
return BSON.ParseDocument(Scanner)
}
Call(ByteArray) {
if !(IsObject(ByteArray))
Throw, Exception("BSON.Load() requires an array of bytes as input.")
ByteCount := ByteArray.Count()
Scanner := new ManagedScanningBuffer(ByteCount + 1) ; Loads a hex BSON object into memory, and then passes the buffer to the normal Load method
for k, Byte in ByteArray
Scanner.WriteChar(Conversions.HexToInt("0x" Byte))
Scanner.SeekStart(0)
return this.FromScanner(Scanner)
}
}
ParseDocument(Scanner) {
DocumentLength := Scanner.ReadUInt()
DocumentObject := {}
try {
loop {
Element := this.ParseElement(Scanner)
DocumentObject[Element.Key] := Element.Value
} until (Scanner.Tell() = DocumentLength)
}
catch E {
if (E.Message = "EOF") {
return DocumentObject
}
else {
Throw, E
}
}
return DocumentObject
}
ParseElement(Scanner) {
ElementType := Scanner.ReadChar()
if (ElementType = BSONTypes.None) {
Throw, Exception("EOF")
}
Key := this.ParseKeyName(Scanner)
Switch (ElementType) {
Case BSONTypes.Double: {
Value := Scanner.ReadDouble()
}
Case BSONTypes.String: {
Value := this.ParseString(Scanner)
}
Case BSONTypes.Object: {
Value := this.ParseDocument(Scanner)
}
Case BSONTypes.Boolean: {
Value := Scanner.ReadChar()
}
Case BSONTypes.Null: {
Value := ""
}
Case BSONTypes.Int: {
Value := Scanner.ReadInt()
}
Case BSONTypes.Int64: {
Value := Scanner.ReadInt64()
}
Default: {
Throw, Exception("Un-supported value-type: " ElementType)
}
}
return {"Key": Key, "Value": Value}
}
ParseKeyName(Scanner) {
; Reads a cstring
String := ""
loop {
NextCharacter := Scanner.ReadChar()
if (NextCharacter = 0) {
return String
}
String .= Chr(NextCharacter)
}
}
ParseString(Scanner) {
; Reads a string length, then a cstring
StringLength := Scanner.ReadUInt()
StringValue := this.ParseKeyName(Scanner)
return StringValue
}
class Dump extends BSON.Functor {
ToFile(FilePath, Object) {
F := FileOpen(FilePath, "w")
for k, Byte in this.Call(Object) {
F.WriteChar(Byte)
}
F.Close()
return 1
}
ToHexString(Object) {
String := ""
for k, Byte in this.Call(Object) {
String .= Conversions.IntToHex(Byte) " "
}
return String
}
Call(Object) {
return BSON.DumpObject(Object)
}
}
DumpObject(Object) {
ObjectBytes := [] ; An object starts with a uint32 of how big it is, but we can insert that at the end
for Key, Value in Object {
KeyValueBytes := this.DumpElement(Key, Value) ; then a list of elements
BSON.Merge(ObjectBytes, KeyValueBytes)
}
ObjectBytes.Push(0) ; And ends with a null terminator
return BSON.Merge(Conversions.SplitIntoBytes32(ObjectBytes.Count() + 4), ObjectBytes) ; BSON.Merge the elements/null terminator onto the size of the object
}
DumpElement(Key, Value) {
ElementBytes := []
if (IsObject(Key)) {
Throw, Exception("The BSON format does not support using objects as keys.")
}
BSON.Merge(ElementBytes, this.DumpString(Key))
if (Conversions.IsFloat(Value)) {
ElementType := BSONTypes.Double
BSON.Merge(ElementBytes, Conversions.SplitIntoBytes64(Conversions.FloatToBinaryInt(Value)))
}
else if (Value = 0 || Value = 1) {
ElementType := BSONTypes.Boolean
BSON.Merge(ElementBytes, [Value]) ; For a boolean, it's just 0x08 NAME (0x00|0x01) so we can just push the value (which is either 0 or 1)
}
else if (Value = "") {
ElementType := BSONTypes.Null ; For the null type, there is no value
}
else if (IsObject(Value)) {
ElementType := BSONTypes.Object
BSON.Merge(ElementBytes, this.DumpObject(Value))
}
else if (Conversions.IsNumber(Value)) {
ValueLength := Conversions.NumberSizeOf(Value)
if (ValueLength <= 32) {
ElementType := BSONTypes.Int
BSON.Merge(ElementBytes, Conversions.SplitIntoBytes32(Value))
}
else {
ElementType := BSONTypes.Int64
BSON.Merge(ElementBytes, Conversions.SplitIntoBytes64(Value))
}
}
else {
ElementType := BSONTypes.String
BSON.Merge(ElementBytes, Conversions.SplitIntoBytes32(StrLen(Value) + 1)) ; When an element is a string, first you have to dump it's length
BSON.Merge(ElementBytes, this.DumpString(Value))
}
return BSON.Merge([ElementType], ElementBytes) ; Prepend the element type onto the key/value
}
DumpString(String) {
StringBytes := [] ; A string is just a null-terminated string
for k, Character in StrSplit(String) {
StringBytes.Push(Asc(Character))
}
StringBytes.Push(0) ; null terminator
return StringBytes
}
Merge(ArrayOne, ArrayTwo) {
for k, v in ArrayTwo {
ArrayOne.Push(v)
}
return ArrayOne
}
}
#Include <class_ScanningBuffer>
#Include <class_Conversions>