summaryrefslogtreecommitdiff
path: root/deps/oauth
diff options
context:
space:
mode:
Diffstat (limited to 'deps/oauth')
-rw-r--r--deps/oauth/Emakefile1
-rw-r--r--deps/oauth/License.txt22
-rw-r--r--deps/oauth/Makefile7
-rw-r--r--deps/oauth/README.txt36
-rw-r--r--deps/oauth/src/oauth.app.src21
-rw-r--r--deps/oauth/src/oauth.erl107
-rw-r--r--deps/oauth/src/oauth_client.erl149
-rw-r--r--deps/oauth/src/oauth_hmac_sha1.erl11
-rw-r--r--deps/oauth/src/oauth_http.erl22
-rw-r--r--deps/oauth/src/oauth_plaintext.erl10
-rw-r--r--deps/oauth/src/oauth_rsa_sha1.erl30
-rw-r--r--deps/oauth/src/oauth_unix.erl16
-rw-r--r--deps/oauth/src/oauth_uri.erl98
13 files changed, 530 insertions, 0 deletions
diff --git a/deps/oauth/Emakefile b/deps/oauth/Emakefile
new file mode 100644
index 00000000..a961122c
--- /dev/null
+++ b/deps/oauth/Emakefile
@@ -0,0 +1 @@
+{"src/*", [debug_info, warn_unused_vars, warn_unused_import, {outdir, "ebin"}]}. \ No newline at end of file
diff --git a/deps/oauth/License.txt b/deps/oauth/License.txt
new file mode 100644
index 00000000..08b71726
--- /dev/null
+++ b/deps/oauth/License.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2008-2009 Tim Fletcher <http://tfletcher.com/>
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/deps/oauth/Makefile b/deps/oauth/Makefile
new file mode 100644
index 00000000..f6aedc90
--- /dev/null
+++ b/deps/oauth/Makefile
@@ -0,0 +1,7 @@
+all:
+ @test -d ebin || mkdir ebin
+ @cp src/oauth.app ebin/
+ @erl -make
+
+clean:
+ @rm -rf ebin/* erl_crash.dump
diff --git a/deps/oauth/README.txt b/deps/oauth/README.txt
new file mode 100644
index 00000000..0145e451
--- /dev/null
+++ b/deps/oauth/README.txt
@@ -0,0 +1,36 @@
+An Erlang OAuth implementation.
+
+Quick start (client usage):
+
+ $ make
+ ...
+ $ erl -pa ebin -s crypto -s inets
+ ...
+ 1> Consumer = {"key", "secret", hmac_sha1}.
+ ...
+ 2> RequestTokenURL = "http://term.ie/oauth/example/request_token.php".
+ ...
+ 3> {ok, ResponseR} = oauth:get(RequestTokenURL, [], Consumer, "", "").
+ ...
+ 4> ParamsR = oauth_http:response_params(ResponseR).
+ ...
+ 5> TokenR = oauth:token(ParamsR).
+ ...
+ 6> TokenSecretR = oauth:token_secret(ParamsR).
+ ...
+ 7> AccessTokenURL = "http://term.ie/oauth/example/access_token.php".
+ ...
+ 8> {ok, ResponseA} = oauth:get(AccessTokenURL, [], Consumer, TokenR, TokenSecretR).
+ ...
+
+
+Thanks to Jason Davies, Paul Bonser, and Roberto Aloi for their patches.
+
+The percent encoding/decoding implementations are based on those found in
+the ibrowse library, written by Chandrashekhar Mullaparthi.
+
+Example client/server code is at http://github.com/tim/erlang-oauth-examples.
+
+Unit tests are at http://github.com/tim/erlang-oauth-tests.
+
+Erlang R12B-5 or greater is required for RSA-SHA1.
diff --git a/deps/oauth/src/oauth.app.src b/deps/oauth/src/oauth.app.src
new file mode 100644
index 00000000..9ff22b1a
--- /dev/null
+++ b/deps/oauth/src/oauth.app.src
@@ -0,0 +1,21 @@
+{application, oauth, [
+ {description, "An Erlang OAuth 1.0 implementation"},
+ {vsn, git},
+ {modules, [
+ oauth,
+ oauth_client,
+ oauth_hmac_sha1,
+ oauth_http,
+ oauth_plaintext,
+ oauth_rsa_sha1,
+ oauth_unix,
+ oauth_uri
+ ]},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ crypto,
+ inets
+ ]}
+]}.
diff --git a/deps/oauth/src/oauth.erl b/deps/oauth/src/oauth.erl
new file mode 100644
index 00000000..866655c9
--- /dev/null
+++ b/deps/oauth/src/oauth.erl
@@ -0,0 +1,107 @@
+-module(oauth).
+
+-export(
+ [ get/5
+ , header/1
+ , post/5
+ , signature/5
+ , signature_base_string/3
+ , signed_params/6
+ , token/1
+ , token_secret/1
+ , uri/2
+ , verify/6
+ ]).
+
+
+get(URL, ExtraParams, Consumer, Token, TokenSecret) ->
+ SignedParams = signed_params("GET", URL, ExtraParams, Consumer, Token, TokenSecret),
+ oauth_http:get(uri(URL, SignedParams)).
+
+post(URL, ExtraParams, Consumer, Token, TokenSecret) ->
+ SignedParams = signed_params("POST", URL, ExtraParams, Consumer, Token, TokenSecret),
+ oauth_http:post(URL, oauth_uri:params_to_string(SignedParams)).
+
+uri(Base, []) ->
+ Base;
+uri(Base, Params) ->
+ lists:concat([Base, "?", oauth_uri:params_to_string(Params)]).
+
+header(Params) ->
+ {"Authorization", "OAuth " ++ oauth_uri:params_to_header_string(Params)}.
+
+token(Params) ->
+ proplists:get_value("oauth_token", Params).
+
+token_secret(Params) ->
+ proplists:get_value("oauth_token_secret", Params).
+
+verify(Signature, HttpMethod, URL, Params, Consumer, TokenSecret) ->
+ case signature_method(Consumer) of
+ plaintext ->
+ oauth_plaintext:verify(Signature, consumer_secret(Consumer), TokenSecret);
+ hmac_sha1 ->
+ BaseString = signature_base_string(HttpMethod, URL, Params),
+ oauth_hmac_sha1:verify(Signature, BaseString, consumer_secret(Consumer), TokenSecret);
+ rsa_sha1 ->
+ BaseString = signature_base_string(HttpMethod, URL, Params),
+ oauth_rsa_sha1:verify(Signature, BaseString, consumer_secret(Consumer))
+ end.
+
+signed_params(HttpMethod, URL, ExtraParams, Consumer, Token, TokenSecret) ->
+ Params = token_param(Token, params(Consumer, ExtraParams)),
+ [{"oauth_signature", signature(HttpMethod, URL, Params, Consumer, TokenSecret)}|Params].
+
+signature(HttpMethod, URL, Params, Consumer, TokenSecret) ->
+ case signature_method(Consumer) of
+ plaintext ->
+ oauth_plaintext:signature(consumer_secret(Consumer), TokenSecret);
+ hmac_sha1 ->
+ BaseString = signature_base_string(HttpMethod, URL, Params),
+ oauth_hmac_sha1:signature(BaseString, consumer_secret(Consumer), TokenSecret);
+ rsa_sha1 ->
+ BaseString = signature_base_string(HttpMethod, URL, Params),
+ oauth_rsa_sha1:signature(BaseString, consumer_secret(Consumer))
+ end.
+
+signature_base_string(HttpMethod, URL, Params) ->
+ NormalizedURL = oauth_uri:normalize(URL),
+ NormalizedParams = oauth_uri:params_to_string(lists:sort(Params)),
+ oauth_uri:calate("&", [HttpMethod, NormalizedURL, NormalizedParams]).
+
+token_param("", Params) ->
+ Params;
+token_param(Token, Params) ->
+ [{"oauth_token", Token}|Params].
+
+params(Consumer, Params) ->
+ Nonce = base64:encode_to_string(crypto:rand_bytes(32)), % cf. ruby-oauth
+ params(Consumer, oauth_unix:timestamp(), Nonce, Params).
+
+params(Consumer, Timestamp, Nonce, Params) ->
+ [ {"oauth_version", "1.0"}
+ , {"oauth_nonce", Nonce}
+ , {"oauth_timestamp", integer_to_list(Timestamp)}
+ , {"oauth_signature_method", signature_method_string(Consumer)}
+ , {"oauth_consumer_key", consumer_key(Consumer)}
+ | Params
+ ].
+
+signature_method_string(Consumer) ->
+ case signature_method(Consumer) of
+ plaintext ->
+ "PLAINTEXT";
+ hmac_sha1 ->
+ "HMAC-SHA1";
+ rsa_sha1 ->
+ "RSA-SHA1"
+ end.
+
+signature_method(_Consumer={_, _, Method}) ->
+ Method.
+
+consumer_secret(_Consumer={_, Secret, _}) ->
+ Secret.
+
+consumer_key(_Consumer={Key, _, _}) ->
+ Key.
diff --git a/deps/oauth/src/oauth_client.erl b/deps/oauth/src/oauth_client.erl
new file mode 100644
index 00000000..2bd24c03
--- /dev/null
+++ b/deps/oauth/src/oauth_client.erl
@@ -0,0 +1,149 @@
+-module(oauth_client).
+
+-behaviour(gen_server).
+
+-export([access_token_params/1, deauthorize/1, get/2, get/3, get/4, get_access_token/2,
+ get_access_token/3, get_access_token/4, get_request_token/2, get_request_token/3,
+ get_request_token/4, start/1, start/2, start_link/1, start_link/2, stop/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]).
+
+%%============================================================================
+%% API functions
+%%============================================================================
+
+start(Consumer) ->
+ gen_server:start(?MODULE, Consumer, []).
+
+start(ServerName, Consumer) ->
+ gen_server:start(ServerName, ?MODULE, Consumer, []).
+
+start_link(Consumer) ->
+ gen_server:start_link(?MODULE, Consumer, []).
+
+start_link(ServerName, Consumer) ->
+ gen_server:start_link(ServerName, ?MODULE, Consumer, []).
+
+get_request_token(Client, URL) ->
+ get_request_token(Client, URL, [], header).
+
+get_request_token(Client, URL, Params) ->
+ gen_server:call(Client, {get_request_token, URL, Params, header}).
+
+get_request_token(Client, URL, Params, ParamsMethod) ->
+ gen_server:call(Client, {get_request_token, URL, Params, ParamsMethod}).
+
+get_access_token(Client, URL) ->
+ get_access_token(Client, URL, [], header).
+
+get_access_token(Client, URL, Params) ->
+ gen_server:call(Client, {get_access_token, URL, Params, header}).
+
+get_access_token(Client, URL, Params, ParamsMethod) ->
+ gen_server:call(Client, {get_access_token, URL, Params, ParamsMethod}).
+
+get(Client, URL) ->
+ get(Client, URL, [], header).
+
+get(Client, URL, Params) ->
+ gen_server:call(Client, {get, URL, Params, header}).
+
+get(Client, URL, Params, ParamsMethod) ->
+ gen_server:call(Client, {get, URL, Params, ParamsMethod}).
+
+access_token_params(Client) ->
+ gen_server:call(Client, {access_token_params}).
+
+deauthorize(Client) ->
+ gen_server:cast(Client, deauthorize).
+
+stop(Client) ->
+ gen_server:cast(Client, stop).
+
+%%============================================================================
+%% Helper functions
+%%============================================================================
+
+oauth_get(header, URL, Params, Consumer, Token, TokenSecret) ->
+ Signed = oauth:signed_params("GET", URL, Params, Consumer, Token, TokenSecret),
+ {AuthorizationParams, QueryParams} = lists:partition(fun({K, _}) -> lists:prefix("oauth_", K) end, Signed),
+ Request = {oauth:uri(URL, QueryParams), [oauth:header(AuthorizationParams)]},
+ httpc:request(get, Request, [{autoredirect, false}], []);
+oauth_get(querystring, URL, Params, Consumer, Token, TokenSecret) ->
+ oauth:get(URL, Params, Consumer, Token, TokenSecret).
+
+%%============================================================================
+%% gen_server callbacks
+%%============================================================================
+
+init(Consumer) ->
+ {ok, {Consumer}}.
+
+handle_call({get_request_token, URL, Params, ParamsMethod}, _From, State={Consumer}) ->
+ case oauth_get(ParamsMethod, URL, Params, Consumer, "", "") of
+ {ok, Response} ->
+ case oauth_http:response_code(Response) of
+ 200 ->
+ RParams = oauth_http:response_params(Response),
+ {reply, {ok, oauth:token(RParams)}, {Consumer, RParams}};
+ _ ->
+ {reply, Response, State}
+ end;
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({get_access_token, URL, Params, ParamsMethod}, _From, State={Consumer, RParams}) ->
+ case oauth_get(ParamsMethod, URL, Params, Consumer, oauth:token(RParams), oauth:token_secret(RParams)) of
+ {ok, Response} ->
+ case oauth_http:response_code(Response) of
+ 200 ->
+ AParams = oauth_http:response_params(Response),
+ {reply, ok, {Consumer, RParams, AParams}};
+ _ ->
+ {reply, Response, State}
+ end;
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({get, URL, Params, ParamsMethod}, _From, State={Consumer, _RParams, AParams}) ->
+ case oauth_get(ParamsMethod, URL, Params, Consumer, oauth:token(AParams), oauth:token_secret(AParams)) of
+ {ok, Response={{_, Status, _}, Headers, Body}} ->
+ case Status of
+ 200 ->
+ case proplists:get_value("content-type", Headers) of
+ undefined ->
+ {reply, {ok, Headers, Body}, State};
+ ContentType ->
+ MediaType = hd(string:tokens(ContentType, ";")),
+ case lists:suffix("/xml", MediaType) orelse lists:suffix("+xml", MediaType) of
+ true ->
+ {XML, []} = xmerl_scan:string(Body),
+ {reply, {ok, Headers, XML}, State};
+ false ->
+ {reply, {ok, Headers, Body}, State}
+ end
+ end;
+ _ ->
+ {reply, Response, State}
+ end;
+ Error ->
+ {reply, Error, State}
+ end;
+handle_call({access_token_params}, _From, State={_Consumer, _RParams, AParams}) ->
+ {reply, AParams, State}.
+
+handle_cast(deauthorize, {Consumer, _RParams}) ->
+ {noreply, {Consumer}};
+handle_cast(deauthorize, {Consumer, _RParams, _AParams}) ->
+ {noreply, {Consumer}};
+handle_cast(stop, State) ->
+ {stop, normal, State}.
+
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(normal, _State) ->
+ ok.
diff --git a/deps/oauth/src/oauth_hmac_sha1.erl b/deps/oauth/src/oauth_hmac_sha1.erl
new file mode 100644
index 00000000..79d59f37
--- /dev/null
+++ b/deps/oauth/src/oauth_hmac_sha1.erl
@@ -0,0 +1,11 @@
+-module(oauth_hmac_sha1).
+
+-export([signature/3, verify/4]).
+
+
+signature(BaseString, CS, TS) ->
+ Key = oauth_uri:calate("&", [CS, TS]),
+ base64:encode_to_string(crypto:sha_mac(Key, BaseString)).
+
+verify(Signature, BaseString, CS, TS) ->
+ couch_util:verify(signature(BaseString, CS, TS), Signature).
diff --git a/deps/oauth/src/oauth_http.erl b/deps/oauth/src/oauth_http.erl
new file mode 100644
index 00000000..92c806cc
--- /dev/null
+++ b/deps/oauth/src/oauth_http.erl
@@ -0,0 +1,22 @@
+-module(oauth_http).
+
+-export([get/1, post/2, response_params/1, response_body/1, response_code/1]).
+
+
+get(URL) ->
+ request(get, {URL, []}).
+
+post(URL, Data) ->
+ request(post, {URL, [], "application/x-www-form-urlencoded", Data}).
+
+request(Method, Request) ->
+ httpc:request(Method, Request, [{autoredirect, false}], []).
+
+response_params(Response) ->
+ oauth_uri:params_from_string(response_body(Response)).
+
+response_body({{_, _, _}, _, Body}) ->
+ Body.
+
+response_code({{_, Code, _}, _, _}) ->
+ Code.
diff --git a/deps/oauth/src/oauth_plaintext.erl b/deps/oauth/src/oauth_plaintext.erl
new file mode 100644
index 00000000..41a1e9b2
--- /dev/null
+++ b/deps/oauth/src/oauth_plaintext.erl
@@ -0,0 +1,10 @@
+-module(oauth_plaintext).
+
+-export([signature/2, verify/3]).
+
+
+signature(CS, TS) ->
+ oauth_uri:calate("&", [CS, TS]).
+
+verify(Signature, CS, TS) ->
+ couch_util:verify(signature(CS, TS), Signature).
diff --git a/deps/oauth/src/oauth_rsa_sha1.erl b/deps/oauth/src/oauth_rsa_sha1.erl
new file mode 100644
index 00000000..6f4828e0
--- /dev/null
+++ b/deps/oauth/src/oauth_rsa_sha1.erl
@@ -0,0 +1,30 @@
+-module(oauth_rsa_sha1).
+
+-export([signature/2, verify/3]).
+
+-include_lib("public_key/include/public_key.hrl").
+
+
+signature(BaseString, PrivateKeyPath) ->
+ {ok, [Info]} = public_key:pem_to_der(PrivateKeyPath),
+ {ok, PrivateKey} = public_key:decode_private_key(Info),
+ base64:encode_to_string(public_key:sign(list_to_binary(BaseString), PrivateKey)).
+
+verify(Signature, BaseString, PublicKey) ->
+ public_key:verify_signature(to_binary(BaseString), sha, base64:decode(Signature), public_key(PublicKey)).
+
+to_binary(Term) when is_list(Term) ->
+ list_to_binary(Term);
+to_binary(Term) when is_binary(Term) ->
+ Term.
+
+public_key(Path) when is_list(Path) ->
+ {ok, [{cert, DerCert, not_encrypted}]} = public_key:pem_to_der(Path),
+ {ok, Cert} = public_key:pkix_decode_cert(DerCert, otp),
+ public_key(Cert);
+public_key(#'OTPCertificate'{tbsCertificate=Cert}) ->
+ public_key(Cert);
+public_key(#'OTPTBSCertificate'{subjectPublicKeyInfo=Info}) ->
+ public_key(Info);
+public_key(#'OTPSubjectPublicKeyInfo'{subjectPublicKey=Key}) ->
+ Key.
diff --git a/deps/oauth/src/oauth_unix.erl b/deps/oauth/src/oauth_unix.erl
new file mode 100644
index 00000000..73ca3143
--- /dev/null
+++ b/deps/oauth/src/oauth_unix.erl
@@ -0,0 +1,16 @@
+-module(oauth_unix).
+
+-export([timestamp/0]).
+
+
+timestamp() ->
+ timestamp(calendar:universal_time()).
+
+timestamp(DateTime) ->
+ seconds(DateTime) - epoch().
+
+epoch() ->
+ seconds({{1970,1,1},{00,00,00}}).
+
+seconds(DateTime) ->
+ calendar:datetime_to_gregorian_seconds(DateTime).
diff --git a/deps/oauth/src/oauth_uri.erl b/deps/oauth/src/oauth_uri.erl
new file mode 100644
index 00000000..5023f983
--- /dev/null
+++ b/deps/oauth/src/oauth_uri.erl
@@ -0,0 +1,98 @@
+-module(oauth_uri).
+
+-export([normalize/1, calate/2, encode/1]).
+-export([params_from_string/1, params_to_string/1,
+ params_from_header_string/1, params_to_header_string/1]).
+
+-import(lists, [concat/1]).
+
+
+normalize(URI) ->
+ case http_uri:parse(URI) of
+ {Scheme, UserInfo, Host, Port, Path, _Query} ->
+ normalize(Scheme, UserInfo, string:to_lower(Host), Port, [Path]);
+ Else ->
+ Else
+ end.
+
+normalize(http, UserInfo, Host, 80, Acc) ->
+ normalize(http, UserInfo, [Host|Acc]);
+normalize(https, UserInfo, Host, 443, Acc) ->
+ normalize(https, UserInfo, [Host|Acc]);
+normalize(Scheme, UserInfo, Host, Port, Acc) ->
+ normalize(Scheme, UserInfo, [Host, ":", Port|Acc]).
+
+normalize(Scheme, [], Acc) ->
+ concat([Scheme, "://"|Acc]);
+normalize(Scheme, UserInfo, Acc) ->
+ concat([Scheme, "://", UserInfo, "@"|Acc]).
+
+params_to_header_string(Params) ->
+ intercalate(", ", [concat([encode(K), "=\"", encode(V), "\""]) || {K, V} <- Params]).
+
+params_from_header_string(String) ->
+ [param_from_header_string(Param) || Param <- re:split(String, ",\\s*", [{return, list}]), Param =/= ""].
+
+param_from_header_string(Param) ->
+ [Key, QuotedValue] = string:tokens(Param, "="),
+ Value = string:substr(QuotedValue, 2, length(QuotedValue) - 2),
+ {decode(Key), decode(Value)}.
+
+params_from_string(Params) ->
+ [param_from_string(Param) || Param <- string:tokens(Params, "&")].
+
+param_from_string(Param) ->
+ list_to_tuple([decode(Value) || Value <- string:tokens(Param, "=")]).
+
+params_to_string(Params) ->
+ intercalate("&", [calate("=", [K, V]) || {K, V} <- Params]).
+
+calate(Sep, Xs) ->
+ intercalate(Sep, [encode(X) || X <- Xs]).
+
+intercalate(Sep, Xs) ->
+ concat(intersperse(Sep, Xs)).
+
+intersperse(_, []) -> [];
+intersperse(_, [X]) -> [X];
+intersperse(Sep, [X|Xs]) ->
+ [X, Sep|intersperse(Sep, Xs)].
+
+-define(is_alphanum(C), C >= $A, C =< $Z; C >= $a, C =< $z; C >= $0, C =< $9).
+
+encode(Term) when is_integer(Term) ->
+ integer_to_list(Term);
+encode(Term) when is_atom(Term) ->
+ encode(atom_to_list(Term));
+encode(Term) when is_list(Term) ->
+ encode(lists:reverse(Term, []), []).
+
+encode([X | T], Acc) when ?is_alphanum(X); X =:= $-; X =:= $_; X =:= $.; X =:= $~ ->
+ encode(T, [X | Acc]);
+encode([X | T], Acc) ->
+ NewAcc = [$%, dec2hex(X bsr 4), dec2hex(X band 16#0f) | Acc],
+ encode(T, NewAcc);
+encode([], Acc) ->
+ Acc.
+
+decode(Str) when is_list(Str) ->
+ decode(Str, []).
+
+decode([$%, A, B | T], Acc) ->
+ decode(T, [(hex2dec(A) bsl 4) + hex2dec(B) | Acc]);
+decode([X | T], Acc) ->
+ decode(T, [X | Acc]);
+decode([], Acc) ->
+ lists:reverse(Acc, []).
+
+-compile({inline, [{dec2hex, 1}, {hex2dec, 1}]}).
+
+dec2hex(N) when N >= 10 andalso N =< 15 ->
+ N + $A - 10;
+dec2hex(N) when N >= 0 andalso N =< 9 ->
+ N + $0.
+
+hex2dec(C) when C >= $A andalso C =< $F ->
+ C - $A + 10;
+hex2dec(C) when C >= $0 andalso C =< $9 ->
+ C - $0.