-module(jamdb_sybase_tds_encoder). %% API -export([encode_packets/3]). -export([encode_tokens/1]). -export([prepare_input_dataformat/1]). -include("jamdb_sybase.hrl"). %% API encode_packets(TokenStream, PacketType, PktSize) -> DataSize = PktSize - 8, PktType = case PacketType of normal -> ?TDS_PKT_NORMAL; login -> ?TDS_PKT_LOGIN end, encode_packets(TokenStream, PktType, PktSize, DataSize, <<>>). encode_packets(InStream, PktType, PktSize, DataSize, OutStream) -> case InStream of <> -> OutStream2 = <>, encode_packets(InStream2, PktType, PktSize, DataSize, OutStream2); PktData -> LastPktSize = byte_size(PktData) + 8, <> end. encode_tokens(TokenList) -> encode_tokens(TokenList, {}, <<>>). encode_tokens([Token|TokensList], Previous, TokenStream) -> TokenType = element(1, Token), Data = case TokenType of language -> encode_language_token(Token); message -> encode_message_token(Token); dynamic -> encode_dynamic_token(Token); paramsformat -> encode_paramsformat_token(Token); dbrpc -> encode_dbrpc_token(Token); login -> encode_login_record(Token); logout -> encode_logout_token(Token); params -> {paramsformat, _, Formats} = Previous, encode_params_token(Token, Formats) end, encode_tokens(TokensList, Token, <>); encode_tokens([], _Previous, TokenStream) -> TokenStream. %% internal encode_login_record({login, EnvOpts}) -> {ok, UserHost} = inet:gethostname(), UserPID = os:getpid(), User = proplists:get_value(user, EnvOpts), Pass = proplists:get_value(password, EnvOpts), AppName = proplists:get_value(app_name, EnvOpts, "jamdb"), LibName = proplists:get_value(lib_name, EnvOpts, "beam"), ServerName = proplists:get_value(server_name, EnvOpts, ""), Language = proplists:get_value(language, EnvOpts, "us_english"), Charset = proplists:get_value(charset, EnvOpts, utf8), PacketSize = proplists:get_value(packet_size, EnvOpts, 512), EncPass = proplists:get_value(encrypt_password, EnvOpts, 0), %% TODO << (encode_data(UserHost, ?USER_TYPE_CHAR, 30)):31/binary, (encode_data(User, ?USER_TYPE_CHAR, 30)):31/binary, (if EncPass > 0 -> 0; true -> encode_data(Pass, ?USER_TYPE_CHAR, 30) end):31/binary, (encode_data(UserPID, ?USER_TYPE_CHAR, 30)):31/binary, 2, %% type of int2: bigendian 0, %% type of int4: bigendian 6, %% type of char: ascii 4, %% type of float: bigendian 8, %% type of date: bigendian 1, %% notify of use db 0, %% disallow dump/load and bulk insert 0, %% sql interface type: server's default SQL 0, %% type of network dialog: client 0:56, %% deprecated fields (encode_data(AppName, ?USER_TYPE_CHAR, 30)):31/binary, (encode_data(ServerName, ?USER_TYPE_CHAR, 30)):31/binary, %% Remote password array (used on server-to-server dialogs) (if EncPass > 0 -> 0; true -> encode_data(encode_lrempw(Pass), ?USER_TYPE_CHAR, 255) end):256/binary, 5,0,0,0, %% Protocol version (encode_data(LibName, ?USER_TYPE_CHAR, 10)):11/binary, 16,0,0,1, %% Lib version 0, %% auto convert 4 byte types to 8 byte 12, %% type of 4 byte floating: bigendian 16, %% type of 4 byte datetime: bigendian (encode_data(Language, ?USER_TYPE_CHAR, 30)):31/binary, 1, %% notify on lang change 0:16, %% security fields if EncPass > 0 -> 33; true -> 0 end, 0:80, (encode_data(Charset, ?USER_TYPE_CHAR, 30)):31/binary, 1, %% notify on charset change (encode_data(PacketSize, ?USER_TYPE_CHAR, 6)):7/binary, 0:32, %% something magic (encode_token_capability())/binary >>. encode_logout_token({logout, _Opts}) -> << ?TDS_TOKEN_LOGOUT, 0 %% Options: no options >>. encode_language_token({language, Query}) -> << ?TDS_TOKEN_LANGUAGE, (byte_size(Query)+1):32, %% token length 0, %% status mask: not parameterized query Query/binary >>. encode_message_token({message, Status, MsgId}) -> << ?TDS_TOKEN_MSG, 3, %% length Status, %% status MsgId:16 %% id >>. %% If TDS_PROTO_DYNAMIC (CS_PROTO_DYNAMIC) capability is enabled (1), %% the TDS_DYN_DESCOUT/DESCIN protocol is used tosend input and output formats to a client. %% If TDS_PROTO_DYNAMIC (CS_PROTO_DYNAMIC) capability is disabled (0), %% the format information is sent back automatically by the serverat TDS_DYN_PREPARE time. %% If TDS_PROTO_DYNPROC (CS_PROTO_DYNPROC) capability is enabled (1), %% a client library will prepend “create proc” in the Stmt field of the TDS_DYN_PREPARE data stream. %% If TDS_PROTO_DYNPROC (CS_PROTO_DYNPROC) capability is disabled (0), %% a client library will just send the Stmt information un-modified. encode_dynamic_token({dynamic, Type, Status, Id, Stmt}) -> StmtLen = byte_size(Stmt), IdLen = byte_size(Id), BinType = case Type of prepare -> ?TDS_DYN_PREPARE; execute -> ?TDS_DYN_EXEC; unprepare -> ?TDS_DYN_DEALLOC end, case (StmtLen < 32765 - IdLen) of true -> << ?TDS_TOKEN_DYNAMIC, (5 + IdLen + StmtLen):16, %% Token Length BinType:8, %% type of dynamic operation (encode_bitmask(Status)):8, %% status IdLen:8, %% Length of statement id Id:IdLen/binary, %% statement id StmtLen:16, %% Length of statement Stmt:StmtLen/binary %% statement >>; false -> << ?TDS_TOKEN_DYNAMIC2, (7 + IdLen + StmtLen):32, %% Token Length BinType:8, %% type of dynamic operation (encode_bitmask(Status)):8, %% status IdLen:8, %% Length of statement id Id:IdLen/binary, %% statement id StmtLen:32, %% Length of statement Stmt:StmtLen/binary %% statement >> end. encode_paramsformat_token({paramsformat, ParamsAmount, ParamsFormat}) -> EncodedParamsFormat = encode_paramsformat(ParamsFormat), EncodedParamsFormatLen = byte_size(EncodedParamsFormat), << ?TDS_TOKEN_PARAMFMT, (EncodedParamsFormatLen + 2):16, ParamsAmount:16, EncodedParamsFormat/binary >>. encode_paramsformat(List) -> encode_paramsformat(List, <<>>). encode_paramsformat([#format{ column_name=ParamName, usertype=UserType, datatype_locale=LocaleInfo} = Format|Rest], Encoded) -> ParamNameLen = byte_size(ParamName), LocaleLen = byte_size(LocaleInfo), EncodedParam = << ParamNameLen, ParamName:ParamNameLen/binary, (encode_bitmask([])), UserType:32/signed, (encode_dataformat(Format))/binary, LocaleLen, LocaleInfo:LocaleLen/binary >>, encode_paramsformat(Rest, <>); encode_paramsformat([], Token) -> Token. encode_dataformat(#format{datatype_group=variable, datatype=DataType, datatype_max_len=MaxLen}) -> <>; encode_dataformat(#format{datatype_group=long, datatype=DataType, datatype_max_len=MaxLen}) -> <>; encode_dataformat(#format{datatype_group=decimal, datatype=DataType, datatype_max_len=MaxLen, datatype_precision=Precision, datatype_scale=Scale}) -> <>; encode_dataformat(#format{datatype_group=clob, datatype=DataType, datatype_max_len=MaxLen, datatype_name=Name}) -> NLen = byte_size(Name), <>; encode_dataformat(#format{datatype_group=fixed, datatype=DataType}) -> <>. encode_params_token({params, Params}, Formats) -> << ?TDS_TOKEN_PARAMS, (encode_params(Params, Formats, <<>>))/binary >>. encode_params([Param|Params], [Format|Formats], EncodedParams) -> Encoded = encode_value(Param, Format), encode_params(Params, Formats, <>); encode_params([], _Formats, EncodedParams) -> EncodedParams. encode_value(null, _Format) -> <<0>>; encode_value(Value, #format{datatype=?TDS_TYPE_INTN, datatype_max_len=Len}) -> case Len of 1 -> <<1, Value:1/unsigned-unit:8>>; _ -> <> end; encode_value(Value, #format{datatype=?TDS_TYPE_UINTN, datatype_max_len=Len}) -> <>; encode_value(Value, #format{datatype=?TDS_TYPE_UINT8}) -> <>; encode_value(Decimal, #format{datatype=?TDS_TYPE_NUMN, datatype_max_len=Len}) -> encode_decimal(Decimal, Len); encode_value(Decimal, #format{datatype=?TDS_TYPE_DECN, datatype_max_len=Len}) -> encode_decimal(Decimal, Len); encode_value(Value, #format{datatype=?TDS_TYPE_CHAR, datatype_max_len=Len}) -> encode_binary(Value, Len, basic); encode_value(Value, #format{datatype=?TDS_TYPE_VARCHAR, datatype_max_len=Len}) -> encode_binary(Value, Len, basic); encode_value(Value, #format{datatype=?TDS_TYPE_BINARY, datatype_max_len=Len}) -> encode_binary(Value, Len, basic); encode_value(Value, #format{datatype=?TDS_TYPE_VARBINARY, datatype_max_len=Len}) -> encode_binary(Value, Len, basic); encode_value(Value, #format{datatype=?TDS_TYPE_FLTN, datatype_max_len=Len}) -> <>; encode_value({Date, Time}, #format{datatype=?TDS_TYPE_DATETIMEN, datatype_max_len=Len}) -> case Len of 4 -> DaysSince1900 = encode_date(Date), Seconds = encode_time(Time, seconds), <<4, DaysSince1900:16, Seconds:16>>; 8 -> DaysSince1900 = encode_date(Date), MlSeconds = encode_time(Time, milliseconds), <<8, DaysSince1900:32, MlSeconds:32>> end; encode_value(Date, #format{datatype=?TDS_TYPE_DATEN, datatype_max_len=Len}) -> DaysSince1900 = encode_date(Date), <>; encode_value(Time, #format{datatype=?TDS_TYPE_TIMEN, datatype_max_len=Len}) -> MlSeconds = encode_time(Time, milliseconds), <>; encode_value(Value, #format{datatype=?TDS_TYPE_MONEYN, datatype_max_len=Len}) -> <>; encode_value(Value, #format{datatype=?TDS_TYPE_BIT}) -> <>; encode_value(Value, #format{datatype=?TDS_TYPE_LONGCHAR, datatype_max_len=Len}) -> encode_binary(Value, Len, long); encode_value(Value, #format{datatype=?TDS_TYPE_LONGBINARY, datatype_max_len=Len}) -> encode_binary(Value, Len, long). encode_decimal(Value, MaxLen) when is_integer(Value) andalso Value >= 0 -> Len = MaxLen - 1, <>; encode_decimal(Value, MaxLen) when is_integer(Value) -> Len = MaxLen - 1, <>; encode_decimal({Sign, Value, _}, MaxLen) when is_integer(Sign) andalso ((Sign =:= 1) orelse (Sign =:= 0)) andalso is_integer(Value) andalso Value >= 0 -> Len = MaxLen - 1, <>. encode_date(Date) -> calendar:date_to_gregorian_days(Date) - 693961. encode_time(Time, seconds) -> calendar:time_to_seconds(Time) div 60; encode_time(Time, milliseconds) -> calendar:time_to_seconds(Time) * 300. encode_binary(Value, MaxLen, basic) when is_binary(Value) -> Length = byte_size(Value), case {Length, MaxLen} of {0, _} -> <<1, " ">>; {L, M} when L < M -> <>; {_, _} -> <> end; encode_binary(Value, MaxLen, long) when is_binary(Value) -> Length = byte_size(Value), case {Length, MaxLen} of {0, _} -> <<1:32, " ">>; {L, M} when L < M -> <>; {_, _} -> <> end; encode_binary(Value, MaxLen, Type) when is_list(Value) -> Bin = unicode:characters_to_binary(Value), encode_binary(Bin, MaxLen, Type); encode_binary(Value, MaxLen, Type) when is_atom(Value) -> Bin = atom_to_binary(Value, utf8), encode_binary(Bin, MaxLen, Type); encode_binary(Value, MaxLen, Type) when is_integer(Value) -> Bin = unicode:characters_to_binary(integer_to_list(Value)), encode_binary(Bin, MaxLen, Type). %% The TDS_DBRPC token will be used by clients if the TDS_REQ_PARAM capability bit is true %% The TDS_DBRPC2 token will be used by clients only if the TDS_REQ_DBRPC2 capability bit is true. %% Return parameters will be returned to a client using the TDS_PARAMFMT/PARAMS tokens if the TDS_RES_NOPARAM capability bit is false. encode_dbrpc_token({dbrpc, RpcName, Options}) -> RpcNameLen = byte_size(RpcName), case RpcNameLen < 255 of true -> << ?TDS_TOKEN_DBRPC, (3 + RpcNameLen):16, %% Token Length RpcNameLen:8, %% RPC name length, in bytes RpcName:RpcNameLen/binary, %% RPC Name (encode_bitmask(Options)):16 %% Options bit mask >>; false -> << ?TDS_TOKEN_DBRPC2, (4 + RpcNameLen):16, %% Token Length RpcNameLen:16, %% RPC name length, in bytes RpcName:RpcNameLen/binary, %% RPC Name (encode_bitmask(Options)):16 %% Options >> end. encode_lrempw(Password) -> << 0:8, %% ServerName length: zero mean universal password (encode_data(Password, ?USER_TYPE_VARCHAR, 1))/binary %% Password >>. encode_token_capability() -> ReqGroup = encode_capability_group(?TDS_CAP_REQUEST, ?JAMDB_REQ_CAP), RespGroup = encode_capability_group(?TDS_CAP_RESPONSE, ?JAMDB_RESP_CAP), ReqGroupLen = byte_size(ReqGroup), RespGroupLen = byte_size(RespGroup), << ?TDS_TOKEN_CAPABILITY, (ReqGroupLen + RespGroupLen):16, ReqGroup/binary, RespGroup/binary >>. encode_capability_group(Type, Capabilities) -> IntValueMask = encode_valuemask(Capabilities), BinValueMask = binary:encode_unsigned(IntValueMask), ValueMaskLen = byte_size(BinValueMask), << Type, ValueMaskLen, BinValueMask:ValueMaskLen/binary >>. encode_bitmask(List) -> lists:foldl(fun(X, Result) -> X bor Result end, 0, List). encode_valuemask(List) -> lists:foldl(fun(X, Result) -> (1 bsl X) bor Result end, 0, List). %% TODO data coded by user type or tds type? encode_data(Data, ?USER_TYPE_VARCHAR, Multiplier) when is_binary(Data) -> Length = byte_size(Data), MaxLength = 8 * Multiplier, case Length < MaxLength of true -> <>; false -> <> end; encode_data(Data, ?USER_TYPE_CHAR, MaxLength) when is_binary(Data) -> %% TODO any char data coded with length at the end %% or login record fields only???? Length = byte_size(Data), case Length < MaxLength of true -> EmptyLength = 8 * (MaxLength - Length), <>; false -> <> end; encode_data(Data, UserType, MaxLength) when is_list(Data) -> Binary = unicode:characters_to_binary(Data), encode_data(Binary, UserType, MaxLength); encode_data(Data, UserType, MaxLength) when is_atom(Data) -> Binary = atom_to_binary(Data, utf8), encode_data(Binary, UserType, MaxLength); encode_data(Data, UserType, MaxLength) when is_integer(Data) -> List = integer_to_list(Data), encode_data(List, UserType, MaxLength). prepare_input_dataformat({paramsformat, ParamsAmount, ParamsFormat}) -> ParamsFormat2 = [replace_format(Format) || Format <- ParamsFormat], {paramsformat, ParamsAmount, ParamsFormat2}; prepare_input_dataformat(undefined) -> undefined. replace_format(F = #format{datatype=?TDS_TYPE_INT1}) -> F#format{ datatype=?TDS_TYPE_INTN, datatype_group=variable, datatype_max_len=1 }; replace_format(F = #format{datatype=?TDS_TYPE_INT2}) -> F#format{ datatype=?TDS_TYPE_INTN, datatype_group=variable, datatype_max_len=2 }; replace_format(F = #format{datatype=?TDS_TYPE_INT4}) -> F#format{ datatype=?TDS_TYPE_INTN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_INT8}) -> F#format{ datatype=?TDS_TYPE_INTN, datatype_group=variable, datatype_max_len=8 }; replace_format(F = #format{datatype=?TDS_TYPE_UINT2}) -> F#format{ datatype=?TDS_TYPE_UINTN, datatype_group=variable, datatype_max_len=2 }; replace_format(F = #format{datatype=?TDS_TYPE_UINT4}) -> F#format{ datatype=?TDS_TYPE_UINTN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_UINT8}) -> F#format{ datatype=?TDS_TYPE_UINTN, datatype_group=variable, datatype_max_len=8 }; replace_format(F = #format{datatype=?TDS_TYPE_FLT4}) -> F#format{ datatype=?TDS_TYPE_FLTN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_FLT8}) -> F#format{ datatype=?TDS_TYPE_FLTN, datatype_group=variable, datatype_max_len=8 }; replace_format(F = #format{datatype=?TDS_TYPE_SHORTDATE}) -> F#format{ datatype=?TDS_TYPE_DATETIMEN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_DATETIME}) -> F#format{ datatype=?TDS_TYPE_DATETIMEN, datatype_group=variable, datatype_max_len=8 }; replace_format(F = #format{datatype=?TDS_TYPE_DATE}) -> F#format{ datatype=?TDS_TYPE_DATEN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_TIME}) -> F#format{ datatype=?TDS_TYPE_TIMEN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_SHORTMONEY}) -> F#format{ datatype=?TDS_TYPE_MONEYN, datatype_group=variable, datatype_max_len=4 }; replace_format(F = #format{datatype=?TDS_TYPE_MONEY}) -> F#format{ datatype=?TDS_TYPE_MONEYN, datatype_group=variable, datatype_max_len=8 }; replace_format(F = #format{datatype=?TDS_TYPE_TEXT}) -> F#format{ datatype=?TDS_TYPE_LONGCHAR, datatype_group=long, datatype_max_len=32768 }; replace_format(F = #format{datatype=?TDS_TYPE_UNITEXT}) -> F#format{ datatype=?TDS_TYPE_LONGCHAR, datatype_group=long, datatype_max_len=32768 }; replace_format(F) -> F.