├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── examples ├── codec.rs ├── erlang_rust_port.erl ├── erlang_rust_port.rs ├── json_port.erl ├── json_port.rs └── parser.rs ├── src └── erl_ext.rs └── tests ├── codec_test.rs ├── data └── wtf.txt └── term_gen.erl /.gitignore: -------------------------------------------------------------------------------- 1 | tests/data/*.bin 2 | target/ 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis doesn't support Rust, so we set it to Erlang, since we need escript 2 | # anyway 3 | language: erlang 4 | # sudo: false 5 | 6 | otp_release: 7 | - 17.4 8 | 9 | install: 10 | - curl -sS https://static.rust-lang.org/rustup.sh | bash /dev/stdin --yes # --prefix=$HOME/rust --disable-sudo 11 | 12 | script: 13 | - cargo build --verbose 14 | - cargo test --verbose 15 | - escript examples/erlang_rust_port.erl 16 | - escript examples/json_port.erl 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rust_erl_ext" 4 | version = "0.2.1" 5 | authors = ["Sergey Prokhorov "] 6 | 7 | description = "Erlang external term format codec." 8 | repository = "https://github.com/seriyps/rust-erl-ext" 9 | readme = "README.md" 10 | keywords = ["serialization", "encoding", "erlang", "bert", "term_to_binary"] 11 | license = "Apache-2.0" 12 | 13 | [lib] 14 | 15 | name = "erl_ext" 16 | # path = "src/erl_ext.rs" 17 | 18 | 19 | [dependencies] 20 | 21 | num = ">=0.1.25" 22 | byteorder = ">=0.5.1" 23 | 24 | [dev-dependencies] 25 | 26 | getopts = ">=0.2.12" 27 | rustc-serialize = ">=0.3.15" 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rust Erl Ext 2 | ============ 3 | 4 | [Erlang external term format](http://erlang.org/doc/apps/erts/erl_ext_dist.html) 5 | parser/serializer for Rust. 6 | 7 | [![Build Status](https://travis-ci.org/seriyps/rust-erl-ext.png?branch=master)](https://travis-ci.org/seriyps/rust-erl-ext) 8 | 9 | Examples 10 | ------- 11 | 12 | Decoding 13 | 14 | ```rust 15 | extern crate erl_ext; 16 | use erl_ext::Decoder; 17 | 18 | fn main() { 19 | let mut decoder = Decoder::new(&mut io::stdin()); 20 | assert!(true == decoder.read_prelude().unwrap()); 21 | println!("{}", decoder.decode_term().unwrap()); 22 | } 23 | ``` 24 | 25 | Encoding 26 | 27 | ```rust 28 | extern crate erl_ext; 29 | use erl_ext::{Eterm, Encoder}; 30 | 31 | fn main() { 32 | let term = Eterm::List(vec!(Eterm::SmallInteger(1), 33 | Eterm::Integer(1000000), 34 | Eterm::Nil)); 35 | // this combination of options make it compatible with erlang:term_to_binary/1 36 | let utf8_atoms = false; 37 | let small_atoms = false; 38 | let fair_new_fun = true; 39 | let mut encoder = Encoder::new(&mut io::stdout(), 40 | utf8_atoms, small_atoms, fair_new_fun); 41 | encoder.write_prelude(); 42 | encoder.encode_term(term); 43 | } 44 | ``` 45 | 46 | More examples are in `examples` directory. 47 | 48 | Types (all Erlang 17.1 types are supported): 49 | 50 | * SmallInteger (u8) : `0..255` 51 | * Integer (i32) : `integer()` 52 | * Float (f64) : `float()` 53 | * Atom (String) : `atom()` 54 | * Reference : `reference()` `erlang:make_ref/0` 55 | * Port : `port()` eg, socket or raw file or `erlang:open_port/2` 56 | * Pid : `pid()` 57 | * Tuple (`Vec`) : `{ any() }` 58 | * Map (`Vec<(Eterm, Eterm)>`) : `#{any() := any()}` 59 | * Nil : `[]` 60 | * String (`Vec`) : `[0..255]` 61 | * List (`Vec`) : `[ any() ]` 62 | * Binary (`Vec`) : `binary()` 63 | * BigNum (`BigInt`) : `integer() > i32` 64 | * Fun : `fun(..) -> ... end.` - deprecated variant 65 | * NewFun : `fun(..) -> ... end.` 66 | * Export : `fun my_mod:my_fun/1` 67 | * BitBinary : `<<128, 128:4>>` 68 | 69 | 70 | TODO 71 | ---- 72 | 73 | * `serialize::Decoder` and `serialize::Encoder` implementations (not so easy for containers) 74 | * Quick-Check - like tests (feed pseudo-random bytes to decoder, feed random Eterm's to encoder) 75 | 76 | Keywords 77 | -------- 78 | 79 | * Rust 80 | * Erlang 81 | * BERT 82 | * External term format 83 | * term_to_binary, binary_to_term 84 | * parser, serializer 85 | -------------------------------------------------------------------------------- /examples/codec.rs: -------------------------------------------------------------------------------- 1 | extern crate erl_ext; 2 | extern crate getopts; 3 | 4 | use getopts::Options; 5 | use erl_ext::{Decoder,Encoder}; 6 | use std::io::Write; 7 | use std::io; 8 | use std::env; 9 | use std::fs; 10 | use std::process::exit; 11 | 12 | fn main() { 13 | let args: Vec = env::args().collect(); 14 | let mut opts = Options::new(); 15 | opts.optflag("u", "utf8-atoms", "Use utf-8 atoms feature"); 16 | opts.optflag("s", "small-atoms", "Use small atoms feature"); 17 | opts.optflag("f", "fair-new-fun", "Fairly calculate NEW_FUN size (requires extra memory)"); 18 | 19 | let matches = match opts.parse(&args[1..]) { 20 | Ok(m) => { m } 21 | Err(f) => { panic!(f.to_string()) } 22 | }; 23 | if matches.free.len() != 2 { 24 | let brief = format!("Usage: {} [opts] ", args[0]); 25 | print!("{}", opts.usage(&brief)); 26 | exit(1); 27 | } 28 | let mut in_f: Box = match matches.free[0].as_ref() { 29 | "-" => Box::new(io::stdin()), 30 | other => 31 | Box::new(fs::File::open(other).unwrap()) 32 | }; 33 | let mut out_f: Box = match matches.free[1].as_ref() { 34 | "-" => Box::new(io::stdout()), 35 | other => 36 | Box::new(fs::File::create(other).unwrap()) 37 | }; 38 | 39 | let mut src = Vec::new(); 40 | in_f.read_to_end(&mut src).unwrap(); 41 | let dest = Vec::new(); 42 | 43 | let mut rdr = io::Cursor::new(src); 44 | let mut wrtr = io::BufWriter::new(dest); 45 | { 46 | // decode term 47 | let mut decoder = Decoder::new(&mut rdr); 48 | match decoder.read_prelude() { 49 | Ok(false) => 50 | panic!("Invalid eterm!"), 51 | Err(e) => 52 | panic!("DecodeError: {}", e), 53 | _ => () 54 | } 55 | let term = decoder.decode_term().unwrap(); 56 | // print it to stderr 57 | (write!(&mut io::stderr(), "{:?}\n", term)).unwrap(); 58 | // and encode it 59 | let mut encoder = Encoder::new(&mut wrtr, 60 | matches.opt_present("u"), 61 | matches.opt_present("s"), 62 | matches.opt_present("f")); 63 | encoder.write_prelude().unwrap(); 64 | encoder.encode_term(term).unwrap(); 65 | encoder.flush().unwrap(); 66 | } 67 | // write encoded result to out_f 68 | out_f.write(wrtr.get_ref()).unwrap(); 69 | 70 | // compare original and encoded 71 | // (write!(&mut io::stderr(), "Before {:?}\n", rdr.get_ref())).unwrap(); 72 | // (write!(&mut io::stderr(), "After {:?}\n", wrtr.get_ref())).unwrap(); 73 | 74 | if wrtr.get_ref() != rdr.get_ref() { 75 | (write!(&mut io::stderr(), "Before and After isn't equal\n")).unwrap(); 76 | exit(1); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/erlang_rust_port.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% See erlang_rust_port.rs 3 | %% 4 | %% We don't use '{packet, N}' option, so in Rust side we can save some memory 5 | %% by encoding directly to stdout (without calculating encoded data size). 6 | %% The drawback of this solution is that in Erlang we should read data back 7 | %% from port until it can be decoded by binary_to_term, and use binary 8 | %% accumulator for that (see 'recv/2'). 9 | %% Alternatively, we can use '{packet, N}' option, and in Rust side first 10 | %% encode terms to temporary MemWriter buffer, calculate and write it's size, 11 | %% and then copy this buffer to stdout. 12 | -mode(compile). 13 | 14 | main([]) -> 15 | main(["target/debug/examples/erlang_rust_port"]); 16 | main([PortPath]) -> 17 | AbsPortPath = filename:absname(PortPath), 18 | Port = erlang:open_port( 19 | {spawn_executable, AbsPortPath}, 20 | [{args, ["-u", "-s", "-f"]}, 21 | {env, [{"RUST_BACKTRACE", "1"}]}, 22 | binary]), 23 | ok = loop(Port, 500), 24 | io:format("~nok~n"), 25 | erlang:port_close(Port). 26 | 27 | 28 | loop(_, 0) -> 29 | ok; 30 | loop(Port, N) -> 31 | Term = #{"string" => atom, 32 | 3.14 => Port, 33 | [] => {self(), -100000000000000000000000000}, 34 | [1,2,3] => << <<0>> || _ <- lists:seq(1, 128) >> 35 | }, 36 | %% send terms to port 37 | Port ! {self(), {command, term_to_binary(Term)}}, 38 | %% receive them back 39 | case recv(Port, <<>>) of 40 | {ok, Term} -> % assert that term is the same 41 | io:format("."), 42 | loop(Port, N - 1); 43 | Other -> 44 | Other 45 | end. 46 | 47 | recv(Port, Acc) -> 48 | receive 49 | {Port, {data, Data}} -> 50 | NewAcc = <>, 51 | try 52 | {ok, binary_to_term(NewAcc)} 53 | catch _:_ -> 54 | recv(Port, NewAcc) 55 | end; 56 | Other -> 57 | Other 58 | after 10000 -> 59 | exit(recv_timeout) 60 | end. 61 | -------------------------------------------------------------------------------- /examples/erlang_rust_port.rs: -------------------------------------------------------------------------------- 1 | // see erlang_rust_port.erl 2 | 3 | extern crate erl_ext; 4 | extern crate getopts; 5 | 6 | use getopts::Options; 7 | use erl_ext::{Decoder,Encoder,Error}; 8 | 9 | use std::io; 10 | use std::env; 11 | 12 | 13 | fn main() { 14 | let args: Vec = env::args().collect(); 15 | let mut opts = Options::new(); 16 | opts.optflag("u", "utf8-atoms", "Use utf-8 atoms feature"); 17 | opts.optflag("s", "small-atoms", "Use small atoms feature"); 18 | opts.optflag("f", "fair-new-fun", "Fairly calculate NEW_FUN size (requires extra memory)"); 19 | 20 | let matches = match opts.parse(&args[1..]) { 21 | Ok(m) => { m } 22 | Err(f) => { panic!(f.to_string()) } 23 | }; 24 | 25 | let mut in_f = io::stdin(); 26 | let mut out_f = io::stdout(); 27 | // let mut out_writer = std::io::BufferedWriter::with_capacity(20480, 28 | // out_f.unwrap()); 29 | let decoder = Decoder::new(&mut in_f); 30 | let encoder = Encoder::new(&mut out_f, 31 | matches.opt_present("u"), 32 | matches.opt_present("s"), 33 | matches.opt_present("f")); 34 | match read_write_loop(decoder, encoder) { 35 | Err(Error::ByteorderUnexpectedEOF) => (), // port was closed 36 | Err(ref err) => 37 | panic!("Error: {}", err), 38 | Ok(()) => () // unreachable in this example 39 | }; 40 | } 41 | 42 | fn read_write_loop(mut decoder: Decoder, mut encoder: Encoder) -> Result<(), Error> { 43 | loop { 44 | assert!(true == try!(decoder.read_prelude())); 45 | let term = try!(decoder.decode_term()); 46 | try!(encoder.write_prelude()); 47 | try!(encoder.encode_term(term)); 48 | try!(encoder.flush()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/json_port.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | %% See json_port.rs 3 | %% 4 | %% In this example we use `{packet, 2}`, so, unlike in erlang_rust_port, whole 5 | %% response packet received at once. But this leads to more complex and less 6 | %% performant Rust part (since we need to serialize to temporary in-memory 7 | %% buffer to calculate packet's size). 8 | -mode(compile). 9 | 10 | main([]) -> 11 | main(["target/debug/examples/json_port"]); 12 | main([PortPath]) -> 13 | AbsPortPath = filename:absname(PortPath), 14 | Port = erlang:open_port( 15 | {spawn_executable, AbsPortPath}, 16 | [{packet, 2}, 17 | {env, [{"RUST_BACKTRACE", "1"}]}, 18 | binary]), 19 | 20 | run(Port, [<<"{\"array\": [1, -1, 0.1, {}, []], \"bool\": true," 21 | "\"float\": 99.999, \"bigint\": -9999999999999, \"biguint\": 999999999999," 22 | " \"null\": null, \"str\": \"Hello, world!\"}">>, 23 | <<"{true: true}">>, 24 | <<"{\"key\": \"unclosed}">>, 25 | <<255, 0>>, 26 | "[\"not binary\"]"]), 27 | 28 | erlang:port_close(Port). 29 | 30 | run(_, []) -> ok; 31 | run(Port, [JsonBin | Examples]) -> 32 | Json = parse(Port, JsonBin), 33 | io:format("==========~nJson:~n'~tp'~nErlang:~n'~p'~n", 34 | [JsonBin, Json]), 35 | run(Port, Examples). 36 | 37 | parse(Port, JsonBin) -> 38 | %% term_to_binary/1 actualy isn't required here, but if you want to 39 | %% implement json 'serialize', you may want to send, say 40 | %% `term_to_binary({parse, Bin})` and `term_to_binary({serialize, Term})` 41 | Port ! {self(), {command, term_to_binary(JsonBin)}}, 42 | receive 43 | {Port, {data, Data}} -> 44 | binary_to_term(Data); 45 | Other -> 46 | {error, Other} 47 | after 10000 -> 48 | exit(recv_timeout) 49 | end. 50 | -------------------------------------------------------------------------------- /examples/json_port.rs: -------------------------------------------------------------------------------- 1 | // see json_port.erl 2 | 3 | extern crate erl_ext; 4 | extern crate rustc_serialize; 5 | extern crate num; 6 | extern crate byteorder; 7 | 8 | // use std::num::ToPrimitive; 9 | use std::io; 10 | use std::io::Write; 11 | 12 | use num::bigint::ToBigInt; 13 | use num::traits::FromPrimitive; 14 | use num::traits::ToPrimitive; 15 | use rustc_serialize::json::{self, Json}; 16 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 17 | 18 | use erl_ext::{Eterm, Error}; 19 | 20 | 21 | fn main() { 22 | let in_f = io::stdin(); 23 | let out_f = io::stdout(); 24 | match read_write_loop(in_f, out_f) { 25 | Err(Error::ByteorderUnexpectedEOF) => (), // port was closed 26 | Err(ref err) => 27 | panic!("Error: '{}'", err), 28 | Ok(()) => () // unreachable in this example 29 | }; 30 | } 31 | 32 | fn read_write_loop(mut r: R, mut w: W) -> Result<(), Error> { 33 | loop { 34 | // {packet, 2} 35 | let _in_packet_size = r.read_u16::(); 36 | { 37 | let mut decoder = erl_ext::Decoder::new(&mut r); 38 | assert!(true == try!(decoder.read_prelude())); 39 | let term = try!(decoder.decode_term()); 40 | // incoming message should be simple `binary()` 41 | let response = match term { 42 | Eterm::Binary(bytes) => { 43 | bytes_to_json(bytes) 44 | }, 45 | _ => 46 | // {error, not_binary} 47 | Eterm::Tuple(vec!( 48 | Eterm::Atom(String::from("error")), 49 | Eterm::Atom(String::from("not_binary")) 50 | )) 51 | }; 52 | // Temp buffer to calculate response term size 53 | let mut wrtr = Vec::new(); 54 | { 55 | // encode response term 56 | let mut encoder = erl_ext::Encoder::new(&mut wrtr, 57 | true, true, true); 58 | try!(encoder.write_prelude()); 59 | try!(encoder.encode_term(response)); 60 | try!(encoder.flush()); 61 | } 62 | // response packet size 63 | let out_packet_size = wrtr.len() as u16; 64 | try!(w.write_u16::(out_packet_size)); 65 | // response term itself 66 | try!(w.write_all(wrtr.as_ref())); 67 | try!(w.flush()); 68 | } 69 | } 70 | } 71 | 72 | fn bytes_to_json(json_bytes: Vec) -> erl_ext::Eterm { 73 | // Vec to utf-8 String 74 | let json_string = match String::from_utf8(json_bytes) { 75 | Ok(s) => s, 76 | Err(_) => 77 | return Eterm::Tuple(vec!( 78 | Eterm::Atom(String::from("error")), 79 | Eterm::Atom(String::from("bad_utf8")))) 80 | }; 81 | // &str to json::Json 82 | let json_obj = match Json::from_str(json_string.as_ref()) { 83 | Ok(o) => o, 84 | Err(json::ParserError::SyntaxError(err_kind, line, col)) => { 85 | let err_str = json::error_str(err_kind); 86 | return Eterm::Tuple(vec!( 87 | Eterm::Atom(String::from("error")), 88 | Eterm::String(format!("{}; line:{}, col:{}", err_str, line, col).into_bytes()) 89 | )) 90 | }, 91 | Err(json::ParserError::IoError(err)) => 92 | return Eterm::Tuple(vec!( 93 | Eterm::Atom(String::from("error")), 94 | Eterm::String(format!("IoError: {}", err).into_bytes()) 95 | )) 96 | }; 97 | // json::Json to erl_ext::Eterm 98 | Eterm::Tuple(vec!(Eterm::Atom(String::from("ok")), json_to_erl(json_obj))) 99 | } 100 | 101 | fn json_to_erl(json: json::Json) -> erl_ext::Eterm { 102 | /* 103 | -type json() :: float() | binary() | bool() 104 | | [json()] | #{binary() => json()}. 105 | 106 | Json | Erlang 107 | -------+------------------ 108 | -0.23 | -0.23 // float() 109 | "wasd" | <<"wasd">> // binary() 110 | true | true // bool() 111 | [] | [] // [json()] 112 | {} | {} // #{binary() => json()} 113 | null | 'undefined' 114 | */ 115 | match json { 116 | Json::F64(num) => Eterm::Float(num), 117 | Json::I64(num) if (num <= (i32::max_value() as i64) && num >= (i32::min_value() as i64)) => 118 | Eterm::Integer(num as i32), 119 | Json::I64(num) => 120 | Eterm::BigNum(num.to_bigint().unwrap()), 121 | Json::U64(num) => { 122 | match num.to_i32() { 123 | Some(i32_num) => Eterm::Integer(i32_num), 124 | None => Eterm::BigNum(FromPrimitive::from_u64(num).unwrap()) 125 | } 126 | }, 127 | Json::String(string) => Eterm::Binary(string.into_bytes()), 128 | Json::Boolean(true) => Eterm::Atom(String::from("true")), 129 | Json::Boolean(false) => Eterm::Atom(String::from("false")), 130 | Json::Array(lst) => { 131 | let mut eterm_lst: erl_ext::List = 132 | lst.into_iter().map(json_to_erl).collect(); 133 | eterm_lst.push(Eterm::Nil); 134 | Eterm::List(eterm_lst) 135 | }, 136 | Json::Object(obj) => { 137 | let eterm_map: erl_ext::Map = 138 | obj.into_iter().map( 139 | |(k, v)| { 140 | let ek = Eterm::Binary(k.into_bytes()); 141 | let ev = json_to_erl(v); 142 | (ek, ev) 143 | }).collect(); 144 | Eterm::Map(eterm_map) 145 | }, 146 | Json::Null => Eterm::Atom(String::from("undefined")), 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /examples/parser.rs: -------------------------------------------------------------------------------- 1 | extern crate erl_ext; 2 | 3 | use std::convert::AsRef; 4 | 5 | use erl_ext::Decoder; 6 | use std::io; 7 | use std::env; 8 | use std::fs; 9 | use std::process::exit; 10 | 11 | fn main() { 12 | let mut args = env::args(); 13 | if args.len() < 2 { 14 | println!("Usage: parser "); 15 | exit(1); 16 | } 17 | let mut f: Box = match args.nth(1).unwrap().as_ref() { 18 | "-" => Box::new(io::stdin()), 19 | other => 20 | Box::new(fs::File::open(other).unwrap()), 21 | }; 22 | let mut decoder = Decoder::new(&mut f); 23 | match decoder.read_prelude() { 24 | Ok(false) => 25 | panic!("Invalid eterm!"), 26 | Err(err) => 27 | panic!("IoError: {}", err), 28 | _ => () 29 | } 30 | let term_opt = decoder.decode_term(); 31 | println!("{:?}", term_opt.unwrap()); 32 | } 33 | -------------------------------------------------------------------------------- /src/erl_ext.rs: -------------------------------------------------------------------------------- 1 | // See erts-6.1/doc/html/erl_ext_dist.html for binary format description. 2 | 3 | #![crate_type = "lib"] 4 | 5 | #![allow(non_camel_case_types)] // this is for enum ErlTermTag 6 | 7 | extern crate num; 8 | extern crate byteorder; 9 | 10 | use std::string::String; 11 | use std::vec::Vec; 12 | use std::io; 13 | use std::io::Read; 14 | use std::{error, fmt}; 15 | use std::mem::transmute; 16 | 17 | use num::FromPrimitive; 18 | use num::bigint; 19 | use std::num::ParseFloatError; 20 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 21 | 22 | 23 | #[derive(Debug, PartialEq)] 24 | pub enum ErlTermTag { 25 | // ATOM_CACHE_REF = 82, 26 | SMALL_INTEGER_EXT = 97, 27 | INTEGER_EXT = 98, 28 | FLOAT_EXT = 99, 29 | ATOM_EXT = 100, 30 | REFERENCE_EXT = 101, 31 | PORT_EXT = 102, 32 | PID_EXT = 103, 33 | SMALL_TUPLE_EXT = 104, 34 | LARGE_TUPLE_EXT = 105, 35 | MAP_EXT = 116, 36 | NIL_EXT = 106, 37 | STRING_EXT = 107, 38 | LIST_EXT = 108, 39 | BINARY_EXT = 109, 40 | SMALL_BIG_EXT = 110, 41 | LARGE_BIG_EXT = 111, 42 | NEW_REFERENCE_EXT = 114, 43 | SMALL_ATOM_EXT = 115, 44 | FUN_EXT = 117, 45 | NEW_FUN_EXT = 112, 46 | EXPORT_EXT = 113, 47 | BIT_BINARY_EXT = 77, 48 | NEW_FLOAT_EXT = 70, 49 | ATOM_UTF8_EXT = 118, 50 | SMALL_ATOM_UTF8_EXT = 119, 51 | } 52 | 53 | // https://www.reddit.com/r/rust/comments/36pgn9/integer_to_enum_after_removal_of_fromprimitive/ 54 | impl ErlTermTag { 55 | fn from_u8(t: u8) -> Option { 56 | if (t <= 119 && t >= 94) || (t == 77) || (t == 70) { 57 | Some(unsafe { transmute(t) }) 58 | } else { 59 | None 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug, PartialEq, Clone)] 65 | pub enum Eterm { 66 | SmallInteger(u8), // small_integer 67 | Integer(i32), // integer 68 | Float(f64), // float, new_float 69 | Atom(Atom), // atom, small_atom, atom_utf8, small_atom_utf8 70 | Reference(Reference), // reference, new_reference TODO 71 | Port(Port), // poort TODO 72 | Pid(Pid), // pid 73 | Tuple(Tuple), // small_tuple, large_tuple 74 | Map(Map), // map 75 | Nil, // nil 76 | String(Vec), // string; it's not String, because not guaranteed to be valid UTF-8 77 | List(List), // list 78 | Binary(Vec), // binary 79 | BigNum(bigint::BigInt), // small_big, large_big 80 | Fun(Fun), // fun TODO 81 | NewFun(NewFun), // new_fun TODO 82 | Export(Export), // export TODO 83 | BitBinary(BitBinary), // bit_binary; maybe implement .to_bitv() -> Bitv for it? TODO 84 | } 85 | pub type Atom = String; 86 | pub type Tuple = Vec; 87 | pub type Map = Vec<(Eterm, Eterm)>; // k-v pairs 88 | pub type List = Vec; 89 | 90 | #[derive(Debug, PartialEq, Clone)] 91 | pub struct Reference { 92 | node: Atom, 93 | id: Vec, 94 | creation: u8 95 | } 96 | #[derive(Debug, PartialEq, Clone)] 97 | pub struct Port { 98 | node: Atom, 99 | id: u32, 100 | creation: u8, 101 | } 102 | #[derive(Debug, PartialEq, Clone)] 103 | pub struct Pid { 104 | node: Atom, 105 | id: u32, 106 | serial: u32, // maybe [u8, ..4]? 107 | creation: u8, 108 | } 109 | #[derive(Debug, PartialEq, Clone)] 110 | pub struct Fun { 111 | pid: Pid, 112 | module: Atom, 113 | index: u32, 114 | uniq: u32, 115 | free_vars: Vec 116 | } 117 | #[derive(Debug, PartialEq, Clone)] 118 | pub struct NewFun { 119 | arity: u8, 120 | uniq: Vec, //[u8, ..16], 121 | index: u32, 122 | module: Atom, 123 | old_index: u32, 124 | old_uniq: u32, 125 | pid: Pid, 126 | free_vars: Vec 127 | } 128 | #[derive(Debug, PartialEq, Clone)] 129 | pub struct Export { 130 | module: Atom, 131 | function: Atom, 132 | arity: u8, 133 | } 134 | #[derive(Debug, PartialEq, Clone)] 135 | pub struct BitBinary { // bit_binary; maybe implement .to_bitv() -> Bitv for it? TODO 136 | bits: u8, 137 | data: Vec, 138 | } 139 | 140 | 141 | #[derive(Debug)] 142 | pub enum Error { 143 | UnexpectedTerm(ErlTermTag), // expected other term inside container 144 | UnknownTag(u8), // invalid term ID 145 | ByteorderUnexpectedEOF, // byteorder error 146 | BadFloat(ParseFloatError), // invalid float, encoded as string 147 | Io(io::Error), // io error 148 | } 149 | 150 | impl From for Error { 151 | fn from (err: io::Error) -> Error { Error::Io(err) } 152 | } 153 | impl From for Error { 154 | fn from (err: ParseFloatError) -> Error { Error::BadFloat(err) } 155 | } 156 | impl error::Error for Error { 157 | fn description(&self) -> &str { 158 | match *self { 159 | Error::BadFloat(_) => "Can't parse float, encoded as string", 160 | Error::UnexpectedTerm(_) => "Expected other term as a part of other complex term", 161 | Error::UnknownTag(_) => "Unknown term tag ID", 162 | Error::ByteorderUnexpectedEOF => "Not enough bytes to parse multibyte value", 163 | Error::Io(ref err) => error::Error::description(err), 164 | } 165 | } 166 | fn cause(&self) -> Option<&error::Error> { 167 | match *self { 168 | Error::Io(ref err) => err.cause(), 169 | _ => None 170 | } 171 | } 172 | } 173 | impl fmt::Display for Error { 174 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 175 | match *self { 176 | Error::BadFloat(ref val) => write!(f, "Bad float '{}'.", val), 177 | Error::UnexpectedTerm(ref val) => write!(f, "Got '{:?}', but expected other term", val), 178 | Error::UnknownTag(ref val) => write!(f, "Unknown term tag ID: '{}'", val), 179 | Error::ByteorderUnexpectedEOF => write!(f, "Not enough bytes to parse multibyte value"), 180 | Error::Io(ref err) => err.fmt(f), 181 | } 182 | } 183 | } 184 | 185 | pub type DecodeResult = Result; 186 | 187 | pub struct Decoder<'a, T: ?Sized + io::Read + 'a> { 188 | rdr: &'a mut T, 189 | } 190 | 191 | macro_rules! decode_some( 192 | ($e:expr, $($t:path),+ ) => ( 193 | { 194 | match try!($e._decode_tag()) { 195 | $( 196 | $t => 197 | try!($e.decode_concrete_term($t)), 198 | )+ 199 | bad => 200 | return Err(Error::UnexpectedTerm(bad)) 201 | } 202 | } 203 | ) 204 | ); 205 | 206 | impl<'a, T> Decoder<'a, T> where T: io::Read + 'a { 207 | pub fn new(rdr: &'a mut T) -> Decoder<'a, T> { 208 | Decoder{rdr: rdr} 209 | } 210 | pub fn read_prelude(&mut self) -> Result { 211 | Ok(131 == try!(self.rdr.read_u8())) 212 | } 213 | fn decode_small_integer(&mut self) -> DecodeResult { 214 | Ok(Eterm::SmallInteger(try!(self.rdr.read_u8()))) 215 | } 216 | fn decode_integer(&mut self) -> DecodeResult { 217 | Ok(Eterm::Integer(try!(self.rdr.read_i32::()))) 218 | } 219 | fn _read_exact(&mut self, len: u64) -> io::Result> { 220 | let mut buf = Vec::with_capacity(len as usize); 221 | try!(io::copy(&mut self.rdr.take(len), &mut buf)); 222 | Ok(buf) 223 | } 224 | fn _read_str(&mut self, len: usize) -> io::Result { 225 | let mut str_buf = String::with_capacity(len); 226 | try!(self.rdr.take(len as u64).read_to_string(&mut str_buf)); 227 | Ok(str_buf) 228 | } 229 | fn decode_float(&mut self) -> DecodeResult { 230 | let float_str = try!(self._read_str(31)); 231 | let num = try!(float_str.parse::()); 232 | Ok(Eterm::Float(num as f64)) 233 | } 234 | fn _decode_any_atom(&mut self) -> DecodeResult { 235 | match try!(self._decode_tag()) { 236 | ErlTermTag::ATOM_EXT | ErlTermTag::ATOM_UTF8_EXT => self.decode_atom(), 237 | ErlTermTag::SMALL_ATOM_EXT | ErlTermTag::SMALL_ATOM_UTF8_EXT => self.decode_small_atom(), 238 | tag => 239 | Err(Error::UnexpectedTerm(tag)) 240 | } 241 | } 242 | fn decode_atom(&mut self) -> DecodeResult { 243 | let len = try!(self.rdr.read_u16::()); 244 | let atom_str = try!(self._read_str(len as usize)); 245 | // XXX: data is in latin1 in case of ATOM_EXT 246 | Ok(Eterm::Atom(atom_str)) 247 | } 248 | fn decode_reference(&mut self) -> DecodeResult { 249 | let node = match try!(self._decode_any_atom()) { 250 | Eterm::Atom(a) => a, 251 | _ => unreachable!() 252 | }; 253 | let id = try!(self._read_exact(4)); 254 | let creation = try!(self.rdr.read_u8()); 255 | Ok(Eterm::Reference(Reference { 256 | node: node, 257 | id: id, 258 | creation: creation 259 | })) 260 | } 261 | fn decode_port(&mut self) -> DecodeResult { 262 | let node = match try!(self._decode_any_atom()) { 263 | Eterm::Atom(a) => a, 264 | _ => unreachable!() 265 | }; 266 | let id = try!(self.rdr.read_u32::()); 267 | let creation = try!(self.rdr.read_u8()); 268 | Ok(Eterm::Port(Port { 269 | node: node, 270 | id: id, 271 | creation: creation 272 | })) 273 | } 274 | fn decode_pid(&mut self) -> DecodeResult { 275 | let node = match try!(self._decode_any_atom()) { 276 | Eterm::Atom(a) => a, 277 | _ => unreachable!() 278 | }; 279 | let id = try!(self.rdr.read_u32::()); 280 | let serial = try!(self.rdr.read_u32::()); 281 | let creation = try!(self.rdr.read_u8()); 282 | Ok(Eterm::Pid(Pid { 283 | node: node, 284 | id: id, 285 | serial: serial, 286 | creation: creation 287 | })) 288 | } 289 | 290 | fn _decode_small_tuple_arity(&mut self) -> io::Result { 291 | self.rdr.read_u8() 292 | } 293 | fn decode_small_tuple(&mut self) -> DecodeResult { 294 | let arity = try!(self._decode_small_tuple_arity()); 295 | let mut tuple: Tuple = Vec::with_capacity(arity as usize); 296 | for _ in 0..arity { 297 | let term = try!(self.decode_term()); 298 | tuple.push(term) 299 | } 300 | Ok(Eterm::Tuple(tuple)) 301 | } 302 | 303 | fn _decode_large_tuple_arity(&mut self) -> io::Result { 304 | self.rdr.read_u32::() 305 | } 306 | fn decode_large_tuple(&mut self) -> DecodeResult { 307 | let arity = try!(self._decode_large_tuple_arity()); 308 | let mut tuple: Tuple = Vec::with_capacity(arity as usize); 309 | for _ in 0..arity { 310 | let term = try!(self.decode_term()); 311 | tuple.push(term) 312 | } 313 | Ok(Eterm::Tuple(tuple)) 314 | } 315 | 316 | fn _decode_map_arity(&mut self) -> io::Result { 317 | self.rdr.read_u32::() 318 | } 319 | fn decode_map(&mut self) -> DecodeResult { 320 | let arity: u32 = try!(self._decode_map_arity()); 321 | let mut map: Map = Vec::with_capacity(arity as usize); 322 | for _ in 0..arity { 323 | let key = try!(self.decode_term()); 324 | let val = try!(self.decode_term()); 325 | map.push((key, val)) 326 | } 327 | Ok(Eterm::Map(map)) 328 | } 329 | fn decode_nil(&mut self) -> DecodeResult { 330 | Ok(Eterm::Nil) 331 | } 332 | fn decode_string(&mut self) -> DecodeResult { 333 | let len = try!(self.rdr.read_u16::()); 334 | Ok(Eterm::String(try!(self._read_exact(len as u64)))) 335 | } 336 | 337 | fn _decode_list_len(&mut self) -> io::Result { 338 | self.rdr.read_u32::() 339 | } 340 | fn decode_list(&mut self) -> DecodeResult { 341 | // XXX: should we push Nil as last element or may ignore it? 342 | let len = try!(self._decode_list_len()) + 1; 343 | let mut list = Vec::with_capacity(len as usize); 344 | for _ in 0..len { 345 | let term = try!(self.decode_term()); 346 | list.push(term) 347 | } 348 | Ok(Eterm::List(list)) 349 | } 350 | fn decode_binary(&mut self) -> DecodeResult { 351 | let len = try!(self.rdr.read_u32::()); 352 | Ok(Eterm::Binary(try!(self._read_exact(len as u64)))) 353 | } 354 | fn _decode_big(&mut self, n: usize) -> DecodeResult { 355 | let sign_int = try!(self.rdr.read_u8()); 356 | let sign = if sign_int == 0 { 357 | bigint::Sign::Plus 358 | } else { 359 | bigint::Sign::Minus 360 | }; 361 | let bytes = try!(self._read_exact(n as u64)); 362 | Ok(Eterm::BigNum(bigint::BigInt::from_bytes_le(sign, bytes.as_ref()))) 363 | } 364 | fn decode_small_big(&mut self) -> DecodeResult { 365 | let n = try!(self.rdr.read_u8()); 366 | self._decode_big(n as usize) 367 | } 368 | fn decode_large_big(&mut self) -> DecodeResult { 369 | let n = try!(self.rdr.read_u32::()); 370 | self._decode_big(n as usize) 371 | } 372 | fn decode_new_reference(&mut self) -> DecodeResult { 373 | let len = try!(self.rdr.read_u16::()) as u64; 374 | let node = match try!(self._decode_any_atom()) { 375 | Eterm::Atom(a) => a, 376 | _ => unreachable!() 377 | }; 378 | let creation = try!(self.rdr.read_u8()); 379 | let id = try!(self._read_exact(4 * len)); 380 | Ok(Eterm::Reference(Reference { 381 | node: node, 382 | id: id, // here id should be Vec, but since it's not interpreted, leave it as is 383 | creation: creation 384 | })) 385 | } 386 | fn decode_small_atom(&mut self) -> DecodeResult { 387 | let len = try!(self.rdr.read_u8()); 388 | let atom_str = try!(self._read_str(len as usize)); 389 | // XXX: data is in latin1 in case of SMALL_ATOM_EXT 390 | Ok(Eterm::Atom(atom_str)) 391 | } 392 | fn decode_fun(&mut self) -> DecodeResult { 393 | let num_free = try!(self.rdr.read_u32::()); 394 | let pid = match decode_some!(self, ErlTermTag::PID_EXT) { 395 | Eterm::Pid(pid) => pid, 396 | _ => unreachable!() 397 | }; 398 | let module = match try!(self._decode_any_atom()) { 399 | Eterm::Atom(atom) => atom, 400 | _ => unreachable!() 401 | }; 402 | let index = match decode_some!(self, ErlTermTag::SMALL_INTEGER_EXT, ErlTermTag::INTEGER_EXT) { 403 | Eterm::SmallInteger(idx) => idx as u32, 404 | Eterm::Integer(idx) => idx as u32, 405 | _ => unreachable!() 406 | }; 407 | let uniq = match decode_some!(self, ErlTermTag::SMALL_INTEGER_EXT, ErlTermTag::INTEGER_EXT) { 408 | Eterm::SmallInteger(uq) => uq as u32, 409 | Eterm::Integer(uq) => uq as u32, 410 | _ => unreachable!() 411 | }; 412 | let mut free_vars = Vec::::with_capacity(num_free as usize); 413 | for _ in 0..num_free { 414 | free_vars.push(try!(self.decode_term())); 415 | } 416 | Ok(Eterm::Fun(Fun { 417 | pid: pid, 418 | module: module, 419 | index: index, 420 | uniq: uniq, 421 | free_vars: free_vars, 422 | })) 423 | } 424 | fn decode_new_fun(&mut self) -> DecodeResult { 425 | let _size = try!(self.rdr.read_u32::()); 426 | let arity = try!(self.rdr.read_u8()); 427 | let uniq = try!(self._read_exact(16)); 428 | let index = try!(self.rdr.read_u32::()); 429 | let num_free = try!(self.rdr.read_u32::()); 430 | 431 | let module = match try!(self._decode_any_atom()) { 432 | Eterm::Atom(atom) => atom, 433 | _ => unreachable!() 434 | }; 435 | let old_index = match decode_some!(self, ErlTermTag::SMALL_INTEGER_EXT, ErlTermTag::INTEGER_EXT) { 436 | Eterm::SmallInteger(idx) => idx as u32, 437 | Eterm::Integer(idx) => idx as u32, 438 | _ => unreachable!() 439 | }; 440 | let old_uniq = match decode_some!(self, ErlTermTag::SMALL_INTEGER_EXT, ErlTermTag::INTEGER_EXT) { 441 | Eterm::SmallInteger(uq) => uq as u32, 442 | Eterm::Integer(uq) => uq as u32, 443 | _ => unreachable!() 444 | }; 445 | let pid = match decode_some!(self, ErlTermTag::PID_EXT) { 446 | Eterm::Pid(pid) => pid, 447 | _ => unreachable!() 448 | }; 449 | let mut free_vars = Vec::::with_capacity(num_free as usize); 450 | for _ in 0..num_free { 451 | free_vars.push(try!(self.decode_term())); 452 | } 453 | Ok(Eterm::NewFun(NewFun { 454 | arity: arity, 455 | uniq: uniq, 456 | index: index, 457 | module: module, 458 | old_index: old_index, 459 | old_uniq: old_uniq, 460 | pid: pid, 461 | free_vars: free_vars, 462 | })) 463 | } 464 | fn decode_export(&mut self) -> DecodeResult { 465 | let module = match try!(self._decode_any_atom()) { 466 | Eterm::Atom(atom) => atom, 467 | _ => unreachable!() 468 | }; 469 | let function = match try!(self._decode_any_atom()) { 470 | Eterm::Atom(atom) => atom, 471 | _ => unreachable!() 472 | }; 473 | let arity = match decode_some!(self, ErlTermTag::SMALL_INTEGER_EXT) { 474 | Eterm::SmallInteger(uq) => uq, 475 | _ => unreachable!() 476 | }; 477 | Ok(Eterm::Export(Export { 478 | module: module, 479 | function: function, 480 | arity: arity, // arity > u8 possible in practice 481 | })) 482 | } 483 | fn decode_bit_binary(&mut self) -> DecodeResult { 484 | let len = try!(self.rdr.read_u32::()); 485 | let bits = try!(self.rdr.read_u8()); 486 | Ok(Eterm::BitBinary(BitBinary { 487 | bits: bits, 488 | data: try!(self._read_exact(len as u64)), 489 | })) 490 | } 491 | fn decode_new_float(&mut self) -> DecodeResult { 492 | Ok(Eterm::Float(try!(self.rdr.read_f64::()))) 493 | } 494 | 495 | 496 | fn _decode_tag(&mut self) -> Result { 497 | let int_tag = try!(self.rdr.read_u8()); 498 | let tag: Option = ErlTermTag::from_u8(int_tag); 499 | match tag { 500 | Some(t) => Ok(t), 501 | None => 502 | Err(Error::UnknownTag(int_tag)) 503 | } 504 | } 505 | pub fn decode_term(&mut self) -> DecodeResult { 506 | let tag = try!(self._decode_tag()); 507 | self.decode_concrete_term(tag) 508 | } 509 | fn decode_concrete_term(&mut self, tag: ErlTermTag) -> DecodeResult { 510 | match tag { 511 | ErlTermTag::SMALL_INTEGER_EXT => self.decode_small_integer(), 512 | ErlTermTag::INTEGER_EXT => self.decode_integer(), 513 | ErlTermTag::FLOAT_EXT => self.decode_float(), 514 | ErlTermTag::ATOM_EXT | ErlTermTag::ATOM_UTF8_EXT => self.decode_atom(), 515 | ErlTermTag::REFERENCE_EXT => self.decode_reference(), 516 | ErlTermTag::PORT_EXT => self.decode_port(), 517 | ErlTermTag::PID_EXT => self.decode_pid(), 518 | ErlTermTag::SMALL_TUPLE_EXT => self.decode_small_tuple(), 519 | ErlTermTag::LARGE_TUPLE_EXT => self.decode_large_tuple(), 520 | ErlTermTag::MAP_EXT => self.decode_map(), 521 | ErlTermTag::NIL_EXT => self.decode_nil(), 522 | ErlTermTag::STRING_EXT => self.decode_string(), 523 | ErlTermTag::LIST_EXT => self.decode_list(), 524 | ErlTermTag::BINARY_EXT => self.decode_binary(), 525 | ErlTermTag::SMALL_BIG_EXT => self.decode_small_big(), 526 | ErlTermTag::LARGE_BIG_EXT => self.decode_large_big(), 527 | ErlTermTag::NEW_REFERENCE_EXT => self.decode_new_reference(), 528 | ErlTermTag::SMALL_ATOM_EXT | ErlTermTag::SMALL_ATOM_UTF8_EXT => self.decode_small_atom(), 529 | ErlTermTag::FUN_EXT => self.decode_fun(), 530 | ErlTermTag::NEW_FUN_EXT => self.decode_new_fun(), 531 | ErlTermTag::EXPORT_EXT => self.decode_export(), 532 | ErlTermTag::BIT_BINARY_EXT => self.decode_bit_binary(), 533 | ErlTermTag::NEW_FLOAT_EXT => self.decode_new_float(), 534 | } 535 | } 536 | } 537 | 538 | pub type EncodeResult = Result<(), Error>; // TODO: maybe return num bytes written? 539 | 540 | pub struct Encoder<'a> { 541 | wrtr: &'a mut (io::Write + 'a), 542 | use_utf8_atoms: bool, 543 | use_small_atoms: bool, 544 | fair_new_fun: bool, 545 | //use_new_float: bool, (>=R11B) 546 | } 547 | 548 | 549 | impl<'a> Encoder<'a> { 550 | // TODO: asserts for overflows 551 | 552 | pub fn new(writer: &'a mut io::Write, utf8_atoms: bool, small_atoms: bool, fair_new_fun: bool) -> Encoder<'a> { 553 | Encoder{wrtr: writer, 554 | use_utf8_atoms: utf8_atoms, 555 | use_small_atoms: small_atoms, 556 | fair_new_fun: fair_new_fun} 557 | } 558 | 559 | pub fn write_prelude(&mut self) -> EncodeResult { 560 | self.wrtr.write_u8(131).map_err(From::from) 561 | } 562 | 563 | pub fn flush(&mut self) -> io::Result<()> { 564 | self.wrtr.flush() 565 | } 566 | 567 | fn encode_small_integer(&mut self, num: u8) -> EncodeResult { 568 | self.wrtr.write_u8(num).map_err(From::from) 569 | } 570 | fn encode_integer(&mut self, num: i32) -> EncodeResult { 571 | self.wrtr.write_i32::(num).map_err(From::from) 572 | } 573 | fn encode_new_float(&mut self, num: f64) -> EncodeResult { 574 | self.wrtr.write_f64::(num).map_err(From::from) 575 | } 576 | 577 | fn _encode_str(&mut self, s: String) -> EncodeResult { 578 | self.wrtr.write_all(s.as_bytes()).map_err(From::from) 579 | } 580 | fn encode_atom(&mut self, atom: Atom) -> EncodeResult { 581 | try!(self.wrtr.write_u16::(atom.len() as u16)); 582 | self._encode_str(atom) 583 | } 584 | fn encode_small_atom(&mut self, atom: Atom) -> EncodeResult { 585 | try!(self.wrtr.write_u8(atom.len() as u8)); 586 | self._encode_str(atom) 587 | } 588 | fn encode_new_reference(&mut self, reference: Reference) -> EncodeResult { 589 | let len = reference.id.len() / 4; // todo: ensure proper rounding, maybe (id.len() / 4) + if (id.len() % 4) == 0 {0} else {1} 590 | try!(self.wrtr.write_u16::(len as u16)); 591 | try!(self.encode_term(Eterm::Atom(reference.node))); 592 | try!(self.wrtr.write_u8(reference.creation)); 593 | self.wrtr.write_all(reference.id.as_ref()).map_err(From::from) 594 | } 595 | fn encode_port(&mut self, port: Port) -> EncodeResult { 596 | try!(self.encode_term(Eterm::Atom(port.node))); 597 | try!(self.wrtr.write_u32::(port.id)); 598 | self.wrtr.write_u8(port.creation).map_err(From::from) 599 | } 600 | fn encode_pid(&mut self, pid: Pid) -> EncodeResult { 601 | try!(self.encode_term(Eterm::Atom(pid.node))); 602 | try!(self.wrtr.write_u32::(pid.id)); 603 | try!(self.wrtr.write_u32::(pid.serial)); 604 | self.wrtr.write_u8(pid.creation).map_err(From::from) 605 | } 606 | 607 | fn encode_small_tuple(&mut self, tuple: Vec) -> EncodeResult { 608 | try!(self.wrtr.write_u8(tuple.len() as u8)); 609 | for term in tuple.into_iter() { 610 | try!(self.encode_term(term)); 611 | } 612 | Ok(()) 613 | } 614 | fn encode_large_tuple(&mut self, tuple: Vec) -> EncodeResult { 615 | try!(self.wrtr.write_u32::(tuple.len() as u32)); 616 | for term in tuple.into_iter() { 617 | try!(self.encode_term(term)); 618 | } 619 | Ok(()) 620 | } 621 | fn encode_map(&mut self, map: Map) -> EncodeResult { 622 | try!(self.wrtr.write_u32::(map.len() as u32)); 623 | for (key, val) in map.into_iter() { 624 | try!(self.encode_term(key)); 625 | try!(self.encode_term(val)); 626 | } 627 | Ok(()) 628 | } 629 | fn encode_string(&mut self, s: Vec) -> EncodeResult { 630 | try!(self.wrtr.write_u16::(s.len() as u16)); 631 | self.wrtr.write_all(s.as_ref()).map_err(From::from) 632 | } 633 | fn encode_list(&mut self, list: Vec) -> EncodeResult { 634 | try!(self.wrtr.write_u32::((list.len() - 1) as u32)); 635 | for term in list.into_iter() { 636 | try!(self.encode_term(term)); 637 | } 638 | Ok(()) 639 | } 640 | 641 | fn encode_binary(&mut self, bin: Vec) -> EncodeResult { 642 | try!(self.wrtr.write_u32::(bin.len() as u32)); 643 | self.wrtr.write_all(bin.as_ref()).map_err(From::from) 644 | } 645 | 646 | fn _encode_big(&mut self, sign: bigint::Sign, bytes: Vec) -> EncodeResult { 647 | try!(self.wrtr.write_u8(match sign { 648 | bigint::Sign::Plus => 0, 649 | bigint::Sign::Minus => 1, 650 | _ => panic!("Invalid bignum sign") 651 | })); 652 | self.wrtr.write_all(bytes.as_ref()).map_err(From::from) 653 | } 654 | fn encode_small_big(&mut self, sign: bigint::Sign, bytes: Vec) -> EncodeResult { 655 | try!(self.wrtr.write_u8(bytes.len() as u8)); 656 | self._encode_big(sign, bytes) 657 | } 658 | fn encode_large_big(&mut self, sign: bigint::Sign, bytes: Vec) -> EncodeResult { 659 | try!(self.wrtr.write_u32::(bytes.len() as u32)); 660 | self._encode_big(sign, bytes) 661 | } 662 | 663 | fn encode_fun(&mut self, fun: Fun) -> EncodeResult { 664 | try!(self.wrtr.write_u32::(fun.free_vars.len() as u32)); 665 | try!(self.encode_term(Eterm::Pid(fun.pid))); 666 | try!(self.encode_term(Eterm::Atom(fun.module))); 667 | try!(self.encode_term( 668 | if fun.index <= 255 { Eterm::SmallInteger(fun.index as u8) } 669 | else { Eterm::Integer(fun.index as i32) })); 670 | try!(self.encode_term( 671 | if fun.uniq <= 255 { Eterm::SmallInteger(fun.uniq as u8) } 672 | else { Eterm::Integer(fun.uniq as i32) })); 673 | for term in fun.free_vars.into_iter() { 674 | try!(self.encode_term(term)); 675 | } 676 | Ok(()) 677 | } 678 | fn _encode_new_fun(&mut self, fun: NewFun) -> EncodeResult { 679 | try!(self.wrtr.write_u8(fun.arity)); 680 | assert!(fun.uniq.len() == 16); 681 | try!(self.wrtr.write_all(fun.uniq.as_ref())); 682 | try!(self.wrtr.write_u32::(fun.index)); 683 | try!(self.wrtr.write_u32::(fun.free_vars.len() as u32)); 684 | try!(self.encode_term(Eterm::Atom(fun.module))); 685 | 686 | let old_index_term = if fun.old_index <= 255 { 687 | Eterm::SmallInteger(fun.old_index as u8) 688 | } else { 689 | Eterm::Integer(fun.old_index as i32) 690 | }; 691 | try!(self.encode_term(old_index_term)); 692 | 693 | let old_uniq_term = if fun.old_uniq <= 255 { 694 | Eterm::SmallInteger(fun.old_uniq as u8) 695 | } else { 696 | Eterm::Integer(fun.old_uniq as i32) 697 | }; 698 | try!(self.encode_term(old_uniq_term)); 699 | 700 | try!(self.encode_term(Eterm::Pid(fun.pid))); 701 | 702 | for term in fun.free_vars.into_iter() { 703 | try!(self.encode_term(term)); 704 | } 705 | Ok(()) 706 | } 707 | fn encode_new_fun(&mut self, fun: NewFun) -> EncodeResult { 708 | // We serialize to temporary memory buffer to calculate encoded term size. 709 | // Erlang itself in 'term_to_binary' does back-patching (see 710 | // erts/emulator/beam/external.c#enc_term_int 'ENC_PATCH_FUN_SIZE'), but 711 | // at the same time, in 'binary_to_term' this size u32 is just skipped! 712 | // So, we make this configurable: do fair encoding or cheating with 713 | // fake zero size. 714 | if self.fair_new_fun { 715 | let mut temp = Vec::new(); 716 | { 717 | let mut encoder = Encoder::new(&mut temp, self.use_utf8_atoms, self.use_small_atoms, self.fair_new_fun); 718 | try!(encoder._encode_new_fun(fun)); 719 | } 720 | let size = temp.len(); 721 | // +4 is size itself 722 | try!(self.wrtr.write_u32::(4 + size as u32)); 723 | self.wrtr.write_all(temp.as_ref()).map_err(From::from) 724 | } else { 725 | // cheating - write 0, since binary_to_term don't use this (at least now, in 17.0) 726 | try!(self.wrtr.write_u32::(0)); 727 | self._encode_new_fun(fun) 728 | } 729 | } 730 | fn encode_export(&mut self, export: Export) -> EncodeResult { 731 | try!(self.encode_term(Eterm::Atom(export.module))); 732 | try!(self.encode_term(Eterm::Atom(export.function))); 733 | self.encode_term(Eterm::SmallInteger(export.arity)) 734 | } 735 | fn encode_bit_binary(&mut self, bit_bin: BitBinary) -> EncodeResult { 736 | try!(self.wrtr.write_u32::(bit_bin.data.len() as u32)); 737 | try!(self.wrtr.write_u8(bit_bin.bits)); 738 | self.wrtr.write_all(bit_bin.data.as_ref()).map_err(From::from) 739 | } 740 | 741 | fn _encode_tag(&mut self, tag: ErlTermTag) -> EncodeResult { 742 | let int_tag = tag as u8; 743 | self.wrtr.write_u8(int_tag).map_err(From::from) 744 | } 745 | pub fn encode_term(&mut self, term: Eterm) -> EncodeResult { 746 | // XXX: maybe use &Eterm, not just Eterm? 747 | match term { 748 | Eterm::SmallInteger(num) => { 749 | try!(self._encode_tag(ErlTermTag::SMALL_INTEGER_EXT)); 750 | self.encode_small_integer(num) 751 | }, 752 | Eterm::Integer(num) => { 753 | try!(self._encode_tag(ErlTermTag::INTEGER_EXT)); 754 | self.encode_integer(num) 755 | }, 756 | Eterm::Float(num) => { 757 | try!(self._encode_tag(ErlTermTag::NEW_FLOAT_EXT)); 758 | self.encode_new_float(num) 759 | }, 760 | Eterm::Atom(atom) => { 761 | let use_utf8 = self.use_utf8_atoms; 762 | let use_small = self.use_small_atoms; 763 | if (atom.len() <= 255) && use_small { 764 | try!(self._encode_tag(if use_utf8 {ErlTermTag::SMALL_ATOM_UTF8_EXT} else {ErlTermTag::SMALL_ATOM_EXT})); 765 | self.encode_small_atom(atom) 766 | } else { 767 | try!(self._encode_tag(if use_utf8 {ErlTermTag::ATOM_UTF8_EXT} else {ErlTermTag::ATOM_EXT})); 768 | self.encode_atom(atom) 769 | } 770 | }, 771 | Eterm::Reference(reference) => { 772 | try!(self._encode_tag(ErlTermTag::NEW_REFERENCE_EXT)); 773 | self.encode_new_reference(reference) 774 | }, 775 | Eterm::Port(port) => { 776 | try!(self._encode_tag(ErlTermTag::PORT_EXT)); 777 | self.encode_port(port) 778 | }, 779 | Eterm::Pid(pid) => { 780 | try!(self._encode_tag(ErlTermTag::PID_EXT)); 781 | self.encode_pid(pid) 782 | }, 783 | Eterm::Tuple(tuple) => { 784 | if tuple.len() <= 255 { 785 | try!(self._encode_tag(ErlTermTag::SMALL_TUPLE_EXT)); 786 | self.encode_small_tuple(tuple) 787 | } else { 788 | try!(self._encode_tag(ErlTermTag::LARGE_TUPLE_EXT)); 789 | self.encode_large_tuple(tuple) 790 | } 791 | }, 792 | Eterm::Map(map) => { 793 | try!(self._encode_tag(ErlTermTag::MAP_EXT)); 794 | self.encode_map(map) 795 | }, 796 | Eterm::Nil => 797 | self._encode_tag(ErlTermTag::NIL_EXT), 798 | Eterm::String(s) => { 799 | try!(self._encode_tag(ErlTermTag::STRING_EXT)); 800 | self.encode_string(s) 801 | }, 802 | Eterm::List(list) => { 803 | try!(self._encode_tag(ErlTermTag::LIST_EXT)); 804 | self.encode_list(list) 805 | }, 806 | Eterm::Binary(bin) => { 807 | try!(self._encode_tag(ErlTermTag::BINARY_EXT)); 808 | self.encode_binary(bin) 809 | }, 810 | Eterm::BigNum(num) => { 811 | let (sign, bytes) = num.to_bytes_le(); 812 | if bytes.len() < 255 { 813 | try!(self._encode_tag(ErlTermTag::SMALL_BIG_EXT)); 814 | self.encode_small_big(sign, bytes) 815 | } else { 816 | try!(self._encode_tag(ErlTermTag::LARGE_BIG_EXT)); 817 | self.encode_large_big(sign, bytes) 818 | } 819 | }, 820 | Eterm::Fun(fun) => { 821 | try!(self._encode_tag(ErlTermTag::FUN_EXT)); 822 | self.encode_fun(fun) 823 | }, 824 | Eterm::NewFun(new_fun) => { 825 | try!(self._encode_tag(ErlTermTag::NEW_FUN_EXT)); 826 | self.encode_new_fun(new_fun) 827 | }, 828 | Eterm::Export(export) => { 829 | try!(self._encode_tag(ErlTermTag::EXPORT_EXT)); 830 | self.encode_export(export) 831 | }, 832 | Eterm::BitBinary(bit_binary) => { 833 | try!(self._encode_tag(ErlTermTag::BIT_BINARY_EXT)); 834 | self.encode_bit_binary(bit_binary) 835 | } 836 | } 837 | } 838 | } 839 | 840 | #[cfg(test)] 841 | mod test { 842 | use super::{Eterm,Encoder,Decoder,DecodeResult,Error}; 843 | use std::io; 844 | use std::iter::FromIterator; 845 | use num::bigint; 846 | use num::traits::FromPrimitive; 847 | 848 | fn term_to_binary(term: Eterm) -> Result, Error> { 849 | let mut writer = Vec::new(); 850 | { 851 | let mut encoder = Encoder::new(&mut writer, false, false, true); 852 | try!(encoder.write_prelude()); 853 | try!(encoder.encode_term(term)); 854 | } 855 | Ok(writer) 856 | } 857 | fn binary_to_term(binary: Vec) -> DecodeResult { 858 | let mut reader = io::Cursor::new(binary); 859 | let mut decoder = Decoder::new(&mut reader); 860 | assert!(true == try!(decoder.read_prelude())); 861 | decoder.decode_term() 862 | } 863 | 864 | macro_rules! codec_eq ( 865 | ($inp:expr) => { 866 | { 867 | let orig = $inp; 868 | let teleported = binary_to_term(term_to_binary(orig.clone()).unwrap()).unwrap(); 869 | assert_eq!(orig, teleported); 870 | } 871 | }; 872 | ); 873 | 874 | 875 | #[test] 876 | fn codec_small_integer() { 877 | codec_eq!(Eterm::SmallInteger(0)); 878 | codec_eq!(Eterm::SmallInteger(255)); 879 | } 880 | 881 | #[test] 882 | fn codec_integer() { 883 | codec_eq!(Eterm::Integer(-2147483647)); 884 | codec_eq!(Eterm::Integer(-1)); 885 | codec_eq!(Eterm::Integer(256)); 886 | codec_eq!(Eterm::Integer(2147483647)); 887 | } 888 | 889 | #[test] 890 | fn codec_float() { 891 | codec_eq!(Eterm::Float(-111111.11)); 892 | codec_eq!(Eterm::Float(0.0)); 893 | codec_eq!(Eterm::Float(111111.11)); 894 | } 895 | 896 | #[test] 897 | fn codec_atom() { 898 | codec_eq!(Eterm::Atom(String::from("hello_world"))); 899 | } 900 | 901 | #[test] 902 | fn codec_reference() { 903 | let node = String::from("my_node"); 904 | let reference = Eterm::Reference(super::Reference { 905 | node: node, 906 | id: vec!(0, 1, 2, 3), 907 | creation: 0 908 | }); 909 | codec_eq!(reference); 910 | } 911 | 912 | #[test] 913 | fn codec_port() { 914 | codec_eq!(Eterm::Port(super::Port { 915 | node: String::from("my_node"), 916 | id: 4294967295, 917 | creation: 0 918 | })); 919 | } 920 | 921 | #[test] 922 | fn codec_pid() { 923 | codec_eq!(Eterm::Pid(super::Pid { 924 | node: String::from("my_node"), 925 | id: 4294967295, 926 | serial: 1, 927 | creation: 0 928 | })); 929 | } 930 | 931 | #[test] 932 | fn codec_tuple() { 933 | codec_eq!(Eterm::Tuple(vec!( 934 | Eterm::SmallInteger(0), 935 | Eterm::Nil 936 | ))); 937 | } 938 | 939 | #[test] 940 | fn codec_map() { 941 | // #{0 => {}, 0.0 => -1} 942 | let mut map: super::Map = Vec::new(); 943 | map.push((Eterm::SmallInteger(0), Eterm::Tuple(vec!()))); 944 | map.push((Eterm::Float(0.0), Eterm::Integer(-1))); 945 | let emap = Eterm::Map(map); 946 | codec_eq!(emap); 947 | } 948 | 949 | #[test] 950 | fn codec_nil() { 951 | codec_eq!(Eterm::Nil); 952 | } 953 | 954 | #[test] 955 | fn codec_string() { 956 | // Vec::from_fn(255, |i| i as u8); 957 | let vec: Vec = FromIterator::from_iter(0..(255 as u8)); 958 | codec_eq!(Eterm::String(vec)); 959 | } 960 | 961 | #[test] 962 | fn codec_list() { 963 | codec_eq!(Eterm::List(vec!( 964 | Eterm::Tuple(vec!()), 965 | Eterm::SmallInteger(1), 966 | Eterm::Nil, 967 | ))); 968 | } 969 | 970 | #[test] 971 | fn codec_binary() { 972 | // Vec::from_fn(1024, |i| (i % 255) as u8) 973 | let mut vec: Vec = Vec::with_capacity(1024); 974 | for i in 0..1024 { 975 | vec.push((i % 255) as u8); 976 | } 977 | codec_eq!(Eterm::Binary(vec)); 978 | } 979 | 980 | #[test] 981 | fn codec_big_num() { 982 | codec_eq!(Eterm::BigNum(bigint::BigInt::new(bigint::Sign::Plus, vec!(1, 1, 1, 1, 1, 1)))); 983 | codec_eq!(Eterm::BigNum(bigint::BigInt::new(bigint::Sign::Minus, vec!(1, 1, 1, 1, 1, 1)))); 984 | codec_eq!(Eterm::BigNum(FromPrimitive::from_i64(i64::max_value()).unwrap())); 985 | let vec: Vec = FromIterator::from_iter(0..(256 as u32)); 986 | codec_eq!(Eterm::BigNum(bigint::BigInt::new(bigint::Sign::Plus, vec))); 987 | } 988 | 989 | #[test] 990 | fn codec_fun() { 991 | let pid = super::Pid { 992 | node: String::from("my_node"), 993 | id: 4294967295, 994 | serial: 1, 995 | creation: 0 996 | }; 997 | codec_eq!(Eterm::Fun(super::Fun { 998 | pid: pid, 999 | module: String::from("my_mod"), 1000 | index: 1, 1001 | uniq: u32::max_value(), 1002 | free_vars: vec!(Eterm::Nil) 1003 | })); 1004 | } 1005 | 1006 | #[test] 1007 | fn codec_new_fun() { 1008 | let pid = super::Pid { 1009 | node: String::from("my_node"), 1010 | id: u32::max_value(), 1011 | serial: 1, 1012 | creation: 0 1013 | }; 1014 | let vec: Vec = FromIterator::from_iter(0..(16 as u8)); 1015 | codec_eq!(Eterm::NewFun(super::NewFun { 1016 | arity: 128, // :-) 1017 | uniq: vec, //Vec::from_fn(16, |i| i as u8), 1018 | index: u32::max_value(), 1019 | module: String::from("my_mod"), 1020 | old_index: u32::max_value(), 1021 | old_uniq: u32::max_value(), 1022 | pid: pid, 1023 | free_vars: vec!(Eterm::Nil) 1024 | })); 1025 | } 1026 | 1027 | #[test] 1028 | fn codec_export() { 1029 | codec_eq!(Eterm::Export(super::Export { 1030 | module: String::from("my_mod"), 1031 | function: String::from("my_fun"), 1032 | arity: u8::max_value() 1033 | })); 1034 | } 1035 | 1036 | #[test] 1037 | fn codec_bit_binary() { 1038 | codec_eq!(Eterm::BitBinary(super::BitBinary { 1039 | bits: 1, 1040 | data: vec!(255, 255) 1041 | })); 1042 | } 1043 | } 1044 | -------------------------------------------------------------------------------- /tests/codec_test.rs: -------------------------------------------------------------------------------- 1 | extern crate erl_ext; 2 | 3 | use erl_ext::{Encoder,Decoder}; 4 | use std::io; 5 | use std::io::{Write,Read}; 6 | use std::fs; 7 | use std::process; 8 | use std::path::Path; 9 | 10 | #[test] 11 | fn main() { 12 | // generate tests/data/*.bin 13 | match process::Command::new("escript").arg("tests/term_gen.erl").spawn() { 14 | Ok(_) => (), 15 | Err(ioerr) => { 16 | (writeln!( 17 | &mut io::stderr(), 18 | "{}:{} [warn] Failed to launch escript - '{}'. Is Erlang installed?", 19 | file!(), line!(), ioerr)).unwrap(); 20 | return 21 | } 22 | }; 23 | // run decode-encode cycle and compare source and resulting binaries 24 | let data_dir = Path::new("tests/data"); 25 | for path in (fs::read_dir(&data_dir) 26 | .unwrap() 27 | .map(|de| de.unwrap().path()) 28 | .filter(|ref p| p.extension().unwrap().to_str() == Some("bin"))) { 29 | 30 | let mut in_f = fs::File::open(&path).unwrap(); 31 | let mut src = Vec::new(); 32 | in_f.read_to_end(&mut src).unwrap(); 33 | let mut rdr = io::Cursor::new(src); 34 | 35 | let dst = Vec::new(); 36 | let mut wrtr = io::BufWriter::new(dst); 37 | 38 | { 39 | let mut decoder = Decoder::new(&mut rdr); 40 | assert!(true == decoder.read_prelude().unwrap(), 41 | "{}: bad prelude", path.display()); 42 | let term = decoder.decode_term().unwrap(); 43 | 44 | let mut encoder = Encoder::new(&mut wrtr, false, false, true); 45 | encoder.write_prelude().unwrap(); 46 | encoder.encode_term(term).unwrap(); 47 | encoder.flush().unwrap(); 48 | } 49 | assert!(wrtr.get_ref() == rdr.get_ref(), 50 | "{}: Before and After isn't equal", path.display()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/data/wtf.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seriyps/rust-erl-ext/6a20b9fa1a6e88964a2318403d719569b44844c6/tests/data/wtf.txt -------------------------------------------------------------------------------- /tests/term_gen.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | -mode(compile). 3 | %-module(term_gen). 4 | -export([main/1]). 5 | 6 | main([]) -> 7 | DataDir = filename:join("tests", "data"), 8 | ok = filelib:ensure_dir(DataDir), 9 | W = fun(Name, Term) -> 10 | ok = file:write_file( 11 | filename:join(DataDir, Name ++ ".bin"), 12 | term_to_binary(Term)) 13 | end, 14 | lists:foreach(fun({N, T}) -> W(N, T) end, primitive_terms()), 15 | EmptyContainers = lists:map(fun({_, Gen}) -> Gen([]) end, container_terms()), 16 | lists:foreach(fun({Name, Gen}) -> 17 | Term = Gen(EmptyContainers 18 | ++ [T || {_, T} <- primitive_terms()]), 19 | W(Name, Term) 20 | end, container_terms()). 21 | 22 | primitive_terms() -> 23 | [{"SmallInt-min", 1}, 24 | {"SmallInt-max", 255}, 25 | {"Int-min", 256}, 26 | {"Int-max", 2147483647}, 27 | {"Int-neg-max", -2147483647}, 28 | {"Int-neg-min", -1}, 29 | {"Float-zero", 0.0}, 30 | {"Float-neg", -11111111111.1}, 31 | {"Float-pos", 11111111111.1}, 32 | {"SmallBig-min", 2147483648}, 33 | {"SmallBig-neg-min", -2147483649}, 34 | {"LargeBig", (fun() -> 35 | N = trunc(math:pow(255, 128)), 36 | N * N 37 | end)()}, 38 | %% {"LargeBig 1", begin N = trunc(math:pow(128, 128)), N * N * N * N * N ... end}, 39 | %% {"LargeBig 2", -2147483649}, 40 | {"Reference", make_ref()}, 41 | %% {"SmallAtom", some_atom}, % don't supported by term_to_binary 42 | {"Atom", list_to_atom(string:copies("a", 255))}, 43 | {"Pid", self()}, 44 | {"Port", (fun() -> 45 | try lists:last(erlang:ports()) 46 | catch _:_ -> 47 | erlang:open_port({spawn, "/bin/true"}, []) 48 | end 49 | end)()}, 50 | {"Nil", []}, 51 | {"Binary", <<0, 1, 2, 33, 44, 55, 66, 77, 88, 99, 110, 220, 230, 240, 255>>}, 52 | {"BitBinary", <<0, 7:1>>}, 53 | {"Fun", fun(A) -> A end}, 54 | {"Export", fun erlang:term_to_binary/1}]. 55 | 56 | container_terms() -> 57 | [{"List", fun(Terms) -> Terms end}, 58 | {"Tuple", fun(Terms) -> list_to_tuple(Terms) end}, %small & large tuples 59 | {"Map", fun(Terms) -> maps:from_list([{T, T} || T <- Terms]) end}, 60 | {"String", fun(_) -> lists:seq(0, 255) end}]. 61 | --------------------------------------------------------------------------------