├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── Setup.hs ├── app ├── Cauterize │ └── ErlangRef │ │ └── Options.hs └── Main.hs ├── caut-erl-ref.cabal ├── crucible └── crucible.erl ├── priv ├── erlang_test.scm └── erlang_test.spec ├── rebar.config ├── rebar3 ├── run_crucible.sh ├── src ├── Cauterize │ └── ErlangRef │ │ └── Generate.hs ├── cauterize.app.src └── cauterize.erl ├── stack.yaml └── test ├── Spec.hs ├── cauterize_schema_test.erl └── primitive_test.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work 2 | _build 3 | ebin 4 | *.swp 5 | *.swo 6 | test/erlang_test.erl 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright John Van Enk (c) 2016 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of John Van Enk nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: compile cover test crucible 2 | 3 | all: compile 4 | 5 | compile: 6 | ./rebar3 compile 7 | stack build 8 | 9 | cover: test 10 | ./rebar3 cover 11 | 12 | test: eunit crucible 13 | 14 | eunit: generate 15 | ./rebar3 do eunit --dir=test, cover 16 | 17 | generate: 18 | stack exec caut-erl-ref -- -s priv/erlang_test.spec -o test 19 | 20 | crucible: 21 | ./run_crucible.sh 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ) _____ _ _ 3 | /(( / __ \ | | (_) 4 | (_))\ | / \/ __ _ _ _| |_ ___ _ __ _ _______ 5 | _)((_) | | / _` | | | | __/ _ \ '__| |_ / _ \ 6 | \ ^ / | \__/\ (_| | |_| | || __/ | | |/ / __/ 7 | \_/ \____/\__,_|\__,_|\__\___|_| |_/___\___| 8 | ``` 9 | 10 | # Cauterize for Erlang reference implementation 11 | 12 | This is an Erlang implementation of a [Cauterize](https://github.com/cauterize-tools/cauterize) encoder/decoder. It differs from several of the other implementations in that it is implemented as a library that consumes an Erlang representation of the Cauterize specification. The code generation tool `caut-erl-ref` generates an Erlang module that contains the Erlang representation of the specification as well as an `encode/1` and a `decode/2` function that call into the `cauterize.erl` library. 13 | 14 | ## How to use it 15 | 16 | Firstly, you will need to run `make`. This will build both the Haskell code-generation tool and the Erlang library. Once `make` suceeds, you will be able to generate Erlang modules from Cauterize specification files: 17 | 18 | ``` 19 | stack exec caut-erl-ref -- -s somefile.spec -o myproject/src 20 | ``` 21 | 22 | In the target directory, you will now have an Erlang module. The name of the module will reflect the Cauterize schema name, so it might require some editing or some single quotes to be callable from Erlang. 23 | 24 | If you want to see a sample generation, you can use `make generate`, which will generate a module in the `test` directory. 25 | 26 | Because the Erlang representation of Cauterize structures is self-describing, you do not need to pass the field name to the module when using `encode/1`, but you MUST supply the correct field name when using `decode/2` because Cauterize data itself does not contain that information. Typically a Cauterize schema will have a 'top level type' that holds the other types inside it, and this will be the type you pass to `decode/2`. 27 | 28 | # Erlang representation 29 | 30 | The Erlang representation of Cauterize schemas is fashioned out of lists and tuples. It is symmetrical (you can pass the result of a decode to an encode and it will result in the same data), it is [key-value-coded](https://github.com/etrepum/kvc) and it tries to be self-descriptive. Below are examples of each of the non-primitive Cauterize types in Erlang syntax. Everything is always wrapped in a top-level list and the outermost structure is always described by name, nested structures are not because they are unambigious. 31 | 32 | ## Ranges 33 | 34 | Given a [Cauterize Range](https://github.com/cauterize-tools/cauterize/blob/master/README.md#ranges) like this, you can construct an instance of it in Erlang like this: 35 | 36 | ```erlang 37 | [{some_range, 1005}] 38 | ``` 39 | 40 | ## Arrays 41 | 42 | Given a [Cauterize Array](https://github.com/cauterize-tools/cauterize/blob/master/README.md#array) like this, you can construct an instance of it in Erlang like this: 43 | 44 | ```erlang 45 | [{mac, [1, 2, 3, 4, 5, 6, 7, 8]}] 46 | ``` 47 | ## Vector 48 | 49 | Given a [Cauterize Vector](https://github.com/cauterize-tools/cauterize/blob/master/README.md#vector) like this, you can construct an instance of it in Erlang like this: 50 | 51 | ```erlang 52 | [{byte_buffer_4k, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}] 53 | ``` 54 | ## Enumeration 55 | 56 | Given a [Cauterize Enumeration](https://github.com/cauterize-tools/cauterize/blob/master/README.md#enumeration) like this, you can construct an instance of it in Erlang like this: 57 | 58 | ```erlang 59 | {days_of_week, monday}] 60 | ``` 61 | ## Records 62 | 63 | Given a [Cauterize Record](https://github.com/cauterize-tools/cauterize/blob/master/README.md#record) like this, you can construct an instance of it in Erlang like this: 64 | 65 | ```erlang 66 | [{person, [{age, 33}, {height, 163}, {likes_cats, false}]}] 67 | ``` 68 | ## Union 69 | 70 | Given a [Cauterize Union](https://github.com/cauterize-tools/cauterize/blob/master/README.md#union) like this, you can construct an instance of it in Erlang like this: 71 | 72 | ```erlang 73 | [{request, [{set_key, [{name, "power"}, {value, 9001}]}]}] 74 | ``` 75 | 76 | Empty fields are considered to be 'true': 77 | 78 | ```erlang 79 | [{request, [{get_key, true}]}] 80 | ``` 81 | 82 | Unions have what seems like a superflous list wrapping the 2-tuple, but it needed to make KVC traversal work. This gets inconvienent for encoding though, so shorthand forms are supported: 83 | 84 | ```erlang 85 | [{request, {set_key, [{name, "power"}, {value, 9001}]}}] 86 | 87 | [{request, {get_key, true}}] 88 | [{request, get_key}] 89 | ``` 90 | 91 | Note that `decode` will always return the KVC form, because it makes it much easier to traverse the structure of complicated specifications. 92 | 93 | ## Combination 94 | 95 | Given a [Cauterize Combination](https://github.com/cauterize-tools/cauterize/blob/master/README.md#combination) like this, you can construct an instance of it in Erlang like this: 96 | 97 | ```erlang 98 | [{sensed, [{ambient_temp, 24}, {air_pressure, 5000}]}] 99 | ``` 100 | 101 | # Extensions 102 | 103 | ## char vectors/arrays 104 | 105 | If you edit the generated Erlang module to change the primitive type of a vector or an array from `u8` to `char`, they will decode as binaries, not lists of u8s. This can sometimes be handy. 106 | 107 | # Key value coding 108 | 109 | erl-caut-ref tries very hard to use key-value coded structures to make it easy to access the fields without cumbersome pattern matching and destructuring code. KVC can help here: 110 | 111 | ```erlang 112 | 1> X = [{a_combination, [{a, 223372036854775808}, {b, -99}, {c, [{a,[{z, [10, 11, 12, 13]}, {a, -1}, {d, [{a, 1}, {d, [{a, 0}, {b, 1}]}]}]}] }]}]. 113 | [{a_combination,[{a,223372036854775808}, 114 | {b,-99}, 115 | {c,[{a,[{z,"\n\v\f\r"}, 116 | {a,-1}, 117 | {d,[{a,1},{d,[{a,0},{b,1}]}]}]}]}]}] 118 | 2> kvc:path([a_combination, a], X). 119 | 223372036854775808 120 | 3> kvc:path([a_combination, c, a], X). 121 | [{z,"\n\v\f\r"},{a,-1},{d,[{a,1},{d,[{a,0},{b,1}]}]}] 122 | 4> kvc:path([a_combination, c, a, d, d, b], X). 123 | 1 124 | ``` 125 | 126 | KVC is sort of like a lite version of XPATH or something. 127 | 128 | # Further Examples 129 | 130 | To see some more elaborate encoding/decoding examples, take a look at `test/cauterize_schema_test.erl`. 131 | 132 | # Errors 133 | 134 | When an encode or a decode fails, you will get back an `{error, Reason}` tuple. This will contain information about which field failed to encode/decode, why it failed and, for decode only, a stack of the elements decoded up to the point the failure occured. This can be helpful when you want to understand what a truncated Cauterize structure is, but given Cauterize's lack of self-description, this may be misleading if the beginning of the structure is corrupted or missing. 135 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Cauterize/ErlangRef/Options.hs: -------------------------------------------------------------------------------- 1 | module Cauterize.ErlangRef.Options 2 | ( runWithOptions 3 | , ErlangOpts(..) 4 | ) where 5 | 6 | import Options.Applicative 7 | 8 | runWithOptions :: (ErlangOpts -> IO ()) -> IO () 9 | runWithOptions fn = execParser options >>= fn 10 | 11 | data ErlangOpts = ErlangOpts 12 | { specFile :: FilePath 13 | , outputDirectory :: FilePath 14 | } deriving (Show) 15 | 16 | options :: ParserInfo ErlangOpts 17 | options = info (optParser <**> helper) 18 | ( fullDesc 19 | <> progDesc "Process Cauterize schema files." 20 | ) 21 | 22 | optParser :: Parser ErlangOpts 23 | optParser = ErlangOpts 24 | <$> strOption 25 | ( long "spec" 26 | <> short 's' 27 | <> metavar "FILE_PATH" 28 | <> help "Input Cauterize specification file." 29 | ) 30 | <*> strOption 31 | ( long "output" 32 | <> short 'o' 33 | <> metavar "DIRECTORY_PATH" 34 | <> help "Output Cauterize directory." 35 | ) 36 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Cauterize.ErlangRef.Options 4 | import Cauterize.ErlangRef.Generate 5 | import System.Directory 6 | import System.FilePath.Posix 7 | import Data.Text (unpack) 8 | 9 | import qualified Cauterize.Specification as S 10 | 11 | main :: IO () 12 | main = runWithOptions caut2erlang 13 | 14 | caut2erlang :: ErlangOpts -> IO () 15 | caut2erlang ErlangOpts { specFile = sf, outputDirectory = od } = createGuard od $ do 16 | spec <- loadSpec sf 17 | let baseName = unpack $ S.specName spec 18 | generateDynamicFiles od baseName spec 19 | where 20 | loadSpec :: FilePath -> IO S.Specification 21 | loadSpec p = do 22 | s <- S.parseSpecificationFromFile p 23 | case s of 24 | Left e -> error $ show e 25 | Right s' -> return s' 26 | 27 | createGuard :: FilePath -> IO a -> IO a 28 | createGuard out go = do 29 | fe <- doesFileExist out 30 | de <- doesDirectoryExist out 31 | 32 | if fe 33 | then error $ "Error: " ++ out ++ " is a file." 34 | else if de 35 | then go 36 | else createDirectory out >> go 37 | 38 | generateDynamicFiles :: FilePath -> String -> S.Specification -> IO () 39 | generateDynamicFiles path baseName spec = do 40 | writeFile (path `combine` (baseName ++ ".erl")) (erlFileFromSpec spec) 41 | -------------------------------------------------------------------------------- /caut-erl-ref.cabal: -------------------------------------------------------------------------------- 1 | name: caut-erl-ref 2 | version: 0.1.0.0 3 | synopsis: Initial project template from stack 4 | description: Please see README.md 5 | homepage: http://github.com/sw17ch/caut-erl-ref#readme 6 | license: BSD3 7 | license-file: LICENSE 8 | author: John Van Enk 9 | maintainer: sw17ch@gmail.com 10 | copyright: 2016 Author Here 11 | category: Web 12 | build-type: Simple 13 | -- extra-source-files: 14 | cabal-version: >=1.10 15 | 16 | library 17 | hs-source-dirs: src 18 | exposed-modules: Cauterize.ErlangRef.Generate 19 | build-depends: base >= 4.7 && < 5 20 | , cauterize >= 0.1.0.0 21 | , text 22 | , interpolate 23 | default-language: Haskell2010 24 | 25 | executable caut-erl-ref 26 | hs-source-dirs: app 27 | main-is: Main.hs 28 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 29 | other-modules: Cauterize.ErlangRef.Options 30 | build-depends: base 31 | , caut-erl-ref 32 | , cauterize >= 0.1.0.0 33 | , optparse-applicative 34 | , directory 35 | , filepath 36 | , text 37 | , bytestring 38 | default-language: Haskell2010 39 | 40 | test-suite caut-erl-ref-test 41 | type: exitcode-stdio-1.0 42 | hs-source-dirs: test 43 | main-is: Spec.hs 44 | build-depends: base 45 | , caut-erl-ref 46 | ghc-options: -threaded -rtsopts -with-rtsopts=-N 47 | default-language: Haskell2010 48 | 49 | source-repository head 50 | type: git 51 | location: https://github.com/sw17ch/caut-erl-ref 52 | -------------------------------------------------------------------------------- /crucible/crucible.erl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env escript 2 | 3 | main([]) -> 4 | %% make sure cauterize.beam is in the code path 5 | true = code:add_pathz(filename:dirname(escript:script_name()) 6 | ++ "/../_build/default/lib/cauterize/ebin"), 7 | [Beam] = filelib:wildcard("*.beam"), 8 | %io:format(standard_error, "beam ~p~n", [Beam]), 9 | ModuleStr = filename:basename(Beam, ".beam"), 10 | {module, Module} = code:load_abs(ModuleStr), 11 | Fingerprints = Module:fingerprints(), 12 | TagSize = Module:tag_size(), 13 | MaxSize = Module:max_size(), 14 | %io:format(standard_error, "tag size ~p max size ~p~n", [TagSize, MaxSize]), 15 | %io:format(standard_error, "fingerprints ~p~n", [Fingerprints]), 16 | LengthBytes = calc_length_bytes(MaxSize), 17 | X0 = io:get_chars("", LengthBytes), 18 | ByteCount = LengthBytes*8, 19 | <> = list_to_binary(X0), 20 | %io:format(standard_error, "length is ~p ~p~n", [list_to_binary(X0), Len]), 21 | X1 = io:get_chars("", TagSize), 22 | <> = list_to_binary(X1), 23 | %io:format(standard_error, "fingerprint ~p~n", [Fingerprint]), 24 | Type = lookup_fingerprint(Fingerprint, Fingerprints), 25 | %io:format(standard_error, "type ~p~n", [Type]), 26 | X2 = io:get_chars("", Len), 27 | Payload = list_to_binary(X2), 28 | %io:format(standard_error, "Payload ~p~n", [Payload]), 29 | case Module:decode(Payload, Type) of 30 | {ok, Decoded} -> 31 | %io:format(standard_error, "Decoded ~p~n", [Decoded]), 32 | %% decoded OK, no trailing bytes 33 | {ok, Encoded0} = Module:encode(Decoded), 34 | Encoded = list_to_binary(Encoded0), 35 | EncodedLen = byte_size(Encoded), 36 | Output = <>, 37 | %io:fwrite(binary_to_list(Output)), 38 | Stdout = erlang:open_port({fd, 0, 1}, [out, binary, stream]), 39 | port_command(Stdout, Output), 40 | %io:format(standard_error, "wrote ~p to stdout", [Output]), 41 | port_close(Stdout); 42 | {ok, _Decoded, _Rem} -> 43 | io:fwrite("error"), 44 | %io:format(standard_error, "WARNING trailing bytes found ~p~n", [Rem]); 45 | halt(1); 46 | {error, _Reason} -> 47 | io:fwrite("error"), 48 | %io:format(standard_error, "ERROR decode failed ~p~n", [Reason]) 49 | halt(1) 50 | end. 51 | 52 | calc_length_bytes(MaxSize) -> 53 | case MaxSize of 54 | X when X < 16#ff -> 55 | 1; 56 | X when X < 16#ffff -> 57 | 2; 58 | X when X < 16#ffffffff -> 59 | 4 60 | end. 61 | 62 | lookup_fingerprint(FP, FPs) -> 63 | Len = byte_size(FP), 64 | [X] = [X || {X, Y} <- FPs, binary:part(Y, {0, Len}) == FP ], 65 | X. 66 | -------------------------------------------------------------------------------- /priv/erlang_test.scm: -------------------------------------------------------------------------------- 1 | (name "erlang_test") 2 | (version "0.0.1") 3 | (type number64 synonym s64) 4 | (type unsigned8 synonym u8) 5 | (type somearray array number64 8) 6 | (type somevector vector number64 8) 7 | (type arecord record 8 | (fields 9 | (field z somevector) 10 | (field a s8) 11 | (field d brecord))) 12 | (type brecord record 13 | (fields 14 | (field a s8) 15 | (field d crecord))) 16 | (type crecord record 17 | (fields 18 | (field a s8) 19 | (field b s8))) 20 | (type a_union union 21 | (fields 22 | (field a arecord) 23 | (field b brecord) 24 | (field c s8) 25 | (field d number64) 26 | (empty e))) 27 | (type a_combination combination 28 | (fields 29 | (field a number64) 30 | (field b s8) 31 | (field c a_union) 32 | (empty d))) 33 | (type someenum enumeration 34 | (values 35 | red 36 | green 37 | blue)) 38 | (type primitivetest union 39 | (fields 40 | (field u8 u8) 41 | (field u16 u16) 42 | (field u32 u32) 43 | (field u64 u64) 44 | (field s8 s8) 45 | (field s16 s16) 46 | (field s32 s32) 47 | (field s64 s64) 48 | (field bool bool) 49 | (field f32 f32) 50 | (field f64 f64))) 51 | (type some_range range 1000 2010) 52 | (type field_enum enumeration 53 | (values 54 | somearray 55 | somevector 56 | arecord)) 57 | (type header vector field_enum 4) 58 | -------------------------------------------------------------------------------- /priv/erlang_test.spec: -------------------------------------------------------------------------------- 1 | (name "erlang_test") 2 | (version "0.0.1") 3 | (fingerprint 43a81c33f4ace23fe0431dbd941203021de9195a) 4 | (size 1 80) 5 | (depth 6) 6 | (typelength 2) 7 | (lengthtag t1) 8 | (type 9 | unsigned8 10 | synonym 11 | (fingerprint e3e070dd78628d7aae3047878ccd6993423bb5bf) 12 | (size 1 1) 13 | (depth 2) 14 | u8) 15 | (type 16 | someenum 17 | enumeration 18 | (fingerprint e40274f6c896c769fa274bdfb53e6374428eb905) 19 | (size 1 1) 20 | (depth 1) 21 | t1 22 | (values (value red 0) (value green 1) (value blue 2))) 23 | (type 24 | some_range 25 | range 26 | (fingerprint d829f56fd4ee577222e9da4282f632581cee336d) 27 | (size 2 2) 28 | (depth 1) 29 | 1000 30 | 2010 31 | t2 32 | u16) 33 | (type 34 | primitivetest 35 | union 36 | (fingerprint 86837a082cb7501791d1ff52f200a2127ffce010) 37 | (size 2 9) 38 | (depth 2) 39 | t1 40 | (fields 41 | (field u8 0 u8) 42 | (field u16 1 u16) 43 | (field u32 2 u32) 44 | (field u64 3 u64) 45 | (field s8 4 s8) 46 | (field s16 5 s16) 47 | (field s32 6 s32) 48 | (field s64 7 s64) 49 | (field bool 8 bool) 50 | (field f32 9 f32) 51 | (field f64 10 f64))) 52 | (type 53 | number64 54 | synonym 55 | (fingerprint a3db7c02b1f69e0897c4aeda37b2f5bc9b6aaa45) 56 | (size 8 8) 57 | (depth 2) 58 | s64) 59 | (type 60 | somearray 61 | array 62 | (fingerprint 1a9a2bf194ebd8e5287225bcbbabb4f592800ee5) 63 | (size 64 64) 64 | (depth 3) 65 | number64 66 | 8) 67 | (type 68 | somevector 69 | vector 70 | (fingerprint 39b9e121f4c0d497424d602dbb955a29b0a6e1e3) 71 | (size 1 65) 72 | (depth 3) 73 | number64 74 | 8 75 | t1) 76 | (type 77 | field_enum 78 | enumeration 79 | (fingerprint 49d934d221faf807f218f6a50a648e65be1acc8a) 80 | (size 1 1) 81 | (depth 1) 82 | t1 83 | (values (value somearray 0) (value somevector 1) (value arecord 2))) 84 | (type 85 | header 86 | vector 87 | (fingerprint a93f7513a7ddb2be400141d2b7bfe6cb4b8f369f) 88 | (size 1 5) 89 | (depth 2) 90 | field_enum 91 | 4 92 | t1) 93 | (type 94 | crecord 95 | record 96 | (fingerprint bfd1b7a978b8202e3b73c600d6e3fb2da6fec5ad) 97 | (size 2 2) 98 | (depth 2) 99 | (fields (field a 0 s8) (field b 1 s8))) 100 | (type 101 | brecord 102 | record 103 | (fingerprint 16a32de194b2eb3cc2167cc5ff96738c61d1eeda) 104 | (size 3 3) 105 | (depth 3) 106 | (fields (field a 0 s8) (field d 1 crecord))) 107 | (type 108 | arecord 109 | record 110 | (fingerprint 9975e8a7ba429c1cd49ea366575b7c6c3e863cd8) 111 | (size 5 69) 112 | (depth 4) 113 | (fields (field z 0 somevector) (field a 1 s8) (field d 2 brecord))) 114 | (type 115 | a_union 116 | union 117 | (fingerprint d02d91ba842586a4e53f52892a4cd9a8794f1a00) 118 | (size 1 70) 119 | (depth 5) 120 | t1 121 | (fields 122 | (field a 0 arecord) 123 | (field b 1 brecord) 124 | (field c 2 s8) 125 | (field d 3 number64) 126 | (empty e 4))) 127 | (type 128 | a_combination 129 | combination 130 | (fingerprint 71f00aeba696a2d73e26bcfdd6ebd9dac3a6cfba) 131 | (size 1 80) 132 | (depth 6) 133 | t1 134 | (fields (field a 0 number64) (field b 1 s8) (field c 2 a_union) (empty d 3))) 135 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {cover_enabled, true}. 2 | -------------------------------------------------------------------------------- /rebar3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cauterize-tools/caut-erl-ref/37e9e5bde3ea82c23d46877473b38d3f2364f4c8/rebar3 -------------------------------------------------------------------------------- /run_crucible.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -rf crucible-* 4 | 5 | stack exec crucible -- tester \ 6 | --build-cmd="stack exec caut-erl-ref -- --spec=%s --output=src" \ 7 | --build-cmd="erlc src/*.erl" \ 8 | --run-cmd="tee data.bin | ../../crucible/crucible.erl" \ 9 | --schema-count=10 \ 10 | --instance-count=100 \ 11 | --type-count=10 \ 12 | --enc-size=1048576 \ 13 | -------------------------------------------------------------------------------- /src/Cauterize/ErlangRef/Generate.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes #-} 2 | module Cauterize.ErlangRef.Generate 3 | ( erlFileFromSpec 4 | ) where 5 | 6 | import qualified Cauterize.CommonTypes as C 7 | import qualified Cauterize.Specification as S 8 | import qualified Cauterize.Hash as H 9 | 10 | import Data.List (intercalate) 11 | import Data.Text (unpack) 12 | import Data.String.Interpolate 13 | import Data.String.Interpolate.Util 14 | import Numeric (showHex) 15 | 16 | erlFileFromSpec :: S.Specification -> String 17 | erlFileFromSpec s = unindent [i| 18 | -module(#{ln}). 19 | -export([decode/2, encode/1, fingerprints/0, tag_size/0, max_size/0]). 20 | 21 | %% specification for Cauterize schema #{ln} 22 | 23 | -define(CAUT_SPEC_#{ln}, [ 24 | #{descriptorList} 25 | ]). 26 | 27 | -define(CAUT_SPEC_#{ln}_tag_size, #{sts}). 28 | -define(CAUT_SPEC_#{ln}_max_size, #{sms}). 29 | -define(CAUT_SPEC_#{ln}_fp_lookup, [ 30 | #{typeFps} 31 | ]). 32 | 33 | -spec decode(Bin :: binary(), Which :: atom()) -> cauterize:decode_result(). 34 | decode(Bin, Which) -> 35 | cauterize:decode(Bin, Which, ?CAUT_SPEC_#{ln}). 36 | 37 | -spec encode([{TypeName :: atom(), Value :: any()}, ...]) -> cauterize:encode_result(). 38 | encode(Inst) -> 39 | cauterize:encode(Inst, ?CAUT_SPEC_#{ln}). 40 | 41 | -spec fingerprints() -> [{Type :: atom(), Fingerprint :: binary()},...]. 42 | fingerprints() -> 43 | ?CAUT_SPEC_#{ln}_fp_lookup. 44 | 45 | -spec tag_size() -> pos_integer(). 46 | tag_size() -> 47 | ?CAUT_SPEC_#{ln}_tag_size. 48 | 49 | -spec max_size() -> pos_integer(). 50 | max_size() -> 51 | ?CAUT_SPEC_#{ln}_max_size. 52 | |] 53 | where 54 | ln = unpack (S.specName s) 55 | types = S.specTypes s 56 | sts = S.specTypeLength s 57 | sms = C.sizeMax . S.specSize $ s 58 | descriptorList = intercalate ",\n" $ map descriptor types 59 | typeFps = intercalate ",\n" $ map fingerprint types 60 | fingerprint t = 61 | let n = ident2str ident 62 | ident = S.typeName t 63 | fp = formatFp (S.typeFingerprint t) 64 | in [i| {#{n}, <<#{fp}>>}|] 65 | 66 | descriptor t = 67 | let n = ident2str ident 68 | ident = S.typeName t 69 | tps = typeToPrimString t 70 | typeDesc = typeDescToString (S.typeDesc t) 71 | in [i| {descriptor, #{tps}, #{n}, #{typeDesc}}|] 72 | 73 | 74 | formatFp :: H.Hash -> String 75 | formatFp f = 76 | let bs = H.hashToBytes f 77 | showByte n = case showHex n "" of 78 | [a] -> ['1', '6', '#', '0', a] 79 | [a,b] -> ['1', '6', '#', a, b] 80 | _ -> error "formatFp: should be impossible" 81 | in intercalate "," (map showByte bs) 82 | 83 | ident2str :: C.Identifier -> String 84 | ident2str = unpack . C.unIdentifier 85 | 86 | typeToPrimString :: S.Type -> String 87 | typeToPrimString S.Type { S.typeDesc = d } = n 88 | where 89 | n = case d of 90 | S.Synonym {} -> "synonym" 91 | S.Range {} -> "range" 92 | S.Array {} -> "array" 93 | S.Vector {} -> "vector" 94 | S.Enumeration {} -> "enumeration" 95 | S.Record {} -> "record" 96 | S.Combination {} -> "combination" 97 | S.Union {} -> "union" 98 | 99 | tag2str :: C.Tag -> String 100 | tag2str C.T1 = "tag8"; 101 | tag2str C.T2 = "tag16"; 102 | tag2str C.T4 = "tag32"; 103 | tag2str C.T8 = "tag64"; 104 | 105 | prim2str :: C.Prim -> String 106 | prim2str C.PU8 = "u8" 107 | prim2str C.PU16 = "u16" 108 | prim2str C.PU32 = "u32" 109 | prim2str C.PU64 = "u64" 110 | prim2str C.PS8 = "s8" 111 | prim2str C.PS16 = "s16" 112 | prim2str C.PS32 = "s32" 113 | prim2str C.PS64 = "s64" 114 | prim2str C.PF32 = "f32" 115 | prim2str C.PF64 = "f64" 116 | prim2str C.PBool = "bool" 117 | 118 | typeDescToString :: S.TypeDesc -> String 119 | typeDescToString d = 120 | case d of 121 | S.Synonym { S.synonymRef = r } 122 | -> [i|#{ident2str r}|] 123 | S.Range { S.rangeOffset = ro, S.rangeLength = rl, S.rangeTag = rt } 124 | -> [i|{ #{ro}, #{rl}, #{tag2str rt} }|] 125 | S.Array { S.arrayRef = r, S.arrayLength = al } 126 | -> [i|{ #{ident2str r}, #{al} }|] 127 | S.Vector { S.vectorRef = r, S.vectorLength = vl, S.vectorTag = vt } 128 | -> [i|{ #{ident2str r}, #{vl}, #{tag2str vt} }|] 129 | S.Enumeration { S.enumerationTag = et, S.enumerationValues = evs } 130 | -> let evsStr = intercalate ", " $ map e2tup evs 131 | e2tup (S.EnumVal v ix) = [i|{#{ident2str v}, #{ix}}|] 132 | in [i|{ #{tag2str et}, [#{evsStr}] }|] 133 | S.Record { S.recordFields = rs } 134 | -> let fieldsStr = intercalate ", " $ map f2str rs 135 | in [i|[#{fieldsStr}]|] 136 | S.Combination { S.combinationFields = cf, S.combinationTag = ct } 137 | -> let fieldsStr = intercalate ", " $ map f2str cf 138 | in [i|{ #{tag2str ct}, [#{fieldsStr}] }|] 139 | S.Union { S.unionFields = uf, S.unionTag = ut } 140 | -> let fieldsStr = intercalate ", " $ map f2str uf 141 | in [i|{ #{tag2str ut}, [#{fieldsStr}] }|] 142 | 143 | f2str :: S.Field -> String 144 | f2str S.EmptyField { S.fieldName = n, S.fieldIndex = ix } = 145 | [i|{ empty, #{ident2str n}, #{ix}}|] 146 | f2str S.DataField { S.fieldName = n, S.fieldIndex = ix, S.fieldRef = r } = 147 | [i|{ data, #{ident2str n}, #{ix}, #{ident2str r}}|] 148 | -------------------------------------------------------------------------------- /src/cauterize.app.src: -------------------------------------------------------------------------------- 1 | %% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- 2 | %% ex: ft=erlang sw=4 et 3 | {application, cauterize, 4 | [ 5 | {description, "Cauterize decoder for Erlang"}, 6 | {vsn, "1.0.0"}, 7 | {modules, 8 | [ 9 | cauterize 10 | ] 11 | }, 12 | {applications, [ 13 | kernel, 14 | stdlib 15 | ]}, 16 | {registered, []} 17 | ] 18 | }. 19 | -------------------------------------------------------------------------------- /src/cauterize.erl: -------------------------------------------------------------------------------- 1 | -module(cauterize). 2 | -export([decode/3, encode/2]). 3 | 4 | -type cauterize_tag() :: tag8 | tag16 | tag32 | tag64. 5 | -type cauterize_primitive() :: u8 | u16 | u32 | u64 | s8 | s16 | s32 | s64 | f32 | f64 | bool. 6 | -type cauterize_type() :: cauterize_primitive() | 'range' | 'array' | 'vector' | 'enumeration' | 'record' | 'union' | 'combination'. 7 | 8 | -type cauterize_synonym() :: {'descriptor', 'synonym', Name :: atom(), Type :: cauterize_type()}. 9 | -type cauterize_enumeration() :: {'descriptor', 'enumeration', Name :: atom(), {Tag :: cauterize_tag(), [{Field:: atom(), Index :: non_neg_integer()}, ...]}}. 10 | -type cauterize_range() :: {'descriptor', 'range', Name :: atom(), {Min :: integer(), Max :: integer(), Tag :: cauterize_tag()}}. 11 | -type cauterize_union() :: {'descriptor', 'union', Name :: atom(), {Tag :: cauterize_tag(), [{'data', FieldName :: atom(), Index :: non_neg_integer(), FieldType :: atom()} | {'empty', FieldName :: atom(), Index :: non_neg_integer()}, ...]}}. 12 | -type cauterize_combination() :: {'descriptor', 'combination', Name :: atom(), {Tag :: cauterize_tag(), [{'data', FieldName :: atom(), Index :: non_neg_integer(), FieldType :: atom()} | {'empty', FieldName :: atom(), Index :: non_neg_integer()}, ...]}}. 13 | -type cauterize_array() :: {'descriptor', 'array', Name :: atom(), { Type :: atom(), Length :: pos_integer()}}. 14 | -type cauterize_vector() :: {'descriptor', 'vector', Name :: atom(), { Type :: atom(), MaxLength :: pos_integer(), Tag :: cauterize_tag()}}. 15 | -type cauterize_record() :: {'descriptor', 'record', Name :: atom(), [{'data', FieldName :: atom(), Index :: non_neg_integer(), Type :: atom()}, ...]}. 16 | 17 | -type cauterize_spec() :: list( 18 | cauterize_synonym() | 19 | cauterize_enumeration() | 20 | cauterize_range() | 21 | cauterize_union() | 22 | cauterize_array() | 23 | cauterize_vector() | 24 | cauterize_record() | 25 | cauterize_combination()). 26 | 27 | -type decode_reason() :: {'unexpected_end_of_input', Type :: atom(), Name :: atom(), Remainder :: binary()} | 28 | {'oversize_vector', Name :: atom(), Length :: pos_integer(), MaxLen :: pos_integer()} | 29 | {'invalid_range_value', Name :: atom(), Value :: any(), Length :: pos_integer()} | 30 | {'out_of_range_enumeration_value', Name :: atom(), Index :: non_neg_integer()} | 31 | {'bad_union_index', Name :: atom(), Index :: non_neg_integer()}. 32 | 33 | -type encode_reason() :: {'oversize_vector', Name :: atom(), Size :: pos_integer(), MaxSize :: pos_integer()} | 34 | {'incorrect_array_size', Name :: atom(), non_neg_integer(), Size :: pos_integer()} | 35 | {'invalid_range_value', Name :: atom(), Value :: non_neg_integer()} | 36 | {'unknown_enumeration_field', Name :: atom(), Field :: atom()} | 37 | {'missing_record_field', Name :: atom(), Field :: atom()} | 38 | {'unknown_union_member', Name :: atom(), Field :: atom()} | 39 | {'data_supplied_for_empty_union_member', Name :: atom(), Field :: atom()}. 40 | 41 | -type decode_stack() :: [] | [{TypeName :: atom(), [{FieldName :: atom(), Value :: any()}]}]. 42 | 43 | -type decode_result() :: {'ok', any()} | {'ok', any(), binary()} | {'error', {decode_reason(), decode_stack()}}. 44 | -type encode_result() :: {'ok', binary()} | {'error', encode_reason()}. 45 | 46 | -export_type([encode_result/0, decode_result/0]). 47 | 48 | -spec decode(Bin :: binary(), Name :: atom() | [atom(),...], Spec :: cauterize_spec()) -> decode_result(). 49 | decode(<<>>, _, _) -> 50 | {error, {no_input, []}}; 51 | decode(Bin, Name, Spec) when is_atom(Name) -> 52 | decode(Bin, [Name], Spec); 53 | decode(Bin, Names, Spec) -> 54 | try decode_int(Bin, Names, [], Spec) of 55 | {Decoded, Rem} -> {ok, Decoded, Rem}; 56 | R -> {ok, R} 57 | catch 58 | throw:{Reason, [TopType|[Stack]]} -> 59 | {error, {Reason, [{TopType, fixup_stacktrace(Stack)}]}}; 60 | throw:{Reason, [TopType|Stack]} -> 61 | R = fixup_stacktrace(Stack), 62 | {error, {Reason, [{TopType, lists:flatten(R)}]}} 63 | 64 | end. 65 | 66 | fixup_stacktrace([]) -> []; 67 | fixup_stacktrace(X) when is_list(X) -> 68 | replace(lists:reverse(fixup_stacktrace_int(lists:reverse(X))), '?'); 69 | fixup_stacktrace(X) -> 70 | replace(X, '?'). 71 | 72 | fixup_stacktrace_int([A]) -> 73 | [replace(A, '?')]; 74 | fixup_stacktrace_int([A,B|C]) -> 75 | case last(B) of 76 | '__duct_tape__' -> 77 | fixup_stacktrace_int([replace(B, fixup_stacktrace(A))|C]); 78 | _ -> 79 | R = fixup_stacktrace_int([B|C]), 80 | [A|R] 81 | end; 82 | fixup_stacktrace_int(X) -> 83 | X. 84 | 85 | last(X) when is_list(X) -> 86 | last(lists:last(X)); 87 | last(X) when is_tuple(X) -> 88 | last(element(tuple_size(X), X)); 89 | last(X) -> 90 | X. 91 | 92 | replace(X, Rep) when is_list(X) -> 93 | R = lists:droplast(X) ++ [replace(lists:last(X), Rep)], 94 | R; 95 | replace(X, Rep) when is_tuple(X) -> 96 | R = setelement(tuple_size(X), X, replace(element(tuple_size(X), X), Rep)), 97 | R; 98 | replace('__duct_tape__', Rep) -> 99 | Rep; 100 | replace(X, _Rep) -> 101 | X. 102 | 103 | decode_int(<<>>, _, Acc, _) -> 104 | lists:reverse(Acc); 105 | decode_int(Rem, [], Acc, _) -> 106 | {lists:reverse(Acc), Rem}; 107 | decode_int(Bin, [Name|T], Acc, Spec) -> 108 | {descriptor, Prototype, Name, Desc} = lookup_type(Name, Spec), 109 | {Decoded,Rem} = decode_internal(Bin, Prototype, Name, Desc, Spec, [Name]), 110 | decode_int(Rem, T, [{Name, Decoded}|Acc], Spec). 111 | 112 | decode_internal(<<>>, Type, Name, _, _, Stack) -> 113 | throw({{unexpected_end_of_input, Type, Name, <<>>}, lists:reverse(Stack)}); 114 | decode_internal(<>, primitive, _, u8, _Spec, _Stack) -> {Val,Rem}; 115 | decode_internal(<>, primitive, _, u16, _Spec, _Stack) -> {Val,Rem}; 116 | decode_internal(<>, primitive, _, u32, _Spec, _Stack) -> {Val,Rem}; 117 | decode_internal(<>, primitive, _, u64, _Spec, _Stack) -> {Val,Rem}; 118 | decode_internal(<>, primitive, _, s8, _Spec, _Stack) -> {Val,Rem}; 119 | decode_internal(<>, primitive, _, s16, _Spec, _Stack) -> {Val,Rem}; 120 | decode_internal(<>, primitive, _, s32, _Spec, _Stack) -> {Val,Rem}; 121 | decode_internal(<>, primitive, _, s64, _Spec, _Stack) -> {Val,Rem}; 122 | decode_internal(<<1:8/integer-unsigned-little,Rem/binary>>, primitive, _, bool, _Spec, _Stack) -> {true,Rem}; 123 | decode_internal(<<0:8/integer-unsigned-little,Rem/binary>>, primitive, _, bool, _Spec, _Stack) -> {false,Rem}; 124 | 125 | decode_internal(<<0,0,128,127,Rem/binary>>, primitive, _, f32, _Spec, _Stack) -> {pos_inf,Rem}; 126 | decode_internal(<<0,0,128,255,Rem/binary>>, primitive, _, f32, _Spec, _Stack) -> {neg_inf,Rem}; 127 | decode_internal(<<0,0,192,255,Rem/binary>>, primitive, _, f32, _Spec, _Stack) -> {nan,Rem}; 128 | decode_internal(<>, primitive, _, f32, _Spec, _Stack) -> {Value,Rem}; 129 | decode_internal(<<0,0,0,0,0,0,240,127,Rem/binary>>, primitive, _, f64, _Spec, _Stack) -> {pos_inf,Rem}; 130 | decode_internal(<<0,0,0,0,0,0,240,255,Rem/binary>>, primitive, _, f64, _Spec, _Stack) -> {neg_inf,Rem}; 131 | decode_internal(<<0,0,0,0,0,0,248,255,Rem/binary>>, primitive, _, f64, _Spec, _Stack) -> {nan,Rem}; 132 | decode_internal(<>, primitive, _, f64, _Spec, _Stack) -> {Value,Rem}; 133 | decode_internal(Bin, primitive, _, Name, _Spec, _Stack) -> throw({{unexpected_end_of_input, primitive, Name, Bin}, lists:reverse(_Stack)}); 134 | 135 | decode_internal(Bin, synonym, _Name, RefName, Spec, _Stack) -> 136 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 137 | decode_internal(Bin, RefProto, RefName, _Desc, Spec, _Stack); 138 | 139 | decode_internal(Bin, vector, Name, {RefName, MaxLen, Tag}, Spec, _Stack) -> 140 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 141 | {Length, Rem} = decode_tag(Bin, Tag, Spec, _Stack), 142 | case Length > MaxLen of 143 | true -> throw({{oversize_vector, Name, Length, MaxLen}, lists:reverse(_Stack)}); 144 | _ -> ok 145 | end, 146 | {FinalRem, Vals} = lists:foldl(fun(_, {FoldRem, Acc}) -> 147 | {Val, NextRem} = decode_internal(FoldRem, RefProto, RefName, _Desc, Spec, [lists:reverse(Acc)|_Stack]), 148 | {NextRem, [Val|Acc]} 149 | end, {Rem, []}, lists:seq(0, Length - 1)), 150 | Res = case RefName of 151 | char -> list_to_binary(lists:reverse(Vals)); 152 | _ -> lists:reverse(Vals) 153 | end, 154 | {Res, FinalRem}; 155 | decode_internal(Bin, array, _Name, {RefName, Length}, Spec, _Stack) -> 156 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 157 | {FinalRem, Vals} = lists:foldl(fun(_, {FoldRem, Acc}) -> 158 | {Val, NextRem} = decode_internal(FoldRem, RefProto, RefName, _Desc, Spec, [lists:reverse(Acc)|_Stack]), 159 | {NextRem, [Val|Acc]} 160 | end, {Bin, []}, lists:seq(0, Length - 1)), 161 | Res = case RefName of 162 | char -> list_to_binary(lists:reverse(Vals)); 163 | _ -> lists:reverse(Vals) 164 | end, 165 | {Res, FinalRem}; 166 | decode_internal(Bin, range, Name, {Offset, Length, Tag}, Spec, _Stack) -> 167 | {Value, Rem} = decode_tag(Bin, Tag, Spec, _Stack), 168 | case Value > Length of 169 | true -> throw({{invalid_range_value, Name, Value, Length}, lists:reverse(_Stack)}); 170 | _ -> ok 171 | end, 172 | {Value + Offset, Rem}; 173 | decode_internal(Bin, enumeration, _Name, {Tag, States}, Spec, _Stack) -> 174 | {Index, Rem} = decode_tag(Bin, Tag, Spec, _Stack), 175 | {Value, Index} = case lists:keyfind(Index, 2, States) of 176 | false -> 177 | throw({{out_of_range_enumeration_value, _Name, Index}, lists:reverse(_Stack)}); 178 | R -> R 179 | end, 180 | {Value, Rem}; 181 | decode_internal(Bin, record, _Name, InstFields, Spec, _Stack) -> 182 | {FinalBin, Vals} = lists:foldl(fun({data, FieldName, _, RefName}, {FoldBin, Acc}) -> 183 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 184 | {Val, NextRem} = decode_internal(FoldBin, RefProto, RefName, _Desc, Spec, [lists:reverse([{FieldName, '__duct_tape__'}|Acc])|_Stack]), 185 | {NextRem,[{FieldName, Val}|Acc]} 186 | end, {Bin, []}, InstFields), 187 | {lists:reverse(Vals), FinalBin}; 188 | decode_internal(Bin, union, _Name, {Tag, InstFields}, Spec, _Stack) -> 189 | {Index, Rem} = decode_tag(Bin, Tag, Spec, _Stack), 190 | case lists:keyfind(Index, 3, InstFields) of 191 | {data, FieldName, Index, RefName} -> 192 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 193 | {Value, FinalRem} = decode_internal(Rem, RefProto, RefName, _Desc, Spec, [[{FieldName, '__duct_tape__'}]|_Stack]), 194 | {[{FieldName, Value}], FinalRem}; 195 | {empty, FieldName, Index} -> 196 | {[{FieldName, true}], Rem}; 197 | false -> 198 | throw({{bad_union_index, _Name, Index}, lists:reverse(_Stack)}) 199 | end; 200 | 201 | decode_internal(Bin, combination, _Name, {Tag, InstFields}, Spec, _Stack) -> 202 | {Flags, Rem} = decode_tag(Bin, Tag, Spec, _Stack), 203 | FieldCount = length(InstFields), 204 | BitFlags = [ ( Flags band trunc(math:pow(2, X))) > 0 || X <- lists:seq(0, FieldCount - 1) ], 205 | {FinalRem, Values} = lists:foldl(fun({true, {empty, FieldName, _}}, {FoldBin,Acc}) -> 206 | {FoldBin, [FieldName|Acc]}; 207 | ({true, {data, FieldName, _, RefName}}, {FoldBin,Acc}) -> 208 | {descriptor, RefProto, RefName, _Desc} = lookup_type(RefName, Spec), 209 | {Value, NextRem} = decode_internal(FoldBin, RefProto, RefName, _Desc, Spec, [lists:reverse([{FieldName, '__duct_tape__'}|Acc])|_Stack]), 210 | {NextRem, [{FieldName, Value}|Acc]}; 211 | ({false, _}, Acc) -> Acc 212 | end, {Rem, []}, lists:zip(BitFlags, InstFields)), 213 | {lists:reverse(Values), FinalRem}. 214 | 215 | decode_tag(Bin, Tag, Spec, _Stack) -> 216 | Prim = tag_to_prim(Tag), 217 | decode_internal(Bin, primitive, Prim, Prim, Spec, _Stack). 218 | 219 | -spec encode([{TypeName :: atom(), Value :: any()}, ...], Spec :: cauterize_spec()) -> encode_result(). 220 | encode([{_TypeName, _Value}|_T]=List, Spec) -> 221 | try [encode(TypeName, Value, Spec) || {TypeName, Value} <- List] of 222 | R -> {ok, R} 223 | catch 224 | throw:Reason -> 225 | {error, Reason} 226 | end. 227 | 228 | encode(TypeName, Value, Spec) -> 229 | {descriptor, Prototype, Name, _Desc} = lookup_type(TypeName, Spec), 230 | encode_int({instance, Prototype, Name, Value}, Spec). 231 | 232 | encode_int({instance, primitive, u8, Value}, _Spec) -> 233 | case Value bsr 8 of 234 | 0 -> 235 | <>; 236 | _ -> 237 | throw({integer_too_large, u8, Value}) 238 | end; 239 | encode_int({instance, primitive, char, Value}, _Spec) -> 240 | case Value bsr 8 of 241 | 0 -> 242 | <>; 243 | _ -> 244 | throw({integer_too_large, char, Value}) 245 | end; 246 | encode_int({instance, primitive, u16, Value}, _Spec) -> 247 | case Value bsr 16 of 248 | 0 -> 249 | <>; 250 | _ -> 251 | throw({integer_too_large, u16, Value}) 252 | end; 253 | encode_int({instance, primitive, u32, Value}, _Spec) -> 254 | case Value bsr 32 of 255 | 0 -> 256 | <>; 257 | _ -> 258 | throw({integer_too_large, u32, Value}) 259 | end; 260 | encode_int({instance, primitive, u64, Value}, _Spec) -> 261 | case Value bsr 64 of 262 | 0 -> 263 | <>; 264 | _ -> 265 | throw({integer_too_large, u64, Value}) 266 | end; 267 | encode_int({instance, primitive, s8, Value}, _Spec) -> 268 | case Value bsr 7 of 269 | 0 when Value >= 0 -> 270 | <>; 271 | -1 when Value < 0 -> 272 | <>; 273 | _ -> 274 | throw({integer_too_large, s8, Value}) 275 | end; 276 | encode_int({instance, primitive, s16, Value}, _Spec) -> 277 | case Value bsr 15 of 278 | 0 when Value >= 0 -> 279 | <>; 280 | -1 when Value < 0 -> 281 | <>; 282 | _ -> 283 | throw({integer_too_large, s16, Value}) 284 | end; 285 | encode_int({instance, primitive, s32, Value}, _Spec) -> 286 | case Value bsr 31 of 287 | 0 when Value >= 0 -> 288 | <>; 289 | -1 when Value < 0 -> 290 | <>; 291 | _ -> 292 | throw({integer_too_large, s32, Value}) 293 | end; 294 | encode_int({instance, primitive, s64, Value}, _Spec) -> 295 | case Value bsr 63 of 296 | 0 when Value >= 0 -> 297 | <>; 298 | -1 when Value < 0 -> 299 | <>; 300 | _ -> 301 | throw({integer_too_large, s64, Value}) 302 | end; 303 | encode_int({instance, primitive, bool, true}, _Spec) -> 304 | <<1:8/integer-unsigned-little>>; 305 | encode_int({instance, primitive, bool, false}, _Spec) -> 306 | <<0:8/integer-unsigned-little>>; 307 | encode_int({instance, primitive, f32, pos_inf}, _Spec) -> <<0,0,128,127>>; 308 | encode_int({instance, primitive, f32, neg_inf}, _Spec) -> <<0,0,128,255>>; 309 | encode_int({instance, primitive, f32, nan}, _Spec) -> <<0,0,192,255>>; 310 | encode_int({instance, primitive, f32, Value}, _Spec) -> <>; 311 | encode_int({instance, primitive, f64, pos_inf}, _Spec) -> <<0,0,0,0,0,0,240,127>>; 312 | encode_int({instance, primitive, f64, neg_inf}, _Spec) -> <<0,0,0,0,0,0,240,255>>; 313 | encode_int({instance, primitive, f64, nan}, _Spec) -> <<0,0,0,0,0,0,248,255>>; 314 | encode_int({instance, primitive, f64, Value}, _Spec) -> <>; 315 | 316 | encode_int({instance, synonym, Name, Value}, Spec) -> 317 | {descriptor, synonym, Name, RefName} = lookup_type(Name, synonym, Spec), 318 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 319 | encode_int({instance, Prototype, RefName, Value}, Spec); 320 | 321 | encode_int({instance, vector, Name, Values}, Spec) when is_list(Values) -> 322 | {descriptor, vector, Name, {RefName, MaxSize, Tag}} = lookup_type(Name, vector, Spec), 323 | case length(Values) > MaxSize of 324 | true -> throw({oversize_vector, Name, length(Values), MaxSize}); 325 | _ -> ok 326 | end, 327 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 328 | [encode_int({instance, primitive, tag_to_prim(Tag), length(Values)}, Spec)| 329 | [encode_int({instance, Prototype, RefName, V}, Spec)||V <- Values]]; 330 | 331 | encode_int({instance, vector, Name, Values}, Spec) when is_binary(Values) -> 332 | encode_int({instance, vector, Name, binary_to_list(Values)}, Spec); 333 | 334 | encode_int({instance, array, Name, Values}, Spec) when is_list(Values) -> 335 | {descriptor, array, Name, {RefName, Size}} = lookup_type(Name, array, Spec), 336 | case length(Values) /= Size of 337 | true -> throw({incorrect_array_size, Name, length(Values), Size}); 338 | _ -> ok 339 | end, 340 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 341 | [encode_int({instance, Prototype, RefName, V}, Spec)||V <- Values]; 342 | 343 | encode_int({instance, range, Name, Value}, Spec) when is_integer(Value) -> 344 | {descriptor, range, Name, {Offset, Length, Tag}} = lookup_type(Name, range, Spec), 345 | NewValue = Value - Offset, 346 | case NewValue of 347 | 0 -> 348 | throw({invalid_range_value, Name, Value}); 349 | N when N > Length -> 350 | throw({invalid_range_value, Name, Value}); 351 | _ -> ok 352 | end, 353 | [encode_int({instance, primitive, tag_to_prim(Tag), NewValue}, Spec)]; 354 | 355 | encode_int({instance, enumeration, Name, Value}, Spec) when is_atom(Value) -> 356 | {descriptor, enumeration, Name, {Tag, States}} = lookup_type(Name, enumeration, Spec), 357 | {Value, Index} = case lists:keyfind(Value, 1, States) of 358 | false -> 359 | throw({unknown_enumeration_field, Name, Value}); 360 | R -> R 361 | end, 362 | [encode_int({instance, primitive, tag_to_prim(Tag), Index}, Spec)]; 363 | 364 | encode_int({instance, record, Name, InstFields}, Spec) -> 365 | {descriptor, record, Name, DescFields} = lookup_type(Name, record, Spec), 366 | RecData = lists:foldl(fun({data, FieldName, _, RefName}, Acc) -> 367 | {FieldName, Value} = case lists:keyfind(FieldName, 1, InstFields) of 368 | false -> 369 | throw({missing_record_field, Name, FieldName}); 370 | R -> R 371 | end, 372 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 373 | [encode_int({instance, Prototype, RefName, Value}, Spec)|Acc] 374 | end, [], DescFields), 375 | lists:reverse(RecData); 376 | 377 | encode_int({instance, union, Name, FieldName}, Spec) when is_atom(FieldName) -> 378 | %% turn it into KVC form 379 | encode_int({instance, union, Name, [{FieldName, true}]}, Spec); 380 | encode_int({instance, union, Name, {FieldName, Value}}, Spec) when is_atom(FieldName) -> 381 | %% turn it into KVC form 382 | encode_int({instance, union, Name, [{FieldName, Value}]}, Spec); 383 | encode_int({instance, union, Name, [{FieldName, Value}]}, Spec) when is_atom(FieldName) -> 384 | {descriptor, union, Name, {Tag, Fields}} = lookup_type(Name, union, Spec), 385 | case lists:keyfind(FieldName, 2, Fields) of 386 | false -> 387 | throw({unknown_union_member, Name, FieldName}); 388 | {empty, FieldName, Index} when Value == true -> 389 | [encode_int({instance, primitive, tag_to_prim(Tag), Index}, Spec)]; 390 | {empty, FieldName, _Index} -> 391 | throw({data_supplied_for_empty_union_member, Name, FieldName}); 392 | {data, FieldName, Index, RefName} -> 393 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 394 | [encode_int({instance, primitive, tag_to_prim(Tag), Index}, Spec), 395 | encode_int({instance, Prototype, RefName, Value}, Spec)] 396 | end; 397 | encode_int({instance, combination, Name, InstFields}, Spec) -> 398 | {descriptor, combination, Name, {Tag, DescFields}} = lookup_type(Name, combination, Spec), 399 | {BitTag,RecData} = lists:foldl(fun({data, FieldName, Index, RefName}, {BitField,Acc}) -> 400 | case lists:keyfind(FieldName, 1, InstFields) of 401 | {FieldName, Value} -> 402 | {descriptor, Prototype, RefName, _Desc} = lookup_type(RefName, Spec), 403 | {BitField bor (1 bsl Index), 404 | [encode_int({instance, Prototype, RefName, Value}, Spec)|Acc]}; 405 | false -> {BitField, Acc} 406 | end; 407 | ({empty, FieldName, Index}, {BitField,Acc}) -> 408 | case lists:member(FieldName, InstFields) of 409 | true -> {BitField bor (1 bsl Index), Acc}; 410 | false -> {BitField, Acc} 411 | end 412 | end, {0,[]}, DescFields), 413 | [encode_int({instance, primitive, tag_to_prim(Tag), BitTag},Spec)|lists:reverse(RecData)]; 414 | encode_int(Instance, _Spec) -> 415 | throw({invalid_instance, Instance}). 416 | 417 | tag_to_prim(tag8) -> u8; 418 | tag_to_prim(tag16) -> u16; 419 | tag_to_prim(tag32) -> u32; 420 | tag_to_prim(tag64) -> u64; 421 | tag_to_prim(Tag) -> throw({invalid_tag, Tag}). 422 | 423 | 424 | lookup_type(u8, _Spec) -> {descriptor, primitive, u8, u8}; 425 | lookup_type(u16, _Spec) -> {descriptor, primitive, u16, u16}; 426 | lookup_type(u32, _Spec) -> {descriptor, primitive, u32, u32}; 427 | lookup_type(u64, _Spec) -> {descriptor, primitive, u64, u64}; 428 | lookup_type(s8, _Spec) -> {descriptor, primitive, s8, s8}; 429 | lookup_type(s16, _Spec) -> {descriptor, primitive, s16, s16}; 430 | lookup_type(s32, _Spec) -> {descriptor, primitive, s32, s32}; 431 | lookup_type(s64, _Spec) -> {descriptor, primitive, s64, s64}; 432 | lookup_type(f32, _Spec) -> {descriptor, primitive, f32, f32}; 433 | lookup_type(f64, _Spec) -> {descriptor, primitive, f64, f64}; 434 | lookup_type(bool, _Spec) -> {descriptor, primitive, bool, bool}; 435 | lookup_type(char, _Spec) -> {descriptor, primitive, char, u8}; 436 | lookup_type(Name, Spec) -> 437 | case lists:keyfind(Name, 3, Spec) of 438 | false -> 439 | throw({unknown_type, Name}); 440 | R -> R 441 | end. 442 | 443 | lookup_type(Name, Type, Spec) -> 444 | R = lookup_type(Name, Spec), 445 | case element(2, R) of 446 | Type -> 447 | R; 448 | OtherType -> 449 | throw({type_mismatch, Name, Type, OtherType}) 450 | end. 451 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | - location: 5 | git: https://github.com/cauterize-tools/cauterize.git 6 | commit: 58774c01c445ba0c163894714e419341500269b8 7 | - location: 8 | git: https://github.com/cauterize-tools/crucible.git 9 | commit: b2125cf19e64411c08b10a26bd8dffa17e11ec0e 10 | extra-deps: 11 | - s-cargot-0.1.0.0 12 | resolver: lts-3.16 -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | main :: IO () 2 | main = putStrLn "Test suite not yet implemented" 3 | -------------------------------------------------------------------------------- /test/cauterize_schema_test.erl: -------------------------------------------------------------------------------- 1 | -module(cauterize_schema_test). 2 | 3 | -compile([export_all]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | roundtrip_test() -> 8 | I0 = [{somearray, [1, 2, 3, 4, 5, 6, 7, 8]}], 9 | {ok, O0} = erlang_test:encode(I0), 10 | ?assertEqual({ok, I0}, erlang_test:decode(list_to_binary(O0), somearray)), 11 | I1 = [{somevector, [1, 2, 3, 4, 5]}], 12 | {ok, O1} = erlang_test:encode(I1), 13 | ?assertEqual({ok, I1}, erlang_test:decode(list_to_binary(O1), somevector)), 14 | I2 = [{arecord, [{z, [10, 11, 12, 13]}, {a, -1}, {d, [{a, 1}, {d, [{a, 0}, {b, 1}]}]}]}], 15 | {ok, O2} = erlang_test:encode(I2), 16 | ?assertEqual({ok, I2}, erlang_test:decode(list_to_binary(O2), arecord)), 17 | I3 = [{a_union, [{c, 99}]}], 18 | {ok, O3} = erlang_test:encode(I3), 19 | ?assertEqual({ok, I3}, erlang_test:decode(list_to_binary(O3), a_union)), 20 | %% non-KVC form 21 | I3a = [{a_union, {c, 99}}], 22 | {ok, O3a} = erlang_test:encode(I3a), 23 | %% decodes to KVC form 24 | ?assertEqual({ok, I3}, erlang_test:decode(list_to_binary(O3a), a_union)), 25 | I4 = [{a_union, [{e, true}]}], 26 | {ok, O4} = erlang_test:encode(I4), 27 | ?assertEqual({ok, I4}, erlang_test:decode(list_to_binary(O4), a_union)), 28 | %% non-KVC form 29 | I4a = [{a_union, e}], 30 | {ok, O4a} = erlang_test:encode(I4a), 31 | %% decodes to KVC form 32 | ?assertEqual({ok, I4}, erlang_test:decode(list_to_binary(O4a), a_union)), 33 | %% non-KVC form 34 | I4b = [{a_union, {e, true}}], 35 | {ok, O4b} = erlang_test:encode(I4b), 36 | %% decodes to KVC form 37 | ?assertEqual({ok, I4}, erlang_test:decode(list_to_binary(O4b), a_union)), 38 | I5 = [{a_combination, [{a, 223372036854775808}, {b, -99}, d]}], 39 | {ok, O5} = erlang_test:encode(I5), 40 | ?assertEqual({ok, I5}, erlang_test:decode(list_to_binary(O5), a_combination)), 41 | I5a = [{a_combination, [{a, 223372036854775808}, {b, -99}]}], 42 | {ok, O5a} = erlang_test:encode(I5a), 43 | ?assertEqual({ok, I5a}, erlang_test:decode(list_to_binary(O5a), a_combination)), 44 | I5b = [{a_combination, [{a, 223372036854775808}, {b, -99}, {c, [{a,[{z, [10, 11, 12, 13]}, {a, -1}, {d, [{a, 1}, {d, [{a, 0}, {b, 1}]}]}]}] }]}], 45 | {ok, O5b} = erlang_test:encode(I5b), 46 | ?assertEqual({ok, I5b}, erlang_test:decode(list_to_binary(O5b), a_combination)), 47 | I6 = [{someenum, green}], 48 | {ok, O6} = erlang_test:encode(I6), 49 | ?assertEqual({ok, I6}, erlang_test:decode(list_to_binary(O6), someenum)), 50 | I7 = [{some_range, 1005}], 51 | {ok, O7} = erlang_test:encode(I7), 52 | ?assertEqual({ok, I7}, erlang_test:decode(list_to_binary(O7), some_range)), 53 | I8 = [{somearray, [1, 2, 3, 4, 5, 6, 7]}], 54 | ?assertEqual({error, {incorrect_array_size, somearray, 7, 8}}, erlang_test:encode(I8)), 55 | ok. 56 | 57 | truncate(B, I) -> 58 | binary:part(B, {0, byte_size(B) - I}). 59 | 60 | decode_test_() -> 61 | [ 62 | {"somearray truncation", fun() -> 63 | ?assertMatch({error, {no_input, []}}, erlang_test:decode(<<>> , somearray)), 64 | ok 65 | end}, 66 | {"somearray truncation", fun() -> 67 | I0 = [{somearray, [1, 2, 3, 4, 5, 6, 7, 8]}], 68 | {ok, O0} = erlang_test:encode(I0), 69 | C = list_to_binary(O0), 70 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{somearray, [1, 2, 3, 4, 5, 6, 7]}]}}, erlang_test:decode(truncate(C, 8), somearray)), 71 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{somearray, [1, 2, 3, 4, 5, 6]}]}}, erlang_test:decode(truncate(C, 9), somearray)), 72 | ok 73 | end}, 74 | {"range truncation", fun() -> 75 | I0 = [{some_range, 1005}], 76 | {ok, O0} = erlang_test:encode(I0), 77 | C = list_to_binary(O0), 78 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{some_range, []}]}}, erlang_test:decode(truncate(C, 1), some_range)), 79 | ok 80 | end}, 81 | {"invalid range value", fun() -> 82 | C = <<2011:16/integer-unsigned-little>>, 83 | ?assertMatch({error, {{invalid_range_value, _, _, _}, [{some_range, []}]}}, erlang_test:decode(C, some_range)), 84 | ok 85 | end}, 86 | {"somevector truncation", fun() -> 87 | I0 = [{somevector, [1, 2, 3, 4, 5]}], 88 | {ok, O0} = erlang_test:encode(I0), 89 | C = list_to_binary(O0), 90 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{somevector, [1, 2, 3, 4]}]}}, erlang_test:decode(truncate(C, 7), somevector)), 91 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{somevector, [1, 2, 3, 4]}]}}, erlang_test:decode(truncate(C, 8), somevector)), 92 | ok 93 | end}, 94 | {"oversize vector", fun() -> 95 | I0 = [{somevector, [1, 2, 3, 4, 5]}], 96 | {ok, O0} = erlang_test:encode(I0), 97 | <<5:8/integer, C0/binary>> = list_to_binary(O0), 98 | C = <<10:8/integer, C0/binary, 6:64/integer-signed-little, 7:64/integer-signed-little, 8:64/integer-signed-little, 9:64/integer-signed-little, 10:64/integer-signed-little>>, 99 | ?assertMatch({error, {{oversize_vector, somevector, 10, 8}, [{somevector, []}]}}, erlang_test:decode(C, somevector)), 100 | ok 101 | end}, 102 | {"someenum invalid value", fun() -> 103 | ?assertMatch({error, {{out_of_range_enumeration_value, someenum, 9}, [{someenum, [] }]}}, erlang_test:decode(<<9>>, someenum)), 104 | ok 105 | end}, 106 | {"union truncation", fun() -> 107 | I0= [{a_union, [{d, 223372036854775808}]}], 108 | {ok, O0} = erlang_test:encode(I0), 109 | C = list_to_binary(O0), 110 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{a_union, [{d,'?'}] }]}}, erlang_test:decode(truncate(C, 1), a_union)), 111 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{a_union, [{d, '?'}] }]}}, erlang_test:decode(truncate(C, 8), a_union)), 112 | ok 113 | end}, 114 | {"union bad index", fun() -> 115 | I0= [{a_union, [{d, 223372036854775808}]}], 116 | {ok, O0} = erlang_test:encode(I0), 117 | <<3:8/integer, C0/binary>> = list_to_binary(O0), 118 | C = <<10:8/integer, C0/binary>>, 119 | ?assertMatch({error, {{bad_union_index, _, _}, [{a_union, []}]}}, erlang_test:decode(truncate(C, 1), a_union)), 120 | ok 121 | end}, 122 | {"record truncation", fun() -> 123 | I0 = [{arecord, [{z, [10, 11, 12, 13]}, {a, 1}, {d, [{a, 2}, {d, [{a, 3}, {b, 1}]}]}]}], 124 | {ok, O0} = erlang_test:encode(I0), 125 | C = list_to_binary(O0), 126 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{arecord, [{z, [10, 11, 12, 13]}, {a, 1}, {d, [{a, 2}, {d, [{a, 3}, {b, '?'}]}]}]}]}}, erlang_test:decode(truncate(C, 1), arecord)), 127 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{arecord, [{z, [10, 11, 12, 13]}, {a, 1}, {d, [{a, 2}, {d, '?'}]}]}]}}, erlang_test:decode(truncate(C, 2), arecord)), 128 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{arecord, [{z, [10, 11, 12, 13]}, {a, 1}, {d, '?'}]}]}}, erlang_test:decode(truncate(C, 3), arecord)), 129 | ok 130 | end}, 131 | {"oversize vector in record", fun() -> 132 | I0 = [{arecord, [{z, [10, 11, 12, 13]}, {a, 1}, {d, [{a, 2}, {d, [{a, 3}, {b, 1}]}]}]}], 133 | {ok, O0} = erlang_test:encode(I0), 134 | <<4:8/integer, C0/binary>> = list_to_binary(O0), 135 | C = <<10:8/integer, C0/binary>>, 136 | ?assertMatch({error, {{oversize_vector, _, _, _}, [{arecord, [{z, '?'}]}]}}, erlang_test:decode(C, arecord)), 137 | ok 138 | end}, 139 | {"combination truncation", fun() -> 140 | I0 = [{a_combination, [{a, 223372036854775808}, {b, -99}, d]}], 141 | {ok, O0} = erlang_test:encode(I0), 142 | C = list_to_binary(O0), 143 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{a_combination, [{a, 223372036854775808}, {b, '?'}]}]}}, erlang_test:decode(truncate(C, 1), a_combination)), 144 | ?assertMatch({error, {{unexpected_end_of_input, _, _, _}, [{a_combination, [{a, '?'}]}]}}, erlang_test:decode(truncate(C, 2), a_combination)), 145 | ok 146 | end}, 147 | {"union unknown field", fun() -> 148 | I0= [{a_union, [{f, 1}]}], 149 | ?assertMatch({error, {unknown_union_member, a_union, f}}, erlang_test:encode(I0)), 150 | ok 151 | end}, 152 | {"union unknown field", fun() -> 153 | I0= [{a_union, [{e, 1}]}], 154 | ?assertMatch({error, {data_supplied_for_empty_union_member, a_union, e}}, erlang_test:encode(I0)), 155 | ok 156 | end} 157 | ]. 158 | 159 | encode_test_() -> 160 | [ 161 | {"oversize vector", fun() -> 162 | I0 = [{somevector, lists:seq(1, 20)}], 163 | ?assertMatch({error, {oversize_vector, somevector, 20, 8}}, erlang_test:encode(I0)), 164 | ok 165 | end}, 166 | {"out-of-range range value", fun() -> 167 | I1 = [{some_range, 2011}], 168 | ?assertMatch({error, {invalid_range_value, some_range, 2011}}, erlang_test:encode(I1)), 169 | ok 170 | end}, 171 | {"invalid enum value", fun() -> 172 | I1 = [{someenum, purple}], 173 | ?assertMatch({error, {unknown_enumeration_field, someenum, purple}}, erlang_test:encode(I1)), 174 | ok 175 | end}, 176 | {"invalid record field", fun() -> 177 | I0 = [{arecord, [{z, [10, 11, 12, 13]}, {d, [{a, 2}, {d, [{a, 3}, {b, 1}]}]}]}], 178 | ?assertMatch({error, {missing_record_field, arecord, a}}, erlang_test:encode(I0)), 179 | ok 180 | end} 181 | ]. 182 | 183 | char_vector_test() -> 184 | I0 = [{charvector, [$h, $e, $l, $l, $o]}], 185 | {ok, O0} = cauterize:encode(I0, [{descriptor, vector, charvector, { u8, 10, tag8 }}]), 186 | I1 = [{charvector, <<"hello">>}], 187 | ?assertEqual({ok, O0}, cauterize:encode(I1, [{descriptor, vector, charvector, { char, 10, tag8}}])), 188 | ?assertEqual({ok, I0}, cauterize:decode(list_to_binary(O0), charvector, [{descriptor, vector, charvector, { u8, 10, tag8 }}])), 189 | ?assertEqual({ok, [{charvector, <<"hello">>}]}, cauterize:decode(list_to_binary(O0), charvector, [{descriptor, vector, charvector, { char, 10, tag8 }}])), 190 | ok. 191 | 192 | 193 | char_array_test() -> 194 | I0 = [{chararray, [$h, $e, $l, $l, $o, $w, $o, $r, $l, $d]}], 195 | {ok, O0} = cauterize:encode(I0, [{descriptor, array, chararray, { u8, 10 }}]), 196 | ?assertEqual({ok, O0}, cauterize:encode(I0, [{descriptor, array, chararray, { char, 10 }}])), 197 | ?assertEqual({ok, I0}, cauterize:decode(list_to_binary(O0), chararray, [{descriptor, array, chararray, { u8, 10 }}])), 198 | ?assertEqual({ok, [{chararray, <<"helloworld">>}]}, cauterize:decode(list_to_binary(O0), chararray, [{descriptor, array, chararray, { char, 10 }}])), 199 | ok. 200 | 201 | coalesce_test() -> 202 | I0 = [{somearray, [1, 2, 3, 4, 5, 6, 7, 8]}], 203 | {ok, O0} = erlang_test:encode(I0), 204 | I1 = [{somevector, [1, 2, 3, 4, 5]}], 205 | {ok, O1} = erlang_test:encode(I1), 206 | I2 = [{arecord, [{z, [10, 11, 12, 13]}, {a, -1}, {d, [{a, 1}, {d, [{a, 0}, {b, 1}]}]}]}], 207 | {ok, O2} = erlang_test:encode(I2), 208 | Header = [{header, [somearray, somevector, arecord]}], 209 | {ok, H} = erlang_test:encode(Header), 210 | Coalesced = list_to_binary([H, O0, O1, O2]), 211 | {ok, Coalesced2} = erlang_test:encode(Header++I0++I1++I2), 212 | %% assert manually coalescing types is the same as passing several 213 | %% types to encode() 214 | ?assertEqual(Coalesced, list_to_binary(Coalesced2)), 215 | %% check we can decode just one type and get back a remainder 216 | {ok, DecodedHeader, Rem} = erlang_test:decode(Coalesced, header), 217 | ?assertEqual(Header, DecodedHeader), 218 | [{header, HeaderFields}] = DecodedHeader, 219 | %% check we can decode several types at once 220 | {ok, DecodedFields} = erlang_test:decode(Rem, HeaderFields), 221 | ?assertEqual(I0++I1++I2, DecodedFields), 222 | ok. 223 | 224 | -------------------------------------------------------------------------------- /test/primitive_test.erl: -------------------------------------------------------------------------------- 1 | -module(primitive_test). 2 | 3 | -compile([export_all]). 4 | 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | primitive_test_() -> 8 | [ 9 | {"u8", fun() -> 10 | I = [{primitivetest, [{u8, 255}]}], 11 | {ok, O} = erlang_test:encode(I), 12 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 13 | end}, 14 | {"u16", fun() -> 15 | I = [{primitivetest, [{u16, 65535}]}], 16 | {ok, O} = erlang_test:encode(I), 17 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 18 | end}, 19 | {"u32", fun() -> 20 | I = [{primitivetest, [{u32, 4294967295}]}], 21 | {ok, O} = erlang_test:encode(I), 22 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 23 | end}, 24 | {"u64", fun() -> 25 | I = [{primitivetest, [{u64, 18446744073709551615}]}], 26 | {ok, O} = erlang_test:encode(I), 27 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 28 | end}, 29 | {"s16", fun() -> 30 | I = [{primitivetest, [{s16, 32767}]}], 31 | {ok, O} = erlang_test:encode(I), 32 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 33 | end}, 34 | {"s32", fun() -> 35 | I = [{primitivetest, [{s32, 2147483647}]}], 36 | {ok, O} = erlang_test:encode(I), 37 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 38 | end}, 39 | {"true", fun() -> 40 | I = [{primitivetest, [{bool, true}]}], 41 | {ok, O} = erlang_test:encode(I), 42 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 43 | end}, 44 | {"false", fun() -> 45 | I = [{primitivetest, [{bool, false}]}], 46 | {ok, O} = erlang_test:encode(I), 47 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 48 | end}, 49 | {"f32 nan", fun() -> 50 | I = [{primitivetest, [{f32, nan}]}], 51 | {ok, O} = erlang_test:encode(I), 52 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 53 | end}, 54 | {"f32 pos_inf", fun() -> 55 | I = [{primitivetest, [{f32, pos_inf}]}], 56 | {ok, O} = erlang_test:encode(I), 57 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 58 | end}, 59 | {"f32 neg_inf", fun() -> 60 | I = [{primitivetest, [{f32, neg_inf}]}], 61 | {ok, O} = erlang_test:encode(I), 62 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 63 | end}, 64 | {"f32", fun() -> 65 | I = [{primitivetest, [{f32, 3.1449999809265137}]}], 66 | {ok, O} = erlang_test:encode(I), 67 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 68 | end}, 69 | {"f64 nan", fun() -> 70 | I = [{primitivetest, [{f64, nan}]}], 71 | {ok, O} = erlang_test:encode(I), 72 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 73 | end}, 74 | {"f64 pos_inf", fun() -> 75 | I = [{primitivetest, [{f64, pos_inf}]}], 76 | {ok, O} = erlang_test:encode(I), 77 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 78 | end}, 79 | {"f64 neg_inf", fun() -> 80 | I = [{primitivetest, [{f64, neg_inf}]}], 81 | {ok, O} = erlang_test:encode(I), 82 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 83 | end}, 84 | {"f64", fun() -> 85 | I = [{primitivetest, [{f64, 3.142857142857143}]}], 86 | {ok, O} = erlang_test:encode(I), 87 | ?assertEqual({ok, I}, erlang_test:decode(list_to_binary(O), primitivetest)) 88 | end}]. 89 | 90 | oversize_primitive_test_() -> 91 | [ 92 | {"u8", fun() -> 93 | I = [{primitivetest, [{u8, 256}]}], 94 | ?assertEqual({error, {integer_too_large, u8, 256}}, erlang_test:encode(I)) 95 | end}, 96 | {"u16", fun() -> 97 | I = [{primitivetest, [{u16, 65536}]}], 98 | ?assertEqual({error, {integer_too_large, u16, 65536}}, erlang_test:encode(I)) 99 | end}, 100 | {"u32", fun() -> 101 | I = [{primitivetest, [{u32, 4294967296}]}], 102 | ?assertEqual({error, {integer_too_large, u32, 4294967296}}, erlang_test:encode(I)) 103 | end}, 104 | {"u64", fun() -> 105 | I = [{primitivetest, [{u64, 18446744073709551616}]}], 106 | ?assertEqual({error, {integer_too_large, u64, 18446744073709551616}}, erlang_test:encode(I)) 107 | end}, 108 | {"s8", fun() -> 109 | I = [{primitivetest, [{s8, 128}]}], 110 | ?assertEqual({error, {integer_too_large, s8, 128}}, erlang_test:encode(I)), 111 | I2 = [{primitivetest, [{s8, -128}]}], 112 | {ok, O} = erlang_test:encode(I2), 113 | ?assertEqual({ok, I2}, erlang_test:decode(list_to_binary(O), primitivetest)), 114 | I3 = [{primitivetest, [{s8, -129}]}], 115 | ?assertEqual({error, {integer_too_large, s8, -129}}, erlang_test:encode(I3)) 116 | end}, 117 | {"s16", fun() -> 118 | I = [{primitivetest, [{s16, 32768}]}], 119 | ?assertEqual({error, {integer_too_large, s16, 32768}}, erlang_test:encode(I)), 120 | I2 = [{primitivetest, [{s16, -32768}]}], 121 | {ok, O} = erlang_test:encode(I2), 122 | ?assertEqual({ok, I2}, erlang_test:decode(list_to_binary(O), primitivetest)), 123 | I3 = [{primitivetest, [{s16, -32769}]}], 124 | ?assertEqual({error, {integer_too_large, s16, -32769}}, erlang_test:encode(I3)) 125 | end}, 126 | {"u32", fun() -> 127 | I = [{primitivetest, [{s32, 2147483648}]}], 128 | ?assertEqual({error, {integer_too_large, s32, 2147483648}}, erlang_test:encode(I)), 129 | I2 = [{primitivetest, [{s32, -2147483648}]}], 130 | {ok, O} = erlang_test:encode(I2), 131 | ?assertEqual({ok, I2}, erlang_test:decode(list_to_binary(O), primitivetest)), 132 | I3 = [{primitivetest, [{s32, -2147483649}]}], 133 | ?assertEqual({error, {integer_too_large, s32, -2147483649}}, erlang_test:encode(I3)) 134 | end}, 135 | {"u64", fun() -> 136 | I = [{primitivetest, [{s64, 9223372036854775808}]}], 137 | ?assertEqual({error, {integer_too_large, s64, 9223372036854775808}}, erlang_test:encode(I)), 138 | I2 = [{primitivetest, [{s64, -9223372036854775808}]}], 139 | {ok, O} = erlang_test:encode(I2), 140 | ?assertEqual({ok, I2}, erlang_test:decode(list_to_binary(O), primitivetest)), 141 | I3 = [{primitivetest, [{s64, -9223372036854775809}]}], 142 | ?assertEqual({error, {integer_too_large, s64, -9223372036854775809}}, erlang_test:encode(I3)) 143 | end} 144 | ]. 145 | --------------------------------------------------------------------------------