%% msrpce %% %% Copyright 2022 The University of Queensland %% Author: Alex Wilson %% %% Redistribution and use in source and binary forms, with or without %% modification, are permitted provided that the following conditions %% are met: %% 1. Redistributions of source code must retain the above copyright %% notice, this list of conditions and the following disclaimer. %% 2. Redistributions in binary form must reproduce the above copyright %% notice, this list of conditions and the following disclaimer in the %% documentation and/or other materials provided with the distribution. %% %% THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR %% IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES %% OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. %% IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, %% INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT %% NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, %% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY %% THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT %% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF %% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. %% %% @doc Contains type definitions for RPCE data types. %% %% Most of these data types have shorter aliases, which are available by %% including types.hrl and records.hrl: %% %%
%% -include_lib("msrpce/include/records.hrl").
%% -include_lib("msrpce/include/types.hrl").
%% 
%% %% All types have at least their own name aliased by types.hrl, %% so e.g. {@link msrpce:uint8()} is aliased as uint8() with %% no module qualifier. These implied aliases are not shown in the tables %% below. %% %%

Base integer types (and their aliases)

%% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
TypeSignedWidth (bits)Aliases
{@link msrpce:uint8()}no8uchar(), byt(), usmall()
{@link msrpce:uint16()}no16word(), ushort(), wchar()
{@link msrpce:uint32()}no32ulong(), dword()
{@link msrpce:uint64()}no64uhyper(), qword()
{@link msrpce:int8()}yes8chr(), small()
{@link msrpce:int16()}yes16word(), short()
{@link msrpce:int32()}yes32long()
{@link msrpce:int64()}yes64hyper()
%%

String and binary types (and their aliases)

%% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
TypeErlang typeConformantVaryingEncodingAliases
{@link msrpce:bin()}binaryyesyesRaw bytes
{@link msrpce:varying_bin()}binarynoyesRaw bytes
{@link msrpce:fixed_bin()}binarynonoRaw bytes (fixed length)
{@link msrpce:aligned_bin()}binarynonoRaw bytes (fixed length and alignment)
{@link msrpce:str()}string (list or binary)yesyesUTF-8lpstr() (pointer)
{@link msrpce:varying_str()}string (list or binary)noyesUTF-8
{@link msrpce:unicode()}string (list or binary)yesyesUTF16-LElpwstr() (pointer)
{@link msrpce:varying_unicode()}string (list or binary)noyesUTF16-LE
{@link msrpce:uuid()}binary (128-bit)nonoRaw bytes (4-byte aligned)
%%

Array types

%% %% Array types take another MSRPCE type as an argument, and represent an %% array of that underlying type. In Erlang, they are used as a list. %% %% For example, msrpce:array(msrpce:uint16()) is a %% conformant-varying array of 16-bit unsigned integers. In Erlang it could %% be set to e.g. [1,2,3]. %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
TypeConformantVaryingAliases
{@link msrpce:array()}yesyes
{@link msrpce:conformant_array()}yesno
{@link msrpce:varying_array()}noyes
{@link msrpce:fixed_array()}nono
%% %%

Pointers

%% %% There is a single pointer type, {@link msrpce:pointer()}, which takes any %% RPCE type as an argument. It represents just the reference pointer itself %% (no length or size information). It is always nullable (NULL is represented %% by the Erlang atom undefined). %% %% If you have a pointer with size information next to it, the annotation types %% {@link msrpce:size_of()} and {@link msrpce:length_of()} can be used to %% automatically set it. It will still require a separate field in your record. %% %%

Annotation types

%% %% These types wrap another RPCE type and change its behaviour. %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
TypeCan wrapSummary
{@link msrpce:be()}Any typeChanges the wrapped type to big-endian representation
{@link msrpce:le()}Any typeChanges the wrapped type to little-endian representation
{@link msrpce:size_of()}Integer type (other field can be any pointer, array or string)Replaces the value of the wrapped integer with the serialized size of another struct field
{@link msrpce:length_of()}Integer type (other field can be any array or string)Replaces the value of the wrapped integer with the array length of another struct field
{@link msrpce:bitset()}Integer typeRepresents an integer unpacked to a Map of bit fields
{@link msrpce:bitset_mask()}Integer typeLike bitset() but uses masks, not bit numbers
{@link msrpce:custom()}Any typePasses the wrapped type through a custom decode/encode function
%% %%

Other built-in compound types

