%% vim: tabstop=8:shiftwidth=4
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%
-module(asn1ct_check).
%% Main Module for ASN.1 compile time functions
-export([check/2,storeindb/2,format_error/1]).
-include("asn1_records.hrl").
%%% The tag-number for universal types
-define(N_BOOLEAN, 1).
-define(N_INTEGER, 2).
-define(N_BIT_STRING, 3).
-define(N_OCTET_STRING, 4).
-define(N_NULL, 5).
-define(N_OBJECT_IDENTIFIER, 6).
-define(N_OBJECT_DESCRIPTOR, 7).
-define(N_EXTERNAL, 8). % constructed
-define(N_INSTANCE_OF,8).
-define(N_REAL, 9).
-define(N_ENUMERATED, 10).
-define(N_EMBEDDED_PDV, 11). % constructed
-define(N_UTF8String, 12).
-define('N_RELATIVE-OID',13).
-define(N_SEQUENCE, 16).
-define(N_SET, 17).
-define(N_NumericString, 18).
-define(N_PrintableString, 19).
-define(N_TeletexString, 20).
-define(N_VideotexString, 21).
-define(N_IA5String, 22).
-define(N_UTCTime, 23).
-define(N_GeneralizedTime, 24).
-define(N_GraphicString, 25).
-define(N_VisibleString, 26).
-define(N_GeneralString, 27).
-define(N_UniversalString, 28).
-define(N_CHARACTER_STRING, 29). % constructed
-define(N_BMPString, 30).
-define(TAG_PRIMITIVE(Num),
#tag{class='UNIVERSAL',number=Num,type='IMPLICIT',form=0}).
-define(TAG_CONSTRUCTED(Num),
#tag{class='UNIVERSAL',number=Num,type='IMPLICIT',form=32}).
%% used in check_type to update type and tag
-record(newt,{type=unchanged,tag=unchanged,constraint=unchanged,inlined=no}).
check(S,{Types,Values,ParameterizedTypes,Classes,Objects,ObjectSets}) ->
%%Predicates used to filter errors
TupleIs = fun({T,_},T) -> true;
(_,_) -> false
end,
IsClass = fun(X) -> TupleIs(X,asn1_class) end,
IsObjSet = fun(X) -> TupleIs(X,objectsetdef) end,
IsPObjSet = fun(X) -> TupleIs(X,pobjectsetdef) end,
IsObject = fun(X) -> TupleIs(X,objectdef) end,
IsValueSet = fun(X) -> TupleIs(X,valueset) end,
Element2 = fun(X) -> element(2,X) end,
Element1 = fun(X) -> element(1,X) end,
%% initialize internal book keeping
save_asn1db_uptodate(S,S#state.erule,S#state.mname),
put(top_module,S#state.mname),
ParamError = checkp(S, ParameterizedTypes), %must do this before the templates are used
%% table to save instances of parameterized objects,object sets
asn1ct_table:new(parameterized_objects),
asn1ct_table:new(inlined_objects),
Terror = checkt(S, Types),
?dbg("checkt finished with errors:~n~p~n~n",[Terror]),
%% get parameterized object sets sent to checkt/3
%% and update Terror
{PObjSetNames1,Terror2} = filter_errors(IsPObjSet,Terror),
Verror = checkv(S, Values ++ ObjectSets), %value sets may be parsed as object sets
?dbg("checkv finished with errors:~n~p~n~n",[Verror]),
%% get information object classes wrongly sent to checkt/3
%% and update Terror2
{AddClasses,Terror3} = filter_errors(IsClass,Terror2),
NewClasses = Classes++AddClasses,
Cerror = checkc(S, NewClasses),
?dbg("checkc finished with errors:~n~p~n~n",[Cerror]),
%% get object sets incorrectly sent to checkv/3
%% and update Verror
{ObjSetNames,Verror2} = filter_errors(IsObjSet,Verror),
%% get parameterized object sets incorrectly sent to checkv/3
%% and update Verror2
{PObjSetNames,Verror3} = filter_errors(IsPObjSet,Verror2),
%% get objects incorrectly sent to checkv/3
%% and update Verror3
{ObjectNames,Verror4} = filter_errors(IsObject,Verror3),
NewObjects = Objects++ObjectNames,
NewObjectSets = ObjSetNames ++ PObjSetNames ++ PObjSetNames1,
%% get value sets
%% and update Verror4
{ValueSetNames,Verror5} = filter_errors(IsValueSet,Verror4),
{Oerror,ExclO,ExclOS} = checko(S,NewObjects ++
NewObjectSets,
[],[],[]),
?dbg("checko finished with errors:~n~p~n~n",[Oerror]),
InlinedObjTuples = asn1ct_table:to_list(inlined_objects),
InlinedObjects = lists:map(Element2,InlinedObjTuples),
asn1ct_table:delete(inlined_objects),
ParameterizedElems = asn1ct_table:to_list(parameterized_objects),
ParObjectSets = lists:filter(fun({_OSName,objectset,_}) -> true;
(_)-> false end,ParameterizedElems),
ParObjectSetNames = lists:map(Element1,ParObjectSets),
ParTypes = lists:filter(fun({_TypeName,type,_}) -> true;
(_) -> false end, ParameterizedElems),
ParTypesNames = lists:map(Element1,ParTypes),
asn1ct_table:delete(parameterized_objects),
put(asn1_reference,undefined),
Exporterror = check_exports(S,S#state.module),
ImportError = check_imports(S,S#state.module),
AllErrors = lists:flatten([ParamError,Terror3,Verror5,Cerror,
Oerror,Exporterror,ImportError]),
case AllErrors of
[] ->
ContextSwitchTs = context_switch_in_spec(),
InstanceOf = instance_of_in_spec(S#state.mname),
NewTypes = lists:subtract(Types,AddClasses) ++ ContextSwitchTs
++ InstanceOf ++ ParTypesNames,
NewValues = lists:subtract(Values,PObjSetNames++ObjectNames++
ValueSetNames),
{ok,
{NewTypes,NewValues,ParameterizedTypes,
NewClasses,NewObjects,NewObjectSets},
{NewTypes,NewValues,ParameterizedTypes,NewClasses,
lists:subtract(NewObjects,ExclO)++InlinedObjects,
lists:subtract(NewObjectSets,ExclOS)++ParObjectSetNames}};
_ ->
{error,AllErrors}
end.
context_switch_in_spec() ->
L = [{external,'EXTERNAL'},
{embedded_pdv,'EMBEDDED PDV'},
{character_string,'CHARACTER STRING'}],
F = fun({T,TName},Acc) ->
case get(T) of
generate -> erase(T),
[TName|Acc];
_ -> Acc
end
end,
lists:foldl(F,[],L).
instance_of_in_spec(ModName) ->
case get(instance_of) of
L when is_list(L) ->
case lists:member(ModName,L) of
true ->
erase(instance_of),
['INSTANCE OF'];
_ ->
erase(instance_of),
[]
end;
_ ->
[]
end.
instance_of_decl(ModName) ->
Mods = get_instance_of(),
case lists:member(ModName,Mods) of
true ->
ok;
_ ->
put(instance_of,[ModName|Mods])
end.
get_instance_of() ->
case get(instance_of) of
undefined ->
[];
L ->
L
end.
put_once(T,State) ->
%% state is one of undefined, unchecked, generate
%% undefined > unchecked > generate
case get(T) of
PrevS when PrevS > State ->
put(T,State);
_ ->
ok
end.
filter_errors(Pred,ErrorList) ->
Element2 = fun(X) -> element(2,X) end,
RemovedTupleElements = lists:filter(Pred,ErrorList),
RemovedNames = lists:map(Element2,RemovedTupleElements),
%% remove value set name tuples from Verror
RestErrors = lists:subtract(ErrorList,RemovedTupleElements),
{RemovedNames,RestErrors}.
check_exports(S,Module = #module{}) ->
case Module#module.exports of
{exports,[]} ->
[];
{exports,all} ->
[];
{exports,ExportList} when is_list(ExportList) ->
IsNotDefined =
fun(X) ->
try
_ = get_referenced_type(S,X),
false
catch {error,_} ->
true
end
end,
[return_asn1_error(S, Ext, {undefined_export, Undef}) ||
Ext = #'Externaltypereference'{type=Undef} <- ExportList,
IsNotDefined(Ext)]
end.
check_imports(S, #module{imports={imports,Imports}}) ->
check_imports_1(S, Imports, []).
check_imports_1(_S, [], Acc) ->
Acc;
check_imports_1(S, [#'SymbolsFromModule'{symbols=Imports,module=ModuleRef}|SFMs], Acc) ->
Module = name_of_def(ModuleRef),
Refs = [{try get_referenced_type(S, Ref)
catch throw:Error -> Error end,
Ref}
|| Ref <- Imports],
CreateError = fun(Ref) ->
Error = {undefined_import,name_of_def(Ref),Module},
return_asn1_error(S, Ref, Error)
end,
Errors = [CreateError(Ref) || {{error, _}, Ref} <- Refs],
check_imports_1(S, SFMs, Errors ++ Acc).
checkt(S0, Names) ->
Check = fun do_checkt/3,
%% NOTE: check_type/3 will store information in the process
%% dictionary if context switching types are encountered;
%% therefore we must force the evaluation order.
Types = check_fold(S0, Names, Check),
CtxtSwitch = check_contextswitchingtypes(S0, []),
check_fold(S0, lists:reverse(CtxtSwitch), Check) ++ Types.
do_checkt(S, Name, #typedef{typespec=TypeSpec}=Type0) ->
NewS = S#state{tname=Name},
try check_type(NewS, Type0, TypeSpec) of
#type{}=Ts ->
case Type0#typedef.checked of
true -> %already checked and updated
ok;
_ ->
Type = Type0#typedef{checked=true,
typespec=Ts},
asn1_db:dbput(NewS#state.mname,
Name, Type),
ok
end
catch
{error,Reason} ->
Reason;
{asn1_class,_ClassDef} ->
{asn1_class,Name};
pobjectsetdef ->
{pobjectsetdef,Name};
pvalueset ->
{pvalueset,Name}
end.
check_contextswitchingtypes(S,Acc) ->
CSTList=[{external,'EXTERNAL'},
{embedded_pdv,'EMBEDDED PDV'},
{character_string,'CHARACTER STRING'}],
check_contextswitchingtypes(S,CSTList,Acc).
check_contextswitchingtypes(S,[{T,TName}|Ts],Acc) ->
case get(T) of
unchecked ->
put(T,generate),
check_contextswitchingtypes(S,Ts,[TName|Acc]);
_ ->
check_contextswitchingtypes(S,Ts,Acc)
end;
check_contextswitchingtypes(_,[],Acc) ->
Acc.
checkv(S, Names) ->
check_fold(S, Names, fun do_checkv/3).
do_checkv(S, Name, Value)
when is_record(Value, valuedef);
is_record(Value, typedef); %Value set may be parsed as object set.
is_record(Value, pvaluedef);
is_record(Value, pvaluesetdef) ->
try check_value(S, Value) of
{valueset,VSet} ->
Pos = asn1ct:get_pos_of_def(Value),
CheckedVSDef = #typedef{checked=true,pos=Pos,
name=Name,typespec=VSet},
asn1_db:dbput(S#state.mname, Name, CheckedVSDef),
{valueset,Name};
V ->
%% update the valuedef
asn1_db:dbput(S#state.mname, Name, V),
ok
catch
{error,Reason} ->
Reason;
{pobjectsetdef} ->
{pobjectsetdef,Name};
{objectsetdef} ->
{objectsetdef,Name};
{asn1_class, _} ->
%% this is an object, save as typedef
#valuedef{checked=C,pos=Pos,name=N,type=Type,
value=Def} = Value,
ClassName = Type#type.def,
NewSpec = #'Object'{classname=ClassName,def=Def},
NewDef = #typedef{checked=C,pos=Pos,name=N,typespec=NewSpec},
asn1_db:dbput(S#state.mname, Name, NewDef),
{objectdef,Name}
end.
%% Check parameterized types.
checkp(S, Names) ->
check_fold(S, Names, fun do_checkp/3).
do_checkp(S0, Name, #ptypedef{typespec=TypeSpec}=Type0) ->
S = S0#state{tname=Name},
try check_ptype(S, Type0, TypeSpec) of
#type{}=Ts ->
Type = Type0#ptypedef{checked=true,typespec=Ts},
asn1_db:dbput(S#state.mname, Name, Type),
ok
catch
{error,Reason} ->
Reason;
{asn1_class,_ClassDef} ->
{asn1_class,Name};
{asn1_param_class,_} ->
ok
end.
%% Check class definitions.
checkc(S, Names) ->
check_fold(S, Names, fun do_checkc/3).
do_checkc(S, Name, Class) ->
try
case is_classname(Name) of
false ->
asn1_error(S, {illegal_class_name,Name});
true ->
do_checkc_1(S, Name, Class)
end
catch {error,Reason} -> Reason
end.
do_checkc_1(S, Name, #classdef{}=Class) ->
C = check_class(S, Class),
store_class(S, true, Class#classdef{typespec=C}, Name),
ok;
do_checkc_1(S, Name, #typedef{typespec=#type{def=Def}=TS}) ->
C = check_class(S, TS),
{Mod,Pos} = case Def of
#'Externaltypereference'{module=M, pos=P} ->
{M,P};
{pt, #'Externaltypereference'{module=M, pos=P}, _} ->
{M,P}
end,
Class = #classdef{name=Name, typespec=C, pos=Pos, module=Mod},
store_class(S, true, Class, Name),
ok.
%% is_classname(Atom) -> true|false.
is_classname(Name) when is_atom(Name) ->
lists:all(fun($-) -> true;
(D) when $0 =< D, D =< $9 -> true;
(UC) when $A =< UC, UC =< $Z -> true;
(_) -> false
end, atom_to_list(Name)).
checko(S0,[Name|Os],Acc,ExclO,ExclOS) ->
Item = asn1_db:dbget(S0#state.mname, Name),
S = S0#state{error_context=Item},
try checko_1(S, Item, Name, ExclO, ExclOS) of
{NewExclO,NewExclOS} ->
checko(S, Os, Acc, NewExclO, NewExclOS)
catch
throw:{error, Error} ->
checko(S, Os, [Error|Acc], ExclO, ExclOS)
end;
checko(_S,[],Acc,ExclO,ExclOS) ->
{lists:reverse(Acc),lists:reverse(ExclO),lists:reverse(ExclOS)}.
checko_1(S, #typedef{typespec=TS}=Object, Name, ExclO, ExclOS) ->
NewS = S#state{tname=Name},
O = check_object(NewS, Object, TS),
NewObj = Object#typedef{checked=true,typespec=O},
asn1_db:dbput(NewS#state.mname, Name, NewObj),
case O of
#'Object'{gen=true} ->
{ExclO,ExclOS};
#'Object'{gen=false} ->
{[Name|ExclO],ExclOS};
#'ObjectSet'{gen=true} ->
{ExclO,ExclOS};
#'ObjectSet'{gen=false} ->
{ExclO,[Name|ExclOS]}
end;
checko_1(S, #pobjectdef{}=PObject, Name, ExclO, ExclOS) ->
NewS = S#state{tname=Name},
PO = check_pobject(NewS, PObject),
NewPObj = PObject#pobjectdef{def=PO},
asn1_db:dbput(NewS#state.mname, Name, NewPObj),
{[Name|ExclO],ExclOS};
checko_1(S, #pvaluesetdef{}=PObjSet, Name, ExclO, ExclOS) ->
NewS = S#state{tname=Name},
POS = check_pobjectset(NewS, PObjSet),
asn1_db:dbput(NewS#state.mname, Name, POS),
{ExclO,[Name|ExclOS]}.
check_class(S,CDef=#classdef{checked=Ch,name=Name,typespec=TS}) ->
case Ch of
true -> TS;
idle -> TS;
_ ->
store_class(S,idle,CDef,Name),
CheckedTS = check_class(S,TS),
store_class(S,true,CDef#classdef{typespec=CheckedTS},Name),
CheckedTS
end;
check_class(S = #state{mname=M,tname=T},ClassSpec)
when is_record(ClassSpec,type) ->
Def = ClassSpec#type.def,
case Def of
#'Externaltypereference'{module=M,type=T} ->
#objectclass{fields=Def}; % in case of recursive definitions
Tref = #'Externaltypereference'{type=TName} ->
{MName,RefType} = get_referenced_type(S,Tref),
#classdef{} = CD = get_class_def(S, RefType),
NewState = update_state(S#state{tname=TName}, MName),
check_class(NewState, CD);
{pt,ClassRef,Params} ->
%% parameterized class
{_,PClassDef} = get_referenced_type(S,ClassRef),
NewParaList = match_parameters(S, Params),
instantiate_pclass(S,PClassDef,NewParaList)
end;
check_class(S, #objectclass{}=C) ->
check_objectclass(S, C);
check_class(S,ClassName) ->
{RefMod,Def} = get_referenced_type(S,ClassName),
case Def of
ClassDef when is_record(ClassDef,classdef) ->
case ClassDef#classdef.checked of
true ->
ClassDef#classdef.typespec;
idle ->
ClassDef#classdef.typespec;
false ->
Name=ClassName#'Externaltypereference'.type,
store_class(S,idle,ClassDef,Name),
NewS = update_state(S#state{tname=Name}, RefMod),
CheckedTS = check_class(NewS,ClassDef#classdef.typespec),
store_class(S,true,ClassDef#classdef{typespec=CheckedTS},Name),
CheckedTS
end;
TypeDef when is_record(TypeDef,typedef) ->
%% this case may occur when a definition is a reference
%% to a class definition.
case TypeDef#typedef.typespec of
#type{def=Ext} when is_record(Ext,'Externaltypereference') ->
check_class(S,Ext)
end
end.
check_objectclass(S, #objectclass{fields=Fs0,syntax=Syntax0}=C) ->
Fs = check_class_fields(S, Fs0),
case Syntax0 of
{'WITH SYNTAX',Syntax1} ->
Syntax = preprocess_syntax(S, Syntax1, Fs),
C#objectclass{fields=Fs,syntax={preprocessed_syntax,Syntax}};
_ ->
C#objectclass{fields=Fs}
end.
instantiate_pclass(S=#state{parameters=_OldArgs},PClassDef,Params) ->
#ptypedef{args=Args,typespec=Type} = PClassDef,
MatchedArgs = match_args(S,Args, Params, []),
NewS = S#state{parameters=MatchedArgs,abscomppath=[]},
check_class(NewS,#classdef{name=S#state.tname,typespec=Type}).
store_class(S,Mode,ClassDef,ClassName) ->
NewCDef = ClassDef#classdef{checked=Mode},
asn1_db:dbput(S#state.mname,ClassName,NewCDef).
check_class_fields(S,Fields) ->
check_class_fields(S,Fields,[]).
check_class_fields(S,[F|Fields],Acc) ->
NewField =
case element(1,F) of
fixedtypevaluefield ->
{_,Name,Type,Unique,OSpec} = F,
case {Unique,OSpec} of
{'UNIQUE',{'DEFAULT',_}} ->
asn1_error(S, {unique_and_default,Name});
{_,_} ->
ok
end,
RefType = check_type(S,#typedef{typespec=Type},Type),
{fixedtypevaluefield,Name,RefType,Unique,OSpec};
object_or_fixedtypevalue_field ->
{_,Name,Type,Unique,OSpec} = F,
Type2 = maybe_unchecked_OCFT(S,Type),
Cat =
case asn1ct_gen:type(asn1ct_gen:get_inner(Type2#type.def)) of
Def when is_record(Def,'Externaltypereference') ->
{_,D} = get_referenced_type(S, Def, true),
D;
{undefined,user} ->
%% neither of {primitive,bif} or {constructed,bif}
{_,D} = get_referenced_type(S,#'Externaltypereference'{module=S#state.mname,type=Type#type.def}),
D;
_ ->
Type
end,
case Cat of
Class when is_record(Class,classdef) ->
%% Type must be a referenced type => change it
%% to an external reference.
ToExt = fun(#type{def= CE = #'Externaltypereference'{}}) -> CE; (T) -> T end,
{objectfield,Name,ToExt(Type),Unique,OSpec};
_ ->
RefType = check_type(S,#typedef{typespec=Type},Type),
{fixedtypevaluefield,Name,RefType,Unique,OSpec}
end;
objectset_or_fixedtypevalueset_field ->
{_,Name,Type,OSpec} = F,
RefType =
try check_type(S,#typedef{typespec=Type},Type) of
#type{} = CheckedType ->
CheckedType
catch {asn1_class,_ClassDef} ->
case if_current_checked_type(S,Type) of
true -> Type#type.def;
_ -> check_class(S,Type)
end
end,
if
is_record(RefType,'Externaltypereference') ->
{objectsetfield,Name,Type,OSpec};
is_record(RefType,classdef) ->
{objectsetfield,Name,Type,OSpec};
is_record(RefType,objectclass) ->
{objectsetfield,Name,Type,OSpec};
true ->
{fixedtypevaluesetfield,Name,RefType,OSpec}
end;
typefield ->
case F of
{TF,Name,{'DEFAULT',Type}} ->
{TF,Name,{'DEFAULT',check_type(S,#typedef{typespec=Type},Type)}};
_ -> F
end;
_ -> F
end,
check_class_fields(S,Fields,[NewField|Acc]);
check_class_fields(_S,[],Acc) ->
lists:reverse(Acc).
maybe_unchecked_OCFT(S,Type) ->
case Type#type.def of
#'ObjectClassFieldType'{type=undefined} ->
check_type(S,#typedef{typespec=Type},Type);
_ ->
Type
end.
if_current_checked_type(S,#type{def=Def}) ->
CurrentModule = S#state.mname,
CurrentCheckedName = S#state.tname,
MergedModules = S#state.inputmodules,
case Def of
#'Externaltypereference'{module=CurrentModule,
type=CurrentCheckedName} ->
true;
#'Externaltypereference'{module=ModuleName,
type=CurrentCheckedName} ->
case MergedModules of
undefined ->
false;
_ ->
lists:member(ModuleName,MergedModules)
end;
_ ->
false
end.
check_pobject(_S,PObject) when is_record(PObject,pobjectdef) ->
Def = PObject#pobjectdef.def,
Def.
check_pobjectset(S,PObjSet) ->
#pvaluesetdef{pos=Pos,name=Name,args=Args,type=Type,
valueset=ValueSet}=PObjSet,
{Mod,Def} = get_referenced_type(S,Type#type.def),
case Def of
#classdef{} ->
ClassName = #'Externaltypereference'{module=Mod,
type=get_datastr_name(Def)},
{valueset,Set} = ValueSet,
ObjectSet = #'ObjectSet'{class=ClassName,
set=Set},
#pobjectsetdef{pos=Pos,name=Name,args=Args,class=Type#type.def,
def=ObjectSet};
_ ->
PObjSet
end.
-record(osi, %Object set information.
{st,
classref,
uniq,
ext
}).
check_object(_S,ObjDef,ObjSpec) when (ObjDef#typedef.checked == true) ->
ObjSpec;
check_object(S,_ObjDef,#'Object'{classname=ClassRef,def=ObjectDef}) ->
?dbg("check_object ~p~n",[ObjectDef]),
_ = check_externaltypereference(S,ClassRef),
{ClassDef, NewClassRef} =
case get_referenced_type(S, ClassRef, true) of
{MName,#classdef{checked=false, name=CLName}=ClDef} ->
Type = ClassRef#'Externaltypereference'.type,
NewState = update_state(S#state{tname=Type}, MName),
ObjClass = check_class(NewState, ClDef),
{ClDef#classdef{checked=true, typespec=ObjClass},
#'Externaltypereference'{module=MName, type=CLName}};
{MName,#classdef{name=CLName}=ClDef} ->
{ClDef, #'Externaltypereference'{module=MName, type=CLName}};
_ ->
asn1_error(S, illegal_object)
end,
NewObj =
case ObjectDef of
{object,_,_}=Def ->
NewSettingList = check_objectdefn(S,Def,ClassDef),
#'Object'{def=NewSettingList};
{po,{object,DefObj},ArgsList} ->
{_,Object} = get_referenced_type(S,DefObj),%DefObj is a
%%#'Externalvaluereference' or a #'Externaltypereference'
%% Maybe this call should be catched and in case of an exception
%% a not initialized parameterized object should be returned.
instantiate_po(S,ClassDef,Object,ArgsList);
{pv,{simpledefinedvalue,ObjRef},ArgList} ->
{_,Object} = get_referenced_type(S,ObjRef),
instantiate_po(S,ClassDef,Object,ArgList);
#'Externalvaluereference'{} ->
{_,Object} = get_referenced_type(S,ObjectDef),
check_object(S, Object, object_to_check(S, Object));
[] ->
%% An object with no fields (parsed as a value).
Def = {object,defaultsyntax,[]},
NewSettingList = check_objectdefn(S, Def, ClassDef),
#'Object'{def=NewSettingList};
_ ->
asn1_error(S, illegal_object)
end,
Fields = (ClassDef#classdef.typespec)#objectclass.fields,
Gen = gen_incl(S,NewObj#'Object'.def, Fields),
NewObj#'Object'{classname=NewClassRef,gen=Gen};
check_object(S, _, #'ObjectSet'{class=ClassRef0,set=Set0}=ObjSet0) ->
{_,ClassDef} = get_referenced_type(S, ClassRef0),
ClassRef = check_externaltypereference(S, ClassRef0),
{UniqueFieldName,UniqueInfo} =
case get_unique_fieldname(S, ClassDef) of
no_unique -> {{unique,undefined},{unique,undefined}};
Other -> {element(1,Other),Other}
end,
OSI0 = #osi{st=S,classref=ClassRef,uniq=UniqueInfo,ext=false},
{Set1,OSI1} = if
is_list(Set0) ->
check_object_set_list(Set0, OSI0);
true ->
check_object_set(Set0, OSI0)
end,
Ext = case Set1 of
[] ->
%% FIXME: X420 does not compile unless we force
%% empty sets to be extensible. There should be
%% a better way.
true;
[_|_] ->
OSI1#osi.ext
end,
Set2 = remove_duplicate_objects(S, Set1),
Set = case Ext of
false -> Set2;
true -> Set2 ++ ['EXTENSIONMARK']
end,
ObjSet = ObjSet0#'ObjectSet'{uniquefname=UniqueFieldName,set=Set},
Gen = gen_incl_set(S, Set, ClassDef),
ObjSet#'ObjectSet'{class=ClassRef,gen=Gen}.
check_object_set({element_set,Root0,Ext0}, OSI0) ->
OSI = case Ext0 of
none -> OSI0;
_ -> OSI0#osi{ext=true}
end,
case {Root0,Ext0} of
{empty,empty} -> {[],OSI};
{empty,Ext} -> check_object_set(Ext, OSI);
{Root,none} -> check_object_set(Root, OSI);
{Root,empty} -> check_object_set(Root, OSI);
{Root,Ext} -> check_object_set_list([Root,Ext], OSI)
end;
check_object_set(#'Externaltypereference'{}=Ref, #osi{st=S}=OSI) ->
{_,#typedef{typespec=OSdef}=OS} = get_referenced_type(S, Ref),
ObjectSet = check_object(S, OS, OSdef),
check_object_set_objset(ObjectSet, OSI);
check_object_set(#'Externalvaluereference'{}=Ref, #osi{st=S}=OSI) ->
{RefedMod,ObjName,#'Object'{def=Def}} = check_referenced_object(S, Ref),
ObjList = check_object_set_mk(RefedMod, ObjName, Def, OSI),
{ObjList,OSI};
check_object_set({'EXCEPT',Incl0,Excl0}, OSI) ->
{Incl1,_} = check_object_set(Incl0, OSI),
{Excl1,_} = check_object_set(Excl0, OSI),
Exclude = sofs:set([N || {N,_} <- Excl1], [name]),
Incl2 = [{Name,Obj} || {Name,_,_}=Obj <- Incl1],
Incl3 = sofs:relation(Incl2, [{name,object}]),
Incl4 = sofs:drestriction(Incl3, Exclude),
Incl5 = sofs:to_external(Incl4),
Incl = [Obj || {_,Obj} <- Incl5],
{Incl,OSI};
check_object_set({object,_,_}=Obj0, OSI) ->
#osi{st=S,classref=ClassRef} = OSI,
#'Object'{def=Def} =
check_object(S, #typedef{typespec=Obj0},
#'Object'{classname=ClassRef,def=Obj0}),
ObjList = check_object_set_mk(Def, OSI),
{ObjList,OSI};
check_object_set(#'ObjectClassFieldType'{classname=ObjName,
fieldname=FieldNames},
#osi{st=S}=OSI) ->
Set = check_ObjectSetFromObjects(S, ObjName, FieldNames),
check_object_set_objset_list(Set, OSI);
check_object_set({'ObjectSetFromObjects',Obj,FieldNames}, #osi{st=S}=OSI) ->
ObjName = element(tuple_size(Obj), Obj),
Set = check_ObjectSetFromObjects(S, ObjName, FieldNames),
check_object_set_objset_list(Set, OSI);
check_object_set({pt,DefinedObjSet,ParamList0}, OSI) ->
#osi{st=S,classref=ClassRef} = OSI,
{_,PObjSetDef} = get_referenced_type(S, DefinedObjSet),
ParamList = match_parameters(S, ParamList0),
ObjectSet = instantiate_pos(S, ClassRef, PObjSetDef, ParamList),
check_object_set_objset(ObjectSet, OSI);
check_object_set({pos,{objectset,_,DefinedObjSet},Params0}, OSI) ->
#osi{st=S,classref=ClassRef} = OSI,
{_,PObjSetDef} = get_referenced_type(S, DefinedObjSet),
Params = match_parameters(S, Params0),
ObjectSet = instantiate_pos(S, ClassRef, PObjSetDef, Params),
check_object_set_objset(ObjectSet, OSI);
check_object_set({pv,{simpledefinedvalue,DefinedObject},Params}=PV, OSI) ->
#osi{st=S,classref=ClassRef} = OSI,
Args = match_parameters(S, Params),
#'Object'{def=Def} =
check_object(S, PV,
#'Object'{classname=ClassRef ,
def={po,{object,DefinedObject},Args}}),
ObjList = check_object_set_mk(Def, OSI),
{ObjList,OSI};
check_object_set({'SingleValue',Val}, OSI) ->
check_object_set(Val, OSI);
check_object_set({'ValueFromObject',{object,Object},FieldNames}, OSI) ->
#osi{st=S} = OSI,
case extract_field(S, Object, FieldNames) of
#'Object'{def=Def} ->
ObjList = check_object_set_mk(Def, OSI),
{ObjList,OSI};
_ ->
asn1_error(S, illegal_object)
end;
check_object_set(#type{def=Def}, OSI) ->
check_object_set(Def, OSI);
check_object_set({union,A0,B0}, OSI0) ->
{A,OSI1} = check_object_set(A0, OSI0),
{B,OSI} = check_object_set(B0, OSI1),
{A++B,OSI}.
check_object_set_list([H|T], OSI0) ->
{Set0,OSI1} = check_object_set(H, OSI0),
{Set1,OSI2} = check_object_set_list(T, OSI1),
{Set0++Set1,OSI2};
check_object_set_list([], OSI) ->
{[],OSI}.
check_object_set_objset(#'ObjectSet'{set=Set}, OSI) ->
check_object_set_objset_list(Set, OSI).
check_object_set_objset_list(Set, OSI) ->
check_object_set_objset_list_1(Set, OSI, []).
check_object_set_objset_list_1(['EXTENSIONMARK'|T], OSI, Acc) ->
check_object_set_objset_list_1(T, OSI#osi{ext=true}, Acc);
check_object_set_objset_list_1([H|T], OSI, Acc) ->
check_object_set_objset_list_1(T, OSI, [H|Acc]);
check_object_set_objset_list_1([], OSI, Acc) ->
{Acc,OSI}.
check_object_set_mk(Fields, OSI) ->
check_object_set_mk(no_mod, no_name, Fields, OSI).
check_object_set_mk(M, N, Def, #osi{uniq={unique,undefined}}) ->
{_,_,Fields} = Def,
[{{M,N},no_unique_value,Fields}];
check_object_set_mk(M, N, Def, #osi{uniq={UniqField,_}}) ->
{_,_,Fields} = Def,
case lists:keyfind(UniqField, 1, Fields) of
{UniqField,#valuedef{value=Val}} ->
[{{M,N},Val,Fields}];
false ->
case Fields of
[{_,#typedef{typespec=#'ObjectSet'{set=['EXTENSIONMARK']}}}] ->
%% FIXME: If object is missing the unique field and
%% only contains a reference to an empty object set,
%% we will remove the entire object as a workaround
%% to get X420 to compile. There should be a better
%% way.
[];
_ ->
[{{M,N},no_unique_value,Fields}]
end
end.
%% remove_duplicate_objects/1 remove duplicates of objects.
%% For instance may Set contain objects of same class from
%% different object sets that in fact might be duplicates.
remove_duplicate_objects(S, Set0) when is_list(Set0) ->
Set1 = [{Id,Orig} || {_,Id,_}=Orig <- Set0],
Set2 = sofs:relation(Set1),
Set3 = sofs:relation_to_family(Set2),
Set = sofs:to_external(Set3),
remove_duplicate_objects_1(S, Set).
remove_duplicate_objects_1(S, [{no_unique_value,Objs}|T]) ->
Objs ++ remove_duplicate_objects_1(S, T);
remove_duplicate_objects_1(S, [{_,[_]=Objs}|T]) ->
Objs ++ remove_duplicate_objects_1(S, T);
remove_duplicate_objects_1(S, [{Id,[_|_]=Objs}|T]) ->
MakeSortable = fun(What) -> sortable_type(S, What) end,
Tagged = order_tag_set(Objs, MakeSortable),
case lists:ukeysort(1, Tagged) of
[{_,Obj}] ->
[Obj|remove_duplicate_objects_1(S, T)];
[_|_] ->
asn1_error(S, {non_unique_object,Id})
end;
remove_duplicate_objects_1(_, []) ->
[].
order_tag_set([{_, _, Fields}=Orig|Fs], Fun) ->
Pair = {[{FId, traverse(F, Fun)} || {FId, F} <- Fields], Orig},
[Pair|order_tag_set(Fs, Fun)];
order_tag_set([], _) -> [].
sortable_type(S, #'Externaltypereference'{}=ERef) ->
try get_referenced_type(S, ERef) of
{_,#typedef{}=OI} ->
OI#typedef{pos=undefined,name=undefined}
catch
_:_ ->
ERef
end;
sortable_type(_, #typedef{}=TD) ->
asn1ct:unset_pos_mod(TD#typedef{name=undefined});
sortable_type(_, Type) ->
asn1ct:unset_pos_mod(Type).
traverse(Structure0, Fun) ->
Structure = Fun(Structure0),
traverse_1(Structure, Fun).
traverse_1(#typedef{typespec=TS0} = TD, Fun) ->
TS = traverse(TS0, Fun),
TD#typedef{typespec=TS};
traverse_1(#valuedef{type=TS0} = VD, Fun) ->
TS = traverse(TS0, Fun),
VD#valuedef{type=TS};
traverse_1(#type{def=TS0} = TD, Fun) ->
TS = traverse(TS0, Fun),
TD#type{def=TS};
traverse_1(#'SEQUENCE'{components=Cs0} = Seq, Fun) ->
Cs = traverse_seq_set(Cs0, Fun),
Seq#'SEQUENCE'{components=Cs};
traverse_1({'SEQUENCE OF',Type0}, Fun) ->
Type = traverse(Type0, Fun),
{'SEQUENCE OF',Type};
traverse_1({'SET OF',Type0}, Fun) ->
Type = traverse(Type0, Fun),
{'SET OF',Type};
traverse_1(#'SET'{components=Cs0} = Set, Fun) ->
Cs = traverse_seq_set(Cs0, Fun),
Set#'SET'{components=Cs};
traverse_1({'CHOICE', Cs0}, Fun) ->
Cs = traverse_seq_set(Cs0, Fun),
{'CHOICE', Cs};
traverse_1(Leaf, _) ->
Leaf.
traverse_seq_set(List, Fun) when is_list(List) ->
traverse_seq_set_1(List, Fun);
traverse_seq_set({Set, Ext}, Fun) ->
{traverse_seq_set_1(Set, Fun), traverse_seq_set_1(Ext, Fun)};
traverse_seq_set({Set1, Set2, Set3}, Fun) ->
{traverse_seq_set_1(Set1, Fun),
traverse_seq_set_1(Set2, Fun),
traverse_seq_set_1(Set3, Fun)}.
traverse_seq_set_1([#'ComponentType'{} = CT0|Cs], Fun) ->
CT = #'ComponentType'{typespec=TS0} = Fun(CT0),
TS = traverse(TS0, Fun),
[CT#'ComponentType'{typespec=TS}|traverse_seq_set_1(Cs, Fun)];
traverse_seq_set_1([{'COMPONENTS OF', _} = CO0|Cs], Fun) ->
{'COMPONENTS OF', TS0} = Fun(CO0),
TS = traverse(TS0, Fun),
[{'COMPONENTS OF', TS}|traverse_seq_set_1(Cs, Fun)];
traverse_seq_set_1([], _) ->
[].
object_to_check(_, #typedef{typespec=ObjDef}) ->
ObjDef;
object_to_check(S, #valuedef{type=Class,value=ObjectRef}) ->
%% If the object definition is parsed as an object the ClassName
%% is parsed as a type.
case Class of
#type{def=#'Externaltypereference'{}=Def} ->
#'Object'{classname=Def,def=ObjectRef};
_ ->
asn1_error(S, illegal_object)
end.
check_referenced_object(S,ObjRef)
when is_record(ObjRef,'Externalvaluereference')->
case get_referenced_type(S,ObjRef) of
{RefedMod,ObjectDef} when is_record(ObjectDef,valuedef) ->
?dbg("Externalvaluereference, ObjectDef: ~p~n",[ObjectDef]),
#type{def=ClassRef} = ObjectDef#valuedef.type,
Def = ObjectDef#valuedef.value,
{RefedMod,get_datastr_name(ObjectDef),
check_object(update_state(S,RefedMod),ObjectDef,#'Object'{classname=ClassRef,
def=Def})};
{RefedMod,ObjectDef} when is_record(ObjectDef,typedef) ->
{RefedMod,get_datastr_name(ObjectDef),
check_object(update_state(S,RefedMod),ObjectDef,ObjectDef#typedef.typespec)}
end.
check_ObjectSetFromObjects(S, ObjName, Fields) ->
{_,Obj0} = get_referenced_type(S, ObjName),
case check_object(S, Obj0, Obj0#typedef.typespec) of
#'ObjectSet'{}=Obj1 ->
get_fieldname_set(S, Obj1, Fields);
#'Object'{classname=Class,
def={object,_,ObjFs}} ->
ObjSet = #'ObjectSet'{class=Class,
set=[{'_','_',ObjFs}]},
get_fieldname_set(S, ObjSet, Fields)
end.
%% get_type_from_object(State, ObjectOrObjectSet, [{RefType,FieldName}]) ->
%% Type
get_type_from_object(S, Object, FieldNames)
when is_record(Object, 'Externaltypereference');
is_record(Object, 'Externalvaluereference') ->
extract_field(S, Object, FieldNames).
%% get_value_from_object(State, ObjectOrObjectSet, [{RefType,FieldName}]) ->
%% UntaggedValue
get_value_from_object(S, Def, FieldNames) ->
case extract_field(S, Def, FieldNames) of
#valuedef{value=Val} ->
Val;
{valueset,_}=Val ->
Val;
_ ->
asn1_error(S, illegal_value)
end.
%% extract_field(State, ObjectOrObjectSet, [{RefType,FieldName}])
%% RefType = typefieldreference | valuefieldreference
%%
%% Get the type, value, object, object set, or value set from the
%% referenced object or object set. The list of field name tuples
%% may have more than one element. All field names but the last
%% refers to either an object or object set.
extract_field(S, Def0, FieldNames) ->
{_,Def1} = get_referenced_type(S, Def0),
Def2 = check_object(S, Def1, Def1#typedef.typespec),
Def = Def1#typedef{typespec=Def2},
get_fieldname_element(S, Def, FieldNames).
%% get_fieldname_element(State, Element, [{RefType,FieldName}]
%% RefType = typefieldreference | valuefieldreference
%%
%% Get the type, value, object, object set, or value set from the referenced
%% element. The list of field name tuples may have more than one element.
%% All field names but the last refers to either an object or object set.
get_fieldname_element(S, Object0, [{_RefType,FieldName}|Fields]) ->
Object = case Object0 of
#typedef{typespec=#'Object'{def=Obj}} -> Obj;
{_,_,_}=Obj -> Obj
end,
case check_fieldname_element(S, FieldName, Object) of
#'Object'{def=D} when Fields =/= [] ->
get_fieldname_element(S, D, Fields);
#'ObjectSet'{}=Set ->
get_fieldname_set(S, Set, Fields);
Result when Fields =:= [] ->
Result
end;
get_fieldname_element(_S, Def, []) ->
Def.
get_fieldname_set(S, #'ObjectSet'{set=Set0}, T) ->
get_fieldname_set_1(S, Set0, T, []).
get_fieldname_set_1(S, ['EXTENSIONMARK'=Ext|T], Fields, Acc) ->
get_fieldname_set_1(S, T, Fields, [Ext|Acc]);
get_fieldname_set_1(S, [H|T], Fields, Acc) ->
try get_fieldname_element(S, H, Fields) of
L when is_list(L) ->
get_fieldname_set_1(S, T, Fields, L++Acc);
{valueset,L} ->
get_fieldname_set_1(S, T, Fields, L++Acc);
Other ->
get_fieldname_set_1(S, T, Fields, [Other|Acc])
catch
throw:{error,_} ->
get_fieldname_set_1(S, T, Fields, Acc)
end;
get_fieldname_set_1(_, [], _Fields, Acc) ->
case Acc of
[#valuedef{}|_] ->
{valueset,Acc};
_ ->
Acc
end.
check_fieldname_element(S, Name, {_,_,Fields}) ->
case lists:keyfind(Name, 1, Fields) of
{Name,Def} ->
check_fieldname_element_1(S, Def);
false ->
asn1_error(S, {undefined_field,Name})
end.
check_fieldname_element_1(S, #typedef{typespec=Ts}=TDef) ->
case Ts of
#'Object'{} ->
check_object(S, TDef, Ts);
_ ->
check_type(S, TDef, Ts)
end;
check_fieldname_element_1(S, #valuedef{}=VDef) ->
try
check_value(S, VDef)
catch
throw:{asn1_class, _} ->
#valuedef{checked=C,pos=Pos,name=N,type=Type,
value=Def} = VDef,
ClassName = Type#type.def,
NewSpec = #'Object'{classname=ClassName,def=Def},
NewDef = #typedef{checked=C,pos=Pos,name=N,typespec=NewSpec},
check_fieldname_element_1(S, NewDef)
end;
check_fieldname_element_1(_S, {value_tag,Val}) ->
#valuedef{value=Val};
check_fieldname_element_1(S, Eref)
when is_record(Eref, 'Externaltypereference');
is_record(Eref, 'Externalvaluereference') ->
{_,TDef} = get_referenced_type(S, Eref),
check_fieldname_element_1(S, TDef).
%% instantiate_po/4
%% ClassDef is the class of Object,
%% Object is the Parameterized object, which is referenced,
%% ArgsList is the list of actual parameters
%% returns an #'Object' record.
instantiate_po(S=#state{parameters=_OldArgs},_ClassDef,Object,ArgsList) when is_record(Object,pobjectdef) ->
FormalParams = get_pt_args(Object),
MatchedArgs = match_args(S,FormalParams,ArgsList,[]),
NewS = S#state{parameters=MatchedArgs},
check_object(NewS,Object,#'Object'{classname=Object#pobjectdef.class,
def=Object#pobjectdef.def}).
%% instantiate_pos/4
%% ClassDef is the class of ObjectSetDef,
%% ObjectSetDef is the Parameterized object set, which is referenced
%% on the right side of the assignment,
%% ArgsList is the list of actual parameters, i.e. real objects
instantiate_pos(S=#state{parameters=_OldArgs},ClassRef,ObjectSetDef,ArgsList) ->
FormalParams = get_pt_args(ObjectSetDef),
OSet = case get_pt_spec(ObjectSetDef) of
{valueset,Set} -> #'ObjectSet'{class=ClassRef,set=Set};
Set when is_record(Set,'ObjectSet') -> Set;
_ -> asn1_error(S, invalid_objectset)
end,
MatchedArgs = match_args(S,FormalParams,ArgsList,[]),
NewS = S#state{parameters=MatchedArgs},
check_object(NewS,ObjectSetDef,OSet).
%% gen_incl -> boolean()
%% If object with Fields has any of the corresponding class' typefields
%% then return value is true otherwise it is false.
%% If an object lacks a typefield but the class has a type field that
%% is OPTIONAL then we want gen to be true
gen_incl(S,{_,_,Fields},CFields)->
gen_incl1(S,Fields,CFields).
gen_incl1(_,_,[]) ->
false;
gen_incl1(S,Fields,[C|CFields]) ->
case element(1,C) of
typefield ->
true; %% should check that field is OPTIONAL or DEFUALT if
%% the object lacks this field
objectfield ->
case lists:keysearch(element(2,C),1,Fields) of
{value,Field} ->
ClassRef = case element(3,C) of
#type{def=Ref} -> Ref;
Eref when is_record(Eref,'Externaltypereference') ->
Eref
end,
ClassFields = get_objclass_fields(S,ClassRef),
ObjDef =
case element(2,Field) of
TDef when is_record(TDef,typedef) ->
check_object(S,TDef,TDef#typedef.typespec);
ERef ->
{_,T} = get_referenced_type(S,ERef),
check_object(S, T, object_to_check(S, T))
end,
case gen_incl(S,ObjDef#'Object'.def,
ClassFields) of
true ->
true;
_ ->
gen_incl1(S,Fields,CFields)
end;
_ ->
gen_incl1(S,Fields,CFields)
end;
_ ->
gen_incl1(S,Fields,CFields)
end.
get_objclass_fields(S,Eref=#'Externaltypereference'{}) ->
{_,ClassDef} = get_referenced_type(S,Eref, true),
get_objclass_fields(S,ClassDef);
get_objclass_fields(S,CD=#classdef{typespec=#'Externaltypereference'{}}) ->
get_objclass_fields(S,CD#classdef.typespec);
get_objclass_fields(_,#classdef{typespec=CDef})
when is_record(CDef,objectclass) ->
CDef#objectclass.fields.
%% first if no unique field in the class return false.(don't generate code)
gen_incl_set(S,Fields,#typedef{typespec=#type{def=Eref}})
when is_record(Eref,'Externaltypereference') ->
%% When a Defined class is a reference toanother class definition
{_,CDef} = get_referenced_type(S,Eref),
gen_incl_set(S,Fields,CDef);
gen_incl_set(S,Fields,ClassDef) ->
case get_unique_fieldname(S, ClassDef) of
no_unique ->
false;
{_, _} ->
gen_incl_set1(S,Fields,
(ClassDef#classdef.typespec)#objectclass.fields)
end.
%% if any of the existing or potentially existing objects has a typefield
%% then return true.
gen_incl_set1(_,[],_CFields)->
false;
gen_incl_set1(_,['EXTENSIONMARK'],_) ->
true;
%% Fields are the fields of an object in the object set.
%% CFields are the fields of the class of the object set.
gen_incl_set1(_,['EXTENSIONMARK'|_],_) ->
true;
gen_incl_set1(S,[Object|Rest],CFields)->
Fields = element(tuple_size(Object), Object),
case gen_incl1(S,Fields,CFields) of
true ->
true;
false ->
gen_incl_set1(S,Rest,CFields)
end.
%%%
%%% Check an object definition.
%%%
check_objectdefn(S, Def, #classdef{typespec=ObjClass}) ->
#objectclass{syntax=Syntax0,fields=ClassFields} = ObjClass,
case Def of
{object,defaultsyntax,Fields} ->
check_defaultfields(S, Fields, ClassFields);
{object,definedsyntax,Fields} ->
Syntax = get_syntax(S, Syntax0, ClassFields),
case match_syntax(S, Syntax, Fields, []) of
{match,NewFields,[]} ->
{object,defaultsyntax,NewFields};
{match,_,[What|_]} ->
syntax_match_error(S, What);
{nomatch,[What|_]} ->
syntax_match_error(S, What);
{nomatch,[]} ->
syntax_match_error(S)
end
end.
%%%
%%% Pre-process the simplified syntax so that it can be more
%%% easily matched.
%%%
get_syntax(_, {preprocessed_syntax,Syntax}, _) ->
Syntax;
get_syntax(S, {'WITH SYNTAX',Syntax}, ClassFields) ->
preprocess_syntax(S, Syntax, ClassFields).
preprocess_syntax(S, Syntax0, Cs) ->
Syntax = preprocess_syntax_1(S, Syntax0, Cs, true),
Present0 = preprocess_get_fields(Syntax, []),
Present1 = lists:sort(Present0),
Present = ordsets:from_list(Present1),
case Present =:= Present1 of
false ->
Dupl = Present1 -- Present,
asn1_error(S, {syntax_duplicated_fields,Dupl});
true ->
ok
end,
Mandatory0 = get_mandatory_class_fields(Cs),
Mandatory = ordsets:from_list(Mandatory0),
case ordsets:subtract(Mandatory, Present) of
[] ->
Syntax;
[_|_]=Missing ->
asn1_error(S, {syntax_missing_mandatory_fields,Missing})
end.
preprocess_syntax_1(S, [H|T], Cs, Mandatory) when is_list(H) ->
[{optional,preprocess_syntax_1(S, H, Cs, false)}|
preprocess_syntax_1(S, T, Cs, Mandatory)];
preprocess_syntax_1(S, [{valuefieldreference,Name}|T], Cs, Mandatory) ->
F = preprocess_check_field(S, Name, Cs, Mandatory),
[F|preprocess_syntax_1(S, T, Cs, Mandatory)];
preprocess_syntax_1(S, [{typefieldreference,Name}|T], Cs, Mandatory) ->
F = preprocess_check_field(S, Name, Cs, Mandatory),
[F|preprocess_syntax_1(S, T, Cs, Mandatory)];
preprocess_syntax_1(S,[{Token,_}|T], Cs, Mandatory) when is_atom(Token) ->
[{token,Token}|preprocess_syntax_1(S, T, Cs, Mandatory)];
preprocess_syntax_1(S, [Token|T], Cs, Mandatory) when is_atom(Token) ->
[{token,Token}|preprocess_syntax_1(S, T, Cs, Mandatory)];
preprocess_syntax_1(_, [], _, _) -> [].
preprocess_check_field(S, Name, Cs, Mandatory) ->
case lists:keyfind(Name, 2, Cs) of
Tuple when is_tuple(Tuple) ->
case not Mandatory andalso is_mandatory_class_field(Tuple) of
true ->
asn1_error(S, {syntax_mandatory_in_optional_group,Name});
false ->
{field,Tuple}
end;
false ->
asn1_error(S, {syntax_undefined_field,Name})
end.
preprocess_get_fields([{field,F}|T], Acc) ->
Name = element(2, F),
preprocess_get_fields(T, [Name|Acc]);
preprocess_get_fields([{optional,L}|T], Acc) ->
preprocess_get_fields(T, preprocess_get_fields(L, Acc));
preprocess_get_fields([_|T], Acc) ->
preprocess_get_fields(T, Acc);
preprocess_get_fields([], Acc) ->
Acc.
%%%
%%% Match the actual fields in the object definition to
%%% the pre-processed simplified syntax.
%%%
match_syntax(S, [{token,Token}|T], [A|As]=Args, Acc) ->
case A of
{word_or_setting,_,#'Externaltypereference'{type=Token}} ->
match_syntax(S, T, As, Acc);
{Token,Line} when is_integer(Line) ->
match_syntax(S, T, As, Acc);
_ ->
{nomatch,Args}
end;
match_syntax(S, [{field,Field}|T]=Fs, [A|As0]=Args0, Acc) ->
try match_syntax_type(S, Field, A) of
{match,Match} ->
match_syntax(S, T, As0, lists:reverse(Match)++Acc);
{params,_Name,#ptypedef{args=Params}=P,Ref} ->
{Args,As} = lists:split(length(Params), As0),
Val = match_syntax_params(S, P, Ref, Args),
match_syntax(S, Fs, [Val|As], Acc)
catch
_:_ ->
{nomatch,Args0}
end;
match_syntax(S, [{optional,L}|T], As0, Acc) ->
case match_syntax(S, L, As0, []) of
{match,Match,As} ->
match_syntax(S, T, As, lists:reverse(Match)++Acc);
{nomatch,As0} ->
match_syntax(S, T, As0, Acc);
{nomatch,_}=NoMatch ->
NoMatch
end;
match_syntax(_, [_|_], [], _Acc) ->
{nomatch,[]};
match_syntax(_, [], As, Acc) ->
{match,Acc,As}.
match_syntax_type(S, Type, {value_tag,Val}) ->
match_syntax_type(S, Type, Val);
match_syntax_type(S, Type, {setting,_,Val}) ->
match_syntax_type(S, Type, Val);
match_syntax_type(S, Type, {word_or_setting,_,Val}) ->
match_syntax_type(S, Type, Val);
match_syntax_type(_S, _Type, {Atom,Line})
when is_atom(Atom), is_integer(Line) ->
throw(nomatch);
match_syntax_type(S, {fixedtypevaluefield,Name,#type{}=T,_,_}=Type,
#'Externalvaluereference'{}=ValRef0) ->
try get_referenced_type(S, ValRef0) of
{M,#valuedef{}=ValDef} ->
match_syntax_type(update_state(S, M), Type, ValDef)
catch
throw:{error,_} ->
ValRef = #valuedef{name=Name,
type=T,
value=ValRef0,
module=S#state.mname},
match_syntax_type(S, Type, ValRef)
end;
match_syntax_type(S, {fixedtypevaluefield,Name,#type{},_,_}, #valuedef{}=Val0) ->
Val = check_value(S, Val0),
{match,[{Name,Val}]};
match_syntax_type(S, {fixedtypevaluefield,Name,#type{},_,_},
{'ValueFromObject',{object,Object},FieldNames}) ->
Val = extract_field(S, Object, FieldNames),
{match,[{Name,Val}]};
match_syntax_type(S, {fixedtypevaluefield,Name,#type{}=T,_,_}=Type, Any) ->
ValDef = #valuedef{name=Name,type=T,value=Any,module=S#state.mname},
match_syntax_type(S, Type, ValDef);
match_syntax_type(_S, {fixedtypevaluesetfield,Name,#type{},_}, Any) ->
{match,[{Name,Any}]};
match_syntax_type(S, {objectfield,Name,_,_,_}, #'Externalvaluereference'{}=Ref) ->
{M,Obj} = get_referenced_type(S, Ref),
check_object(S, Obj, object_to_check(S, Obj)),
{match,[{Name,Ref#'Externalvaluereference'{module=M}}]};
match_syntax_type(S, {objectfield,Name,Class,_,_}, {object,_,_}=ObjDef) ->
InlinedObjName = list_to_atom(lists:concat([S#state.tname,
'_',Name])),
ObjSpec = #'Object'{classname=Class,def=ObjDef},
CheckedObj = check_object(S, #typedef{typespec=ObjSpec}, ObjSpec),
InlObj = #typedef{checked=true,name=InlinedObjName,typespec=CheckedObj},
ObjKey = {InlinedObjName, InlinedObjName},
insert_once(S, inlined_objects, ObjKey),
%% Which module to use here? Could it be other than top_module?
asn1_db:dbput(get(top_module), InlinedObjName, InlObj),
{match,[{Name,InlObj}]};
match_syntax_type(_S, {objectfield,Name,_,_,_}, Any) ->
{match,[{Name,Any}]};
match_syntax_type(S, {objectsetfield,Name,CDef0,_}, Any) ->
CDef = case CDef0 of
#type{def=CDef1} -> CDef1;
CDef1 -> CDef1
end,
case match_syntax_objset(S, Any, CDef) of
#typedef{typespec=#'ObjectSet'{}=Ts0}=Def ->
Ts = check_object(S, Def, Ts0),
{match,[{Name,Def#typedef{checked=true,typespec=Ts}}]};
_ ->
syntax_match_error(S, Any)
end;
match_syntax_type(S, {typefield,Name0,_}, #type{def={pt,_,_}=Def}=Actual) ->
%% This is an inlined type. If constructed type, save in data base.
T = check_type(S, #typedef{typespec=Actual}, Actual),
#'Externaltypereference'{type=PtName} = element(2, Def),
NameList = [PtName,S#state.tname],
Name = list_to_atom(asn1ct_gen:list2name(NameList)),
NewTDef = #typedef{checked=true,name=Name,typespec=T},
asn1_db:dbput(S#state.mname, Name, NewTDef),
insert_once(S, parameterized_objects, {Name,type,NewTDef}),
{match,[{Name0,NewTDef}]};
match_syntax_type(S, {typefield,Name,_}, #type{def=#'ObjectClassFieldType'{}}=Actual) ->
T = check_type(S, #typedef{typespec=Actual}, Actual),
{match,[{Name,ocft_def(T)}]};
match_syntax_type(S, {typefield,Name,_}, #type{def=#'Externaltypereference'{}=Ref}) ->
match_syntax_external(S, Name, Ref);
match_syntax_type(S, {typefield,Name,_}, #type{def=Def}=Actual) ->
T = check_type(S, #typedef{typespec=Actual}, Actual),
TypeName = asn1ct_gen:type(asn1ct_gen:get_inner(Def)),
{match,[{Name,#typedef{checked=true,name=TypeName,typespec=T}}]};
match_syntax_type(S, {typefield,Name,_}, #'Externaltypereference'{}=Ref) ->
match_syntax_external(S, Name, Ref);
match_syntax_type(_S, {variabletypevaluefield,Name,_,_}, Any) ->
{match,[{Name,Any}]};
match_syntax_type(_S, {variabletypevaluesetfield,Name,_,_}, Any) ->
{match,[{Name,Any}]};
match_syntax_type(_S, _Type, _Actual) ->
throw(nomatch).
match_syntax_params(S0, #ptypedef{name=Name}=PtDef,
#'Externaltypereference'{module=M,type=N}=ERef0, Args) ->
S = S0#state{mname=M,module=load_asn1_module(S0, M),tname=Name},
Type = check_type(S, PtDef, #type{def={pt,ERef0,Args}}),
ERefName = new_reference_name(N),
ERef = #'Externaltypereference'{type=ERefName,module=S0#state.mname},
TDef = #typedef{checked=true,name=ERefName,typespec=Type},
insert_once(S0, parameterized_objects, {ERefName,type,TDef}),
asn1_db:dbput(S0#state.mname, ERef#'Externaltypereference'.type, TDef),
ERef.
match_syntax_external(#state{mname=Mname}=S0, Name, Ref0) ->
{M,T0} = get_referenced_type(S0, Ref0),
Ref1 = Ref0#'Externaltypereference'{module=M},
case T0 of
#ptypedef{} ->
{params,Name,T0,Ref1};
#typedef{checked=false}=TDef0 when Mname =/= M ->
%% This typedef is an imported type (or maybe a set.asn
%% compilation).
S = S0#state{mname=M,module=load_asn1_module(S0, M),
tname=get_datastr_name(TDef0)},
Type = check_type(S, TDef0, TDef0#typedef.typespec),
TDef = TDef0#typedef{checked=true,typespec=Type},
asn1_db:dbput(M, get_datastr_name(TDef), TDef),
{match,[{Name,merged_name(S, Ref1)}]};
TDef ->
%% This might be a renamed type in a set of specs,
%% so rename the ref.
Type = asn1ct:get_name_of_def(TDef),
Ref = Ref1#'Externaltypereference'{type=Type},
{match,[{Name,Ref}]}
end.
match_syntax_objset(_S, {element_set,_,_}=Set, ClassDef) ->
make_objset(ClassDef, Set);
match_syntax_objset(S, #'Externaltypereference'{}=Ref, _) ->
{_,T} = get_referenced_type(S, Ref),
T;
match_syntax_objset(S, #'Externalvaluereference'{}=Ref, _) ->
{_,T} = get_referenced_type(S, Ref),
T;
match_syntax_objset(_, [_|_]=Set, ClassDef) ->
make_objset(ClassDef, Set);
match_syntax_objset(S, {object,definedsyntax,Words}, ClassDef) ->
case Words of
[Word] ->
match_syntax_objset_1(S, Word, ClassDef);
[_|_] ->
%% More than one word does not make sense.
none
end;
match_syntax_objset(S, #type{def=#'Externaltypereference'{}=Set}, ClassDef) ->
match_syntax_objset(S, Set, ClassDef);
match_syntax_objset(_, #type{}, _) ->
none.
match_syntax_objset_1(S, {setting,_,Set}, ClassDef) ->
%% Word that starts with an uppercase letter.
match_syntax_objset(S, Set, ClassDef);
match_syntax_objset_1(S, {word_or_setting,_,Set}, ClassDef) ->
%% Word in uppercase/hyphens only.
match_syntax_objset(S, Set, ClassDef);
match_syntax_objset_1(S, #type{def={'TypeFromObject', {object,Object}, FNs}},
ClassDef) ->
Set = extract_field(S, Object, FNs),
[_|_] = Set,
#typedef{checked=true,typespec=#'ObjectSet'{class=ClassDef,set=Set}};
match_syntax_objset_1(_, #type{def=#'ObjectClassFieldType'{}}=Set, ClassDef) ->
make_objset(ClassDef, Set);
match_syntax_objset_1(_, {object,_,_}=Object, ClassDef) ->
make_objset(ClassDef, [Object]).
make_objset(ClassDef, Set) ->
#typedef{typespec=#'ObjectSet'{class=ClassDef,set=Set}}.
-spec syntax_match_error(_) -> no_return().
syntax_match_error(S) ->
asn1_error(S, syntax_nomatch).
-spec syntax_match_error(_, _) -> no_return().
syntax_match_error(S, What0) ->
What = printable_string(What0),
asn1_error(S, {syntax_nomatch,What}).
printable_string(Def) ->
printable_string_1(Def).
printable_string_1({word_or_setting,_,Def}) ->
printable_string_1(Def);
printable_string_1({value_tag,V}) ->
printable_string_1(V);
printable_string_1({#seqtag{val=Val1},Val2}) ->
atom_to_list(Val1) ++ " " ++ printable_string_1(Val2);
printable_string_1(#type{def=Def}) ->
atom_to_list(asn1ct_gen:get_inner(Def));
printable_string_1(#'Externaltypereference'{type=Type}) ->
atom_to_list(Type);
printable_string_1(#'Externalvaluereference'{value=Type}) ->
atom_to_list(Type);
printable_string_1({Atom,Line}) when is_atom(Atom), is_integer(Line) ->
q(Atom);
printable_string_1({object,definedsyntax,L}) ->
Str = lists:join($\s, [printable_string_1(Item) || Item <- L]),
q(lists:flatten(Str));
printable_string_1([_|_]=Def) ->
case lists:all(fun is_integer/1, Def) of
true ->
lists:flatten(io_lib:format("~p", [Def]));
false ->
Str = lists:join($\s, [printable_string_1(Item) || Item <- Def]),
q(lists:flatten(Str))
end;
printable_string_1(Def) ->
lists:flatten(io_lib:format("~p", [Def])).
q(S) ->
lists:concat(["\"",S,"\""]).
check_defaultfields(S, Fields, ClassFields) ->
Present = ordsets:from_list([F || {F,_} <- Fields]),
Mandatory0 = get_mandatory_class_fields(ClassFields),
Mandatory = ordsets:from_list(Mandatory0),
All = ordsets:from_list([element(2, F) || F <- ClassFields]),
#state{tname=Obj} = S,
case ordsets:subtract(Present, All) of
[] ->
ok;
[_|_]=Invalid ->
asn1_error(S, {invalid_fields,Invalid,Obj})
end,
case ordsets:subtract(Mandatory, Present) of
[] ->
check_defaultfields_1(S, Fields, ClassFields, []);
[_|_]=Missing ->
asn1_error(S, {missing_mandatory_fields,Missing,Obj})
end.
check_defaultfields_1(_S, [], _ClassFields, Acc) ->
{object,defaultsyntax,lists:reverse(Acc)};
check_defaultfields_1(S, [{FName,Spec}|Fields], ClassFields, Acc) ->
CField = lists:keyfind(FName, 2, ClassFields),
{match,Match} = match_syntax_type(S, CField, Spec),
check_defaultfields_1(S, Fields, ClassFields, Match++Acc).
get_mandatory_class_fields(ClassFields) ->
[element(2, F) || F <- ClassFields,
is_mandatory_class_field(F)].
is_mandatory_class_field({fixedtypevaluefield,_,_,_,'MANDATORY'}) ->
true;
is_mandatory_class_field({objectfield,_,_,_,'MANDATORY'}) ->
true;
is_mandatory_class_field({objectsetfield,_,_,'MANDATORY'}) ->
true;
is_mandatory_class_field({typefield,_,'MANDATORY'}) ->
true;
is_mandatory_class_field({variabletypevaluefield,_,_,'MANDATORY'}) ->
true;
is_mandatory_class_field({variabletypevaluesetfield,_,_,'MANDATORY'}) ->
true;
is_mandatory_class_field(_) ->
false.
merged_name(#state{inputmodules=[]},ERef) ->
ERef;
merged_name(S,ERef=#'Externaltypereference'{module=M}) ->
case {S#state.mname,lists:member(M,S#state.inputmodules)} of
{M,_} ->
ERef;
{MergeM,true} ->
%% maybe the reference is renamed
NewName = renamed_reference(S,ERef),
ERef#'Externaltypereference'{module=MergeM,type=NewName};
{_,_} -> % i.e. M /= MergeM, not an inputmodule
ERef
end.
ocft_def(#type{def=#'ObjectClassFieldType'{type=OCFT}}=T) ->
case OCFT of
{fixedtypevaluefield,_,InnerType} ->
case asn1ct_gen:type(asn1ct_gen:get_inner(InnerType#type.def)) of
Bif when Bif =:= {primitive,bif}; Bif =:= {constructed,bif} ->
#typedef{checked=true,name=Bif,typespec=InnerType};
#'Externaltypereference'{}=Ref ->
Ref
end;
'ASN1_OPEN_TYPE' ->
#typedef{checked=true,typespec=T#type{def='ASN1_OPEN_TYPE'}}
end.
check_value(OldS,V) when is_record(V,pvaluesetdef) ->
#pvaluesetdef{checked=Checked,type=Type} = V,
case Checked of
true -> V;
{error,_} -> V;
false ->
case get_referenced_type(OldS,Type#type.def) of
{_,Class} when is_record(Class,classdef) ->
throw({pobjectsetdef});
_ -> continue
end
end;
check_value(_OldS,V) when is_record(V,pvaluedef) ->
%% Fix this case later
V;
check_value(OldS,V) when is_record(V,typedef) ->
%% This case when a value set has been parsed as an object set.
%% It may be a value set
?dbg("check_value, V: ~p~n",[V]),
#typedef{typespec=TS} = V,
case TS of
#'ObjectSet'{class=ClassRef} ->
{_RefM,TSDef} = get_referenced_type(OldS, ClassRef),
case TSDef of
#classdef{} -> throw({objectsetdef});
#typedef{typespec=#type{def=Eref}} when
is_record(Eref,'Externaltypereference') ->
%% This case if the class reference is a defined
%% reference to class
check_value(OldS,V#typedef{typespec=TS#'ObjectSet'{class=Eref}});
#typedef{typespec=HostType} ->
%% an ordinary value set with a type in #typedef.typespec
ValueSet0 = TS#'ObjectSet'.set,
Constr = check_constraints(OldS, HostType, [ValueSet0]),
Type = check_type(OldS,TSDef,TSDef#typedef.typespec),
{valueset,Type#type{constraint=Constr}}
end;
_ ->
throw({objectsetdef})
end;
check_value(S,#valuedef{pos=Pos,name=Name,type=Type,
value={valueset,Constr}}) ->
NewType = Type#type{constraint=[Constr]},
{valueset,
check_type(S,#typedef{pos=Pos,name=Name,typespec=NewType},NewType)};
check_value(S, #valuedef{}=V) ->
?dbg("check_value, V: ~p~n",[V]),
case V of
#valuedef{checked=true} ->
V;
#valuedef{checked=false} ->
check_valuedef(S, V)
end.
check_valuedef(#state{recordtopname=TopName}=S0, V0) ->
#valuedef{name=Name,type=Vtype0,value=Value,module=ModName} = V0,
V = V0#valuedef{checked=true},
Vtype1 = expand_valuedef_type(Vtype0),
Vtype = check_type(S0, #typedef{name=Name,typespec=Vtype1},Vtype1),
Def = Vtype#type.def,
S1 = S0#state{tname=Def},
SVal = update_state(S1, ModName),
case Def of
#'Externaltypereference'{type=RecName}=Ext ->
{RefM,Type} = get_referenced_type(S1, Ext),
%% If V isn't a value but an object Type is a #classdef{}
S2 = update_state(S1, RefM),
case Type of
#typedef{typespec=TypeSpec0}=TypeDef ->
TypeSpec = check_type(S2, TypeDef, TypeSpec0),
S3 = case is_contextswitchtype(Type) of
true ->
S2;
false ->
S2#state{recordtopname=[RecName|TopName]}
end,
#valuedef{value=CheckedVal} =
check_value(S3, V0#valuedef{type=TypeSpec}),
V#valuedef{value=CheckedVal};
#type{} ->
%% A parameter that couldn't be categorized.
#valuedef{value=CheckedVal} =
check_value(S2#state{recordtopname=[RecName|TopName]},
V#valuedef{type=Type}),
V#valuedef{value=CheckedVal}
end;
'ASN1_OPEN_TYPE' ->
{opentypefieldvalue,ANYType,ANYValue} = Value,
CheckedV = check_value(SVal,#valuedef{name=Name,
type=ANYType,
value=ANYValue,
module=ModName}),
V#valuedef{value=CheckedV#valuedef.value};
'INTEGER' ->
V#valuedef{value=normalize_value(SVal, Vtype, Value, [])};
{'INTEGER',_NamedNumberList} ->
V#valuedef{value=normalize_value(SVal, Vtype, Value, [])};
#'SEQUENCE'{} ->
{ok,SeqVal} = convert_external(SVal, Vtype, Value),
V#valuedef{value=normalize_value(SVal, Vtype, SeqVal, TopName)};
_ ->
V#valuedef{value=normalize_value(SVal, Vtype, Value, TopName)}
end.
expand_valuedef_type(#type{def=Seq}=Type)
when is_record(Seq,'SEQUENCE') ->
NewComponents = case Seq#'SEQUENCE'.components of
{R1,_Ext,R2} -> R1 ++ R2;
{Root,_Ext} -> Root;
Root -> take_only_rootset(Root)
end,
NewSeq = Seq#'SEQUENCE'{components = NewComponents},
Type#type{def=NewSeq};
expand_valuedef_type(#type{def=Set}=Type)
when is_record(Set,'SET') ->
NewComponents = case Set#'SET'.components of
{R1,_Ext,R2} -> R1 ++ R2;
{Root,_Ext} -> Root;
Root -> take_only_rootset(Root)
end,
NewSet = Set#'SET'{components = NewComponents},
Type#type{def=NewSet};
expand_valuedef_type(Type) ->
Type.
is_contextswitchtype(#typedef{name='EXTERNAL'})->
true;
is_contextswitchtype(#typedef{name='EMBEDDED PDV'}) ->
true;
is_contextswitchtype(#typedef{name='CHARACTER STRING'}) ->
true;
is_contextswitchtype(_) ->
false.
%%%
%%% Start of OBJECT IDENTFIER/RELATIVE-OID validation.
%%%
validate_objectidentifier(S, OidType, #'Externalvaluereference'{}=Id) ->
%% Must be an OBJECT IDENTIFIER or RELATIVE-OID depending on OidType.
get_oid_value(S, OidType, false, Id);
validate_objectidentifier(S, OidType, {'ValueFromObject',{object,Obj},Fields}) ->
%% Must be an OBJECT IDENTIFIER/RELATIVE-OID depending on OidType.
case extract_field(S, Obj, Fields) of
#valuedef{checked=true,value=Value,type=Type} when is_tuple(Value) ->
_ = get_oid_type(S, OidType, Type),
Value;
_ ->
asn1_error(S, {illegal_oid,OidType})
end;
validate_objectidentifier(S, OidType,
[{#seqtag{module=Mod,pos=Pos,val=Atom},Val}]) ->
%% This case is when an OBJECT IDENTIFIER value has been parsed as a
%% SEQUENCE value.
Rec = #'Externalvaluereference'{pos=Pos,
module=Mod,
value=Atom},
validate_oid(S, OidType, [Rec,Val], []);
validate_objectidentifier(S, OidType, [_|_]=L0) ->
validate_oid(S, OidType, L0, []);
validate_objectidentifier(S, OidType, _) ->
asn1_error(S, {illegal_oid,OidType}).
get_oid_value(S, OidType, AllowInteger, #'Externalvaluereference'{}=Id) ->
case get_referenced_type(S, Id) of
{_,#valuedef{checked=Checked,type=Type,value=V}} ->
case get_oid_type(S, OidType, Type) of
'INTEGER' when not AllowInteger ->
asn1_error(S, {illegal_oid,OidType});
_ when Checked ->
V;
'INTEGER' ->
V;
_ ->
validate_objectidentifier(S, OidType, V)
end;
_ ->
asn1_error(S, {illegal_oid,OidType})
end.
validate_oid(S, OidType, [], Acc) ->
Oid = lists:reverse(Acc),
validate_oid_path(S, OidType, Oid),
list_to_tuple(Oid);
validate_oid(S, OidType, [Value|Vrest], Acc) when is_integer(Value) ->
validate_oid(S, OidType, Vrest, [Value|Acc]);
validate_oid(S, OidType, [{'NamedNumber',_Name,Value}|Vrest], Acc)
when is_integer(Value) ->
validate_oid(S, OidType, Vrest, [Value|Acc]);
validate_oid(S, OidType, [#'Externalvaluereference'{}=Id|Vrest], Acc) ->
NeededOidType = case Acc of
[] -> o_id;
[_|_] -> rel_oid
end,
try get_oid_value(S, NeededOidType, true, Id) of
Val when is_integer(Val) ->
validate_oid(S, OidType, Vrest, [Val|Acc]);
Val when is_tuple(Val) ->
L = tuple_to_list(Val),
validate_oid(S, OidType, Vrest, lists:reverse(L, Acc))
catch
_:_ ->
case reserved_objectid(Id#'Externalvaluereference'.value, Acc) of
Value when is_integer(Value) ->
validate_oid(S, OidType,Vrest, [Value|Acc]);
false ->
asn1_error(S, {illegal_oid,OidType})
end
end;
validate_oid(S, OidType, _V, _Acc) ->
asn1_error(S, {illegal_oid,OidType}).
get_oid_type(S, OidType, #type{def=Def}) ->
get_oid_type(S, OidType, Def);
get_oid_type(S, OidType, #'Externaltypereference'{}=Id) ->
{_,OI} = get_referenced_type(S, Id),
get_oid_type(S, OidType, OI#typedef.typespec);
get_oid_type(_S, o_id, 'OBJECT IDENTIFIER'=T) ->
T;
get_oid_type(_S, rel_oid, 'RELATIVE-OID'=T) ->
T;
get_oid_type(_S, _, 'INTEGER'=T) ->
T;
get_oid_type(S, OidType, _) ->
asn1_error(S, {illegal_oid,OidType}).
%% ITU-T Rec. X.680 Annex B - D
reserved_objectid('itu-t',[]) -> 0;
reserved_objectid('ccitt',[]) -> 0;
%% arcs below "itu-t"
reserved_objectid('recommendation',[0]) -> 0;
reserved_objectid('question',[0]) -> 1;
reserved_objectid('administration',[0]) -> 2;
reserved_objectid('network-operator',[0]) -> 3;
reserved_objectid('identified-organization',[0]) -> 4;
%% arcs below "recommendation"
reserved_objectid('a',[0,0]) -> 1;
reserved_objectid('b',[0,0]) -> 2;
reserved_objectid('c',[0,0]) -> 3;
reserved_objectid('d',[0,0]) -> 4;
reserved_objectid('e',[0,0]) -> 5;
reserved_objectid('f',[0,0]) -> 6;
reserved_objectid('g',[0,0]) -> 7;
reserved_objectid('h',[0,0]) -> 8;
reserved_objectid('i',[0,0]) -> 9;
reserved_objectid('j',[0,0]) -> 10;
reserved_objectid('k',[0,0]) -> 11;
reserved_objectid('l',[0,0]) -> 12;
reserved_objectid('m',[0,0]) -> 13;
reserved_objectid('n',[0,0]) -> 14;
reserved_objectid('o',[0,0]) -> 15;
reserved_objectid('p',[0,0]) -> 16;
reserved_objectid('q',[0,0]) -> 17;
reserved_objectid('r',[0,0]) -> 18;
reserved_objectid('s',[0,0]) -> 19;
reserved_objectid('t',[0,0]) -> 20;
reserved_objectid('u',[0,0]) -> 21;
reserved_objectid('v',[0,0]) -> 22;
reserved_objectid('w',[0,0]) -> 23;
reserved_objectid('x',[0,0]) -> 24;
reserved_objectid('y',[0,0]) -> 25;
reserved_objectid('z',[0,0]) -> 26;
reserved_objectid(iso,[]) -> 1;
%% arcs below "iso", note that number 1 is not used
reserved_objectid('standard',[1]) -> 0;
reserved_objectid('member-body',[1]) -> 2;
reserved_objectid('identified-organization',[1]) -> 3;
reserved_objectid('joint-iso-itu-t',[]) -> 2;
reserved_objectid('joint-iso-ccitt',[]) -> 2;
reserved_objectid(_,_) -> false.
validate_oid_path(_, rel_oid, _) ->
ok;
validate_oid_path(_, o_id, [0,I|_]) when 0 =< I, I =< 9 ->
ok;
validate_oid_path(_, o_id, [1,I|_]) when 0 =< I, I =< 3 ->
ok;
validate_oid_path(_, o_id, [2|_]) ->
ok;
validate_oid_path(S, o_id=OidType, _) ->
asn1_error(S, {illegal_oid,OidType}).
%%%
%%% End of OBJECT IDENTFIER/RELATIVE-OID validation.
%%%
convert_external(S, Vtype, Value) ->
case Vtype of
#type{tag=[{tag,'UNIVERSAL',8,'IMPLICIT',32}]} ->
%% this is an 'EXTERNAL' (or INSTANCE OF)
case Value of
[{#seqtag{val=identification},_}|_] ->
{ok,to_EXTERNAL1990(S, Value)};
_ ->
{ok,Value}
end;
_ ->
{ok,Value}
end.
to_EXTERNAL1990(S, [{#seqtag{val=identification}=T,
{'CHOICE',{syntax,Stx}}}|Rest]) ->
to_EXTERNAL1990(S, Rest, [{T#seqtag{val='direct-reference'},Stx}]);
to_EXTERNAL1990(S, [{#seqtag{val=identification}=T,
{'CHOICE',{'presentation-context-id',I}}}|Rest]) ->
to_EXTERNAL1990(S, Rest, [{T#seqtag{val='indirect-reference'},I}]);
to_EXTERNAL1990(S, [{#seqtag{val=identification}=T,
{'CHOICE',{'context-negotiation',[{_,PCid},{_,TrStx}]}}}|Rest]) ->
to_EXTERNAL1990(S, Rest, [{T#seqtag{val='indirect-reference'},PCid},
{T#seqtag{val='direct-reference'},TrStx}]);
to_EXTERNAL1990(S, _) ->
asn1_error(S, illegal_external_value).
to_EXTERNAL1990(S, [V={#seqtag{val='data-value-descriptor'},_}|Rest], Acc) ->
to_EXTERNAL1990(S, Rest, [V|Acc]);
to_EXTERNAL1990(_S, [{#seqtag{val='data-value'}=T,Val}], Acc) ->
Encoding = {T#seqtag{val=encoding},{'CHOICE',{'octet-aligned',Val}}},
lists:reverse([Encoding|Acc]);
to_EXTERNAL1990(S, _, _) ->
asn1_error(S, illegal_external_value).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Functions to normalize the default values of SEQUENCE
%% and SET components into Erlang valid format
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
normalize_value(_,_,mandatory,_) ->
mandatory;
normalize_value(_,_,'OPTIONAL',_) ->
'OPTIONAL';
normalize_value(S, Type, {'DEFAULT',Value}, NameList) ->
case catch get_canonic_type(S,Type,NameList) of
{'BOOLEAN',CType,_} ->
normalize_boolean(S,Value,CType);
{'INTEGER',CType,_} ->
normalize_integer(S, Value, CType);
{'BIT STRING',CType,_} ->
normalize_bitstring(S,Value,CType);
{'OCTET STRING',_,_} ->
normalize_octetstring(S, Value);
{'NULL',_CType,_} ->
%%normalize_null(Value);
'NULL';
{'RELATIVE-OID',_,_} ->
normalize_relative_oid(S,Value);
{'OBJECT IDENTIFIER',_,_} ->
normalize_objectidentifier(S,Value);
{'ObjectDescriptor',_,_} ->
normalize_objectdescriptor(Value);
{'REAL',_,_} ->
normalize_real(Value);
{'ENUMERATED',CType,_} ->
normalize_enumerated(S,Value,CType);
{'CHOICE',CType,NewNameList} ->
ChoiceComponents = get_choice_components(S, {'CHOICE',CType}),
normalize_choice(S,Value,ChoiceComponents,NewNameList);
{'SEQUENCE',CType,NewNameList} ->
normalize_sequence(S,Value,CType,NewNameList);
{'SEQUENCE OF',CType,NewNameList} ->
normalize_seqof(S,Value,CType,NewNameList);
{'SET',CType,NewNameList} ->
normalize_set(S,Value,CType,NewNameList);
{'SET OF',CType,NewNameList} ->
normalize_setof(S,Value,CType,NewNameList);
{restrictedstring,CType,_} ->
normalize_restrictedstring(S,Value,CType);
{'ASN1_OPEN_TYPE',{typefield,_TF},NL} -> %an open type
normalize_objectclassfieldvalue(S,Value,NL);
Err ->
asn1ct:warning("could not check default value ~p~nType:~n~p~nNameList:~n~p~n",
[Value,Type,Err],S,"could not check default value"),
Value
end;
normalize_value(S,Type,Val,NameList) ->
normalize_value(S,Type,{'DEFAULT',Val},NameList).
normalize_boolean(_,true,_) ->
true;
normalize_boolean(_,false,_) ->
false;
normalize_boolean(S,Bool=#'Externalvaluereference'{},CType) ->
get_normalized_value(S,Bool,CType,fun normalize_boolean/3,[]);
normalize_boolean(S, _, _) ->
asn1_error(S, {illegal_value, "BOOLEAN"}).
normalize_integer(_S, Int, _) when is_integer(Int) ->
Int;
normalize_integer(S, #'Externalvaluereference'{value=Name}=Ref, NNL) ->
case lists:keyfind(Name, 1, NNL) of
{Name,Val} ->
Val;
false ->
try get_referenced_value(S, Ref) of
Val when is_integer(Val) ->
Val;
_ ->
asn1_error(S, illegal_integer_value)
catch
throw:_ ->
asn1_error(S, illegal_integer_value)
end
end;
normalize_integer(S, {'ValueFromObject',{object,Obj},FieldNames}, _) ->
case extract_field(S, Obj, FieldNames) of
#valuedef{value=Val} when is_integer(Val) ->
Val;
_ ->
asn1_error(S, illegal_integer_value)
end;
normalize_integer(S, _, _) ->
asn1_error(S, illegal_integer_value).
%% normalize_bitstring(S, Value, Type) -> bitstring()
%% Convert a literal value for a BIT STRING to an Erlang bit string.
%%
normalize_bitstring(S, Value, Type)->
case Value of
{hstring,String} when is_list(String) ->
hstring_to_bitstring(String);
{bstring,String} when is_list(String) ->
bstring_to_bitstring(String);
#'Externalvaluereference'{} ->
Val = get_referenced_value(S, Value),
normalize_bitstring(S, Val, Type);
{'ValueFromObject',{object,Obj},FieldNames} ->
case extract_field(S, Obj, FieldNames) of
#valuedef{value=Val} ->
normalize_bitstring(S, Val, Type);
_ ->
asn1_error(S, {illegal_value, "BIT STRING"})
end;
RecList when is_list(RecList) ->
[normalize_bs_item(S, Item, Type) || Item <- RecList];
Bs when is_bitstring(Bs) ->
%% Already normalized.
Bs;
_ ->
asn1_error(S, {illegal_value, "BIT STRING"})
end.
normalize_bs_item(S, #'Externalvaluereference'{value=Name}, Type) ->
case lists:keymember(Name, 1, Type) of
true -> Name;
false -> asn1_error(S, {illegal_value, "BIT STRING"})
end;
normalize_bs_item(_, Atom, _) when is_atom(Atom) ->
Atom;
normalize_bs_item(S, _, _) ->
asn1_error(S, {illegal_value, "BIT STRING"}).
hstring_to_binary(L) ->
byte_align(hstring_to_bitstring(L)).
bstring_to_binary(L) ->
byte_align(bstring_to_bitstring(L)).
byte_align(Bs) ->
case bit_size(Bs) rem 8 of
0 -> Bs;
N -> <<Bs/bitstring,0:(8-N)>>
end.
hstring_to_bitstring(L) ->
<< <<(hex_to_int(D)):4>> || D <- L >>.
bstring_to_bitstring(L) ->
<< <<(D-$0):1>> || D <- L >>.
hex_to_int(D) when $0 =< D, D =< $9 -> D - $0;
hex_to_int(D) when $A =< D, D =< $F -> D - ($A - 10).
%% normalize_octetstring/1 changes representation of input Value to a
%% list of octets.
%% Format of Value is one of:
%% {bstring,String} each element in String corresponds to one bit in an octet
%% {hstring,String} each element in String corresponds to one byte in an octet
%% #'Externalvaluereference'
normalize_octetstring(S, Value) ->
case Value of
{bstring,String} ->
bstring_to_binary(String);
{hstring,String} ->
hstring_to_binary(String);
#'Externalvaluereference'{} ->
case get_referenced_value(S, Value) of
String when is_binary(String) ->
String;
Other ->
normalize_octetstring(S, Other)
end;
{'ValueFromObject',{object,Obj},FieldNames} ->
case extract_field(S, Obj, FieldNames) of
#valuedef{value=Val} when is_binary(Val) ->
Val;
_ ->
asn1_error(S, illegal_octet_string_value)
end;
Val when is_binary(Val) ->
%% constant default value
Val;
_ ->
asn1_error(S, illegal_octet_string_value)
end.
normalize_objectidentifier(S, Value) ->
validate_objectidentifier(S, o_id, Value).
normalize_relative_oid(S, Value) ->
validate_objectidentifier(S, rel_oid, Value).
normalize_objectdescriptor(Value) ->
Value.
normalize_real(Value) ->
Value.
normalize_enumerated(S, Id0, NNL) ->
{Id,_} = lookup_enum_value(S, Id0, NNL),
Id.
lookup_enum_value(S, Id, {Base,Ext}) ->
%% Extensible ENUMERATED.
lookup_enum_value(S, Id, Base++Ext);
lookup_enum_value(S, #'Externalvaluereference'{value=Id}, NNL) ->
lookup_enum_value(S, Id, NNL);
lookup_enum_value(S, Id, NNL) when is_atom(Id) ->
case lists:keyfind(Id, 1, NNL) of
{_,_}=Ret ->
Ret;
false ->
asn1_error(S, {undefined,Id})
end.
normalize_choice(S, {'CHOICE',{C,V}}, CType, NameList)
when is_atom(C) ->
case lists:keyfind(C, #'ComponentType'.name, CType) of
#'ComponentType'{typespec=CT,name=Name} ->
{C,normalize_value(S, CT, {'DEFAULT',V}, [Name|NameList])};
false ->
asn1_error(S, {illegal_id,C})
end;
normalize_choice(S,CV={Name,_ChoiceVal},CType,NameList)
when is_atom(Name) ->
normalize_choice(S,{'CHOICE',CV},CType,NameList);
normalize_choice(S, V, _CType, _NameList) ->
asn1_error(S, {illegal_id, error_value(V)}).
normalize_sequence(S,Value,Components,NameList)
when is_tuple(Components) ->
normalize_sequence(S,Value,lists:flatten(tuple_to_list(Components)),
NameList);
normalize_sequence(S,{Name,Value},Components,NameList)
when is_atom(Name),is_list(Value) ->
normalize_sequence(S,Value,Components,NameList);
normalize_sequence(S,Value,Components,NameList) ->
normalized_record('SEQUENCE',S,Value,Components,NameList).
normalize_set(S,Value,Components,NameList) when is_tuple(Components) ->
normalize_set(S,Value,lists:flatten(tuple_to_list(Components)),NameList);
normalize_set(S,{Name,Value},Components,NameList)
when is_atom(Name),is_list(Value) ->
normalized_record('SET',S,Value,Components,NameList);
normalize_set(S,Value,Components,NameList) ->
NewName = list_to_atom(asn1ct_gen:list2name(NameList)),
case is_record_normalized(S,NewName,Value,length(Components)) of
true ->
Value;
_ ->
SortedVal = sort_value(Components,Value),
normalized_record('SET',S,SortedVal,Components,NameList)
end.
sort_value(Components, Value0) when is_list(Value0) ->
{Keys0,_} = lists:mapfoldl(fun(#'ComponentType'{name=N}, I) ->
{{N,I},I+1}
end, 0, Components),
Keys = gb_trees:from_orddict(orddict:from_list(Keys0)),
Value1 = [{case gb_trees:lookup(N, Keys) of
{value,K} -> K;
none -> 'end'
end,Pair} || {#seqtag{val=N},_}=Pair <- Value0],
Value = lists:sort(Value1),
[Pair || {_,Pair} <- Value];
sort_value(_Components, #'Externalvaluereference'{}=Value) ->
%% Sort later.
Value.
sort_val_if_set(['SET'|_],Val,Type) ->
sort_value(Type,Val);
sort_val_if_set(_,Val,_) ->
Val.
normalized_record(SorS,S,Value,Components,NameList) ->
NewName = list_to_atom(lists:concat([get_record_prefix_name(S),
asn1ct_gen:list2name(NameList)])),
case is_record_normalized(S,NewName,Value,length(Components)) of
true ->
Value;
false ->
NoComps = length(Components),
ListOfVals = normalize_seq_or_set(SorS,S,Value,Components,NameList,[]),
NoComps = length(ListOfVals), %Assertion.
case use_maps(S) of
false ->
list_to_tuple([NewName|ListOfVals]);
true ->
create_map_value(Components, ListOfVals)
end
end.
is_record_normalized(S,Name,V = #'Externalvaluereference'{},NumComps) ->
case get_referenced_type(S,V) of
{_M,#valuedef{type=_T1,value=V2}} ->
is_record_normalized(S,Name,V2,NumComps);
_ -> false
end;
is_record_normalized(_S,Name,Value,NumComps) when is_tuple(Value) ->
(tuple_size(Value) =:= (NumComps + 1)) andalso (element(1, Value) =:= Name);
is_record_normalized(_S, _Name, Value, _NumComps) when is_map(Value) ->
true;
is_record_normalized(_,_,_,_) ->
false.
use_maps(#state{options=Opts}) ->
lists:member(maps, Opts).
create_map_value(Components, ListOfVals) ->
Zipped = lists:zip(Components, ListOfVals),
L = [{Name,V} || {#'ComponentType'{name=Name},V} <- Zipped,
V =/= asn1_NOVALUE],
maps:from_list(L).
normalize_seq_or_set(SorS, S,
[{#seqtag{val=Cname},V}|Vs],
[#'ComponentType'{name=Cname,typespec=TS}|Cs],
NameList, Acc) ->
NewNameList =
case TS#type.def of
#'Externaltypereference'{type=TName} ->
[TName];
_ -> [Cname|NameList]
end,
NVal = normalize_value(S,TS,{'DEFAULT',V},NewNameList),
normalize_seq_or_set(SorS,S,Vs,Cs,NameList,[NVal|Acc]);
normalize_seq_or_set(SorS, S,
Values=[{#seqtag{val=Cname0},_V}|_Vs],
[#'ComponentType'{prop='OPTIONAL'}|Cs],
NameList, Acc) ->
verify_valid_component(S, Cname0, Cs),
normalize_seq_or_set(SorS,S,Values,Cs,NameList,[asn1_NOVALUE|Acc]);
normalize_seq_or_set(SorS, S,
Values=[{#seqtag{val=Cname0},_V}|_Vs],
[#'ComponentType'{name=Cname,typespec=TS,
prop={'DEFAULT',Value}}|Cs],
NameList, Acc) ->
verify_valid_component(S, Cname0, Cs),
NewNameList =
case TS#type.def of
#'Externaltypereference'{type=TName} ->
[TName];
_ -> [Cname|NameList]
end,
NVal = normalize_value(S,TS,{'DEFAULT',Value},NewNameList),
normalize_seq_or_set(SorS,S,Values,Cs,NameList,[NVal|Acc]);
%% If default value is {} ComponentTypes in SEQUENCE are marked DEFAULT
%% or OPTIONAL (or the type is defined SEQUENCE{}, which is handled by
%% the previous case).
normalize_seq_or_set(SorS,S,[],
[#'ComponentType'{name=Name,typespec=TS,
prop={'DEFAULT',Value}}|Cs],
NameList,Acc) ->
NewNameList =
case TS#type.def of
#'Externaltypereference'{type=TName} ->
[TName];
_ -> [Name|NameList]
end,
NVal = normalize_value(S,TS,{'DEFAULT',Value},NewNameList),
normalize_seq_or_set(SorS,S,[],Cs,NameList,[NVal|Acc]);
normalize_seq_or_set(SorS,S,[],[#'ComponentType'{prop='OPTIONAL'}|Cs],
NameList,Acc) ->
normalize_seq_or_set(SorS,S,[],Cs,NameList,[asn1_NOVALUE|Acc]);
normalize_seq_or_set(SorS,S,Value=#'Externalvaluereference'{},
Cs,NameList,Acc) ->
get_normalized_value(S,Value,Cs,fun normalize_seq_or_set/6,
[SorS,NameList,Acc]);
normalize_seq_or_set(_SorS, _S, [], [], _, Acc) ->
lists:reverse(Acc);
normalize_seq_or_set(_SorS, S, V, Cs, _, _) ->
case V of
[{#seqtag{val=Name},_}|_] ->
asn1_error(S, {illegal_id,error_value(Name)});
[] ->
[#'ComponentType'{name=Name}|_] = Cs,
asn1_error(S, {missing_id,error_value(Name)})
end.
verify_valid_component(S, Name, Cs) ->
case lists:keyfind(Name, #'ComponentType'.name, Cs) of
false -> asn1_error(S, {illegal_id,error_value(Name)});
#'ComponentType'{} -> ok
end.
normalize_seqof(S,Value,Type,NameList) ->
normalize_s_of('SEQUENCE OF',S,Value,Type,NameList).
normalize_setof(S,Value,Type,NameList) ->
normalize_s_of('SET OF',S,Value,Type,NameList).
normalize_s_of(SorS,S,Value,Type,NameList) when is_list(Value) ->
DefValueList = lists:map(fun(X) -> {'DEFAULT',X} end,Value),
Suffix = asn1ct_gen:constructed_suffix(SorS,Type),
Def = Type#type.def,
InnerType = asn1ct_gen:get_inner(Def),
WhatKind = asn1ct_gen:type(InnerType),
NewNameList =
case WhatKind of
{constructed,bif} ->
[Suffix|NameList];
#'Externaltypereference'{type=Name} ->
[Name];
_ -> []
end,
NormFun = fun (X) -> normalize_value(S,Type,X,
NewNameList) end,
case catch lists:map(NormFun, DefValueList) of
List when is_list(List) ->
List;
_ ->
asn1ct:warning("~p could not handle value ~p~n",[SorS,Value],S,
"could not handle value"),
Value
end;
normalize_s_of(SorS,S,Value,Type,NameList)
when is_record(Value,'Externalvaluereference') ->
get_normalized_value(S,Value,Type,fun normalize_s_of/5,
[SorS,NameList]).
%% normalize_restrictedstring handles all format of restricted strings.
%% character string list case
normalize_restrictedstring(S,[H|T],CType) when is_list(H);is_tuple(H) ->
[normalize_restrictedstring(S,H,CType)|normalize_restrictedstring(S,T,CType)];
%% character sting case
normalize_restrictedstring(_S,CString,_) when is_list(CString) ->
CString;
%% definedvalue case or argument in a parameterized type
normalize_restrictedstring(S,ERef,CType) when is_record(ERef,'Externalvaluereference') ->
get_normalized_value(S,ERef,CType,
fun normalize_restrictedstring/3,[]).
normalize_objectclassfieldvalue(S,{opentypefieldvalue,Type,Value},NameList) ->
%% An open type has per definition no type. Thus should the type
%% information of the default type be available at
%% encode/decode. But as encoding the default value causes special
%% treatment (no encoding) whatever type is used the type
%% information is not necessary in encode/decode.
normalize_value(S,Type,Value,NameList);
normalize_objectclassfieldvalue(_S,Other,_NameList) ->
%% If the type info was thrown away in an earlier step the value
%% is already normalized.
Other.
get_normalized_value(S,Val,Type,Func,AddArg) ->
case catch get_referenced_type(S,Val) of
{ExtM,_VDef = #valuedef{type=_T1,value=V}} ->
%% should check that Type and T equals
V2 = sort_val_if_set(AddArg,V,Type),
call_Func(update_state(S,ExtM),V2,Type,Func,AddArg);
{error,_} ->
asn1ct:warning("default value not comparable ~p~n",[Val],S),
Val;
{ExtM,NewVal} ->
V2 = sort_val_if_set(AddArg,NewVal,Type),
call_Func(update_state(S,ExtM),V2,Type,Func,AddArg);
_ ->
asn1ct:warning("default value not comparable ~p~n",[Val],S,
"default value not comparable"),
Val
end.
call_Func(S,Val,Type,Func,ArgList) ->
case ArgList of
[] ->
Func(S,Val,Type);
[LastArg] ->
Func(S,Val,Type,LastArg);
[Arg1,LastArg1] ->
Func(Arg1,S,Val,Type,LastArg1);
[Arg1,LastArg1,LastArg2] ->
Func(Arg1,S,Val,Type,LastArg1,LastArg2)
end.
get_canonic_type(S,Type,NameList) ->
{InnerType,NewType,NewNameList} =
case Type#type.def of
'INTEGER'=Name ->
{Name,[],NameList};
Name when is_atom(Name) ->
{Name,Type,NameList};
Ref when is_record(Ref,'Externaltypereference') ->
{_,#typedef{name=Name,typespec=RefedType}} =
get_referenced_type(S,Ref),
get_canonic_type(S,RefedType,[Name]);
{Name,T} when is_atom(Name) ->
{Name,T,NameList};
Seq when is_record(Seq,'SEQUENCE') ->
{'SEQUENCE',Seq#'SEQUENCE'.components,NameList};
Set when is_record(Set,'SET') ->
{'SET',Set#'SET'.components,NameList};
#'ObjectClassFieldType'{type=T} ->
{'ASN1_OPEN_TYPE',T,NameList}
end,
{asn1ct_gen:unify_if_string(InnerType),NewType,NewNameList}.
check_ptype(S,Type,Ts) when is_record(Ts,type) ->
check_formal_parameters(S, Type#ptypedef.args),
Def = Ts#type.def,
NewDef=
case Def of
Seq when is_record(Seq,'SEQUENCE') ->
Components = expand_components(S,Seq#'SEQUENCE'.components),
#newt{type=Seq#'SEQUENCE'{pname=get_datastr_name(Type),
components = Components}};
Set when is_record(Set,'SET') ->
Components = expand_components(S,Set#'SET'.components),
#newt{type=Set#'SET'{pname=get_datastr_name(Type),
components = Components}};
_Other ->
#newt{}
end,
Ts2 = case NewDef of
#newt{type=unchanged} ->
Ts;
#newt{type=TDef}->
Ts#type{def=TDef}
end,
Ts2;
%% parameterized class
check_ptype(_S,_PTDef,Ts) when is_record(Ts,objectclass) ->
throw({asn1_param_class,Ts}).
check_formal_parameters(S, Args) ->
_ = [check_formal_parameter(S, A) || A <- Args],
ok.
check_formal_parameter(_, {_,_}) ->
ok;
check_formal_parameter(_, #'Externaltypereference'{}) ->
ok;
check_formal_parameter(S, #'Externalvaluereference'{value=Name}) ->
asn1_error(S, {illegal_typereference,Name}).
check_type(_S,Type,Ts) when is_record(Type,typedef),
(Type#typedef.checked==true) ->
Ts;
check_type(_S,Type,Ts) when is_record(Type,typedef),
(Type#typedef.checked==idle) -> % the check is going on
Ts;
check_type(S=#state{recordtopname=TopName},Type,Ts) when is_record(Ts,type) ->
{Def,Tag,Constr,IsInlined} =
case match_parameter(S, Ts#type.def) of
#type{tag=PTag,constraint=_Ctmp,def=Dtmp,inlined=Inl} ->
{Dtmp,merge_tags(Ts#type.tag,PTag),Ts#type.constraint,Inl};
#typedef{typespec=#type{tag=PTag,def=Dtmp,inlined=Inl}} ->
{Dtmp,merge_tags(Ts#type.tag,PTag),Ts#type.constraint,Inl};
Dtmp ->
{Dtmp,Ts#type.tag,Ts#type.constraint,Ts#type.inlined}
end,
TempNewDef = #newt{type=Def,tag=Tag,constraint=Constr,
inlined=IsInlined},
TestFun =
fun(Tref) ->
{_, MaybeChoice} = get_referenced_type(S, Tref, true),
case catch((MaybeChoice#typedef.typespec)#type.def) of
{'CHOICE',_} ->
maybe_illicit_implicit_tag(S, choice, Tag);
'ANY' ->
maybe_illicit_implicit_tag(S, open_type, Tag);
'ANY DEFINED BY' ->
maybe_illicit_implicit_tag(S, open_type, Tag);
'ASN1_OPEN_TYPE' ->
maybe_illicit_implicit_tag(S, open_type, Tag);
_ ->
Tag
end
end,
NewDef=
case Def of
Ext when is_record(Ext,'Externaltypereference') ->
{RefMod,RefTypeDef,IsParamDef} =
case get_referenced_type(S, Ext) of
{undefined,TmpTDef} -> %% A parameter
{get(top_module),TmpTDef,true};
{TmpRefMod,TmpRefDef} ->
{TmpRefMod,TmpRefDef,false}
end,
case get_class_def(S, RefTypeDef) of
none -> ok;
#classdef{} -> throw({asn1_class,RefTypeDef})
end,
Ct = TestFun(Ext),
{RefType,ExtRef} =
case RefTypeDef#typedef.checked of
true ->
{RefTypeDef#typedef.typespec,Ext};
_ ->
%% Put as idle to prevent recursive loops
NewRefTypeDef1 = RefTypeDef#typedef{checked=idle},
asn1_db:dbput(RefMod,
get_datastr_name(NewRefTypeDef1),
NewRefTypeDef1),
NewS = S#state{mname=RefMod,
module=load_asn1_module(S,RefMod),
tname=get_datastr_name(NewRefTypeDef1),
abscomppath=[],recordtopname=[]},
RefType1 =
check_type(NewS,RefTypeDef,RefTypeDef#typedef.typespec),
%% update the type and mark as checked
NewRefTypeDef2 =
RefTypeDef#typedef{checked=true,typespec = RefType1},
TmpName = get_datastr_name(NewRefTypeDef2),
asn1_db:dbput(RefMod,
TmpName,
NewRefTypeDef2),
case {RefMod == get(top_module),IsParamDef} of
{true,true} ->
Key = {TmpName,
type,
NewRefTypeDef2},
asn1ct_gen:insert_once(parameterized_objects,
Key);
_ -> ok
end,
Pos = Ext#'Externaltypereference'.pos,
{RefType1,#'Externaltypereference'{module=RefMod,
pos=Pos,
type=TmpName}}
end,
case asn1ct_gen:prim_bif(asn1ct_gen:get_inner(RefType#type.def)) of
true ->
%% Here we expand to a built in type and inline it
NewC = check_constraints(S, RefType, Constr ++
RefType#type.constraint),
TempNewDef#newt{
type = RefType#type.def,
tag = merge_tags(Ct,RefType#type.tag),
constraint = NewC};
_ ->
%% Here we only expand the tags and keep the ext ref.
NewExt = ExtRef#'Externaltypereference'{module=merged_mod(S,RefMod,Ext)},
TempNewDef#newt{
type = check_externaltypereference(S,NewExt),
tag = merge_tags(Ct,RefType#type.tag)}
end;
'ANY' ->
Ct = maybe_illicit_implicit_tag(S, open_type, Tag),
TempNewDef#newt{type='ASN1_OPEN_TYPE',tag=Ct};
{'ANY_DEFINED_BY',_} ->
Ct = maybe_illicit_implicit_tag(S, open_type, Tag),
TempNewDef#newt{type='ASN1_OPEN_TYPE',tag=Ct};
'INTEGER' ->
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_INTEGER))};
{'INTEGER',NamedNumberList} ->
TempNewDef#newt{type={'INTEGER',check_integer(S,NamedNumberList)},
tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_INTEGER))};
'REAL' ->
check_real(S,Constr),
TempNewDef#newt{tag=merge_tags(Tag,?TAG_PRIMITIVE(?N_REAL))};
{'BIT STRING',NamedNumberList} ->
NewL = check_bitstring(S, NamedNumberList),
TempNewDef#newt{type={'BIT STRING',NewL},
tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_BIT_STRING))};
'NULL' ->
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_NULL))};
'OBJECT IDENTIFIER' ->
check_objectidentifier(S,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_OBJECT_IDENTIFIER))};
'ObjectDescriptor' ->
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_OBJECT_DESCRIPTOR))};
'EXTERNAL' ->
put_once(external,unchecked),
TempNewDef#newt{type=
#'Externaltypereference'{module=S#state.mname,
type='EXTERNAL'},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_EXTERNAL))};
{'INSTANCE OF',DefinedObjectClass,Constraint} ->
%% check that DefinedObjectClass is of TYPE-IDENTIFIER class
%% If Constraint is empty make it the general INSTANCE OF type
%% If Constraint is not empty make an inlined type
%% convert INSTANCE OF to the associated type
IOFDef=check_instance_of(S,DefinedObjectClass,Constraint),
TempNewDef#newt{type=IOFDef,
tag=merge_tags(Tag,?TAG_CONSTRUCTED(?N_INSTANCE_OF))};
{'ENUMERATED',NamedNumberList} ->
TempNewDef#newt{type=
{'ENUMERATED',
check_enumerated(S, NamedNumberList)},
tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_ENUMERATED)),
constraint=[]};
'EMBEDDED PDV' ->
put_once(embedded_pdv,unchecked),
TempNewDef#newt{type=
#'Externaltypereference'{module=S#state.mname,
type='EMBEDDED PDV'},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_EMBEDDED_PDV))};
'BOOLEAN'->
check_boolean(S,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_BOOLEAN))};
'OCTET STRING' ->
check_octetstring(S,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_OCTET_STRING))};
'NumericString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_NumericString))};
TString when TString =:= 'TeletexString';
TString =:= 'T61String' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_TeletexString))};
'VideotexString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_VideotexString))};
'UTCTime' ->
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_UTCTime))};
'GeneralizedTime' ->
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_GeneralizedTime))};
'GraphicString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_GraphicString))};
'VisibleString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_VisibleString))};
'GeneralString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_GeneralString))};
'PrintableString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_PrintableString))};
'IA5String' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_IA5String))};
'BMPString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_BMPString))};
'UniversalString' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_UniversalString))};
'UTF8String' ->
check_restrictedstring(S,Def,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?N_UTF8String))};
'RELATIVE-OID' ->
check_relative_oid(S,Constr),
TempNewDef#newt{tag=
merge_tags(Tag,?TAG_PRIMITIVE(?'N_RELATIVE-OID'))};
'CHARACTER STRING' ->
put_once(character_string,unchecked),
TempNewDef#newt{type=
#'Externaltypereference'{module=S#state.mname,
type='CHARACTER STRING'},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_CHARACTER_STRING))};
Seq when is_record(Seq,'SEQUENCE') ->
RecordName =
case TopName of
[] ->
[get_datastr_name(Type)];
_ ->
TopName
end,
{TableCInf,Components} =
check_sequence(S#state{recordtopname=
RecordName},
Type,Seq#'SEQUENCE'.components),
TempNewDef#newt{type=Seq#'SEQUENCE'{tablecinf=tablecinf_choose(Seq,TableCInf),
components=Components},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_SEQUENCE))};
{'SEQUENCE OF',Components} ->
TempNewDef#newt{type={'SEQUENCE OF',check_sequenceof(S,Type,Components)},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_SEQUENCE))};
{'CHOICE',_} = Choice->
Ct = maybe_illicit_implicit_tag(S, choice, Tag),
Components = get_choice_components(S, Choice),
TempNewDef#newt{type={'CHOICE',check_choice(S,Type,Components)},tag=Ct};
Set when is_record(Set,'SET') ->
RecordName=
case TopName of
[] ->
[get_datastr_name(Type)];
_ ->
TopName
end,
{Sorted,TableCInf,Components} =
check_set(S#state{recordtopname=RecordName},
Type,Set#'SET'.components),
TempNewDef#newt{type=Set#'SET'{sorted=Sorted,
tablecinf=tablecinf_choose(Set,TableCInf),
components=Components},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_SET))};
{'SET OF',Components} ->
TempNewDef#newt{type={'SET OF',check_setof(S,Type,Components)},
tag=
merge_tags(Tag,?TAG_CONSTRUCTED(?N_SET))};
{pt,Ptype,ParaList} ->
%% Ptype might be a parameterized - type, object set or
%% value set. If it isn't a parameterized type notify the
%% calling function.
{_RefMod,Ptypedef} = get_referenced_type(S,Ptype),
notify_if_not_ptype(S,Ptypedef),
NewParaList = match_parameters(S, ParaList),
Instance = instantiate_ptype(S,Ptypedef,NewParaList),
TempNewDef#newt{type=Instance#type.def,
tag=merge_tags(Tag,Instance#type.tag),
constraint=Instance#type.constraint,
inlined=yes};
#'ObjectClassFieldType'{classname=ClRef0}=OCFT0 ->
%% this case occures in a SEQUENCE when
%% the type of the component is a ObjectClassFieldType
ClRef = match_parameter(S, ClRef0),
OCFT = OCFT0#'ObjectClassFieldType'{classname=ClRef},
ClassSpec = check_class(S,ClRef),
NewTypeDef =
maybe_open_type(S,ClassSpec,
OCFT#'ObjectClassFieldType'{class=ClassSpec},Constr),
InnerTag = get_innertag(S,NewTypeDef),
MergedTag = merge_tags(Tag,InnerTag),
Ct =
case is_open_type(NewTypeDef) of
true ->
maybe_illicit_implicit_tag(S, open_type, MergedTag);
_ ->
MergedTag
end,
case TopName of
[] when Type#typedef.name =/= undefined ->
%% This is a top-level type.
#type{constraint=C,def=Simplified} =
simplify_type(#type{def=NewTypeDef,
constraint=Constr}),
TempNewDef#newt{type=Simplified,tag=Ct,
constraint=C};
_ ->
TempNewDef#newt{type=NewTypeDef,tag=Ct}
end;
{'TypeFromObject',{object,Object},TypeField} ->
CheckedT = get_type_from_object(S,Object,TypeField),
TempNewDef#newt{tag=merge_tags(Tag,CheckedT#type.tag),
type=CheckedT#type.def};
{'SelectionType',Name,T} ->
CheckedT = check_selectiontype(S,Name,T),
TempNewDef#newt{tag=merge_tags(Tag,CheckedT#type.tag),
type=CheckedT#type.def};
'ASN1_OPEN_TYPE' ->
TempNewDef
end,
#newt{type=TDef,tag=NewTags,constraint=NewConstr,inlined=Inlined} = NewDef,
Ts#type{def=TDef,
inlined=Inlined,
constraint=check_constraints(S, #type{def=TDef}, NewConstr),
tag=lists:map(fun(#tag{type={default,TTx}}=TempTag) ->
TempTag#tag{type=TTx};
(Other) -> Other
end, NewTags)}.
%%
%% Simplify the backends by getting rid of an #'ObjectClassFieldType'{}
%% with a type known at compile time.
%%
simplify_comps(Comps) ->
[simplify_comp(Comp) || Comp <- Comps].
simplify_comp(#'ComponentType'{typespec=Type0}=C) ->
Type = simplify_type(Type0),
C#'ComponentType'{typespec=Type};
simplify_comp(Other) -> Other.
simplify_type(#type{tag=Tag,def=Inner,constraint=Constr0}=T) ->
case Inner of
#'ObjectClassFieldType'{type={fixedtypevaluefield,_,Type}}=OCFT ->
Constr = [{ocft,OCFT}|Type#type.constraint++Constr0],
Type#type{tag=Tag,constraint=Constr};
_ ->
T
end.
%% tablecinf_choose. A SEQUENCE or SET may be inserted in another
%% SEQUENCE or SET by the COMPONENTS OF directive. If this inserted
%% type is a referenced type that already has been checked it already
%% has its tableconstraint information. Furthermore this information
%% may be lost in the analysis in the new environment. Assume this
%% SEQUENCE/SET has a simpletable constraint and a componentrelation
%% constraint whose atlist points to the outermost component of its
%% "standalone" definition. This will cause the analysis to fail as it
%% will not find the right atlist component in the outermost
%% environment in the new inlined environment.
tablecinf_choose(SetOrSeq,false) ->
tablecinf_choose(SetOrSeq);
tablecinf_choose(_, TableCInf) ->
TableCInf.
tablecinf_choose(#'SET'{tablecinf=TCI}) ->
TCI;
tablecinf_choose(#'SEQUENCE'{tablecinf=TCI}) ->
TCI.
get_innertag(_S,#'ObjectClassFieldType'{type=Type}) ->
case Type of
{fixedtypevaluefield,_,#type{tag=Tag}} -> Tag;
{TypeFieldName,_} when is_atom(TypeFieldName) -> [];
_ -> []
end.
%% get_class_def(S, Type) -> #classdef{} | 'none'.
get_class_def(S, #typedef{typespec=#type{def=#'Externaltypereference'{}=Eref}}) ->
{_,NextDef} = get_referenced_type(S, Eref, true),
get_class_def(S, NextDef);
get_class_def(S, #'Externaltypereference'{}=Eref) ->
{_,NextDef} = get_referenced_type(S, Eref, true),
get_class_def(S, NextDef);
get_class_def(_S, #classdef{}=CD) ->
CD;
get_class_def(_S, _) ->
none.
maybe_illicit_implicit_tag(S, Kind, Tag) ->
case Tag of
[#tag{type='IMPLICIT'}|_T] ->
asn1_error(S, {implicit_tag_before,Kind});
[ChTag = #tag{type={default,_}}|T] ->
case Kind of
open_type ->
[ChTag#tag{type='EXPLICIT',form=32}|T]; %X.680 30.6c, X.690 8.14.2
choice ->
[ChTag#tag{type='EXPLICIT',form=32}|T] % X.680 28.6 c, 30.6c
end;
_ ->
Tag % unchanged
end.
merged_mod(S,RefMod,Ext) ->
case S of
#state{inputmodules=[]} ->
RefMod;
_ ->
Ext#'Externaltypereference'.module
end.
%% maybe_open_type/2 -> #ObjectClassFieldType with updated fieldname and
%% type
%% if the FieldRefList points out a typefield and the class don't have
%% any UNIQUE field, so that a component relation constraint cannot specify
%% the type of a typefield, return 'ASN1_OPEN_TYPE'.
%%
maybe_open_type(_, _, #'ObjectClassFieldType'{fieldname={_,_}}=OCFT, _) ->
%% Already converted.
OCFT;
maybe_open_type(S, #objectclass{fields=Fs}=ClassSpec,
#'ObjectClassFieldType'{fieldname=FieldRefList}=OCFT,
Constr) ->
Type = get_OCFType(S, Fs, FieldRefList),
FieldNames = get_referenced_fieldname(FieldRefList),
case lists:last(FieldRefList) of
{valuefieldreference,_} ->
OCFT#'ObjectClassFieldType'{fieldname=FieldNames,
type=Type};
{typefieldreference,_} ->
%% Note: The constraints have not been checked yet,
%% so we must use a special lookup routine.
case {get_unique_fieldname(S, #classdef{typespec=ClassSpec}),
get_componentrelation(Constr)} of
{no_unique,_} ->
OCFT#'ObjectClassFieldType'{fieldname=FieldNames,
type='ASN1_OPEN_TYPE'};
{_,no} ->
OCFT#'ObjectClassFieldType'{fieldname=FieldNames,
type='ASN1_OPEN_TYPE'};
_ ->
OCFT#'ObjectClassFieldType'{fieldname=FieldNames,
type=Type}
end
end.
get_componentrelation([{element_set,{componentrelation,_,_}=Cr,none}|_]) ->
Cr;
get_componentrelation([_|T]) ->
get_componentrelation(T);
get_componentrelation([]) ->
no.
is_open_type(#'ObjectClassFieldType'{type='ASN1_OPEN_TYPE'}) ->
true;
is_open_type(#'ObjectClassFieldType'{}) ->
false.
notify_if_not_ptype(S,#pvaluesetdef{type=Type}) ->
case Type#type.def of
Ref when is_record(Ref,'Externaltypereference') ->
case get_referenced_type(S,Ref) of
{_,#classdef{}} ->
throw(pobjectsetdef);
{_,#typedef{}} ->
throw(pvalueset)
end;
T when is_record(T,type) -> % this must be a value set
throw(pvalueset)
end;
notify_if_not_ptype(_S,PT=#ptypedef{}) ->
%% this may be a parameterized CLASS, in that case throw an
%% asn1_class exception
case PT#ptypedef.typespec of
#objectclass{} -> throw({asn1_class,PT});
_ -> ok
end;
notify_if_not_ptype(S,#pobjectsetdef{class=Cl}) ->
case Cl of
#'Externaltypereference'{} ->
case get_referenced_type(S,Cl) of
{_,#classdef{}} ->
throw(pobjectsetdef);
{_,#typedef{}} ->
throw(pvalueset)
end;
_ ->
throw(pobjectsetdef)
end;
notify_if_not_ptype(S, PT) ->
asn1_error(S, {param_bad_type, error_value(PT)}).
instantiate_ptype(S,Ptypedef,ParaList) ->
#ptypedef{args=Args,typespec=Type} = Ptypedef,
NewType = check_ptype(S,Ptypedef,Type#type{inlined=yes}),
MatchedArgs = match_args(S,Args, ParaList, []),
OldArgs = S#state.parameters,
NewS = S#state{parameters=MatchedArgs++OldArgs,abscomppath=[]},
check_type(NewS, Ptypedef#ptypedef{typespec=NewType}, NewType).
get_datastr_name(Type) ->
asn1ct:get_name_of_def(Type).
get_pt_args(#ptypedef{args=Args}) ->
Args;
get_pt_args(#pvaluesetdef{args=Args}) ->
Args;
get_pt_args(#pvaluedef{args=Args}) ->
Args;
get_pt_args(#pobjectdef{args=Args}) ->
Args;
get_pt_args(#pobjectsetdef{args=Args}) ->
Args.
get_pt_spec(#ptypedef{typespec=Type}) ->
Type;
get_pt_spec(#pvaluedef{value=Value}) ->
Value;
get_pt_spec(#pvaluesetdef{valueset=VS}) ->
VS;
get_pt_spec(#pobjectdef{def=Def}) ->
Def;
get_pt_spec(#pobjectsetdef{def=Def}) ->
Def.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% match_args(S,FormalArgs, ActualArgs, Accumulator) -> Result
%% S = #state{}
%% FormalArgs = [term()] | [{Governor,Parameter}]
%% ActualArgs = [term()]
%% Accumulator = [term()]
%% Result = [{term(),term()}] | throw()
%% Governor = #type{} | Reference | 'TYPE-IDENTIFIER' | 'ABSTRACT-SYNTAX'
%% Parameter = Reference | {Governor,Reference}
%% Reference = #'Externaltypereference'{} | #'Externalvaluerference'{}
%%
%% Different categories of parameters and governors (Dubuisson p.382)
%% +----------------+-------------------------------+----------------------+
%% |Governor is | Parameter name style | Parameter is |
%% +----------------+-------------------------------+----------------------+
%% | absent | begins with uppercase,(bu) | a type |
%% | | | |
%% | a type | begins with a lowercase,(bl)| a value |
%% | | | |
%% | a type | begins with an uppercase | a value set |
%% | | | |
%% | absent | entirely in uppercase, (eu) | a class (or type) |
%% | | | |
%% | a class name | begins with a lowercase | an object |
%% | | | |
%% | a class name | begins with an uppercase | an object set |
%% +----------------+-------------------------------+----------------------+
%%
%% Matches each of the formal parameters to corresponding actual
%% parameter, and changes format of the actual parameter according to
%% above table if necessary.
match_args(S,FA = [FormArg|Ft], AA = [ActArg|At], Acc) ->
OldParams = S#state.parameters,
case categorize_arg(S,FormArg,ActArg) of
[CategorizedArg] ->
match_args(S#state{parameters=
[{FormArg,CategorizedArg}|OldParams]},
Ft, At, [{FormArg,CategorizedArg}|Acc]);
CategorizedArgs ->
match_args(S#state{parameters=CategorizedArgs++OldParams},
FA, CategorizedArgs ++ AA, Acc)
end;
match_args(_S,[], [], Acc) ->
lists:reverse(Acc);
match_args(S, _, _, _) ->
asn1_error(S, param_wrong_number_of_arguments).
%%%%%%%%%%%%%%%%%
%% categorize_arg(S,FormalArg,ActualArg) -> {FormalArg,CatgorizedActualArg}
%%
categorize_arg(S,{Governor,Param},ActArg) ->
case {governor_category(S, Governor),parameter_name_style(Param)} of
{type,beginning_lowercase} -> %a value
categorize(S, value, Governor, ActArg);
{type,beginning_uppercase} -> %a value set
categorize(ActArg);
{{class,ClassRef},beginning_lowercase} ->
categorize(S, object, ActArg, ClassRef);
{{class,ClassRef},beginning_uppercase} ->
categorize(S, object_set, ActArg, ClassRef)
end;
categorize_arg(_S, _FormalArg, ActualArg) ->
%% Governor is absent -- must be a type or a class. We have already
%% checked that the FormalArg begins with an uppercase letter.
categorize(ActualArg).
%% governor_category(S, Item) -> type | {class,#'Externaltypereference'{}}
%% Determine whether Item is a type or a class.
governor_category(S, #type{def=#'Externaltypereference'{}=Eref}) ->
governor_category(S, Eref);
governor_category(_S, #type{}) ->
type;
governor_category(S, #'Externaltypereference'{}=Ref) ->
case get_class_def(S, Ref) of
#classdef{pos=Pos,module=Mod,name=Name} ->
{class,#'Externaltypereference'{pos=Pos,module=Mod,type=Name}};
none ->
type
end.
%% parameter_name_style(Param,Data) -> Result
%% gets the Parameter and the name of the Data and if it exists tells
%% whether it begins with a lowercase letter or is partly or entirely
%% spelled with uppercase letters. Otherwise returns undefined
%%
parameter_name_style(#'Externaltypereference'{}) ->
beginning_uppercase;
parameter_name_style(#'Externalvaluereference'{}) ->
beginning_lowercase.
%% categorize(Parameter) -> CategorizedParameter
%% If Parameter has an abstract syntax of another category than
%% Category, transform it to a known syntax.
categorize({object,_,Type}) ->
%% One example of this case is an object with a parameterized type
%% having a locally defined type as parameter.
Def = fun(D = #type{}) ->
#typedef{name = new_reference_name("type_argument"),
typespec = D#type{inlined=yes}};
({setting,_,Eref}) when is_record(Eref,'Externaltypereference') ->
Eref;
(D) ->
D
end,
[Def(X)||X<-Type];
categorize(#type{}=Def) ->
[#typedef{name = new_reference_name("type_argument"),
typespec = Def#type{inlined=yes}}];
categorize(Def) ->
[Def].
categorize(S,object_set,Def,ClassRef) ->
NewObjSetSpec =
check_object(S,Def,#'ObjectSet'{class = ClassRef,
set = parse_objectset(Def)}),
Name = new_reference_name("object_set_argument"),
[save_object_set_instance(S,Name,NewObjSetSpec)];
categorize(_S,object,Def,_ClassRef) ->
%% should be handled
[Def];
categorize(_S,value,_Type,Value) when is_record(Value,valuedef) ->
[Value];
categorize(S,value,Type,Value) ->
%% [check_value(S,#valuedef{type=Type,value=Value})].
[#valuedef{type=Type,value=Value,module=S#state.mname}].
parse_objectset({valueset,#type{def=#'Externaltypereference'{}=Ref}}) ->
Ref;
parse_objectset({valueset,Set}) ->
Set;
parse_objectset(#type{def=Ref}) when is_record(Ref,'Externaltypereference') ->
Ref;
parse_objectset(Set) ->
%% extend this later
Set.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Check and simplify constraints.
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
check_constraints(_S, _HostType, []) ->
[];
check_constraints(S, HostType0, [_|_]=Cs0) ->
HostType = get_real_host_type(HostType0, Cs0),
Cs1 = top_level_intersections(Cs0),
Cs2 = [coalesce_constraints(C) || C <- Cs1],
{_,Cs3} = filter_extensions(Cs2),
Cs = simplify_element_sets(S, HostType, Cs3),
finish_constraints(Cs).
get_real_host_type(HostType, Cs) ->
case lists:keyfind(ocft, 1, Cs) of
false -> HostType;
{_,OCFT} -> HostType#type{def=OCFT}
end.
top_level_intersections([{element_set,{intersection,_,_}=C,none}]) ->
top_level_intersections_1(C);
top_level_intersections(Cs) ->
Cs.
top_level_intersections_1({intersection,A,B}) ->
[{element_set,A,none}|top_level_intersections_1(B)];
top_level_intersections_1(Other) ->
[{element_set,Other,none}].
coalesce_constraints({element_set,
{Tag,{element_set,A,_}},
{Tag,{element_set,B,_}}}) ->
%% (SIZE (C1), ..., (SIZE (C2)) => (SIZE (C1, ..., C2))
{element_set,{Tag,{element_set,A,B}},none};
coalesce_constraints(Other) ->
Other.
%% Remove all outermost extensions except the last.
filter_extensions([H0|T0]) ->
case filter_extensions(T0) of
{true,T} ->
H = remove_extension(H0),
{true,[H|T]};
{false,T} ->
{any_extension(H0),[H0|T]}
end;
filter_extensions([]) ->
{false,[]}.
remove_extension({element_set,Root,_}) ->
{element_set,remove_extension(Root),none};
remove_extension(Tuple) when is_tuple(Tuple) ->
L = [remove_extension(El) || El <- tuple_to_list(Tuple)],
list_to_tuple(L);
remove_extension(Other) -> Other.
any_extension({element_set,_,Ext}) when Ext =/= none ->
true;
any_extension(Tuple) when is_tuple(Tuple) ->
any_extension_tuple(1, Tuple);
any_extension(_) -> false.
any_extension_tuple(I, T) when I =< tuple_size(T) ->
any_extension(element(I, T)) orelse any_extension_tuple(I+1, T);
any_extension_tuple(_, _) -> false.
simplify_element_sets(S, HostType, [{element_set,R0,E0}|T0]) ->
R1 = simplify_element_set(S, HostType, R0),
E1 = simplify_element_set(S, HostType, E0),
case simplify_element_sets(S, HostType, T0) of
[{element_set,R2,E2}] ->
[{element_set,cs_intersection(S, R1, R2),
cs_intersection(S, E1, E2)}];
L when is_list(L) ->
[{element_set,R1,E1}|L]
end;
simplify_element_sets(S, HostType, [H|T]) ->
[H|simplify_element_sets(S, HostType, T)];
simplify_element_sets(_, _, []) ->
[].
simplify_element_set(_S, _HostType, empty) ->
{set,[]};
simplify_element_set(S, HostType, {'SingleValue',Vs0}) when is_list(Vs0) ->
Vs1 = [resolve_value(S, HostType, V) || V <- Vs0],
Vs = make_constr_set_vs(Vs1),
simplify_element_set(S, HostType, Vs);
simplify_element_set(S, HostType, {'SingleValue',V0}) ->
V1 = resolve_value(S, HostType, V0),
V = {set,[{range,V1,V1}]},
simplify_element_set(S, HostType, V);
simplify_element_set(S, HostType, {'ValueRange',{Lb0,Ub0}}) ->
Lb = resolve_value(S, HostType, Lb0),
Ub = resolve_value(S, HostType, Ub0),
V = make_constr_set(S, Lb, Ub),
simplify_element_set(S, HostType, V);
simplify_element_set(S, HostType, {'ALL-EXCEPT',Set0}) ->
Set = simplify_element_set(S, HostType, Set0),
{'ALL-EXCEPT',Set};
simplify_element_set(S, HostType, {intersection,A0,B0}) ->
A = simplify_element_set(S, HostType, A0),
B = simplify_element_set(S, HostType, B0),
cs_intersection(S, A, B);
simplify_element_set(S, HostType, {union,A0,B0}) ->
A = simplify_element_set(S, HostType, A0),
B = simplify_element_set(S, HostType, B0),
cs_union(S, A, B);
simplify_element_set(S, HostType, {simpletable,{element_set,Type,_}}) ->
check_simpletable(S, HostType, Type);
simplify_element_set(S, _, {componentrelation,R,Id}) ->
check_componentrelation(S, R, Id);
simplify_element_set(S, HostType, {Tag,{element_set,_,_}=El0}) ->
[El1] = simplify_element_sets(S, HostType, [El0]),
{Tag,El1};
simplify_element_set(S, HostType, #type{}=Type) ->
simplify_element_set_type(S, HostType, Type);
simplify_element_set(_, _, C) ->
C.
simplify_element_set_type(S, HostType, #type{def=Def0}=Type0) ->
#'Externaltypereference'{} = Def0, %Assertion.
case get_referenced_type(S, Def0) of
{_,#valuedef{checked=false,value={valueset,Vs0}}} ->
[Vs1] = simplify_element_sets(S, HostType, [Vs0]),
case Vs1 of
{element_set,Set,none} ->
Set;
{element_set,Set,{set,[]}} ->
Set
end;
{_,{valueset,#type{def=#'Externaltypereference'{}}=Type}} ->
simplify_element_set_type(S, HostType, Type);
_ ->
case HostType of
#type{def=#'ObjectClassFieldType'{}} ->
%% Open type.
#type{def=Def} = check_type(S, HostType, Type0),
Def;
_ ->
#type{constraint=Cs} = check_type(S, HostType, Type0),
C = convert_back(Cs),
simplify_element_set(S, HostType, C)
end
end.
convert_back([H1,H2|T]) ->
{intersection,H1,convert_back([H2|T])};
convert_back([H]) ->
H;
convert_back([]) ->
none.
check_simpletable(S, HostType, Type) ->
case HostType of
#type{def=#'ObjectClassFieldType'{}} ->
ok;
_ ->
%% Table constraints may only be applied to
%% CLASS.&field constructs.
asn1_error(S, illegal_table_constraint)
end,
Def = case Type of
#type{def=D} -> D;
{'SingleValue',#'Externalvaluereference'{}=ObjRef} ->
ObjRef;
_ ->
asn1_error(S, invalid_table_constraint)
end,
C = match_parameter(S, Def),
case C of
#'Externaltypereference'{} ->
ERef = check_externaltypereference(S, C),
{simpletable,ERef#'Externaltypereference'.type};
#'Externalvaluereference'{} ->
%% This is an object set with a referenced object
{_,TorVDef} = get_referenced_type(S, C),
Set = case TorVDef of
#typedef{typespec=#'Object'{classname=ClassName}} ->
#'ObjectSet'{class=ClassName,
set={'SingleValue',C}};
#valuedef{type=#type{def=ClassDef},
value=#'Externalvaluereference'{}=Obj} ->
%% an object might reference another object
#'ObjectSet'{class=ClassDef,
set={'SingleValue',Obj}}
end,
{simpletable,check_object(S, Type, Set)};
{'ValueFromObject',{_,Object},FieldNames} ->
%% This is an ObjectFromObject.
{simpletable,extract_field(S, Object, FieldNames)}
end.
check_componentrelation(S, {objectset,Opos,Objset0}, Id) ->
%% Objset is an 'Externaltypereference' record, since Objset is
%% a DefinedObjectSet.
ObjSet = match_parameter(S, Objset0),
Ext = check_externaltypereference(S, ObjSet),
{componentrelation,{objectset,Opos,Ext},Id}.
%%%
%%% Internal set representation.
%%%
%%% We represent sets as a union of strictly disjoint ranges:
%%%
%%% {set,[Range]}
%%%
%%% A range is represented as:
%%%
%%% Range = {a_range,UpperBound} | {range,LowerBound,UpperBound}
%%%
%%% We don't use the atom 'MIN' to represent MIN, because atoms
%%% compare higher than integer. Instead we use {a_range,UpperBound}
%%% to represent MIN..UpperBound. We represent MAX as 'MAX' because
%%% 'MAX' compares higher than any integer.
%%%
%%% The ranges are sorted in term order. The ranges must not overlap
%%% or be adjacent to each other. This invariant is established when
%%% creating sets, and maintained by the intersection and union
%%% operators.
%%%
%%% Example of invalid set representaions:
%%%
%%% [{range,0,10},{range,5,10}] %Overlapping ranges
%%% [{range,0,5},{range,6,10}] %Adjancent ranges
%%% [{range,10,20},{a_range,100}] %Not sorted
%%%
make_constr_set(_, 'MIN', Ub) ->
{set,[{a_range,make_constr_set_val(Ub)}]};
make_constr_set(_, Lb, Ub) when Lb =< Ub ->
{set,[{range,make_constr_set_val(Lb),
make_constr_set_val(Ub)}]};
make_constr_set(S, _, _) ->
asn1_error(S, reversed_range).
make_constr_set_val([C]) when is_integer(C) -> C;
make_constr_set_val(Val) -> Val.
make_constr_set_vs(Vs) ->
{set,make_constr_set_vs_1(Vs)}.
make_constr_set_vs_1([]) ->
[];
make_constr_set_vs_1([V]) ->
[{range,V,V}];
make_constr_set_vs_1([V0|Vs]) ->
V1 = make_constr_set_vs_1(Vs),
range_union([{range,V0,V0}], V1).
%%%
%%% Set operators.
%%%
cs_intersection(_S, Other, none) ->
Other;
cs_intersection(_S, none, Other) ->
Other;
cs_intersection(_S, {set,SetA}, {set,SetB}) ->
{set,range_intersection(SetA, SetB)};
cs_intersection(_S, A, B) ->
{intersection,A,B}.
range_intersection([], []) ->
[];
range_intersection([_|_], []) ->
[];
range_intersection([], [_|_]) ->
[];
range_intersection([H1|_]=A, [H2|_]=B) when H1 > H2 ->
range_intersection(B, A);
range_intersection([H1|T1], [H2|T2]=B) ->
%% Now H1 =< H2.
case {H1,H2} of
{{a_range,Ub0},{a_range,Ub1}} when Ub0 < Ub1 ->
%% Ub0 =/= 'MAX'
[H1|range_intersection(T1, [{range,Ub0+1,Ub1}|T2])];
{{a_range,_},{a_range,_}} ->
%% Must be equal.
[H1|range_intersection(T1, T2)];
{{a_range,Ub0},{range,Lb1,_Ub1}} when Ub0 < Lb1 ->
%% No intersection.
range_intersection(T1, B);
{{a_range,Ub0},{range,Lb1,Ub1}} when Ub0 < Ub1 ->
%% Ub0 =/= 'MAX'
[{range,Lb1,Ub0}|range_intersection(T1, [{range,Ub0+1,Ub1}|T2])];
{{a_range,Ub},{range,_Lb1,Ub}} ->
%% The first range covers the second range, but does not
%% go beyond. We handle this case specially because Ub may
%% be 'MAX', and evaluating 'MAX'+1 will fail.
[H2|range_intersection(T1, T2)];
{{a_range,Ub0},{range,_Lb1,Ub1}} ->
%% Ub0 > Ub1, Ub1 =/= 'MAX'. The first range completely
%% covers and extends beyond the second range.
[H2|range_intersection([{range,Ub1+1,Ub0}|T1], T2)];
{{range,_Lb0,Ub0},{range,Lb1,_Ub1}} when Ub0 < Lb1 ->
%% Lb0 < Lb1. No intersection.
range_intersection(T1, B);
{{range,_Lb0,Ub0},{range,Lb1,Ub1}} when Ub0 < Ub1 ->
%% Ub0 >= Lb1, Ub0 =/= 'MAX'. Partial overlap.
[{range,Lb1,Ub0}|range_intersection(T1, [{range,Ub0+1,Ub1}|T2])];
{{range,_Lb0,Ub},{range,_Lb1,Ub}} ->
%% The first range covers the second range, but does not
%% go beyond. We handle this case specially because Ub may
%% be 'MAX', and evaluating 'MAX'+1 will fail.
[H2|range_intersection(T1, T2)];
{{range,_Lb0,Ub0},{range,_Lb1,Ub1}} ->
%% Ub1 =/= MAX. The first range completely covers and
%% extends beyond the second.
[H2|range_intersection([{range,Ub1+1,Ub0}|T1], T2)]
end.
cs_union(_S, {set,SetA}, {set,SetB}) ->
{set,range_union(SetA, SetB)};
cs_union(_S, A, B) ->
{union,A,B}.
range_union(A, B) ->
range_union_1(lists:merge(A, B)).
range_union_1([{a_range,Ub0},{a_range,Ub1}|T]) ->
range_union_1([{a_range,max(Ub0, Ub1)}|T]);
range_union_1([{a_range,Ub0},{range,Lb1,Ub1}|T]) when Lb1-1 =< Ub0 ->
range_union_1([{a_range,max(Ub0, Ub1)}|T]);
range_union_1([{a_range,_}=H|T]) ->
%% Ranges are disjoint.
[H|range_union_1(T)];
range_union_1([{range,Lb0,Ub0},{range,Lb1,Ub1}|T]) when Lb1-1 =< Ub0 ->
range_union_1([{range,Lb0,max(Ub0, Ub1)}|T]);
range_union_1([{range,_,_}=H|T]) ->
%% Ranges are disjoint.
[H|range_union_1(T)];
range_union_1([]) ->
[].
%%%
%%% Finish up constrains, making them suitable for the back-ends.
%%%
%%% A 'PermittedAlphabet' (FROM) constraint will be reduced to:
%%%
%%% {'SingleValue',[integer()]}
%%%
%%% A 'SizeConstraint' (SIZE) constraint will be reduced to:
%%%
%%% {Lb,Ub}
%%%
%%% All other constraints will be reduced to:
%%%
%%% {'SingleValue',[integer()]} | {'ValueRange',Lb,Ub}
%%%
finish_constraints(Cs) ->
finish_constraints_1(Cs, fun smart_collapse/1).
finish_constraints_1([{element_set,{Tag,{element_set,_,_}=Set0},none}|T],
Collapse0) ->
Collapse = collapse_fun(Tag),
case finish_constraints_1([Set0], Collapse) of
[] ->
finish_constraints_1(T, Collapse0);
[Set] ->
[{Tag,Set}|finish_constraints_1(T, Collapse0)]
end;
finish_constraints_1([{element_set,{set,[{a_range,'MAX'}]},_}|T], Collapse) ->
finish_constraints_1(T, Collapse);
finish_constraints_1([{element_set,{intersection,A0,B0},none}|T], Collapse) ->
A = {element_set,A0,none},
B = {element_set,B0,none},
finish_constraints_1([A,B|T], Collapse);
finish_constraints_1([{element_set,Root,Ext}|T], Collapse) ->
case finish_constraint(Root, Ext, Collapse) of
none ->
finish_constraints_1(T, Collapse);
Constr ->
[Constr|finish_constraints_1(T, Collapse)]
end;
finish_constraints_1([H|T], Collapse) ->
[H|finish_constraints_1(T, Collapse)];
finish_constraints_1([], _) ->
[].
finish_constraint({set,Root0}, Ext, Collapse) ->
case Collapse(Root0) of
none -> none;
Root -> finish_constraint(Root, Ext, Collapse)
end;
finish_constraint(Root, Ext, _Collapse) ->
case Ext of
none -> Root;
_ -> {Root,[]}
end.
collapse_fun('SizeConstraint') ->
fun size_constraint_collapse/1;
collapse_fun('PermittedAlphabet') ->
fun single_value_collapse/1.
single_value_collapse(V) ->
{'SingleValue',ordsets:from_list(single_value_collapse_1(V))}.
single_value_collapse_1([{range,Lb,Ub}|T]) when is_integer(Lb),
is_integer(Ub) ->
lists:seq(Lb, Ub) ++ single_value_collapse_1(T);
single_value_collapse_1([]) ->
[].
smart_collapse([{a_range,Ub}]) ->
{'ValueRange',{'MIN',Ub}};
smart_collapse([{a_range,_}|T]) ->
{range,_,Ub} = lists:last(T),
{'ValueRange',{'MIN',Ub}};
smart_collapse([{range,Lb,Ub}]) ->
{'ValueRange',{Lb,Ub}};
smart_collapse([_|_]=L) ->
V = lists:foldr(fun({range,Lb,Ub}, A) ->
seq(Lb, Ub) ++ A
end, [], L),
{'SingleValue',V}.
size_constraint_collapse([{range,0,'MAX'}]) ->
none;
size_constraint_collapse(Root) ->
[{range,Lb,_}|_] = Root,
{range,_,Ub} = lists:last(Root),
{Lb,Ub}.
seq(Same, Same) ->
[Same];
seq(Lb, Ub) when is_integer(Lb), is_integer(Ub) ->
lists:seq(Lb, Ub).
%%%-----------------------------------------
%% If the constraint value is a defined value the valuename
%% is replaced by the actual value
%%
resolve_value(S, HostType, Val) ->
Id = match_parameter(S, Val),
resolve_value1(S, HostType, Id).
resolve_value1(S, HostType, #'Externalvaluereference'{value=Name}=ERef) ->
case resolve_namednumber(S, HostType, Name) of
V when is_integer(V) ->
V;
not_named ->
resolve_value1(S, HostType, get_referenced_value(S, ERef))
end;
resolve_value1(S, HostType, {gt,V}) ->
case resolve_value1(S, HostType, V) of
Int when is_integer(Int) ->
Int + 1;
_Other ->
asn1_error(S, illegal_integer_value)
end;
resolve_value1(S, HostType, {lt,V}) ->
case resolve_value1(S, HostType, V) of
Int when is_integer(Int) ->
Int - 1;
_Other ->
asn1_error(S, illegal_integer_value)
end;
resolve_value1(S, _HostType, {'ValueFromObject',{object,Object},FieldName}) ->
get_value_from_object(S, Object, FieldName);
resolve_value1(_, _, #valuedef{checked=true,value=V}) ->
V;
resolve_value1(S, _, #valuedef{value={'ValueFromObject',
{object,Object},FieldName}}) ->
get_value_from_object(S, Object, FieldName);
resolve_value1(S, _HostType, #valuedef{}=VDef) ->
#valuedef{value=Val} = check_value(S,VDef),
Val;
resolve_value1(_, _, V) ->
V.
resolve_namednumber(S, #type{def=Def}, Name) ->
case Def of
{'ENUMERATED',NameList} ->
resolve_namednumber_1(S, Name, NameList);
{'INTEGER',NameList} ->
resolve_namednumber_1(S, Name, NameList);
_ ->
not_named
end.
resolve_namednumber_1(S, Name, NameList) ->
try
NamedNumberList = check_enumerated(S, NameList),
{_,N} = lookup_enum_value(S, Name, NamedNumberList),
N
catch _:_ ->
not_named
end.
%%%
%%% End of constraint handling.
%%%
check_imported(S,Imodule,Name) ->
check_imported(S,Imodule,Name,false).
check_imported(S,Imodule,Name,IsParsed) ->
case asn1_db:dbget(Imodule,'MODULE') of
undefined when IsParsed == true ->
ErrStr = io_lib:format("Type ~s imported from non existing module ~s~n",[Name,Imodule]),
error({imported,ErrStr,S});
undefined ->
parse_and_save(S,Imodule),
check_imported(S,Imodule,Name,true);
Im when is_record(Im,module) ->
case is_exported(Im,Name) of
false ->
ErrStr = io_lib:format("Imported type ~s not exported from module ~s~n",[Name,Imodule]),
error({imported,ErrStr,S});
_ ->
ok
end
end,
ok.
is_exported(Module,Name) when is_record(Module,module) ->
{exports,Exports} = Module#module.exports,
case Exports of
all ->
true;
[] ->
false;
L when is_list(L) ->
case lists:keysearch(Name,#'Externaltypereference'.type,Exports) of
false -> false;
_ -> true
end
end.
check_externaltypereference(S,Etref=#'Externaltypereference'{module=Emod})->
Currmod = S#state.mname,
MergedMods = S#state.inputmodules,
case Emod of
Currmod ->
%% reference to current module or to imported reference
check_reference(S,Etref);
_ ->
%% io:format("Type ~s IMPORTED FROM ~s~n",[Etype,Emod]),
case lists:member(Emod,MergedMods) of
true ->
check_reference(S,Etref);
false ->
{NewMod,_} = get_referenced_type(S,Etref),
Etref#'Externaltypereference'{module=NewMod}
end
end.
check_reference(S,#'Externaltypereference'{pos=Pos,module=Emod,type=Name}) ->
ModName = S#state.mname,
case asn1_db:dbget(ModName,Name) of
undefined ->
case imported(S,Name) of
{ok,Imodule} ->
check_imported(S,Imodule,Name),
#'Externaltypereference'{module=Imodule,type=Name};
_ ->
%% may be a renamed type in multi file compiling!
{M,T}=get_renamed_reference(S,Name,Emod),
NewName = asn1ct:get_name_of_def(T),
NewPos = asn1ct:get_pos_of_def(T),
#'Externaltypereference'{pos=NewPos,
module=M,
type=NewName}
end;
_ ->
%% cannot do check_type here due to recursive definitions, like
%% S ::= SEQUENCE {a INTEGER, b S}. This implies that references
%% that appear before the definition will be an
%% Externaltypereference in the abstract syntax tree
#'Externaltypereference'{pos=Pos,module=ModName,type=Name}
end.
get_referenced_value(S, T) ->
case get_referenced_type(S, T) of
{ExtMod,#valuedef{value=#'Externalvaluereference'{}=Ref}} ->
get_referenced_value(update_state(S, ExtMod), Ref);
{_,#valuedef{value=Val}} ->
Val
end.
get_referenced_type(S, T) ->
get_referenced_type(S, T, false).
get_referenced_type(S, T, Recurse) ->
case do_get_referenced_type(S, T) of
{_,#typedef{typespec=#type{def=#'Externaltypereference'{}=ERef}}}
when Recurse ->
get_referenced_type(S, ERef, Recurse);
{_,_}=Res ->
Res
end.
do_get_referenced_type(S, T0) ->
case match_parameter(S, T0) of
T0 ->
do_get_ref_type_1(S, T0);
T ->
do_get_referenced_type(S, T)
end.
do_get_ref_type_1(S, #'Externaltypereference'{pos=P,
module=M,
type=T}) ->
do_get_ref_type_2(S, P, M, T);
do_get_ref_type_1(S, #'Externalvaluereference'{pos=P,
module=M,
value=V}) ->
do_get_ref_type_2(S, P, M, V);
do_get_ref_type_1(_, T) ->
{undefined,T}.
do_get_ref_type_2(#state{mname=Current,inputmodules=Modules}=S,
Pos, M, T) ->
case M =:= Current orelse lists:member(M, Modules) of
true ->
get_referenced1(S, M, T, Pos);
false ->
get_referenced(S, M, T, Pos)
end.
%% get_referenced/3
%% The referenced entity Ename may in case of an imported parameterized
%% type reference imported entities in the other module, which implies that
%% asn1_db:dbget will fail even though the referenced entity exists. Thus
%% Emod may be the module that imports the entity Ename and not holds the
%% data about Ename.
get_referenced(S,Emod,Ename,Pos) ->
?dbg("get_referenced: ~p~n",[Ename]),
parse_and_save(S,Emod),
?dbg("get_referenced,parse_and_save ~n",[]),
case asn1_db:dbget(Emod,Ename) of
undefined ->
%% May be an imported entity in module Emod or Emod may not exist
case asn1_db:dbget(Emod,'MODULE') of
undefined ->
asn1_error(S, {undefined_import, Ename, Emod});
_ ->
NewS = update_state(S,Emod),
get_imported(NewS,Ename,Emod,Pos)
end;
T when is_record(T,typedef) ->
?dbg("get_referenced T: ~p~n",[T]),
{Emod,T}; % should add check that T is exported here
V ->
?dbg("get_referenced V: ~p~n",[V]),
{Emod,V}
end.
get_referenced1(S,ModuleName,Name,Pos) ->
case asn1_db:dbget(S#state.mname,Name) of
undefined ->
%% ModuleName may be other than S#state.mname when
%% multi file compiling is used.
get_imported(S,Name,ModuleName,Pos);
T ->
{S#state.mname,T}
end.
get_imported(S,Name,Module,Pos) ->
?dbg("get_imported, Module: ~p, Name: ~p~n",[Module,Name]),
case imported(S,Name) of
{ok,Imodule} ->
parse_and_save(S,Imodule),
case asn1_db:dbget(Imodule,'MODULE') of
undefined ->
asn1_error(S, {undefined_import, Name, Module});
Im when is_record(Im,module) ->
case is_exported(Im,Name) of
false ->
asn1_error(S, {undefined_export, Name});
_ ->
?dbg("get_imported, is_exported ~p, ~p~n",[Imodule,Name]),
get_referenced_type(S,
#'Externaltypereference'
{module=Imodule,
type=Name,pos=Pos})
end
end;
_ ->
get_renamed_reference(S,Name,Module)
end.
save_object_set_instance(S,Name,ObjSetSpec)
when is_record(ObjSetSpec,'ObjectSet') ->
NewObjSet = #typedef{checked=true,name=Name,typespec=ObjSetSpec},
asn1_db:dbput(S#state.mname,Name,NewObjSet),
case ObjSetSpec of
#'ObjectSet'{uniquefname={unique,undefined}} ->
ok;
_ ->
%% Should be generated iff
%% ObjSpec#'ObjectSet'.uniquefname /= {unique,undefined}
ObjSetKey = {Name,objectset,NewObjSet},
%% asn1ct_gen:insert_once(parameterized_objects,ObjSetKey)
insert_once(S,parameterized_objects,ObjSetKey)
end,
#'Externaltypereference'{module=S#state.mname,type=Name}.
%% load_asn1_module do not check that the module is saved.
%% If get_referenced_type is called before the module must
%% be saved.
load_asn1_module(#state{mname=M,module=Mod},M)->
Mod;
load_asn1_module(_,M) ->
asn1_db:dbget(M,'MODULE').
parse_and_save(S,Module) when is_record(S,state) ->
Erule = S#state.erule,
case asn1db_member(S,Erule,Module) of
true ->
ok;
_ ->
case asn1ct:parse_and_save(Module,S) of
ok ->
save_asn1db_uptodate(S,Erule,Module);
Err ->
Err
end
end.
asn1db_member(S,Erule,Module) ->
Asn1dbUTL = get_asn1db_uptodate(S),
lists:member({Erule,Module},Asn1dbUTL).
save_asn1db_uptodate(S,Erule,Module) ->
Asn1dbUTL = get_asn1db_uptodate(S),
Asn1dbUTL2 = lists:keydelete(Module,2,Asn1dbUTL),
put_asn1db_uptodate([{Erule,Module}|Asn1dbUTL2]).
get_asn1db_uptodate(S) ->
case get(asn1db_uptodate) of
undefined -> [{S#state.erule,S#state.mname}]; %initialize
L -> L
end.
put_asn1db_uptodate(L) ->
put(asn1db_uptodate,L).
update_state(S,undefined) ->
S;
update_state(S=#state{mname=ModuleName},ModuleName) ->
S;
update_state(S,ModuleName) ->
case lists:member(ModuleName,S#state.inputmodules) of
true ->
S;
_ ->
parse_and_save(S,ModuleName),
Mod = #module{} = asn1_db:dbget(ModuleName,'MODULE'),
S#state{mname=ModuleName,module=Mod}
end.
get_renamed_reference(S,Name,Module) ->
case renamed_reference(S,Name,Module) of
undefined ->
asn1_error(S, {undefined, Name});
NewTypeName when NewTypeName =/= Name ->
get_referenced1(S,Module,NewTypeName,undefined)
end.
renamed_reference(S,#'Externaltypereference'{type=Name,module=Module}) ->
case renamed_reference(S,Name,Module) of
undefined ->
Name;
Other ->
Other
end.
renamed_reference(S,Name,Module) ->
%% first check if there is a renamed type in this module
%% second check if any type was imported with this name
case asn1ct_table:exists(renamed_defs) of
false -> undefined;
true ->
case asn1ct_table:match(renamed_defs, {'$1',Name,Module}) of
[] ->
case asn1ct_table:exists(original_imports) of
false ->
undefined;
true ->
case asn1ct_table:match(original_imports, {Module,'$1'}) of
[] ->
undefined;
[[ImportsList]] ->
case get_importmoduleoftype(ImportsList,Name) of
undefined ->
undefined;
NextMod ->
renamed_reference(S,Name,NextMod)
end
end
end;
[[NewTypeName]] ->
NewTypeName
end
end.
get_importmoduleoftype([I|Is],Name) ->
Index = #'Externaltypereference'.type,
case lists:keysearch(Name,Index,I#'SymbolsFromModule'.symbols) of
{value,_Ref} ->
(I#'SymbolsFromModule'.module)#'Externaltypereference'.type;
_ ->
get_importmoduleoftype(Is,Name)
end;
get_importmoduleoftype([],_) ->
undefined.
match_parameters(S, Names) ->
[match_parameter(S, Name) || Name <- Names].
match_parameter(#state{parameters=Ps}=S, Name) ->
match_parameter(S, Name, Ps).
match_parameter(_S, Name, []) ->
Name;
match_parameter(S, {valueset,{element_set,#type{}=Ts,none}}, Ps) ->
match_parameter(S, {valueset,Ts}, Ps);
match_parameter(_S, #'Externaltypereference'{type=Name},
[{#'Externaltypereference'{type=Name},NewName}|_T]) ->
NewName;
match_parameter(_S, #'Externaltypereference'{type=Name},
[{{_,#'Externaltypereference'{type=Name}},NewName}|_T]) ->
NewName;
match_parameter(_S, #'Externalvaluereference'{value=Name},
[{#'Externalvaluereference'{value=Name},NewName}|_T]) ->
NewName;
match_parameter(_S, #'Externalvaluereference'{value=Name},
[{{_,#'Externalvaluereference'{value=Name}},NewName}|_T]) ->
NewName;
match_parameter(_S, #type{def=#'Externaltypereference'{module=M,type=Name}},
[{#'Externaltypereference'{module=M,type=Name},Type}]) ->
Type;
match_parameter(_S, {valueset,#type{def=#'Externaltypereference'{type=Name}}},
[{{_,#'Externaltypereference'{type=Name}},
{valueset,#type{def=NewName}}}|_T]) ->
NewName;
match_parameter(_S, {valueset,#type{def=#'Externaltypereference'{type=Name}}},
[{{_,#'Externaltypereference'{type=Name}},
NewName=#type{def=#'Externaltypereference'{}}}|_T]) ->
NewName#type.def;
match_parameter(_S, {valueset,#type{def=#'Externaltypereference'{type=Name}}},
[{{_,#'Externaltypereference'{type=Name}},NewName}|_T]) ->
NewName;
%% When a parameter is a parameterized element it has to be
%% instantiated now!
match_parameter(S, {valueset,T=#type{def={pt,_,_Args}}}, _Ps) ->
try check_type(S,#typedef{name=S#state.tname,typespec=T},T) of
#type{def=Ts} ->
Ts
catch pobjectsetdef ->
{_,ObjRef,_Params} = T#type.def,
{_,ObjDef}=get_referenced_type(S,ObjRef),
%%ObjDef is a pvaluesetdef where the type field holds the class
ClassRef =
case ObjDef of
#pvaluesetdef{type=TDef} ->
TDef#type.def;
#pobjectsetdef{class=ClRef} -> ClRef
end,
%% The reference may not have the home module of the class
{HomeMod,_} = get_referenced_type(S,ClassRef),
RightClassRef =
ClassRef#'Externaltypereference'{module=HomeMod},
ObjectSet = #'ObjectSet'{class=RightClassRef,set=T},
ObjSpec = check_object(S,#typedef{typespec=ObjectSet},ObjectSet),
Name = list_to_atom(asn1ct_gen:list2name([get_datastr_name(ObjDef)|S#state.recordtopname])),
save_object_set_instance(S,Name,ObjSpec)
end;
%% same as previous, only depends on order of parsing
match_parameter(S, {valueset,{pos,{objectset,_,POSref},Args}}, Ps) ->
match_parameter(S, {valueset,#type{def={pt,POSref,Args}}}, Ps);
match_parameter(S, Name, [_H|T]) ->
%%io:format("match_parameter(~p,~p)~n",[Name,[H|T]]),
match_parameter(S, Name, T).
imported(S,Name) ->
{imports,Ilist} = (S#state.module)#module.imports,
imported1(Name,Ilist).
imported1(Name,
[#'SymbolsFromModule'{symbols=Symlist,
module=#'Externaltypereference'{type=ModuleName}}|T]) ->
case lists:keysearch(Name,#'Externaltypereference'.type,Symlist) of
{value,_V} ->
{ok,ModuleName};
_ ->
imported1(Name,T)
end;
imported1(_Name,[]) ->
false.
%% Check the named number list for an INTEGER or a BIT STRING.
check_named_number_list(_S, []) ->
[];
check_named_number_list(_S, [{_,_}|_]=NNL) ->
%% The named number list has already been checked.
NNL;
check_named_number_list(S, NNL0) ->
%% Check that the names are unique.
case check_unique(NNL0, 2) of
[] ->
NNL1 = [{Id,resolve_valueref(S, Val)} || {'NamedNumber',Id,Val} <- NNL0],
NNL = lists:keysort(2, NNL1),
case check_unique(NNL, 2) of
[] ->
NNL;
[Val|_] ->
asn1_error(S, {value_reused,Val})
end;
[H|_] ->
asn1_error(S, {namelist_redefinition,H})
end.
resolve_valueref(S, #'Externalvaluereference'{} = T) ->
get_referenced_value(S, T);
resolve_valueref(_, Val) when is_integer(Val) ->
Val.
check_integer(S, NNL) ->
check_named_number_list(S, NNL).
check_bitstring(S, NNL0) ->
NNL = check_named_number_list(S, NNL0),
_ = [asn1_error(S, {invalid_bit_number,Bit}) ||
{_,Bit} <- NNL, Bit < 0],
NNL.
check_real(_S,_Constr) ->
ok.
%% Check INSTANCE OF
%% check that DefinedObjectClass is of TYPE-IDENTIFIER class
%% If Constraint is empty make it the general INSTANCE OF type
%% If Constraint is not empty make an inlined type
%% convert INSTANCE OF to the associated type
check_instance_of(S,DefinedObjectClass,Constraint) ->
check_type_identifier(S,DefinedObjectClass),
iof_associated_type(S,Constraint).
check_type_identifier(S, Eref=#'Externaltypereference'{type=Class}) ->
case get_referenced_type(S, Eref) of
{_,#classdef{name='TYPE-IDENTIFIER'}} ->
ok;
{_,#classdef{typespec=#'Externaltypereference'{}=NextEref}} ->
check_type_identifier(S, NextEref);
{_,TD=#typedef{typespec=#type{def=#'Externaltypereference'{}}}} ->
check_type_identifier(S, (TD#typedef.typespec)#type.def);
_ ->
asn1_error(S, {illegal_instance_of,Class})
end.
iof_associated_type(S,[]) ->
%% in this case encode/decode functions for INSTANCE OF must be
%% generated
case get(instance_of) of
undefined ->
AssociateSeq = iof_associated_type1(S,[]),
Tag = [?TAG_CONSTRUCTED(?N_INSTANCE_OF)],
TypeDef=#typedef{checked=true,
name='INSTANCE OF',
typespec=#type{tag=Tag,
def=AssociateSeq}},
asn1_db:dbput(S#state.mname,'INSTANCE OF',TypeDef),
instance_of_decl(S#state.mname);
_ ->
instance_of_decl(S#state.mname),
ok
end,
#'Externaltypereference'{module=S#state.mname,type='INSTANCE OF'};
iof_associated_type(S,C) ->
iof_associated_type1(S,C).
iof_associated_type1(S,C) ->
{TableCInf,Comp1Cnstr,Comp2Cnstr,Comp2tablecinf}=
instance_of_constraints(S,C),
ModuleName = S#state.mname,
Typefield_type=
case C of
[] -> 'ASN1_OPEN_TYPE';
_ -> {typefield,'Type'}
end,
ObjIdTag = [{'UNIVERSAL',8}],
C1TypeTag = [#tag{class='UNIVERSAL',
number=6,
type='IMPLICIT',
form=0}],
TypeIdentifierRef=#'Externaltypereference'{module=ModuleName,
type='TYPE-IDENTIFIER'},
ObjectIdentifier =
#'ObjectClassFieldType'{classname=TypeIdentifierRef,
class=[],
fieldname={id,[]},
type={fixedtypevaluefield,id,
#type{def='OBJECT IDENTIFIER'}}},
Typefield =
#'ObjectClassFieldType'{classname=TypeIdentifierRef,
class=[],
fieldname={'Type',[]},
type=Typefield_type},
IOFComponents0 =
[#'ComponentType'{name='type-id',
typespec=#type{tag=C1TypeTag,
def=ObjectIdentifier,
constraint=Comp1Cnstr},
prop=mandatory,
tags=ObjIdTag},
#'ComponentType'{name=value,
typespec=#type{tag=[#tag{class='CONTEXT',
number=0,
type='EXPLICIT',
form=32}],
def=Typefield,
constraint=Comp2Cnstr,
tablecinf=Comp2tablecinf},
prop=mandatory,
tags=[{'CONTEXT',0}]}],
IOFComponents = textual_order(IOFComponents0),
#'SEQUENCE'{tablecinf=TableCInf,
components=simplify_comps(IOFComponents)}.
%% returns the leading attribute, the constraint of the components and
%% the tablecinf value for the second component.
instance_of_constraints(_, []) ->
{false,[],[],[]};
instance_of_constraints(S, [{element_set,{simpletable,C},none}]) ->
{element_set,Type,none} = C,
instance_of_constraints_1(S, Type).
instance_of_constraints_1(S, Type) ->
#type{def=#'Externaltypereference'{type=Name}} = Type,
ModuleName = S#state.mname,
ObjectSetRef=#'Externaltypereference'{module=ModuleName,
type=Name},
CRel=[{componentrelation,{objectset,
undefined, %% pos
ObjectSetRef},
[{innermost,
[#'Externalvaluereference'{module=ModuleName,
value=type}]}]}],
Mod = S#state.mname,
TableCInf=#simpletableattributes{objectsetname={Mod,Name},
c_name='type-id',
c_index=1,
usedclassfield=id,
uniqueclassfield=id,
valueindex=[]},
{TableCInf,[{simpletable,Name}],CRel,[{objfun,ObjectSetRef}]}.
%%%
%%% Check ENUMERATED.
%%%
check_enumerated(_S, [{Name,Number}|_]=NNL)
when is_atom(Name), is_integer(Number) ->
%% Already checked.
NNL;
check_enumerated(_S, {[{Name,Number}|_],L}=NNL)
when is_atom(Name), is_integer(Number), is_list(L) ->
%% Already checked (with extension).
NNL;
check_enumerated(S, NNL) ->
check_enum_ids(S, NNL, gb_sets:empty()),
check_enum(S, NNL, gb_sets:empty(), []).
check_enum_ids(S, [{'NamedNumber',Id,_}|T], Ids0) ->
Ids = check_enum_update_ids(S, Id, Ids0),
check_enum_ids(S, T, Ids);
check_enum_ids(S, ['EXTENSIONMARK'|T], Ids) ->
check_enum_ids(S, T, Ids);
check_enum_ids(S, [Id|T], Ids0) when is_atom(Id) ->
Ids = check_enum_update_ids(S, Id, Ids0),
check_enum_ids(S, T, Ids);
check_enum_ids(_, [], _) ->
ok.
check_enum(S, [{'NamedNumber',Id,N}|T], Used0, Acc) ->
Used = check_enum_update_used(S, Id, N, Used0),
check_enum(S, T, Used, [{Id,N}|Acc]);
check_enum(S, ['EXTENSIONMARK'|Ext0], Used0, Acc0) ->
Acc = lists:reverse(Acc0),
{Root,Used,Cnt} = check_enum_number_root(Acc, Used0, 0, []),
Ext = check_enum_ext(S, Ext0, Used, Cnt, []),
{Root,Ext};
check_enum(S, [Id|T], Used, Acc) when is_atom(Id) ->
check_enum(S, T, Used, [Id|Acc]);
check_enum(_, [], Used, Acc0) ->
Acc = lists:reverse(Acc0),
{Root,_,_} = check_enum_number_root(Acc, Used, 0, []),
lists:keysort(2, Root).
check_enum_number_root([Id|T]=T0, Used0, Cnt, Acc) when is_atom(Id) ->
case gb_sets:is_element(Cnt, Used0) of
false ->
Used = gb_sets:insert(Cnt, Used0),
check_enum_number_root(T, Used, Cnt+1, [{Id,Cnt}|Acc]);
true ->
check_enum_number_root(T0, Used0, Cnt+1, Acc)
end;
check_enum_number_root([H|T], Used, Cnt, Acc) ->
check_enum_number_root(T, Used, Cnt, [H|Acc]);
check_enum_number_root([], Used, Cnt, Acc) ->
{lists:keysort(2, Acc),Used,Cnt}.
check_enum_ext(S, [{'NamedNumber',Id,N}|T], Used0, C, Acc) ->
Used = check_enum_update_used(S, Id, N, Used0),
if
N < C ->
asn1_error(S, {enum_not_ascending,Id,N,C-1});
true ->
ok
end,
check_enum_ext(S, T, Used, N+1, [{Id,N}|Acc]);
check_enum_ext(S, [Id|T]=T0, Used0, C, Acc) when is_atom(Id) ->
case gb_sets:is_element(C, Used0) of
true ->
check_enum_ext(S, T0, Used0, C+1, Acc);
false ->
Used = gb_sets:insert(C, Used0),
check_enum_ext(S, T, Used, C+1, [{Id,C}|Acc])
end;
check_enum_ext(_, [], _, _, Acc) ->
lists:keysort(2, Acc).
check_enum_update_ids(S, Id, Ids) ->
case gb_sets:is_element(Id, Ids) of
false ->
gb_sets:insert(Id, Ids);
true ->
asn1_error(S, {enum_illegal_redefinition,Id})
end.
check_enum_update_used(S, Id, N, Used) ->
case gb_sets:is_element(N, Used) of
false ->
gb_sets:insert(N, Used);
true ->
asn1_error(S, {enum_reused_value,Id,N})
end.
%%%
%%% End of ENUMERATED checking.
%%%
check_boolean(_S,_Constr) ->
ok.
check_octetstring(_S,_Constr) ->
ok.
%% check all aspects of a SEQUENCE
%% - that all component names are unique
%% - that all TAGS are ok (when TAG default is applied)
%% - that each component is of a valid type
%% - that the extension marks are valid
check_sequence(S,Type,Comps) ->
Components = expand_components(S,Comps),
case check_unique([C||C <- Components ,is_record(C,'ComponentType')]
,#'ComponentType'.name) of
[] ->
%% sort_canonical(Components),
Components2 = maybe_automatic_tags(S,Components),
%% check the table constraints from here. The outermost type
%% is Type, the innermost is Comps (the list of components)
NewComps = check_each_component2(S,Type,Components2),
check_unique_sequence_tags(S,NewComps),
%% CRelInf is the "leading attribute" information
%% necessary for code generating of the look up in the
%% object set table,
%% i.e. getenc_ObjectSet/getdec_ObjectSet.
%% {objfun,ERef} tuple added in NewComps2 in tablecinf
%% field in type record of component relation constrained
%% type
{CRelInf,NewComps2} = componentrelation_leadingattr(S,NewComps),
%% CompListWithTblInf has got a lot unecessary info about
%% the involved class removed, as the class of the object
%% set.
CompListWithTblInf = get_tableconstraint_info(S,Type,NewComps2),
NewComps3 = textual_order(CompListWithTblInf),
NewComps4 = simplify_comps(NewComps3),
CompListTuple = complist_as_tuple(NewComps4),
{CRelInf,CompListTuple};
Dupl ->
asn1_error(S, {duplicate_identifier, error_value(hd(Dupl))})
end.
complist_as_tuple(CompList) ->
complist_as_tuple(CompList, [], [], [], root).
complist_as_tuple([#'EXTENSIONMARK'{}|T], Acc, Ext, Acc2, root) ->
complist_as_tuple(T, Acc, Ext, Acc2, ext);
complist_as_tuple([#'EXTENSIONMARK'{}|T], Acc, Ext, Acc2, ext) ->
complist_as_tuple(T, Acc, Ext, Acc2, root2);
complist_as_tuple([C|T], Acc, Ext, Acc2, root) ->
complist_as_tuple(T, [C|Acc], Ext, Acc2, root);
complist_as_tuple([C|T], Acc, Ext, Acc2, ext) ->
complist_as_tuple(T, Acc, [C|Ext], Acc2, ext);
complist_as_tuple([C|T], Acc, Ext, Acc2, root2) ->
complist_as_tuple(T, Acc, Ext, [C|Acc2], root2);
complist_as_tuple([], Acc, _Ext, _Acc2, root) ->
lists:reverse(Acc);
complist_as_tuple([], Acc, Ext, _Acc2, ext) ->
{lists:reverse(Acc),lists:reverse(Ext)};
complist_as_tuple([], Acc, Ext, Acc2, root2) ->
{lists:reverse(Acc),lists:reverse(Ext),lists:reverse(Acc2)}.
expand_components(S, [{'COMPONENTS OF',Type}|T]) ->
CompList = expand_components2(S,get_referenced_type(S,Type#type.def)),
expand_components(S,CompList) ++ expand_components(S,T);
expand_components(S,[H|T]) ->
[H|expand_components(S,T)];
expand_components(_,[]) ->
[].
expand_components2(_S,{_,#typedef{typespec=#type{def=Seq}}})
when is_record(Seq,'SEQUENCE') ->
case Seq#'SEQUENCE'.components of
{R1,_Ext,R2} -> R1 ++ R2;
{Root,_Ext} -> Root;
Root -> take_only_rootset(Root)
end;
expand_components2(_S,{_,#typedef{typespec=#type{def=Set}}})
when is_record(Set,'SET') ->
case Set#'SET'.components of
{R1,_Ext,R2} -> R1 ++ R2;
{Root,_Ext} -> Root;
Root -> take_only_rootset(Root)
end;
expand_components2(_S,{_,#typedef{typespec=RefType=#type{def=#'Externaltypereference'{}}}}) ->
[{'COMPONENTS OF',RefType}];
expand_components2(S,{_,PT={pt,_,_}}) ->
PTType = check_type(S,PT,#type{def=PT}),
expand_components2(S,{dummy,#typedef{typespec=PTType}});
expand_components2(S,{_,OCFT = #'ObjectClassFieldType'{}}) ->
UncheckedType = #type{def=OCFT},
Type = check_type(S,#typedef{typespec=UncheckedType},UncheckedType),
expand_components2(S, {undefined,ocft_def(Type)});
expand_components2(S,{_,ERef}) when is_record(ERef,'Externaltypereference') ->
expand_components2(S,get_referenced_type(S,ERef));
expand_components2(S,{_, What}) ->
asn1_error(S, {illegal_COMPONENTS_OF, error_value(What)}).
take_only_rootset([])->
[];
take_only_rootset([#'EXTENSIONMARK'{}|_T])->
[];
take_only_rootset([H|T]) ->
[H|take_only_rootset(T)].
check_unique_sequence_tags(S,CompList) ->
TagComps = case complist_as_tuple(CompList) of
{R1,Ext,R2} ->
R1 ++ [C#'ComponentType'{prop='OPTIONAL'}||
C = #'ComponentType'{} <- Ext]++R2;
{R1,Ext} ->
R1 ++ [C#'ComponentType'{prop='OPTIONAL'}||
C = #'ComponentType'{} <- Ext];
_ ->
CompList
end,
check_unique_sequence_tags0(S,TagComps).
check_unique_sequence_tags0(S,[#'ComponentType'{prop=mandatory}|Rest]) ->
check_unique_sequence_tags0(S,Rest);
check_unique_sequence_tags0(S,[C=#'ComponentType'{}|Rest]) ->
check_unique_sequence_tags1(S,Rest,[C]);% optional or default
check_unique_sequence_tags0(S,[_ExtensionMarker|Rest]) ->
check_unique_sequence_tags0(S,Rest);
check_unique_sequence_tags0(_S,[]) ->
true.
check_unique_sequence_tags1(S,[C|Rest],Acc) when is_record(C,'ComponentType') ->
case C#'ComponentType'.prop of
mandatory ->
check_unique_tags(S,lists:reverse([C|Acc])),
check_unique_sequence_tags(S,Rest);
_ ->
check_unique_sequence_tags1(S,Rest,[C|Acc]) % default or optional
end;
check_unique_sequence_tags1(S,[H|Rest],Acc) ->
check_unique_sequence_tags1(S,Rest,[H|Acc]);
check_unique_sequence_tags1(S,[],Acc) ->
check_unique_tags(S,lists:reverse(Acc)).
check_sequenceof(S,Type,Component) when is_record(Component,type) ->
simplify_type(check_type(S, Type, Component)).
check_set(S,Type,Components) ->
{TableCInf,NewComponents} = check_sequence(S,Type,Components),
check_unique_tags(S, collect_components(NewComponents), []),
case {lists:member(der,S#state.options),S#state.erule} of
{true,_} ->
{Sorted,SortedComponents} = sort_components(der,S,NewComponents),
{Sorted,TableCInf,SortedComponents};
{_,PER} when PER =:= per; PER =:= uper ->
{Sorted,SortedComponents} = sort_components(per,S,NewComponents),
{Sorted,TableCInf,SortedComponents};
_ ->
{false,TableCInf,NewComponents}
end.
collect_components({C1,C2,C3}) ->
collect_components(C1++C2++C3);
collect_components({C1,C2}) ->
collect_components(C1++C2);
collect_components(Cs) ->
%% Assert that tags are not empty
[] = [EmptyTag || EmptyTag = #'ComponentType'{tags=[]} <- Cs],
Cs.
%% sorting in canonical order according to X.680 8.6, X.691 9.2
%% DER: all components shall be sorted in canonical order.
%% PER: only root components shall be sorted in canonical order. The
%% extension components shall remain in textual order.
%%
sort_components(der, S, Components) ->
{R1,Ext,R2} = extension(textual_order(Components)),
CompsList = case Ext of
noext -> R1;
_ -> R1 ++ Ext ++ R2
end,
case {untagged_choice(S,CompsList),Ext} of
{false,noext} ->
{true,sort_components1(CompsList)};
{false,_} ->
{true,{sort_components1(CompsList),[]}};
{true,noext} ->
%% sort in run-time
{dynamic,R1};
_ ->
{dynamic,{R1, Ext, R2}}
end;
sort_components(per, S, Components) ->
{R1,Ext,R2} = extension(textual_order(Components)),
Root = tag_untagged_choice(S,R1++R2),
case Ext of
noext ->
{true,sort_components1(Root)};
_ ->
{true,{sort_components1(Root),Ext}}
end.
sort_components1(Cs0) ->
Cs1 = [{tag_key(Tag),C} || #'ComponentType'{tags=[Tag|_]}=C <- Cs0],
Cs = lists:sort(Cs1),
[C || {_,C} <- Cs].
tag_key({'UNIVERSAL',Tag}) -> {0,Tag};
tag_key({'APPLICATION',Tag}) -> {1,Tag};
tag_key({'CONTEXT',Tag}) -> {2,Tag};
tag_key({'PRIVATE',Tag}) -> {3,Tag}.
untagged_choice(_S,[#'ComponentType'{typespec=#type{tag=[],def={'CHOICE',_}}}|_Rest]) ->
true;
untagged_choice(S,[#'ComponentType'{typespec=#type{tag=[],def=ExRef}}|Rest])
when is_record(ExRef,'Externaltypereference')->
case get_referenced_type(S,ExRef) of
{_,#typedef{typespec=#type{tag=[],
def={'CHOICE',_}}}} -> true;
_ -> untagged_choice(S,Rest)
end;
untagged_choice(S,[_|Rest]) ->
untagged_choice(S,Rest);
untagged_choice(_,[]) ->
false.
tag_untagged_choice(S,Cs) ->
tag_untagged_choice(S,Cs,[]).
tag_untagged_choice(S,[C = #'ComponentType'{typespec=#type{tag=[],def={'CHOICE',_}}}|Rest],Acc) ->
TagList = C#'ComponentType'.tags,
TaggedC = C#'ComponentType'{tags=get_least_tag(TagList)},
tag_untagged_choice(S,Rest,[TaggedC|Acc]);
tag_untagged_choice(S,[C = #'ComponentType'{typespec=#type{tag=[],def=ExRef}}|Rest],Acc) when is_record(ExRef,'Externaltypereference') ->
case get_referenced_type(S,ExRef) of
{_,#typedef{typespec=#type{tag=[],
def={'CHOICE',_}}}} ->
TagList = C#'ComponentType'.tags,
TaggedC = C#'ComponentType'{tags = get_least_tag(TagList)},
tag_untagged_choice(S,Rest,[TaggedC|Acc]);
_ ->
tag_untagged_choice(S,Rest,[C|Acc])
end;
tag_untagged_choice(S,[C|Rest],Acc) ->
tag_untagged_choice(S,Rest,[C|Acc]);
tag_untagged_choice(_S,[],Acc) ->
Acc.
get_least_tag([]) ->
[];
get_least_tag(TagList) ->
%% The smallest tag 'PRIVATE' < 'CONTEXT' < 'APPLICATION' < 'UNIVERSAL'
Pred = fun({'PRIVATE',_},{'CONTEXT',_}) -> true;
({'CONTEXT',_},{'APPLICATION',_}) -> true;
({'APPLICATION',_},{'UNIVERSAL',_}) -> true;
({A,T1},{A,T2}) when T1 =< T2 -> true; (_,_) -> false
end,
[T|_] = lists:sort(Pred,TagList),
[T].
%% adds the textual order to the components to keep right order of
%% components in the asn1-value.
textual_order(Cs) ->
Fun = fun(C=#'ComponentType'{},Index) ->
{C#'ComponentType'{textual_order=Index},Index+1};
(Other,Index) ->
{Other,Index}
end,
{NewCs,_} = textual_order(Cs,Fun,1),
NewCs.
textual_order(Cs,Fun,IxIn) when is_list(Cs) ->
lists:mapfoldl(Fun,IxIn,Cs);
textual_order({Root,Ext},Fun,IxIn) ->
{NewRoot,IxR} = textual_order(Root,Fun,IxIn),
{NewExt,_} = textual_order(Ext,Fun,IxR),
{{NewRoot,NewExt},dummy};
textual_order({Root1,Ext,Root2},Fun,IxIn) ->
{NewRoot1,IxR} = textual_order(Root1,Fun,IxIn),
{NewExt,IxE} = textual_order(Ext,Fun,IxR),
{NewRoot2,_} = textual_order(Root2,Fun,IxE),
{{NewRoot1,NewExt,NewRoot2},dummy}.
extension(Components) when is_list(Components) ->
{Components,noext,[]};
extension({Root,ExtList}) ->
ToOpt = fun(mandatory) ->
'OPTIONAL';
(X) -> X
end,
{Root, [X#'ComponentType'{prop=ToOpt(Y)}||
X = #'ComponentType'{prop=Y}<-ExtList],[]};
extension({Root1,ExtList,Root2}) ->
ToOpt = fun(mandatory) ->
'OPTIONAL';
(X) -> X
end,
{Root1, [X#'ComponentType'{prop=ToOpt(Y)}||
X = #'ComponentType'{prop=Y}<-ExtList], Root2}.
check_setof(S,Type,Component) when is_record(Component,type) ->
simplify_type(check_type(S, Type, Component)).
check_selectiontype(S,Name,#type{def=Eref})
when is_record(Eref,'Externaltypereference') ->
{RefMod,TypeDef} = get_referenced_type(S,Eref),
NewS = S#state{module=load_asn1_module(S,RefMod),
mname=RefMod,
tname=get_datastr_name(TypeDef)},
check_selectiontype2(NewS,Name,TypeDef);
check_selectiontype(S,Name,Type=#type{def={pt,_,_}}) ->
TName = case S#state.recordtopname of
[] -> S#state.tname;
N -> N
end,
TDef = #typedef{name=TName,typespec=Type},
check_selectiontype2(S,Name,TDef);
check_selectiontype(S, _Name, Type) ->
asn1_error(S, {illegal_choice_type, error_value(Type)}).
check_selectiontype2(S,Name,TypeDef) ->
NewS = S#state{recordtopname=get_datastr_name(TypeDef)},
Components =
try
CheckedType = check_type(NewS,TypeDef,TypeDef#typedef.typespec),
get_choice_components(S,CheckedType#type.def)
catch error:_ ->
asn1_error(S, {illegal_choice_type, error_value(TypeDef)})
end,
case lists:keyfind(Name, #'ComponentType'.name, Components) of
#'ComponentType'{typespec=TS} -> TS;
false -> asn1_error(S, {illegal_id, error_value(Name)})
end.
get_choice_components(_S,{'CHOICE',Components}) when is_list(Components)->
Components;
get_choice_components(_S,{'CHOICE',{C1,C2}}) when is_list(C1),is_list(C2) ->
C1++C2;
get_choice_components(S,ERef=#'Externaltypereference'{}) ->
{_RefMod,TypeDef}=get_referenced_type(S,ERef),
#typedef{typespec=TS} = TypeDef,
get_choice_components(S,TS#type.def).
check_restrictedstring(_S,_Def,_Constr) ->
ok.
check_objectidentifier(_S,_Constr) ->
ok.
check_relative_oid(_S,_Constr) ->
ok.
%% check all aspects of a CHOICE
%% - that all alternative names are unique
%% - that all TAGS are ok (when TAG default is applied)
%% - that each alternative is of a valid type
%% - that the extension marks are valid
check_choice(S,Type,Components) when is_list(Components) ->
Components1 = [C||C = #'ComponentType'{} <- Components],
case check_unique(Components1,#'ComponentType'.name) of
[] ->
%% sort_canonical(Components),
Components2 = maybe_automatic_tags(S,Components),
NewComps = check_each_alternative2(S,Type,Components2),
%% ExtensionAdditionGroup markers i.e '[[' ']]' are not
%% significant for encoding/decoding a choice
%% therefore we remove them here
NewComps2 = lists:filter(fun(#'ExtensionAdditionGroup'{}) -> false;
('ExtensionAdditionGroupEnd') -> false;
(_) -> true
end,NewComps),
NewComps3 = simplify_comps(NewComps2),
check_unique_tags(S, NewComps3),
complist_as_tuple(NewComps3);
Dupl ->
asn1_error(S, {duplicate_identifier,error_value(hd(Dupl))})
end;
check_choice(_S,_,[]) ->
[].
maybe_automatic_tags(S,C) ->
TagNos = tag_nums(C),
case (S#state.module)#module.tagdefault of
'AUTOMATIC' ->
generate_automatic_tags(S,C,TagNos);
_ ->
%% maybe is the module a multi file module were only some of
%% the modules have defaulttag AUTOMATIC TAGS then the names
%% of those types are saved in the table automatic_tags
Name= S#state.tname,
case is_automatic_tagged_in_multi_file(Name) of
true ->
generate_automatic_tags(S,C,TagNos);
false ->
C
end
end.
%% Pos == 1 for Root1, 2 for Ext, 3 for Root2
tag_nums(Cl) ->
tag_nums(Cl,0,0).
tag_nums([#'EXTENSIONMARK'{}|Rest],Ext,Root2) ->
tag_nums_ext(Rest,Ext,Root2);
tag_nums([_|Rest],Ext,Root2) ->
tag_nums(Rest,Ext+1,Root2+1);
tag_nums([],Ext,Root2) ->
[0,Ext,Root2].
tag_nums_ext([#'EXTENSIONMARK'{}|Rest],Ext,Root2) ->
tag_nums_root2(Rest,Ext,Root2);
tag_nums_ext([_|Rest],Ext,Root2) ->
tag_nums_ext(Rest,Ext,Root2);
tag_nums_ext([],Ext,_Root2) ->
[0,Ext,0].
tag_nums_root2([_|Rest],Ext,Root2) ->
tag_nums_root2(Rest,Ext+1,Root2);
tag_nums_root2([],Ext,Root2) ->
[0,Ext,Root2].
is_automatic_tagged_in_multi_file(Name) ->
case asn1ct_table:exists(automatic_tags) of
false ->
%% this case when not multifile compilation
false;
true ->
case asn1ct_table:lookup(automatic_tags, Name) of
[] -> false;
_ -> true
end
end.
generate_automatic_tags(_S,C,TagNo) ->
case any_manual_tag(C) of
true ->
C;
false ->
generate_automatic_tags1(C,TagNo)
end.
generate_automatic_tags1([H|T],[TagNo|TagNos]) when is_record(H,'ComponentType') ->
#'ComponentType'{typespec=Ts} = H,
NewTs = Ts#type{tag=[#tag{class='CONTEXT',
number=TagNo,
type={default,'IMPLICIT'},
form= 0 }]}, % PRIMITIVE
[H#'ComponentType'{typespec=NewTs}|generate_automatic_tags1(T,[TagNo+1|TagNos])];
generate_automatic_tags1([ExtMark = #'EXTENSIONMARK'{}|T],[_TagNo|TagNos]) ->
[ExtMark | generate_automatic_tags1(T,TagNos)];
generate_automatic_tags1([H|T],TagList) -> % ExtensionAdditionGroup etc are just ignored
[H | generate_automatic_tags1(T,TagList)];
generate_automatic_tags1([],_) ->
[].
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Returns true if there is at least one ComponentType with a manually
%% specified tag. No manual tag is indicated by typespec=#type{tag=[]}
%% so we check if we find a tag =/= [] and return true in that case
%% all other things in the componentlist like (EXTENSIONMARK,
%% ExtensionAdditionGroup,...) except ComponentType is simply
%% ignored/skipped
any_manual_tag([#'ComponentType'{typespec=#type{tag=Tag}}|_Rest])
when Tag =/= []->
true;
any_manual_tag([_|Rest]) ->
any_manual_tag(Rest);
any_manual_tag([]) ->
false.
check_unique_tags(S,C) ->
case (S#state.module)#module.tagdefault of
'AUTOMATIC' ->
case any_manual_tag(C) of
false ->
true;
true ->
check_unique_tags(S, C, [])
end;
_ ->
check_unique_tags(S, C, [])
end.
check_unique_tags(S, [#'ComponentType'{name=Name,tags=Tags0}|T], Acc) ->
Tags = [{Tag,Name} || Tag <- Tags0],
check_unique_tags(S, T, Tags ++ Acc);
check_unique_tags(S, [_|T], Acc) ->
check_unique_tags(S, T, Acc);
check_unique_tags(S, [], Acc) ->
R0 = sofs:relation(Acc),
R1 = sofs:relation_to_family(R0),
R2 = sofs:to_external(R1),
Dup = [Els || {_,[_,_|_]=Els} <- R2],
case Dup of
[] ->
ok;
[FirstDupl|_] ->
asn1_error(S, {duplicate_tags,FirstDupl})
end.
check_unique(L,Pos) ->
Slist = lists:keysort(Pos,L),
check_unique2(Slist,Pos,[]).
check_unique2([A,B|T],Pos,Acc) when element(Pos,A) == element(Pos,B) ->
check_unique2([B|T],Pos,[element(Pos,B)|Acc]);
check_unique2([_|T],Pos,Acc) ->
check_unique2(T,Pos,Acc);
check_unique2([],_,Acc) ->
lists:reverse(Acc).
%% Replaces check_each_component and does the same work except that
%% it keeps the complist as a flat list and does not create a tuple with root and
%% extensions separated
check_each_component2(S,Type,Components) ->
check_each_component2(S,Type,Components,[]).
check_each_component2(S = #state{abscomppath=Path,recordtopname=TopName},
Type,
[C = #'ComponentType'{name=Cname,typespec=Ts,prop=Prop}|Ct],
Acc) ->
NewAbsCPath =
case Ts#type.def of
#'Externaltypereference'{} -> [];
_ -> [Cname|Path]
end,%%XXX Cname = 'per-message-indicators'
CheckedTs = check_type(S#state{abscomppath=NewAbsCPath,
recordtopname=[Cname|TopName]},Type,Ts),
NewTags = get_taglist(S,CheckedTs),
NewProp =
case normalize_value(S,CheckedTs,Prop,[Cname|TopName]) of
mandatory -> mandatory;
'OPTIONAL' -> 'OPTIONAL';
DefaultValue -> {'DEFAULT',DefaultValue}
end,
NewC = C#'ComponentType'{typespec=CheckedTs,prop=NewProp,tags=NewTags},
check_each_component2(S,Type,Ct,[NewC|Acc]);
check_each_component2(S,Type,[OtherMarker|Ct],Acc) ->
%% let 'EXTENSIONMARK' and 'ExtensionAdditionGroup' markers pass through as is
check_each_component2(S,Type,Ct,[OtherMarker|Acc]);
check_each_component2(_S,_,[],Acc) ->
lists:reverse(Acc).
%% check_each_alternative2(S,Type,{Rlist,ExtList}) ->
%% {check_each_alternative(S,Type,Rlist),
%% check_each_alternative(S,Type,ExtList)};
check_each_alternative2(S,Type,[C|Ct]) ->
check_each_alternative2(S,Type,[C|Ct],[]).
check_each_alternative2(S=#state{abscomppath=Path,recordtopname=TopName},
Type,
[C = #'ComponentType'{name=Cname,typespec=Ts}|Ct],
Acc) ->
NewAbsCPath =
case Ts#type.def of
#'Externaltypereference'{} -> [];
_ -> [Cname|Path]
end,
CheckedTs = check_type(S#state{abscomppath=NewAbsCPath,
recordtopname=[Cname|TopName]},Type,Ts),
NewTags = get_taglist(S,CheckedTs),
NewC = C#'ComponentType'{typespec=CheckedTs,tags=NewTags},
check_each_alternative2(S,Type,Ct,[NewC|Acc]);
check_each_alternative2(S,Type,[OtherMarker|Ct],Acc) ->
%% let 'EXTENSIONMARK' and 'ExtensionAdditionGroup' markers pass through as is
check_each_alternative2(S,Type,Ct,[OtherMarker|Acc]);
check_each_alternative2(_S,_,[],Acc) ->
lists:reverse(Acc).
%% componentrelation_leadingattr/2 searches the structure for table
%% constraints, if any is found componentrelation_leadingattr/5 is
%% called.
componentrelation_leadingattr(S,CompList) ->
%% get_simple_table_if_used/2 should find out whether there are any
%% component relation constraints in the entire tree of Cs1 that
%% relates to this level. It returns information about the simple
%% table constraint necessary for the the call to
%% componentrelation_leadingattr/6. The step when the leading
%% attribute and the syntax tree is modified to support the code
%% generating.
case get_simple_table_if_used(S,CompList) of
[] -> {false,CompList};
_ ->
componentrelation_leadingattr(S,CompList,CompList,[],[])
end.
%%FIXME expand_ExtAddGroups([C#'ExtensionAdditionGroup'{components=ExtAdds}|T],
%% CurrPos,PosAcc,CompAcc) ->
%% expand_ExtAddGroups(T,CurrPos+ L = length(ExtAdds),[{CurrPos,L}|PosAcc],ExtAdds++CompAcc);
%% expand_ExtAddGroups([C|T],CurrPos,PosAcc,CompAcc) ->
%% expand_ExtAddGroups(T,CurrPos+ 1,PosAcc,[C|CompAcc]);
%% expand_ExtAddGroups([],_CurrPos,PosAcc,CompAcc) ->
%% {lists:reverse(PosAcc),lists:reverse(CompAcc)}.
%% componentrelation_leadingattr/6 when all components are searched
%% the new modified components are returned together with the "leading
%% attribute" information, which later is stored in the tablecinf
%% field in the SEQUENCE/SET record. The "leading attribute"
%% information is used to generate the lookup in the object set
%% table. The other information gathered in the #type.tablecinf field
%% is used in code generating phase too, to recognice the proper
%% components for "open type" encoding and to propagate the result of
%% the object set lookup when needed.
componentrelation_leadingattr(_,[],_CompList,[],NewCompList) ->
{false,lists:reverse(NewCompList)};
componentrelation_leadingattr(_,[],_CompList,LeadingAttr,NewCompList) ->
{lists:last(LeadingAttr),lists:reverse(NewCompList)}; %send all info in Ts later
componentrelation_leadingattr(S,[C= #'ComponentType'{}|Cs],CompList,Acc,CompAcc) ->
{LAAcc,NewC} =
case catch componentrelation1(S,C#'ComponentType'.typespec,
[C#'ComponentType'.name]) of
{'EXIT',_} ->
{[],C};
{CRI=[{_A1,_B1,_C1,_D1}|_Rest],NewTSpec} ->
%% {ObjectSet,AtPath,ClassDef,Path}
%% _A1 is a reference to the object set of the
%% component relation constraint.
%% _B1 is the path of names in the at-list of the
%% component relation constraint.
%% _C1 is the class definition of the
%% ObjectClassFieldType.
%% _D1 is the path of components that was traversed to
%% find this constraint.
case leading_attr_index(S,CompList,CRI,
lists:reverse(S#state.abscomppath),[]) of
[] ->
{[],C};
[{ObjSet,Attr,N,ClassDef,_Path,ValueIndex}|_NewRest] ->
OS = object_set_mod_name(S,ObjSet),
UniqFN = get_unique_fieldname(S,
#classdef{typespec=ClassDef}),
%% Res should be done differently: even though
%% a unique field name exists it is not
%% certain that the ObjectClassFieldType of
%% the simple table constraint picks that
%% class field.
Res = #simpletableattributes{objectsetname=OS,
c_name=Attr,
c_index=N,
usedclassfield=UniqFN,
uniqueclassfield=UniqFN,
valueindex=ValueIndex},
{[Res],C#'ComponentType'{typespec=NewTSpec}}
end;
_ ->
%% no constraint was found
{[],C}
end,
componentrelation_leadingattr(S,Cs,CompList,LAAcc++Acc,
[NewC|CompAcc]);
componentrelation_leadingattr(S,[NotComponentType|Cs],CompList,LeadingAttr,NewCompList) ->
componentrelation_leadingattr(S,Cs,CompList,LeadingAttr,[NotComponentType|NewCompList]).
object_set_mod_name(_S,ObjSet) when is_atom(ObjSet) ->
ObjSet;
object_set_mod_name(#state{mname=M},
#'Externaltypereference'{module=M,type=T}) ->
{M,T};
object_set_mod_name(S,#'Externaltypereference'{module=M,type=T}) ->
case lists:member(M,S#state.inputmodules) of
true ->
T;
false ->
{M,T}
end.
%% get_simple_table_if_used/2 searches the structure of Cs for any
%% component relation constraints due to the present level of the
%% structure. If there are any, the necessary information for code
%% generation of the look up functionality in the object set table are
%% returned.
get_simple_table_if_used(S,Cs) ->
CNames = [Name||#'ComponentType'{name=Name}<-Cs],
JustComponents = [C || C = #'ComponentType'{}<-Cs],
RefedSimpleTable=any_component_relation(S,JustComponents,CNames,[],[]),
get_simple_table_info(S,Cs,remove_doubles(RefedSimpleTable)).
remove_doubles(L) ->
remove_doubles(L,[]).
remove_doubles([H|T],Acc) ->
NewT = remove_doubles1(H,T),
remove_doubles(NewT,[H|Acc]);
remove_doubles([],Acc) ->
Acc.
remove_doubles1(El,L) ->
case lists:delete(El,L) of
L -> L;
NewL -> remove_doubles1(El,NewL)
end.
%% get_simple_table_info searches the components Cs by the path from
%% an at-list (third argument), and follows into a component of it if
%% necessary, to get information needed for code generating.
%%
%% Returns a list of tuples with three elements. It holds a list of
%% atoms that is the path, the name of the field of the class that are
%% referred to in the ObjectClassFieldType, and the name of the unique
%% field of the class of the ObjectClassFieldType.
%%
%% The level information outermost/innermost must be kept. There are
%% at least two possibilities to cover here for an outermost case: 1)
%% Both the simple table and the component relation have a common path
%% at least one step below the outermost level, i.e. the leading
%% information shall be on a sub level. 2) They don't have any common
%% path.
get_simple_table_info(S, Cs, AtLists) ->
[get_simple_table_info1(S, Cs, AtList, []) || AtList <- AtLists].
get_simple_table_info1(S, Cs, [Cname|Cnames], Path) ->
#'ComponentType'{} = C =
lists:keyfind(Cname, #'ComponentType'.name, Cs),
get_simple_table_info2(S, C, Cnames, [Cname|Path]).
get_simple_table_info2(S, #'ComponentType'{name=Name,typespec=TS}, [], Path) ->
OCFT = simple_table_get_ocft(S, Name, TS),
case lists:keymember(simpletable, 1, TS#type.constraint) of
true ->
simple_table_info(S, OCFT, Path);
false ->
asn1_error(S, {missing_table_constraint,Name})
end;
get_simple_table_info2(S, #'ComponentType'{typespec=TS}, Cnames, Path) ->
Components = get_atlist_components(TS#type.def),
get_simple_table_info1(S, Components, Cnames, Path).
simple_table_get_ocft(_, _, #type{def=#'ObjectClassFieldType'{}=OCFT}) ->
OCFT;
simple_table_get_ocft(S, Component, #type{constraint=Constr}) ->
case lists:keyfind(ocft, 1, Constr) of
{ocft,OCFT} ->
OCFT;
false ->
asn1_error(S, {missing_ocft,Component})
end.
simple_table_info(S,#'ObjectClassFieldType'{classname=ClRef,
class=ObjectClass,
fieldname=FieldName},Path) ->
ObjectClassFieldName =
case FieldName of
{LastFieldName,[]} -> LastFieldName;
{_FirstFieldName,FieldNames} ->
lists:last(FieldNames)
end,
%% ObjectClassFieldName is the last element in the dotted list of
%% the ObjectClassFieldType. The last element may be of another
%% class, that is referenced from the class of the
%% ObjectClassFieldType
ClassDef =
case ObjectClass of
[] ->
{_,CDef}=get_referenced_type(S,ClRef),
CDef;
_ -> #classdef{typespec=ObjectClass}
end,
UniqueName = get_unique_fieldname(S, ClassDef),
{lists:reverse(Path),ObjectClassFieldName,UniqueName}.
%% any_component_relation searches for all component relation
%% constraints that refers to the actual level and returns a list of
%% the "name path" in the at-list to the component relation constraint
%% that must refer to a simple table constraint. The list is empty if
%% no component relation constraints were found.
%%
%% NamePath has the names of all components that are followed from the
%% beginning of the search. CNames holds the names of all components
%% of the start level, this info is used if an outermost at-notation
%% is found to check the validity of the at-list.
any_component_relation(S,[#'ComponentType'{name=CName,typespec=Type}|Cs],CNames,NamePath,Acc) ->
CRelPath =
case lists:keyfind(componentrelation, 1, Type#type.constraint) of
{_,_,AtNotation} ->
%% Found component relation constraint, now check
%% whether this constraint is relevant for the level
%% where the search started
AtNot = extract_at_notation(AtNotation),
%% evaluate_atpath returns the relative path to the
%% simple table constraint from where the component
%% relation is found.
evaluate_atpath(S,NamePath,CNames,AtNot);
false ->
[]
end,
InnerAcc =
case {Type#type.inlined,
asn1ct_gen:type(asn1ct_gen:get_inner(Type#type.def))} of
{no,{constructed,bif}} ->
{InnerCs,NewNamePath} =
case get_components(Type#type.def) of
T when is_record(T,type) -> {T,NamePath};
IC -> {IC,[CName|NamePath]}
end,
%% here we are interested in components of an
%% SEQUENCE/SET OF as well as SEQUENCE, SET and CHOICE
any_component_relation(S,InnerCs,CNames,NewNamePath,[]);
_ ->
[]
end,
any_component_relation(S,Cs,CNames,NamePath,InnerAcc++CRelPath++Acc);
any_component_relation(S,Type,CNames,NamePath,Acc) when is_record(Type,type) ->
CRelPath =
case lists:keyfind(componentrelation, 1, Type#type.constraint) of
{_,_,AtNotation} ->
AtNot = extract_at_notation(AtNotation),
evaluate_atpath(S,NamePath,CNames,AtNot);
false ->
[]
end,
InnerAcc =
case {Type#type.inlined,
asn1ct_gen:type(asn1ct_gen:get_inner(Type#type.def))} of
{no,{constructed,bif}} ->
InnerCs = get_components(Type#type.def),
any_component_relation(S,InnerCs,CNames,NamePath,[]);
_ ->
[]
end,
InnerAcc ++ CRelPath ++ Acc;
%% Just skip the markers for ExtensionAdditionGroup start and end
%% in this function
any_component_relation(S,[#'ExtensionAdditionGroup'{}|Cs],CNames,NamePath,Acc) ->
any_component_relation(S,Cs,CNames,NamePath,Acc);
any_component_relation(S,['ExtensionAdditionGroupEnd'|Cs],CNames,NamePath,Acc) ->
any_component_relation(S,Cs,CNames,NamePath,Acc);
any_component_relation(_,[],_,_,Acc) ->
Acc.
%% evaluate_atpath/4 finds out whether the at notation refers to the
%% search level. The list of referenced names in the AtNot list shall
%% begin with a name that exists on the level it refers to. If the
%% found AtPath is refering to the same sub-branch as the simple table
%% has, then there shall not be any leading attribute info on this
%% level.
evaluate_atpath(_,[],Cnames,{innermost,AtPath=[Ref|_Refs]}) ->
%% any innermost constraint found deeper in the structure is
%% ignored.
case lists:member(Ref,Cnames) of
true -> [AtPath];
false -> []
end;
%% In this case must check that the AtPath doesn't step any step of
%% the NamePath, in that case the constraint will be handled in an
%% inner level.
evaluate_atpath(S=#state{abscomppath=TopPath},NamePath,Cnames,{outermost,AtPath=[_Ref|_Refs]}) ->
AtPathBelowTop =
case TopPath of
[] -> AtPath;
_ ->
case lists:prefix(TopPath,AtPath) of
true ->
lists:subtract(AtPath,TopPath);
_ -> []
end
end,
case {NamePath,AtPathBelowTop} of
{[H|_T1],[H|_T2]} -> []; % this must be handled in lower level
{_,[]} -> [];% this must be handled in an above level
{_,[H|_T]} ->
case lists:member(H,Cnames) of
true -> [AtPathBelowTop];
_ -> asn1_error(S, {invalid_at_path, AtPath})
end
end;
evaluate_atpath(_,_,_,_) ->
[].
%% Type may be any of SEQUENCE, SET, CHOICE, SEQUENCE OF, SET OF but
%% only the three first have valid components.
get_atlist_components(Def) ->
get_components(atlist,Def).
get_components(Def) ->
get_components(any,Def).
get_components(_,#'SEQUENCE'{components=Cs}) ->
tuple2complist(Cs);
get_components(_,#'SET'{components=Cs}) ->
tuple2complist(Cs);
get_components(_,{'CHOICE',Cs}) ->
tuple2complist(Cs);
%%do not step in inlined structures
get_components(any,{'SEQUENCE OF',T = #type{def=_Def,inlined=no}}) ->
T;
get_components(any,{'SET OF',T = #type{def=_Def,inlined=no}}) ->
T;
get_components(_,_) ->
[].
tuple2complist({R,E}) ->
R ++ E;
tuple2complist({R1,E,R2}) ->
R1 ++ E ++ R2;
tuple2complist(List) when is_list(List) ->
List.
extract_at_notation([{Level,ValueRefs}]) ->
{Level,[Name || #'Externalvaluereference'{value=Name} <- ValueRefs]}.
%% componentrelation1/1 identifies all componentrelation constraints
%% that exist in C or in the substructure of C. Info about the found
%% constraints are returned in a list. It is ObjectSet, the reference
%% to the object set, AttrPath, the name atoms extracted from the
%% at-list in the component relation constraint, ClassDef, the
%% objectclass record of the class of the ObjectClassFieldType, Path,
%% that is the component name "path" from the searched level to this
%% constraint.
%%
%% The function is called with one component of the type in turn and
%% with the component name in Path at the first call. When called from
%% within, the name of the inner component is added to Path.
componentrelation1(S,C = #type{def=Def,constraint=Constraint,tablecinf=TCI},
Path) ->
Ret =
case lists:keyfind(componentrelation, 1, Constraint) of
{_,{_,_,ObjectSet},AtList} ->
[{_,AL=[#'Externalvaluereference'{}|_R1]}|_R2] = AtList,
%% Note: if Path is longer than one,i.e. it is within
%% an inner type of the actual level, then the only
%% relevant at-list is of "outermost" type.
ClassDef = get_ObjectClassFieldType_classdef(S,Def),
AtPath =
lists:map(fun(#'Externalvaluereference'{value=V})->V end,
AL),
{[{ObjectSet,AtPath,ClassDef,Path}],Def};
false ->
%% check the inner type of component
innertype_comprel(S,Def,Path)
end,
case Ret of
nofunobj ->
nofunobj; %% ignored by caller
{CRelI=[{ObjSet,_,_,_}],NewDef} -> %%
TCItmp = lists:subtract(TCI,[{objfun,ObjSet}]),
{CRelI,C#type{tablecinf=[{objfun,ObjSet}|TCItmp],def=NewDef}};
{CompRelInf,NewDef} -> %% more than one tuple in CompRelInf
TCItmp = lists:subtract(TCI,[{objfun,anyset}]),
{CompRelInf,C#type{tablecinf=[{objfun,anyset}|TCItmp],def=NewDef}}
end.
innertype_comprel(S,{'SEQUENCE OF',Type},Path) ->
case innertype_comprel1(S,Type,Path) of
nofunobj ->
nofunobj;
{CompRelInf,NewType} ->
{CompRelInf,{'SEQUENCE OF',NewType}}
end;
innertype_comprel(S,{'SET OF',Type},Path) ->
case innertype_comprel1(S,Type,Path) of
nofunobj ->
nofunobj;
{CompRelInf,NewType} ->
{CompRelInf,{'SET OF',NewType}}
end;
innertype_comprel(S,{'CHOICE',CTypeList},Path) ->
case componentlist_comprel(S,CTypeList,[],Path,[]) of
nofunobj ->
nofunobj;
{CompRelInf,NewCs} ->
{CompRelInf,{'CHOICE',NewCs}}
end;
innertype_comprel(S,Seq = #'SEQUENCE'{components=Cs},Path) ->
case componentlist_comprel(S,Cs,[],Path,[]) of
nofunobj ->
nofunobj;
{CompRelInf,NewCs} ->
{CompRelInf,Seq#'SEQUENCE'{components=NewCs}}
end;
innertype_comprel(S,Set = #'SET'{components=Cs},Path) ->
case componentlist_comprel(S,Cs,[],Path,[]) of
nofunobj ->
nofunobj;
{CompRelInf,NewCs} ->
{CompRelInf,Set#'SET'{components=NewCs}}
end;
innertype_comprel(_,_,_) ->
nofunobj.
componentlist_comprel(S,[C = #'ComponentType'{name=Name,typespec=Type}|Cs],
Acc,Path,NewCL) ->
case catch componentrelation1(S,Type,Path++[Name]) of
{'EXIT',_} ->
componentlist_comprel(S,Cs,Acc,Path,[C|NewCL]);
nofunobj ->
componentlist_comprel(S,Cs,Acc,Path,[C|NewCL]);
{CRelInf,NewType} ->
componentlist_comprel(S,Cs,CRelInf++Acc,Path,
[C#'ComponentType'{typespec=NewType}|NewCL])
end;
componentlist_comprel(_,[],Acc,_,NewCL) ->
case Acc of
[] ->
nofunobj;
_ ->
{Acc,lists:reverse(NewCL)}
end.
innertype_comprel1(S,T = #type{def=Def,constraint=Cons,tablecinf=TCI},Path) ->
Ret =
case lists:keyfind(componentrelation, 1, Cons) of
{_,{_,_,ObjectSet},AtList} ->
%% This AtList must have an "outermost" at sign to be
%% relevent here.
[{_,AL=[#'Externalvaluereference'{value=_Attr}|_R1]}|_R2]
= AtList,
ClassDef = get_ObjectClassFieldType_classdef(S,Def),
AtPath =
lists:map(fun(#'Externalvaluereference'{value=V})->V end,
AL),
[{ObjectSet,AtPath,ClassDef,Path}];
false ->
innertype_comprel(S,Def,Path)
end,
case Ret of
nofunobj -> nofunobj;
L = [{ObjSet,_,_,_}] ->
TCItmp = lists:subtract(TCI,[{objfun,ObjSet}]),
{L,T#type{tablecinf=[{objfun,ObjSet}|TCItmp]}};
{CRelInf,NewDef} ->
TCItmp = lists:subtract(TCI,[{objfun,anyset}]),
{CRelInf,T#type{def=NewDef,tablecinf=[{objfun,anyset}|TCItmp]}}
end.
%% leading_attr_index counts the index and picks the name of the
%% component that is at the actual level in the at-list of the
%% component relation constraint (AttrP). AbsP is the path of
%% component names from the top type level to the actual level. AttrP
%% is a list with the atoms from the at-list.
leading_attr_index(S,Cs,[H={_,AttrP,_,_}|T],AbsP,Acc) ->
AttrInfo =
case lists:prefix(AbsP,AttrP) of
%% why this ?? It is necessary when in same situation as
%% TConstrChoice, there is an inner structure with an
%% outermost at-list and the "leading attribute" code gen
%% may be at a level some steps below the outermost level.
true ->
RelativAttrP = lists:subtract(AttrP,AbsP),
%% The header is used to calculate the index of the
%% component and to give the fun, received from the
%% object set look up, an unique name. The tail is
%% used to match the proper value input to the fun.
{hd(RelativAttrP),tl(RelativAttrP)};
false ->
{hd(AttrP),tl(AttrP)}
end,
case leading_attr_index1(S,Cs,H,AttrInfo,1) of
0 ->
leading_attr_index(S,Cs,T,AbsP,Acc);
Res ->
leading_attr_index(S,Cs,T,AbsP,[Res|Acc])
end;
leading_attr_index(_,_Cs,[],_,Acc) ->
lists:reverse(Acc).
leading_attr_index1(_,[],_,_,_) ->
0;
leading_attr_index1(S,[C|Cs],Arg={ObjectSet,_,CDef,P},
AttrInfo={Attr,SubAttr},N) ->
case C#'ComponentType'.name of
Attr ->
ValueMatch = value_match(S,C,Attr,SubAttr),
{ObjectSet,Attr,N,CDef,P,ValueMatch};
_ ->
leading_attr_index1(S,Cs,Arg,AttrInfo,N+1)
end.
%% value_math gathers information for a proper value match in the
%% generated encode function. For a SEQUENCE or a SET the index of the
%% component is counted. For a CHOICE the index is 2.
value_match(S,C,Name,SubAttr) ->
value_match(S,C,Name,SubAttr,[]). % C has name Name
value_match(_S,#'ComponentType'{},_Name,[],Acc) ->
Acc; % do not reverse, indexes in reverse order
value_match(S,#'ComponentType'{typespec=Type},Name,[At|Ats],Acc) ->
InnerType = asn1ct_gen:get_inner(Type#type.def),
Components =
case get_atlist_components(Type#type.def) of
[] -> asn1_error(S, {invalid_element, Name});
Comps -> Comps
end,
{Index,ValueIndex} = component_value_index(S,InnerType,At,Components),
value_match(S,lists:nth(Index,Components),At,Ats,[ValueIndex|Acc]).
component_value_index(S,'CHOICE',At,Components) ->
{component_index(S,At,Components),2};
component_value_index(S,_,At,Components) ->
%% SEQUENCE or SET
Index = component_index(S,At,Components),
{Index,{Index+1,At}}.
component_index(S,Name,Components) ->
component_index1(S,Name,Components,1).
component_index1(_S,Name,[#'ComponentType'{name=Name}|_Cs],N) ->
N;
component_index1(S,Name,[_C|Cs],N) ->
component_index1(S,Name,Cs,N+1);
component_index1(S,Name,[],_) ->
asn1_error(S, {invalid_at_list, Name}).
get_unique_fieldname(S, #classdef{typespec=TS}) ->
Fields = TS#objectclass.fields,
get_unique_fieldname1(S, Fields, []);
get_unique_fieldname(S,#typedef{typespec=#type{def=ClassRef}}) ->
%% A class definition may be referenced as
%% REFED-CLASS ::= DEFINED-CLASS and then REFED-CLASS is a typedef
{_M,ClassDef} = get_referenced_type(S,ClassRef),
get_unique_fieldname(S,ClassDef).
get_unique_fieldname1(S, [{fixedtypevaluefield,Name,_,'UNIQUE',Opt}|T], Acc) ->
get_unique_fieldname1(S, T, [{Name,Opt}|Acc]);
get_unique_fieldname1(S, [_|T], Acc) ->
get_unique_fieldname1(S, T, Acc);
get_unique_fieldname1(S, [], Acc) ->
case Acc of
[] -> no_unique;
[Name] -> Name;
[_|_] -> asn1_error(S, multiple_uniqs)
end.
get_tableconstraint_info(S,Type,{CheckedTs,EComps,CheckedTs2}) ->
{get_tableconstraint_info(S,Type,CheckedTs,[]),
get_tableconstraint_info(S,Type,EComps,[]),
get_tableconstraint_info(S,Type,CheckedTs2,[])};
get_tableconstraint_info(S,Type,{CheckedTs,EComps}) ->
{get_tableconstraint_info(S,Type,CheckedTs,[]),
get_tableconstraint_info(S,Type,EComps,[])};
get_tableconstraint_info(S,Type,CheckedTs) ->
get_tableconstraint_info(S,Type,CheckedTs,[]).
get_tableconstraint_info(_S,_Type,[],Acc) ->
lists:reverse(Acc);
get_tableconstraint_info(S,Type,[C=#'ComponentType'{typespec=CheckedTs}|Cs],Acc) ->
AccComp =
case CheckedTs#type.def of
%% ObjectClassFieldType
OCFT=#'ObjectClassFieldType'{} ->
NewOCFT =
OCFT#'ObjectClassFieldType'{class=[]},
C#'ComponentType'{typespec=
CheckedTs#type{
def=NewOCFT
}};
{'SEQUENCE OF',SOType} when is_record(SOType,type),
(element(1,SOType#type.def)=='CHOICE') ->
CTypeList = element(2,SOType#type.def),
NewInnerCList =
get_tableconstraint_info(S,Type,CTypeList),
C#'ComponentType'{typespec=
CheckedTs#type{
def={'SEQUENCE OF',
SOType#type{def={'CHOICE',
NewInnerCList}}}}};
{'SET OF',SOType} when is_record(SOType,type),
(element(1,SOType#type.def)=='CHOICE') ->
CTypeList = element(2,SOType#type.def),
NewInnerCList =
get_tableconstraint_info(S,Type,CTypeList),
C#'ComponentType'{typespec=
CheckedTs#type{
def={'SET OF',
SOType#type{def={'CHOICE',
NewInnerCList}}}}};
_ ->
C
end,
get_tableconstraint_info(S,Type,Cs,[AccComp|Acc]);
get_tableconstraint_info(S,Type,[C|Cs],Acc) ->
get_tableconstraint_info(S,Type,Cs,[C|Acc]).
get_referenced_fieldname([{_,FirstFieldname}]) ->
{FirstFieldname,[]};
get_referenced_fieldname([{_,FirstFieldname}|T]) ->
{FirstFieldname,[element(2, X) || X <- T]}.
%% get_ObjectClassFieldType_classdef gets the def of the class of the
%% ObjectClassFieldType, i.e. the objectclass record. If the type has
%% been checked (it may be a field type of an internal SEQUENCE) the
%% class field = [], then the classdef has to be fetched by help of
%% the class reference in the classname field.
get_ObjectClassFieldType_classdef(S,#'ObjectClassFieldType'{classname=Name,class=[]}) ->
{_,#classdef{typespec=TS}} = get_referenced_type(S,Name),
TS;
get_ObjectClassFieldType_classdef(_,#'ObjectClassFieldType'{class=Cl}) ->
Cl.
get_OCFType(S,Fields,FieldnameList=[{_FieldType,_PrimFieldName}|_]) ->
get_OCFType(S,Fields,[PFN||{_,PFN} <- FieldnameList]);
get_OCFType(S,Fields,[PrimFieldName|Rest]) ->
case lists:keysearch(PrimFieldName,2,Fields) of
{value,{fixedtypevaluefield,_,Type,_Unique,_OptSpec}} ->
{fixedtypevaluefield,PrimFieldName,Type};
{value,{objectfield,_,ClassRef,_Unique,_OptSpec}} ->
{MName,ClassDef} = get_referenced_type(S,ClassRef),
NewS = update_state(S#state{tname=get_datastr_name(ClassDef)},
MName),
CheckedCDef = check_class(NewS,ClassDef),
get_OCFType(S,CheckedCDef#objectclass.fields,Rest);
{value,{objectsetfield,_,Type,_OptSpec}} ->
{MName,ClassDef} = get_referenced_type(S,Type#type.def),
NewS = update_state(S#state{tname=get_datastr_name(ClassDef)},
MName),
CheckedCDef = check_class(NewS,ClassDef),
get_OCFType(S,CheckedCDef#objectclass.fields,Rest);
{value,Other} ->
{element(1,Other),PrimFieldName};
_ ->
asn1_error(S, {illegal_object_field, PrimFieldName})
end.
get_taglist(S,Ext) when is_record(Ext,'Externaltypereference') ->
{_,T} = get_referenced_type(S,Ext),
get_taglist(S,T#typedef.typespec);
get_taglist(S,Type) when is_record(Type,type) ->
case Type#type.tag of
[] ->
get_taglist(S,Type#type.def);
[Tag|_] ->
[asn1ct_gen:def_to_tag(Tag)]
end;
get_taglist(S,{'CHOICE',{Rc,Ec}}) ->
get_taglist1(S,Rc ++ Ec);
get_taglist(S,{'CHOICE',{R1,E,R2}}) ->
get_taglist1(S,R1 ++ E ++ R2);
get_taglist(S,{'CHOICE',Components}) ->
get_taglist1(S,Components);
%% ObjectClassFieldType OTP-4390
get_taglist(_S,#'ObjectClassFieldType'{type={typefield,_}}) ->
[];
get_taglist(S,#'ObjectClassFieldType'{type={fixedtypevaluefield,_,Type}}) ->
get_taglist(S,Type);
get_taglist(_, _) ->
[].
get_taglist1(S,[#'ComponentType'{name=_Cname,tags=TagL}|Rest]) when is_list(TagL) ->
%% tag_list has been here , just return TagL and continue with next alternative
TagL ++ get_taglist1(S,Rest);
get_taglist1(S,[#'ComponentType'{typespec=Ts,tags=undefined}|Rest]) ->
get_taglist(S,Ts) ++ get_taglist1(S,Rest);
get_taglist1(S,[_H|Rest]) -> % skip EXTENSIONMARK
get_taglist1(S,Rest);
get_taglist1(_S,[]) ->
[].
merge_tags(T1, T2) when is_list(T2) ->
merge_tags2(T1 ++ T2, []);
merge_tags(T1, T2) ->
merge_tags2(T1 ++ [T2], []).
merge_tags2([T1= #tag{type='IMPLICIT'}, T2 |Rest], Acc) ->
merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc);
merge_tags2([T1= #tag{type={default,'IMPLICIT'}}, T2 |Rest], Acc) ->
merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc);
merge_tags2([T1= #tag{type={default,'AUTOMATIC'}}, T2 |Rest], Acc) ->
merge_tags2([T1#tag{type=T2#tag.type, form=T2#tag.form}|Rest],Acc);
merge_tags2([H|T],Acc) ->
merge_tags2(T, [H|Acc]);
merge_tags2([], Acc) ->
lists:reverse(Acc).
storeindb(S0, #module{name=ModName,typeorval=TVlist0}=M) ->
S = S0#state{mname=ModName},
TVlist1 = [{asn1ct:get_name_of_def(Def),Def} || Def <- TVlist0],
case check_duplicate_defs(S, TVlist1) of
ok ->
storeindb_1(S, M, TVlist0, TVlist1);
{error,_}=Error ->
Error
end.
storeindb_1(S, #module{name=ModName}=M, TVlist0, TVlist) ->
NewM = M#module{typeorval=findtypes_and_values(TVlist0)},
Maps = lists:member(maps, S#state.options),
asn1_db:dbnew(ModName, S#state.erule, Maps),
asn1_db:dbput(ModName, 'MODULE', NewM),
asn1_db:dbput(ModName, TVlist),
include_default_class(S, NewM#module.name),
include_default_type(NewM#module.name),
ok.
check_duplicate_defs(S, Defs) ->
Set0 = sofs:relation(Defs),
Set1 = sofs:relation_to_family(Set0),
Set = sofs:to_external(Set1),
case [duplicate_def(S, N, Dup) || {N,[_,_|_]=Dup} <- Set] of
[] ->
ok;
[_|_]=E ->
{error,lists:append(E)}
end.
duplicate_def(S, Name, Dups0) ->
Dups1 = [{asn1ct:get_pos_of_def(Def),Def} || Def <- Dups0],
[{Prev,_}|Dups] = lists:sort(Dups1),
duplicate_def_1(S, Dups, Name, Prev).
duplicate_def_1(S, [{_,Def}|T], Name, Prev) ->
E = return_asn1_error(S, Def, {already_defined,Name,Prev}),
[E|duplicate_def_1(S, T, Name, Prev)];
duplicate_def_1(_, [], _, _) ->
[].
findtypes_and_values(TVList) ->
findtypes_and_values(TVList,[],[],[],[],[],[]).%% Types,Values,
%% Parameterizedtypes,Classes,Objects and ObjectSets
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,typedef),is_record(H#typedef.typespec,'Object') ->
findtypes_and_values(T,Tacc,Vacc,Pacc,Cacc,[H#typedef.name|Oacc],OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,typedef),is_record(H#typedef.typespec,'ObjectSet') ->
findtypes_and_values(T,Tacc,Vacc,Pacc,Cacc,Oacc,[H#typedef.name|OSacc]);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,typedef) ->
findtypes_and_values(T,[H#typedef.name|Tacc],Vacc,Pacc,Cacc,Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,valuedef) ->
findtypes_and_values(T,Tacc,[H#valuedef.name|Vacc],Pacc,Cacc,Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,ptypedef) ->
findtypes_and_values(T,Tacc,Vacc,[H#ptypedef.name|Pacc],Cacc,Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,classdef) ->
findtypes_and_values(T,Tacc,Vacc,Pacc,[H#classdef.name|Cacc],Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,pvaluedef) ->
findtypes_and_values(T,Tacc,[H#pvaluedef.name|Vacc],Pacc,Cacc,Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,pvaluesetdef) ->
findtypes_and_values(T,Tacc,[H#pvaluesetdef.name|Vacc],Pacc,Cacc,Oacc,OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,pobjectdef) ->
findtypes_and_values(T,Tacc,Vacc,Pacc,Cacc,[H#pobjectdef.name|Oacc],OSacc);
findtypes_and_values([H|T],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc)
when is_record(H,pobjectsetdef) ->
findtypes_and_values(T,Tacc,Vacc,Pacc,Cacc,Oacc,[H#pobjectsetdef.name|OSacc]);
findtypes_and_values([],Tacc,Vacc,Pacc,Cacc,Oacc,OSacc) ->
{lists:reverse(Tacc),lists:reverse(Vacc),lists:reverse(Pacc),
lists:reverse(Cacc),lists:reverse(Oacc),lists:reverse(OSacc)}.
return_asn1_error(#state{error_context=Context}=S, Error) ->
return_asn1_error(S, Context, Error).
return_asn1_error(#state{mname=Where}, Item, Error) ->
Pos = asn1ct:get_pos_of_def(Item),
{structured_error,{Where,Pos},?MODULE,Error}.
-spec asn1_error(_, _) -> no_return().
asn1_error(S, Error) ->
throw({error,return_asn1_error(S, Error)}).
format_error({already_defined,Name,PrevLine}) ->
io_lib:format("the name ~p has already been defined at line ~p",
[Name,PrevLine]);
format_error({duplicate_identifier,Ids}) ->
io_lib:format("the identifier '~p' has already been used", [Ids]);
format_error({duplicate_tags,Elements}) ->
io_lib:format("duplicate tags in the elements: ~s",
[format_elements(Elements)]);
format_error({enum_illegal_redefinition,Id}) ->
io_lib:format("'~s' must not be redefined", [Id]);
format_error({enum_not_ascending,Id,N,Prev}) ->
io_lib:format("the values for enumerations which follow '...' must "
"be in ascending order, but '~p(~p)' is less than the "
"previous value '~p'", [Id,N,Prev]);
format_error({enum_reused_value,Id,Val}) ->
io_lib:format("'~s' has the value '~p' which is used more than once",
[Id,Val]);
format_error({illegal_id, Id}) ->
io_lib:format("illegal identifier: ~p", [Id]);
format_error({illegal_choice_type, Ref}) ->
io_lib:format("expecting a CHOICE type: ~p", [Ref]);
format_error({illegal_class_name,Class}) ->
io_lib:format("the class name '~s' is illegal (it must start with an uppercase letter and only contain uppercase letters, digits, or hyphens)", [Class]);
format_error({illegal_COMPONENTS_OF, Ref}) ->
io_lib:format("expected a SEQUENCE or SET got: ~p", [Ref]);
format_error(illegal_external_value) ->
"illegal value in EXTERNAL type";
format_error({illegal_instance_of,Class}) ->
io_lib:format("using INSTANCE OF on class '~s' is illegal, "
"because INSTANCE OF may only be used on the class TYPE-IDENTIFIER",
[Class]);
format_error(illegal_integer_value) ->
"expecting an integer value";
format_error(illegal_object) ->
"expecting an object";
format_error({illegal_object_field, Id}) ->
io_lib:format("expecting a class field: ~p",[Id]);
format_error({illegal_oid,o_id}) ->
"illegal OBJECT IDENTIFIER";
format_error({illegal_oid,rel_oid}) ->
"illegal RELATIVE-OID";
format_error(illegal_octet_string_value) ->
"expecting a bstring or an hstring as value for an OCTET STRING";
format_error({illegal_typereference,Name}) ->
io_lib:format("'~p' is used as a typereference, but does not start with an uppercase letter", [Name]);
format_error(illegal_table_constraint) ->
"table constraints may only be applied to CLASS.&field constructs";
format_error(illegal_value) ->
"expecting a value";
format_error({illegal_value, TYPE}) ->
io_lib:format("expecting a ~s value", [TYPE]);
format_error({invalid_fields,Fields,Obj}) ->
io_lib:format("invalid ~s in ~p", [format_fields(Fields),Obj]);
format_error({invalid_bit_number,Bit}) ->
io_lib:format("the bit number '~p' is invalid", [Bit]);
format_error(invalid_table_constraint) ->
"the table constraint is not an object set";
format_error(invalid_objectset) ->
"expecting an object set";
format_error({implicit_tag_before,Kind}) ->
"illegal implicit tag before " ++
case Kind of
choice -> "'CHOICE'";
open_type -> "open type"
end;
format_error({missing_mandatory_fields,Fields,Obj}) ->
io_lib:format("missing mandatory ~s in ~p",
[format_fields(Fields),Obj]);
format_error({missing_table_constraint,Component}) ->
io_lib:format("the component '~s' is referenced by a component relation constraint using the '@field-name' notation, but does not have a table constraint",
[Component]);
format_error({missing_id,Id}) ->
io_lib:format("expected the mandatory component '~p'", [Id]);
format_error({missing_ocft,Component}) ->
io_lib:format("the component '~s' must be an ObjectClassFieldType (CLASSNAME.&field-name)", [Component]);
format_error(multiple_uniqs) ->
"implementation limitation: only one UNIQUE field is allowed in CLASS";
format_error({namelist_redefinition,Name}) ->
io_lib:format("the name '~s' cannot be redefined", [Name]);
format_error({param_bad_type, Ref}) ->
io_lib:format("'~p' is not a parameterized type", [Ref]);
format_error(param_wrong_number_of_arguments) ->
"wrong number of arguments";
format_error(reversed_range) ->
"ranges must be given in increasing order";
format_error({syntax_duplicated_fields,Fields}) ->
io_lib:format("~s must only occur once in the syntax list",
[format_fields(Fields)]);
format_error(syntax_nomatch) ->
"unexpected end of object definition";
format_error({syntax_mandatory_in_optional_group,Name}) ->
io_lib:format("the field '&~s' must not be within an optional group since it is not optional",
[Name]);
format_error({syntax_missing_mandatory_fields,Fields}) ->
io_lib:format("missing mandatory ~s in the syntax list",
[format_fields(Fields)]);
format_error({syntax_nomatch,Actual}) ->
io_lib:format("~s is not the next item allowed according to the defined syntax",
[Actual]);
format_error({syntax_undefined_field,Field}) ->
io_lib:format("'&~s' is not a field of the class being defined",
[Field]);
format_error({undefined,Name}) ->
io_lib:format("'~s' is referenced, but is not defined", [Name]);
format_error({undefined_export,Ref}) ->
io_lib:format("'~s' is exported but is not defined", [Ref]);
format_error({undefined_field,FieldName}) ->
io_lib:format("the field '&~s' is undefined", [FieldName]);
format_error({undefined_import,Ref,Module}) ->
io_lib:format("'~s' is not exported from ~s", [Ref,Module]);
format_error({unique_and_default,Field}) ->
io_lib:format("the field '&~s' must not have both 'UNIQUE' and 'DEFAULT'",
[Field]);
format_error({value_reused,Val}) ->
io_lib:format("the value '~p' is used more than once", [Val]);
format_error({non_unique_object,Id}) ->
io_lib:format("object set with a UNIQUE field value of '~p' is used more than once", [Id]);
format_error(Other) ->
io_lib:format("~p", [Other]).
format_fields([F]) ->
io_lib:format("field '&~s'", [F]);
format_fields([H|T]) ->
[io_lib:format("fields '&~s'", [H])|
[io_lib:format(", '&~s'", [F]) || F <- T]].
format_elements([H1,H2|T]) ->
[io_lib:format("~p, ", [H1])|format_elements([H2|T])];
format_elements([H]) ->
io_lib:format("~p", [H]).
include_default_type(Module) ->
NameAbsList = default_type_list(),
include_default_type1(Module,NameAbsList).
include_default_type1(_,[]) ->
ok;
include_default_type1(Module,[{Name,TS}|Rest]) ->
case asn1_db:dbget(Module,Name) of
undefined ->
T = #typedef{name=Name,
typespec=TS},
asn1_db:dbput(Module,Name,T);
_ -> ok
end,
include_default_type1(Module,Rest).
default_type_list() ->
%% The EXTERNAL type is represented, according to ASN.1 1997,
%% as a SEQUENCE with components: identification, data-value-descriptor
%% and data-value.
Syntax =
#'ComponentType'{name=syntax,
typespec=#type{def='OBJECT IDENTIFIER'},
prop=mandatory},
Presentation_Cid =
#'ComponentType'{name='presentation-context-id',
typespec=#type{def='INTEGER'},
prop=mandatory},
Transfer_syntax =
#'ComponentType'{name='transfer-syntax',
typespec=#type{def='OBJECT IDENTIFIER'},
prop=mandatory},
Negotiation_items =
#type{def=
#'SEQUENCE'{components=
[Presentation_Cid,
Transfer_syntax#'ComponentType'{prop=mandatory}]}},
Context_negot =
#'ComponentType'{name='context-negotiation',
typespec=Negotiation_items,
prop=mandatory},
Data_value_descriptor =
#'ComponentType'{name='data-value-descriptor',
typespec=#type{def='ObjectDescriptor'},
prop='OPTIONAL'},
Data_value =
#'ComponentType'{name='data-value',
typespec=#type{def='OCTET STRING'},
prop=mandatory},
%% The EXTERNAL type is represented, according to ASN.1 1990,
%% as a SEQUENCE with components: direct-reference, indirect-reference,
%% data-value-descriptor and encoding.
Direct_reference =
#'ComponentType'{name='direct-reference',
typespec=#type{def='OBJECT IDENTIFIER'},
prop='OPTIONAL',
tags=[{'UNIVERSAL',6}]},
Indirect_reference =
#'ComponentType'{name='indirect-reference',
typespec=#type{def='INTEGER'},
prop='OPTIONAL',
tags=[{'UNIVERSAL',2}]},
Single_ASN1_type =
#'ComponentType'{name='single-ASN1-type',
typespec=#type{tag=[{tag,'CONTEXT',0,
'EXPLICIT',32}],
def='ANY'},
prop=mandatory,
tags=[{'CONTEXT',0}]},
Octet_aligned =
#'ComponentType'{name='octet-aligned',
typespec=#type{tag=[{tag,'CONTEXT',1,
'IMPLICIT',0}],
def='OCTET STRING'},
prop=mandatory,
tags=[{'CONTEXT',1}]},
Arbitrary =
#'ComponentType'{name=arbitrary,
typespec=#type{tag=[{tag,'CONTEXT',2,
'IMPLICIT',0}],
def={'BIT STRING',[]}},
prop=mandatory,
tags=[{'CONTEXT',2}]},
Encoding =
#'ComponentType'{name=encoding,
typespec=#type{def={'CHOICE',
[Single_ASN1_type,Octet_aligned,
Arbitrary]}},
prop=mandatory},
EXTERNAL_components1990 =
[Direct_reference,Indirect_reference,Data_value_descriptor,Encoding],
%% The EMBEDDED PDV type is represented by a SEQUENCE type
%% with components: identification and data-value
Abstract =
#'ComponentType'{name=abstract,
typespec=#type{def='OBJECT IDENTIFIER'},
prop=mandatory},
Transfer =
#'ComponentType'{name=transfer,
typespec=#type{def='OBJECT IDENTIFIER'},
prop=mandatory},
AbstractTrSeq =
#'SEQUENCE'{components=[Abstract,Transfer]},
Syntaxes =
#'ComponentType'{name=syntaxes,
typespec=#type{def=AbstractTrSeq},
prop=mandatory},
Fixed = #'ComponentType'{name=fixed,
typespec=#type{def='NULL'},
prop=mandatory},
Negotiations =
[Syntaxes,Syntax,Presentation_Cid,Context_negot,
Transfer_syntax,Fixed],
Identification2 =
#'ComponentType'{name=identification,
typespec=#type{def={'CHOICE',Negotiations}},
prop=mandatory},
EmbeddedPdv_components =
[Identification2,Data_value],
%% The CHARACTER STRING type is represented by a SEQUENCE type
%% with components: identification and string-value
String_value =
#'ComponentType'{name='string-value',
typespec=#type{def='OCTET STRING'},
prop=mandatory},
CharacterString_components =
[Identification2,String_value],
[{'EXTERNAL',
#type{tag=[#tag{class='UNIVERSAL',
number=8,
type='IMPLICIT',
form=32}],
def=#'SEQUENCE'{components=
EXTERNAL_components1990}}},
{'EMBEDDED PDV',
#type{tag=[#tag{class='UNIVERSAL',
number=11,
type='IMPLICIT',
form=32}],
def=#'SEQUENCE'{components=EmbeddedPdv_components}}},
{'CHARACTER STRING',
#type{tag=[#tag{class='UNIVERSAL',
number=29,
type='IMPLICIT',
form=32}],
def=#'SEQUENCE'{components=CharacterString_components}}}
].
include_default_class(S, Module) ->
_ = [include_default_class1(S, Module, ClassDef) ||
ClassDef <- default_class_list()],
ok.
include_default_class1(S, Module, {Name,Ts0}) ->
case asn1_db:dbget(Module, Name) of
undefined ->
#objectclass{fields=Fields,
syntax={'WITH SYNTAX',Syntax0}} = Ts0,
Syntax = preprocess_syntax(S, Syntax0, Fields),
Ts = Ts0#objectclass{syntax={preprocessed_syntax,Syntax}},
C = #classdef{checked=true,module=Module,
name=Name,typespec=Ts},
asn1_db:dbput(Module, Name, C);
_ ->
ok
end.
default_class_list() ->
[{'TYPE-IDENTIFIER',
#objectclass{fields=[{fixedtypevaluefield,
id,
#type{tag=[?TAG_PRIMITIVE(?N_OBJECT_IDENTIFIER)],
def='OBJECT IDENTIFIER'},
'UNIQUE',
'MANDATORY'},
{typefield,'Type','MANDATORY'}],
syntax={'WITH SYNTAX',
[{typefieldreference,'Type'},
'IDENTIFIED',
'BY',
{valuefieldreference,id}]}}},
{'ABSTRACT-SYNTAX',
#objectclass{fields=[{fixedtypevaluefield,
id,
#type{tag=[?TAG_PRIMITIVE(?N_OBJECT_IDENTIFIER)],
def='OBJECT IDENTIFIER'},
'UNIQUE',
'MANDATORY'},
{typefield,'Type','MANDATORY'},
{fixedtypevaluefield,
property,
#type{tag=[?TAG_PRIMITIVE(?N_BIT_STRING)],
def={'BIT STRING',[]}},
undefined,
{'DEFAULT',
[0,1,0]}}],
syntax={'WITH SYNTAX',
[{typefieldreference,'Type'},
'IDENTIFIED',
'BY',
{valuefieldreference,id},
['HAS',
'PROPERTY',
{valuefieldreference,property}]]}}}].
new_reference_name(Name) ->
case get(asn1_reference) of
undefined ->
put(asn1_reference,1),
list_to_atom(lists:concat([internal_,Name,"_",1]));
Num when is_integer(Num) ->
put(asn1_reference,Num+1),
list_to_atom(lists:concat([internal_,Name,"_",Num+1]))
end.
get_record_prefix_name(S) ->
case lists:keysearch(record_name_prefix,1,S#state.options) of
{value,{_,Prefix}} ->
Prefix;
_ ->
""
end.
insert_once(S,Tab,Key) ->
case get(top_module) of
M when M == S#state.mname ->
asn1ct_gen:insert_once(Tab,Key),
ok;
_ ->
skipped
end.
check_fold(S0, [H|T], Check) ->
Type = asn1_db:dbget(S0#state.mname, H),
S = S0#state{error_context=Type},
case Check(S, H, Type) of
ok ->
check_fold(S, T, Check);
Error ->
[Error|check_fold(S, T, Check)]
end;
check_fold(_, [], Check) when is_function(Check, 3) -> [].
error_value(Value) when is_integer(Value) -> Value;
error_value(Value) when is_atom(Value) -> Value;
error_value(#type{def=Value}) when is_atom(Value) -> Value;
error_value(#type{def=Value}) -> error_value(Value);
error_value(RefOrType) ->
try name_of_def(RefOrType) of
Name -> Name
catch _:_ ->
case get_datastr_name(RefOrType) of
undefined -> RefOrType;
Name -> Name
end
end.
name_of_def(#'Externaltypereference'{type=N}) -> N;
name_of_def(#'Externalvaluereference'{value=N}) -> N.