├── .codeclimate.yml ├── .travis.yml ├── LICENSE ├── README.markdown └── v2 ├── erlang ├── erlang.go └── erlang_test.go └── go.mod /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | golint: 4 | enabled: true 5 | checks: 6 | GoLint/Naming/MixedCaps: 7 | enabled: false 8 | govet: 9 | enabled: true 10 | gofmt: 11 | enabled: true 12 | fixme: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - "**.go" 17 | exclude_paths: 18 | - "**/*_test.go" 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.6 4 | - 1.8.x 5 | - 1.9.x 6 | - 1.10.x 7 | - 1.11.x 8 | - 1.12.x 9 | - 1.13.x 10 | - 1.14.x 11 | - 1.15.x 12 | - 1.16.x 13 | - 1.17.x 14 | - 1.18.x 15 | - 1.19.x 16 | - 1.20.x 17 | branches: 18 | only: 19 | - master 20 | notifications: 21 | email: 22 | recipients: 23 | - mjtruog@gmail.com 24 | irc: 25 | channels: 26 | - "irc.oftc.net#cloudi" 27 | template: 28 | - "%{repository_slug} (%{branch} - %{commit}) %{author}: %{commit_message}" 29 | - "View Changes %{compare_url}" 30 | - "Build #%{build_number}: %{message} (%{build_url})" 31 | on_success: change 32 | on_failure: always 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2023 Michael Truog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Erlang External Term Format for Go 2 | ================================== 3 | 4 | [![Build Status](https://app.travis-ci.com/okeuday/erlang_go.svg?branch=master)](https://app.travis-ci.com/okeuday/erlang_go) [![Go Report Card](https://goreportcard.com/badge/github.com/okeuday/erlang_go?maxAge=3600)](https://goreportcard.com/report/github.com/okeuday/erlang_go) 5 | 6 | Provides all encoding and decoding for the Erlang External Term Format 7 | (as defined at [https://erlang.org/doc/apps/erts/erl_ext_dist.html](https://erlang.org/doc/apps/erts/erl_ext_dist.html)) 8 | in a single Go package. 9 | 10 | (For `go` (version < 1.11) command-line use you can use the prefix 11 | `GOPATH=`pwd` GOBIN=$$GOPATH/bin` to avoid additional shell setup) 12 | (For `go` (version > 1.11) command-line use you can use the prefix 13 | `GO111MODULE=auto` to avoid additional shell setup) 14 | 15 | Build 16 | ----- 17 | 18 | go build ./... 19 | 20 | Test 21 | ---- 22 | 23 | go test ./... 24 | 25 | Author 26 | ------ 27 | 28 | Michael Truog (mjtruog at protonmail dot com) 29 | 30 | License 31 | ------- 32 | 33 | MIT License 34 | -------------------------------------------------------------------------------- /v2/erlang/erlang.go: -------------------------------------------------------------------------------- 1 | package erlang 2 | 3 | //-*-Mode:Go;coding:utf-8;tab-width:4;c-basic-offset:4-*- 4 | // ex: set ft=go fenc=utf-8 sts=4 ts=4 sw=4 noet nomod: 5 | // 6 | // MIT License 7 | // 8 | // Copyright (c) 2017-2023 Michael Truog 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a 11 | // copy of this software and associated documentation files (the "Software"), 12 | // to deal in the Software without restriction, including without limitation 13 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 14 | // and/or sell copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in 18 | // all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | // DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | import ( 30 | "bytes" 31 | "compress/zlib" 32 | "encoding/binary" 33 | "io" 34 | "math" 35 | "math/big" 36 | "reflect" 37 | "strconv" 38 | ) 39 | 40 | var undefined = "undefined" // Change with SetUndefined 41 | 42 | const ( 43 | // tag values here http://www.erlang.org/doc/apps/erts/erl_ext_dist.html 44 | tagVersion = 131 45 | tagCompressedZlib = 80 46 | tagNewFloatExt = 70 47 | tagBitBinaryExt = 77 48 | tagAtomCacheRef = 78 49 | tagNewPidExt = 88 50 | tagNewPortExt = 89 51 | tagNewerReferenceExt = 90 52 | tagSmallIntegerExt = 97 53 | tagIntegerExt = 98 54 | tagFloatExt = 99 55 | tagAtomExt = 100 56 | tagReferenceExt = 101 57 | tagPortExt = 102 58 | tagPidExt = 103 59 | tagSmallTupleExt = 104 60 | tagLargeTupleExt = 105 61 | tagNilExt = 106 62 | tagStringExt = 107 63 | tagListExt = 108 64 | tagBinaryExt = 109 65 | tagSmallBigExt = 110 66 | tagLargeBigExt = 111 67 | tagNewFunExt = 112 68 | tagExportExt = 113 69 | tagNewReferenceExt = 114 70 | tagSmallAtomExt = 115 71 | tagMapExt = 116 72 | tagFunExt = 117 73 | tagAtomUtf8Ext = 118 74 | tagSmallAtomUtf8Ext = 119 75 | tagV4PortExt = 120 76 | tagLocalExt = 121 77 | ) 78 | 79 | // Erlang term structs listed alphabetically 80 | 81 | // OtpErlangAtom represents SMALL_ATOM_EXT or ATOM_EXT 82 | type OtpErlangAtom string 83 | 84 | // OtpErlangAtomCacheRef represents ATOM_CACHE_REF 85 | type OtpErlangAtomCacheRef uint8 86 | 87 | // OtpErlangAtomUTF8 represents SMALL_ATOM_UTF8_EXT or ATOM_UTF8_EXT 88 | type OtpErlangAtomUTF8 string 89 | 90 | // OtpErlangBinary represents BIT_BINARY_EXT or BINARY_EXT 91 | type OtpErlangBinary struct { 92 | Value []byte 93 | Bits uint8 94 | } 95 | 96 | // OtpErlangList represents NIL_EXT or LIST_EXT 97 | type OtpErlangList struct { 98 | Value []interface{} 99 | Improper bool 100 | } 101 | 102 | // OtpErlangMap represents MAP_EXT 103 | type OtpErlangMap map[interface{}]interface{} 104 | 105 | // OtpErlangPid represents NEW_PID_EXT or PID_EXT 106 | type OtpErlangPid struct { 107 | NodeTag uint8 108 | Node []byte 109 | ID []byte 110 | Serial []byte 111 | Creation []byte 112 | } 113 | 114 | // OtpErlangPort represents NEW_PORT_EXT or PORT_EXT 115 | type OtpErlangPort struct { 116 | NodeTag uint8 117 | Node []byte 118 | ID []byte 119 | Creation []byte 120 | } 121 | 122 | // OtpErlangReference represents 123 | // NEWER_REFERENCE_EXT, REFERENCE_EXT or NEW_REFERENCE_EXT 124 | type OtpErlangReference struct { 125 | NodeTag uint8 126 | Node []byte 127 | ID []byte 128 | Creation []byte 129 | } 130 | 131 | // OtpErlangFunction represents EXPORT_EXT, FUN_EXT or NEW_FUN_EXT 132 | type OtpErlangFunction struct { 133 | Tag uint8 134 | Value []byte 135 | } 136 | 137 | // OtpErlangTuple represents SMALL_TUPLE_EXT or LARGE_TUPLE_EXT 138 | type OtpErlangTuple []interface{} 139 | 140 | // Error structs listed alphabetically 141 | 142 | // InputError describes problems with function input parameters 143 | type InputError struct { 144 | message string 145 | } 146 | 147 | func inputErrorNew(message string) error { 148 | return &InputError{message} 149 | } 150 | func (e *InputError) Error() string { 151 | return e.message 152 | } 153 | 154 | // OutputError describes problems with creating function output data 155 | type OutputError struct { 156 | message string 157 | } 158 | 159 | func outputErrorNew(message string) error { 160 | return &OutputError{message} 161 | } 162 | func (e *OutputError) Error() string { 163 | return e.message 164 | } 165 | 166 | // ParseError provides specific parsing failure information 167 | type ParseError struct { 168 | message string 169 | } 170 | 171 | func parseErrorNew(message string) error { 172 | return &ParseError{message} 173 | } 174 | func (e *ParseError) Error() string { 175 | return e.message 176 | } 177 | 178 | // core functionality 179 | 180 | // BinaryToTerm decodes the Erlang External Term Format into Go types 181 | func BinaryToTerm(data []byte) (interface{}, error) { 182 | size := len(data) 183 | if size <= 1 { 184 | return nil, parseErrorNew("null input") 185 | } 186 | reader := bytes.NewReader(data) 187 | version, err := reader.ReadByte() 188 | if err != nil { 189 | return nil, err 190 | } 191 | if version != tagVersion { 192 | return nil, parseErrorNew("invalid version") 193 | } 194 | var i int 195 | var term interface{} 196 | i, term, err = binaryToTerms(1, reader) 197 | if err != nil { 198 | return nil, err 199 | } 200 | if i != size { 201 | return nil, parseErrorNew("unparsed data") 202 | } 203 | return term, nil 204 | } 205 | 206 | // TermToBinary encodes Go types into the Erlang External Term Format 207 | func TermToBinary(term interface{}, compressed int) ([]byte, error) { 208 | if compressed < -1 || compressed > 9 { 209 | return nil, inputErrorNew("compressed in [-1..9]") 210 | } 211 | dataUncompressed, err := termsToBinary(term, new(bytes.Buffer)) 212 | if err != nil { 213 | return nil, err 214 | } 215 | if compressed == -1 { 216 | return append([]byte{tagVersion}, dataUncompressed.Bytes()...), nil 217 | } 218 | var length = dataUncompressed.Len() 219 | var dataCompressed *bytes.Buffer = new(bytes.Buffer) 220 | dataCompressed.Grow(length) 221 | var compress *zlib.Writer 222 | compress, err = zlib.NewWriterLevel(dataCompressed, compressed) 223 | if err != nil { 224 | return nil, err 225 | } 226 | _, err = compress.Write(dataUncompressed.Bytes()) 227 | if err != nil { 228 | return nil, err 229 | } 230 | err = compress.Close() 231 | if err != nil { 232 | return nil, err 233 | } 234 | var result *bytes.Buffer = new(bytes.Buffer) 235 | _, err = result.Write([]byte{tagVersion, tagCompressedZlib}) 236 | if err != nil { 237 | return nil, err 238 | } 239 | err = binary.Write(result, binary.BigEndian, uint32(length)) 240 | if err != nil { 241 | return nil, err 242 | } 243 | _, err = result.Write(dataCompressed.Bytes()) 244 | if err != nil { 245 | return nil, err 246 | } 247 | return result.Bytes(), nil 248 | } 249 | 250 | // SetUndefined assigns the undefined atom name, Elixir use can set to "nil" 251 | func SetUndefined(value string) { 252 | undefined = value 253 | } 254 | 255 | // BinaryToTerm implementation functions 256 | 257 | func binaryToTerms(i int, reader *bytes.Reader) (int, interface{}, error) { 258 | tag, err := reader.ReadByte() 259 | if err != nil { 260 | return i, nil, err 261 | } 262 | i += 1 263 | switch tag { 264 | case tagNewFloatExt: 265 | var value float64 266 | err = binary.Read(reader, binary.BigEndian, &value) 267 | return i + 8, value, err 268 | case tagBitBinaryExt: 269 | var j uint32 270 | err = binary.Read(reader, binary.BigEndian, &j) 271 | if err != nil { 272 | return i, nil, err 273 | } 274 | i += 4 275 | var bits uint8 276 | bits, err = reader.ReadByte() 277 | if err != nil { 278 | return i, nil, err 279 | } 280 | i += 1 281 | value := make([]byte, j) 282 | if j > 0 { 283 | _, err = reader.Read(value) 284 | if err != nil { 285 | return i, nil, err 286 | } 287 | } 288 | return i + int(j), OtpErlangBinary{Value: value, Bits: bits}, nil 289 | case tagAtomCacheRef: 290 | var value uint8 291 | value, err = reader.ReadByte() 292 | if err != nil { 293 | return i, nil, err 294 | } 295 | return i + 1, OtpErlangAtomCacheRef(value), nil 296 | case tagSmallIntegerExt: 297 | var value uint8 298 | value, err = reader.ReadByte() 299 | if err != nil { 300 | return i, nil, err 301 | } 302 | return i + 1, uint8(value), nil 303 | case tagIntegerExt: 304 | var value int32 305 | err = binary.Read(reader, binary.BigEndian, &value) 306 | if err != nil { 307 | return i, nil, err 308 | } 309 | return i + 4, value, nil 310 | case tagFloatExt: 311 | valueRaw := make([]byte, 31) 312 | _, err = reader.Read(valueRaw) 313 | if err != nil { 314 | return i, nil, err 315 | } 316 | var value float64 317 | value, err = strconv.ParseFloat(string(bytes.TrimRight(valueRaw, "\x00")), 64) 318 | if err != nil { 319 | return i, nil, err 320 | } 321 | return i + 31, value, nil 322 | case tagV4PortExt: 323 | fallthrough 324 | case tagNewPortExt: 325 | fallthrough 326 | case tagReferenceExt: 327 | fallthrough 328 | case tagPortExt: 329 | var nodeTag uint8 330 | var node []byte 331 | i, nodeTag, node, err = binaryToAtom(i, reader) 332 | if err != nil { 333 | return i, nil, err 334 | } 335 | var id []byte 336 | switch tag { 337 | case tagV4PortExt: 338 | id = make([]byte, 8) 339 | _, err = reader.Read(id) 340 | if err != nil { 341 | return i, nil, err 342 | } 343 | i += 8 344 | default: 345 | id = make([]byte, 4) 346 | _, err = reader.Read(id) 347 | if err != nil { 348 | return i, nil, err 349 | } 350 | i += 4 351 | } 352 | var creation []byte 353 | switch tag { 354 | case tagV4PortExt: 355 | fallthrough 356 | case tagNewPortExt: 357 | creation = make([]byte, 4) 358 | _, err = reader.Read(creation) 359 | if err != nil { 360 | return i, nil, err 361 | } 362 | i += 4 363 | default: 364 | creation = make([]byte, 1) 365 | _, err = reader.Read(creation) 366 | if err != nil { 367 | return i, nil, err 368 | } 369 | i += 1 370 | if tag == tagReferenceExt { 371 | return i, OtpErlangReference{NodeTag: nodeTag, Node: node, ID: id, Creation: creation}, nil 372 | } 373 | } 374 | // tag == tagV4PortExt || tag == tagNewPortExt || tag == tagPortExt 375 | return i, OtpErlangPort{NodeTag: nodeTag, Node: node, ID: id, Creation: creation}, nil 376 | case tagNewPidExt: 377 | fallthrough 378 | case tagPidExt: 379 | var nodeTag uint8 380 | var node []byte 381 | i, nodeTag, node, err = binaryToAtom(i, reader) 382 | if err != nil { 383 | return i, nil, err 384 | } 385 | id := make([]byte, 4) 386 | _, err = reader.Read(id) 387 | if err != nil { 388 | return i, nil, err 389 | } 390 | i += 4 391 | serial := make([]byte, 4) 392 | _, err = reader.Read(serial) 393 | if err != nil { 394 | return i, nil, err 395 | } 396 | i += 4 397 | var creation []byte 398 | switch tag { 399 | case tagNewPidExt: 400 | creation = make([]byte, 4) 401 | _, err = reader.Read(creation) 402 | if err != nil { 403 | return i, nil, err 404 | } 405 | i += 4 406 | case tagPidExt: 407 | creation = make([]byte, 1) 408 | _, err = reader.Read(creation) 409 | if err != nil { 410 | return i, nil, err 411 | } 412 | i += 1 413 | } 414 | return i, OtpErlangPid{NodeTag: nodeTag, Node: node, ID: id, Serial: serial, Creation: creation}, nil 415 | case tagSmallTupleExt: 416 | fallthrough 417 | case tagLargeTupleExt: 418 | var length int 419 | switch tag { 420 | case tagSmallTupleExt: 421 | var lengthValue uint8 422 | lengthValue, err = reader.ReadByte() 423 | if err != nil { 424 | return i, nil, err 425 | } 426 | i += 1 427 | length = int(lengthValue) 428 | case tagLargeTupleExt: 429 | var lengthValue uint32 430 | err = binary.Read(reader, binary.BigEndian, &lengthValue) 431 | if err != nil { 432 | return i, nil, err 433 | } 434 | i += 4 435 | length = int(lengthValue) 436 | default: 437 | return i, nil, parseErrorNew("invalid tag case") 438 | } 439 | var tmp []interface{} 440 | i, tmp, err = binaryToTermSequence(i, length, reader) 441 | if err != nil { 442 | return i, nil, err 443 | } 444 | return i, OtpErlangTuple(tmp), nil 445 | case tagNilExt: 446 | value := make([]interface{}, 0) 447 | return i, OtpErlangList{Value: value, Improper: false}, nil 448 | case tagStringExt: 449 | var j uint16 450 | err = binary.Read(reader, binary.BigEndian, &j) 451 | if err != nil { 452 | return i, nil, err 453 | } 454 | i += 2 455 | value := make([]byte, j) 456 | if j > 0 { 457 | _, err = reader.Read(value) 458 | if err != nil { 459 | return i, nil, err 460 | } 461 | } 462 | return i + int(j), string(value), nil 463 | case tagListExt: 464 | var length uint32 465 | err = binary.Read(reader, binary.BigEndian, &length) 466 | if err != nil { 467 | return i, nil, err 468 | } 469 | i += 4 470 | var tmp []interface{} 471 | i, tmp, err = binaryToTermSequence(i, int(length), reader) 472 | if err != nil { 473 | return i, nil, err 474 | } 475 | var tail interface{} 476 | i, tail, err = binaryToTerms(i, reader) 477 | if err != nil { 478 | return i, nil, err 479 | } 480 | var improper bool 481 | switch tail.(type) { 482 | case OtpErlangList: 483 | improper = (len(tail.(OtpErlangList).Value) != 0) 484 | default: 485 | improper = true 486 | } 487 | if improper { 488 | tmp = append(tmp, tail) 489 | } 490 | return i, OtpErlangList{Value: tmp, Improper: improper}, nil 491 | case tagBinaryExt: 492 | var j uint32 493 | err = binary.Read(reader, binary.BigEndian, &j) 494 | if err != nil { 495 | return i, nil, err 496 | } 497 | i += 4 498 | value := make([]byte, j) 499 | if j > 0 { 500 | _, err = reader.Read(value) 501 | if err != nil { 502 | return i, nil, err 503 | } 504 | } 505 | return i + int(j), OtpErlangBinary{Value: value, Bits: 8}, nil 506 | case tagSmallBigExt: 507 | fallthrough 508 | case tagLargeBigExt: 509 | var j int 510 | switch tag { 511 | case tagSmallBigExt: 512 | var jValue uint8 513 | jValue, err = reader.ReadByte() 514 | if err != nil { 515 | return i, nil, err 516 | } 517 | i += 1 518 | j = int(jValue) 519 | case tagLargeBigExt: 520 | var jValue uint32 521 | err = binary.Read(reader, binary.BigEndian, &jValue) 522 | if err != nil { 523 | return i, nil, err 524 | } 525 | i += 4 526 | j = int(jValue) 527 | default: 528 | return i, nil, parseErrorNew("invalid tag case") 529 | } 530 | var sign uint8 531 | sign, err = reader.ReadByte() 532 | if err != nil { 533 | return i, nil, err 534 | } 535 | bignum := big.NewInt(0) 536 | digit := make([]byte, 1) 537 | for bignumIndex := 0; bignumIndex < j; bignumIndex++ { 538 | _, err = reader.ReadAt(digit, int64(i+j-bignumIndex)) 539 | if err != nil { 540 | return i, nil, err 541 | } 542 | bignum.Lsh(bignum, 8) 543 | bignum.Add(bignum, big.NewInt(int64(digit[0]))) 544 | } 545 | if sign == 1 { 546 | bignum.Neg(bignum) 547 | } 548 | i += 1 549 | _, err = reader.Seek(int64(j), 1) 550 | if err != nil { 551 | return i, nil, err 552 | } 553 | return i + j, bignum, nil 554 | case tagNewFunExt: 555 | var length uint32 556 | err = binary.Read(reader, binary.BigEndian, &length) 557 | if err != nil { 558 | return i, nil, err 559 | } 560 | i += 4 561 | value := make([]byte, length) 562 | if length > 0 { 563 | _, err = reader.Read(value) 564 | if err != nil { 565 | return i, nil, err 566 | } 567 | } 568 | return i + int(length), OtpErlangFunction{Tag: tag, Value: value}, nil 569 | case tagExportExt: 570 | iOld := i 571 | i, _, _, err = binaryToAtom(i, reader) // module 572 | if err != nil { 573 | return i, nil, err 574 | } 575 | i, _, _, err = binaryToAtom(i, reader) // function 576 | if err != nil { 577 | return i, nil, err 578 | } 579 | var arityTag uint8 580 | arityTag, err = reader.ReadByte() 581 | if err != nil { 582 | return i, nil, err 583 | } 584 | if arityTag != tagSmallIntegerExt { 585 | return i, nil, parseErrorNew("invalid small integer tag") 586 | } 587 | i += 1 588 | _, err = reader.ReadByte() // arity 589 | if err != nil { 590 | return i, nil, err 591 | } 592 | i += 1 593 | value := make([]byte, i-iOld) 594 | _, err = reader.ReadAt(value, int64(iOld)) 595 | if err != nil { 596 | return i, nil, err 597 | } 598 | return i, OtpErlangFunction{Tag: tag, Value: value}, nil 599 | case tagNewerReferenceExt: 600 | fallthrough 601 | case tagNewReferenceExt: 602 | var j uint16 603 | err = binary.Read(reader, binary.BigEndian, &j) 604 | if err != nil { 605 | return i, nil, err 606 | } 607 | j *= 4 608 | i += 2 609 | var nodeTag uint8 610 | var node []byte 611 | i, nodeTag, node, err = binaryToAtom(i, reader) 612 | if err != nil { 613 | return i, nil, err 614 | } 615 | var creation []byte 616 | switch tag { 617 | case tagNewerReferenceExt: 618 | creation = make([]byte, 4) 619 | _, err = reader.Read(creation) 620 | if err != nil { 621 | return i, nil, err 622 | } 623 | i += 4 624 | case tagNewReferenceExt: 625 | creation = make([]byte, 1) 626 | _, err = reader.Read(creation) 627 | if err != nil { 628 | return i, nil, err 629 | } 630 | i += 1 631 | } 632 | id := make([]byte, j) 633 | if j > 0 { 634 | _, err = reader.Read(id) 635 | if err != nil { 636 | return i, nil, err 637 | } 638 | } 639 | return i + int(j), OtpErlangReference{NodeTag: nodeTag, Node: node, ID: id, Creation: creation}, nil 640 | case tagMapExt: 641 | var length uint32 642 | err = binary.Read(reader, binary.BigEndian, &length) 643 | if err != nil { 644 | return i, nil, err 645 | } 646 | i += 4 647 | pairs := make(map[interface{}]interface{}) 648 | for lengthIndex := 0; lengthIndex < int(length); lengthIndex++ { 649 | var key interface{} 650 | i, key, err = binaryToTerms(i, reader) 651 | if err != nil { 652 | return i, nil, err 653 | } 654 | if !reflect.TypeOf(key).Comparable() { 655 | // no way to solve this properly in Go while preserving 656 | // the Erlang type information 657 | return i, nil, parseErrorNew("map key not comparable") 658 | } 659 | var value interface{} 660 | i, value, err = binaryToTerms(i, reader) 661 | if err != nil { 662 | return i, nil, err 663 | } 664 | pairs[key] = value 665 | } 666 | return i, OtpErlangMap(pairs), nil 667 | case tagFunExt: 668 | iOld := i 669 | var numfree uint32 670 | err = binary.Read(reader, binary.BigEndian, &numfree) 671 | if err != nil { 672 | return i, nil, err 673 | } 674 | i += 4 675 | i, _, err = binaryToPid(i, reader) // pid 676 | if err != nil { 677 | return i, nil, err 678 | } 679 | i, _, _, err = binaryToAtom(i, reader) // module 680 | if err != nil { 681 | return i, nil, err 682 | } 683 | i, _, err = binaryToInteger(i, reader) // index 684 | if err != nil { 685 | return i, nil, err 686 | } 687 | i, _, err = binaryToInteger(i, reader) // uniq 688 | if err != nil { 689 | return i, nil, err 690 | } 691 | i, _, err = binaryToTermSequence(i, int(numfree), reader) // free 692 | if err != nil { 693 | return i, nil, err 694 | } 695 | value := make([]byte, i-iOld) 696 | _, err = reader.ReadAt(value, int64(iOld)) 697 | if err != nil { 698 | return i, nil, err 699 | } 700 | return i, OtpErlangFunction{Tag: tag, Value: value}, nil 701 | case tagAtomUtf8Ext: 702 | fallthrough 703 | case tagAtomExt: 704 | var j uint16 705 | err = binary.Read(reader, binary.BigEndian, &j) 706 | if err != nil { 707 | return i, nil, err 708 | } 709 | i += 2 710 | value := make([]byte, j) 711 | if j > 0 { 712 | _, err = reader.Read(value) 713 | if err != nil { 714 | return i, nil, err 715 | } 716 | } 717 | if string(value) == "true" { 718 | return i + int(j), true, nil 719 | } 720 | if string(value) == "false" { 721 | return i + int(j), false, nil 722 | } 723 | if string(value) == undefined { 724 | return i + int(j), nil, nil 725 | } 726 | switch tag { 727 | case tagAtomUtf8Ext: 728 | return i + int(j), OtpErlangAtomUTF8(value), nil 729 | case tagAtomExt: 730 | return i + int(j), OtpErlangAtom(value), nil 731 | default: 732 | return i, nil, parseErrorNew("invalid tag case") 733 | } 734 | case tagSmallAtomUtf8Ext: 735 | fallthrough 736 | case tagSmallAtomExt: 737 | var j uint8 738 | j, err = reader.ReadByte() 739 | if err != nil { 740 | return i, nil, err 741 | } 742 | i += 1 743 | value := make([]byte, j) 744 | if j > 0 { 745 | _, err = reader.Read(value) 746 | if err != nil { 747 | return i, nil, err 748 | } 749 | } 750 | if string(value) == "true" { 751 | return i + int(j), true, nil 752 | } 753 | if string(value) == "false" { 754 | return i + int(j), false, nil 755 | } 756 | if string(value) == undefined { 757 | return i + int(j), nil, nil 758 | } 759 | switch tag { 760 | case tagSmallAtomUtf8Ext: 761 | return i + int(j), OtpErlangAtomUTF8(value), nil 762 | case tagSmallAtomExt: 763 | return i + int(j), OtpErlangAtom(value), nil 764 | default: 765 | return i, nil, parseErrorNew("invalid tag case") 766 | } 767 | case tagCompressedZlib: 768 | var sizeUncompressed uint32 769 | err = binary.Read(reader, binary.BigEndian, &sizeUncompressed) 770 | if err != nil { 771 | return i, nil, err 772 | } 773 | i += 4 774 | if sizeUncompressed == 0 { 775 | return i, nil, parseErrorNew("compressed data null") 776 | } 777 | j := reader.Len() 778 | var compress io.ReadCloser 779 | compress, err = zlib.NewReader(reader) 780 | if err != nil { 781 | return i, nil, err 782 | } 783 | dataUncompressed := make([]byte, sizeUncompressed) 784 | var sizeUncompressedStored int 785 | sizeUncompressedStored, _ = io.ReadFull(compress, dataUncompressed) 786 | err = compress.Close() 787 | if err != nil { 788 | return i, nil, err 789 | } 790 | if int(sizeUncompressed) != sizeUncompressedStored { 791 | return i, nil, parseErrorNew("compression corrupt") 792 | } 793 | var iNew int 794 | var term interface{} 795 | iNew, term, err = binaryToTerms(0, bytes.NewReader(dataUncompressed)) 796 | if err != nil { 797 | return i, nil, err 798 | } 799 | if iNew != int(sizeUncompressed) { 800 | return i, nil, parseErrorNew("unparsed data") 801 | } 802 | return i + j, term, nil 803 | case tagLocalExt: 804 | return i, nil, parseErrorNew("LOCAL_EXT is opaque") 805 | default: 806 | return i, nil, parseErrorNew("invalid tag") 807 | } 808 | } 809 | 810 | func binaryToTermSequence(i, length int, reader *bytes.Reader) (int, []interface{}, error) { 811 | sequence := make([]interface{}, length) 812 | var err error 813 | for lengthIndex := 0; lengthIndex < length; lengthIndex++ { 814 | var element interface{} 815 | i, element, err = binaryToTerms(i, reader) 816 | if err != nil { 817 | return i, nil, err 818 | } 819 | sequence[lengthIndex] = element 820 | } 821 | return i, sequence, nil 822 | } 823 | 824 | // (BinaryToTerm Erlang term primitive type functions) 825 | 826 | func binaryToInteger(i int, reader *bytes.Reader) (int, interface{}, error) { 827 | tag, err := reader.ReadByte() 828 | if err != nil { 829 | return i, nil, err 830 | } 831 | i += 1 832 | switch tag { 833 | case tagSmallIntegerExt: 834 | var value uint8 835 | value, err = reader.ReadByte() 836 | if err != nil { 837 | return i, nil, err 838 | } 839 | return i + 1, uint8(value), nil 840 | case tagIntegerExt: 841 | var value int32 842 | err = binary.Read(reader, binary.BigEndian, &value) 843 | if err != nil { 844 | return i, nil, err 845 | } 846 | return i + 4, value, nil 847 | default: 848 | return i, nil, parseErrorNew("invalid integer tag") 849 | } 850 | } 851 | 852 | func binaryToPid(i int, reader *bytes.Reader) (int, interface{}, error) { 853 | tag, err := reader.ReadByte() 854 | if err != nil { 855 | return i, nil, err 856 | } 857 | i += 1 858 | switch tag { 859 | case tagNewPidExt: 860 | var nodeTag uint8 861 | var node []byte 862 | i, nodeTag, node, err = binaryToAtom(i, reader) 863 | if err != nil { 864 | return i, nil, err 865 | } 866 | id := make([]byte, 4) 867 | _, err = reader.Read(id) 868 | if err != nil { 869 | return i, nil, err 870 | } 871 | i += 4 872 | serial := make([]byte, 4) 873 | _, err = reader.Read(serial) 874 | if err != nil { 875 | return i, nil, err 876 | } 877 | i += 4 878 | creation := make([]byte, 4) 879 | _, err = reader.Read(creation) 880 | if err != nil { 881 | return i, nil, err 882 | } 883 | i += 4 884 | return i, OtpErlangPid{NodeTag: nodeTag, Node: node, ID: id, Serial: serial, Creation: creation}, nil 885 | case tagPidExt: 886 | var nodeTag uint8 887 | var node []byte 888 | i, nodeTag, node, err = binaryToAtom(i, reader) 889 | if err != nil { 890 | return i, nil, err 891 | } 892 | id := make([]byte, 4) 893 | _, err = reader.Read(id) 894 | if err != nil { 895 | return i, nil, err 896 | } 897 | i += 4 898 | serial := make([]byte, 4) 899 | _, err = reader.Read(serial) 900 | if err != nil { 901 | return i, nil, err 902 | } 903 | i += 4 904 | creation := make([]byte, 1) 905 | _, err = reader.Read(creation) 906 | if err != nil { 907 | return i, nil, err 908 | } 909 | i += 1 910 | return i, OtpErlangPid{NodeTag: nodeTag, Node: node, ID: id, Serial: serial, Creation: creation}, nil 911 | default: 912 | return i, nil, parseErrorNew("invalid pid tag") 913 | } 914 | } 915 | 916 | func binaryToAtom(i int, reader *bytes.Reader) (int, uint8, []byte, error) { 917 | tag, err := reader.ReadByte() 918 | if err != nil { 919 | return i, 0, nil, err 920 | } 921 | i += 1 922 | switch tag { 923 | case tagAtomUtf8Ext: 924 | fallthrough 925 | case tagAtomExt: 926 | var j uint16 927 | err = binary.Read(reader, binary.BigEndian, &j) 928 | if err != nil { 929 | return i, tag, nil, err 930 | } 931 | value := make([]byte, 2+j) 932 | _, err = reader.ReadAt(value, int64(i)) 933 | if err != nil { 934 | return i, tag, nil, err 935 | } 936 | i += 2 937 | _, err = reader.Seek(int64(j), 1) 938 | if err != nil { 939 | return i, tag, nil, err 940 | } 941 | return i + int(j), tag, value, nil 942 | case tagSmallAtomUtf8Ext: 943 | fallthrough 944 | case tagSmallAtomExt: 945 | var j uint8 946 | j, err = reader.ReadByte() 947 | if err != nil { 948 | return i, tag, nil, err 949 | } 950 | value := make([]byte, 1+j) 951 | _, err = reader.ReadAt(value, int64(i)) 952 | if err != nil { 953 | return i, tag, nil, err 954 | } 955 | i += 1 956 | _, err = reader.Seek(int64(j), 1) 957 | if err != nil { 958 | return i, tag, nil, err 959 | } 960 | return i + int(j), tag, value, nil 961 | case tagAtomCacheRef: 962 | var value uint8 963 | value, err = reader.ReadByte() 964 | if err != nil { 965 | return i, tag, nil, err 966 | } 967 | return i + 1, tag, []byte{value}, nil 968 | default: 969 | return i, tag, nil, parseErrorNew("invalid atom tag") 970 | } 971 | } 972 | 973 | // TermToBinary implementation functions 974 | 975 | func termsToBinary(termI interface{}, buffer *bytes.Buffer) (*bytes.Buffer, error) { 976 | switch term := termI.(type) { 977 | case uint8: 978 | _, err := buffer.Write([]byte{tagSmallIntegerExt, term}) 979 | return buffer, err 980 | case uint16: 981 | return integerToBinary(int32(term), buffer) 982 | case uint32: 983 | return bignumToBinary(big.NewInt(int64(term)), buffer) 984 | case uint64: 985 | var value *big.Int = new(big.Int) 986 | value.SetUint64(term) 987 | return bignumToBinary(value, buffer) 988 | case int8: 989 | return integerToBinary(int32(term), buffer) 990 | case int16: 991 | return integerToBinary(int32(term), buffer) 992 | case int32: 993 | return integerToBinary(term, buffer) 994 | case int64: 995 | return bignumToBinary(big.NewInt(term), buffer) 996 | case int: 997 | switch { 998 | case term >= 0 && term <= math.MaxUint8: 999 | return termsToBinary(uint8(term), buffer) 1000 | case term >= math.MinInt32 && term <= math.MaxInt32: 1001 | return integerToBinary(int32(term), buffer) 1002 | default: 1003 | return termsToBinary(int64(term), buffer) 1004 | } 1005 | case *big.Int: 1006 | return bignumToBinary(term, buffer) 1007 | case float32: 1008 | return floatToBinary(float64(term), buffer) 1009 | case float64: 1010 | return floatToBinary(term, buffer) 1011 | case bool: 1012 | if term { 1013 | return atomUtf8ToBinary("true", buffer) 1014 | } 1015 | return atomUtf8ToBinary("false", buffer) 1016 | case nil: 1017 | return atomUtf8ToBinary(undefined, buffer) 1018 | case OtpErlangAtom: 1019 | return atomToBinary(string(term), buffer) 1020 | case OtpErlangAtomUTF8: 1021 | return atomUtf8ToBinary(string(term), buffer) 1022 | case OtpErlangAtomCacheRef: 1023 | _, err := buffer.Write([]byte{tagAtomCacheRef, uint8(term)}) 1024 | return buffer, err 1025 | case []byte: 1026 | return binaryObjectToBinary(OtpErlangBinary{Value: term, Bits: 8}, buffer) 1027 | case OtpErlangBinary: 1028 | return binaryObjectToBinary(term, buffer) 1029 | case OtpErlangFunction: 1030 | return functionToBinary(term, buffer) 1031 | case OtpErlangPid: 1032 | return pidToBinary(term, buffer) 1033 | case OtpErlangPort: 1034 | return portToBinary(term, buffer) 1035 | case OtpErlangReference: 1036 | return referenceToBinary(term, buffer) 1037 | case string: 1038 | return stringToBinary(term, buffer) 1039 | case OtpErlangTuple: 1040 | return tupleToBinary(term, buffer) 1041 | case []interface{}: 1042 | return tupleToBinary(term, buffer) 1043 | case OtpErlangMap: 1044 | return mapToBinary(term, buffer) 1045 | case map[interface{}]interface{}: 1046 | return mapToBinary(term, buffer) 1047 | case OtpErlangList: 1048 | return listToBinary(term, buffer) 1049 | default: 1050 | return buffer, outputErrorNew("unknown go type") 1051 | } 1052 | } 1053 | 1054 | // (TermToBinary Erlang term composite type functions) 1055 | 1056 | func stringToBinary(term string, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1057 | switch length := len(term); { 1058 | case length == 0: 1059 | err := buffer.WriteByte(tagNilExt) 1060 | return buffer, err 1061 | case length <= math.MaxUint16: 1062 | err := buffer.WriteByte(tagStringExt) 1063 | if err != nil { 1064 | return buffer, err 1065 | } 1066 | err = binary.Write(buffer, binary.BigEndian, uint16(length)) 1067 | if err != nil { 1068 | return buffer, err 1069 | } 1070 | _, err = buffer.WriteString(term) 1071 | return buffer, err 1072 | case uint64(length) <= math.MaxUint32: 1073 | err := buffer.WriteByte(tagListExt) 1074 | if err != nil { 1075 | return buffer, err 1076 | } 1077 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1078 | if err != nil { 1079 | return buffer, err 1080 | } 1081 | for i := 0; i < length; i++ { 1082 | _, err = buffer.Write([]byte{tagSmallIntegerExt, term[i]}) 1083 | if err != nil { 1084 | return buffer, err 1085 | } 1086 | } 1087 | err = buffer.WriteByte(tagNilExt) 1088 | return buffer, err 1089 | default: 1090 | return buffer, outputErrorNew("uint32 overflow") 1091 | } 1092 | } 1093 | 1094 | func tupleToBinary(term []interface{}, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1095 | var length int 1096 | var err error 1097 | switch length = len(term); { 1098 | case length <= math.MaxUint8: 1099 | _, err = buffer.Write([]byte{tagSmallTupleExt, byte(length)}) 1100 | if err != nil { 1101 | return buffer, err 1102 | } 1103 | case uint64(length) <= math.MaxUint32: 1104 | err = buffer.WriteByte(tagLargeTupleExt) 1105 | if err != nil { 1106 | return buffer, err 1107 | } 1108 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1109 | if err != nil { 1110 | return buffer, err 1111 | } 1112 | default: 1113 | return buffer, outputErrorNew("uint32 overflow") 1114 | } 1115 | for i := 0; i < length; i++ { 1116 | buffer, err = termsToBinary(term[i], buffer) 1117 | if err != nil { 1118 | return buffer, err 1119 | } 1120 | } 1121 | return buffer, nil 1122 | } 1123 | 1124 | func mapToBinary(term map[interface{}]interface{}, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1125 | var length int 1126 | var err error 1127 | switch length = len(term); { 1128 | case uint64(length) <= math.MaxUint32: 1129 | err = buffer.WriteByte(tagMapExt) 1130 | if err != nil { 1131 | return buffer, err 1132 | } 1133 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1134 | if err != nil { 1135 | return buffer, err 1136 | } 1137 | default: 1138 | return buffer, outputErrorNew("uint32 overflow") 1139 | } 1140 | for key, value := range term { 1141 | buffer, err = termsToBinary(key, buffer) 1142 | if err != nil { 1143 | return buffer, err 1144 | } 1145 | buffer, err = termsToBinary(value, buffer) 1146 | if err != nil { 1147 | return buffer, err 1148 | } 1149 | } 1150 | return buffer, nil 1151 | } 1152 | 1153 | func listToBinary(term OtpErlangList, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1154 | var length int 1155 | var err error 1156 | switch length = len(term.Value); { 1157 | case length == 0: 1158 | err = buffer.WriteByte(tagNilExt) 1159 | return buffer, err 1160 | case uint64(length) <= math.MaxUint32: 1161 | err = buffer.WriteByte(tagListExt) 1162 | if err != nil { 1163 | return buffer, err 1164 | } 1165 | if term.Improper { 1166 | err = binary.Write(buffer, binary.BigEndian, uint32(length-1)) 1167 | if err != nil { 1168 | return buffer, err 1169 | } 1170 | } else { 1171 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1172 | if err != nil { 1173 | return buffer, err 1174 | } 1175 | } 1176 | default: 1177 | return buffer, outputErrorNew("uint32 overflow") 1178 | } 1179 | for i := 0; i < length; i++ { 1180 | buffer, err = termsToBinary(term.Value[i], buffer) 1181 | if err != nil { 1182 | return buffer, err 1183 | } 1184 | } 1185 | if !term.Improper { 1186 | err = buffer.WriteByte(tagNilExt) 1187 | } 1188 | return buffer, err 1189 | } 1190 | 1191 | // (TermToBinary Erlang term primitive type functions) 1192 | 1193 | func integerToBinary(term int32, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1194 | err := buffer.WriteByte(tagIntegerExt) 1195 | if err != nil { 1196 | return buffer, err 1197 | } 1198 | err = binary.Write(buffer, binary.BigEndian, term) 1199 | return buffer, err 1200 | } 1201 | 1202 | func bignumToBinary(term *big.Int, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1203 | var sign uint8 1204 | if term.Sign() < 0 { 1205 | sign = 1 1206 | } else { 1207 | sign = 0 1208 | } 1209 | value := term.Bytes() 1210 | var length int 1211 | var err error 1212 | switch length = len(value); { 1213 | case length <= math.MaxUint8: 1214 | _, err = buffer.Write([]byte{tagSmallBigExt, uint8(length)}) 1215 | if err != nil { 1216 | return buffer, err 1217 | } 1218 | case uint64(length) <= math.MaxUint32: 1219 | err = buffer.WriteByte(tagLargeBigExt) 1220 | if err != nil { 1221 | return buffer, err 1222 | } 1223 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1224 | if err != nil { 1225 | return buffer, err 1226 | } 1227 | default: 1228 | return buffer, outputErrorNew("uint32 overflow") 1229 | } 1230 | err = buffer.WriteByte(sign) 1231 | if err != nil { 1232 | return buffer, err 1233 | } 1234 | // little-endian is required 1235 | half := length >> 1 1236 | iLast := length - 1 1237 | for i := 0; i < half; i++ { 1238 | j := iLast - i 1239 | value[i], value[j] = value[j], value[i] 1240 | } 1241 | _, err = buffer.Write(value) 1242 | return buffer, err 1243 | } 1244 | 1245 | func floatToBinary(term float64, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1246 | err := buffer.WriteByte(tagNewFloatExt) 1247 | if err != nil { 1248 | return buffer, err 1249 | } 1250 | err = binary.Write(buffer, binary.BigEndian, term) 1251 | return buffer, err 1252 | } 1253 | 1254 | func atomToBinary(term string, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1255 | // deprecated 1256 | // (not used in Erlang/OTP 26, i.e., minor_version 2) 1257 | switch length := len(term); { 1258 | case length <= math.MaxUint8: 1259 | _, err := buffer.Write([]byte{tagSmallAtomExt, uint8(length)}) 1260 | if err != nil { 1261 | return buffer, err 1262 | } 1263 | _, err = buffer.WriteString(term) 1264 | return buffer, err 1265 | case length <= math.MaxUint16: 1266 | err := buffer.WriteByte(tagAtomExt) 1267 | if err != nil { 1268 | return buffer, err 1269 | } 1270 | err = binary.Write(buffer, binary.BigEndian, uint16(length)) 1271 | if err != nil { 1272 | return buffer, err 1273 | } 1274 | _, err = buffer.WriteString(term) 1275 | return buffer, err 1276 | default: 1277 | return buffer, outputErrorNew("uint16 overflow") 1278 | } 1279 | } 1280 | 1281 | func atomUtf8ToBinary(term string, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1282 | switch length := len(term); { 1283 | case length <= math.MaxUint8: 1284 | _, err := buffer.Write([]byte{tagSmallAtomUtf8Ext, uint8(length)}) 1285 | if err != nil { 1286 | return buffer, err 1287 | } 1288 | _, err = buffer.WriteString(term) 1289 | return buffer, err 1290 | case length <= math.MaxUint16: 1291 | err := buffer.WriteByte(tagAtomUtf8Ext) 1292 | if err != nil { 1293 | return buffer, err 1294 | } 1295 | err = binary.Write(buffer, binary.BigEndian, uint16(length)) 1296 | if err != nil { 1297 | return buffer, err 1298 | } 1299 | _, err = buffer.WriteString(term) 1300 | return buffer, err 1301 | default: 1302 | return buffer, outputErrorNew("uint16 overflow") 1303 | } 1304 | } 1305 | 1306 | func binaryObjectToBinary(term OtpErlangBinary, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1307 | var err error 1308 | switch length := len(term.Value); { 1309 | case term.Bits < 1 || term.Bits > 8: 1310 | return buffer, outputErrorNew("invalid OtpErlangBinary.Bits") 1311 | case uint64(length) <= math.MaxUint32: 1312 | if term.Bits != 8 { 1313 | err = buffer.WriteByte(tagBitBinaryExt) 1314 | if err != nil { 1315 | return buffer, err 1316 | } 1317 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1318 | if err != nil { 1319 | return buffer, err 1320 | } 1321 | err = buffer.WriteByte(term.Bits) 1322 | if err != nil { 1323 | return buffer, err 1324 | } 1325 | } else { 1326 | err = buffer.WriteByte(tagBinaryExt) 1327 | if err != nil { 1328 | return buffer, err 1329 | } 1330 | err = binary.Write(buffer, binary.BigEndian, uint32(length)) 1331 | if err != nil { 1332 | return buffer, err 1333 | } 1334 | } 1335 | default: 1336 | return buffer, outputErrorNew("uint32 overflow") 1337 | } 1338 | _, err = buffer.Write(term.Value) 1339 | return buffer, err 1340 | } 1341 | 1342 | func pidToBinary(term OtpErlangPid, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1343 | var err error 1344 | switch creationSize := len(term.Creation); { 1345 | case creationSize == 1: 1346 | err = buffer.WriteByte(tagPidExt) 1347 | if err != nil { 1348 | return buffer, err 1349 | } 1350 | case creationSize == 4: 1351 | err = buffer.WriteByte(tagNewPidExt) 1352 | if err != nil { 1353 | return buffer, err 1354 | } 1355 | default: 1356 | return buffer, outputErrorNew("unknown pid type") 1357 | } 1358 | err = buffer.WriteByte(term.NodeTag) 1359 | if err != nil { 1360 | return buffer, err 1361 | } 1362 | _, err = buffer.Write(term.Node) 1363 | if err != nil { 1364 | return buffer, err 1365 | } 1366 | _, err = buffer.Write(term.ID) 1367 | if err != nil { 1368 | return buffer, err 1369 | } 1370 | _, err = buffer.Write(term.Serial) 1371 | if err != nil { 1372 | return buffer, err 1373 | } 1374 | _, err = buffer.Write(term.Creation) 1375 | return buffer, err 1376 | } 1377 | 1378 | func portToBinary(term OtpErlangPort, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1379 | var err error 1380 | switch len(term.ID) { 1381 | case 8: 1382 | err = buffer.WriteByte(tagV4PortExt) 1383 | if err != nil { 1384 | return buffer, err 1385 | } 1386 | case 4: 1387 | switch len(term.Creation) { 1388 | case 4: 1389 | err = buffer.WriteByte(tagNewPortExt) 1390 | if err != nil { 1391 | return buffer, err 1392 | } 1393 | case 1: 1394 | err = buffer.WriteByte(tagPortExt) 1395 | if err != nil { 1396 | return buffer, err 1397 | } 1398 | default: 1399 | return buffer, outputErrorNew("unknown port type") 1400 | } 1401 | default: 1402 | return buffer, outputErrorNew("unknown port type") 1403 | } 1404 | err = buffer.WriteByte(term.NodeTag) 1405 | if err != nil { 1406 | return buffer, err 1407 | } 1408 | _, err = buffer.Write(term.Node) 1409 | if err != nil { 1410 | return buffer, err 1411 | } 1412 | _, err = buffer.Write(term.ID) 1413 | if err != nil { 1414 | return buffer, err 1415 | } 1416 | _, err = buffer.Write(term.Creation) 1417 | return buffer, err 1418 | } 1419 | 1420 | func referenceToBinary(term OtpErlangReference, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1421 | switch length := len(term.ID) / 4; { 1422 | case length == 0: 1423 | err := buffer.WriteByte(tagReferenceExt) 1424 | if err != nil { 1425 | return buffer, err 1426 | } 1427 | err = buffer.WriteByte(term.NodeTag) 1428 | if err != nil { 1429 | return buffer, err 1430 | } 1431 | _, err = buffer.Write(term.Node) 1432 | if err != nil { 1433 | return buffer, err 1434 | } 1435 | _, err = buffer.Write(term.ID) 1436 | if err != nil { 1437 | return buffer, err 1438 | } 1439 | _, err = buffer.Write(term.Creation) 1440 | return buffer, err 1441 | case length <= math.MaxUint16: 1442 | var err error 1443 | switch creationSize := len(term.Creation); { 1444 | case creationSize == 1: 1445 | err = buffer.WriteByte(tagNewReferenceExt) 1446 | if err != nil { 1447 | return buffer, err 1448 | } 1449 | case creationSize == 4: 1450 | err = buffer.WriteByte(tagNewerReferenceExt) 1451 | if err != nil { 1452 | return buffer, err 1453 | } 1454 | default: 1455 | return buffer, outputErrorNew("unknown reference type") 1456 | } 1457 | err = binary.Write(buffer, binary.BigEndian, uint16(length)) 1458 | if err != nil { 1459 | return buffer, err 1460 | } 1461 | err = buffer.WriteByte(term.NodeTag) 1462 | if err != nil { 1463 | return buffer, err 1464 | } 1465 | _, err = buffer.Write(term.Node) 1466 | if err != nil { 1467 | return buffer, err 1468 | } 1469 | _, err = buffer.Write(term.Creation) 1470 | if err != nil { 1471 | return buffer, err 1472 | } 1473 | _, err = buffer.Write(term.ID) 1474 | return buffer, err 1475 | default: 1476 | return buffer, outputErrorNew("uint16 overflow") 1477 | } 1478 | } 1479 | 1480 | func functionToBinary(term OtpErlangFunction, buffer *bytes.Buffer) (*bytes.Buffer, error) { 1481 | err := buffer.WriteByte(term.Tag) 1482 | if err != nil { 1483 | return buffer, err 1484 | } 1485 | _, err = buffer.Write(term.Value) 1486 | return buffer, err 1487 | } 1488 | -------------------------------------------------------------------------------- /v2/erlang/erlang_test.go: -------------------------------------------------------------------------------- 1 | package erlang 2 | 3 | //-*-Mode:Go;coding:utf-8;tab-width:4;c-basic-offset:4-*- 4 | // ex: set ft=go fenc=utf-8 sts=4 ts=4 sw=4 noet nomod: 5 | // 6 | // MIT License 7 | // 8 | // Copyright (c) 2017-2023 Michael Truog 9 | // Copyright (c) 2009-2013 Dmitry Vasiliev 10 | // 11 | // Permission is hereby granted, free of charge, to any person obtaining a 12 | // copy of this software and associated documentation files (the "Software"), 13 | // to deal in the Software without restriction, including without limitation 14 | // the rights to use, copy, modify, merge, publish, distribute, sublicense, 15 | // and/or sell copies of the Software, and to permit persons to whom the 16 | // Software is furnished to do so, subject to the following conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be included in 19 | // all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 27 | // DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | import ( 31 | "fmt" 32 | "log" 33 | "math/big" 34 | "reflect" 35 | "strings" 36 | "testing" 37 | ) 38 | 39 | func assertEqual(t *testing.T, expect interface{}, result interface{}, message string) { 40 | // Go doesn't believe in assertions (https://golang.org/doc/faq#assertions) 41 | // (Go isn't pursuing fail-fast development or fault-tolerance) 42 | if reflect.DeepEqual(expect, result) { 43 | return 44 | } 45 | if len(message) == 0 { 46 | message = fmt.Sprintf("%#v != %#v", expect, result) 47 | } 48 | t.Fail() 49 | log.SetPrefix("\t") 50 | log.SetFlags(log.Lshortfile) 51 | log.Output(2, message) 52 | } 53 | 54 | func assertDecodeError(t *testing.T, expectedError, b string, message string) { 55 | _, err := BinaryToTerm([]byte(b)) 56 | if err == nil { 57 | log.SetPrefix("\t") 58 | log.SetFlags(log.Lshortfile) 59 | log.Output(2, fmt.Sprintf("no error to compare with \"%s\"", expectedError)) 60 | t.FailNow() 61 | return 62 | } 63 | resultError := err.Error() 64 | if expectedError == resultError { 65 | return 66 | } 67 | if len(message) == 0 { 68 | message = fmt.Sprintf("\"%s\" != \"%s\"", expectedError, resultError) 69 | } 70 | t.Fail() 71 | log.SetPrefix("\t") 72 | log.SetFlags(log.Lshortfile) 73 | log.Output(2, message) 74 | } 75 | 76 | func decode(t *testing.T, b string) interface{} { 77 | term, err := BinaryToTerm([]byte(b)) 78 | if err != nil { 79 | log.SetPrefix("\t") 80 | log.SetFlags(log.Lshortfile) 81 | log.Output(2, err.Error()) 82 | t.FailNow() 83 | return nil 84 | } 85 | return term 86 | } 87 | 88 | func encode(t *testing.T, term interface{}, compressed int) string { 89 | b, err := TermToBinary(term, compressed) 90 | if err != nil { 91 | log.SetPrefix("\t") 92 | log.SetFlags(log.Lshortfile) 93 | log.Output(2, err.Error()) 94 | t.FailNow() 95 | return "" 96 | } 97 | return string(b) 98 | } 99 | 100 | func TestAtom(t *testing.T) { 101 | atom1 := OtpErlangAtomUTF8("test") 102 | assertEqual(t, OtpErlangAtomUTF8("test"), atom1, "") 103 | atom2 := OtpErlangAtom("test") 104 | assertEqual(t, OtpErlangAtom("test"), atom2, "") 105 | assertEqual(t, strings.Repeat("X", 255), string(OtpErlangAtomUTF8(strings.Repeat("X", 255))), "") 106 | assertEqual(t, strings.Repeat("X", 256), string(OtpErlangAtomUTF8(strings.Repeat("X", 256))), "") 107 | } 108 | 109 | func TestPid(t *testing.T) { 110 | pid1 := OtpErlangPid{NodeTag: 100, Node: []uint8{0x0, 0xd, 0x6e, 0x6f, 0x6e, 0x6f, 0x64, 0x65, 0x40, 0x6e, 0x6f, 0x68, 0x6f, 0x73, 0x74}, ID: []uint8{0x0, 0x0, 0x0, 0x3b}, Serial: []uint8{0x0, 0x0, 0x0, 0x0}, Creation: []uint8{0x0}} 111 | binary := "\x83\x67\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x3B\x00\x00\x00\x00\x00" 112 | assertEqual(t, pid1, decode(t, binary), "") 113 | assertEqual(t, binary, encode(t, pid1, -1), "") 114 | 115 | pidOldBinary := "\x83\x67\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00" 116 | pidOld := decode(t, pidOldBinary) 117 | assertEqual(t, "\x83gd\x00\rnonode@nohost\x00\x00\x00N\x00\x00\x00\x00\x00", encode(t, pidOld, -1), "") 118 | pidNewBinary := "\x83\x58\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x4E\x00\x00\x00\x00\x00\x00\x00\x00" 119 | pidNew := decode(t, pidNewBinary) 120 | assertEqual(t, "\x83Xd\x00\rnonode@nohost\x00\x00\x00N\x00\x00\x00\x00\x00\x00\x00\x00", encode(t, pidNew, -1), "") 121 | } 122 | 123 | func TestPort(t *testing.T) { 124 | portOldBinary := "\x83\x66\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x06\x00" 125 | portOld := decode(t, portOldBinary) 126 | assertEqual(t, "\x83fd\x00\rnonode@nohost\x00\x00\x00\x06\x00", encode(t, portOld, -1), "") 127 | portNewBinary := "\x83\x59\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x06\x00\x00\x00\x00" 128 | portNew := decode(t, portNewBinary) 129 | assertEqual(t, "\x83Yd\x00\rnonode@nohost\x00\x00\x00\x06\x00\x00\x00\x00", encode(t, portNew, -1), "") 130 | } 131 | 132 | func TestReference(t *testing.T) { 133 | ref1 := OtpErlangReference{NodeTag: 100, Node: []uint8{0x0, 0xd, 0x6e, 0x6f, 0x6e, 0x6f, 0x64, 0x65, 0x40, 0x6e, 0x6f, 0x68, 0x6f, 0x73, 0x74}, ID: []uint8{0x0, 0x0, 0x0, 0xaf, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0}, Creation: []uint8{0x0}} 134 | binary := "\x83\x72\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x00\xAF\x00\x00\x00\x03\x00\x00\x00\x00" 135 | assertEqual(t, ref1, decode(t, binary), "") 136 | assertEqual(t, binary, encode(t, ref1, -1), "") 137 | 138 | refNewBinary := "\x83\x72\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x03\xE8\x4E\xE7\x68\x00\x02\xA4\xC8\x53\x40" 139 | refNew := decode(t, refNewBinary) 140 | assertEqual(t, "\x83r\x00\x03d\x00\rnonode@nohost\x00\x00\x03\xe8N\xe7h\x00\x02\xa4\xc8S@", encode(t, refNew, -1), "") 141 | refNewerBinary := "\x83\x5A\x00\x03\x64\x00\x0D\x6E\x6F\x6E\x6F\x64\x65\x40\x6E\x6F\x68\x6F\x73\x74\x00\x00\x00\x00\x00\x01\xAC\x03\xC7\x00\x00\x04\xBB\xB2\xCA\xEE" 142 | refNewer := decode(t, refNewerBinary) 143 | assertEqual(t, "\x83Z\x00\x03d\x00\rnonode@nohost\x00\x00\x00\x00\x00\x01\xac\x03\xc7\x00\x00\x04\xbb\xb2\xca\xee", encode(t, refNewer, -1), "") 144 | } 145 | 146 | func TestFunction(t *testing.T) { 147 | fun1 := OtpErlangFunction{Tag: 113, Value: []uint8{0x64, 0x0, 0x5, 0x6c, 0x69, 0x73, 0x74, 0x73, 0x64, 0x0, 0x6, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x61, 0x2}} 148 | binary := "\x83\x71\x64\x00\x05\x6C\x69\x73\x74\x73\x64\x00\x06\x6D\x65\x6D\x62\x65\x72\x61\x02" 149 | assertEqual(t, fun1, decode(t, binary), "") 150 | assertEqual(t, binary, encode(t, fun1, -1), "") 151 | } 152 | 153 | func TestDecodeBinaryToTerm(t *testing.T) { 154 | assertDecodeError(t, "null input", "", "") 155 | assertDecodeError(t, "null input", "\x00", "") 156 | assertDecodeError(t, "null input", "\x83", "") 157 | assertDecodeError(t, "invalid tag", "\x83z", "") 158 | } 159 | func TestDecodeBinaryToTermAtom(t *testing.T) { 160 | assertDecodeError(t, "EOF", "\x83d", "") 161 | assertDecodeError(t, "unexpected EOF", "\x83d\x00", "") 162 | assertDecodeError(t, "EOF", "\x83d\x00\x01", "") 163 | assertDecodeError(t, "EOF", "\x83s\x01", "") 164 | assertEqual(t, OtpErlangAtomUTF8(""), decode(t, "\x83v\x00\x00"), "") 165 | assertEqual(t, OtpErlangAtom(""), decode(t, "\x83d\x00\x00"), "") 166 | assertEqual(t, OtpErlangAtomUTF8(""), decode(t, "\x83w\x00"), "") 167 | assertEqual(t, OtpErlangAtom(""), decode(t, "\x83s\x00"), "") 168 | assertEqual(t, OtpErlangAtomUTF8("test"), decode(t, "\x83v\x00\x04test"), "") 169 | assertEqual(t, OtpErlangAtom("test"), decode(t, "\x83d\x00\x04test"), "") 170 | assertEqual(t, OtpErlangAtomUTF8("test"), decode(t, "\x83w\x04test"), "") 171 | assertEqual(t, OtpErlangAtom("test"), decode(t, "\x83s\x04test"), "") 172 | } 173 | func TestDecodeBinaryToTermPredefinedAtom(t *testing.T) { 174 | assertEqual(t, true, decode(t, "\x83w\x04true"), "") 175 | assertEqual(t, false, decode(t, "\x83w\x05false"), "") 176 | assertEqual(t, nil, decode(t, "\x83v\x00\x09undefined"), "") 177 | } 178 | func TestDecodeBinaryToTermEmptyList(t *testing.T) { 179 | assertEqual(t, OtpErlangList{Value: make([]interface{}, 0), Improper: false}, decode(t, "\x83j"), "") 180 | } 181 | func TestDecodeBinaryToTermStringList(t *testing.T) { 182 | assertDecodeError(t, "EOF", "\x83k", "") 183 | assertDecodeError(t, "unexpected EOF", "\x83k\x00", "") 184 | assertDecodeError(t, "EOF", "\x83k\x00\x01", "") 185 | assertEqual(t, "", decode(t, "\x83k\x00\x00"), "") 186 | assertEqual(t, "test", decode(t, "\x83k\x00\x04test"), "") 187 | } 188 | func TestDecodeBinaryToTermList(t *testing.T) { 189 | assertDecodeError(t, "EOF", "\x83l", "") 190 | assertDecodeError(t, "unexpected EOF", "\x83l\x00", "") 191 | assertDecodeError(t, "unexpected EOF", "\x83l\x00\x00", "") 192 | assertDecodeError(t, "unexpected EOF", "\x83l\x00\x00\x00", "") 193 | assertDecodeError(t, "EOF", "\x83l\x00\x00\x00\x00", "") 194 | assertEqual(t, OtpErlangList{Value: make([]interface{}, 0), Improper: false}, decode(t, "\x83l\x00\x00\x00\x00j"), "") 195 | list1 := make([]interface{}, 2) 196 | list1[0] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 197 | list1[1] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 198 | assertEqual(t, OtpErlangList{Value: list1, Improper: false}, decode(t, "\x83l\x00\x00\x00\x02jjj"), "") 199 | } 200 | func TestDecodeBinaryToTermLargeList(t *testing.T) { 201 | n := 256 202 | encoded := encode(t, OtpErlangList{Value: listOfLargeTuples(n)}, 1) 203 | decoded := decode(t, encoded) 204 | assertEqual(t, n, len(decoded.(OtpErlangList).Value), "") 205 | } 206 | func TestDecodeBinaryToTermImproperList(t *testing.T) { 207 | assertDecodeError(t, "EOF", "\x83l\x00\x00\x00k", "") 208 | lst := decode(t, "\x83l\x00\x00\x00\x01jv\x00\x04tail") 209 | lstCmp := make([]interface{}, 2) 210 | lstCmp[0] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 211 | lstCmp[1] = OtpErlangAtomUTF8("tail") 212 | assertEqual(t, OtpErlangList{Value: lstCmp, Improper: true}, lst, "") 213 | } 214 | func TestDecodeBinaryToTermSmallTuple(t *testing.T) { 215 | assertDecodeError(t, "EOF", "\x83h", "") 216 | assertDecodeError(t, "EOF", "\x83h\x01", "") 217 | assertEqual(t, OtpErlangTuple(make([]interface{}, 0)), decode(t, "\x83h\x00"), "") 218 | tuple1 := make([]interface{}, 2) 219 | tuple1[0] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 220 | tuple1[1] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 221 | assertEqual(t, OtpErlangTuple(tuple1), decode(t, "\x83h\x02jj"), "") 222 | } 223 | func TestDecodeBinaryToTermLargeTuple(t *testing.T) { 224 | assertDecodeError(t, "EOF", "\x83i", "") 225 | assertDecodeError(t, "unexpected EOF", "\x83i\x00", "") 226 | assertDecodeError(t, "unexpected EOF", "\x83i\x00\x00", "") 227 | assertDecodeError(t, "unexpected EOF", "\x83i\x00\x00\x00", "") 228 | assertDecodeError(t, "EOF", "\x83i\x00\x00\x00\x01", "") 229 | assertEqual(t, OtpErlangTuple(make([]interface{}, 0)), decode(t, "\x83i\x00\x00\x00\x00"), "") 230 | tuple1 := make([]interface{}, 2) 231 | tuple1[0] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 232 | tuple1[1] = OtpErlangList{Value: make([]interface{}, 0), Improper: false} 233 | assertEqual(t, OtpErlangTuple(tuple1), decode(t, "\x83i\x00\x00\x00\x02jj"), "") 234 | } 235 | func TestDecodeBinaryToTermSmallInteger(t *testing.T) { 236 | assertDecodeError(t, "EOF", "\x83a", "") 237 | assertEqual(t, uint8(0), decode(t, "\x83a\x00"), "") 238 | assertEqual(t, uint8(255), decode(t, "\x83a\xff"), "") 239 | } 240 | func TestDecodeBinaryToTermInteger(t *testing.T) { 241 | assertDecodeError(t, "EOF", "\x83b", "") 242 | assertDecodeError(t, "unexpected EOF", "\x83b\x00", "") 243 | assertDecodeError(t, "unexpected EOF", "\x83b\x00\x00", "") 244 | assertDecodeError(t, "unexpected EOF", "\x83b\x00\x00\x00", "") 245 | assertEqual(t, int32(0), decode(t, "\x83b\x00\x00\x00\x00"), "") 246 | assertEqual(t, int32(2147483647), decode(t, "\x83b\x7f\xff\xff\xff"), "") 247 | assertEqual(t, int32(-2147483648), decode(t, "\x83b\x80\x00\x00\x00"), "") 248 | assertEqual(t, int32(-1), decode(t, "\x83b\xff\xff\xff\xff"), "") 249 | } 250 | func TestDecodeBinaryToTermBinary(t *testing.T) { 251 | assertDecodeError(t, "EOF", "\x83m", "") 252 | assertDecodeError(t, "unexpected EOF", "\x83m\x00", "") 253 | assertDecodeError(t, "unexpected EOF", "\x83m\x00\x00", "") 254 | assertDecodeError(t, "unexpected EOF", "\x83m\x00\x00\x00", "") 255 | assertEqual(t, OtpErlangBinary{Value: []byte(""), Bits: 8}, decode(t, "\x83m\x00\x00\x00\x00"), "") 256 | assertEqual(t, OtpErlangBinary{Value: []byte("data"), Bits: 8}, decode(t, "\x83m\x00\x00\x00\x04data"), "") 257 | } 258 | func TestDecodeBinaryToTermFloat(t *testing.T) { 259 | assertDecodeError(t, "EOF", "\x83F", "") 260 | assertDecodeError(t, "unexpected EOF", "\x83F\x00", "") 261 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00", "") 262 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00\x00", "") 263 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00\x00\x00", "") 264 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00\x00\x00\x00", "") 265 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00\x00\x00\x00\x00", "") 266 | assertDecodeError(t, "unexpected EOF", "\x83F\x00\x00\x00\x00\x00\x00\x00", "") 267 | assertEqual(t, 0.0, decode(t, "\x83F\x00\x00\x00\x00\x00\x00\x00\x00"), "") 268 | assertEqual(t, 1.5, decode(t, "\x83F?\xf8\x00\x00\x00\x00\x00\x00"), "") 269 | } 270 | func TestDecodeBinaryToTermSmallBigInteger(t *testing.T) { 271 | assertDecodeError(t, "EOF", "\x83n", "") 272 | assertDecodeError(t, "EOF", "\x83n\x00", "") 273 | assertDecodeError(t, "EOF", "\x83n\x01\x00", "") 274 | assertEqual(t, big.NewInt(0), decode(t, "\x83n\x00\x00"), "") 275 | bignum1, _ := big.NewInt(0).SetString("6618611909121", 10) 276 | assertEqual(t, bignum1, decode(t, "\x83n\x06\x00\x01\x02\x03\x04\x05\x06"), "") 277 | bignum2, _ := big.NewInt(0).SetString("-6618611909121", 10) 278 | assertEqual(t, bignum2, decode(t, "\x83n\x06\x01\x01\x02\x03\x04\x05\x06"), "") 279 | } 280 | func TestDecodeBinaryToTermLargeBigInteger(t *testing.T) { 281 | assertDecodeError(t, "EOF", "\x83o", "") 282 | assertDecodeError(t, "unexpected EOF", "\x83o\x00", "") 283 | assertDecodeError(t, "unexpected EOF", "\x83o\x00\x00", "") 284 | assertDecodeError(t, "unexpected EOF", "\x83o\x00\x00\x00", "") 285 | assertDecodeError(t, "EOF", "\x83o\x00\x00\x00\x00", "") 286 | assertDecodeError(t, "EOF", "\x83o\x00\x00\x00\x01\x00", "") 287 | assertEqual(t, big.NewInt(0), decode(t, "\x83o\x00\x00\x00\x00\x00"), "") 288 | bignum1, _ := big.NewInt(0).SetString("6618611909121", 10) 289 | assertEqual(t, bignum1, decode(t, "\x83o\x00\x00\x00\x06\x00\x01\x02\x03\x04\x05\x06"), "") 290 | bignum2, _ := big.NewInt(0).SetString("-6618611909121", 10) 291 | assertEqual(t, bignum2, decode(t, "\x83o\x00\x00\x00\x06\x01\x01\x02\x03\x04\x05\x06"), "") 292 | } 293 | func TestDecodeBinaryToTermMap(t *testing.T) { 294 | assertDecodeError(t, "EOF", "\x83t", "") 295 | assertDecodeError(t, "unexpected EOF", "\x83t\x00", "") 296 | assertDecodeError(t, "unexpected EOF", "\x83t\x00\x00", "") 297 | assertDecodeError(t, "unexpected EOF", "\x83t\x00\x00\x00", "") 298 | assertDecodeError(t, "EOF", "\x83t\x00\x00\x00\x01", "") 299 | assertEqual(t, make(OtpErlangMap), decode(t, "\x83t\x00\x00\x00\x00"), "") 300 | map1 := make(OtpErlangMap) 301 | map1[OtpErlangAtom("a")] = uint8(1) 302 | assertEqual(t, map1, decode(t, "\x83t\x00\x00\x00\x01d\x00\x01aa\x01"), "") 303 | // only able to compare OtpErlangMap of size 1 due to being unordered 304 | map2 := make(OtpErlangMap) 305 | map2[OtpErlangAtomUTF8("everything")] = OtpErlangBinary{Value: []byte("\xA8"), Bits: 6} 306 | assertEqual(t, map2, decode(t, "\x83\x74\x00\x00\x00\x01\x77\x0A\x65\x76\x65\x72\x79\x74\x68\x69\x6E\x67\x4D\x00\x00\x00\x01\x06\xA8"), "") 307 | } 308 | func TestDecodeBinaryToTermCompressedTerm(t *testing.T) { 309 | assertDecodeError(t, "EOF", "\x83P", "") 310 | assertDecodeError(t, "unexpected EOF", "\x83P\x00", "") 311 | assertDecodeError(t, "unexpected EOF", "\x83P\x00\x00", "") 312 | assertDecodeError(t, "unexpected EOF", "\x83P\x00\x00\x00", "") 313 | assertDecodeError(t, "compressed data null", "\x83P\x00\x00\x00\x00", "") 314 | assertEqual(t, strings.Repeat("d", 20), decode(t, "\x83P\x00\x00\x00\x17\x78\xda\xcb\x66\x10\x49\xc1\x02\x00\x5d\x60\x08\x50"), "") 315 | } 316 | 317 | func TestEncodeTermToBinaryTuple(t *testing.T) { 318 | assertEqual(t, "\x83h\x00", encode(t, []interface{}{}, -1), "") 319 | assertEqual(t, "\x83h\x00", encode(t, OtpErlangTuple{}, -1), "") 320 | assertEqual(t, "\x83h\x02h\x00h\x00", encode(t, []interface{}{[]interface{}{}, []interface{}{}}, -1), "") 321 | tuple1 := make(OtpErlangTuple, 255) 322 | for i := 0; i < 255; i++ { 323 | tuple1[i] = OtpErlangTuple{} 324 | } 325 | assertEqual(t, "\x83h\xff"+strings.Repeat("h\x00", 255), encode(t, tuple1, -1), "") 326 | tuple2 := make(OtpErlangTuple, 256) 327 | for i := 0; i < 256; i++ { 328 | tuple2[i] = OtpErlangTuple{} 329 | } 330 | assertEqual(t, "\x83i\x00\x00\x01\x00"+strings.Repeat("h\x00", 256), encode(t, tuple2, -1), "") 331 | } 332 | func TestEncodeTermToBinaryEmptyList(t *testing.T) { 333 | assertEqual(t, "\x83j", encode(t, OtpErlangList{}, -1), "") 334 | assertEqual(t, "\x83j", encode(t, OtpErlangList{Value: []interface{}{}}, -1), "") 335 | assertEqual(t, "\x83j", encode(t, OtpErlangList{Value: []interface{}{}, Improper: false}, -1), "") 336 | } 337 | func TestEncodeTermToBinaryStringList(t *testing.T) { 338 | assertEqual(t, "\x83j", encode(t, "", -1), "") 339 | assertEqual(t, "\x83k\x00\x01\x00", encode(t, "\x00", -1), "") 340 | s := "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" 341 | assertEqual(t, "\x83k\x01\x00"+s, encode(t, s, -1), "") 342 | } 343 | func TestEncodeTermToBinaryListBasic(t *testing.T) { 344 | assertEqual(t, "\x83\x6A", encode(t, OtpErlangList{}, -1), "") 345 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x6A\x6A", encode(t, OtpErlangList{Value: []interface{}{""}}, -1), "") 346 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x61\x01\x6A", encode(t, OtpErlangList{Value: []interface{}{1}}, -1), "") 347 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x61\xFF\x6A", encode(t, OtpErlangList{Value: []interface{}{255}}, -1), "") 348 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\x00\x00\x01\x00\x6A", encode(t, OtpErlangList{Value: []interface{}{256}}, -1), "") 349 | i1 := 2147483647 350 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\x7F\xFF\xFF\xFF\x6A", encode(t, OtpErlangList{Value: []interface{}{i1}}, -1), "") 351 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x6E\x04\x00\x00\x00\x00\x80\x6A", encode(t, OtpErlangList{Value: []interface{}{i1 + 1}}, -1), "") 352 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x61\x00\x6A", encode(t, OtpErlangList{Value: []interface{}{0}}, -1), "") 353 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\xFF\x6A", encode(t, OtpErlangList{Value: []interface{}{-1}}, -1), "") 354 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFF\x00\x6A", encode(t, OtpErlangList{Value: []interface{}{-256}}, -1), "") 355 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\xFF\xFF\xFE\xFF\x6A", encode(t, OtpErlangList{Value: []interface{}{-257}}, -1), "") 356 | i2 := -2147483648 357 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x62\x80\x00\x00\x00\x6A", encode(t, OtpErlangList{Value: []interface{}{i2}}, -1), "") 358 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x6E\x04\x01\x01\x00\x00\x80\x6A", encode(t, OtpErlangList{Value: []interface{}{i2 - 1}}, -1), "") 359 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x6B\x00\x04\x74\x65\x73\x74\x6A", encode(t, OtpErlangList{Value: []interface{}{"test"}}, -1), "") 360 | assertEqual(t, "\x83\x6C\x00\x00\x00\x02\x62\x00\x00\x01\x75\x62\x00\x00\x01\xC7\x6A", encode(t, OtpErlangList{Value: []interface{}{373, 455}}, -1), "") 361 | list1 := OtpErlangList{} 362 | list2 := OtpErlangList{Value: []interface{}{list1}} 363 | assertEqual(t, "\x83\x6C\x00\x00\x00\x01\x6A\x6A", encode(t, list2, -1), "") 364 | list3 := OtpErlangList{Value: []interface{}{list1, list1}} 365 | assertEqual(t, "\x83\x6C\x00\x00\x00\x02\x6A\x6A\x6A", encode(t, list3, -1), "") 366 | list4 := OtpErlangList{Value: []interface{}{OtpErlangList{Value: []interface{}{"this", "is"}}, OtpErlangList{Value: []interface{}{OtpErlangList{Value: []interface{}{"a"}}}}, "test"}} 367 | assertEqual(t, "\x83\x6C\x00\x00\x00\x03\x6C\x00\x00\x00\x02\x6B\x00\x04\x74\x68\x69\x73\x6B\x00\x02\x69\x73\x6A\x6C\x00\x00\x00\x01\x6C\x00\x00\x00\x01\x6B\x00\x01\x61\x6A\x6A\x6B\x00\x04\x74\x65\x73\x74\x6A", encode(t, list4, -1), "") 368 | } 369 | func TestEncodeTermToBinaryList(t *testing.T) { 370 | list1 := OtpErlangList{} 371 | list2 := OtpErlangList{Value: []interface{}{list1}} 372 | assertEqual(t, "\x83l\x00\x00\x00\x01jj", encode(t, list2, -1), "") 373 | list3 := OtpErlangList{Value: []interface{}{list1, list1, list1, list1, list1}} 374 | assertEqual(t, "\x83l\x00\x00\x00\x05jjjjjj", encode(t, list3, -1), "") 375 | } 376 | func TestEncodeTermToBinaryImproperList(t *testing.T) { 377 | list1 := OtpErlangList{Value: []interface{}{OtpErlangTuple{}, []interface{}{}}, Improper: true} 378 | assertEqual(t, "\x83l\x00\x00\x00\x01h\x00h\x00", encode(t, list1, -1), "") 379 | list2 := OtpErlangList{Value: []interface{}{0, 1}, Improper: true} 380 | assertEqual(t, "\x83l\x00\x00\x00\x01a\x00a\x01", encode(t, list2, -1), "") 381 | } 382 | func TestEncodeTermToBinaryUnicode(t *testing.T) { 383 | assertEqual(t, "\x83j", encode(t, "", -1), "") 384 | assertEqual(t, "\x83k\x00\x04test", encode(t, "test", -1), "") 385 | assertEqual(t, "\x83k\x00\x03\x00\xc3\xbf", encode(t, "\x00\xc3\xbf", -1), "") 386 | assertEqual(t, "\x83k\x00\x02\xc4\x80", encode(t, "\xc4\x80", -1), "") 387 | assertEqual(t, "\x83k\x00\x08\xd1\x82\xd0\xb5\xd1\x81\xd1\x82", encode(t, "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82", -1), "") 388 | // becomes a list of small integers 389 | assertEqual(t, "\x83l\x00\x02\x00\x00"+strings.Repeat("a\xd0a\x90", 65536)+"j", encode(t, strings.Repeat("\xd0\x90", 65536), -1), "") 390 | } 391 | func TestEncodeTermToBinaryAtom(t *testing.T) { 392 | assertEqual(t, "\x83s\x00", encode(t, OtpErlangAtom(""), -1), "") 393 | assertEqual(t, "\x83s\x04test", encode(t, OtpErlangAtom("test"), -1), "") 394 | } 395 | func TestEncodeTermToBinaryStringBasic(t *testing.T) { 396 | assertEqual(t, "\x83\x6A", encode(t, "", -1), "") 397 | assertEqual(t, "\x83\x6B\x00\x04\x74\x65\x73\x74", encode(t, "test", -1), "") 398 | assertEqual(t, "\x83\x6B\x00\x09\x74\x77\x6F\x20\x77\x6F\x72\x64\x73", encode(t, "two words", -1), "") 399 | assertEqual(t, "\x83\x6B\x00\x16\x74\x65\x73\x74\x69\x6E\x67\x20\x6D\x75\x6C\x74\x69\x70\x6C\x65\x20\x77\x6F\x72\x64\x73", encode(t, "testing multiple words", -1), "") 400 | assertEqual(t, "\x83\x6B\x00\x01\x20", encode(t, " ", -1), "") 401 | assertEqual(t, "\x83\x6B\x00\x02\x20\x20", encode(t, " ", -1), "") 402 | assertEqual(t, "\x83\x6B\x00\x01\x31", encode(t, "1", -1), "") 403 | assertEqual(t, "\x83\x6B\x00\x02\x33\x37", encode(t, "37", -1), "") 404 | assertEqual(t, "\x83\x6B\x00\x07\x6F\x6E\x65\x20\x3D\x20\x31", encode(t, "one = 1", -1), "") 405 | assertEqual(t, "\x83\x6B\x00\x20\x21\x40\x23\x24\x25\x5E\x26\x2A\x28\x29\x5F\x2B\x2D\x3D\x5B\x5D\x7B\x7D\x5C\x7C\x3B\x27\x3A\x22\x2C\x2E\x2F\x3C\x3E\x3F\x7E\x60", encode(t, "!@#$%^&*()_+-=[]{}\\|;':\",./<>?~`", -1), "") 406 | assertEqual(t, "\x83\x6B\x00\x09\x22\x08\x0C\x0A\x0D\x09\x0B\x53\x12", encode(t, "\"\b\f\n\r\t\v\123\x12", -1), "") 407 | } 408 | func TestEncodeTermToBinaryString(t *testing.T) { 409 | assertEqual(t, "\x83j", encode(t, "", -1), "") 410 | assertEqual(t, "\x83k\x00\x04test", encode(t, "test", -1), "") 411 | } 412 | func TestEncodeTermToBinaryPredefinedAtoms(t *testing.T) { 413 | assertEqual(t, "\x83w\x04true", encode(t, true, -1), "") 414 | assertEqual(t, "\x83w\x05false", encode(t, false, -1), "") 415 | assertEqual(t, "\x83w\x09undefined", encode(t, nil, -1), "") 416 | } 417 | func TestEncodeTermToBinaryShortInteger(t *testing.T) { 418 | assertEqual(t, "\x83a\x00", encode(t, 0, -1), "") 419 | assertEqual(t, "\x83a\xff", encode(t, 255, -1), "") 420 | } 421 | func TestEncodeTermToBinaryInteger(t *testing.T) { 422 | assertEqual(t, "\x83b\xff\xff\xff\xff", encode(t, -1, -1), "") 423 | assertEqual(t, "\x83b\x80\x00\x00\x00", encode(t, -2147483648, -1), "") 424 | assertEqual(t, "\x83b\x00\x00\x01\x00", encode(t, 256, -1), "") 425 | assertEqual(t, "\x83b\x7f\xff\xff\xff", encode(t, 2147483647, -1), "") 426 | } 427 | func TestEncodeTermToBinaryLongInteger(t *testing.T) { 428 | assertEqual(t, "\x83n\x04\x00\x00\x00\x00\x80", encode(t, 2147483648, -1), "") 429 | assertEqual(t, "\x83n\x04\x01\x01\x00\x00\x80", encode(t, -2147483649, -1), "") 430 | i1 := big.NewInt(0) 431 | i1.Exp(big.NewInt(2), big.NewInt(2040), nil) 432 | assertEqual(t, "\x83o\x00\x00\x01\x00\x00"+strings.Repeat("\x00", 255)+"\x01", encode(t, i1, -1), "") 433 | i2 := big.NewInt(0).Neg(i1) 434 | assertEqual(t, "\x83o\x00\x00\x01\x00\x01"+strings.Repeat("\x00", 255)+"\x01", encode(t, i2, -1), "") 435 | } 436 | func TestEncodeTermToBinaryFloat(t *testing.T) { 437 | assertEqual(t, "\x83F\x00\x00\x00\x00\x00\x00\x00\x00", encode(t, 0.0, -1), "") 438 | assertEqual(t, "\x83F?\xe0\x00\x00\x00\x00\x00\x00", encode(t, 0.5, -1), "") 439 | assertEqual(t, "\x83F\xbf\xe0\x00\x00\x00\x00\x00\x00", encode(t, -0.5, -1), "") 440 | assertEqual(t, "\x83F@\t!\xfbM\x12\xd8J", encode(t, 3.1415926, -1), "") 441 | assertEqual(t, "\x83F\xc0\t!\xfbM\x12\xd8J", encode(t, -3.1415926, -1), "") 442 | } 443 | func TestEncodeTermToBinaryMap(t *testing.T) { 444 | assertEqual(t, "\x83t\x00\x00\x00\x00", encode(t, make(OtpErlangMap), -1), "") 445 | map1 := make(OtpErlangMap) 446 | map1[OtpErlangAtom("a")] = uint8(1) 447 | assertEqual(t, "\x83t\x00\x00\x00\x01s\x01aa\x01", encode(t, map1, -1), "") 448 | // only able to compare OtpErlangMap of size 1 due to being unordered 449 | map2 := make(OtpErlangMap) 450 | map2[OtpErlangAtomUTF8("everything")] = OtpErlangBinary{Value: []byte("\xA8"), Bits: 6} 451 | assertEqual(t, "\x83\x74\x00\x00\x00\x01\x77\x0A\x65\x76\x65\x72\x79\x74\x68\x69\x6E\x67\x4D\x00\x00\x00\x01\x06\xA8", encode(t, map2, -1), "") 452 | } 453 | func TestEncodeTermToBinaryCompressedTerm(t *testing.T) { 454 | list1 := OtpErlangList{} 455 | list2 := OtpErlangList{Value: []interface{}{list1, list1, list1, list1, list1, list1, list1, list1, list1, list1, list1, list1, list1, list1, list1}} 456 | assertEqual(t, "\x83P\x00\x00\x00\x15x\x9c\xcaa``\xe0\xcfB\x03\x80\x00\x00\x00\xff\xffB@\a\x1c", encode(t, list2, 6), "") 457 | assertEqual(t, "\x83P\x00\x00\x00\x15x\xda\xcaa``\xe0\xcfB\x03\x80\x00\x00\x00\xff\xffB@\a\x1c", encode(t, list2, 9), "") 458 | assertEqual(t, "\x83P\x00\x00\x00\x15x\x01\x00\x15\x00\xea\xffl\x00\x00\x00\x0fjjjjjjjjjjjjjjjj\x01\x00\x00\xff\xffB@\a\x1c", encode(t, list2, 0), "") 459 | assertEqual(t, "\x83P\x00\x00\x00\x17x\xda\xcaf\x10I\xc1\x02\x00\x01\x00\x00\xff\xff]`\bP", encode(t, strings.Repeat("d", 20), 9), "") 460 | } 461 | 462 | func listOfLargeTuples(size int) []interface{} { 463 | // resembles an entry of the Apache CouchDB's update_seq in a clustered setup 464 | example := OtpErlangTuple{ 465 | OtpErlangAtom("couchdb@this-is-a-long-hostname-somewhere-over-the-rainbow-in-the.cloudapp.net"), 466 | OtpErlangList{ 467 | Value: []interface{}{ 468 | big.NewInt(4278190080), 469 | big.NewInt(4294967295), 470 | }, 471 | }, 472 | OtpErlangTuple{ 473 | 1012001, 474 | OtpErlangBinary{ 475 | Value: []uint8{31, 42, 53, 64, 75, 86, 97}, 476 | Bits: 8, 477 | }, 478 | OtpErlangAtom("couchdb@this-is-a-long-hostname-somewhere-over-the-rainbow-in-the.cloudapp.net"), 479 | }, 480 | } 481 | tuples := make([]interface{}, size) 482 | for i := 0; i < size; i++ { 483 | tuples[i] = example 484 | } 485 | return tuples 486 | } 487 | -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/okeuday/erlang_go/v2 2 | --------------------------------------------------------------------------------