%% %% These types are defined in types.hrl and may only be used %% by their bare names, after including that header. %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
TypeSerializationErlang representationSummary
sid()SID{@link msrpce:sid()}A MS Security Identifier (SID), e.g. [1,5,...]
rpc_unicode_str()structstring()A RPC_UNICODE_STRING structure
filetime()uint32{@link msrpce:filetime()}File modification timestamp (also used for other timestamps)
ntstatus()uint32{@link msrpce:ntstatus()}Generic status code format used by many parts of Windows
multi_string()pointer(array(uint8()))[string()]Multiple zero-terminated UTF8 strings, with a double-zero-terminator at the end (msz)
multi_unicode()pointer(array(uint16()))[string()]Multiple zero-terminated UTF16 strings, with a double-zero-terminator (4 bytes) at the end
rpc_multi_sz()struct[string()]A RPC_MULTI_SZ structure, containing a multi-string
%% -module(msrpce). -export([ encode_sid/1, decode_sid/1, encode_filetime/1, decode_filetime/1, encode_rpc_unicode/1, decode_rpc_unicode/1, uuid_to_string/1, uuid_from_string/1, encode_ntstatus/1, decode_ntstatus/1, encode_rpc_multi_sz/1, decode_rpc_multi_sz/1, encode_multi_sz/1, decode_multi_sz/1 ]). -export_type([ sid/0, filetime/0, custom/4, builtin/4, ntstatus/0, le/1, be/1 ]). -export_type([ uint8/0, uint16/0, uint32/0, uint64/0, int8/0, int16/0, int32/0, int64/0, bitset/3, bitset_mask/3, bitset_bitmap/0, bitset_maskmap/0, size_of/2, length_of/2 ]). -export_type([ fixed_array/2, conformant_array/1, varying_array/1, array/1, pointer/1, str/0, varying_str/0, unicode/0, bin/0, fixed_bin/1, varying_bin/0, aligned_bin/2, uuid/0, varying_unicode/0 ]). -include("include/records.hrl"). -type sid() :: [integer()]. %% A Microsoft Security Identifier (SID) in numeric form (e.g. [1,5,1234,123]) -type time_unit() :: decimicrosecond | microsecond | millisecond | second. -type filetime() :: null | never | {integer(), time_unit()}. %% Common time specification format used in MSRPCE -type uuid() :: aligned_bin(16, 4). %% A UUID in binary form -type uint8() :: integer(). %% An unsigned 8-bit integer -type uint16() :: integer(). %% An unsigned 16-bit integer -type uint32() :: integer(). %% An unsigned 32-bit integer (also known as a ulong or dword) -type uint64() :: integer(). %% An unsigned 64-bit integer -type int8() :: integer(). %% A signed 8-bit integer -type int16() :: integer(). %% A signed 16-bit integer -type int32() :: integer(). %% A signed 32-bit integer -type int64() :: integer(). %% A signed 64-bit integer -type fixed_array(_N, T) :: [T]. %% A fixed-size array with no length integer included. The first argument %% (N) should be the length as an integer. -type conformant_array(T) :: [T]. %% A conformant array (has "maximum" length, offset and real length, then the %% data). Maximum length may be hoisted. -type varying_array(T) :: [T]. %% A varying array (has only a "maximum" length and then the data) -type array(T) :: [T]. %% A conformant-varying array (the most commonly used kind) -type pointer(T) :: undefined | T. %% A pointer to any RPCE type. A 32-bit pointer value is included in %% the stream and then the actual content of it is serialised at the end. %% %% Pointer values are generated as either 16#00000000 (the %% NULL value for undefined) or %% 16#00020000 bor (Index bsl 2) %% to match the behaviour of the MS IDL compiler in most common cases. -type custom(_Base, RealType, _Encoder, _Decoder) :: RealType. %% Defines a custom extension to a base RPC type. %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
BaseThe base RPCE type (e.g. {@link msrpce:uint16()})
RealTypeActual Erlang type of the final decoded value.
EncoderAtom name of an arity-1 function which encodes the value (takes %% a value of type RealType and converts to type %% Base).
DecoderAtom name of an arity-1 function which decodes the value (takes %% a value of type Base and converts to type %% RealType).
%% %%

Example

%%
%% -type thing() :: msrpce:custom(uint8(), {integer(), integer()},
%%     encode_thing, decode_thing).
%%
%% encode_thing({A, B}) -> A + B * 10.
%% decode_thing(Sum) -> {Sum rem 10, Sum div 10}.
%%
%% -record(foobar, { field :: thing() }).
%% -msrpce_struct(foobar).
%%
%% #foobar{field = {1,3}}       % => encoded as a uint8 of value 13
%% 
-type builtin(_Base, RealType, _Encoder, _Decoder) :: RealType. %% An extension type defined in the msrpce module. -type bitset(_Base, BitName, _BitMap) :: #{BitName => boolean()}. %% An integer which is made up of bits, each representing a boolean flag. %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %% %%
BaseAny unsigned integer type (e.g. {@link msrpce:uint32()})The actual serialised format of this field
BitNameUnion of atom types (e.g. bit_a | bit_b)All the possible bit names used in this set
BitMap{@link msrpce:bitset_bitmap()} (e.g. #{bit_a => 5, bit_b => 0})Map of bit name => bit number
%% %%

Example

%%
%% -type field() :: msrpce:bitset(
%%     msrpce:uint32(),
%%     bit_a | bit_b,
%%     #{bit_a => 0, bit_b => 5}).
%%
%% -record(thing, {
%%     bar :: field()
%%     }).
%% -msrpce_struct(thing).
%%
%% #thing{bar = #{bit_a => true}}   % => encoded as 0x00000001
%% #thing{bar = #{bit_b => true}}   % => encoded as 0x00000020
%% 
-type bitset_mask(_Base, BitName, _MaskMap) :: #{BitName => boolean()}. %% Like a bitset() but the map takes masks rather than bit numbers. %% %%

Example

%%
%% -type field() :: msrpce:bitset_mask(
%%     msrpce:uint32(),
%%     bit_a | bit_b,
%%     #{bit_a => 16#00100000, bit_b => 16#00000020}).
%%
%% -record(thing, {
%%     bar :: field()
%%     }).
%% -msrpce_struct(thing).
%%
%% #thing{bar = #{bit_a => true, bit_b => true}}   % => encoded as 0x00100020
%% 
-type bitnum() :: integer(). %% Bit number, starting at 0 for LSB. -type mask() :: integer(). %% Bit mask, represented as an integer that will be bitwise-OR'd into the %% value if this bit is set. -type bitset_bitmap() :: #{atom() => bitnum()}. %% The type of the BitMap argument to {@link bitset()}. -type bitset_maskmap() :: #{atom() => mask()}. %% The type of the MaskMap argument to {@link bitset_mask()}. -type le(T) :: T. %% Forces the inner type to be little-endian always (ignores the stream %% endian options). -type be(T) :: T. %% Forces the inner type to be big-endian always (ignores the stream %% endian option). -type size_of(_Field, IntType) :: IntType. %% An integer type which represents the encoded byte size of a sibling field %% in the same struct. It will be automatically calculated during encoding. -type length_of(_Field, IntType) :: IntType. %% An integer type which represents the array length of a sibling field in %% the same struct. It will be automatically calculated during encoding. -type bin() :: binary(). %% A conformant-varying binary string, with no terminator. Conformant string %% maximum lengths are not hoisted. -type fixed_bin(_N) :: binary(). %% A fixed-length binary inserted into the stream with alignment 1 and no %% length integer attached. -type aligned_bin(_N, _Align) :: binary(). %% Same as fixed_bin(N) but with the specified alignment. This is %% a useful "escape hatch" for custom types. -type varying_bin() :: binary(). %% A varying binary string. -type str() :: string(). %% A conformant-varying UTF8 string. -type varying_str() :: string(). %% A varying UTF8 string. -type unicode() :: string(). %% A conformant-varying UTF16-LE string. -type varying_unicode() :: string(). %% A varying UTF16-LE string. %% @private -spec encode_sid(sid()) -> #msrpce_sid{}. encode_sid([Rev, IdAuth | SubAuths]) -> #msrpce_sid{revision = Rev, sub_auth_count = length(SubAuths), identifier_auth = <>, sub_auths = SubAuths}. %% @private -spec decode_sid(#msrpce_sid{}) -> sid(). decode_sid(#msrpce_sid{revision = Rev, sub_auth_count = SubAuthCount, identifier_auth = <>, sub_auths = SubAuths}) when (length(SubAuths) == SubAuthCount) -> [Rev, IdAuth | SubAuths]. %% @private -spec encode_filetime(filetime()) -> aligned_bin(16,4). encode_filetime(null) -> <<0:64>>; encode_filetime(never) -> <<16#7fffffffffffffff:64/little>>; encode_filetime({N, decimicrosecond}) -> V = N + 116444736000000000, <>; encode_filetime({N, microsecond}) -> encode_filetime({N * 10, decimicrosecond}); encode_filetime({N, millisecond}) -> encode_filetime({N * 1000, microsecond}); encode_filetime({N, second}) -> encode_filetime({N * 1000, millisecond}). %% @private -spec decode_filetime(aligned_bin(16,4)) -> filetime(). decode_filetime(<<0:64>>) -> null; decode_filetime(<<16#7fffffffffffffff:64/little>>) -> never; decode_filetime(<>) -> DUSec = V - 116444736000000000, case (DUSec rem 10) of 0 -> USec = DUSec div 10, case (USec rem 1000) of 0 -> MSec = USec div 1000, case (MSec rem 1000) of 0 -> Sec = MSec div 1000, {Sec, second}; _ -> {MSec, millisecond} end; _ -> {USec, microsecond} end; _ -> {DUSec, decimicrosecond} end. %% @private -spec encode_rpc_unicode(string()) -> #msrpce_unicode_string{}. encode_rpc_unicode(String) -> Len = string:len(String), #msrpce_unicode_string{len = Len * 2, maxlen = Len * 2, str = String}. %% @private -spec decode_rpc_unicode(#msrpce_unicode_string{}) -> string(). decode_rpc_unicode(#msrpce_unicode_string{len = 0, maxlen = 0, str = _}) -> ""; decode_rpc_unicode(R = #msrpce_unicode_string{len = L, maxlen = MaxL, str = S0}) -> case string:len(S0) of V when (V * 2 =< MaxL) and (V * 2 >= L) -> string:slice(S0, 0, L div 2); _ -> error({bad_rpc_unicode, R}) end. -type string_uuid() :: string(). %% A UUID in string hex form (e.g. "5e8cb9bc-bbc2-38b2-afc9-a9218c3b1d9c") %% @doc Converts a UUID to the standard hex string format. -spec uuid_to_string(uuid()) -> string_uuid(). uuid_to_string(<>) -> string:to_lower(io_lib:format( "~8.16.0B-~4.16.0B-~4.16.0B-~4.16.0B-~12.16.0B", [TimeLow, TimeMid, TimeHiVer, ClockSeq, Node])). %% @doc Parses a hex-string-format UUID and returns a binary. -spec uuid_from_string(string_uuid()) -> uuid(). uuid_from_string(Str0) -> Hex = lists:seq($0, $9) ++ lists:seq($a, $f), Str1 = string:to_lower(Str0), {TimeLowHex, [$- | Str2]} = string:take(Str1, Hex), {TimeMidHex, [$- | Str3]} = string:take(Str2, Hex), {TimeHighVerHex, [$- | Str4]} = string:take(Str3, Hex), {ClockSeqHex, [$- | Str5]} = string:take(Str4, Hex), {NodeHex, []} = string:take(Str5, Hex), <<(binary_to_integer(iolist_to_binary([TimeLowHex]), 16)):32/big, (binary_to_integer(iolist_to_binary([TimeMidHex]), 16)):16/big, (binary_to_integer(iolist_to_binary([TimeHighVerHex]), 16)):16/big, (binary_to_integer(iolist_to_binary([ClockSeqHex]), 16)):16/big, (binary_to_integer(iolist_to_binary([NodeHex]), 16)):48/big>>. -type ntstatus() :: {ntstatus:severity(), ntstatus:code() | integer()}. %% windows NT status return value %% @doc Converts an NTSTATUS value from integer to atom/tuple form. -spec decode_ntstatus(uint32()) -> ntstatus(). decode_ntstatus(Int) -> Sev = case (Int bsr 30) band 3 of 0 -> success; 1 -> info; 2 -> warn; 3 -> error end, Code = ntstatus:int_to_code(Int), {Sev, Code}. %% @doc Converts an NTSTATUS value from atom/tuple form to an integer. -spec encode_ntstatus(ntstatus()) -> uint32(). encode_ntstatus({Sev, Code}) -> Int0 = if is_atom(Code) -> ntstatus:code_to_int(Code); is_integer(Code) -> Code end, SevMask = case Sev of success -> 0; info -> 1 bsl 30; warn -> 2 bsl 30; error -> 3 bsl 30 end, Int0 bor SevMask. %% @private decode_rpc_multi_sz(#msrpce_multi_sz{value = Arr, nchar = N}) -> N = length(Arr), string:lexemes(Arr, [0]). %% @private encode_rpc_multi_sz(Strings) -> Arr = lists:flatten(lists:join(0, Strings) ++ [0,0]), #msrpce_multi_sz{value = Arr, nchar = length(Arr)}. %% @private decode_multi_sz(Arr) -> string:lexemes(Arr, [0]). %% @private encode_multi_sz(Strings) -> lists:flatten(lists:join(0, Strings) ++ [0,0]).