aboutsummaryrefslogtreecommitdiffstats
path: root/lib/diameter/src
diff options
context:
space:
mode:
Diffstat (limited to 'lib/diameter/src')
-rw-r--r--lib/diameter/src/.gitignore (renamed from lib/diameter/src/compiler/.gitignore)2
-rw-r--r--lib/diameter/src/Makefile270
-rw-r--r--lib/diameter/src/app/.gitignore6
-rw-r--r--lib/diameter/src/app/Makefile212
-rw-r--r--lib/diameter/src/app/diameter.appup.src47
-rw-r--r--lib/diameter/src/app/diameter.erl190
-rw-r--r--lib/diameter/src/app/diameter.mk.in47
-rw-r--r--lib/diameter/src/app/diameter_callback.erl91
-rw-r--r--lib/diameter/src/app/diameter_gen_base_accounting.dia68
-rw-r--r--lib/diameter/src/app/diameter_gen_base_rfc3588.dia413
-rw-r--r--lib/diameter/src/app/diameter_peer_fsm.erl746
-rw-r--r--lib/diameter/src/app/diameter_reg.erl327
-rw-r--r--lib/diameter/src/app/diameter_stats.erl342
-rw-r--r--lib/diameter/src/app/diameter_types.hrl139
-rw-r--r--lib/diameter/src/app/modules.mk70
-rw-r--r--lib/diameter/src/base/diameter.app.src (renamed from lib/diameter/src/app/diameter.app.src)2
-rw-r--r--lib/diameter/src/base/diameter.appup.src40
-rw-r--r--lib/diameter/src/base/diameter.erl357
-rw-r--r--lib/diameter/src/base/diameter_app.erl (renamed from lib/diameter/src/app/diameter_app.erl)0
-rw-r--r--lib/diameter/src/base/diameter_callback.erl234
-rw-r--r--lib/diameter/src/base/diameter_capx.erl (renamed from lib/diameter/src/app/diameter_capx.erl)169
-rw-r--r--lib/diameter/src/base/diameter_codec.erl (renamed from lib/diameter/src/app/diameter_codec.erl)36
-rw-r--r--lib/diameter/src/base/diameter_config.erl (renamed from lib/diameter/src/app/diameter_config.erl)103
-rw-r--r--lib/diameter/src/base/diameter_dbg.erl (renamed from lib/diameter/src/app/diameter_dbg.erl)0
-rw-r--r--lib/diameter/src/base/diameter_dict.erl (renamed from lib/diameter/src/app/diameter_dict.erl)0
-rw-r--r--lib/diameter/src/base/diameter_info.erl (renamed from lib/diameter/src/app/diameter_info.erl)0
-rw-r--r--lib/diameter/src/base/diameter_internal.hrl (renamed from lib/diameter/src/app/diameter_internal.hrl)0
-rw-r--r--lib/diameter/src/base/diameter_lib.erl (renamed from lib/diameter/src/app/diameter_lib.erl)0
-rw-r--r--lib/diameter/src/base/diameter_misc_sup.erl (renamed from lib/diameter/src/app/diameter_misc_sup.erl)0
-rw-r--r--lib/diameter/src/base/diameter_peer.erl (renamed from lib/diameter/src/app/diameter_peer.erl)143
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm.erl1162
-rw-r--r--lib/diameter/src/base/diameter_peer_fsm_sup.erl (renamed from lib/diameter/src/app/diameter_peer_fsm_sup.erl)0
-rw-r--r--lib/diameter/src/base/diameter_reg.erl356
-rw-r--r--lib/diameter/src/base/diameter_service.erl (renamed from lib/diameter/src/app/diameter_service.erl)1393
-rw-r--r--lib/diameter/src/base/diameter_service_sup.erl (renamed from lib/diameter/src/app/diameter_service_sup.erl)0
-rw-r--r--lib/diameter/src/base/diameter_session.erl (renamed from lib/diameter/src/app/diameter_session.erl)24
-rw-r--r--lib/diameter/src/base/diameter_stats.erl287
-rw-r--r--lib/diameter/src/base/diameter_sup.erl (renamed from lib/diameter/src/app/diameter_sup.erl)0
-rw-r--r--lib/diameter/src/base/diameter_sync.erl (renamed from lib/diameter/src/app/diameter_sync.erl)0
-rw-r--r--lib/diameter/src/base/diameter_types.erl (renamed from lib/diameter/src/app/diameter_types.erl)61
-rw-r--r--lib/diameter/src/base/diameter_watchdog.erl (renamed from lib/diameter/src/app/diameter_watchdog.erl)261
-rw-r--r--lib/diameter/src/base/diameter_watchdog_sup.erl (renamed from lib/diameter/src/app/diameter_watchdog_sup.erl)0
-rw-r--r--lib/diameter/src/compiler/Makefile131
-rw-r--r--lib/diameter/src/compiler/diameter_codegen.erl402
-rw-r--r--lib/diameter/src/compiler/diameter_dict_parser.yrl324
-rw-r--r--lib/diameter/src/compiler/diameter_dict_scanner.erl276
-rw-r--r--lib/diameter/src/compiler/diameter_dict_util.erl1358
-rw-r--r--lib/diameter/src/compiler/diameter_exprecs.erl (renamed from lib/diameter/src/app/diameter_exprecs.erl)44
-rw-r--r--lib/diameter/src/compiler/diameter_forms.hrl7
-rw-r--r--lib/diameter/src/compiler/diameter_make.erl172
-rw-r--r--lib/diameter/src/compiler/diameter_nowarn.erl41
-rw-r--r--lib/diameter/src/compiler/diameter_spec_scan.erl157
-rw-r--r--lib/diameter/src/compiler/diameter_spec_util.erl1068
-rw-r--r--lib/diameter/src/compiler/diameter_vsn.hrl22
-rw-r--r--lib/diameter/src/compiler/modules.mk27
-rw-r--r--lib/diameter/src/depend.sed (renamed from lib/diameter/src/app/depend.sed)28
-rw-r--r--lib/diameter/src/dict/base_accounting.dia69
-rw-r--r--lib/diameter/src/dict/base_rfc3588.dia461
-rw-r--r--lib/diameter/src/dict/relay.dia (renamed from lib/diameter/src/app/diameter_gen_relay.dia)3
-rw-r--r--lib/diameter/src/gen/.gitignore2
-rw-r--r--lib/diameter/src/modules.mk105
-rw-r--r--lib/diameter/src/subdirs.mk21
-rw-r--r--lib/diameter/src/transport/.gitignore3
-rw-r--r--lib/diameter/src/transport/Makefile141
-rw-r--r--lib/diameter/src/transport/diameter_etcp.erl22
-rw-r--r--lib/diameter/src/transport/diameter_sctp.erl222
-rw-r--r--lib/diameter/src/transport/diameter_tcp.erl347
-rw-r--r--lib/diameter/src/transport/diameter_transport.erl55
-rw-r--r--lib/diameter/src/transport/modules.mk29
69 files changed, 7743 insertions, 5392 deletions
diff --git a/lib/diameter/src/compiler/.gitignore b/lib/diameter/src/.gitignore
index d9f072e262..cc06720fd1 100644
--- a/lib/diameter/src/compiler/.gitignore
+++ b/lib/diameter/src/.gitignore
@@ -1,3 +1,3 @@
/depend.mk
-
+/otp.plt
diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile
index 6935eb053e..060659bce9 100644
--- a/lib/diameter/src/Makefile
+++ b/lib/diameter/src/Makefile
@@ -1,7 +1,7 @@
#
# %CopyrightBegin%
#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
+# Copyright Ericsson AB 2010-2012. All Rights Reserved.
#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
@@ -16,28 +16,268 @@
#
# %CopyrightEnd%
-ifneq ($(ERL_TOP),)
include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
-else
-include $(DIAMETER_TOP)/make/target.mk
-include $(DIAMETER_TOP)/make/$(TARGET)/rules.mk
-endif
# ----------------------------------------------------
-# Common Macros
+# Application version
+# ----------------------------------------------------
+
+include ../vsn.mk
+
+VSN = $(DIAMETER_VSN)
+
# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+
+RELSYSDIR = $(RELEASE_PATH)/lib/diameter-$(VSN)
+
+# Where to put/find things.
+EBIN = ../ebin
+INCDIR = ../include
+
+# Dumbed down to make 3.80. In 3.81 and later it's just $(realpath $(EBIN)).
+ABS_EBIN := $(shell cd $(EBIN) && pwd)
+
+# Where make should look for dependencies.
+VPATH = .:base:compiler:transport:gen
+
+# ----------------------------------------------------
+# Target specs
+# ----------------------------------------------------
+
+include modules.mk
+
+# Modules generated from dictionary specifications.
+DICT_MODULES = $(DICTS:%=gen/diameter_gen_%)
+DICT_ERLS = $(DICT_MODULES:%=%.erl)
+DICT_HRLS = $(DICT_MODULES:%=%.hrl)
+
+# Modules to build before compiling dictionaries.
+COMPILER_MODULES = $(notdir $(filter compiler/%, $(CT_MODULES))) \
+ $(DICT_YRL)
+
+# All handwritten modules from which a depend.mk is generated.
+MODULES = \
+ $(RT_MODULES) \
+ $(CT_MODULES)
-include subdirs.mk
+# Modules whose names are inserted into the app file.
+APP_MODULES = \
+ $(RT_MODULES) \
+ $(DICT_MODULES)
-SPECIAL_TARGETS =
+# Modules for which to build beams.
+TARGET_MODULES = \
+ $(APP_MODULES) \
+ $(CT_MODULES) \
+ $(DICT_YRL:%=gen/%)
+
+# What to build for the 'opt' target.
+TARGET_FILES = \
+ $(patsubst %, $(EBIN)/%.$(EMULATOR), $(notdir $(TARGET_MODULES))) \
+ $(APP_TARGET) \
+ $(APPUP_TARGET)
+
+# Subdirectories of src to release modules into.
+TARGET_DIRS = $(sort $(dir $(TARGET_MODULES)))
+
+# Ditto for examples.
+EXAMPLE_DIRS = $(sort $(dir $(EXAMPLES)))
+
+APP_FILE = diameter.app
+APP_SRC = $(APP_FILE).src
+APP_TARGET = $(EBIN)/$(APP_FILE)
+
+APPUP_FILE = diameter.appup
+APPUP_SRC = $(APPUP_FILE).src
+APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
# ----------------------------------------------------
-# Default Subdir Targets
+# Flags
# ----------------------------------------------------
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/otp_subdir.mk
-else
-include $(DIAMETER_TOP)/make/subdir.mk
+
+ifeq ($(TYPE),debug)
+ERL_COMPILE_FLAGS += -Ddebug
endif
-#include ../make/subdir.mk
+
+ERL_COMPILE_FLAGS += \
+ +'{parse_transform,sys_pre_attributes}' \
+ +'{attribute,insert,app_vsn,$(APP_VSN)}' \
+ +warn_export_vars \
+ +warn_unused_vars \
+ -pa $(ABS_EBIN) \
+ -I $(INCDIR) \
+ -I gen
+# -pa is to be able to include_lib from the include directory: the
+# path must contain the application name.
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+# erl/hrl from dictionary file.
+gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia
+ ../bin/diameterc -o gen -i $(EBIN) $<
+
+opt: $(TARGET_FILES)
+
+debug:
+ @$(MAKE) TYPE=debug opt
+
+# The dictionary parser.
+gen/$(DICT_YRL).erl: compiler/$(DICT_YRL).yrl
+ $(ERLC) -Werror -o $(@D) $<
+
+# Generate the app file.
+$(APP_TARGET): $(APP_SRC) ../vsn.mk modules.mk
+ M=`echo $(notdir $(APP_MODULES)) | tr ' ' ,`; \
+ sed -e 's;%VSN%;$(VSN);' \
+ -e "s;%MODULES%;$$M;" \
+ $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+app: $(APP_TARGET) $(APPUP_TARGET)
+dict: $(DICT_ERLS)
+
+docs:
+
+list = echo $(1):; echo $($(1)) | tr ' ' '\n' | sort | sed 's@^@ @'
+
+info:
+ @echo ========================================
+ @$(call list,DICTS)
+ @echo
+ @$(call list,DICT_YRL)
+ @echo
+ @$(call list,RT_MODULES)
+ @echo
+ @$(call list,CT_MODULES)
+ @echo
+ @$(call list,TARGET_MODULES)
+ @echo
+ @$(call list,TARGET_DIRS)
+ @echo
+ @$(call list,EXTERNAL_HRLS)
+ @echo
+ @$(call list,INTERNAL_HRLS)
+ @echo
+ @$(call list,EXAMPLES)
+ @echo
+ @$(call list,EXAMPLE_DIRS)
+ @echo
+ @$(call list,BINS)
+ @echo ========================================
+
+clean:
+ rm -f $(TARGET_FILES) gen/*
+ rm -f depend.mk
+
+realclean: clean
+ rm -f ../ebin/*
+# Not $(EBIN) just to be a bit paranoid
+
+PLT = ./otp.plt
+
+plt:
+ dialyzer --build_plt \
+ --apps erts stdlib kernel \
+ xmerl ssl public_key crypto \
+ compiler syntax_tools runtime_tools \
+ --output_plt $(PLT) \
+ --verbose
+
+dialyze: opt $(PLT)
+ dialyzer --plt $(PLT) \
+ --verbose \
+ -Wno_improper_lists \
+ $(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR) \
+ $(patsubst %, $(EBIN)/%.$(EMULATOR), \
+ $(notdir $(RT_MODULES) $(CT_MODULES)))
+# Omit all but the common dictionary module since these
+# (diameter_gen_relay in particular) generate warning depending on how
+# much of the included diameter_gen.hrl they use.
+
+# ----------------------------------------------------
+# Release targets
+# ----------------------------------------------------
+
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+# Can't $(INSTALL_DIR) more than one directory at a time on Solaris.
+
+release_spec: opt
+ for d in bin ebin include src/dict; do \
+ $(INSTALL_DIR) "$(RELSYSDIR)/$$d"; \
+ done
+ $(INSTALL_SCRIPT) $(BINS:%=../bin/%) "$(RELSYSDIR)/bin"
+ $(INSTALL_DATA) $(TARGET_FILES) "$(RELSYSDIR)/ebin"
+ $(INSTALL_DATA) $(EXTERNAL_HRLS:%=../include/%) $(DICT_HRLS) \
+ "$(RELSYSDIR)/include"
+ $(INSTALL_DATA) $(DICTS:%=dict/%.dia) "$(RELSYSDIR)/src/dict"
+ $(MAKE) $(TARGET_DIRS:%/=release_src_%)
+ $(MAKE) $(EXAMPLE_DIRS:%/=release_examples_%)
+
+$(TARGET_DIRS:%/=release_src_%): release_src_%:
+ $(INSTALL_DIR) "$(RELSYSDIR)/src/$*"
+ $(INSTALL_DATA) $(filter $*/%, $(TARGET_MODULES:%=%.erl) \
+ $(INTERNAL_HRLS)) \
+ $(filter $*/%, compiler/$(DICT_YRL).yrl) \
+ "$(RELSYSDIR)/src/$*"
+
+$(EXAMPLE_DIRS:%/=release_examples_%): release_examples_%:
+ $(INSTALL_DIR) "$(RELSYSDIR)/examples/$*"
+ $(INSTALL_DATA) $(patsubst %, ../examples/%, $(filter $*/%, $(EXAMPLES))) \
+ "$(RELSYSDIR)/examples/$*"
+
+release_docs_spec:
+
+# ----------------------------------------------------
+# Dependencies
+# ----------------------------------------------------
+
+gen/diameter_gen_base_accounting.erl gen/diameter_gen_relay.erl \
+gen/diameter_gen_base_accounting.hrl gen/diameter_gen_relay.hrl: \
+ $(EBIN)/diameter_gen_base_rfc3588.$(EMULATOR)
+
+gen/diameter_gen_base_rfc3588.erl gen/diameter_gen_base_rfc3588.hrl: \
+ $(COMPILER_MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+$(DICT_MODULES:gen/%=$(EBIN)/%.$(EMULATOR)): \
+ $(INCDIR)/diameter.hrl \
+ $(INCDIR)/diameter_gen.hrl
+
+depend: depend.mk
+
+# Generate dependencies makefile.
+depend.mk: depend.sed $(MODULES:%=%.erl) Makefile
+ (for f in $(MODULES); do \
+ (echo $$f; cat $$f.erl) | sed -f $<; \
+ done) \
+ > $@
+
+-include depend.mk
+
+.PHONY: app clean realclean depend dict info release_subdir
+.PHONY: debug opt release_docs_spec release_spec
+.PHONY: $(TARGET_DIRS:%/=%) $(TARGET_DIRS:%/=release_src_%)
+.PHONY: $(EXAMPLE_DIRS:%/=release_examples_%)
+.PHONY: plt dialyze
+
+# Keep intermediate files.
+.SECONDARY: $(DICT_ERLS) $(DICT_HRLS) gen/$(DICT_YRL:%=%.erl)
+
+# ----------------------------------------------------
+# Targets using secondary expansion (make >= 3.81)
+# ----------------------------------------------------
+
+.SECONDEXPANSION:
+
+# Make beams from a subdirectory.
+$(TARGET_DIRS:%/=%): \
+ $$(patsubst $$@/%, \
+ $(EBIN)/%.$(EMULATOR), \
+ $$(filter $$@/%, $(TARGET_MODULES) compiler/$(DICT_YRL)))
diff --git a/lib/diameter/src/app/.gitignore b/lib/diameter/src/app/.gitignore
deleted file mode 100644
index d388e61877..0000000000
--- a/lib/diameter/src/app/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-
-/diameter_gen_*.erl
-/diameter_gen_*.hrl
-/depend.mk
-/diameter.mk
-
diff --git a/lib/diameter/src/app/Makefile b/lib/diameter/src/app/Makefile
deleted file mode 100644
index a75c70d71c..0000000000
--- a/lib/diameter/src/app/Makefile
+++ /dev/null
@@ -1,212 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-#
-#
-
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-else
-include $(DIAMETER_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(DIAMETER_TOP)/make/$(TARGET)/rules.mk
-endif
-
-
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-
-include ../../vsn.mk
-
-VSN=$(DIAMETER_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-
-RELSYSDIR = $(RELEASE_PATH)/lib/diameter-$(VSN)
-
-INCDIR = ../../include
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
-include modules.mk
-
-SPEC_MODULES = \
- $(SPEC_FILES:%.dia=%)
-
-SPEC_ERL_FILES = \
- $(SPEC_FILES:%.dia=%.erl)
-
-SPEC_HRL_FILES = \
- $(SPEC_FILES:%.dia=%.hrl)
-
-MODULES = \
- $(RUNTIME_MODULES) \
- $(HELP_MODULES)
-
-APP_MODULES = \
- $(RUNTIME_MODULES) \
- $(SPEC_MODULES)
-
-TARGET_MODULES = \
- $(APP_MODULES) \
- $(HELP_MODULES)
-
-TARGET_FILES = \
- $(TARGET_MODULES:%=$(EBIN)/%.$(EMULATOR)) \
- $(APP_TARGET) \
- $(APPUP_TARGET)
-
-ESCRIPT_FILES = \
- ../../bin/diameterc
-
-APP_FILE = diameter.app
-APP_SRC = $(APP_FILE).src
-APP_TARGET = $(EBIN)/$(APP_FILE)
-
-APPUP_FILE = diameter.appup
-APPUP_SRC = $(APPUP_FILE).src
-APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-
-ifeq ($(TYPE),debug)
-ERL_COMPILE_FLAGS += -Ddebug
-endif
-
-include diameter.mk
-
-ERL_COMPILE_FLAGS += \
- $(DIAMETER_ERL_COMPILE_FLAGS) \
- -I$(INCDIR)
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug:
- @$(MAKE) TYPE=debug opt
-
-opt: $(TARGET_FILES)
-
-clean:
- rm -f $(TARGET_FILES) $(SPEC_ERL_FILES) $(SPEC_HRL_FILES)
- rm -f $(APP_TARGET) $(APPUP_TARGET)
- rm -f errs core *~ diameter_gen_*.forms diameter_gen_*.spec
- rm -f depend.mk
-
-docs:
-
-info:
- @echo ""
- @echo "SPEC_FILES = $(FILES)"
- @echo "MODULES = $(MODULES)"
- @echo ""
- @echo "EXTERNAL_HRL_FILES = $(EXTERNAL_HRL_FILES)"
- @echo "INTERNAL_HRL_FILES = $(INTERNAL_HRL_FILES)"
- @echo ""
- @echo "EXAMPLE_FILES = $(EXAMPLE_FILES)"
- @echo ""
-
-# ----------------------------------------------------
-# Special Build Targets
-# ----------------------------------------------------
-
-# Generate the app file and then modules into in. This shouldn't know
-# about ../transport but good enough for now.
-$(APP_TARGET): $(APP_SRC) \
- ../../vsn.mk \
- modules.mk \
- ../transport/modules.mk
- sed -e 's;%VSN%;$(VSN);' $< > $@
- M=`echo $(APP_MODULES) | sed -e 's/^ *//' -e 's/ *$$//' -e 'y/ /,/'`; \
- echo "/%APP_MODULES%/s//$$M/;w;q" | tr ';' '\n' \
- | ed -s $@
- $(MAKE) -C ../transport $(APP_TARGET) APP_TARGET=$(APP_TARGET)
-
-$(APPUP_TARGET): $(APPUP_SRC) ../../vsn.mk
- sed -e 's;%VSN%;$(VSN);' $< > $@
-
-compiler:
- $(MAKE) -C ../$@
-
-app: $(APP_TARGET) $(APPUP_TARGET)
-
-# erl/hrl from application spec
-diameter_gen_%.erl diameter_gen_%.hrl: diameter_gen_%.dia
- ../../bin/diameterc -i $(EBIN) -o $(@D) $<
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/otp_release_targets.mk
-else
-include $(DIAMETER_TOP)/make/release_targets.mk
-endif
-
-release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/bin
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
- $(INSTALL_DIR) $(RELSYSDIR)/src/app
- $(INSTALL_DIR) $(RELSYSDIR)/include
- $(INSTALL_DIR) $(RELSYSDIR)/examples
- $(INSTALL_SCRIPT) $(ESCRIPT_FILES) $(RELSYSDIR)/bin
- $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
- $(INSTALL_DATA) $(MODULES:%=%.erl) $(SPEC_ERL_FILES) $(RELSYSDIR)/src/app
- $(INSTALL_DATA) $(SPEC_FILES) $(RELSYSDIR)/src/app
- $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src/app
- $(INSTALL_DATA) $(EXTERNAL_HRL_FILES) $(SPEC_HRL_FILES) $(RELSYSDIR)/include
- $(INSTALL_DATA) $(EXAMPLE_FILES) $(RELSYSDIR)/examples
-
-release_docs_spec:
-
-# ----------------------------------------------------
-# Dependencies
-# ----------------------------------------------------
-
-$(SPEC_MODULES:%=$(EBIN)/%.$(EMULATOR)): \
- $(EBIN)/diameter_exprecs.$(EMULATOR) \
- $(DIAMETER_TOP)/include/diameter.hrl \
- $(DIAMETER_TOP)/include/diameter_gen.hrl
-
-depend: depend.mk
-
-# Generate dependencies makefile. It's assumed that the compile target
-# has already been made since it's currently not smart enough to not
-# force a rebuild of those beams dependent on generated hrls, and this
-# is a no-no at make release.
-depend.mk: depend.sed $(MODULES:%=%.erl) Makefile
- (for f in $(MODULES); do \
- sed -f $< $$f.erl | sed "s@/@/$$f@"; \
- done) \
- > $@
-
--include depend.mk
-
-.PRECIOUS: $(SPEC_ERL_FILES) $(SPEC_HRL_FILES)
-.PHONY: app clean debug depend info opt compiler release_spec release_docs_spec
diff --git a/lib/diameter/src/app/diameter.appup.src b/lib/diameter/src/app/diameter.appup.src
deleted file mode 100644
index 6d8ceadb92..0000000000
--- a/lib/diameter/src/app/diameter.appup.src
+++ /dev/null
@@ -1,47 +0,0 @@
-%% This is an -*- erlang -*- file.
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-{"%VSN%",
- [
- {"0.9",
- [
- {load_module, diameter, soft_purge, soft_purge, []},
- {load_module, diameter_capx, soft_purge, soft_purge, []},
- {load_module, diameter_codec, soft_purge, soft_purge, [diameter_lib]},
- {load_module, diameter_lib, soft_purge, soft_purge, []},
- {load_module, diameter_types, soft_purge, soft_purge, []},
- {load_module, diameter_gen_base_accounting, soft_purge, soft_purge, []},
- {load_module, diameter_gen_base_rfc3588, soft_purge, soft_purge, []},
- {load_module, diameter_gen_relay, soft_purge, soft_purge, []},
- {update, diameter_service, soft, soft_purge, soft_purge, [diameter_lib]},
- {update, diameter_config, soft, soft_purge, soft_purge, []},
- {update, diameter_peer, soft, soft_purge, soft_purge, []},
- {update, diameter_peer_fsm, soft, soft_purge, soft_purge, [diameter_lib]},
- {update, diameter_reg, soft, soft_purge, soft_purge, []},
- {update, diameter_sctp, soft, soft_purge, soft_purge, []},
- {update, diameter_stats, soft, soft_purge, soft_purge, []},
- {update, diameter_sync, soft, soft_purge, soft_purge, []},
- {update, diameter_watchdog, soft, soft_purge, soft_purge, [diameter_lib]}
- ]
- }
- ],
- [
- ]
-}.
diff --git a/lib/diameter/src/app/diameter.erl b/lib/diameter/src/app/diameter.erl
deleted file mode 100644
index 2f721421d8..0000000000
--- a/lib/diameter/src/app/diameter.erl
+++ /dev/null
@@ -1,190 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
--module(diameter).
-
-%% Configuration.
--export([start_service/2,
- stop_service/1,
- add_transport/2,
- remove_transport/2,
- subscribe/1,
- unsubscribe/1]).
-
-%% Traffic.
--export([session_id/1,
- origin_state_id/0,
- call/3,
- call/4]).
-
-%% Information.
--export([services/0,
- service_info/2]).
-
-%% Start/stop the application. In a "real" application this should
-%% typically be a consequence of specifying diameter in a release file
-%% rather than by calling start/stop explicitly.
--export([start/0,
- stop/0]).
-
--include("diameter_internal.hrl").
--include("diameter_types.hrl").
-
-%%% --------------------------------------------------------------------------
-%%% start/0
-%%% --------------------------------------------------------------------------
-
--spec start()
- -> ok
- | {error, term()}.
-
-start() ->
- application:start(?APPLICATION).
-
-%%% --------------------------------------------------------------------------
-%%% stop/0
-%%% --------------------------------------------------------------------------
-
--spec stop()
- -> ok
- | {error, term()}.
-
-stop() ->
- application:stop(?APPLICATION).
-
-%%% --------------------------------------------------------------------------
-%%% start_service/2
-%%% --------------------------------------------------------------------------
-
--spec start_service(service_name(), [service_opt()])
- -> ok
- | {error, term()}.
-
-start_service(SvcName, Opts)
- when is_list(Opts) ->
- diameter_config:start_service(SvcName, Opts).
-
-%%% --------------------------------------------------------------------------
-%%% stop_service/1
-%%% --------------------------------------------------------------------------
-
--spec stop_service(service_name())
- -> ok
- | {error, term()}.
-
-stop_service(SvcName) ->
- diameter_config:stop_service(SvcName).
-
-%%% --------------------------------------------------------------------------
-%%% services/0
-%%% --------------------------------------------------------------------------
-
--spec services()
- -> [service_name()].
-
-services() ->
- [Name || {Name, _} <- diameter_service:services()].
-
-%%% --------------------------------------------------------------------------
-%%% service_info/2
-%%% --------------------------------------------------------------------------
-
--spec service_info(service_name(), atom() | [atom()])
- -> any().
-
-service_info(SvcName, Option) ->
- diameter_service:info(SvcName, Option).
-
-%%% --------------------------------------------------------------------------
-%%% add_transport/3
-%%% --------------------------------------------------------------------------
-
--spec add_transport(service_name(), {listen|connect, [transport_opt()]})
- -> {ok, transport_ref()}
- | {error, term()}.
-
-add_transport(SvcName, {T, Opts} = Cfg)
- when is_list(Opts), (T == connect orelse T == listen) ->
- diameter_config:add_transport(SvcName, Cfg).
-
-%%% --------------------------------------------------------------------------
-%%% remove_transport/2
-%%% --------------------------------------------------------------------------
-
--spec remove_transport(service_name(), transport_pred())
- -> ok | {error, term()}.
-
-remove_transport(SvcName, Pred) ->
- diameter_config:remove_transport(SvcName, Pred).
-
-%%% --------------------------------------------------------------------------
-%%% # subscribe(SvcName)
-%%%
-%%% Description: Subscribe to #diameter_event{} messages for the specified
-%%% service.
-%%% --------------------------------------------------------------------------
-
--spec subscribe(service_name())
- -> true.
-
-subscribe(SvcName) ->
- diameter_service:subscribe(SvcName).
-
-%%% --------------------------------------------------------------------------
-%%% # unsubscribe(SvcName)
-%%% --------------------------------------------------------------------------
-
--spec unsubscribe(service_name())
- -> true.
-
-unsubscribe(SvcName) ->
- diameter_service:unsubscribe(SvcName).
-
-%%% ----------------------------------------------------------
-%%% # session_id/1
-%%% ----------------------------------------------------------
-
--spec session_id('DiameterIdentity'())
- -> 'OctetString'().
-
-session_id(Ident) ->
- diameter_session:session_id(Ident).
-
-%%% ----------------------------------------------------------
-%%% # origin_state_id/0
-%%% ----------------------------------------------------------
-
--spec origin_state_id()
- -> 'Unsigned32'().
-
-origin_state_id() ->
- diameter_session:origin_state_id().
-
-%%% --------------------------------------------------------------------------
-%%% # call/[34]
-%%% --------------------------------------------------------------------------
-
--spec call(service_name(), app_alias(), any(), [call_opt()])
- -> any().
-
-call(SvcName, App, Message, Options) ->
- diameter_service:call(SvcName, {alias, App}, Message, Options).
-
-call(SvcName, App, Message) ->
- call(SvcName, App, Message, []).
diff --git a/lib/diameter/src/app/diameter.mk.in b/lib/diameter/src/app/diameter.mk.in
deleted file mode 100644
index c161064303..0000000000
--- a/lib/diameter/src/app/diameter.mk.in
+++ /dev/null
@@ -1,47 +0,0 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
-
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-
-DIAMETER_TOP = @DIAMETER_TOP@
-
-# ifneq ($(PREFIX),)
-# ifeq ($(TESTROOT),)
-# TESTROOT = $(PREFIX)
-# endif
-# endif
-
-ifeq ($(USE_DIAMETER_TEST_CODE), true)
-ERL_COMPILE_FLAGS += -DDIAMETER_TEST_CODE=mona_lisa_spelar_doom
-endif
-
-ifeq ($(USE_DIAMETER_HIPE), true)
-ERL_COMPILE_FLAGS += +native
-endif
-
-ifeq ($(WARN_UNUSED_WARS), true)
-ERL_COMPILE_FLAGS += +warn_unused_vars
-endif
-
-DIAMETER_APP_VSN_COMPILE_FLAGS = \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
-
-DIAMETER_ERL_COMPILE_FLAGS += \
- -pa $(DIAMETER_TOP)/ebin \
- $(DIAMETER_APP_VSN_COMPILE_FLAGS)
-
diff --git a/lib/diameter/src/app/diameter_callback.erl b/lib/diameter/src/app/diameter_callback.erl
deleted file mode 100644
index 6d5c8cdca1..0000000000
--- a/lib/diameter/src/app/diameter_callback.erl
+++ /dev/null
@@ -1,91 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% A minimal application callback module.
-%%
-
--module(diameter_callback).
-
--export([peer_up/3,
- peer_down/3,
- pick_peer/4,
- prepare_request/3,
- prepare_retransmit/3,
- handle_request/3,
- handle_answer/4,
- handle_error/4]).
-
--include_lib("diameter/include/diameter.hrl").
-
-%%% ----------------------------------------------------------
-%%% # peer_up/3
-%%% ----------------------------------------------------------
-
-peer_up(_Svc, _Peer, State) ->
- State.
-
-%%% ----------------------------------------------------------
-%%% # peer_down/3
-%%% ----------------------------------------------------------
-
-peer_down(_SvcName, _Peer, State) ->
- State.
-
-%%% ----------------------------------------------------------
-%%% # pick_peer/4
-%%% ----------------------------------------------------------
-
-pick_peer([Peer|_], _, _SvcName, _State) ->
- {ok, Peer}.
-
-%%% ----------------------------------------------------------
-%%% # prepare_request/3
-%%% ----------------------------------------------------------
-
-prepare_request(Pkt, _SvcName, _Peer) ->
- {send, Pkt}.
-
-%%% ----------------------------------------------------------
-%%% # prepare_retransmit/3
-%%% ----------------------------------------------------------
-
-prepare_retransmit(Pkt, _SvcName, _Peer) ->
- {send, Pkt}.
-
-%%% ----------------------------------------------------------
-%%% # handle_request/3
-%%% ----------------------------------------------------------
-
-handle_request(_Pkt, _SvcName, _Peer) ->
- {protocol_error, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
-
-%%% ----------------------------------------------------------
-%%% # handle_answer/4
-%%% ----------------------------------------------------------
-
-handle_answer(#diameter_packet{msg = Ans}, _Req, _SvcName, _Peer) ->
- Ans.
-
-%%% ---------------------------------------------------------------------------
-%%% # handle_error/4
-%%% ---------------------------------------------------------------------------
-
-handle_error(Reason, _Req, _SvcName, _Peer) ->
- {error, Reason}.
diff --git a/lib/diameter/src/app/diameter_gen_base_accounting.dia b/lib/diameter/src/app/diameter_gen_base_accounting.dia
deleted file mode 100644
index 64e95dddb5..0000000000
--- a/lib/diameter/src/app/diameter_gen_base_accounting.dia
+++ /dev/null
@@ -1,68 +0,0 @@
-;;
-;; %CopyrightBegin%
-;;
-;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
-;;
-;; The contents of this file are subject to the Erlang Public License,
-;; Version 1.1, (the "License"); you may not use this file except in
-;; compliance with the License. You should have received a copy of the
-;; Erlang Public License along with this software. If not, it can be
-;; retrieved online at http://www.erlang.org/.
-;;
-;; Software distributed under the License is distributed on an "AS IS"
-;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-;; the License for the specific language governing rights and limitations
-;; under the License.
-;;
-;; %CopyrightEnd%
-;;
-
-@id 3
-@prefix diameter_base_accounting
-@vendor 0 IETF
-
-@inherits diameter_gen_base_rfc3588
-
-@messages
-
- ACR ::= < Diameter Header: 271, REQ, PXY >
- < Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Destination-Realm }
- { Accounting-Record-Type }
- { Accounting-Record-Number }
- [ Acct-Application-Id ]
- [ Vendor-Specific-Application-Id ]
- [ User-Name ]
- [ Accounting-Sub-Session-Id ]
- [ Acct-Session-Id ]
- [ Acct-Multi-Session-Id ]
- [ Acct-Interim-Interval ]
- [ Accounting-Realtime-Required ]
- [ Origin-State-Id ]
- [ Event-Timestamp ]
- * [ Proxy-Info ]
- * [ Route-Record ]
- * [ AVP ]
-
- ACA ::= < Diameter Header: 271, PXY >
- < Session-Id >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- { Accounting-Record-Type }
- { Accounting-Record-Number }
- [ Acct-Application-Id ]
- [ Vendor-Specific-Application-Id ]
- [ User-Name ]
- [ Accounting-Sub-Session-Id ]
- [ Acct-Session-Id ]
- [ Acct-Multi-Session-Id ]
- [ Error-Reporting-Host ]
- [ Acct-Interim-Interval ]
- [ Accounting-Realtime-Required ]
- [ Origin-State-Id ]
- [ Event-Timestamp ]
- * [ Proxy-Info ]
- * [ AVP ]
diff --git a/lib/diameter/src/app/diameter_gen_base_rfc3588.dia b/lib/diameter/src/app/diameter_gen_base_rfc3588.dia
deleted file mode 100644
index 4a12e21acd..0000000000
--- a/lib/diameter/src/app/diameter_gen_base_rfc3588.dia
+++ /dev/null
@@ -1,413 +0,0 @@
-;;
-;; %CopyrightBegin%
-;;
-;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
-;;
-;; The contents of this file are subject to the Erlang Public License,
-;; Version 1.1, (the "License"); you may not use this file except in
-;; compliance with the License. You should have received a copy of the
-;; Erlang Public License along with this software. If not, it can be
-;; retrieved online at http://www.erlang.org/.
-;;
-;; Software distributed under the License is distributed on an "AS IS"
-;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-;; the License for the specific language governing rights and limitations
-;; under the License.
-;;
-;; %CopyrightEnd%
-;;
-
-@id 0
-@prefix diameter_base
-@vendor 0 IETF
-
-@avp_types
-
- Acct-Interim-Interval 85 Unsigned32 M
- Accounting-Realtime-Required 483 Enumerated M
- Acct-Multi-Session-Id 50 UTF8String M
- Accounting-Record-Number 485 Unsigned32 M
- Accounting-Record-Type 480 Enumerated M
- Acct-Session-Id 44 OctetString M
- Accounting-Sub-Session-Id 287 Unsigned64 M
- Acct-Application-Id 259 Unsigned32 M
- Auth-Application-Id 258 Unsigned32 M
- Auth-Request-Type 274 Enumerated M
- Authorization-Lifetime 291 Unsigned32 M
- Auth-Grace-Period 276 Unsigned32 M
- Auth-Session-State 277 Enumerated M
- Re-Auth-Request-Type 285 Enumerated M
- Class 25 OctetString M
- Destination-Host 293 DiamIdent M
- Destination-Realm 283 DiamIdent M
- Disconnect-Cause 273 Enumerated M
- E2E-Sequence 300 Grouped M
- Error-Message 281 UTF8String -
- Error-Reporting-Host 294 DiamIdent -
- Event-Timestamp 55 Time M
- Experimental-Result 297 Grouped M
- Experimental-Result-Code 298 Unsigned32 M
- Failed-AVP 279 Grouped M
- Firmware-Revision 267 Unsigned32 -
- Host-IP-Address 257 Address M
- Inband-Security-Id 299 Unsigned32 M
- Multi-Round-Time-Out 272 Unsigned32 M
- Origin-Host 264 DiamIdent M
- Origin-Realm 296 DiamIdent M
- Origin-State-Id 278 Unsigned32 M
- Product-Name 269 UTF8String -
- Proxy-Host 280 DiamIdent M
- Proxy-Info 284 Grouped M
- Proxy-State 33 OctetString M
- Redirect-Host 292 DiamURI M
- Redirect-Host-Usage 261 Enumerated M
- Redirect-Max-Cache-Time 262 Unsigned32 M
- Result-Code 268 Unsigned32 M
- Route-Record 282 DiamIdent M
- Session-Id 263 UTF8String M
- Session-Timeout 27 Unsigned32 M
- Session-Binding 270 Unsigned32 M
- Session-Server-Failover 271 Enumerated M
- Supported-Vendor-Id 265 Unsigned32 M
- Termination-Cause 295 Enumerated M
- User-Name 1 UTF8String M
- Vendor-Id 266 Unsigned32 M
- Vendor-Specific-Application-Id 260 Grouped M
-
-@messages
-
- CER ::= < Diameter Header: 257, REQ >
- { Origin-Host }
- { Origin-Realm }
- 1* { Host-IP-Address }
- { Vendor-Id }
- { Product-Name }
- [ Origin-State-Id ]
- * [ Supported-Vendor-Id ]
- * [ Auth-Application-Id ]
- * [ Inband-Security-Id ]
- * [ Acct-Application-Id ]
- * [ Vendor-Specific-Application-Id ]
- [ Firmware-Revision ]
- * [ AVP ]
-
- CEA ::= < Diameter Header: 257 >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- 1* { Host-IP-Address }
- { Vendor-Id }
- { Product-Name }
- [ Origin-State-Id ]
- [ Error-Message ]
- * [ Failed-AVP ]
- * [ Supported-Vendor-Id ]
- * [ Auth-Application-Id ]
- * [ Inband-Security-Id ]
- * [ Acct-Application-Id ]
- * [ Vendor-Specific-Application-Id ]
- [ Firmware-Revision ]
- * [ AVP ]
-
- DPR ::= < Diameter Header: 282, REQ >
- { Origin-Host }
- { Origin-Realm }
- { Disconnect-Cause }
-
- DPA ::= < Diameter Header: 282 >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- [ Error-Message ]
- * [ Failed-AVP ]
-
- DWR ::= < Diameter Header: 280, REQ >
- { Origin-Host }
- { Origin-Realm }
- [ Origin-State-Id ]
-
- DWA ::= < Diameter Header: 280 >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- [ Error-Message ]
- * [ Failed-AVP ]
- [ Origin-State-Id ]
-
- answer-message ::= < Diameter Header: code, ERR [PXY] >
- 0*1< Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Result-Code }
- [ Origin-State-Id ]
- [ Error-Reporting-Host ]
- [ Proxy-Info ]
- * [ AVP ]
-
- RAR ::= < Diameter Header: 258, REQ, PXY >
- < Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Destination-Realm }
- { Destination-Host }
- { Auth-Application-Id }
- { Re-Auth-Request-Type }
- [ User-Name ]
- [ Origin-State-Id ]
- * [ Proxy-Info ]
- * [ Route-Record ]
- * [ AVP ]
-
- RAA ::= < Diameter Header: 258, PXY >
- < Session-Id >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- [ User-Name ]
- [ Origin-State-Id ]
- [ Error-Message ]
- [ Error-Reporting-Host ]
- * [ Failed-AVP ]
- * [ Redirect-Host ]
- [ Redirect-Host-Usage ]
- [ Redirect-Max-Cache-Time ]
- * [ Proxy-Info ]
- * [ AVP ]
-
- STR ::= < Diameter Header: 275, REQ, PXY >
- < Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Destination-Realm }
- { Auth-Application-Id }
- { Termination-Cause }
- [ User-Name ]
- [ Destination-Host ]
- * [ Class ]
- [ Origin-State-Id ]
- * [ Proxy-Info ]
- * [ Route-Record ]
- * [ AVP ]
-
- STA ::= < Diameter Header: 275, PXY >
- < Session-Id >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- [ User-Name ]
- * [ Class ]
- [ Error-Message ]
- [ Error-Reporting-Host ]
- * [ Failed-AVP ]
- [ Origin-State-Id ]
- * [ Redirect-Host ]
- [ Redirect-Host-Usage ]
- [ Redirect-Max-Cache-Time ]
- * [ Proxy-Info ]
- * [ AVP ]
-
- ASR ::= < Diameter Header: 274, REQ, PXY >
- < Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Destination-Realm }
- { Destination-Host }
- { Auth-Application-Id }
- [ User-Name ]
- [ Origin-State-Id ]
- * [ Proxy-Info ]
- * [ Route-Record ]
- * [ AVP ]
-
- ASA ::= < Diameter Header: 274, PXY >
- < Session-Id >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- [ User-Name ]
- [ Origin-State-Id ]
- [ Error-Message ]
- [ Error-Reporting-Host ]
- * [ Failed-AVP ]
- * [ Redirect-Host ]
- [ Redirect-Host-Usage ]
- [ Redirect-Max-Cache-Time ]
- * [ Proxy-Info ]
- * [ AVP ]
-
- ACR ::= < Diameter Header: 271, REQ, PXY >
- < Session-Id >
- { Origin-Host }
- { Origin-Realm }
- { Destination-Realm }
- { Accounting-Record-Type }
- { Accounting-Record-Number }
- [ Acct-Application-Id ]
- [ Vendor-Specific-Application-Id ]
- [ User-Name ]
- [ Accounting-Sub-Session-Id ]
- [ Acct-Session-Id ]
- [ Acct-Multi-Session-Id ]
- [ Acct-Interim-Interval ]
- [ Accounting-Realtime-Required ]
- [ Origin-State-Id ]
- [ Event-Timestamp ]
- * [ Proxy-Info ]
- * [ Route-Record ]
- * [ AVP ]
-
- ACA ::= < Diameter Header: 271, PXY >
- < Session-Id >
- { Result-Code }
- { Origin-Host }
- { Origin-Realm }
- { Accounting-Record-Type }
- { Accounting-Record-Number }
- [ Acct-Application-Id ]
- [ Vendor-Specific-Application-Id ]
- [ User-Name ]
- [ Accounting-Sub-Session-Id ]
- [ Acct-Session-Id ]
- [ Acct-Multi-Session-Id ]
- [ Error-Reporting-Host ]
- [ Acct-Interim-Interval ]
- [ Accounting-Realtime-Required ]
- [ Origin-State-Id ]
- [ Event-Timestamp ]
- * [ Proxy-Info ]
- * [ AVP ]
-
-@enum Disconnect-Cause
-
- REBOOTING 0
- BUSY 1
- DO_NOT_WANT_TO_TALK_TO_YOU 2
-
-@enum Redirect-Host-Usage
-
- DONT_CACHE 0
- ALL_SESSION 1
- ALL_REALM 2
- REALM_AND_APPLICATION 3
- ALL_APPLICATION 4
- ALL_HOST 5
- ALL_USER 6
-
-@enum Auth-Request-Type
-
- AUTHENTICATE_ONLY 1
- AUTHORIZE_ONLY 2
- AUTHORIZE_AUTHENTICATE 3
-
-@enum Auth-Session-State
-
- STATE_MAINTAINED 0
- NO_STATE_MAINTAINED 1
-
-@enum Re-Auth-Request-Type
-
- AUTHORIZE_ONLY 0
- AUTHORIZE_AUTHENTICATE 1
-
-@enum Termination-Cause
-
- DIAMETER_LOGOUT 1
- DIAMETER_SERVICE_NOT_PROVIDED 2
- DIAMETER_BAD_ANSWER 3
- DIAMETER_ADMINISTRATIVE 4
- DIAMETER_LINK_BROKEN 5
- DIAMETER_AUTH_EXPIRED 6
- DIAMETER_USER_MOVED 7
- DIAMETER_SESSION_TIMEOUT 8
-
-@enum Session-Server-Failover
-
- REFUSE_SERVICE 0
- TRY_AGAIN 1
- ALLOW_SERVICE 2
- TRY_AGAIN_ALLOW_SERVICE 3
-
-@enum Accounting-Record-Type
-
- EVENT_RECORD 1
- START_RECORD 2
- INTERIM_RECORD 3
- STOP_RECORD 4
-
-@enum Accounting-Realtime-Required
-
- DELIVER_AND_GRANT 1
- GRANT_AND_STORE 2
- GRANT_AND_LOSE 3
-
-@result_code Result-Code
-
-;; 7.1.1. Informational
- DIAMETER_MULTI_ROUND_AUTH 1001
-
-;; 7.1.2. Success
- DIAMETER_SUCCESS 2001
- DIAMETER_LIMITED_SUCCESS 2002
-
-;; 7.1.3. Protocol Errors
- DIAMETER_COMMAND_UNSUPPORTED 3001
- DIAMETER_UNABLE_TO_DELIVER 3002
- DIAMETER_REALM_NOT_SERVED 3003
- DIAMETER_TOO_BUSY 3004
- DIAMETER_LOOP_DETECTED 3005
- DIAMETER_REDIRECT_INDICATION 3006
- DIAMETER_APPLICATION_UNSUPPORTED 3007
- DIAMETER_INVALID_HDR_BITS 3008
- DIAMETER_INVALID_AVP_BITS 3009
- DIAMETER_UNKNOWN_PEER 3010
-
-;; 7.1.4. Transient Failures
- DIAMETER_AUTHENTICATION_REJECTED 4001
- DIAMETER_OUT_OF_SPACE 4002
- ELECTION_LOST 4003
-
-;; 7.1.5. Permanent Failures
- DIAMETER_AVP_UNSUPPORTED 5001
- DIAMETER_UNKNOWN_SESSION_ID 5002
- DIAMETER_AUTHORIZATION_REJECTED 5003
- DIAMETER_INVALID_AVP_VALUE 5004
- DIAMETER_MISSING_AVP 5005
- DIAMETER_RESOURCES_EXCEEDED 5006
- DIAMETER_CONTRADICTING_AVPS 5007
- DIAMETER_AVP_NOT_ALLOWED 5008
- DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
- DIAMETER_NO_COMMON_APPLICATION 5010
- DIAMETER_UNSUPPORTED_VERSION 5011
- DIAMETER_UNABLE_TO_COMPLY 5012
- DIAMETER_INVALID_BIT_IN_HEADER 5013
- DIAMETER_INVALID_AVP_LENGTH 5014
- DIAMETER_INVALID_MESSAGE_LENGTH 5015
- DIAMETER_INVALID_AVP_BIT_COMBO 5016
- DIAMETER_NO_COMMON_SECURITY 5017
-
-@grouped
-
- Proxy-Info ::= < AVP Header: 284 >
- { Proxy-Host }
- { Proxy-State }
- * [ AVP ]
-
- Failed-AVP ::= < AVP Header: 279 >
- 1* {AVP}
-
- Experimental-Result ::= < AVP Header: 297 >
- { Vendor-Id }
- { Experimental-Result-Code }
-
- Vendor-Specific-Application-Id ::= < AVP Header: 260 >
- 1* { Vendor-Id }
- [ Auth-Application-Id ]
- [ Acct-Application-Id ]
-
-;; The E2E-Sequence AVP is defined in RFC 3588 as Grouped, but
-;; there is no definition of the group - only an informal text stating
-;; that there should be a nonce (an OctetString) and a counter
-;; (integer)
-;;
- E2E-Sequence ::= <AVP Header: 300 >
- 2* { AVP }
diff --git a/lib/diameter/src/app/diameter_peer_fsm.erl b/lib/diameter/src/app/diameter_peer_fsm.erl
deleted file mode 100644
index 0252fb3809..0000000000
--- a/lib/diameter/src/app/diameter_peer_fsm.erl
+++ /dev/null
@@ -1,746 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% This module implements (as a process) the RFC 3588 Peer State
-%% Machine modulo the necessity of adapting the peer election to the
-%% fact that we don't know the identity of a peer until we've
-%% received a CER/CEA from it.
-%%
-
--module(diameter_peer_fsm).
--behaviour(gen_server).
-
-%% Interface towards diameter_watchdog.
--export([start/3]).
-
-%% gen_server callbacks
--export([init/1,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- terminate/2,
- code_change/3]).
-
-%% diameter_peer_fsm_sup callback
--export([start_link/1]).
-
-%% internal callbacks
--export([match/1]).
-
--include_lib("diameter/include/diameter.hrl").
--include("diameter_internal.hrl").
--include("diameter_types.hrl").
--include("diameter_gen_base_rfc3588.hrl").
-
--define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU').
--define(REBOOT, ?'DIAMETER_BASE_DISCONNECT-CAUSE_REBOOTING').
-
--define(LOOP_TIMEOUT, 2000).
-
-%% RFC 3588:
-%%
-%% Timeout An application-defined timer has expired while waiting
-%% for some event.
-%%
--define(EVENT_TIMEOUT, 10000).
-
-%% How long to wait for a DPA in response to DPR before simply
-%% aborting. Used to distinguish between shutdown and not but there's
-%% not really any need. Stopping a service will require a timeout if
-%% the peer doesn't answer DPR so the value should be short-ish.
--define(DPA_TIMEOUT, 1000).
-
--record(state,
- {state = 'Wait-Conn-Ack' %% state of RFC 3588 Peer State Machine
- :: 'Wait-Conn-Ack' | recv_CER | 'Wait-CEA' | 'Open',
- mode :: accept | connect | {connect, reference()},
- parent :: pid(),
- transport :: pid(),
- service :: #diameter_service{},
- dpr = false :: false | {'Unsigned32'(), 'Unsigned32'()}}).
- %% | hop by hop and end to end identifiers
-
-%% There are non-3588 states possible as a consequence of 5.6.1 of the
-%% standard and the corresponding problem for incoming CEA's: we don't
-%% know who we're talking to until either a CER or CEA has been
-%% received. The CEA problem in particular makes it impossible to
-%% follow the state machine exactly as documented in 3588: there can
-%% be no election until the CEA arrives and we have an Origin-Host to
-%% elect.
-
-%%
-%% Once upon a time start/2 started a process akin to that started by
-%% start/3 below, which in turn started a watchdog/transport process
-%% with the result that the watchdog could send DWR/DWA regardless of
-%% whether or not the corresponding Peer State Machine was in its open
-%% state; that is, before capabilities exchange had taken place. This
-%% is not what RFC's 3588 and 3539 say (albeit not very clearly).
-%% Watchdog messages are only exchanged on *open* connections, so the
-%% 3539 state machine is more naturally placed on top of the 3588 Peer
-%% State Machine rather than closer to the transport. This is what we
-%% now do below: connect/accept call diameter_watchdog and return the
-%% pid of the watchdog process, and the watchdog in turn calls start/3
-%% below to start the process implementing the Peer State Machine. The
-%% former is a "peer" in diameter_service while the latter is a
-%% "conn". In a sense, diameter_service sees the watchdog as
-%% implementing the Peer State Machine and the process implemented
-%% here as being the transport, not being aware of the watchdog at
-%% all.
-%%
-
-%%% ---------------------------------------------------------------------------
-%%% # start({connect|accept, Ref}, Opts, Service)
-%%%
-%%% Output: Pid
-%%% ---------------------------------------------------------------------------
-
-%% diameter_config requires a non-empty list of applications on the
-%% service but diameter_service then constrains the list to any
-%% specified on the transport in question. Check here that the list is
-%% still non-empty.
-
-start({_, Ref} = Type, Opts, #diameter_service{applications = Apps} = Svc) ->
- [] /= Apps orelse ?ERROR({no_apps, Type, Opts}),
- T = {self(), Type, Opts, Svc},
- {ok, Pid} = diameter_peer_fsm_sup:start_child(T),
- diameter_stats:reg(Pid, Ref),
- Pid.
-
-start_link(T) ->
- {ok, _} = proc_lib:start_link(?MODULE,
- init,
- [T],
- infinity,
- diameter_lib:spawn_opts(server, [])).
-
-%%% ---------------------------------------------------------------------------
-%%% ---------------------------------------------------------------------------
-
-%% init/1
-
-init(T) ->
- proc_lib:init_ack({ok, self()}),
- gen_server:enter_loop(?MODULE, [], i(T)).
-
-i({WPid, {M, _} = T, Opts, #diameter_service{capabilities = Caps} = Svc0}) ->
- putr(dwa, dwa(Caps)),
- {ok, TPid, Svc} = start_transport(T, Opts, Svc0),
- erlang:monitor(process, TPid),
- erlang:monitor(process, WPid),
- #state{parent = WPid,
- transport = TPid,
- mode = M,
- service = Svc}.
-%% The transport returns its local ip addresses so that different
-%% transports on the same service can use different local addresses.
-%% The local addresses are put into Host-IP-Address avps here when
-%% sending capabilities exchange messages.
-%%
-%% Invalid transport config may cause us to crash but note that the
-%% watchdog start (start/2) succeeds regardless so as not to crash the
-%% service.
-
-start_transport(T, Opts, Svc) ->
- case diameter_peer:start(T, Opts, Svc) of
- {ok, TPid} ->
- {ok, TPid, Svc};
- {ok, TPid, [_|_] = Addrs} ->
- #diameter_service{capabilities = Caps0} = Svc,
- Caps = Caps0#diameter_caps{host_ip_address = Addrs},
- {ok, TPid, Svc#diameter_service{capabilities = Caps}};
- No ->
- exit({shutdown, No})
- end.
-
-%% handle_call/3
-
-handle_call(_, _, State) ->
- {reply, nok, State}.
-
-%% handle_cast/2
-
-handle_cast(_, State) ->
- {noreply, State}.
-
-%% handle_info/1
-
-handle_info(T, #state{} = State) ->
- try transition(T, State) of
- ok ->
- {noreply, State};
- #state{state = X} = S ->
- ?LOGC(X =/= State#state.state, transition, X),
- {noreply, S};
- {stop, Reason} ->
- ?LOG(stop, Reason),
- x(Reason, State);
- stop ->
- ?LOG(stop, T),
- x(T, State)
- catch
- throw: {?MODULE, close = C, Reason} ->
- ?LOG(C, {Reason, T}),
- x(Reason, State);
- throw: {?MODULE, abort, Reason} ->
- {stop, {shutdown, Reason}, State}
- end.
-
-x(Reason, #state{} = S) ->
- close_wd(Reason, S),
- {stop, {shutdown, Reason}, S}.
-
-%% terminate/2
-
-terminate(_, _) ->
- ok.
-
-%% code_change/3
-
-code_change(_, State, _) ->
- {ok, State}.
-
-%%% ---------------------------------------------------------------------------
-%%% ---------------------------------------------------------------------------
-
-putr(Key, Val) ->
- put({?MODULE, Key}, Val).
-
-getr(Key) ->
- get({?MODULE, Key}).
-
-%% transition/2
-
-%% Connection to peer.
-transition({diameter, {TPid, connected, Remote}},
- #state{state = PS,
- mode = M}
- = S) ->
- 'Wait-Conn-Ack' = PS, %% assert
- connect = M, %%
- send_CER(S#state{mode = {M, Remote},
- transport = TPid});
-
-%% Connection from peer.
-transition({diameter, {TPid, connected}},
- #state{state = PS,
- mode = M,
- parent = Pid}
- = S) ->
- 'Wait-Conn-Ack' = PS, %% assert
- accept = M, %%
- Pid ! {accepted, self()},
- start_timer(S#state{state = recv_CER,
- transport = TPid});
-
-%% Incoming message from the transport.
-transition({diameter, {recv, Pkt}}, S) ->
- recv(Pkt, S);
-
-%% Timeout when still in the same state ...
-transition({timeout, PS}, #state{state = PS}) ->
- stop;
-
-%% ... or not.
-transition({timeout, _}, _) ->
- ok;
-
-%% Outgoing message.
-transition({send, Msg}, #state{transport = TPid}) ->
- send(TPid, Msg),
- ok;
-
-%% Request for graceful shutdown.
-transition({shutdown, Pid}, #state{parent = Pid, dpr = false} = S) ->
- dpr(?GOAWAY, S);
-transition({shutdown, Pid}, #state{parent = Pid}) ->
- ok;
-
-%% Application shutdown.
-transition(shutdown, #state{dpr = false} = S) ->
- dpr(?REBOOT, S);
-transition(shutdown, _) -> %% DPR already send: ensure expected timeout
- dpa_timer(),
- ok;
-
-%% Request to close the transport connection.
-transition({close = T, Pid}, #state{parent = Pid,
- transport = TPid}
- = S) ->
- diameter_peer:close(TPid),
- close(T,S);
-
-%% DPA reception has timed out.
-transition(dpa_timeout, _) ->
- stop;
-
-%% Someone wants to know a resolved port: forward to the transport process.
-transition({resolve_port, _Pid} = T, #state{transport = TPid}) ->
- TPid ! T,
- ok;
-
-%% Parent or transport has died.
-transition({'DOWN', _, process, P, _},
- #state{parent = Pid,
- transport = TPid})
- when P == Pid;
- P == TPid ->
- stop;
-
-%% State query.
-transition({state, Pid}, #state{state = S, transport = TPid}) ->
- Pid ! {self(), [S, TPid]},
- ok.
-
-%% Crash on anything unexpected.
-
-%% send_CER/1
-
-send_CER(#state{mode = {connect, Remote},
- service = #diameter_service{capabilities = Caps},
- transport = TPid}
- = S) ->
- req_send_CER(Caps#diameter_caps.origin_host, Remote)
- orelse
- close(connected, S),
- CER = build_CER(S),
- ?LOG(send, 'CER'),
- send(TPid, encode(CER)),
- start_timer(S#state{state = 'Wait-CEA'}).
-
-%% Register ourselves as connecting to the remote endpoint in
-%% question. This isn't strictly necessary since a peer implementing
-%% the 3588 Peer State Machine should reject duplicate connection's
-%% from the same peer but there's little point in us setting up a
-%% duplicate connection in the first place. This could also include
-%% the transport protocol being used but since we're blind to
-%% transport just avoid duplicate connections to the same host/port.
-req_send_CER(OriginHost, Remote) ->
- register_everywhere({?MODULE, connection, OriginHost, {remote, Remote}}).
-
-%% start_timer/1
-
-start_timer(#state{state = PS} = S) ->
- erlang:send_after(?EVENT_TIMEOUT, self(), {timeout, PS}),
- S.
-
-%% build_CER/1
-
-build_CER(#state{service = #diameter_service{capabilities = Caps}}) ->
- {ok, CER} = diameter_capx:build_CER(Caps),
- CER.
-
-%% encode/1
-
-encode(Rec) ->
- #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Rec),
- Bin.
-
-%% recv/2
-
-%% RFC 3588 has result code 5015 for an invalid length but if a
-%% transport is detecting message boundaries using the length header
-%% then a length error will likely lead to further errors.
-
-recv(#diameter_packet{header = #diameter_header{length = Len}
- = Hdr,
- bin = Bin},
- S)
- when Len < 20;
- (0 /= Len rem 4 orelse bit_size(Bin) /= 8*Len) ->
- discard(invalid_message_length, recv, [size(Bin),
- bit_size(Bin) rem 8,
- Hdr,
- S]);
-
-recv(#diameter_packet{header = #diameter_header{} = Hdr}
- = Pkt,
- #state{parent = Pid}
- = S) ->
- Name = diameter_codec:msg_name(Hdr),
- Pid ! {recv, self(), Name, Pkt},
- diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received
- rcv(Name, Pkt, S);
-
-recv(#diameter_packet{header = undefined,
- bin = Bin}
- = Pkt,
- S) ->
- recv(Pkt#diameter_packet{header = diameter_codec:decode_header(Bin)}, S);
-
-recv(Bin, S)
- when is_binary(Bin) ->
- recv(#diameter_packet{bin = Bin}, S);
-
-recv(#diameter_packet{header = false} = Pkt, S) ->
- discard(truncated_header, recv, [Pkt, S]).
-
-msg_id({_,_,_} = T, _) ->
- T;
-msg_id(_, Hdr) ->
- diameter_codec:msg_id(Hdr).
-
-%% Treat invalid length as a transport error and die. Especially in
-%% the TCP case, in which there's no telling where the next message
-%% begins in the incoming byte stream, keeping a crippled connection
-%% alive may just make things worse.
-
-discard(Reason, F, A) ->
- diameter_stats:incr(Reason),
- diameter_lib:warning_report(Reason, {?MODULE, F, A}),
- throw({?MODULE, abort, Reason}).
-
-%% rcv/3
-
-%% Incoming CEA.
-rcv('CEA', Pkt, #state{state = 'Wait-CEA'} = S) ->
- handle_CEA(Pkt, S);
-
-%% Incoming CER
-rcv('CER' = N, Pkt, #state{state = recv_CER} = S) ->
- handle_request(N, Pkt, S);
-
-%% Anything but CER/CEA in a non-Open state is an error, as is
-%% CER/CEA in anything but recv_CER/Wait-CEA.
-rcv(Name, _, #state{state = PS} = S)
- when PS /= 'Open';
- Name == 'CER';
- Name == 'CEA' ->
- close({Name, PS}, S);
-
-rcv(N, Pkt, S)
- when N == 'DWR';
- N == 'DPR' ->
- handle_request(N, Pkt, S);
-
-%% DPA even though we haven't sent DPR: ignore.
-rcv('DPA', _Pkt, #state{dpr = false}) ->
- ok;
-
-%% DPA in response to DPR. We could check the sequence numbers but
-%% don't bother, just close.
-rcv('DPA' = N, _Pkt, #state{transport = TPid}) ->
- diameter_peer:close(TPid),
- {stop, N};
-
-rcv(_, _, _) ->
- ok.
-
-%% send/2
-
-%% Msg here could be a #diameter_packet or a binary depending on who's
-%% sending. In particular, the watchdog will send DWR as a binary
-%% while messages coming from clients will be in a #diameter_packet.
-send(Pid, Msg) ->
- diameter_stats:incr({diameter_codec:msg_id(Msg), send}),
- diameter_peer:send(Pid, Msg).
-
-%% handle_request/3
-
-handle_request(Type, #diameter_packet{} = Pkt, S) ->
- ?LOG(recv, Type),
- send_answer(Type, diameter_codec:decode(?BASE, Pkt), S).
-
-%% send_answer/3
-
-send_answer(Type, ReqPkt, #state{transport = TPid} = S) ->
- #diameter_packet{header = #diameter_header{version = V,
- end_to_end_id = Eid,
- hop_by_hop_id = Hid,
- is_proxiable = P},
- transport_data = TD}
- = ReqPkt,
-
- {Answer, PostF} = build_answer(Type, V, ReqPkt, S),
-
- Pkt = #diameter_packet{header = #diameter_header{version = V,
- end_to_end_id = Eid,
- hop_by_hop_id = Hid,
- is_proxiable = P},
- msg = Answer,
- transport_data = TD},
-
- send(TPid, diameter_codec:encode(?BASE, Pkt)),
- eval(PostF, S).
-
-eval([F|A], S) ->
- apply(F, A ++ [S]);
-eval(ok, S) ->
- S.
-
-%% build_answer/4
-
-build_answer('CER',
- ?DIAMETER_VERSION,
- #diameter_packet{msg = CER,
- header = #diameter_header{is_error = false},
- errors = []}
- = Pkt,
- #state{service = Svc}
- = S) ->
- #diameter_service{capabilities = #diameter_caps{origin_host = OH}}
- = Svc,
-
- {SupportedApps, #diameter_caps{origin_host = DH} = RCaps, CEA}
- = recv_CER(CER, S),
-
- try
- [] == SupportedApps
- andalso ?THROW({no_common_application, 5010}),
- register_everywhere({?MODULE, connection, OH, DH})
- orelse ?THROW({election_lost, 4003}),
- {CEA, [fun open/4, Pkt, SupportedApps, RCaps]}
- catch
- ?FAILURE({Reason, RC}) ->
- {answer('CER', S) ++ [{'Result-Code', RC}],
- [fun close/2, {'CER', Reason, DH}]}
- end;
-
-%% The error checks below are similar to those in diameter_service for
-%% other messages. Should factor out the commonality.
-
-build_answer(Type, V, #diameter_packet{header = H, errors = Es} = Pkt, S) ->
- FailedAvp = failed_avp([A || {_,A} <- Es]),
- Ans = answer(answer(Type, S), V, H, Es),
- {set(Ans, FailedAvp), if 'CER' == Type ->
- [fun close/2, {Type, V, Pkt}];
- true ->
- ok
- end}.
-
-failed_avp([] = No) ->
- No;
-failed_avp(Avps) ->
- [{'Failed-AVP', [[{'AVP', Avps}]]}].
-
-set(Ans, []) ->
- Ans;
-set(['answer-message' | _] = Ans, FailedAvp) ->
- Ans ++ [{'AVP', [FailedAvp]}];
-set([_|_] = Ans, FailedAvp) ->
- Ans ++ FailedAvp.
-
-answer([_, OH, OR | _], _, #diameter_header{is_error = true}, _) ->
- ['answer-message', OH, OR, {'Result-Code', 3008}];
-
-answer([_, OH, OR | _], _, _, [Bs|_])
- when is_bitstring(Bs) ->
- ['answer-message', OH, OR, {'Result-Code', 3009}];
-
-answer(Ans, ?DIAMETER_VERSION, _, Es) ->
- Ans ++ [{'Result-Code', rc(Es)}];
-
-answer(Ans, _, _, _) ->
- Ans ++ [{'Result-Code', 5011}]. %% DIAMETER_UNSUPPORTED_VERSION
-
-rc([]) ->
- 2001; %% DIAMETER_SUCCESS
-rc([{RC,_}|_]) ->
- RC;
-rc([RC|_]) ->
- RC.
-
-%% DIAMETER_INVALID_HDR_BITS 3008
-%% A request was received whose bits in the Diameter header were
-%% either set to an invalid combination, or to a value that is
-%% inconsistent with the command code's definition.
-
-%% DIAMETER_INVALID_AVP_BITS 3009
-%% A request was received that included an AVP whose flag bits are
-%% set to an unrecognized value, or that is inconsistent with the
-%% AVP's definition.
-
-%% ELECTION_LOST 4003
-%% The peer has determined that it has lost the election process and
-%% has therefore disconnected the transport connection.
-
-%% DIAMETER_NO_COMMON_APPLICATION 5010
-%% This error is returned when a CER message is received, and there
-%% are no common applications supported between the peers.
-
-%% DIAMETER_UNSUPPORTED_VERSION 5011
-%% This error is returned when a request was received, whose version
-%% number is unsupported.
-
-%% answer/2
-
-answer('DWR', _) ->
- getr(dwa);
-
-answer(Name, #state{service = #diameter_service{capabilities = Caps}}) ->
- a(Name, Caps).
-
-a('CER', #diameter_caps{vendor_id = Vid,
- origin_host = Host,
- origin_realm = Realm,
- host_ip_address = Addrs,
- product_name = Name}) ->
- ['CEA', {'Origin-Host', Host},
- {'Origin-Realm', Realm},
- {'Host-IP-Address', Addrs},
- {'Vendor-Id', Vid},
- {'Product-Name', Name}];
-
-a('DPR', #diameter_caps{origin_host = Host,
- origin_realm = Realm}) ->
- ['DPA', {'Origin-Host', Host},
- {'Origin-Realm', Realm}].
-
-%% recv_CER/2
-
-recv_CER(CER, #state{service = Svc}) ->
- {ok, T} = diameter_capx:recv_CER(CER, Svc),
- T.
-
-%% handle_CEA/1
-
-handle_CEA(#diameter_packet{header = #diameter_header{version = V},
- bin = Bin}
- = Pkt,
- #state{service = Svc}
- = S)
- when is_binary(Bin) ->
- ?LOG(recv, 'CEA'),
-
- ?DIAMETER_VERSION == V orelse close({version, V}, S),
-
- #diameter_packet{msg = CEA, errors = Errors}
- = DPkt
- = diameter_codec:decode(?BASE, Pkt),
-
- [] == Errors orelse close({errors, Errors}, S),
-
- {SApps, #diameter_caps{origin_host = DH} = RCaps} = recv_CEA(CEA, S),
-
- %% Ensure that we don't already have a connection to the peer in
- %% question. This isn't the peer election of 3588 except in the
- %% sense that, since we don't know who we're talking to until we
- %% receive a CER/CEA, the first that arrives wins the right to a
- %% connection with the peer.
-
- #diameter_service{capabilities = #diameter_caps{origin_host = OH}}
- = Svc,
-
- register_everywhere({?MODULE, connection, OH, DH})
- orelse
- close({'CEA', DH}, S),
-
- open(DPkt, SApps, RCaps, S).
-
-%% recv_CEA/2
-
-recv_CEA(CEA, #state{service = Svc} = S) ->
- case diameter_capx:recv_CEA(CEA, Svc) of
- {ok, {[], _}} ->
- close({'CEA', no_common_application}, S);
- {ok, T} ->
- T;
- {error, Reason} ->
- close({'CEA', Reason}, S)
- end.
-
-%% open/4
-
-open(Pkt, SupportedApps, RCaps, #state{parent = Pid,
- service = Svc}
- = S) ->
- #diameter_service{capabilities = #diameter_caps{origin_host = OH}
- = LCaps}
- = Svc,
- #diameter_caps{origin_host = DH}
- = RCaps,
- Pid ! {open, self(), {OH,DH}, {capz(LCaps, RCaps), SupportedApps, Pkt}},
- S#state{state = 'Open'}.
-
-capz(#diameter_caps{} = L, #diameter_caps{} = R) ->
- #diameter_caps{}
- = list_to_tuple([diameter_caps | lists:zip(tl(tuple_to_list(L)),
- tl(tuple_to_list(R)))]).
-
-%% close/2
-
-%% Tell the watchdog that our death isn't due to transport failure.
-close(Reason, #state{parent = Pid}) ->
- close_wd(Reason, Pid),
- throw({?MODULE, close, Reason}).
-
-%% close_wd/2
-
-%% Ensure the watchdog dies if DPR has been sent ...
-close_wd(_, #state{dpr = false}) ->
- ok;
-close_wd(Reason, #state{parent = Pid}) ->
- close_wd(Reason, Pid);
-
-%% ... or otherwise
-close_wd(Reason, Pid) ->
- Pid ! {close, self(), Reason}.
-
-%% dwa/1
-
-dwa(#diameter_caps{origin_host = OH,
- origin_realm = OR,
- origin_state_id = OSI}) ->
- ['DWA', {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Origin-State-Id', OSI}].
-
-%% dpr/2
-
-dpr(Cause, #state{transport = TPid,
- service = #diameter_service{capabilities = Caps}}
- = S) ->
- #diameter_caps{origin_host = OH,
- origin_realm = OR}
- = Caps,
-
- Bin = encode(['DPR', {'Origin-Host', OH},
- {'Origin-Realm', OR},
- {'Disconnect-Cause', Cause}]),
- send(TPid, Bin),
- dpa_timer(),
- ?LOG(send, 'DPR'),
- S#state{dpr = diameter_codec:sequence_numbers(Bin)}.
-
-dpa_timer() ->
- erlang:send_after(?DPA_TIMEOUT, self(), dpa_timeout).
-
-%% register_everywhere/1
-%%
-%% Register a term and ensure it's not registered elsewhere. Note that
-%% two process that simultaneously register the same term may well
-%% both fail to do so this isn't foolproof.
-
-register_everywhere(T) ->
- diameter_reg:add_new(T)
- andalso unregistered(T).
-
-unregistered(T) ->
- {ResL, _} = rpc:multicall(?MODULE, match, [{node(), T}]),
- lists:all(fun(L) -> [] == L end, ResL).
-
-match({Node, _})
- when Node == node() ->
- [];
-match({_, T}) ->
- try
- diameter_reg:match(T)
- catch
- _:_ -> []
- end.
diff --git a/lib/diameter/src/app/diameter_reg.erl b/lib/diameter/src/app/diameter_reg.erl
deleted file mode 100644
index 882b9da238..0000000000
--- a/lib/diameter/src/app/diameter_reg.erl
+++ /dev/null
@@ -1,327 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% The module implements a simple term -> pid registry.
-%%
-
--module(diameter_reg).
--compile({no_auto_import, [monitor/2]}).
-
--behaviour(gen_server).
-
--export([add/1,
- add_new/1,
- del/1,
- repl/2,
- match/1]).
-
--export([start_link/0]).
-
-%% gen_server callbacks
--export([init/1,
- terminate/2,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3]).
-
-%% test
--export([pids/0,
- terms/0]).
-
-%% debug
--export([state/0,
- uptime/0]).
-
--include("diameter_internal.hrl").
-
--define(SERVER, ?MODULE).
--define(TABLE, ?MODULE).
-
-%% Table entry used to keep from starting more than one monitor on the
-%% same process. This isn't a problem but there's no point in starting
-%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple
-%% keyed on Pid since a registered term can be anything. Want the entry
-%% keyed on Pid so that lookup is fast.
--define(MONITOR(Pid, MRef), {Pid, monitor, MRef}).
-
-%% Table entry containing the Term -> Pid mapping.
--define(MAPPING(Term, Pid), {Term, Pid}).
-
--record(state, {id = now()}).
-
-%%% ----------------------------------------------------------
-%%% # add(T)
-%%%
-%%% Input: Term = term()
-%%%
-%%% Output: true
-%%%
-%%% Description: Associate the specified term with self(). The list of pids
-%%% having this or other assocations can be retrieved using
-%%% match/1.
-%%%
-%%% An association is removed when the calling process dies
-%%% or as a result of calling del/1. Adding the same term
-%%% more than once is equivalent to adding it exactly once.
-%%%
-%%% Note that since match/1 takes a pattern as argument,
-%%% specifying a term that contains match variables is
-%%% probably not a good idea
-%%% ----------------------------------------------------------
-
--spec add(any())
- -> true.
-
-add(T) ->
- call({add, fun ets:insert/2, T, self()}).
-
-%%% ----------------------------------------------------------
-%%% # add_new(T)
-%%%
-%%% Input: T = term()
-%%%
-%%% Output: true | false
-%%%
-%%% Description: Like add/1 but only one process is allowed to have the
-%%% the association, false being returned if an association
-%%% already exists.
-%%% ----------------------------------------------------------
-
--spec add_new(any())
- -> boolean().
-
-add_new(T) ->
- call({add, fun insert_new/2, T, self()}).
-
-%%% ----------------------------------------------------------
-%%% # repl(T, NewT)
-%%%
-%%% Input: T, NewT = term()
-%%%
-%%% Output: true | false
-%%%
-%%% Description: Like add/1 but only replace an existing association on T,
-%%% false being returned if it doesn't exist.
-%%% ----------------------------------------------------------
-
--spec repl(any(), any())
- -> boolean().
-
-repl(T, U) ->
- call({repl, T, U, self()}).
-
-%%% ----------------------------------------------------------
-%%% # del(Term)
-%%%
-%%% Input: Term = term()
-%%%
-%%% Output: true
-%%%
-%%% Description: Remove any existing association of Term with self().
-%%% ----------------------------------------------------------
-
--spec del(any())
- -> true.
-
-del(T) ->
- call({del, T, self()}).
-
-%%% ----------------------------------------------------------
-%%% # match(Pat)
-%%%
-%%% Input: Pat = pattern in the sense of ets:match_object/2.
-%%%
-%%% Output: list of {Term, Pid}
-%%%
-%%% Description: Return the list of associations whose Term, as specified
-%%% to add/1 or add_new/1, matches the specified pattern.
-%%%
-%%% Note that there's no guarantee that the returned processes
-%%% are still alive. (Although one that isn't will soon have
-%%% its associations removed.)
-%%% ----------------------------------------------------------
-
--spec match(tuple())
- -> [{term(), pid()}].
-
-match(Pat) ->
- ets:match_object(?TABLE, ?MAPPING(Pat, '_')).
-
-%% ---------------------------------------------------------
-%% EXPORTED INTERNAL FUNCTIONS
-%% ---------------------------------------------------------
-
-start_link() ->
- ServerName = {local, ?SERVER},
- Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
- gen_server:start_link(ServerName, ?MODULE, [], Options).
-
-state() ->
- call(state).
-
-uptime() ->
- call(uptime).
-
-%% pids/0
-%%
-%% Output: list of {Pid, [Term, ...]}
-
-pids() ->
- to_list(fun swap/1).
-
-to_list(Fun) ->
- ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE).
-
-acc(Fun, ?MAPPING(Term, Pid), Dict) ->
- append(Fun({Term, Pid}), Dict);
-acc(_, _, Dict) ->
- Dict.
-
-append({K,V}, Dict) ->
- orddict:append(K, V, Dict).
-
-id(T) -> T.
-
-%% terms/0
-%%
-%% Output: list of {Term, [Pid, ...]}
-
-terms() ->
- to_list(fun id/1).
-
-swap({X,Y}) -> {Y,X}.
-
-%%% ----------------------------------------------------------
-%%% # init(Role)
-%%%
-%%% Output: {ok, State}
-%%% ----------------------------------------------------------
-
-init(_) ->
- ets:new(?TABLE, [bag, named_table]),
- {ok, #state{}}.
-
-%%% ----------------------------------------------------------
-%%% # handle_call(Request, From, State)
-%%% ----------------------------------------------------------
-
-handle_call({add, Fun, Key, Pid}, _, State) ->
- B = Fun(?TABLE, {Key, Pid}),
- monitor(B andalso no_monitor(Pid), Pid),
- {reply, B, State};
-
-handle_call({del, Key, Pid}, _, State) ->
- {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), State};
-
-handle_call({repl, T, U, Pid}, _, State) ->
- MatchSpec = [{?MAPPING('$1', Pid),
- [{'=:=', '$1', {const, T}}],
- ['$_']}],
- {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), State};
-
-handle_call(state, _, State) ->
- {reply, State, State};
-
-handle_call(uptime, _, #state{id = Time} = State) ->
- {reply, diameter_lib:now_diff(Time), State};
-
-handle_call(Req, From, State) ->
- ?UNEXPECTED([Req, From]),
- {reply, nok, State}.
-
-%%% ----------------------------------------------------------
-%%% # handle_cast(Request, State)
-%%% ----------------------------------------------------------
-
-handle_cast(Msg, State)->
- ?UNEXPECTED([Msg]),
- {noreply, State}.
-
-%%% ----------------------------------------------------------
-%%% # handle_info(Request, State)
-%%% ----------------------------------------------------------
-
-handle_info({'DOWN', MRef, process, Pid, _}, State) ->
- ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)),
- ets:match_delete(?TABLE, ?MAPPING('_', Pid)),
- {noreply, State};
-
-handle_info(Info, State) ->
- ?UNEXPECTED([Info]),
- {noreply, State}.
-
-%%% ----------------------------------------------------------
-%%% # terminate(Reason, State)
-%%% ----------------------------------------------------------
-
-terminate(_Reason, _State)->
- ok.
-
-%%% ----------------------------------------------------------
-%%% # code_change(OldVsn, State, Extra)
-%%% ----------------------------------------------------------
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%% ---------------------------------------------------------
-%% INTERNAL FUNCTIONS
-%% ---------------------------------------------------------
-
-monitor(true, Pid) ->
- ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid)));
-monitor(false, _) ->
- ok.
-
-%% Do we need a monitor for the specified Pid?
-no_monitor(Pid) ->
- [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')).
-
-%% insert_new/2
-
-insert_new(?TABLE, {Key, _} = T) ->
- flush(ets:lookup(?TABLE, Key)),
- ets:insert_new(?TABLE, T).
-
-%% Remove any processes that are dead but for which we may not have
-%% received 'DOWN' yet. This is to ensure that add_new can be used
-%% to register a unique name each time a process restarts.
-flush(List) ->
- lists:foreach(fun({_,P} = T) ->
- del(erlang:is_process_alive(P), T)
- end,
- List).
-
-del(Alive, T) ->
- Alive orelse ets:delete_object(?TABLE, T).
-
-%% repl/3
-
-repl([?MAPPING(_, Pid) = M], Key, Pid) ->
- ets:delete_object(?TABLE, M),
- true = ets:insert(?TABLE, ?MAPPING(Key, Pid));
-repl([], _, _) ->
- false.
-
-%% call/1
-
-call(Request) ->
- gen_server:call(?SERVER, Request, infinity).
diff --git a/lib/diameter/src/app/diameter_stats.erl b/lib/diameter/src/app/diameter_stats.erl
deleted file mode 100644
index 71479afa95..0000000000
--- a/lib/diameter/src/app/diameter_stats.erl
+++ /dev/null
@@ -1,342 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% Statistics collector.
-%%
-
--module(diameter_stats).
--compile({no_auto_import, [monitor/2]}).
-
--behaviour(gen_server).
-
--export([reg/1, reg/2,
- incr/1, incr/2, incr/3,
- read/1,
- flush/0, flush/1]).
-
-%% supervisor callback
--export([start_link/0]).
-
-%% gen_server callbacks
--export([init/1,
- terminate/2,
- handle_call/3,
- handle_cast/2,
- handle_info/2,
- code_change/3]).
-
-%% debug
--export([state/0,
- uptime/0]).
-
--include("diameter_internal.hrl").
-
-%% ets table containing stats. reg(Pid, Ref) inserts a {Pid, Ref},
-%% incr(Counter, X, N) updates the counter keyed at {Counter, X}, and
-%% Pid death causes counters keyed on {Counter, Pid} to be deleted and
-%% added to those keyed on {Counter, Ref}.
--define(TABLE, ?MODULE).
-
-%% Name of registered server.
--define(SERVER, ?MODULE).
-
-%% Entries in the table.
--define(REC(Key, Value), {Key, Value}).
-
-%% Server state.
--record(state, {id = now()}).
-
--type counter() :: any().
--type contrib() :: any().
-
-%%% ---------------------------------------------------------------------------
-%%% # reg(Pid, Contrib)
-%%%
-%%% Description: Register a process as a contributor of statistics
-%%% associated with a specified term. Statistics can be
-%%% contributed by specifying either Pid or Contrib as
-%%% the second argument to incr/3. Statistics contributed
-%%% by Pid are folded into the corresponding entry for
-%%% Contrib when the process dies.
-%%%
-%%% Contrib can be any term but should not be a pid
-%%% passed as the first argument to reg/2. Subsequent
-%%% registrations for the same Pid overwrite the association
-%%% ---------------------------------------------------------------------------
-
--spec reg(pid(), contrib())
- -> true.
-
-reg(Pid, Contrib)
- when is_pid(Pid) ->
- call({reg, Pid, Contrib}).
-
--spec reg(contrib())
- -> true.
-
-reg(Ref) ->
- reg(self(), Ref).
-
-%%% ---------------------------------------------------------------------------
-%%% # incr(Counter, Contrib, N)
-%%%
-%%% Description: Increment a counter for the specified contributor.
-%%%
-%%% Contrib will typically be an argument passed to reg/2
-%%% but there's nothing that requires this. In particular,
-%%% if Contrib is a pid that hasn't been registered then
-%%% counters are unaffected by the death of the process.
-%%% ---------------------------------------------------------------------------
-
--spec incr(counter(), contrib(), integer())
- -> integer().
-
-incr(Ctr, Contrib, N) ->
- update_counter({Ctr, Contrib}, N).
-
-incr(Ctr, N)
- when is_integer(N) ->
- incr(Ctr, self(), N);
-
-incr(Ctr, Contrib) ->
- incr(Ctr, Contrib, 1).
-
-incr(Ctr) ->
- incr(Ctr, self(), 1).
-
-%%% ---------------------------------------------------------------------------
-%%% # read(Contribs)
-%%%
-%%% Description: Retrieve counters for the specified contributors.
-%%% ---------------------------------------------------------------------------
-
--spec read([contrib()])
- -> [{contrib(), [{counter(), integer()}]}].
-
-read(Contribs) ->
- lists:foldl(fun(?REC({T,C}, N), D) -> orddict:append(C, {T,N}, D) end,
- orddict:new(),
- ets:select(?TABLE, [{?REC({'_', '$1'}, '_'),
- [?ORCOND([{'=:=', '$1', {const, C}}
- || C <- Contribs])],
- ['$_']}])).
-
-%%% ---------------------------------------------------------------------------
-%%% # flush(Contrib)
-%%%
-%%% Description: Retrieve and delete statistics for the specified
-%%% contributor.
-%%%
-%%% If Contrib is a pid registered with reg/2 then statistics
-%%% for both and its associated contributor are retrieved.
-%%% ---------------------------------------------------------------------------
-
--spec flush(contrib())
- -> [{counter(), integer()}].
-
-flush(Contrib) ->
- try
- call({flush, Contrib})
- catch
- exit: _ ->
- []
- end.
-
-flush() ->
- flush(self()).
-
-%%% ---------------------------------------------------------
-%%% EXPORTED INTERNAL FUNCTIONS
-%%% ---------------------------------------------------------
-
-start_link() ->
- ServerName = {local, ?SERVER},
- Module = ?MODULE,
- Args = [],
- Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
- gen_server:start_link(ServerName, Module, Args, Options).
-
-state() ->
- call(state).
-
-uptime() ->
- call(uptime).
-
-%%% ----------------------------------------------------------
-%%% # init(_)
-%%%
-%%% Output: {ok, State}
-%%% ----------------------------------------------------------
-
-init([]) ->
- ets:new(?TABLE, [named_table, ordered_set, public]),
- {ok, #state{}}.
-
-%% ----------------------------------------------------------
-%% handle_call(Request, From, State)
-%% ----------------------------------------------------------
-
-handle_call(state, _, State) ->
- {reply, State, State};
-
-handle_call(uptime, _, #state{id = Time} = State) ->
- {reply, diameter_lib:now_diff(Time), State};
-
-handle_call({reg, Pid, Contrib}, _From, State) ->
- monitor(not ets:member(?TABLE, Pid), Pid),
- {reply, insert(?REC(Pid, Contrib)), State};
-
-handle_call({flush, Contrib}, _From, State) ->
- {reply, fetch(Contrib), State};
-
-handle_call(Req, From, State) ->
- ?UNEXPECTED([Req, From]),
- {reply, nok, State}.
-
-%% ----------------------------------------------------------
-%% handle_cast(Request, State)
-%% ----------------------------------------------------------
-
-handle_cast({incr, Rec}, State) ->
- update_counter(Rec),
- {noreply, State};
-
-handle_cast(Msg, State) ->
- ?UNEXPECTED([Msg]),
- {noreply, State}.
-
-%% ----------------------------------------------------------
-%% handle_info(Request, State)
-%% ----------------------------------------------------------
-
-handle_info({'DOWN', _MRef, process, Pid, _}, State) ->
- down(Pid),
- {noreply, State};
-
-handle_info(Info, State) ->
- ?UNEXPECTED([Info]),
- {noreply, State}.
-
-%% ----------------------------------------------------------
-%% terminate(Reason, State)
-%% ----------------------------------------------------------
-
-terminate(_Reason, _State) ->
- ok.
-
-%% ----------------------------------------------------------
-%% code_change(OldVsn, State, Extra)
-%% ----------------------------------------------------------
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%%% ---------------------------------------------------------
-%%% INTERNAL FUNCTIONS
-%%% ---------------------------------------------------------
-
-%% monitor/2
-
-monitor(true, Pid) ->
- erlang:monitor(process, Pid);
-monitor(false = No, _) ->
- No.
-
-%% down/1
-
-down(Pid) ->
- L = ets:match_object(?TABLE, ?REC({'_', Pid}, '_')),
- [?REC(_, Ref) = T] = lookup(Pid),
- fold(Ref, L),
- delete_object(T),
- delete(L).
-
-%% Fold Pid-based entries into Ref-based ones.
-fold(Ref, L) ->
- lists:foreach(fun(?REC({K, _}, V)) -> update_counter({{K, Ref}, V}) end,
- L).
-
-delete(Objs) ->
- lists:foreach(fun delete_object/1, Objs).
-
-%% fetch/1
-
-fetch(X) ->
- MatchSpec = [{?REC({'_', '$1'}, '_'),
- [?ORCOND([{'==', '$1', {const, T}} || T <- [X | ref(X)]])],
- ['$_']}],
- L = ets:select(?TABLE, MatchSpec),
- delete(L),
- D = lists:foldl(fun sum/2, dict:new(), L),
- dict:to_list(D).
-
-sum({{Ctr, _}, N}, Dict) ->
- dict:update(Ctr, fun(V) -> V+N end, N, Dict).
-
-ref(Pid)
- when is_pid(Pid) ->
- ets:select(?TABLE, [{?REC(Pid, '$1'), [], ['$1']}]);
-ref(_) ->
- [].
-
-%% update_counter/2
-%%
-%% From an arbitrary request process. Cast to the server process to
-%% insert a new element if the counter doesn't exists so that two
-%% processes don't do so simultaneously.
-
-update_counter(Key, N) ->
- try
- ets:update_counter(?TABLE, Key, N)
- catch
- error: badarg ->
- cast({incr, ?REC(Key, N)})
- end.
-
-%% update_counter/1
-%%
-%% From the server process.
-
-update_counter(?REC(Key, N) = T) ->
- try
- ets:update_counter(?TABLE, Key, N)
- catch
- error: badarg ->
- insert(T)
- end.
-
-insert(T) ->
- ets:insert(?TABLE, T).
-
-lookup(Key) ->
- ets:lookup(?TABLE, Key).
-
-delete_object(T) ->
- ets:delete_object(?TABLE, T).
-
-%% cast/1
-
-cast(Msg) ->
- gen_server:cast(?SERVER, Msg).
-
-%% call/1
-
-call(Request) ->
- gen_server:call(?SERVER, Request, infinity).
diff --git a/lib/diameter/src/app/diameter_types.hrl b/lib/diameter/src/app/diameter_types.hrl
deleted file mode 100644
index 02bf8a74dd..0000000000
--- a/lib/diameter/src/app/diameter_types.hrl
+++ /dev/null
@@ -1,139 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% Types for function specifications, primarily in diameter.erl. This
-%% has nothing specifically to do with diameter_types.erl.
-%%
-
--type evaluable()
- :: {module(), atom(), list()}
- | fun()
- | nonempty_improper_list(evaluable(), list()). %% [evaluable() | Args]
-
--type app_alias()
- :: any().
-
--type service_name()
- :: any().
-
-%% Diameter basic types
-
--type 'OctetString'() :: iolist().
--type 'Integer32'() :: -2147483647..2147483647.
--type 'Integer64'() :: -9223372036854775807..9223372036854775807.
--type 'Unsigned32'() :: 0..4294967295.
--type 'Unsigned64'() :: 0..18446744073709551615.
--type 'Float32'() :: '-infinity' | float() | infinity.
--type 'Float64'() :: '-infinity' | float() | infinity.
--type 'Grouped'() :: list() | tuple().
-
-%% Diameter derived types
-
--type 'Address'()
- :: inet:ip_address()
- | string().
-
--type 'Time'() :: {{integer(), 1..12, 1..31},
- {0..23, 0..59, 0..59}}.
--type 'UTF8String'() :: iolist().
--type 'DiameterIdentity'() :: 'OctetString'().
--type 'DiameterURI'() :: 'OctetString'().
--type 'Enumerated'() :: 'Integer32'().
--type 'IPFilterRule'() :: 'OctetString'().
--type 'QoSFilterRule'() :: 'OctetString'().
-
-%% Capabilities options/avps on start_service/2 and/or add_transport/2
-
--type capability()
- :: {'Origin-Host', 'DiameterIdentity'()}
- | {'Origin-Realm', 'DiameterIdentity'()}
- | {'Host-IP-Address', ['Address'()]}
- | {'Vendor-Id', 'Unsigned32'()}
- | {'Product-Name', 'UTF8String'()}
- | {'Supported-Vendor-Id', ['Unsigned32'()]}
- | {'Auth-Application-Id', ['Unsigned32'()]}
- | {'Vendor-Specific-Application-Id', ['Grouped'()]}
- | {'Firmware-Revision', 'Unsigned32'()}.
-
-%% Filters for call/4
-
--type peer_filter()
- :: none
- | host
- | realm
- | {host, any|'DiameterIdentity'()}
- | {realm, any|'DiameterIdentity'()}
- | {eval, evaluable()}
- | {neg, peer_filter()}
- | {all, [peer_filter()]}
- | {any, [peer_filter()]}.
-
-%% Options passed to start_service/2
-
--type service_opt()
- :: capability()
- | {application, [application_opt()]}.
-
--type application_opt()
- :: {alias, app_alias()}
- | {dictionary, module()}
- | {module, app_module()}
- | {state, any()}
- | {call_mutates_state, boolean()}
- | {answer_errors, callback|report|discard}.
-
--type app_module()
- :: module()
- | nonempty_improper_list(module(), list()). %% list with module() head
-
-%% Identifier returned by add_transport/2
-
--type transport_ref()
- :: reference().
-
-%% Options passed to add_transport/2
-
--type transport_opt()
- :: {transport_module, atom()}
- | {transport_config, any()}
- | {applications, [app_alias()]}
- | {capabilities, [capability()]}
- | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
- | {reconnect_timer, 'Unsigned32'()}
- | {private, any()}.
-
-%% Predicate passed to remove_transport/2
-
--type transport_pred()
- :: fun((reference(), connect|listen, list()) -> boolean())
- | fun((reference(), list()) -> boolean())
- | fun((list()) -> boolean())
- | reference()
- | list()
- | {connect|listen, transport_pred()}
- | {atom(), atom(), list()}.
-
-%% Options passed to call/4
-
--type call_opt()
- :: {extra, list()}
- | {filter, peer_filter()}
- | {timeout, 'Unsigned32'()}
- | detach.
diff --git a/lib/diameter/src/app/modules.mk b/lib/diameter/src/app/modules.mk
deleted file mode 100644
index c133e6f64e..0000000000
--- a/lib/diameter/src/app/modules.mk
+++ /dev/null
@@ -1,70 +0,0 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
-
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-
-SPEC_FILES = \
- diameter_gen_base_rfc3588.dia \
- diameter_gen_base_accounting.dia \
- diameter_gen_relay.dia
-
-RUNTIME_MODULES = \
- diameter \
- diameter_app \
- diameter_capx \
- diameter_config \
- diameter_codec \
- diameter_dict \
- diameter_lib \
- diameter_misc_sup \
- diameter_peer \
- diameter_peer_fsm \
- diameter_peer_fsm_sup \
- diameter_reg \
- diameter_service \
- diameter_service_sup \
- diameter_session \
- diameter_stats \
- diameter_sup \
- diameter_sync \
- diameter_types \
- diameter_watchdog \
- diameter_watchdog_sup
-
-HELP_MODULES = \
- diameter_callback \
- diameter_exprecs \
- diameter_dbg \
- diameter_info
-
-INTERNAL_HRL_FILES = \
- diameter_internal.hrl \
- diameter_types.hrl
-
-EXTERNAL_HRL_FILES = \
- ../../include/diameter.hrl \
- ../../include/diameter_gen.hrl
-
-EXAMPLE_FILES = \
- ../../examples/GNUmakefile \
- ../../examples/peer.erl \
- ../../examples/client.erl \
- ../../examples/client_cb.erl \
- ../../examples/server.erl \
- ../../examples/server_cb.erl \
- ../../examples/relay.erl \
- ../../examples/relay_cb.erl
diff --git a/lib/diameter/src/app/diameter.app.src b/lib/diameter/src/base/diameter.app.src
index a806b5c78a..c092fdb022 100644
--- a/lib/diameter/src/app/diameter.app.src
+++ b/lib/diameter/src/base/diameter.app.src
@@ -20,7 +20,7 @@
{application, diameter,
[{description, "Diameter protocol"},
{vsn, "%VSN%"},
- {modules, [%APP_MODULES%,%TRANSPORT_MODULES%]},
+ {modules, [%MODULES%]},
{registered, []},
{applications, [stdlib, kernel]},
{env, []},
diff --git a/lib/diameter/src/base/diameter.appup.src b/lib/diameter/src/base/diameter.appup.src
new file mode 100644
index 0000000000..a04a387918
--- /dev/null
+++ b/lib/diameter/src/base/diameter.appup.src
@@ -0,0 +1,40 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+{"%VSN%",
+ [
+ {"0.9", [{restart_application, diameter}]},
+ {"0.10", [{restart_application, diameter}]},
+ {"1.0", [{restart_application, diameter}]},
+ {"1.1", [{restart_application, diameter}]},
+ {"1.2", [{restart_application, diameter}]},
+ {"1.2.1", [{restart_application, diameter}]},
+ {"1.3", [{load_module, diameter_service}]}
+ ],
+ [
+ {"0.9", [{restart_application, diameter}]},
+ {"0.10", [{restart_application, diameter}]},
+ {"1.0", [{restart_application, diameter}]},
+ {"1.1", [{restart_application, diameter}]},
+ {"1.2", [{restart_application, diameter}]},
+ {"1.2.1", [{restart_application, diameter}]},
+ {"1.3", [{load_module, diameter_service}]}
+ ]
+}.
diff --git a/lib/diameter/src/base/diameter.erl b/lib/diameter/src/base/diameter.erl
new file mode 100644
index 0000000000..8f9901907a
--- /dev/null
+++ b/lib/diameter/src/base/diameter.erl
@@ -0,0 +1,357 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(diameter).
+
+%% Configuration.
+-export([start_service/2,
+ stop_service/1,
+ add_transport/2,
+ remove_transport/2,
+ subscribe/1,
+ unsubscribe/1]).
+
+%% Traffic.
+-export([session_id/1,
+ origin_state_id/0,
+ call/3,
+ call/4]).
+
+%% Information.
+-export([services/0,
+ service_info/2]).
+
+%% Start/stop the application. In a "real" application this should
+%% typically be a consequence of a release file rather than by calling
+%% start/stop explicitly.
+-export([start/0,
+ stop/0]).
+
+-export_type([evaluable/0,
+ restriction/0,
+ sequence/0,
+ app_alias/0,
+ service_name/0,
+ capability/0,
+ peer_filter/0,
+ service_opt/0,
+ application_opt/0,
+ app_module/0,
+ transport_ref/0,
+ transport_opt/0,
+ transport_pred/0,
+ call_opt/0]).
+
+-export_type(['OctetString'/0,
+ 'Integer32'/0,
+ 'Integer64'/0,
+ 'Unsigned32'/0,
+ 'Unsigned64'/0,
+ 'Float32'/0,
+ 'Float64'/0,
+ 'Grouped'/0,
+ 'Address'/0,
+ 'Time'/0,
+ 'UTF8String'/0,
+ 'DiameterIdentity'/0,
+ 'DiameterURI'/0,
+ 'Enumerated'/0,
+ 'IPFilterRule'/0,
+ 'QoSFilterRule'/0]).
+
+-include_lib("diameter/include/diameter.hrl").
+-include("diameter_internal.hrl").
+
+%% ---------------------------------------------------------------------------
+%% start/0
+%% ---------------------------------------------------------------------------
+
+-spec start()
+ -> ok
+ | {error, term()}.
+
+start() ->
+ application:start(?APPLICATION).
+
+%% ---------------------------------------------------------------------------
+%% stop/0
+%% ---------------------------------------------------------------------------
+
+-spec stop()
+ -> ok
+ | {error, term()}.
+
+stop() ->
+ application:stop(?APPLICATION).
+
+%% ---------------------------------------------------------------------------
+%% start_service/2
+%% ---------------------------------------------------------------------------
+
+-spec start_service(service_name(), [service_opt()])
+ -> ok
+ | {error, term()}.
+
+start_service(SvcName, Opts)
+ when is_list(Opts) ->
+ diameter_config:start_service(SvcName, Opts).
+
+%% ---------------------------------------------------------------------------
+%% stop_service/1
+%% ---------------------------------------------------------------------------
+
+-spec stop_service(service_name())
+ -> ok
+ | {error, term()}.
+
+stop_service(SvcName) ->
+ diameter_config:stop_service(SvcName).
+
+%% ---------------------------------------------------------------------------
+%% services/0
+%% ---------------------------------------------------------------------------
+
+-spec services()
+ -> [service_name()].
+
+services() ->
+ [Name || {Name, _} <- diameter_service:services()].
+
+%% ---------------------------------------------------------------------------
+%% service_info/2
+%% ---------------------------------------------------------------------------
+
+-spec service_info(service_name(), atom() | [atom()])
+ -> any().
+
+service_info(SvcName, Option) ->
+ diameter_service:info(SvcName, Option).
+
+%% ---------------------------------------------------------------------------
+%% add_transport/3
+%% ---------------------------------------------------------------------------
+
+-spec add_transport(service_name(), {listen|connect, [transport_opt()]})
+ -> {ok, transport_ref()}
+ | {error, term()}.
+
+add_transport(SvcName, {T, Opts} = Cfg)
+ when is_list(Opts), (T == connect orelse T == listen) ->
+ diameter_config:add_transport(SvcName, Cfg).
+
+%% ---------------------------------------------------------------------------
+%% remove_transport/2
+%% ---------------------------------------------------------------------------
+
+-spec remove_transport(service_name(), transport_pred())
+ -> ok | {error, term()}.
+
+remove_transport(SvcName, Pred) ->
+ diameter_config:remove_transport(SvcName, Pred).
+
+%% ---------------------------------------------------------------------------
+%% subscribe/1
+%% ---------------------------------------------------------------------------
+
+-spec subscribe(service_name())
+ -> true.
+
+subscribe(SvcName) ->
+ diameter_service:subscribe(SvcName).
+
+%% ---------------------------------------------------------------------------
+%% unsubscribe/1
+%% ---------------------------------------------------------------------------
+
+-spec unsubscribe(service_name())
+ -> true.
+
+unsubscribe(SvcName) ->
+ diameter_service:unsubscribe(SvcName).
+
+%% ---------------------------------------------------------------------------
+%% session_id/1
+%% ---------------------------------------------------------------------------
+
+-spec session_id('DiameterIdentity'())
+ -> 'OctetString'().
+
+session_id(Ident) ->
+ diameter_session:session_id(Ident).
+
+%% ---------------------------------------------------------------------------
+%% origin_state_id/0
+%% ---------------------------------------------------------------------------
+
+-spec origin_state_id()
+ -> 'Unsigned32'().
+
+origin_state_id() ->
+ diameter_session:origin_state_id().
+
+%% ---------------------------------------------------------------------------
+%% call/3,4
+%% ---------------------------------------------------------------------------
+
+-spec call(service_name(), app_alias(), any(), [call_opt()])
+ -> any().
+
+call(SvcName, App, Message, Options) ->
+ diameter_service:call(SvcName, {alias, App}, Message, Options).
+
+call(SvcName, App, Message) ->
+ call(SvcName, App, Message, []).
+
+%% ===========================================================================
+
+%% Diameter basic types
+
+-type 'OctetString'() :: iolist().
+-type 'Integer32'() :: -2147483647..2147483647.
+-type 'Integer64'() :: -9223372036854775807..9223372036854775807.
+-type 'Unsigned32'() :: 0..4294967295.
+-type 'Unsigned64'() :: 0..18446744073709551615.
+-type 'Float32'() :: '-infinity' | float() | infinity.
+-type 'Float64'() :: '-infinity' | float() | infinity.
+-type 'Grouped'() :: list() | tuple().
+
+%% Diameter derived types
+
+-type 'Address'()
+ :: inet:ip_address()
+ | string().
+
+-type 'Time'() :: {{integer(), 1..12, 1..31},
+ {0..23, 0..59, 0..59}}.
+-type 'UTF8String'() :: iolist().
+-type 'DiameterIdentity'() :: 'OctetString'().
+-type 'DiameterURI'() :: 'OctetString'().
+-type 'Enumerated'() :: 'Integer32'().
+-type 'IPFilterRule'() :: 'OctetString'().
+-type 'QoSFilterRule'() :: 'OctetString'().
+
+%% The handle to a service.
+
+-type service_name()
+ :: any().
+
+%% Capabilities options/avps on start_service/2 and/or add_transport/2
+
+-type capability()
+ :: {'Origin-Host', 'DiameterIdentity'()}
+ | {'Origin-Realm', 'DiameterIdentity'()}
+ | {'Host-IP-Address', ['Address'()]}
+ | {'Vendor-Id', 'Unsigned32'()}
+ | {'Product-Name', 'UTF8String'()}
+ | {'Supported-Vendor-Id', ['Unsigned32'()]}
+ | {'Auth-Application-Id', ['Unsigned32'()]}
+ | {'Vendor-Specific-Application-Id', ['Grouped'()]}
+ | {'Firmware-Revision', 'Unsigned32'()}.
+
+%% Filters for call/4
+
+-type peer_filter()
+ :: none
+ | host
+ | realm
+ | {host, any|'DiameterIdentity'()}
+ | {realm, any|'DiameterIdentity'()}
+ | {eval, evaluable()}
+ | {neg, peer_filter()}
+ | {all, [peer_filter()]}
+ | {any, [peer_filter()]}.
+
+-type evaluable()
+ :: {module(), atom(), list()}
+ | fun()
+ | maybe_improper_list(evaluable(), list()).
+
+-type sequence()
+ :: {'Unsigned32'(), 0..32}.
+
+-type restriction()
+ :: false
+ | node
+ | nodes
+ | [node()]
+ | evaluable().
+
+%% Options passed to start_service/2
+
+-type service_opt()
+ :: capability()
+ | {application, [application_opt()]}
+ | {restrict_connections, restriction()}
+ | {sequence, sequence() | evaluable()}.
+
+-type application_opt()
+ :: {alias, app_alias()}
+ | {dictionary, module()}
+ | {module, app_module()}
+ | {state, any()}
+ | {call_mutates_state, boolean()}
+ | {answer_errors, callback|report|discard}.
+
+-type app_alias()
+ :: any().
+
+-type app_module()
+ :: module()
+ | maybe_improper_list(module(), list())
+ | #diameter_callback{}.
+
+%% Identifier returned by add_transport/2
+
+-type transport_ref()
+ :: reference().
+
+%% Options passed to add_transport/2
+
+-type transport_opt()
+ :: {transport_module, atom()}
+ | {transport_config, any()}
+ | {transport_config, any(), non_neg_integer() | infinity}
+ | {applications, [app_alias()]}
+ | {capabilities, [capability()]}
+ | {capabilities_cb, evaluable()}
+ | {capx_timeout, 'Unsigned32'()}
+ | {disconnect_cb, evaluable()}
+ | {watchdog_timer, 'Unsigned32'() | {module(), atom(), list()}}
+ | {reconnect_timer, 'Unsigned32'()}
+ | {private, any()}.
+
+%% Predicate passed to remove_transport/2
+
+-type transport_pred()
+ :: fun((transport_ref(), connect|listen, list()) -> boolean())
+ | fun((transport_ref(), list()) -> boolean())
+ | fun((list()) -> boolean())
+ | transport_ref()
+ | boolean()
+ | list()
+ | {connect|listen, transport_pred()}
+ | {atom(), atom(), list()}.
+
+%% Options passed to call/4
+
+-type call_opt()
+ :: {extra, list()}
+ | {filter, peer_filter()}
+ | {timeout, 'Unsigned32'()}
+ | detach.
diff --git a/lib/diameter/src/app/diameter_app.erl b/lib/diameter/src/base/diameter_app.erl
index 600f7ff04d..600f7ff04d 100644
--- a/lib/diameter/src/app/diameter_app.erl
+++ b/lib/diameter/src/base/diameter_app.erl
diff --git a/lib/diameter/src/base/diameter_callback.erl b/lib/diameter/src/base/diameter_callback.erl
new file mode 100644
index 0000000000..90431099b0
--- /dev/null
+++ b/lib/diameter/src/base/diameter_callback.erl
@@ -0,0 +1,234 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% A diameter callback module that can redirect selected callbacks,
+%% providing reasonable default implementations otherwise.
+%%
+%% To order alternate callbacks, configure a #diameter_callback record
+%% as the Diameter application callback in question. The record has
+%% one field for each callback function as well as 'default' and
+%% 'extra' fields. A function-specific field can be set to a
+%% diameter:evaluable() in order to redirect the callback
+%% corresponding to that field, or to 'false' to request the default
+%% callback implemented in this module. If neither of these fields are
+%% set then the 'default' field determines the form of the callback: a
+%% module name results in the usual callback as if the module had been
+%% configured directly as the callback module, a diameter_evaluable()
+%% in a callback applied to the atom-valued callback name and argument
+%% list. For all callbacks not to this module, the 'extra' field is a
+%% list of additional arguments, following arguments supplied by
+%% diameter but preceeding those of the diameter:evaluable() being
+%% applied.
+%%
+%% For example, the following config to diameter:start_service/2, in
+%% an 'application' tuple, would result in only a mymod:peer_down/3
+%% callback, this module implementing the remaining callbacks.
+%%
+%% {module, #diameter_callback{peer_down = {mymod, down, []}}}
+%%
+%% Equivalently, this can also be specified with a [Mod | Args]
+%% field/value list as follows.
+%%
+%% {module, [diameter_callback, {peer_down, {mymod, down, []}}]}
+%%
+%% The following would result in this module suppying peer_up and
+%% peer_down callback, others taking place in module mymod.
+%%
+%% {module, #diameter_callback{peer_up = false,
+%% peer_down = false,
+%% default = mymod}}
+%%
+%% The following would result in all callbacks taking place as
+%% calls to mymod:diameter/2.
+%%
+%% {module, #diameter_callback{default = {mymod, diameter, []}}}
+%%
+%% The following are equivalent and result in all callbacks being
+%% provided by this module.
+%%
+%% {module, #diameter_callback{}}
+%% {module, diameter_callback}
+%%
+
+-module(diameter_callback).
+
+%% Default callbacks when no aleternate is specified.
+-export([peer_up/3,
+ peer_down/3,
+ pick_peer/4,
+ prepare_request/3,
+ prepare_retransmit/3,
+ handle_request/3,
+ handle_answer/4,
+ handle_error/4]).
+
+%% Callbacks taking a #diameter_callback record.
+-export([peer_up/4,
+ peer_down/4,
+ pick_peer/5,
+ prepare_request/4,
+ prepare_retransmit/4,
+ handle_request/4,
+ handle_answer/5,
+ handle_error/5]).
+
+-include_lib("diameter/include/diameter.hrl").
+
+%%% ----------------------------------------------------------
+%%% # peer_up/3
+%%% ----------------------------------------------------------
+
+peer_up(_Svc, _Peer, State) ->
+ State.
+
+peer_up(Svc, Peer, State, D) ->
+ cb(peer_up,
+ [Svc, Peer, State],
+ D#diameter_callback.peer_up,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # peer_down/3
+%%% ----------------------------------------------------------
+
+peer_down(_Svc, _Peer, State) ->
+ State.
+
+peer_down(Svc, Peer, State, D) ->
+ cb(peer_down,
+ [Svc, Peer, State],
+ D#diameter_callback.peer_down,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # pick_peer/4
+%%% ----------------------------------------------------------
+
+pick_peer([Peer|_], _, _Svc, _State) ->
+ {ok, Peer};
+pick_peer([], _, _Svc, _State) ->
+ false.
+
+pick_peer(PeersL, PeersR, Svc, State, D) ->
+ cb(pick_peer,
+ [PeersL, PeersR, Svc, State],
+ D#diameter_callback.pick_peer,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # prepare_request/3
+%%% ----------------------------------------------------------
+
+prepare_request(Pkt, _Svc, _Peer) ->
+ {send, Pkt}.
+
+prepare_request(Pkt, Svc, Peer, D) ->
+ cb(prepare_request,
+ [Pkt, Svc, Peer],
+ D#diameter_callback.prepare_request,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # prepare_retransmit/3
+%%% ----------------------------------------------------------
+
+prepare_retransmit(Pkt, _Svc, _Peer) ->
+ {send, Pkt}.
+
+prepare_retransmit(Pkt, Svc, Peer, D) ->
+ cb(prepare_retransmit,
+ [Pkt, Svc, Peer],
+ D#diameter_callback.prepare_retransmit,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # handle_request/3
+%%% ----------------------------------------------------------
+
+handle_request(_Pkt, _Svc, _Peer) ->
+ {protocol_error, 3001}. %% DIAMETER_COMMAND_UNSUPPORTED
+
+handle_request(Pkt, Svc, Peer, D) ->
+ cb(handle_request,
+ [Pkt, Svc, Peer],
+ D#diameter_callback.handle_request,
+ D).
+
+%%% ----------------------------------------------------------
+%%% # handle_answer/4
+%%% ----------------------------------------------------------
+
+handle_answer(#diameter_packet{msg = Ans, errors = []}, _Req, _Svc, _Peer) ->
+ Ans;
+handle_answer(#diameter_packet{msg = Ans, errors = Es}, _Req, _Svc, _Peer) ->
+ [Ans | Es].
+
+handle_answer(Pkt, Req, Svc, Peer, D) ->
+ cb(handle_answer,
+ [Pkt, Req, Svc, Peer],
+ D#diameter_callback.handle_answer,
+ D).
+
+%%% ---------------------------------------------------------------------------
+%%% # handle_error/4
+%%% ---------------------------------------------------------------------------
+
+handle_error(Reason, _Req, _Svc, _Peer) ->
+ {error, Reason}.
+
+handle_error(Reason, Req, Svc, Peer, D) ->
+ cb(handle_error,
+ [Reason, Req, Svc, Peer],
+ D#diameter_callback.handle_error,
+ D).
+
+%% ===========================================================================
+
+%% cb/4
+
+%% Unspecified callback: use default field to determine something
+%% appropriate.
+cb(CB, Args, undefined, D) ->
+ cb(CB, Args, D);
+
+%% Explicitly requested default.
+cb(CB, Args, false, _) ->
+ apply(?MODULE, CB, Args);
+
+%% A specified callback.
+cb(_, Args, F, #diameter_callback{extra = X}) ->
+ diameter_lib:eval([[F|X] | Args]).
+
+%% cb/3
+
+%% No user-supplied default: call ours.
+cb(CB, Args, #diameter_callback{default = undefined}) ->
+ apply(?MODULE, CB, Args);
+
+%% Default is a module name: make the usual callback.
+cb(CB, Args, #diameter_callback{default = M,
+ extra = X})
+ when is_atom(M) ->
+ apply(M, CB, Args ++ X);
+
+%% Default is something else: apply if to callback name and arguments.
+cb(CB, Args, #diameter_callback{default = F,
+ extra = X}) ->
+ diameter_lib:eval([F, CB, Args | X]).
diff --git a/lib/diameter/src/app/diameter_capx.erl b/lib/diameter/src/base/diameter_capx.erl
index aa5318e79d..c6c3d2934d 100644
--- a/lib/diameter/src/app/diameter_capx.erl
+++ b/lib/diameter/src/base/diameter_capx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -54,14 +54,14 @@
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
--include("diameter_types.hrl").
-include("diameter_gen_base_rfc3588.hrl").
--define(SUCCESS, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_SUCCESS').
--define(NOAPP, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_NO_COMMON_APPLICATION').
--define(NOSECURITY, ?'DIAMETER_BASE_RESULT-CODE_DIAMETER_NO_COMMON_SECURITY').
+-define(SUCCESS, 2001). %% DIAMETER_SUCCESS
+-define(NOAPP, 5010). %% DIAMETER_NO_COMMON_APPLICATION
+-define(NOSECURITY, 5017). %% DIAMETER_NO_COMMON_SECURITY
-define(NO_INBAND_SECURITY, 0).
+-define(TLS, 1).
%% ===========================================================================
@@ -74,13 +74,17 @@ build_CER(Caps) ->
try_it([fun bCER/1, Caps]).
-spec recv_CER(#diameter_base_CER{}, #diameter_service{})
- -> tried({['Unsigned32'()], #diameter_caps{}, #diameter_base_CEA{}}).
+ -> tried({[diameter:'Unsigned32'()],
+ #diameter_caps{},
+ #diameter_base_CEA{}}).
recv_CER(CER, Svc) ->
try_it([fun rCER/2, CER, Svc]).
-spec recv_CEA(#diameter_base_CEA{}, #diameter_service{})
- -> tried({['Unsigned32'()], #diameter_caps{}}).
+ -> tried({[diameter:'Unsigned32'()],
+ [diameter:'Unsigned32'()],
+ #diameter_caps{}}).
recv_CEA(CEA, Svc) ->
try_it([fun rCEA/2, CEA, Svc]).
@@ -95,7 +99,7 @@ try_it([Fun | Args]) ->
try apply(Fun, Args) of
T -> {ok, T}
catch
- throw: ?FAILURE(Reason) -> {error, {Reason, Args}}
+ throw: ?FAILURE(Reason) -> {error, Reason}
end.
%% mk_caps/2
@@ -126,28 +130,28 @@ mk_caps(Caps0, Opts) ->
set_cap({Key, _}, _) ->
?THROW({duplicate, Key}).
-cap(K, V) when K == 'Origin-Host';
- K == 'Origin-Realm';
- K == 'Vendor-Id';
- K == 'Product-Name' ->
+cap(K, V)
+ when K == 'Origin-Host';
+ K == 'Origin-Realm';
+ K == 'Vendor-Id';
+ K == 'Product-Name' ->
V;
cap('Host-IP-Address', Vs)
when is_list(Vs) ->
lists:map(fun ipaddr/1, Vs);
-cap('Firmware-Revision', V) ->
+cap(K, V)
+ when K == 'Firmware-Revision';
+ K == 'Origin-State-Id' ->
[V];
-%% Not documented but accept it as long as it's what we support.
-cap('Inband-Security-Id', [0] = Vs) -> %% NO_INBAND_SECURITY
- Vs;
-
-cap(K, Vs) when K /= 'Inband-Security-Id', is_list(Vs) ->
+cap(_, Vs)
+ when is_list(Vs) ->
Vs;
cap(K, V) ->
- ?THROW({invalid, K, V}).
+ ?THROW({invalid, {K,V}}).
ipaddr(A) ->
try
@@ -161,28 +165,10 @@ ipaddr(A) ->
%%
%% Build a CER record to send to a remote peer.
-bCER(#diameter_caps{origin_host = Host,
- origin_realm = Realm,
- host_ip_address = Addrs,
- vendor_id = Vid,
- product_name = Name,
- origin_state_id = OSI,
- supported_vendor_id = SVid,
- auth_application_id = AuId,
- acct_application_id = AcId,
- vendor_specific_application_id = VSA,
- firmware_revision = Rev}) ->
- #diameter_base_CER{'Origin-Host' = Host,
- 'Origin-Realm' = Realm,
- 'Host-IP-Address' = Addrs,
- 'Vendor-Id' = Vid,
- 'Product-Name' = Name,
- 'Origin-State-Id' = OSI,
- 'Supported-Vendor-Id' = SVid,
- 'Auth-Application-Id' = AuId,
- 'Acct-Application-Id' = AcId,
- 'Vendor-Specific-Application-Id' = VSA,
- 'Firmware-Revision' = Rev}.
+%% Use the fact that diameter_caps has the same field names as CER.
+bCER(#diameter_caps{} = Rec) ->
+ #diameter_base_CER{}
+ = list_to_tuple([diameter_base_CER | tl(tuple_to_list(Rec))]).
%% rCER/2
%%
@@ -219,19 +205,16 @@ bCER(#diameter_caps{origin_host = Host,
%% That is, each side sends all of its capabilities and is responsible for
%% not sending commands that the peer doesn't support.
-%% TODO: Make it an option to send only common applications in CEA to
-%% allow backwards compatibility, and also because there are likely
-%% servers that expect this. Or maybe a callback.
-
%% 6.10. Inband-Security-Id AVP
%%
%% NO_INBAND_SECURITY 0
%% This peer does not support TLS. This is the default value, if the
%% AVP is omitted.
+%%
+%% TLS 1
+%% This node supports TLS security, as defined by [TLS].
rCER(CER, #diameter_service{capabilities = LCaps} = Svc) ->
- #diameter_base_CER{'Inband-Security-Id' = RIS}
- = CER,
#diameter_base_CEA{}
= CEA
= cea_from_cer(bCER(LCaps)),
@@ -241,59 +224,81 @@ rCER(CER, #diameter_service{capabilities = LCaps} = Svc) ->
{SApps,
RCaps,
- build_CEA([] == SApps,
- RIS,
- lists:member(?NO_INBAND_SECURITY, RIS),
- CEA#diameter_base_CEA{'Result-Code' = ?SUCCESS,
- 'Inband-Security-Id' = []})}.
-
-%% TODO: 5.3 of RFC3588 says we MUST return DIAMETER_NO_COMMON_APPLICATION
-%% in the CEA and SHOULD disconnect the transport. However, we have
-%% no way to guarantee the send before disconnecting.
+ build_CEA(SApps,
+ LCaps,
+ RCaps,
+ CEA#diameter_base_CEA{'Result-Code' = ?SUCCESS})}.
-build_CEA(true, _, _, CEA) ->
+build_CEA([], _, _, CEA) ->
CEA#diameter_base_CEA{'Result-Code' = ?NOAPP};
-build_CEA(false, [_|_], false, CEA) ->
- CEA#diameter_base_CEA{'Result-Code' = ?NOSECURITY};
-build_CEA(false, [_|_], true, CEA) ->
- CEA#diameter_base_CEA{'Inband-Security-Id' = [?NO_INBAND_SECURITY]};
-build_CEA(false, [], false, CEA) ->
- CEA.
+
+build_CEA(_, LCaps, RCaps, CEA) ->
+ case common_security(LCaps, RCaps) of
+ [] ->
+ CEA#diameter_base_CEA{'Result-Code' = ?NOSECURITY};
+ [_] = IS ->
+ CEA#diameter_base_CEA{'Inband-Security-Id' = IS}
+ end.
+
+%% common_security/2
+
+common_security(#diameter_caps{inband_security_id = LS},
+ #diameter_caps{inband_security_id = RS}) ->
+ cs(LS, RS).
+
+%% Unspecified is equivalent to NO_INBAND_SECURITY.
+cs([], RS) ->
+ cs([?NO_INBAND_SECURITY], RS);
+cs(LS, []) ->
+ cs(LS, [?NO_INBAND_SECURITY]);
+
+%% Agree on TLS if both parties support it. When sending CEA, this is
+%% to ensure the peer is clear that we will be expecting a TLS
+%% handshake since there is no ssl:maybe_accept that would allow the
+%% peer to choose between TLS or not upon reception of our CEA. When
+%% receiving CEA it deals with a server that isn't explicit about its choice.
+%% TODO: Make the choice configurable.
+cs(LS, RS) ->
+ Is = ordsets:to_list(ordsets:intersection(ordsets:from_list(LS),
+ ordsets:from_list(RS))),
+ case lists:member(?TLS, Is) of
+ true ->
+ [?TLS];
+ false when [] == Is ->
+ Is;
+ false ->
+ [hd(Is)] %% probably NO_INBAND_SECURITY
+ end.
+%% The only two values defined by RFC 3588 are NO_INBAND_SECURITY and
+%% TLS but don't enforce this. In theory this allows some other
+%% security mechanism we don't have to know about, although in
+%% practice something there may be a need for more synchronization
+%% than notification by way of an event subscription offers.
%% cea_from_cer/1
+%% CER is a subset of CEA, the latter adding Result-Code and a few
+%% more AVP's.
cea_from_cer(#diameter_base_CER{} = CER) ->
lists:foldl(fun(F,A) -> to_cea(CER, F, A) end,
#diameter_base_CEA{},
record_info(fields, diameter_base_CER)).
to_cea(CER, Field, CEA) ->
- try ?BASE:'#info-'(diameter_base_CEA, {index, Field}) of
- N ->
- setelement(N, CEA, ?BASE:'#get-'(Field, CER))
+ try ?BASE:'#get-'(Field, CER) of
+ V -> ?BASE:'#set-'({Field, V}, CEA)
catch
- error: _ ->
- CEA
+ error: _ -> CEA
end.
-
+
%% rCEA/2
-rCEA(CEA, #diameter_service{capabilities = LCaps} = Svc)
- when is_record(CEA, diameter_base_CEA) ->
- #diameter_base_CEA{'Result-Code' = RC}
- = CEA,
-
- RC == ?SUCCESS orelse ?THROW({'Result-Code', RC}),
-
+rCEA(CEA, #diameter_service{capabilities = LCaps} = Svc) ->
RCaps = capx_to_caps(CEA),
SApps = common_applications(LCaps, RCaps, Svc),
+ IS = common_security(LCaps, RCaps),
- [] == SApps andalso ?THROW({no_common_apps, LCaps, RCaps}),
-
- {SApps, RCaps};
-
-rCEA(CEA, _Svc) ->
- ?THROW({invalid, CEA}).
+ {SApps, IS, RCaps}.
%% capx_to_caps/1
diff --git a/lib/diameter/src/app/diameter_codec.erl b/lib/diameter/src/base/diameter_codec.erl
index d88f42fb7c..a94d37f7a8 100644
--- a/lib/diameter/src/app/diameter_codec.erl
+++ b/lib/diameter/src/base/diameter_codec.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -63,9 +63,9 @@ encode(Mod, #diameter_packet{} = Pkt) ->
e(Mod, Pkt)
catch
error: Reason ->
- %% Be verbose rather than letting the emulator truncate the
- %% error report.
- X = {Reason, ?STACK},
+ %% Be verbose since a crash report may be truncated and
+ %% encode errors are self-inflicted.
+ X = {?MODULE, encode, {Reason, ?STACK}},
diameter_lib:error_report(X, {?MODULE, encode, [Mod, Pkt]}),
exit(X)
end;
@@ -91,7 +91,8 @@ e(_, #diameter_packet{msg = [#diameter_header{} = Hdr | As]} = Pkt) ->
Flags = make_flags(0, Hdr),
- Pkt#diameter_packet{bin = <<Vsn:8, Length:24,
+ Pkt#diameter_packet{header = Hdr,
+ bin = <<Vsn:8, Length:24,
Flags:8, Code:24,
Aid:32,
Hid:32,
@@ -190,26 +191,13 @@ encode_avps(Avps) ->
%% msg_header/3
-msg_header(Mod, MsgName, Header) ->
- {Code, Flags, ApplId} = h(Mod, MsgName, Header),
- {Code, p(Flags, Header), ApplId}.
-
-%% 6.2 of 3588 requires the same 'P' bit on an answer as on the
-%% request.
-
-p(Flags, #diameter_header{is_request = true,
- is_proxiable = P}) ->
- Flags band (2#10110000 bor choose(P, 2#01000000, 0));
-p(Flags, _) ->
- Flags.
-
-h(Mod, 'answer-message' = MsgName, Header) ->
+msg_header(Mod, 'answer-message' = MsgName, Header) ->
?BASE = Mod,
#diameter_header{cmd_code = Code} = Header,
{_, Flags, ApplId} = ?BASE:msg_header(MsgName),
{Code, Flags, ApplId};
-h(Mod, MsgName, _) ->
+msg_header(Mod, MsgName, _) ->
Mod:msg_header(MsgName).
%% rec2msg/2
@@ -345,6 +333,9 @@ decode_header(_) ->
%% wraparound counter. The 8-bit counter is incremented each time the
%% system is restarted.
+sequence_numbers({_,_} = T) ->
+ T;
+
sequence_numbers(#diameter_packet{bin = Bin})
when is_binary(Bin) ->
sequence_numbers(Bin);
@@ -554,8 +545,3 @@ pack_avp(Code, Flags, Vid, Sz, Bin) ->
pack_avp(Code, Flags, Sz, Bin) ->
Length = Sz + 8,
<<Code:32, Flags:8, Length:24, Bin/binary>>.
-
-%% ===========================================================================
-
-choose(true, X, _) -> X;
-choose(false, _, X) -> X.
diff --git a/lib/diameter/src/app/diameter_config.erl b/lib/diameter/src/base/diameter_config.erl
index a6b48fe65b..63d28f25a2 100644
--- a/lib/diameter/src/app/diameter_config.erl
+++ b/lib/diameter/src/base/diameter_config.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -97,6 +97,9 @@
-record(monitor, {mref = make_ref() :: reference(),
service}). %% name
+%% The default sequence mask.
+-define(NOMASK, {0,32}).
+
%% Time to lay low before restarting a dead service.
-define(RESTART_SLEEP, 2000).
@@ -519,6 +522,7 @@ rm(SvcName, L) ->
Refs = lists:map(fun(#transport{ref = R}) -> R end, L),
case stop_transport(SvcName, Refs) of
ok ->
+ diameter_stats:flush(Refs),
lists:foreach(fun delete_object/1, L);
{error, _} = No ->
No
@@ -548,9 +552,11 @@ make_config(SvcName, Opts) ->
ok = encode_CER(COpts),
- Os = split(Opts, [{[fun erlang:is_boolean/1], false, share_peers},
- {[fun erlang:is_boolean/1], false, use_shared_peers},
- {[fun erlang:is_pid/1, false], false, monitor}]),
+ Os = split(Opts, fun opt/2, [{false, share_peers},
+ {false, use_shared_peers},
+ {false, monitor},
+ {?NOMASK, sequence},
+ {nodes, restrict_connections}]),
%% share_peers and use_shared_peers are currently undocumented.
#service{name = SvcName,
@@ -558,11 +564,66 @@ make_config(SvcName, Opts) ->
capabilities = Caps},
options = Os}.
+split(Opts, F, Defs) ->
+ [{K, F(K, get_opt(K, Opts, D))} || {D,K} <- Defs].
+
+opt(K, false = B)
+ when K /= sequence ->
+ B;
+
+opt(K, true = B)
+ when K == share_peer;
+ K == use_shared_peers ->
+ B;
+
+opt(monitor, P)
+ when is_pid(P) ->
+ P;
+
+opt(restrict_connections, T)
+ when T == node;
+ T == nodes;
+ T == [];
+ is_atom(hd(T)) ->
+ T;
+
+opt(restrict_connections = K, F) ->
+ try diameter_lib:eval(F) of %% no guarantee that it won't fail later
+ Nodes when is_list(Nodes) ->
+ F;
+ V ->
+ ?THROW({value, {K,V}})
+ catch
+ E:R ->
+ ?THROW({value, {K, E, R, ?STACK}})
+ end;
+
+opt(sequence, {_,_} = T) ->
+ sequence(T);
+
+opt(sequence = K, F) ->
+ try diameter_lib:eval(F) of
+ T -> sequence(T)
+ catch
+ E:R ->
+ ?THROW({value, {K, E, R, ?STACK}})
+ end;
+
+opt(K, _) ->
+ ?THROW({value, K}).
+
+sequence({H,N} = T)
+ when 0 =< N, N =< 32, 0 =< H, 0 == H bsr N ->
+ T;
+
+sequence(_) ->
+ ?THROW({value, sequence}).
+
make_caps(Caps, Opts) ->
case diameter_capx:make_caps(Caps, Opts) of
{ok, T} ->
T;
- {error, {Reason, _}} ->
+ {error, Reason} ->
?THROW(Reason)
end.
@@ -600,11 +661,18 @@ app_acc({application, Opts}, Acc) ->
module = init_mod(Mod),
init_state = ModS,
mutable = init_mutable(M),
- answer_errors = init_answers(A)}
+ options = [{answer_errors, init_answers(A)}]}
| Acc];
app_acc(_, Acc) ->
Acc.
+init_mod(#diameter_callback{} = R) ->
+ init_mod([diameter_callback, R]);
+init_mod([diameter_callback, #diameter_callback{}] = L) ->
+ L;
+init_mod([diameter_callback = M | L])
+ when is_list(L) ->
+ [M, init_cb(L)];
init_mod(M)
when is_atom(M) ->
[M];
@@ -614,6 +682,14 @@ init_mod([M|_] = L)
init_mod(M) ->
?THROW({module, M}).
+init_cb(List) ->
+ Fields = record_info(fields, diameter_callback),
+ Defaults = lists:zip(Fields, tl(tuple_to_list(#diameter_callback{}))),
+ Values = [V || F <- Fields,
+ D <- [proplists:get_value(F, Defaults)],
+ V <- [proplists:get_value(F, List, D)]],
+ #diameter_callback{} = list_to_tuple([diameter_callback | Values]).
+
init_mutable(M)
when M == true;
M == false ->
@@ -647,21 +723,6 @@ get_opt(Key, List, Def) ->
_ -> ?THROW({arity, Key})
end.
-split(Opts, Defs) ->
- [{K, value(D, Opts)} || {_,_,K} = D <- Defs].
-
-value({Preds, Def, Key}, Opts) ->
- V = get_opt(Key, Opts, Def),
- lists:any(fun(P) -> pred(P,V) end, Preds)
- orelse ?THROW({value, Key}),
- V.
-
-pred(F, V)
- when is_function(F) ->
- F(V);
-pred(T, V) ->
- T == V.
-
cb(M,F) ->
try M:F() of
V -> V
diff --git a/lib/diameter/src/app/diameter_dbg.erl b/lib/diameter/src/base/diameter_dbg.erl
index 5b0ac3a3b6..5b0ac3a3b6 100644
--- a/lib/diameter/src/app/diameter_dbg.erl
+++ b/lib/diameter/src/base/diameter_dbg.erl
diff --git a/lib/diameter/src/app/diameter_dict.erl b/lib/diameter/src/base/diameter_dict.erl
index 3b9ba00a3f..3b9ba00a3f 100644
--- a/lib/diameter/src/app/diameter_dict.erl
+++ b/lib/diameter/src/base/diameter_dict.erl
diff --git a/lib/diameter/src/app/diameter_info.erl b/lib/diameter/src/base/diameter_info.erl
index 39d32d07cd..39d32d07cd 100644
--- a/lib/diameter/src/app/diameter_info.erl
+++ b/lib/diameter/src/base/diameter_info.erl
diff --git a/lib/diameter/src/app/diameter_internal.hrl b/lib/diameter/src/base/diameter_internal.hrl
index 63b35550a8..63b35550a8 100644
--- a/lib/diameter/src/app/diameter_internal.hrl
+++ b/lib/diameter/src/base/diameter_internal.hrl
diff --git a/lib/diameter/src/app/diameter_lib.erl b/lib/diameter/src/base/diameter_lib.erl
index 362d593b24..362d593b24 100644
--- a/lib/diameter/src/app/diameter_lib.erl
+++ b/lib/diameter/src/base/diameter_lib.erl
diff --git a/lib/diameter/src/app/diameter_misc_sup.erl b/lib/diameter/src/base/diameter_misc_sup.erl
index 4e40476f14..4e40476f14 100644
--- a/lib/diameter/src/app/diameter_misc_sup.erl
+++ b/lib/diameter/src/base/diameter_misc_sup.erl
diff --git a/lib/diameter/src/app/diameter_peer.erl b/lib/diameter/src/base/diameter_peer.erl
index 3e78c4caef..1b2f32ddff 100644
--- a/lib/diameter/src/app/diameter_peer.erl
+++ b/lib/diameter/src/base/diameter_peer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -27,12 +27,15 @@
up/2]).
%% ... and the stack.
--export([start/3,
+-export([start/1,
send/2,
close/1,
abort/1,
notify/2]).
+%% Old interface only called from old code.
+-export([start/3]). %% < diameter-1.2 (R15B02)
+
%% Server start.
-export([start_link/0]).
@@ -57,6 +60,11 @@
%% Server state.
-record(state, {id = now()}).
+%% Default transport_module/config.
+-define(DEFAULT_TMOD, diameter_tcp).
+-define(DEFAULT_TCFG, []).
+-define(DEFAULT_TTMO, infinity).
+
%%% ---------------------------------------------------------------------------
%%% # notify/2
%%% ---------------------------------------------------------------------------
@@ -68,9 +76,119 @@ notify(SvcName, T) ->
%%% # start/3
%%% ---------------------------------------------------------------------------
-start(T, Opts, #diameter_service{} = Svc) ->
- {Mod, Cfg} = split_transport(Opts),
- apply(Mod, start, [T, Svc, Cfg]).
+%% From old code: make it restart.
+start(_T, _Opts, #diameter_service{}) ->
+ {error, restart}.
+
+%%% ---------------------------------------------------------------------------
+%%% # start/1
+%%% ---------------------------------------------------------------------------
+
+-spec start({T, [Opt], #diameter_service{}})
+ -> {TPid, [Addr], Tmo, Data}
+ | {error, [term()]}
+ when T :: {connect|accept, diameter:transport_ref()},
+ Opt :: diameter:transport_opt(),
+ TPid :: pid(),
+ Addr :: inet:ip_address(),
+ Tmo :: non_neg_integer() | infinity,
+ Data :: {{T, Mod, Cfg}, [Mod], [{T, [Mod], Cfg}], [Err]},
+ Mod :: module(),
+ Cfg :: term(),
+ Err :: term()
+ ; ({#diameter_service{}, Tmo, Data})
+ -> {TPid, [Addr], Tmo, Data}
+ | {error, [term()]}
+ when TPid :: pid(),
+ Addr :: inet:ip_address(),
+ Tmo :: non_neg_integer() | infinity,
+ Data :: {{T, Mod, Cfg}, [Mod], [{T, [Mod], Cfg}], [Err]},
+ T :: {connect|accept, diameter:transport_ref()},
+ Mod :: module(),
+ Cfg :: term(),
+ Err :: term().
+
+%% Initial start.
+start({T, Opts, #diameter_service{} = Svc}) ->
+ start(T, Svc, pair(Opts, [], []), []);
+
+%% Subsequent start.
+start({#diameter_service{} = Svc, Tmo, {{T, _, Cfg}, Ms, Rest, Errs}}) ->
+ start(T, Ms, Cfg, Svc, Tmo, Rest, Errs).
+
+%% pair/3
+%%
+%% Pair transport modules with config.
+
+%% Another transport_module: accumulate it.
+pair([{transport_module, M} | Rest], Mods, Acc) ->
+ pair(Rest, [M|Mods], Acc);
+
+%% Another transport_config: accumulate another tuple.
+pair([{transport_config = T, C} | Rest], Mods, Acc) ->
+ pair([{T, C, ?DEFAULT_TTMO} | Rest], Mods, Acc);
+pair([{transport_config, C, Tmo} | Rest], Mods, Acc) ->
+ pair(Rest, [], acc({Mods, C, Tmo}, Acc));
+
+pair([_ | Rest], Mods, Acc) ->
+ pair(Rest, Mods, Acc);
+
+%% No transport_module or transport_config: defaults.
+pair([], [], []) ->
+ [{[?DEFAULT_TMOD], ?DEFAULT_TCFG, ?DEFAULT_TTMO}];
+
+%% One transport_module, one transport_config.
+pair([], [M], [{[], Cfg, Tmo}]) ->
+ [{[M], Cfg, Tmo}];
+
+%% Trailing transport_module: default transport_config.
+pair([], [_|_] = Mods, Acc) ->
+ lists:reverse(acc({Mods, ?DEFAULT_TCFG, ?DEFAULT_TTMO}, Acc));
+
+pair([], [], Acc) ->
+ lists:reverse(def(Acc)).
+
+%% acc/2
+
+acc(T, Acc) ->
+ [T | def(Acc)].
+
+%% def/1
+%%
+%% Default module of previous pair if none were specified.
+
+def([{[], Cfg, Tmo} | Acc]) ->
+ [{[?DEFAULT_TMOD], Cfg, Tmo} | Acc];
+def(Acc) ->
+ Acc.
+
+%% start/4
+
+start(T, Svc, [{Ms, Cfg, Tmo} | Rest], Errs) ->
+ start(T, Ms, Cfg, Svc, Tmo, Rest, Errs);
+
+start(_, _, [], Errs) ->
+ {error, Errs}.
+
+%% start/7
+
+start(T, [], _, Svc, _, Rest, Errs) ->
+ start(T, Svc, Rest, Errs);
+
+start(T, [M|Ms], Cfg, Svc, Tmo, Rest, Errs) ->
+ case start(M, [T, Svc, Cfg]) of
+ {ok, TPid} ->
+ {TPid, [], Tmo, {{T, M, Cfg}, Ms, Rest, Errs}};
+ {ok, TPid, [_|_] = Addrs} ->
+ {TPid, Addrs, Tmo, {{T, M, Cfg}, Ms, Rest, Errs}};
+ E ->
+ start(T, Ms, Cfg, Svc, Tmo, Rest, [E|Errs])
+ end.
+
+%% start/2
+
+start(Mod, Args) ->
+ apply(Mod, start, Args).
%%% ---------------------------------------------------------------------------
%%% # up/[12]
@@ -204,21 +322,6 @@ bang(undefined = No, _) ->
bang(Pid, T) ->
Pid ! T.
-%% split_transport/1
-%%
-%% Split options into transport module, transport config and
-%% remaining options.
-
-split_transport(Opts) ->
- {[M,C], _} = proplists:split(Opts, [transport_module,
- transport_config]),
- {value(M, diameter_tcp), value(C, [])}.
-
-value([{_,V}], _) ->
- V;
-value([], V) ->
- V.
-
%% call/1
call(Request) ->
diff --git a/lib/diameter/src/base/diameter_peer_fsm.erl b/lib/diameter/src/base/diameter_peer_fsm.erl
new file mode 100644
index 0000000000..c4320fcb99
--- /dev/null
+++ b/lib/diameter/src/base/diameter_peer_fsm.erl
@@ -0,0 +1,1162 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This module implements (as a process) the RFC 3588 Peer State
+%% Machine modulo the necessity of adapting the peer election to the
+%% fact that we don't know the identity of a peer until we've
+%% received a CER/CEA from it.
+%%
+
+-module(diameter_peer_fsm).
+-behaviour(gen_server).
+
+%% Interface towards diameter_watchdog.
+-export([start/3]).
+
+%% gen_server callbacks
+-export([init/1,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ terminate/2,
+ code_change/3]).
+
+%% diameter_peer_fsm_sup callback
+-export([start_link/1]).
+
+%% internal callbacks
+-export([match/1]).
+
+-include_lib("diameter/include/diameter.hrl").
+-include("diameter_internal.hrl").
+-include("diameter_gen_base_rfc3588.hrl").
+
+%% Values of Disconnect-Cause in DPR.
+-define(GOAWAY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_DO_NOT_WANT_TO_TALK_TO_YOU').
+-define(REBOOT, ?'DIAMETER_BASE_DISCONNECT-CAUSE_REBOOTING').
+-define(BUSY, ?'DIAMETER_BASE_DISCONNECT-CAUSE_BUSY').
+
+-define(NO_INBAND_SECURITY, 0).
+-define(TLS, 1).
+
+%% Keys in process dictionary.
+-define(CB_KEY, cb). %% capabilities callback
+-define(DPR_KEY, dpr). %% disconnect callback
+-define(DWA_KEY, dwa). %% outgoing DWA
+-define(REF_KEY, ref). %% transport_ref()
+-define(Q_KEY, q). %% transport start queue
+-define(START_KEY, start). %% start of connected transport
+-define(SEQUENCE_KEY, mask). %% mask for sequence numbers
+-define(RESTRICT_KEY, restrict). %% nodes for connection check
+
+%% The default sequence mask.
+-define(NOMASK, {0,32}).
+
+%% A 2xxx series Result-Code. Not necessarily 2001.
+-define(IS_SUCCESS(N), 2 == (N) div 1000).
+
+%% Guards.
+-define(IS_UINT32(N), (is_integer(N) andalso 0 =< N andalso 0 == N bsr 32)).
+-define(IS_TIMEOUT(N), ?IS_UINT32(N)).
+-define(IS_CAUSE(N), N == ?REBOOT; N == rebooting;
+ N == ?GOAWAY; N == goaway;
+ N == ?BUSY; N == busy).
+
+%% RFC 3588:
+%%
+%% Timeout An application-defined timer has expired while waiting
+%% for some event.
+%%
+-define(EVENT_TIMEOUT, 10000).
+%% Default timeout for reception of CER/CEA.
+
+%% Default timeout for DPA in response to DPR. A bit short but the
+%% timeout used to be hardcoded. (So it could be worse.)
+-define(DPA_TIMEOUT, 1000).
+
+-type uint32() :: diameter:'Unsigned32'().
+
+-record(state,
+ {state %% of RFC 3588 Peer State Machine
+ :: 'Wait-Conn-Ack' %% old code
+ | {'Wait-Conn-Ack', uint32()}
+ | recv_CER
+ | 'Wait-CEA' %% old code
+ | {'Wait-CEA', uint32(), uint32()}
+ | 'Open',
+ mode :: accept | connect | {connect, reference()},
+ parent :: pid(), %% watchdog process
+ transport :: pid(), %% transport process
+ service :: #diameter_service{},
+ dpr = false :: false | {uint32(), uint32()}}).
+ %% | hop by hop and end to end identifiers
+
+%% There are non-3588 states possible as a consequence of 5.6.1 of the
+%% standard and the corresponding problem for incoming CEA's: we don't
+%% know who we're talking to until either a CER or CEA has been
+%% received. The CEA problem in particular makes it impossible to
+%% follow the state machine exactly as documented in 3588: there can
+%% be no election until the CEA arrives and we have an Origin-Host to
+%% elect.
+
+%%
+%% Once upon a time start/2 started a process akin to that started by
+%% start/3 below, which in turn started a watchdog/transport process
+%% with the result that the watchdog could send DWR/DWA regardless of
+%% whether or not the corresponding Peer State Machine was in its open
+%% state; that is, before capabilities exchange had taken place. This
+%% is not what RFC's 3588 and 3539 say (albeit not very clearly).
+%% Watchdog messages are only exchanged on *open* connections, so the
+%% 3539 state machine is more naturally placed on top of the 3588 Peer
+%% State Machine rather than closer to the transport. This is what we
+%% now do below: connect/accept call diameter_watchdog and return the
+%% pid of the watchdog process, and the watchdog in turn calls start/3
+%% below to start the process implementing the Peer State Machine. The
+%% former is a "peer" in diameter_service while the latter is a
+%% "conn". In a sense, diameter_service sees the watchdog as
+%% implementing the Peer State Machine and the process implemented
+%% here as being the transport, not being aware of the watchdog at
+%% all.
+%%
+
+%%% ---------------------------------------------------------------------------
+%%% # start({connect|accept, Ref}, Opts, Service)
+%%%
+%%% Output: Pid
+%%% ---------------------------------------------------------------------------
+
+-spec start(T, [Opt], #diameter_service{} %% from old code
+ | {diameter:sequence(),
+ diameter:restriction(),
+ #diameter_service{}})
+ -> pid()
+ when T :: {connect|accept, diameter:transport_ref()},
+ Opt :: diameter:transport_opt().
+
+%% diameter_config requires a non-empty list of applications on the
+%% service but diameter_service then constrains the list to any
+%% specified on the transport in question. Check here that the list is
+%% still non-empty.
+
+start({_,_} = Type, Opts, MS) ->
+ {ok, Pid} = diameter_peer_fsm_sup:start_child({self(), Type, Opts, MS}),
+ Pid.
+
+start_link(T) ->
+ {ok, _} = proc_lib:start_link(?MODULE,
+ init,
+ [T],
+ infinity,
+ diameter_lib:spawn_opts(server, [])).
+
+%%% ---------------------------------------------------------------------------
+%%% ---------------------------------------------------------------------------
+
+%% init/1
+
+init(T) ->
+ proc_lib:init_ack({ok, self()}),
+ gen_server:enter_loop(?MODULE, [], i(T)).
+
+i({WPid, Type, Opts, #diameter_service{} = Svc}) -> %% from old code
+ i({WPid, Type, Opts, {?NOMASK, [node() | nodes()], Svc}});
+
+i({WPid, T, Opts, {Mask, Nodes, #diameter_service{applications = Apps,
+ capabilities = LCaps}
+ = Svc}}) ->
+ [] /= Apps orelse ?ERROR({no_apps, T, Opts}),
+ putr(?DWA_KEY, dwa(LCaps)),
+ {M, Ref} = T,
+ diameter_stats:reg(Ref),
+ {[Cs,Ds], Rest} = proplists:split(Opts, [capabilities_cb, disconnect_cb]),
+ putr(?CB_KEY, {Ref, [F || {_,F} <- Cs]}),
+ putr(?DPR_KEY, [F || {_, F} <- Ds]),
+ putr(?REF_KEY, Ref),
+ putr(?SEQUENCE_KEY, Mask),
+ putr(?RESTRICT_KEY, Nodes),
+ erlang:monitor(process, WPid),
+ {TPid, Addrs} = start_transport(T, Rest, Svc),
+ Tmo = proplists:get_value(capx_timeout, Opts, ?EVENT_TIMEOUT),
+ ?IS_TIMEOUT(Tmo) orelse ?ERROR({invalid, {capx_timeout, Tmo}}),
+ #state{state = {'Wait-Conn-Ack', Tmo},
+ parent = WPid,
+ transport = TPid,
+ mode = M,
+ service = svc(Svc, Addrs)}.
+%% The transport returns its local ip addresses so that different
+%% transports on the same service can use different local addresses.
+%% The local addresses are put into Host-IP-Address avps here when
+%% sending capabilities exchange messages.
+%%
+%% Invalid transport config may cause us to crash but note that the
+%% watchdog start (start/2) succeeds regardless so as not to crash the
+%% service.
+
+start_transport(T, Opts, #diameter_service{capabilities = LCaps} = Svc) ->
+ Addrs0 = LCaps#diameter_caps.host_ip_address,
+ start_transport(Addrs0, {T, Opts, Svc}).
+
+start_transport(Addrs0, T) ->
+ case diameter_peer:start(T) of
+ {TPid, Addrs, Tmo, Data} ->
+ erlang:monitor(process, TPid),
+ q_next(TPid, Addrs0, Tmo, Data),
+ {TPid, addrs(Addrs, Addrs0)};
+ No ->
+ exit({shutdown, No})
+ end.
+
+addrs([], Addrs0) ->
+ Addrs0;
+addrs(Addrs, _) ->
+ Addrs.
+
+svc(Svc, []) ->
+ Svc;
+svc(Svc, Addrs) ->
+ readdr(Svc, Addrs).
+
+readdr(#diameter_service{capabilities = LCaps0} = Svc, Addrs) ->
+ LCaps = LCaps0#diameter_caps{host_ip_address = Addrs},
+ Svc#diameter_service{capabilities = LCaps}.
+
+%% The 4-tuple Data returned from diameter_peer:start/1 identifies the
+%% transport module/config use to start the transport process in
+%% question as well as any alternates to try if a connection isn't
+%% established within Tmo.
+q_next(TPid, Addrs0, Tmo, {_,_,_,_} = Data) ->
+ send_after(Tmo, {connection_timeout, TPid}),
+ putr(?Q_KEY, {Addrs0, Tmo, Data}).
+
+%% Connection has been established: retain the started
+%% pid/module/config in the process dictionary. This is a part of the
+%% interface defined by this module, so that the transport pid can be
+%% found when constructing service_info (in order to extract further
+%% information from it).
+keep_transport(TPid) ->
+ {_, _, {{_,_,_} = T, _, _, _}} = eraser(?Q_KEY),
+ putr(?START_KEY, {TPid, T}).
+
+send_after(infinity, _) ->
+ ok;
+send_after(Tmo, T) ->
+ erlang:send_after(Tmo, self(), T).
+
+%% handle_call/3
+
+handle_call(_, _, State) ->
+ {reply, nok, State}.
+
+%% handle_cast/2
+
+handle_cast(_, State) ->
+ {noreply, State}.
+
+%% handle_info/1
+
+handle_info(T, #state{} = State) ->
+ try transition(T, State) of
+ ok ->
+ {noreply, State};
+ #state{state = X} = S ->
+ ?LOGC(X =/= State#state.state, transition, X),
+ {noreply, S};
+ {stop, Reason} ->
+ ?LOG(stop, Reason),
+ x(Reason, State);
+ stop ->
+ ?LOG(stop, T),
+ x(T, State)
+ catch
+ exit: {diameter_codec, encode, _} = Reason ->
+ close_wd(Reason, State#state.parent),
+ ?LOG(stop, Reason),
+ %% diameter_codec:encode/2 emits an error report. Only
+ %% indicate the probable reason here.
+ diameter_lib:info_report(probable_configuration_error,
+ insufficient_capabilities),
+ {stop, {shutdown, Reason}, State};
+ {?MODULE, Tag, Reason} ->
+ ?LOG(Tag, {Reason, T}),
+ {stop, {shutdown, Reason}, State}
+ end.
+%% The form of the throw caught here is historical. It's
+%% significant that it's not a 2-tuple, as in ?FAILURE(Reason),
+%% since these are caught elsewhere.
+
+%% Note that there's no guarantee that the service and transport
+%% capabilities are good enough to build a CER/CEA that can be
+%% succesfully encoded. It's not checked at diameter:add_transport/2
+%% since this can be called before creating the service.
+
+x(Reason, #state{} = S) ->
+ close_wd(Reason, S),
+ {stop, {shutdown, Reason}, S}.
+
+%% terminate/2
+
+terminate(_, _) ->
+ ok.
+
+%% code_change/3
+
+code_change(_, State, _) ->
+ {ok, State}.
+
+%%% ---------------------------------------------------------------------------
+%%% ---------------------------------------------------------------------------
+
+putr(Key, Val) ->
+ put({?MODULE, Key}, Val).
+
+getr(Key) ->
+ get({?MODULE, Key}).
+
+eraser(Key) ->
+ erase({?MODULE, Key}).
+
+%% transition/2
+
+%% Started in old code.
+transition(T, #state{state = 'Wait-Conn-Ack' = PS} = S) ->
+ transition(T, S#state{state = {PS, ?EVENT_TIMEOUT}});
+
+%% Connection to peer.
+transition({diameter, {TPid, connected, Remote}},
+ #state{transport = TPid,
+ state = PS,
+ mode = M}
+ = S) ->
+ {'Wait-Conn-Ack', _} = PS, %% assert
+ connect = M, %%
+ keep_transport(TPid),
+ send_CER(S#state{mode = {M, Remote}});
+
+%% Connection from peer.
+transition({diameter, {TPid, connected}},
+ #state{transport = TPid,
+ state = PS,
+ mode = M,
+ parent = Pid}
+ = S) ->
+ {'Wait-Conn-Ack', Tmo} = PS, %% assert
+ accept = M, %%
+ keep_transport(TPid),
+ Pid ! {accepted, self()},
+ start_timer(Tmo, S#state{state = recv_CER});
+
+%% Connection established after receiving a connection_timeout
+%% message. This may be followed by an incoming message which arrived
+%% before the transport was killed and this can't be distinguished
+%% from one from the transport that's been started to replace it.
+transition({diameter, {_, connected}}, _) ->
+ {stop, connection_timeout};
+transition({diameter, {_, connected, _}}, _) ->
+ {stop, connection_timeout};
+
+%% Connection has timed out: start an alternate.
+transition({connection_timeout = T, TPid},
+ #state{transport = TPid,
+ state = {'Wait-Conn-Ack', _}}
+ = S) ->
+ exit(TPid, {shutdown, T}),
+ start_next(S);
+
+%% Connect timeout after connection or alternate start: ignore.
+transition({connection_timeout, _}, _) ->
+ ok;
+
+%% Incoming message from the transport.
+transition({diameter, {recv, Pkt}}, S) ->
+ recv(Pkt, S);
+
+%% Timeout when still in the same state ...
+transition({timeout, PS}, #state{state = PS}) ->
+ {stop, {capx(PS), timeout}};
+
+%% ... or not.
+transition({timeout, _}, _) ->
+ ok;
+
+%% Outgoing message.
+transition({send, Msg}, #state{transport = TPid}) ->
+ send(TPid, Msg),
+ ok;
+
+%% Messages from old (diameter_service) code.
+transition(shutdown = T, #state{parent = Pid} = S) ->
+ transition({T, Pid, service}, S); %% Reason irrelevant: old code has no cb
+
+%% Request for graceful shutdown at remove_transport, stop_service of
+%% application shutdown.
+transition({shutdown = T, Pid}, S) ->
+ transition({T, Pid, transport}, S);
+transition({shutdown, Pid, Reason}, #state{parent = Pid, dpr = false} = S) ->
+ dpr(Reason, S);
+transition({shutdown, Pid, _}, #state{parent = Pid}) ->
+ ok;
+
+%% DPA reception has timed out.
+transition(dpa_timeout, _) ->
+ stop;
+
+%% Someone wants to know a resolved port: forward to the transport process.
+transition({resolve_port, _Pid} = T, #state{transport = TPid}) ->
+ TPid ! T,
+ ok;
+
+%% Parent has died.
+transition({'DOWN', _, process, WPid, _},
+ #state{parent = WPid}) ->
+ stop;
+
+%% Transport has died before connection timeout.
+transition({'DOWN', _, process, TPid, _},
+ #state{transport = TPid}
+ = S) ->
+ start_next(S);
+
+%% Transport has died after connection timeout.
+transition({'DOWN', _, process, _, _}, _) ->
+ ok;
+
+%% State query.
+transition({state, Pid}, #state{state = S, transport = TPid}) ->
+ Pid ! {self(), [S, TPid]},
+ ok.
+
+%% Crash on anything unexpected.
+
+capx(recv_CER) ->
+ 'CER';
+capx({'Wait-CEA', _, _}) ->
+ 'CEA'.
+
+%% start_next/1
+
+start_next(#state{service = Svc0} = S) ->
+ case getr(?Q_KEY) of
+ {Addrs0, Tmo, Data} ->
+ Svc = readdr(Svc0, Addrs0),
+ {TPid, Addrs} = start_transport(Addrs0, {Svc, Tmo, Data}),
+ S#state{transport = TPid,
+ service = svc(Svc, Addrs)};
+ undefined ->
+ stop
+ end.
+
+%% send_CER/1
+
+send_CER(#state{state = {'Wait-Conn-Ack', Tmo},
+ mode = {connect, Remote},
+ service = #diameter_service{capabilities = LCaps},
+ transport = TPid}
+ = S) ->
+ OH = LCaps#diameter_caps.origin_host,
+ req_send_CER(OH, Remote)
+ orelse
+ close({already_connected, Remote, LCaps}, S),
+ CER = build_CER(S),
+ ?LOG(send, 'CER'),
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}}
+ = Pkt
+ = encode(CER),
+ send(TPid, Pkt),
+ start_timer(Tmo, S#state{state = {'Wait-CEA', Hid, Eid}}).
+
+%% Register ourselves as connecting to the remote endpoint in
+%% question. This isn't strictly necessary since a peer implementing
+%% the 3588 Peer State Machine should reject duplicate connection's
+%% from the same peer but there's little point in us setting up a
+%% duplicate connection in the first place. This could also include
+%% the transport protocol being used but since we're blind to
+%% transport just avoid duplicate connections to the same host/port.
+req_send_CER(OriginHost, Remote) ->
+ register_everywhere({?MODULE, connection, OriginHost, {remote, Remote}}).
+
+%% start_timer/2
+
+start_timer(Tmo, #state{state = PS} = S) ->
+ erlang:send_after(Tmo, self(), {timeout, PS}),
+ S.
+
+%% build_CER/1
+
+build_CER(#state{service = #diameter_service{capabilities = LCaps}}) ->
+ {ok, CER} = diameter_capx:build_CER(LCaps),
+ CER.
+
+%% encode/1
+
+encode(Rec) ->
+ Seq = diameter_session:sequence(sequence()),
+ Hdr = #diameter_header{version = ?DIAMETER_VERSION,
+ end_to_end_id = Seq,
+ hop_by_hop_id = Seq},
+ diameter_codec:encode(?BASE, #diameter_packet{header = Hdr,
+ msg = Rec}).
+
+sequence() ->
+ case getr(?SEQUENCE_KEY) of
+ {_,_} = Mask ->
+ Mask;
+ undefined -> %% started in old code
+ putr(?SEQUENCE_KEY, ?NOMASK),
+ ?NOMASK
+ end.
+
+%% recv/2
+
+%% RFC 3588 has result code 5015 for an invalid length but if a
+%% transport is detecting message boundaries using the length header
+%% then a length error will likely lead to further errors.
+
+recv(#diameter_packet{header = #diameter_header{length = Len}
+ = Hdr,
+ bin = Bin},
+ S)
+ when Len < 20;
+ (0 /= Len rem 4 orelse bit_size(Bin) /= 8*Len) ->
+ discard(invalid_message_length, recv, [size(Bin),
+ bit_size(Bin) rem 8,
+ Hdr,
+ S]);
+
+recv(#diameter_packet{header = #diameter_header{} = Hdr}
+ = Pkt,
+ #state{parent = Pid}
+ = S) ->
+ Name = diameter_codec:msg_name(Hdr),
+ Pid ! {recv, self(), Name, Pkt},
+ diameter_stats:incr({msg_id(Name, Hdr), recv}), %% count received
+ rcv(Name, Pkt, S);
+
+recv(#diameter_packet{header = undefined,
+ bin = Bin}
+ = Pkt,
+ S) ->
+ recv(Pkt#diameter_packet{header = diameter_codec:decode_header(Bin)}, S);
+
+recv(Bin, S)
+ when is_binary(Bin) ->
+ recv(#diameter_packet{bin = Bin}, S);
+
+recv(#diameter_packet{header = false} = Pkt, S) ->
+ discard(truncated_header, recv, [Pkt, S]).
+
+msg_id({_,_,_} = T, _) ->
+ T;
+msg_id(_, Hdr) ->
+ diameter_codec:msg_id(Hdr).
+
+%% Treat invalid length as a transport error and die. Especially in
+%% the TCP case, in which there's no telling where the next message
+%% begins in the incoming byte stream, keeping a crippled connection
+%% alive may just make things worse.
+
+discard(Reason, F, A) ->
+ diameter_stats:incr(Reason),
+ diameter_lib:warning_report(Reason, {?MODULE, F, A}),
+ throw({?MODULE, abort, Reason}).
+
+%% rcv/3
+
+%% Incoming CEA.
+rcv('CEA',
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}}
+ = Pkt,
+ #state{state = {'Wait-CEA' = T, Hid, Eid}}
+ = S) ->
+ handle_CEA(Pkt, S#state{state = T});
+rcv('CEA', Pkt, #state{state = 'Wait-CEA'} = S) -> %% old code
+ handle_CEA(Pkt, S);
+
+%% Incoming CER
+rcv('CER' = N, Pkt, #state{state = recv_CER} = S) ->
+ handle_request(N, Pkt, S);
+
+%% Anything but CER/CEA in a non-Open state is an error, as is
+%% CER/CEA in anything but recv_CER/Wait-CEA.
+rcv(Name, _, #state{state = PS})
+ when PS /= 'Open';
+ Name == 'CER';
+ Name == 'CEA' ->
+ {stop, {Name, PS}};
+
+rcv(N, Pkt, S)
+ when N == 'DWR';
+ N == 'DPR' ->
+ handle_request(N, Pkt, S);
+
+%% DPA in response to DPR and with the expected identifiers.
+rcv('DPA' = N,
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}},
+ #state{transport = TPid,
+ dpr = {Hid, Eid}}) ->
+ diameter_peer:close(TPid),
+ {stop, N};
+
+%% Ignore anything else, an unsolicited DPA in particular.
+rcv(_, _, _) ->
+ ok.
+
+%% send/2
+
+%% Msg here could be a #diameter_packet or a binary depending on who's
+%% sending. In particular, the watchdog will send DWR as a binary
+%% while messages coming from clients will be in a #diameter_packet.
+send(Pid, Msg) ->
+ diameter_stats:incr({diameter_codec:msg_id(Msg), send}),
+ diameter_peer:send(Pid, Msg).
+
+%% handle_request/3
+
+handle_request(Type, #diameter_packet{} = Pkt, S) ->
+ ?LOG(recv, Type),
+ send_answer(Type, diameter_codec:decode(?BASE, Pkt), S).
+
+%% send_answer/3
+
+send_answer(Type, ReqPkt, #state{transport = TPid} = S) ->
+ #diameter_packet{header = H,
+ transport_data = TD}
+ = ReqPkt,
+
+ {Msg, PostF} = build_answer(Type, ReqPkt, S),
+
+ %% An answer message clears the R and T flags and retains the P
+ %% flag. The E flag is set at encode.
+ Pkt = #diameter_packet{header
+ = H#diameter_header{version = ?DIAMETER_VERSION,
+ is_request = false,
+ is_error = undefined,
+ is_retransmitted = false},
+ msg = Msg,
+ transport_data = TD},
+
+ send(TPid, diameter_codec:encode(?BASE, Pkt)),
+ eval(PostF, S).
+
+eval([F|A], S) ->
+ apply(F, A ++ [S]);
+eval(ok, S) ->
+ S.
+
+%% build_answer/3
+
+build_answer('CER',
+ #diameter_packet{msg = CER,
+ header = #diameter_header{version
+ = ?DIAMETER_VERSION,
+ is_error = false},
+ errors = []}
+ = Pkt,
+ S) ->
+ {SupportedApps, RCaps, #diameter_base_CEA{'Result-Code' = RC,
+ 'Inband-Security-Id' = IS}
+ = CEA}
+ = recv_CER(CER, S),
+
+ #diameter_caps{origin_host = {OH, DH}}
+ = Caps
+ = capz(caps(S), RCaps),
+
+ try
+ 2001 == RC %% DIAMETER_SUCCESS
+ orelse ?THROW(RC),
+ register_everywhere({?MODULE, connection, OH, DH})
+ orelse ?THROW(4003), %% DIAMETER_ELECTION_LOST
+ caps_cb(Caps)
+ of
+ N -> {cea(CEA, N), [fun open/5, Pkt,
+ SupportedApps,
+ Caps,
+ {accept, hd([_] = IS)}]}
+ catch
+ ?FAILURE(Reason) ->
+ rejected(Reason, {'CER', Reason, Caps, Pkt}, S)
+ end;
+
+%% The error checks below are similar to those in diameter_service for
+%% other messages. Should factor out the commonality.
+
+build_answer(Type,
+ #diameter_packet{header = H,
+ errors = Es}
+ = Pkt,
+ S) ->
+ RC = rc(H, Es),
+ {answer(Type, RC, Es, S), post(Type, RC, Pkt, S)}.
+
+cea(CEA, ok) ->
+ CEA;
+cea(CEA, 2001) ->
+ CEA;
+cea(CEA, RC) ->
+ CEA#diameter_base_CEA{'Result-Code' = RC}.
+
+post('CER' = T, RC, Pkt, S) ->
+ [fun close/2, {T, caps(S), {RC, Pkt}}];
+post(_, _, _, _) ->
+ ok.
+
+rejected({capabilities_cb, _F, Reason}, T, S) ->
+ rejected(Reason, T, S);
+
+rejected(discard, T, S) ->
+ close(T, S);
+rejected({N, Es}, T, S) ->
+ {answer('CER', N, Es, S), [fun close/2, T]};
+rejected(N, T, S) ->
+ rejected({N, []}, T, S).
+
+answer(Type, RC, Es, S) ->
+ set(answer(Type, RC, S), failed_avp([A || {_,A} <- Es])).
+
+answer(Type, RC, S) ->
+ answer_message(answer(Type, S), RC).
+
+%% answer_message/2
+
+answer_message([_ | Avps], RC)
+ when 3000 =< RC, RC < 4000 ->
+ ['answer-message', {'Result-Code', RC}
+ | lists:filter(fun is_origin/1, Avps)];
+
+answer_message(Msg, RC) ->
+ Msg ++ [{'Result-Code', RC}].
+
+is_origin({N, _}) ->
+ N == 'Origin-Host'
+ orelse N == 'Origin-Realm'
+ orelse N == 'Origin-State-Id'.
+
+%% failed_avp/1
+
+failed_avp([] = No) ->
+ No;
+failed_avp(Avps) ->
+ [{'Failed-AVP', [[{'AVP', Avps}]]}].
+
+%% set/2
+
+set(Ans, []) ->
+ Ans;
+set(['answer-message' | _] = Ans, FailedAvp) ->
+ Ans ++ [{'AVP', [FailedAvp]}];
+set([_|_] = Ans, FailedAvp) ->
+ Ans ++ FailedAvp.
+
+%% rc/2
+
+rc(#diameter_header{is_error = true}, _) ->
+ 3008; %% DIAMETER_INVALID_HDR_BITS
+
+rc(_, [Bs|_])
+ when is_bitstring(Bs) ->
+ 3009; %% DIAMETER_INVALID_HDR_BITS
+
+rc(#diameter_header{version = ?DIAMETER_VERSION}, Es) ->
+ rc(Es);
+
+rc(_, _) ->
+ 5011. %% DIAMETER_UNSUPPORTED_VERSION
+
+%% rc/1
+
+rc([]) ->
+ 2001; %% DIAMETER_SUCCESS
+rc([{RC,_}|_]) ->
+ RC;
+rc([RC|_]) ->
+ RC.
+
+%% DIAMETER_INVALID_HDR_BITS 3008
+%% A request was received whose bits in the Diameter header were
+%% either set to an invalid combination, or to a value that is
+%% inconsistent with the command code's definition.
+
+%% DIAMETER_INVALID_AVP_BITS 3009
+%% A request was received that included an AVP whose flag bits are
+%% set to an unrecognized value, or that is inconsistent with the
+%% AVP's definition.
+
+%% ELECTION_LOST 4003
+%% The peer has determined that it has lost the election process and
+%% has therefore disconnected the transport connection.
+
+%% DIAMETER_NO_COMMON_APPLICATION 5010
+%% This error is returned when a CER message is received, and there
+%% are no common applications supported between the peers.
+
+%% DIAMETER_UNSUPPORTED_VERSION 5011
+%% This error is returned when a request was received, whose version
+%% number is unsupported.
+
+%% answer/2
+
+answer('DWR', _) ->
+ getr(?DWA_KEY);
+
+answer(Name, #state{service = #diameter_service{capabilities = Caps}}) ->
+ a(Name, Caps).
+
+a('CER', #diameter_caps{vendor_id = Vid,
+ origin_host = Host,
+ origin_realm = Realm,
+ host_ip_address = Addrs,
+ product_name = Name,
+ origin_state_id = OSI}) ->
+ ['CEA', {'Origin-Host', Host},
+ {'Origin-Realm', Realm},
+ {'Host-IP-Address', Addrs},
+ {'Vendor-Id', Vid},
+ {'Product-Name', Name},
+ {'Origin-State-Id', OSI}];
+
+a('DPR', #diameter_caps{origin_host = {Host, _},
+ origin_realm = {Realm, _}}) ->
+ ['DPA', {'Origin-Host', Host},
+ {'Origin-Realm', Realm}].
+
+%% recv_CER/2
+
+recv_CER(CER, #state{service = Svc}) ->
+ {ok, T} = diameter_capx:recv_CER(CER, Svc),
+ T.
+
+%% handle_CEA/1
+
+handle_CEA(#diameter_packet{bin = Bin}
+ = Pkt,
+ #state{service = #diameter_service{capabilities = LCaps}}
+ = S)
+ when is_binary(Bin) ->
+ ?LOG(recv, 'CEA'),
+
+ #diameter_packet{msg = CEA}
+ = DPkt
+ = diameter_codec:decode(?BASE, Pkt),
+
+ {SApps, IS, RCaps} = recv_CEA(DPkt, S),
+
+ #diameter_caps{origin_host = {OH, DH}}
+ = Caps
+ = capz(LCaps, RCaps),
+
+ #diameter_base_CEA{'Result-Code' = RC}
+ = CEA,
+
+ %% Ensure that we don't already have a connection to the peer in
+ %% question. This isn't the peer election of 3588 except in the
+ %% sense that, since we don't know who we're talking to until we
+ %% receive a CER/CEA, the first that arrives wins the right to a
+ %% connection with the peer.
+
+ try
+ ?IS_SUCCESS(RC)
+ orelse ?THROW(RC),
+ [] == SApps
+ andalso ?THROW(no_common_application),
+ [] == IS
+ andalso ?THROW(no_common_security),
+ register_everywhere({?MODULE, connection, OH, DH})
+ orelse ?THROW(election_lost),
+ caps_cb(Caps)
+ of
+ _ -> open(DPkt, SApps, Caps, {connect, hd([_] = IS)}, S)
+ catch
+ ?FAILURE(Reason) -> close({'CEA', Reason, Caps, DPkt}, S)
+ end.
+%% Check more than the result code since the peer could send success
+%% regardless. If not 2001 then a peer_up callback could do anything
+%% required. It's not unimaginable that a peer agreeing to TLS after
+%% capabilities exchange could send DIAMETER_LIMITED_SUCCESS = 2002,
+%% even if this isn't required by RFC 3588.
+
+%% recv_CEA/2
+
+recv_CEA(#diameter_packet{header = #diameter_header{version
+ = ?DIAMETER_VERSION,
+ is_error = false},
+ msg = CEA,
+ errors = []},
+ #state{service = Svc}) ->
+ {ok, T} = diameter_capx:recv_CEA(CEA, Svc),
+ T;
+
+recv_CEA(Pkt, S) ->
+ close({'CEA', caps(S), Pkt}, S).
+
+caps(#diameter_service{capabilities = Caps}) ->
+ Caps;
+caps(#state{service = Svc}) ->
+ caps(Svc).
+
+%% caps_cb/1
+
+caps_cb(Caps) ->
+ {Ref, Ts} = eraser(?CB_KEY),
+ caps_cb(Ts, [Ref, Caps]).
+
+caps_cb([], _) ->
+ ok;
+caps_cb([F | Rest], T) ->
+ case diameter_lib:eval([F|T]) of
+ ok ->
+ caps_cb(Rest, T);
+ N when ?IS_SUCCESS(N) -> %% 2xxx result code: accept immediately
+ N;
+ Res ->
+ ?THROW({capabilities_cb, F, rejected(Res)})
+ end.
+%% Note that returning 2xxx causes the capabilities exchange to be
+%% accepted directly, without further callbacks.
+
+rejected(discard = T) ->
+ T;
+rejected(unknown) ->
+ 3010; %% DIAMETER_UNKNOWN_PEER
+rejected(N)
+ when is_integer(N) ->
+ N.
+
+%% open/5
+
+open(Pkt, SupportedApps, Caps, {Type, IS}, #state{parent = Pid,
+ service = Svc}
+ = S) ->
+ #diameter_caps{origin_host = {_,_} = H,
+ inband_security_id = {LS,_}}
+ = Caps,
+
+ tls_ack(lists:member(?TLS, LS), Caps, Type, IS, S),
+ Pid ! {open, self(), H, {Caps, SupportedApps, Pkt}},
+
+ %% Replace capabilities record with local/remote pairs.
+ S#state{state = 'Open',
+ service = Svc#diameter_service{capabilities = Caps}}.
+
+%% We've advertised TLS support: tell the transport the result
+%% and expect a reply when the handshake is complete.
+tls_ack(true, Caps, Type, IS, #state{transport = TPid} = S) ->
+ Ref = make_ref(),
+ TPid ! {diameter, {tls, Ref, Type, IS == ?TLS}},
+ receive
+ {diameter, {tls, Ref}} ->
+ ok;
+ {'DOWN', _, process, TPid, Reason} ->
+ close({tls_ack, Reason, Caps}, S)
+ end;
+
+%% Or not. Don't send anything to the transport so that transports
+%% not supporting TLS work as before without modification.
+tls_ack(false, _, _, _, _) ->
+ ok.
+
+capz(#diameter_caps{} = L, #diameter_caps{} = R) ->
+ #diameter_caps{}
+ = list_to_tuple([diameter_caps | lists:zip(tl(tuple_to_list(L)),
+ tl(tuple_to_list(R)))]).
+
+%% close/2
+
+%% Tell the watchdog that our death isn't due to transport failure.
+close(Reason, #state{parent = Pid}) ->
+ close_wd(Reason, Pid),
+ throw({?MODULE, close, Reason}).
+
+%% close_wd/2
+
+%% Ensure the watchdog dies if DPR has been sent ...
+close_wd(_, #state{dpr = false}) ->
+ ok;
+close_wd(Reason, #state{parent = Pid}) ->
+ close_wd(Reason, Pid);
+
+%% ... or otherwise
+close_wd(Reason, Pid) ->
+ Pid ! {close, self(), Reason}.
+
+%% dwa/1
+
+dwa(#diameter_caps{origin_host = OH,
+ origin_realm = OR,
+ origin_state_id = OSI}) ->
+ ['DWA', {'Origin-Host', OH},
+ {'Origin-Realm', OR},
+ {'Origin-State-Id', OSI}].
+
+%% dpr/2
+%%
+%% The RFC isn't clear on whether DPR should be send in a non-Open
+%% state. The Peer State Machine transitions it documents aren't
+%% exhaustive (no Stop in Wait-I-CEA for example) so assume it's up to
+%% the implementation and transition to Closed (ie. die) if we haven't
+%% yet reached Open.
+
+%% Connection is open, DPR has not been sent.
+dpr(Reason, #state{state = 'Open',
+ dpr = false,
+ service = #diameter_service{capabilities = Caps}}
+ = S) ->
+ case getr(?DPR_KEY) of
+ CBs when is_list(CBs) ->
+ Ref = getr(?REF_KEY),
+ Peer = {self(), Caps},
+ dpr(CBs, [Reason, Ref, Peer], S);
+ undefined -> %% started in old code
+ send_dpr(Reason, [], S)
+ end;
+
+%% Connection is open, DPR already sent.
+dpr(_, #state{state = 'Open'}) ->
+ ok;
+
+%% Connection not open.
+dpr(_Reason, _S) ->
+ stop.
+
+%% dpr/3
+%%
+%% Note that an implementation that wants to do something
+%% transport_module-specific can lookup the pid of the transport
+%% process and contact it. (eg. diameter:service_info/2)
+
+dpr([CB|Rest], [Reason | _] = Args, S) ->
+ try diameter_lib:eval([CB | Args]) of
+ {dpr, Opts} when is_list(Opts) ->
+ send_dpr(Reason, Opts, S);
+ dpr ->
+ send_dpr(Reason, [], S);
+ close = T ->
+ {stop, {disconnect_cb, T}};
+ ignore ->
+ dpr(Rest, Args, S);
+ T ->
+ No = {disconnect_cb, T},
+ diameter_lib:error_report(invalid, No),
+ {stop, No}
+ catch
+ E:R ->
+ No = {disconnect_cb, E, R, ?STACK},
+ diameter_lib:error_report(failure, No),
+ {stop, No}
+ end;
+
+dpr([], [Reason | _], S) ->
+ send_dpr(Reason, [], S).
+
+-record(opts, {cause, timeout = ?DPA_TIMEOUT}).
+
+send_dpr(Reason, Opts, #state{transport = TPid,
+ service = #diameter_service{capabilities = Caps}}
+ = S) ->
+ #opts{cause = Cause, timeout = Tmo}
+ = lists:foldl(fun opt/2,
+ #opts{cause = case Reason of
+ transport -> ?GOAWAY;
+ _ -> ?REBOOT
+ end,
+ timeout = ?DPA_TIMEOUT},
+ Opts),
+ #diameter_caps{origin_host = {OH, _},
+ origin_realm = {OR, _}}
+ = Caps,
+
+ #diameter_packet{header = #diameter_header{end_to_end_id = Eid,
+ hop_by_hop_id = Hid}}
+ = Pkt
+ = encode(['DPR', {'Origin-Host', OH},
+ {'Origin-Realm', OR},
+ {'Disconnect-Cause', Cause}]),
+ send(TPid, Pkt),
+ dpa_timer(Tmo),
+ ?LOG(send, 'DPR'),
+ S#state{dpr = {Hid, Eid}}.
+
+opt({timeout, Tmo}, Rec)
+ when ?IS_TIMEOUT(Tmo) ->
+ Rec#opts{timeout = Tmo};
+opt({cause, Cause}, Rec)
+ when ?IS_CAUSE(Cause) ->
+ Rec#opts{cause = cause(Cause)};
+opt(T, _) ->
+ ?ERROR({invalid_option, T}).
+
+cause(rebooting) -> ?REBOOT;
+cause(goaway) -> ?GOAWAY;
+cause(busy) -> ?BUSY;
+cause(N)
+ when ?IS_CAUSE(N) ->
+ N;
+cause(N) ->
+ ?ERROR({invalid_cause, N}).
+
+dpa_timer(Tmo) ->
+ erlang:send_after(Tmo, self(), dpa_timeout).
+
+%% register_everywhere/1
+%%
+%% Register a term and ensure it's not registered elsewhere. Note that
+%% two process that simultaneously register the same term may well
+%% both fail to do so this isn't foolproof.
+%%
+%% Everywhere is no longer everywhere, it's where a
+%% restrict_connections service_opt() specifies.
+
+register_everywhere(T) ->
+ reg(getr(?RESTRICT_KEY), T).
+
+reg(Nodes, T) ->
+ add(lists:member(node(), Nodes), T) andalso unregistered(Nodes, T).
+
+add(true, T) ->
+ diameter_reg:add_new(T);
+add(false, T) ->
+ diameter_reg:add(T).
+
+%% unregistered
+%%
+%% Ensure that the term in question isn't registered on other nodes.
+
+unregistered(Nodes, T) ->
+ {ResL, _} = rpc:multicall(Nodes, ?MODULE, match, [{node(), T}]),
+ lists:all(fun nomatch/1, ResL).
+
+nomatch({badrpc, {'EXIT', {undef, _}}}) -> %% no diameter on remote node
+ true;
+nomatch(L) ->
+ [] == L.
+
+%% match/1
+
+match({Node, _})
+ when Node == node() ->
+ [];
+match({_, T}) ->
+ try
+ diameter_reg:match(T)
+ catch
+ _:_ -> []
+ end.
diff --git a/lib/diameter/src/app/diameter_peer_fsm_sup.erl b/lib/diameter/src/base/diameter_peer_fsm_sup.erl
index 995eaf74d0..995eaf74d0 100644
--- a/lib/diameter/src/app/diameter_peer_fsm_sup.erl
+++ b/lib/diameter/src/base/diameter_peer_fsm_sup.erl
diff --git a/lib/diameter/src/base/diameter_reg.erl b/lib/diameter/src/base/diameter_reg.erl
new file mode 100644
index 0000000000..619b12ecad
--- /dev/null
+++ b/lib/diameter/src/base/diameter_reg.erl
@@ -0,0 +1,356 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% The module implements a simple term -> pid registry.
+%%
+
+-module(diameter_reg).
+-compile({no_auto_import, [monitor/2]}).
+
+-behaviour(gen_server).
+
+-export([add/1,
+ add_new/1,
+ del/1,
+ repl/2,
+ match/1,
+ wait/1]).
+
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1,
+ terminate/2,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ code_change/3]).
+
+%% test
+-export([pids/0,
+ terms/0]).
+
+%% debug
+-export([state/0,
+ uptime/0]).
+
+-include("diameter_internal.hrl").
+
+-define(SERVER, ?MODULE).
+-define(TABLE, ?MODULE).
+
+%% Table entry used to keep from starting more than one monitor on the
+%% same process. This isn't a problem but there's no point in starting
+%% multiple monitors if we can avoid it. Note that we can't have a 2-tuple
+%% keyed on Pid since a registered term can be anything. Want the entry
+%% keyed on Pid so that lookup is fast.
+-define(MONITOR(Pid, MRef), {Pid, monitor, MRef}).
+
+%% Table entry containing the Term -> Pid mapping.
+-define(MAPPING(Term, Pid), {Term, Pid}).
+
+-record(state, {id = now(),
+ q = []}). %% [{From, Pat}]
+
+%% ===========================================================================
+%% # add(T)
+%%
+%% Associate the specified term with self(). The list of pids having
+%% this or other assocations can be retrieved using match/1.
+%%
+%% An association is removed when the calling process dies or as a
+%% result of calling del/1. Adding the same term more than once is
+%% equivalent to adding it exactly once.
+%%
+%% Note that since match/1 takes a pattern as argument, specifying a
+%% term that contains match variables is probably not a good idea
+%% ===========================================================================
+
+-spec add(any())
+ -> true.
+
+add(T) ->
+ call({add, fun ets:insert/2, T, self()}).
+
+%% ===========================================================================
+%% # add_new(T)
+%%
+%% Like add/1 but only one process is allowed to have the the
+%% association, false being returned if an association already exists.
+%% ===========================================================================
+
+-spec add_new(any())
+ -> boolean().
+
+add_new(T) ->
+ call({add, fun insert_new/2, T, self()}).
+
+%% ===========================================================================
+%% # repl(T, NewT)
+%%
+%% Like add/1 but only replace an existing association on T, false
+%% being returned if it doesn't exist.
+%% ===========================================================================
+
+-spec repl(any(), any())
+ -> boolean().
+
+repl(T, U) ->
+ call({repl, T, U, self()}).
+
+%% ===========================================================================
+%% # del(Term)
+%%
+%% Remove any existing association of Term with self().
+%% ===========================================================================
+
+-spec del(any())
+ -> true.
+
+del(T) ->
+ call({del, T, self()}).
+
+%% ===========================================================================
+%% # match(Pat)
+%%
+%% Return the list of associations whose Term, as specified to add/1
+%% or add_new/1, matches the specified pattern.
+%%
+%% Note that there's no guarantee that the returned processes are
+%% still alive. (Although one that isn't will soon have its
+%% associations removed.)
+%% ===========================================================================
+
+-spec match(tuple())
+ -> [{term(), pid()}].
+
+match(Pat) ->
+ ets:match_object(?TABLE, ?MAPPING(Pat, '_')).
+
+%% ===========================================================================
+%% # wait(Pat)
+%%
+%% Like match/1 but return only when the result is non-empty or fails.
+%% It's up to the caller to ensure that the wait won't be forever.
+%% ===========================================================================
+
+wait(Pat) ->
+ call({wait, Pat}).
+
+%% ===========================================================================
+
+start_link() ->
+ ServerName = {local, ?SERVER},
+ Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
+ gen_server:start_link(ServerName, ?MODULE, [], Options).
+
+state() ->
+ call(state).
+
+uptime() ->
+ call(uptime).
+
+%% pids/0
+%%
+%% Return: list of {Pid, [Term, ...]}
+
+pids() ->
+ to_list(fun swap/1).
+
+to_list(Fun) ->
+ ets:foldl(fun(T,A) -> acc(Fun, T, A) end, orddict:new(), ?TABLE).
+
+acc(Fun, ?MAPPING(Term, Pid), Dict) ->
+ append(Fun({Term, Pid}), Dict);
+acc(_, _, Dict) ->
+ Dict.
+
+append({K,V}, Dict) ->
+ orddict:append(K, V, Dict).
+
+id(T) -> T.
+
+%% terms/0
+%%
+%% Return: list of {Term, [Pid, ...]}
+
+terms() ->
+ to_list(fun id/1).
+
+swap({X,Y}) -> {Y,X}.
+
+%% ----------------------------------------------------------
+%% # init/1
+%% ----------------------------------------------------------
+
+init(_) ->
+ ets:new(?TABLE, [bag, named_table]),
+ {ok, #state{}}.
+
+%% ----------------------------------------------------------
+%% # handle_call/3
+%% ----------------------------------------------------------
+
+handle_call(Req, From, S)
+ when not is_record(S, state) ->
+ handle_call(Req, From, upgrade(S));
+
+handle_call({add, Fun, Key, Pid}, _, S) ->
+ B = Fun(?TABLE, {Key, Pid}),
+ monitor(B andalso no_monitor(Pid), Pid),
+ {reply, B, pending(B, S)};
+
+handle_call({del, Key, Pid}, _, S) ->
+ {reply, ets:delete_object(?TABLE, ?MAPPING(Key, Pid)), S};
+
+handle_call({repl, T, U, Pid}, _, S) ->
+ MatchSpec = [{?MAPPING('$1', Pid),
+ [{'=:=', '$1', {const, T}}],
+ ['$_']}],
+ {reply, repl(ets:select(?TABLE, MatchSpec), U, Pid), S};
+
+handle_call({wait, Pat}, From, #state{q = Q} = S) ->
+ case find(Pat) of
+ {ok, L} ->
+ {reply, L, S};
+ false ->
+ {noreply, S#state{q = [{From, Pat} | Q]}}
+ end;
+
+handle_call(state, _, S) ->
+ {reply, S, S};
+
+handle_call(uptime, _, #state{id = Time} = S) ->
+ {reply, diameter_lib:now_diff(Time), S};
+
+handle_call(Req, From, S) ->
+ ?UNEXPECTED([Req, From]),
+ {reply, nok, S}.
+
+%% ----------------------------------------------------------
+%% # handle_cast/2
+%% ----------------------------------------------------------
+
+handle_cast(Msg, S)->
+ ?UNEXPECTED([Msg]),
+ {noreply, S}.
+
+%% ----------------------------------------------------------
+%% # handle_info/2
+%% ----------------------------------------------------------
+
+handle_info({'DOWN', MRef, process, Pid, _}, S) ->
+ ets:delete_object(?TABLE, ?MONITOR(Pid, MRef)),
+ ets:match_delete(?TABLE, ?MAPPING('_', Pid)),
+ {noreply, S};
+
+handle_info(Info, S) ->
+ ?UNEXPECTED([Info]),
+ {noreply, S}.
+
+%% ----------------------------------------------------------
+%% # terminate/2
+%% ----------------------------------------------------------
+
+terminate(_Reason, _State)->
+ ok.
+
+%% ----------------------------------------------------------
+%% # code_change/3
+%% ----------------------------------------------------------
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% ===========================================================================
+
+upgrade(S) ->
+ #state{} = list_to_tuple(tuple_to_list(S) ++ [[]]).
+
+monitor(true, Pid) ->
+ ets:insert(?TABLE, ?MONITOR(Pid, erlang:monitor(process, Pid)));
+monitor(false, _) ->
+ ok.
+
+%% Do we need a monitor for the specified Pid?
+no_monitor(Pid) ->
+ [] == ets:match_object(?TABLE, ?MONITOR(Pid, '_')).
+
+%% insert_new/2
+
+insert_new(?TABLE, {Key, _} = T) ->
+ flush(ets:lookup(?TABLE, Key)),
+ ets:insert_new(?TABLE, T).
+
+%% Remove any processes that are dead but for which we may not have
+%% received 'DOWN' yet. This is to ensure that add_new can be used
+%% to register a unique name each time a process restarts.
+flush(List) ->
+ lists:foreach(fun({_,P} = T) ->
+ del(erlang:is_process_alive(P), T)
+ end,
+ List).
+
+del(Alive, T) ->
+ Alive orelse ets:delete_object(?TABLE, T).
+
+%% repl/3
+
+repl([?MAPPING(_, Pid) = M], Key, Pid) ->
+ ets:delete_object(?TABLE, M),
+ true = ets:insert(?TABLE, ?MAPPING(Key, Pid));
+repl([], _, _) ->
+ false.
+
+%% pending/1
+
+pending(true, #state{q = [_|_] = Q} = S) ->
+ S#state{q = q(lists:reverse(Q), [])}; %% retain reply order
+pending(_, S) ->
+ S.
+
+q([], Q) ->
+ Q;
+q([{From, Pat} = T | Rest], Q) ->
+ case find(Pat) of
+ {ok, L} ->
+ gen_server:reply(From, L),
+ q(Rest, Q);
+ false ->
+ q(Rest, [T|Q])
+ end.
+
+%% find/1
+
+find(Pat) ->
+ try match(Pat) of
+ [] ->
+ false;
+ L ->
+ {ok, L}
+ catch
+ _:_ ->
+ {ok, []}
+ end.
+
+%% call/1
+
+call(Request) ->
+ gen_server:call(?SERVER, Request, infinity).
diff --git a/lib/diameter/src/app/diameter_service.erl b/lib/diameter/src/base/diameter_service.erl
index 421e36ccf5..66c526b379 100644
--- a/lib/diameter/src/app/diameter_service.erl
+++ b/lib/diameter/src/base/diameter_service.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2013. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -43,8 +43,7 @@
subscriptions/0,
services/0,
services/1,
- whois/1,
- flush_stats/1]).
+ whois/1]).
%% test/debug
-export([call_module/3,
@@ -64,15 +63,34 @@
-include_lib("diameter/include/diameter.hrl").
-include("diameter_internal.hrl").
--include("diameter_types.hrl").
+%% The states mirrored by peer_up/peer_down callbacks.
-define(STATE_UP, up).
-define(STATE_DOWN, down).
+-type op_state() :: ?STATE_UP
+ | ?STATE_DOWN.
+
+%% The RFC 3539 watchdog states that are now maintained, albeit
+%% along with the old up/down. okay = up, else down.
+-define(WD_INITIAL, initial).
+-define(WD_OKAY, okay).
+-define(WD_SUSPECT, suspect).
+-define(WD_DOWN, down).
+-define(WD_REOPEN, reopen).
+
+-type wd_state() :: ?WD_INITIAL
+ | ?WD_OKAY
+ | ?WD_SUSPECT
+ | ?WD_DOWN
+ | ?WD_REOPEN.
+
-define(DEFAULT_TC, 30000). %% RFC 3588 ch 2.1
-define(DEFAULT_TIMEOUT, 5000). %% for outgoing requests
-define(RESTART_TC, 1000). %% if restart was this recent
+-define(RELAY, ?DIAMETER_DICT_RELAY).
+
%% Used to be able to swap this with anything else dict-like but now
%% rely on the fact that a service's #state{} record does not change
%% in storing in it ?STATE table and not always going through the
@@ -89,6 +107,12 @@
%% process.
-define(STATE_TABLE, ?MODULE).
+%% The default sequence mask.
+-define(NOMASK, {0,32}).
+
+%% The default restrict_connections.
+-define(RESTRICT, nodes).
+
%% Workaround for dialyzer's lack of understanding of match specs.
-type match(T)
:: T | '_' | '$1' | '$2' | '$3' | '$4'.
@@ -96,15 +120,18 @@
%% State of service gen_server.
-record(state,
{id = now(),
- service_name, %% as passed to start_service/2, key in ?STATE_TABLE
+ service_name, %% as passed to start_service/2, key in ?STATE_TABLE
service :: #diameter_service{},
- peerT = ets_new(peers) :: ets:tid(), %% #peer{} at start_fsm
- connT = ets_new(conns) :: ets:tid(), %% #conn{} at connection_up
- share_peers = false :: boolean(), %% broadcast peers to remote nodes?
- use_shared_peers = false :: boolean(), %% use broadcasted peers?
+ peerT = ets_new(peers) :: ets:tid(),%% #peer{} at start_fsm
+ connT = ets_new(conns) :: ets:tid(),%% #conn{} at connection_up/reopen
shared_peers = ?Dict:new(), %% Alias -> [{TPid, Caps}, ...]
local_peers = ?Dict:new(), %% Alias -> [{TPid, Caps}, ...]
- monitor = false :: false | pid()}). %% process to die with
+ monitor = false :: false | pid(), %% process to die with
+ options
+ :: [{sequence, diameter:sequence()} %% sequence mask
+ | {restrict_connections, diameter:restriction()}
+ | {share_peers, boolean()} %% broadcast peers to remote nodes?
+ | {use_shared_peers, boolean()}]}).%% use broadcasted peers?
%% shared_peers reflects the peers broadcast from remote nodes. Note
%% that the state term itself doesn't change, which is relevant for
%% the stateless application callbacks since the state is retrieved
@@ -112,21 +139,33 @@
%% service record is used to determine whether or not we need to call
%% the process for a pick_peer callback.
-%% Record representing a watchdog process.
+%% Record representing a watchdog process as implemented by
+%% diameter_watchdog. The term "peer" here is historical, made
+%% especially confusing by the fact that a peer_ref() in the
+%% documentation is the key of a #conn{} record, not a #peer{} record.
+%% The name is also unfortunate given the meaning of peer in the
+%% Diameter sense.
-record(peer,
{pid :: match(pid()),
type :: match(connect | accept),
ref :: match(reference()), %% key into diameter_config
- options :: match([transport_opt()]), %% as passed to start_transport
- op_state = ?STATE_DOWN :: match(?STATE_DOWN | ?STATE_UP),
+ options :: match([diameter:transport_opt()]),%% from start_transport
+ op_state = {?STATE_DOWN, ?WD_INITIAL}
+ :: match(op_state() | {op_state(), wd_state()}),
started = now(), %% at process start
conn = false :: match(boolean() | pid())}).
- %% true at accept, pid() at connection_up (connT key)
-
-%% Record representing a peer_fsm process.
+ %% true at accepted, pid() at connection_up or reopen
+
+%% Record representing a peer process as implemented by
+%% diameter_peer_fsm. The term "conn" is historical. Despite the name
+%% here, comments refer to watchdog and peer processes, that are keys
+%% in #peer{} and #conn{} records respectively. To add to the
+%% confusion, a #request.transport is a peer process = key in a
+%% #conn{} record. The actual transport process (that the peer process
+%% knows about and that has a transport connection) isn't seen here.
-record(conn,
{pid :: pid(),
- apps :: [{0..16#FFFFFFFF, app_alias()}], %% {Id, Alias}
+ apps :: [{0..16#FFFFFFFF, diameter:app_alias()}], %% {Id, Alias}
caps :: #diameter_caps{},
started = now(), %% at process start
peer :: pid()}). %% key into peerT
@@ -137,34 +176,19 @@
handler :: match(pid()), %% request process
transport :: match(pid()), %% peer process
caps :: match(#diameter_caps{}),
- app :: match(app_alias()), %% #diameter_app.alias
- dictionary :: match(module()), %% #diameter_app.dictionary
- module :: match(nonempty_improper_list(module(), list())),
- %% #diameter_app.module
- filter :: match(peer_filter()),
+ app :: match(diameter:app_alias()),%% #diameter_app.alias
+ dictionary :: match(module()), %% #diameter_app.dictionary
+ module :: match([module() | list()]), %% #diameter_app.module
+ filter :: match(diameter:peer_filter()),
packet :: match(#diameter_packet{})}).
%% Record call/4 options are parsed into.
-record(options,
- {filter = none :: peer_filter(),
+ {filter = none :: diameter:peer_filter(),
extra = [] :: list(),
timeout = ?DEFAULT_TIMEOUT :: 0..16#FFFFFFFF,
detach = false :: boolean()}).
-%% Since RFC 3588 requires that a Diameter agent not modify End-to-End
-%% Identifiers, the possibility of explicitly setting an End-to-End
-%% Identifier would be needed to be able to implement an agent in
-%% which one side of the communication is not implemented on top of
-%% diameter. For example, Diameter being sent or received encapsulated
-%% in some other protocol, or even another Diameter stack in a
-%% non-Erlang environment. (Not that this is likely to be a normal
-%% case.)
-%%
-%% The implemented solution is not an option but to respect any header
-%% values set in a diameter_header record returned from a
-%% prepare_request callback. A call to diameter:call/4 can communicate
-%% values to the callback using the 'extra' option if so desired.
-
%%% ---------------------------------------------------------------------------
%%% # start(SvcName)
%%% ---------------------------------------------------------------------------
@@ -217,20 +241,20 @@ stop_transport(SvcName, [_|_] = Refs) ->
%%% ---------------------------------------------------------------------------
info(SvcName, Item) ->
- info_rc(call_service_by_name(SvcName, {info, Item})).
-
-info_rc({error, _}) ->
- undefined;
-info_rc(Info) ->
- Info.
+ case find_state(SvcName) of
+ #state{} = S ->
+ service_info(Item, S);
+ false ->
+ undefined
+ end.
%%% ---------------------------------------------------------------------------
%%% # receive_message(TPid, Pkt, MessageData)
%%% ---------------------------------------------------------------------------
-%% Handle an incoming message in the watchdog process. This used to
-%% come through the service process but this avoids that becoming a
-%% bottleneck.
+%% Handle an incoming Diameter message in the watchdog process. This
+%% used to come through the service process but this avoids that
+%% becoming a bottleneck.
receive_message(TPid, Pkt, T)
when is_pid(TPid) ->
@@ -310,21 +334,39 @@ call_rc(_, _, Sent) ->
%% In the process spawned for the outgoing request.
call(SvcName, App, Msg, Opts, Caller) ->
- c(ets:lookup(?STATE_TABLE, SvcName), App, Msg, Opts, Caller).
+ c(find_state(SvcName), App, Msg, Opts, Caller).
-c([#state{service_name = SvcName} = S], App, Msg, Opts, Caller) ->
+c(#state{service_name = Svc, options = [{_, Mask} | _]} = S,
+ App,
+ Msg,
+ Opts,
+ Caller) ->
case find_transport(App, Msg, Opts, S) of
{_,_,_} = T ->
- send_request(T, Msg, Opts, Caller, SvcName);
+ send_request(T, Mask, Msg, Opts, Caller, Svc);
false ->
{error, no_connection};
{error, _} = No ->
No
end;
-c([], _, _, _, _) ->
+c(false, _, _, _, _) ->
{error, no_service}.
+%% find_state/1
+
+find_state(SvcName) ->
+ fs(ets:lookup(?STATE_TABLE, SvcName)).
+
+fs([#state{} = S]) ->
+ S;
+
+fs([S]) -> %% inserted from old code
+ upgrade(S);
+
+fs([]) ->
+ false.
+
%% make_options/1
make_options(Options) ->
@@ -389,15 +431,6 @@ whois(SvcName) ->
undefined
end.
-%%% ---------------------------------------------------------------------------
-%%% # flush_stats/1
-%%%
-%%% Output: list of {{SvcName, Alias, Counter}, Value}
-%%% ---------------------------------------------------------------------------
-
-flush_stats(TPid) ->
- diameter_stats:flush(TPid).
-
%% ===========================================================================
%% ===========================================================================
@@ -429,6 +462,10 @@ i(_, false) ->
%%% # handle_call(Req, From, State)
%%% ---------------------------------------------------------------------------
+handle_call(T, From, S)
+ when not is_record(S, state) ->
+ handle_call(T, From, upgrade(S));
+
handle_call(state, _, S) ->
{reply, S, S};
@@ -452,16 +489,25 @@ handle_call({pick_peer, Local, Remote, App}, _From, S) ->
handle_call({call_module, AppMod, Req}, From, S) ->
call_module(AppMod, Req, From, S);
+%% Call from old code.
handle_call({info, Item}, _From, S) ->
{reply, service_info(Item, S), S};
handle_call(stop, _From, S) ->
- shutdown(S),
+ shutdown(service, S),
{stop, normal, ok, S};
%% The server currently isn't guaranteed to be dead when the caller
%% gets the reply. We deal with this in the call to the server,
%% stating a monitor that waits for DOWN before returning.
+%% Watchdog is asking for the sequence mask.
+handle_call(sequence, _From, #state{options = [{_, Mask} | _]} = S) ->
+ {reply, Mask, S};
+
+%% Watchdog is asking for the nodes restriction.
+handle_call(restriction, _From, #state{options = [_,_,_,{_,R} | _]} = S) ->
+ {reply, R, S};
+
handle_call(Req, From, S) ->
unexpected(handle_call, [Req, From], S),
{reply, nok, S}.
@@ -478,15 +524,16 @@ handle_cast(Req, S) ->
%%% # handle_info(Req, State)
%%% ---------------------------------------------------------------------------
-handle_info(T,S) ->
+handle_info(T, #state{} = S) ->
case transition(T,S) of
ok ->
{noreply, S};
- #state{} = NS ->
- {noreply, NS};
{stop, Reason} ->
{stop, {shutdown, Reason}, S}
- end.
+ end;
+
+handle_info(T, S) ->
+ handle_info(T, upgrade(S)).
%% transition/2
@@ -497,15 +544,26 @@ transition({accepted, Pid, TPid}, S) ->
%% Peer process has a new open connection.
transition({connection_up, Pid, T}, S) ->
- connection_up(Pid, T, S);
+ connection_up(Pid, T, S),
+ ok;
-%% Peer process has left state open.
+%% Watchdog has a new connection that will be opened after DW[RA]
+%% exchange. This message was added long after connection_up, to
+%% communicate the information as soon as it's available. Leave
+%% connection_up as is it for now, duplicated information and all.
+transition({reopen, Pid, T}, S) ->
+ reopen(Pid, T, S),
+ ok;
+
+%% Watchdog has left state OKAY.
transition({connection_down, Pid}, S) ->
- connection_down(Pid, S);
+ connection_down(Pid, S),
+ ok;
-%% Peer process has returned to state open.
+%% Watchdog has returned to state OKAY.
transition({connection_up, Pid}, S) ->
- connection_up(Pid, S);
+ connection_up(Pid, S),
+ ok;
%% Accepting transport has lost connectivity.
transition({close, Pid}, S) ->
@@ -517,29 +575,61 @@ transition({reconnect, Pid}, S) ->
reconnect(Pid, S),
ok;
+%% Watchdog is sending notification of a state transition. Note that
+%% the connection_up/down messages are pre-date this message and are
+%% still used. A watchdog message will follow these and communicate
+%% the same state as was set in handling connection_up/down.
+transition({watchdog, Pid, {TPid, From, To}}, #state{service_name = SvcName,
+ peerT = PeerT}) ->
+ #peer{ref = Ref, type = T, options = Opts, op_state = {OS,_}}
+ = P
+ = fetch(PeerT, Pid),
+ insert(PeerT, P#peer{op_state = {OS, To}}),
+ send_event(SvcName, {watchdog, Ref, TPid, {From, To}, {T, Opts}}),
+ ok;
+%% Death of a watchdog process (#peer.pid) results in the removal of
+%% it's peer and any associated conn record when 'DOWN' is received
+%% (after this) but the states will be {?STATE_UP, ?WD_DOWN} for a
+%% short time. (No real problem since ?WD_* is only used in
+%% service_info.) We set ?WD_OKAY as a consequence of connection_up
+%% since we know a watchdog is coming. We can't set anything at
+%% connection_down since we don't know if the subsequent watchdog
+%% message will be ?WD_DOWN or ?WD_SUSPECT. We don't (yet) set
+%% ?STATE_* as a consequence of a watchdog message since this requires
+%% changing some of the matching on ?STATE_*.
+%%
+%% Death of a peer process process (#conn.pid, #peer.conn) results in
+%% connection_down followed by watchdog ?WD_DOWN. The latter doesn't
+%% result in the conn record being deleted since 'DOWN' from death of
+%% its watchdog doesn't (yet) deal with the record having been
+%% removed.
+
%% Monitor process has died. Just die with a reason that tells
%% diameter_config about the happening. If a cleaner shutdown is
%% required then someone should stop us.
transition({'DOWN', MRef, process, _, Reason}, #state{monitor = MRef}) ->
{stop, {monitor, Reason}};
-%% Local peer process has died.
+%% Local watchdog process has died.
transition({'DOWN', _, process, Pid, Reason}, S)
when node(Pid) == node() ->
- peer_down(Pid, Reason, S);
+ peer_down(Pid, Reason, S),
+ ok;
-%% Remote service wants to know about shared transports.
+%% Remote service wants to know about shared peers.
transition({service, Pid}, S) ->
share_peers(Pid, S),
ok;
%% Remote service is communicating a shared peer.
transition({peer, TPid, Aliases, Caps}, S) ->
- remote_peer_up(TPid, Aliases, Caps, S);
+ remote_peer_up(TPid, Aliases, Caps, S),
+ ok;
%% Remote peer process has died.
transition({'DOWN', _, process, TPid, _}, S) ->
- remote_peer_down(TPid, S);
+ remote_peer_down(TPid, S),
+ ok;
%% Restart after tc expiry.
transition({tc_timeout, T}, S) ->
@@ -553,18 +643,48 @@ transition({failover, TRef, Seqs}, S) ->
failover(TRef, Seqs, S),
ok;
+%% Ensure upgraded state is stored in state table.
+transition(upgrade, _) ->
+ ok;
+
transition(Req, S) ->
unexpected(handle_info, [Req], S),
ok.
+%% upgrade/1
+
+upgrade({state, Id, Svc, Name, Svc, PT, CT, SB, UB, SD, LD, MPid}) ->
+ S = #state{id = Id,
+ service_name = Name,
+ service = Svc,
+ peerT = PT,
+ connT = CT,
+ shared_peers = SD,
+ local_peers = LD,
+ monitor = MPid,
+ options = [{sequence, ?NOMASK},
+ {share_peers, SB},
+ {use_shared_peers, UB},
+ {restrict_connections, ?RESTRICT}]},
+ upgrade_insert(S),
+ S.
+
+upgrade_insert(#state{service = #diameter_service{pid = Pid}} = S) ->
+ if Pid == self() ->
+ ets:insert(?STATE_TABLE, S);
+ true ->
+ Pid ! upgrade
+ end.
+
%%% ---------------------------------------------------------------------------
%%% # terminate(Reason, State)
%%% ---------------------------------------------------------------------------
terminate(Reason, #state{service_name = Name} = S) ->
+ send_event(Name, stop),
ets:delete(?STATE_TABLE, Name),
shutdown == Reason %% application shutdown
- andalso shutdown(S).
+ andalso shutdown(application, S).
%%% ---------------------------------------------------------------------------
%%% # code_change(FromVsn, State, Extra)
@@ -630,10 +750,6 @@ insert(Tbl, Rec) ->
ets:insert(Tbl, Rec),
Rec.
-monitor(Pid) ->
- erlang:monitor(process, Pid),
- Pid.
-
%% Using the process dictionary for the callback state was initially
%% just a way to make what was horrendous trace (big state record and
%% much else everywhere) somewhat more readable. There's not as much
@@ -647,50 +763,53 @@ mod_state(Alias) ->
mod_state(Alias, ModS) ->
put({?MODULE, mod_state, Alias}, ModS).
-%% have_transport/2
-
-have_transport(SvcName, Ref) ->
- [] /= diameter_config:have_transport(SvcName, Ref).
-
%%% ---------------------------------------------------------------------------
%%% # shutdown/2
%%% ---------------------------------------------------------------------------
-shutdown(Refs, #state{peerT = PeerT}) ->
- ets:foldl(fun(P,ok) -> s(P, Refs), ok end, ok, PeerT).
-
-s(#peer{ref = Ref, pid = Pid}, Refs) ->
- s(lists:member(Ref, Refs), Pid);
+%% remove_transport: ask watchdogs to terminate their transport.
+shutdown(Refs, #state{peerT = PeerT})
+ when is_list(Refs) ->
+ ets:foldl(fun(P,ok) -> sp(P, Refs), ok end, ok, PeerT);
-s(true, Pid) ->
- Pid ! {shutdown, self()}; %% 'DOWN' will cleanup as usual
-s(false, _) ->
- ok.
-
-%%% ---------------------------------------------------------------------------
-%%% # shutdown/1
-%%% ---------------------------------------------------------------------------
-
-shutdown(#state{peerT = PeerT}) ->
+%% application/service shutdown: ask transports to terminate themselves.
+shutdown(Reason, #state{peerT = PeerT}) ->
%% A transport might not be alive to receive the shutdown request
%% but give those that are a chance to shutdown gracefully.
- wait(fun st/2, PeerT),
+ shutdown(conn, Reason, PeerT),
%% Kill the watchdogs explicitly in case there was no transport.
- wait(fun sw/2, PeerT).
+ shutdown(peer, Reason, PeerT).
-wait(Fun, T) ->
- diameter_lib:wait(ets:foldl(Fun, [], T)).
+%% sp/2
-st(#peer{conn = B}, Acc)
- when is_boolean(B) ->
- Acc;
-st(#peer{conn = Pid}, Acc) ->
- Pid ! shutdown,
- [Pid | Acc].
+sp(#peer{ref = Ref, pid = Pid}, Refs) ->
+ lists:member(Ref, Refs)
+ andalso (Pid ! {shutdown, self()}). %% 'DOWN' cleans up
+
+%% shutdown/3
+
+shutdown(Who, Reason, T) ->
+ diameter_lib:wait(ets:foldl(fun(X,A) -> shutdown(Who, X, Reason, A) end,
+ [],
+ T)).
-sw(#peer{pid = Pid}, Acc) ->
+shutdown(conn = Who, #peer{op_state = {OS,_}} = P, Reason, Acc) ->
+ shutdown(Who, P#peer{op_state = OS}, Reason, Acc);
+
+shutdown(conn,
+ #peer{pid = Pid, op_state = ?STATE_UP, conn = TPid},
+ Reason,
+ Acc) ->
+ TPid ! {shutdown, Pid, Reason},
+ [TPid | Acc];
+
+shutdown(peer, #peer{pid = Pid}, _Reason, Acc)
+ when is_pid(Pid) ->
exit(Pid, shutdown),
- [Pid | Acc].
+ [Pid | Acc];
+
+shutdown(_, #peer{}, _, Acc) ->
+ Acc.
%%% ---------------------------------------------------------------------------
%%% # call_service/2
@@ -743,6 +862,7 @@ i(SvcName) ->
lists:foreach(fun(T) -> start_fsm(T,S) end, CL),
init_shared(S),
+ send_event(SvcName, start),
S.
cfg_acc({SvcName, #diameter_service{applications = Apps} = Rec, Opts},
@@ -750,9 +870,8 @@ cfg_acc({SvcName, #diameter_service{applications = Apps} = Rec, Opts},
lists:foreach(fun init_mod/1, Apps),
S = #state{service_name = SvcName,
service = Rec#diameter_service{pid = self()},
- share_peers = get_value(share_peers, Opts),
- use_shared_peers = get_value(use_shared_peers, Opts),
- monitor = mref(get_value(monitor, Opts))},
+ monitor = mref(get_value(monitor, Opts)),
+ options = service_options(Opts)},
{S, Acc};
cfg_acc({_Ref, Type, _Opts} = T, {S, Acc})
@@ -760,15 +879,24 @@ cfg_acc({_Ref, Type, _Opts} = T, {S, Acc})
Type == listen ->
{S, [T | Acc]}.
+service_options(Opts) ->
+ [{sequence, proplists:get_value(sequence, Opts, ?NOMASK)},
+ {share_peers, get_value(share_peers, Opts)},
+ {use_shared_peers, get_value(use_shared_peers, Opts)},
+ {restrict_connections, proplists:get_value(restrict_connections,
+ Opts,
+ ?RESTRICT)}].
+%% The order of options is significant since we match against the list.
+
mref(false = No) ->
No;
mref(P) ->
erlang:monitor(process, P).
-init_shared(#state{use_shared_peers = true,
+init_shared(#state{options = [_, _, {_, true} | _],
service_name = Svc}) ->
diameter_peer:notify(Svc, {service, self()});
-init_shared(#state{use_shared_peers = false}) ->
+init_shared(#state{options = [_, _, {_, false} | _]}) ->
ok.
init_mod(#diameter_app{alias = Alias,
@@ -820,10 +948,10 @@ start(Ref, Type, Opts, #state{peerT = PeerT,
service = Svc})
when Type == connect;
Type == accept ->
- Pid = monitor(s(Type, Ref, {ConnT,
- Opts,
- SvcName,
- merge_service(Opts, Svc)})),
+ Pid = s(Type, Ref, {ConnT,
+ Opts,
+ SvcName,
+ merge_service(Opts, Svc)}),
insert(PeerT, #peer{pid = Pid,
type = Type,
ref = Ref,
@@ -831,12 +959,17 @@ start(Ref, Type, Opts, #state{peerT = PeerT,
Pid.
%% Note that the service record passed into the watchdog is the merged
-%% record so that each watchdog (and peer_fsm) may get a different
-%% record. This record is what is passed back into application
-%% callbacks.
+%% record so that each watchdog may get a different record. This
+%% record is what is passed back into application callbacks.
s(Type, Ref, T) ->
- diameter_watchdog:start({Type, Ref}, T).
+ case diameter_watchdog:start({Type, Ref}, T) of
+ {_MRef, Pid} ->
+ Pid;
+ Pid when is_pid(Pid) -> %% from old code
+ erlang:monitor(process, Pid),
+ Pid
+ end.
%% merge_service/2
@@ -878,20 +1011,25 @@ accepted(Pid, _TPid, #state{peerT = PeerT} = S) ->
#peer{ref = Ref, type = accept = T, conn = false, options = Opts}
= P
= fetch(PeerT, Pid),
- insert(PeerT, P#peer{conn = true}), %% mark replacement transport started
- start(Ref, T, Opts, S). %% start new peer
+ insert(PeerT, P#peer{conn = true}), %% mark replacement as started
+ start(Ref, T, Opts, S). %% start new watchdog
fetch(Tid, Key) ->
[T] = ets:lookup(Tid, Key),
- T.
+ case T of
+ #peer{op_state = ?STATE_UP} = P ->
+ P#peer{op_state = {?STATE_UP, ?WD_OKAY}};
+ #peer{op_state = ?STATE_DOWN} = P ->
+ P#peer{op_state = {?STATE_DOWN, ?WD_DOWN}};
+ _ ->
+ T
+ end.
%%% ---------------------------------------------------------------------------
%%% # connection_up/3
-%%%
-%%% Output: #state{}
%%% ---------------------------------------------------------------------------
-%% Peer process has reached the open state.
+%% Watchdog process has reached state OKAY.
connection_up(Pid, {TPid, {Caps, SApps, Pkt}}, #state{peerT = PeerT,
connT = ConnT}
@@ -906,9 +1044,29 @@ connection_up(Pid, {TPid, {Caps, SApps, Pkt}}, #state{peerT = PeerT,
connection_up([Pkt], P#peer{conn = TPid}, C, S).
%%% ---------------------------------------------------------------------------
+%%% # reopen/3
+%%% ---------------------------------------------------------------------------
+
+%% Note that this connection_up/3 rewrites the same #conn{} now
+%% written here. Both do so in case reopen has not happened in old
+%% code.
+
+reopen(Pid, {TPid, {Caps, SApps, _Pkt}}, #state{peerT = PeerT,
+ connT = ConnT}) ->
+ P = fetch(PeerT, Pid),
+ C = #conn{pid = TPid,
+ apps = SApps,
+ caps = Caps,
+ peer = Pid},
+
+ insert(ConnT, C),
+ #peer{op_state = {?STATE_DOWN, _}}
+ = P,
+ insert(PeerT, P#peer{op_state = {?STATE_DOWN, ?WD_REOPEN},
+ conn = TPid}).
+
+%%% ---------------------------------------------------------------------------
%%% # connection_up/2
-%%%
-%%% Output: #state{}
%%% ---------------------------------------------------------------------------
%% Peer process has transitioned back into the open state. Note that there
@@ -929,18 +1087,16 @@ connection_up(T, P, C, #state{peerT = PeerT,
service
= #diameter_service{applications = Apps}}
= S) ->
- #peer{conn = TPid, op_state = ?STATE_DOWN}
+ #peer{conn = TPid, op_state = {?STATE_DOWN, _}}
= P,
#conn{apps = SApps, caps = Caps}
= C,
- insert(PeerT, P#peer{op_state = ?STATE_UP}),
+ insert(PeerT, P#peer{op_state = {?STATE_UP, ?WD_OKAY}}),
request_peer_up(TPid),
- report_status(up, P, C, S, T),
- S#state{local_peers = insert_local_peer(SApps,
- {{TPid, Caps}, {SvcName, Apps}},
- LDict)}.
+ insert_local_peer(SApps, {{TPid, Caps}, {SvcName, Apps}}, LDict),
+ report_status(up, P, C, S, T).
insert_local_peer(SApps, T, LDict) ->
lists:foldl(fun(A,D) -> ilp(A, T, D) end, LDict, SApps).
@@ -949,52 +1105,62 @@ ilp({Id, Alias}, {TC, SA}, LDict) ->
init_conn(Id, Alias, TC, SA),
?Dict:append(Alias, TC, LDict).
-init_conn(Id, Alias, TC, {SvcName, Apps}) ->
+init_conn(Id, Alias, {TPid, _} = TC, {SvcName, Apps}) ->
#diameter_app{module = ModX,
id = Id} %% assert
= find_app(Alias, Apps),
- peer_cb({ModX, peer_up, [SvcName, TC]}, Alias).
+ peer_cb({ModX, peer_up, [SvcName, TC]}, Alias)
+ orelse exit(TPid, kill). %% fake transport failure
+
+%% find_app/2
find_app(Alias, Apps) ->
- lists:keyfind(Alias, #diameter_app.alias, Apps).
+ case lists:keyfind(Alias, #diameter_app.alias, Apps) of
+ #diameter_app{options = E} = A when is_atom(E) -> %% upgrade
+ A#diameter_app{options = [{answer_errors, E}]};
+ A ->
+ A
+ end.
-%% A failing peer callback brings down the service. In the case of
-%% peer_up we could just kill the transport and emit an error but for
-%% peer_down we have no way to cleanup any state change that peer_up
-%% may have introduced.
+%% Don't bring down the service (and all associated connections)
+%% regardless of what happens.
peer_cb(MFA, Alias) ->
try state_cb(MFA, Alias) of
ModS ->
- mod_state(Alias, ModS)
+ mod_state(Alias, ModS),
+ true
catch
- E: Reason ->
- ?ERROR({E, Reason, MFA, ?STACK})
+ E:R ->
+ diameter_lib:error_report({failure, {E, R, Alias, ?STACK}}, MFA),
+ false
end.
%%% ---------------------------------------------------------------------------
%%% # connection_down/2
-%%%
-%%% Output: #state{}
%%% ---------------------------------------------------------------------------
-%% Peer process has transitioned out of the open state.
+%% Watchdog has transitioned out of state OKAY.
connection_down(Pid, #state{peerT = PeerT,
connT = ConnT}
= S) ->
- #peer{conn = TPid}
+ #peer{op_state = {?STATE_UP, WS}, %% assert
+ conn = TPid}
= P
= fetch(PeerT, Pid),
C = fetch(ConnT, TPid),
- insert(PeerT, P#peer{op_state = ?STATE_DOWN}),
+ insert(PeerT, P#peer{op_state = {?STATE_DOWN, WS}}),
connection_down(P,C,S).
%% connection_down/3
+connection_down(#peer{op_state = {?STATE_DOWN, _}}, _, _) ->
+ ok;
+
connection_down(#peer{conn = TPid,
- op_state = ?STATE_UP}
+ op_state = {?STATE_UP, _}}
= P,
#conn{caps = Caps,
apps = SApps}
@@ -1004,12 +1170,8 @@ connection_down(#peer{conn = TPid,
local_peers = LDict}
= S) ->
report_status(down, P, C, S, []),
- NewS = S#state{local_peers
- = remove_local_peer(SApps,
- {{TPid, Caps}, {SvcName, Apps}},
- LDict)},
- request_peer_down(TPid, NewS),
- NewS.
+ remove_local_peer(SApps, {{TPid, Caps}, {SvcName, Apps}}, LDict),
+ request_peer_down(TPid, S).
remove_local_peer(SApps, T, LDict) ->
lists:foldl(fun(A,D) -> rlp(A, T, D) end, LDict, SApps).
@@ -1028,47 +1190,37 @@ down_conn(Id, Alias, TC, {SvcName, Apps}) ->
%%% ---------------------------------------------------------------------------
%%% # peer_down/3
-%%%
-%%% Output: #state{}
%%% ---------------------------------------------------------------------------
-%% Peer process has died.
+%% Watchdog process has died.
-peer_down(Pid, _Reason, #state{peerT = PeerT} = S) ->
+peer_down(Pid, Reason, #state{peerT = PeerT} = S) ->
P = fetch(PeerT, Pid),
ets:delete_object(PeerT, P),
+ closed(Reason, P, S),
restart(P,S),
peer_down(P,S).
-%% peer_down/2
+%% Send an event at connection establishment failure.
+closed({shutdown, {close, _TPid, Reason}},
+ #peer{op_state = {?STATE_DOWN, _},
+ ref = Ref,
+ type = Type,
+ options = Opts},
+ #state{service_name = SvcName}) ->
+ send_event(SvcName, {closed, Ref, Reason, {type(Type), Opts}});
+closed(_, _, _) ->
+ ok.
-%% The peer has never come up ...
-peer_down(#peer{conn = B}, S)
+%% The watchdog has never reached OKAY ...
+peer_down(#peer{conn = B}, _)
when is_boolean(B) ->
- S;
+ ok;
-%% ... or it has.
-peer_down(#peer{ref = Ref,
- conn = TPid,
- type = Type,
- options = Opts}
- = P,
- #state{service_name = SvcName,
- connT = ConnT}
- = S) ->
- #conn{caps = Caps}
- = C
- = fetch(ConnT, TPid),
+%% ... or maybe it has.
+peer_down(#peer{conn = TPid} = P, #state{connT = ConnT} = S) ->
+ #conn{} = C = fetch(ConnT, TPid),
ets:delete_object(ConnT, C),
- try
- pd(P,C,S)
- after
- send_event(SvcName, {closed, Ref, {TPid, Caps}, {type(Type), Opts}})
- end.
-
-pd(#peer{op_state = ?STATE_DOWN}, _, S) ->
- S;
-pd(#peer{op_state = ?STATE_UP} = P, C, S) ->
connection_down(P,C,S).
%% restart/2
@@ -1093,7 +1245,7 @@ restart(#peer{ref = Ref,
started = Time}) ->
{Time, {Ref, T, Opts}};
-%% ... or it has: a replacement transport has already been spawned.
+%% ... or it has: a replacement has already been spawned.
restart(#peer{type = accept}) ->
false.
@@ -1119,8 +1271,8 @@ default_tc(connect, Opts) ->
default_tc(accept, _) ->
0.
-%% Bound tc below if the peer was restarted recently to avoid
-%% continuous in case of faulty config or other problems.
+%% Bound tc below if the watchdog was restarted recently to avoid
+%% continuous restarted in case of faulty config or other problems.
tc(Time, Tc) ->
choose(Tc > ?RESTART_TC
orelse timer:now_diff(now(), Time) > 1000*?RESTART_TC,
@@ -1135,7 +1287,7 @@ start_tc(Tc, T, _) ->
%% tc_timeout/2
tc_timeout({Ref, _Type, _Opts} = T, #state{service_name = SvcName} = S) ->
- tc(have_transport(SvcName, Ref), T, S).
+ tc(diameter_config:have_transport(SvcName, Ref), T, S).
tc(true, {Ref, Type, Opts}, #state{service_name = SvcName}
= S) ->
@@ -1163,7 +1315,7 @@ close(Pid, #state{service_name = SvcName,
options = Opts}
= fetch(PeerT, Pid),
- c(Pid, have_transport(SvcName, Ref), Opts).
+ c(Pid, diameter_config:have_transport(SvcName, Ref), Opts).
%% Tell watchdog to (maybe) die later ...
c(Pid, true, Opts) ->
@@ -1242,7 +1394,7 @@ cm([_,_|_], _, _, _) ->
multiple.
%%% ---------------------------------------------------------------------------
-%%% # send_request/5
+%%% # send_request/6
%%% ---------------------------------------------------------------------------
%% Send an outgoing request in its dedicated process.
@@ -1255,93 +1407,114 @@ cm([_,_|_], _, _, _) ->
%% The mod field of the #diameter_app{} here includes any extra
%% arguments passed to diameter:call/2.
-send_request({TPid, Caps, App}, Msg, Opts, Caller, SvcName) ->
+send_request({TPid, Caps, App} = T, Mask, Msg, Opts, Caller, SvcName) ->
#diameter_app{module = ModX}
= App,
- Pkt = make_packet(Msg),
-
- case cb(ModX, prepare_request, [Pkt, SvcName, {TPid, Caps}]) of
- {send, P} ->
- send_request(make_packet(P, Pkt),
- TPid,
- Caps,
- App,
- Opts,
- Caller,
- SvcName);
- {discard, Reason} ->
- {error, Reason};
- discard ->
- {error, discarded};
- T ->
- ?ERROR({invalid_return, prepare_request, App, T})
- end.
+ Pkt = make_prepare_packet(Mask, Msg),
+
+ send_req(cb(ModX, prepare_request, [Pkt, SvcName, {TPid, Caps}]),
+ Pkt,
+ T,
+ Opts,
+ Caller,
+ SvcName,
+ []).
+
+send_req({send, P}, Pkt, T, Opts, Caller, SvcName, Fs) ->
+ send_req(make_request_packet(P, Pkt), T, Opts, Caller, SvcName, Fs);
+
+send_req({discard, Reason} , _, _, _, _, _, _) ->
+ {error, Reason};
+
+send_req(discard, _, _, _, _, _, _) ->
+ {error, discarded};
+
+send_req({eval_packet, RC, F}, Pkt, T, Opts, Caller, SvcName, Fs) ->
+ send_req(RC, Pkt, T, Opts, Caller, SvcName, [F|Fs]);
-%% make_packet/1
+send_req(E, _, {_, _, App}, _, _, _, _) ->
+ ?ERROR({invalid_return, prepare_request, App, E}).
+
+%% make_prepare_packet/2
%%
%% Turn an outgoing request as passed to call/4 into a diameter_packet
%% record in preparation for a prepare_request callback.
-make_packet(Bin)
+make_prepare_packet(_, Bin)
when is_binary(Bin) ->
#diameter_packet{header = diameter_codec:decode_header(Bin),
bin = Bin};
-make_packet(#diameter_packet{msg = [#diameter_header{} = Hdr | Avps]} = Pkt) ->
- Pkt#diameter_packet{msg = [make_header(Hdr) | Avps]};
+make_prepare_packet(Mask, #diameter_packet{msg = [#diameter_header{} = Hdr
+ | Avps]}
+ = Pkt) ->
+ Pkt#diameter_packet{msg = [make_prepare_header(Mask, Hdr) | Avps]};
+
+make_prepare_packet(Mask, #diameter_packet{header = Hdr} = Pkt) ->
+ Pkt#diameter_packet{header = make_prepare_header(Mask, Hdr)};
+
+make_prepare_packet(Mask, Msg) ->
+ make_prepare_packet(Mask, #diameter_packet{msg = Msg}).
+
+%% make_prepare_header/1
-make_packet(#diameter_packet{header = Hdr} = Pkt) ->
- Pkt#diameter_packet{header = make_header(Hdr)};
+make_prepare_header(Mask, undefined) ->
+ Seq = diameter_session:sequence(Mask),
+ make_prepare_header(#diameter_header{end_to_end_id = Seq,
+ hop_by_hop_id = Seq});
-make_packet(Msg) ->
- make_packet(#diameter_packet{msg = Msg}).
+make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined,
+ hop_by_hop_id = undefined}) ->
+ Seq = diameter_session:sequence(Mask),
+ make_prepare_header(#diameter_header{end_to_end_id = Seq,
+ hop_by_hop_id = Seq});
-%% make_header/1
+make_prepare_header(Mask, #diameter_header{end_to_end_id = undefined} = H) ->
+ Seq = diameter_session:sequence(Mask),
+ make_prepare_header(H#diameter_header{end_to_end_id = Seq});
-make_header(undefined) ->
- Seq = diameter_session:sequence(),
- make_header(#diameter_header{end_to_end_id = Seq,
- hop_by_hop_id = Seq});
+make_prepare_header(Mask, #diameter_header{hop_by_hop_id = undefined} = H) ->
+ Seq = diameter_session:sequence(Mask),
+ make_prepare_header(H#diameter_header{hop_by_hop_id = Seq});
-make_header(#diameter_header{version = undefined} = Hdr) ->
- make_header(Hdr#diameter_header{version = ?DIAMETER_VERSION});
+make_prepare_header(_, Hdr) ->
+ make_prepare_header(Hdr).
-make_header(#diameter_header{end_to_end_id = undefined} = H) ->
- Seq = diameter_session:sequence(),
- make_header(H#diameter_header{end_to_end_id = Seq});
+%% make_prepare_header/1
-make_header(#diameter_header{hop_by_hop_id = undefined} = H) ->
- Seq = diameter_session:sequence(),
- make_header(H#diameter_header{hop_by_hop_id = Seq});
+make_prepare_header(#diameter_header{version = undefined} = Hdr) ->
+ make_prepare_header(Hdr#diameter_header{version = ?DIAMETER_VERSION});
-make_header(#diameter_header{} = Hdr) ->
+make_prepare_header(#diameter_header{} = Hdr) ->
Hdr;
-make_header(T) ->
+make_prepare_header(T) ->
?ERROR({invalid_header, T}).
-%% make_packet/2
+%% make_request_packet/2
%%
%% Reconstruct a diameter_packet from the return value of
%% prepare_request or prepare_retransmit callback.
-make_packet(Bin, _)
+make_request_packet(Bin, _)
when is_binary(Bin) ->
- make_packet(Bin);
+ make_prepare_packet(false, Bin);
-make_packet(#diameter_packet{msg = [#diameter_header{} | _]} = Pkt, _) ->
+make_request_packet(#diameter_packet{msg = [#diameter_header{} | _]}
+ = Pkt,
+ _) ->
Pkt;
%% Returning a diameter_packet with no header from a prepare_request
%% or prepare_retransmit callback retains the header passed into it.
%% This is primarily so that the end to end and hop by hop identifiers
%% are retained.
-make_packet(#diameter_packet{header = Hdr} = Pkt,
- #diameter_packet{header = Hdr0}) ->
+make_request_packet(#diameter_packet{header = Hdr} = Pkt,
+ #diameter_packet{header = Hdr0}) ->
Pkt#diameter_packet{header = fold_record(Hdr0, Hdr)};
-make_packet(Msg, Pkt) ->
+make_request_packet(Msg, Pkt) ->
Pkt#diameter_packet{msg = Msg}.
%% fold_record/2
@@ -1351,16 +1524,16 @@ fold_record(undefined, R) ->
fold_record(Rec, R) ->
diameter_lib:fold_tuple(2, Rec, R).
-%% send_request/7
+%% send_req/6
-send_request(Pkt, TPid, Caps, App, Opts, Caller, SvcName) ->
+send_req(Pkt, {TPid, Caps, App}, Opts, Caller, SvcName, Fs) ->
#diameter_app{alias = Alias,
dictionary = Dict,
module = ModX,
- answer_errors = AE}
+ options = [{answer_errors, AE} | _]}
= App,
- EPkt = encode(Dict, Pkt),
+ EPkt = encode(Dict, Pkt, Fs),
#options{filter = Filter,
timeout = Timeout}
@@ -1441,6 +1614,13 @@ msg(#diameter_packet{msg = undefined, bin = Bin}) ->
msg(#diameter_packet{msg = Msg}) ->
Msg.
+%% encode/3
+
+encode(Dict, Pkt, Fs) ->
+ P = encode(Dict, Pkt),
+ eval_packet(P, Fs),
+ P.
+
%% encode/2
%% Note that prepare_request can return a diameter_packet containing
@@ -1491,7 +1671,7 @@ pd([], _) ->
send_request(TPid, #diameter_packet{bin = Bin} = Pkt, Req, Timeout)
when node() == node(TPid) ->
%% Store the outgoing request before sending to avoid a race with
- %% reply reception.
+ %% reply reception.
TRef = store_request(TPid, Bin, Req, Timeout),
send(TPid, Pkt),
TRef;
@@ -1522,38 +1702,47 @@ send(Pid, Pkt) ->
%% retransmit/4
-retransmit({TPid, Caps, #diameter_app{alias = Alias} = App},
- #request{app = Alias,
- packet = Pkt}
+retransmit({TPid, Caps, #diameter_app{alias = Alias} = App} = T,
+ #request{app = Alias, packet = Pkt}
= Req,
SvcName,
Timeout) ->
have_request(Pkt, TPid) %% Don't failover to a peer we've
andalso ?THROW(timeout), %% already sent to.
- case cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]) of
- {send, P} ->
- retransmit(make_packet(P, Pkt), TPid, Caps, Req, Timeout);
- {discard, Reason} ->
- ?THROW(Reason);
- discard ->
- ?THROW(discarded);
- T ->
- ?ERROR({invalid_return, prepare_retransmit, App, T})
- end.
+ resend_req(cb(App, prepare_retransmit, [Pkt, SvcName, {TPid, Caps}]),
+ T,
+ Req,
+ Timeout,
+ []).
+
+resend_req({send, P}, T, #request{packet = Pkt} = Req, Timeout, Fs) ->
+ retransmit(make_request_packet(P, Pkt), T, Req, Timeout, Fs);
+
+resend_req({discard, Reason}, _, _, _, _) ->
+ ?THROW(Reason);
-%% retransmit/5
+resend_req(discard, _, _, _, _) ->
+ ?THROW(discarded);
-retransmit(Pkt, TPid, Caps, #request{dictionary = Dict} = Req, Timeout) ->
- EPkt = encode(Dict, Pkt),
+resend_req({eval_packet, RC, F}, T, Req, Timeout, Fs) ->
+ resend_req(RC, T, Req, Timeout, [F|Fs]);
+
+resend_req(T, {_, _, App}, _, _, _) ->
+ ?ERROR({invalid_return, prepare_retransmit, App, T}).
- NewReq = Req#request{transport = TPid,
- packet = Pkt,
- caps = Caps},
+%% retransmit/6
- ?LOG(retransmission, NewReq),
- TRef = send_request(TPid, EPkt, NewReq, Timeout),
- {TRef, NewReq}.
+retransmit(Pkt, {TPid, Caps, _}, #request{dictionary = D} = Req0, Tmo, Fs) ->
+ EPkt = encode(D, Pkt, Fs),
+
+ Req = Req0#request{transport = TPid,
+ packet = Pkt,
+ caps = Caps},
+
+ ?LOG(retransmission, Req),
+ TRef = send_request(TPid, EPkt, Req, Tmo),
+ {TRef, Req}.
%% store_request/4
@@ -1625,10 +1814,13 @@ request_peer_down(TPid, S) ->
%%% recv_request/3
%%% ---------------------------------------------------------------------------
-recv_request(TPid, Pkt, {ConnT, SvcName, Apps}) ->
+recv_request(TPid, Pkt, {ConnT, SvcName, Apps}) -> %% upgrade
+ recv_request(TPid, Pkt, {ConnT, SvcName, Apps, ?NOMASK});
+
+recv_request(TPid, Pkt, {ConnT, SvcName, Apps, Mask}) ->
try ets:lookup(ConnT, TPid) of
[C] ->
- recv_request(C, TPid, Pkt, SvcName, Apps);
+ recv_request(C, TPid, Pkt, SvcName, Apps, Mask);
[] -> %% transport has gone down
ok
catch
@@ -1638,7 +1830,12 @@ recv_request(TPid, Pkt, {ConnT, SvcName, Apps}) ->
%% recv_request/5
-recv_request(#conn{apps = SApps, caps = Caps}, TPid, Pkt, SvcName, Apps) ->
+recv_request(#conn{apps = SApps, caps = Caps},
+ TPid,
+ Pkt,
+ SvcName,
+ Apps,
+ Mask) ->
#diameter_caps{origin_host = {OH,_},
origin_realm = {OR,_}}
= Caps,
@@ -1650,6 +1847,7 @@ recv_request(#conn{apps = SApps, caps = Caps}, TPid, Pkt, SvcName, Apps) ->
{SvcName, OH, OR},
TPid,
Apps,
+ Mask,
Caps,
Pkt).
@@ -1675,20 +1873,24 @@ keyfind([Key | Rest], Pos, L) ->
T
end.
-%% recv_request/6
+%% recv_request/7
-recv_request({Id, Alias}, T, TPid, Apps, Caps, Pkt) ->
+recv_request({Id, Alias}, T, TPid, Apps, Mask, Caps, Pkt) ->
#diameter_app{dictionary = Dict}
= A
= find_app(Alias, Apps),
- recv_request(T, {TPid, Caps}, A, diameter_codec:decode(Id, Dict, Pkt));
+ recv_request(T,
+ {TPid, Caps},
+ A,
+ Mask,
+ diameter_codec:decode(Id, Dict, Pkt));
%% Note that the decode is different depending on whether or not Id is
%% ?APP_ID_RELAY.
%% DIAMETER_APPLICATION_UNSUPPORTED 3007
%% A request was sent for an application that is not supported.
-recv_request(false, T, TPid, _, _, Pkt) ->
+recv_request(false, T, TPid, _, _, _, Pkt) ->
As = collect_avps(Pkt),
protocol_error(3007, T, TPid, Pkt#diameter_packet{avps = As}).
@@ -1700,7 +1902,7 @@ collect_avps(Pkt) ->
As
end.
-%% recv_request/4
+%% recv_request/5
%% Wrong number of bits somewhere in the message: reply.
%%
@@ -1709,7 +1911,7 @@ collect_avps(Pkt) ->
%% set to an unrecognized value, or that is inconsistent with the
%% AVP's definition.
%%
-recv_request(T, {TPid, _}, _, #diameter_packet{errors = [Bs | _]} = Pkt)
+recv_request(T, {TPid, _}, _, _, #diameter_packet{errors = [Bs | _]} = Pkt)
when is_bitstring(Bs) ->
protocol_error(3009, T, TPid, Pkt);
@@ -1724,6 +1926,7 @@ recv_request(T, {TPid, _}, _, #diameter_packet{errors = [Bs | _]} = Pkt)
recv_request(T,
{TPid, _},
#diameter_app{id = Id},
+ _,
#diameter_packet{header = #diameter_header{is_proxiable = P},
msg = M}
= Pkt)
@@ -1741,6 +1944,7 @@ recv_request(T,
recv_request(T,
{TPid, _},
_,
+ _,
#diameter_packet{header = #diameter_header{is_error = true}}
= Pkt) ->
protocol_error(3008, T, TPid, Pkt);
@@ -1749,14 +1953,20 @@ recv_request(T,
%% in the relay application. Don't distinguish between the two since
%% each application has its own callback config. That is, the user can
%% easily distinguish between the two cases.
-recv_request(T, TC, App, Pkt) ->
- request_cb(T, TC, App, examine(Pkt)).
+recv_request(T, TC, App, Mask, Pkt) ->
+ request_cb(T, TC, App, Mask, examine(Pkt)).
%% Note that there may still be errors but these aren't protocol
%% (3xxx) errors that lead to an answer-message.
-request_cb({SvcName, _OH, _OR} = T, TC, App, Pkt) ->
- request_cb(cb(App, handle_request, [Pkt, SvcName, TC]), App, T, TC, Pkt).
+request_cb({SvcName, _OH, _OR} = T, TC, App, Mask, Pkt) ->
+ request_cb(cb(App, handle_request, [Pkt, SvcName, TC]),
+ App,
+ Mask,
+ T,
+ TC,
+ [],
+ Pkt).
%% examine/1
%%
@@ -1776,7 +1986,7 @@ examine(#diameter_packet{errors = Es} = Pkt) ->
Pkt#diameter_packet{errors = [5011 | Es]}.
%% It's odd/unfortunate that this isn't a protocol error.
-%% request_cb/5
+%% request_cb/7
%% A reply may be an answer-message, constructed either here or by
%% the handle_request callback. The header from the incoming request
@@ -1786,21 +1996,23 @@ examine(#diameter_packet{errors = Es} = Pkt) ->
request_cb({reply, Ans},
#diameter_app{dictionary = Dict},
_,
+ _,
{TPid, _},
+ Fs,
Pkt) ->
- reply(Ans, Dict, TPid, Pkt);
+ reply(Ans, Dict, TPid, Fs, Pkt);
%% An 3xxx result code, for which the E-bit is set in the header.
-request_cb({protocol_error, RC}, _, T, {TPid, _}, Pkt)
+request_cb({protocol_error, RC}, _, _, T, {TPid, _}, Fs, Pkt)
when 3000 =< RC, RC < 4000 ->
- protocol_error(RC, T, TPid, Pkt);
+ protocol_error(RC, T, TPid, Fs, Pkt);
%% RFC 3588 says we must reply 3001 to anything unrecognized or
%% unsupported. 'noreply' is undocumented (and inappropriately named)
%% backwards compatibility for this, protocol_error the documented
%% alternative.
-request_cb(noreply, _, T, {TPid, _}, Pkt) ->
- protocol_error(3001, T, TPid, Pkt);
+request_cb(noreply, _, _, T, {TPid, _}, Fs, Pkt) ->
+ protocol_error(3001, T, TPid, Fs, Pkt);
%% Relay a request to another peer. This is equivalent to doing an
%% explicit call/4 with the message in question except that (1) a loop
@@ -1820,38 +2032,51 @@ request_cb(noreply, _, T, {TPid, _}, Pkt) ->
request_cb({A, Opts},
#diameter_app{id = Id}
= App,
+ Mask,
T,
TC,
+ Fs,
Pkt)
when A == relay, Id == ?APP_ID_RELAY;
A == proxy, Id /= ?APP_ID_RELAY;
A == resend ->
- resend(Opts, App, T, TC, Pkt);
+ resend(Opts, App, Mask, T, TC, Fs, Pkt);
-request_cb(discard, _, _, _, _) ->
+request_cb(discard, _, _, _, _, _, _) ->
ok;
-request_cb({eval, RC, F}, App, T, TC, Pkt) ->
- request_cb(RC, App, T, TC, Pkt),
+request_cb({eval_packet, RC, F}, App, Mask, T, TC, Fs, Pkt) ->
+ request_cb(RC, App, Mask, T, TC, [F|Fs], Pkt);
+
+request_cb({eval, RC, F}, App, Mask, T, TC, Fs, Pkt) ->
+ request_cb(RC, App, Mask, T, TC, Fs, Pkt),
diameter_lib:eval(F).
-%% protocol_error/4
+%% protocol_error/5
-protocol_error(RC, {_, OH, OR}, TPid, #diameter_packet{avps = Avps} = Pkt) ->
+protocol_error(RC, {_, OH, OR}, TPid, Fs, Pkt) ->
+ #diameter_packet{avps = Avps} = Pkt,
?LOG({error, RC}, Pkt),
- reply(answer_message({OH, OR, RC}, Avps), ?BASE, TPid, Pkt).
+ reply(answer_message({OH, OR, RC}, Avps), ?BASE, TPid, Fs, Pkt).
-%% resend/5
+%% protocol_error/4
+
+protocol_error(RC, T, TPid, Pkt) ->
+ protocol_error(RC, T, TPid, [], Pkt).
+
+%% resend/7
%%
%% Resend a message as a relay or proxy agent.
resend(Opts,
#diameter_app{} = App,
+ Mask,
{_SvcName, OH, _OR} = T,
{_TPid, _Caps} = TC,
+ Fs,
#diameter_packet{avps = Avps} = Pkt) ->
{Code, _Flags, Vid} = ?BASE:avp_header('Route-Record'),
- resend(is_loop(Code, Vid, OH, Avps), Opts, App, T, TC, Pkt).
+ resend(is_loop(Code, Vid, OH, Avps), Opts, App, Mask, T, TC, Fs, Pkt).
%% DIAMETER_LOOP_DETECTED 3005
%% An agent detected a loop while trying to get the message to the
@@ -1859,8 +2084,8 @@ resend(Opts,
%% if one is available, but the peer reporting the error has
%% identified a configuration problem.
-resend(true, _, _, T, {TPid, _}, Pkt) -> %% Route-Record loop
- protocol_error(3005, T, TPid, Pkt);
+resend(true, _, _, _, T, {TPid, _}, Fs, Pkt) -> %% Route-Record loop
+ protocol_error(3005, T, TPid, Fs, Pkt);
%% 6.1.8. Relaying and Proxying Requests
%%
@@ -1871,16 +2096,18 @@ resend(true, _, _, T, {TPid, _}, Pkt) -> %% Route-Record loop
resend(false,
Opts,
App,
+ Mask,
{SvcName, _, _} = T,
{TPid, #diameter_caps{origin_host = {_, OH}}},
+ Fs,
#diameter_packet{header = Hdr0,
avps = Avps}
= Pkt) ->
Route = #diameter_avp{data = {?BASE, 'Route-Record', OH}},
- Seq = diameter_session:sequence(),
+ Seq = diameter_session:sequence(Mask),
Hdr = Hdr0#diameter_header{hop_by_hop_id = Seq},
Msg = [Hdr, Route | Avps],
- resend(call(SvcName, App, Msg, Opts), T, TPid, Pkt).
+ resend(call(SvcName, App, Msg, Opts), T, TPid, Fs, Pkt).
%% The incoming request is relayed with the addition of a
%% Route-Record. Note the requirement on the return from call/4 below,
%% which places a requirement on the value returned by the
@@ -1906,15 +2133,18 @@ resend(#diameter_packet{bin = B}
= Pkt,
_,
TPid,
+ Fs,
#diameter_packet{header = #diameter_header{hop_by_hop_id = Id},
transport_data = TD}) ->
- send(TPid, Pkt#diameter_packet{bin = diameter_codec:hop_by_hop_id(Id, B),
- transport_data = TD});
+ P = Pkt#diameter_packet{bin = diameter_codec:hop_by_hop_id(Id, B),
+ transport_data = TD},
+ eval_packet(P, Fs),
+ send(TPid, P);
%% TODO: counters
%% Or not: DIAMETER_UNABLE_TO_DELIVER.
-resend(_, T, TPid, Pkt) ->
- protocol_error(3002, T, TPid, Pkt).
+resend(_, T, TPid, Fs, Pkt) ->
+ protocol_error(3002, T, TPid, Fs, Pkt).
%% is_loop/4
%%
@@ -1936,45 +2166,68 @@ is_loop(Code, Vid, OH, [_ | Avps])
is_loop(Code, Vid, OH, Avps) ->
is_loop(Code, Vid, ?BASE:avp(encode, OH, 'Route-Record'), Avps).
-%% reply/4
+%% reply/5
%%
%% Send a locally originating reply.
+%% Skip the setting of Result-Code and Failed-AVP's below.
+reply([Msg], Dict, TPid, Fs, Pkt)
+ when is_list(Msg);
+ is_tuple(Msg) ->
+ reply(Msg, Dict, TPid, Fs, Pkt#diameter_packet{errors = []});
+
%% No errors or a diameter_header/avp list.
-reply(Msg, Dict, TPid, #diameter_packet{errors = Es,
- transport_data = TD}
- = ReqPkt)
+reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = Es} = ReqPkt)
when [] == Es;
is_record(hd(Msg), diameter_header) ->
- Pkt = diameter_codec:encode(Dict, make_reply_packet(Msg, ReqPkt)),
+ Pkt = diameter_codec:encode(Dict, make_answer_packet(Msg, ReqPkt)),
+ eval_packet(Pkt, Fs),
incr(send, Pkt, Dict, TPid), %% count result codes in sent answers
- send(TPid, Pkt#diameter_packet{transport_data = TD});
+ send(TPid, Pkt);
%% Or not: set Result-Code and Failed-AVP AVP's.
-reply(Msg, Dict, TPid, #diameter_packet{errors = [H|_] = Es} = Pkt) ->
+reply(Msg, Dict, TPid, Fs, #diameter_packet{errors = [H|_] = Es} = Pkt) ->
reply(rc(Msg, rc(H), [A || {_,A} <- Es], Dict),
Dict,
TPid,
+ Fs,
Pkt#diameter_packet{errors = []}).
-%% make_reply_packet/2
+eval_packet(Pkt, Fs) ->
+ lists:foreach(fun(F) -> diameter_lib:eval([F,Pkt]) end, Fs).
+
+%% make_answer_packet/2
+
+%% A reply message clears the R and T flags and retains the P flag.
+%% The E flag will be set at encode. 6.2 of 3588 requires the same P
+%% flag on an answer as on the request. A #diameter_packet{} returned
+%% from a handle_request callback can circumvent this by setting its
+%% own header values.
+make_answer_packet(#diameter_packet{header = Hdr,
+ msg = Msg,
+ transport_data = TD},
+ #diameter_packet{header = ReqHdr}) ->
+ Hdr0 = ReqHdr#diameter_header{version = ?DIAMETER_VERSION,
+ is_request = false,
+ is_error = undefined,
+ is_retransmitted = false},
+ #diameter_packet{header = fold_record(Hdr0, Hdr),
+ msg = Msg,
+ transport_data = TD};
%% Binaries and header/avp lists are sent as-is.
-make_reply_packet(Bin, _)
+make_answer_packet(Bin, #diameter_packet{transport_data = TD})
when is_binary(Bin) ->
- #diameter_packet{bin = Bin};
-make_reply_packet([#diameter_header{} | _] = Msg, _) ->
- #diameter_packet{msg = Msg};
-
-%% Otherwise a reply message clears the R and T flags and retains the
-%% P flag. The E flag will be set at encode.
-make_reply_packet(Msg, #diameter_packet{header = ReqHdr}) ->
- Hdr = ReqHdr#diameter_header{version = ?DIAMETER_VERSION,
- is_request = false,
- is_error = undefined,
- is_retransmitted = false},
- #diameter_packet{header = Hdr,
- msg = Msg}.
+ #diameter_packet{bin = Bin,
+ transport_data = TD};
+make_answer_packet([#diameter_header{} | _] = Msg,
+ #diameter_packet{transport_data = TD}) ->
+ #diameter_packet{msg = Msg,
+ transport_data = TD};
+
+%% Otherwise, preserve transport_data.
+make_answer_packet(Msg, #diameter_packet{transport_data = TD} = Pkt) ->
+ make_answer_packet(#diameter_packet{msg = Msg, transport_data = TD}, Pkt).
%% rc/1
@@ -1987,7 +2240,10 @@ rc(RC) ->
rc(Rec, RC, Failed, Dict)
when is_integer(RC) ->
- set(Rec, [{'Result-Code', RC} | failed_avp(Rec, Failed, Dict)], Dict).
+ set(Rec,
+ lists:append([rc(Rec, {'Result-Code', RC}, Dict),
+ failed_avp(Rec, Failed, Dict)]),
+ Dict).
%% Reply as name and tuple list ...
set([_|_] = Ans, Avps, _) ->
@@ -1997,6 +2253,22 @@ set([_|_] = Ans, Avps, _) ->
set(Rec, Avps, Dict) ->
Dict:'#set-'(Avps, Rec).
+%% rc/3
+%%
+%% Turn the result code into a list if its optional and only set it if
+%% the arity is 1 or {0,1}. In other cases (which probably shouldn't
+%% exist in practise) we can't know what's appropriate.
+
+rc([MsgName | _], {'Result-Code' = K, RC} = T, Dict) ->
+ case Dict:avp_arity(MsgName, 'Result-Code') of
+ 1 -> [T];
+ {0,1} -> [{K, [RC]}];
+ _ -> []
+ end;
+
+rc(Rec, T, Dict) ->
+ rc([Dict:rec2msg(element(1, Rec))], T, Dict).
+
%% failed_avp/3
failed_avp(_, [] = No, _) ->
@@ -2204,45 +2476,39 @@ handle_answer(SvcName, _, {error, Req, Reason}) ->
handle_answer(SvcName,
AnswerErrors,
{answer, #request{dictionary = Dict} = Req, Pkt}) ->
- a(examine(diameter_codec:decode(Dict, Pkt)),
- SvcName,
- AnswerErrors,
- Req).
+ answer(examine(diameter_codec:decode(Dict, Pkt)),
+ SvcName,
+ AnswerErrors,
+ Req).
%% We don't really need to do a full decode if we're a relay and will
%% just resend with a new hop by hop identifier, but might a proxy
%% want to examine the answer?
-a(#diameter_packet{errors = []}
- = Pkt,
- SvcName,
- AE,
- #request{transport = TPid,
- dictionary = Dict,
- caps = Caps,
- packet = P}
- = Req) ->
+answer(Pkt, SvcName, AE, #request{transport = TPid,
+ dictionary = Dict}
+ = Req) ->
try
- incr(in, Pkt, Dict, TPid)
+ incr(recv, Pkt, Dict, TPid)
of
- _ ->
- cb(Req, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}])
+ _ -> a(Pkt, SvcName, AE, Req)
catch
exit: {invalid_error_bit, _} = E ->
- e(Pkt#diameter_packet{errors = [E]}, SvcName, AE, Req)
- end;
+ a(Pkt#diameter_packet{errors = [E]}, SvcName, AE, Req)
+ end.
-a(#diameter_packet{} = Pkt, SvcName, AE, Req) ->
- e(Pkt, SvcName, AE, Req).
+a(#diameter_packet{errors = Es} = Pkt, SvcName, AE, #request{transport = TPid,
+ caps = Caps,
+ packet = P}
+ = Req)
+ when [] == Es;
+ callback == AE ->
+ cb(Req, handle_answer, [Pkt, msg(P), SvcName, {TPid, Caps}]);
-e(Pkt, SvcName, callback, #request{transport = TPid,
- caps = Caps,
- packet = Pkt}
- = Req) ->
- cb(Req, handle_answer, [Pkt, msg(Pkt), SvcName, {TPid, Caps}]);
-e(Pkt, SvcName, report, Req) ->
+a(Pkt, SvcName, report, Req) ->
x(errors, handle_answer, [SvcName, Req, Pkt]);
-e(Pkt, SvcName, discard, Req) ->
+
+a(Pkt, SvcName, discard, Req) ->
x({errors, handle_answer, [SvcName, Req, Pkt]}).
%% Note that we don't check that the application id in the answer's
@@ -2257,15 +2523,16 @@ e(Pkt, SvcName, discard, Req) ->
incr(_, #diameter_packet{msg = undefined}, _, _) ->
ok;
-incr(Dir, Pkt, Dict, TPid)
- when is_pid(TPid) ->
+incr(recv = D, #diameter_packet{header = H, errors = [_|_]}, _, TPid) ->
+ incr(TPid, {diameter_codec:msg_id(H), D, error});
+
+incr(Dir, Pkt, Dict, TPid) ->
#diameter_packet{header = #diameter_header{is_error = E}
= Hdr,
msg = Rec}
= Pkt,
- D = choose(E, ?BASE, Dict),
- RC = int(get_avp_value(D, 'Result-Code', Rec)),
+ RC = int(get_avp_value(Dict, 'Result-Code', Rec)),
PE = is_protocol_error(RC),
%% Check that the E bit is set only for 3xxx result codes.
@@ -2273,15 +2540,21 @@ incr(Dir, Pkt, Dict, TPid)
orelse (E andalso PE)
orelse x({invalid_error_bit, RC}, answer, [Dir, Pkt]),
- Ctr = rc_counter(D, Rec, RC),
- is_tuple(Ctr)
- andalso incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}).
+ irc(TPid, Hdr, Dir, rc_counter(Dict, Rec, RC)).
+
+irc(_, _, _, undefined) ->
+ false;
+
+irc(TPid, Hdr, Dir, Ctr) ->
+ incr(TPid, {diameter_codec:msg_id(Hdr), Dir, Ctr}).
%% incr/2
incr(TPid, Counter) ->
diameter_stats:incr(Counter, TPid, 1).
+%% error_counter/2
+
%% RFC 3588, 7.6:
%%
%% All Diameter answer messages defined in vendor-specific
@@ -2291,26 +2564,27 @@ incr(TPid, Counter) ->
%% Maintain statistics assuming one or the other, not both, which is
%% surely the intent of the RFC.
-rc_counter(_, _, RC)
- when is_integer(RC) ->
- {'Result-Code', RC};
-rc_counter(D, Rec, _) ->
- rcc(get_avp_value(D, 'Experimental-Result', Rec)).
+rc_counter(Dict, Rec, undefined) ->
+ er(get_avp_value(Dict, 'Experimental-Result', Rec));
+rc_counter(_, _, RC) ->
+ {'Result-Code', RC}.
%% Outgoing answers may be in any of the forms messages can be sent
%% in. Incoming messages will be records. We're assuming here that the
%% arity of the result code AVP's is 0 or 1.
-rcc([{_,_,RC} = T])
- when is_integer(RC) ->
+er([{_,_,N} = T | _])
+ when is_integer(N) ->
T;
-rcc({_,_,RC} = T)
- when is_integer(RC) ->
+er({_,_,N} = T)
+ when is_integer(N) ->
T;
-rcc(_) ->
+er(_) ->
undefined.
-int([N])
+%% Extract the first good looking integer. There's no guarantee
+%% that what we're looking for has arity 1.
+int([N|_])
when is_integer(N) ->
N;
int(N)
@@ -2355,8 +2629,11 @@ rt(#request{packet = #diameter_packet{msg = undefined}}, _) ->
false; %% TODO: Not what we should do.
%% ... or not.
-rt(#request{packet = #diameter_packet{msg = Msg}, dictionary = D} = Req, S) ->
- find_transport(get_destination(Msg, D), Req, S).
+rt(#request{packet = #diameter_packet{msg = Msg},
+ dictionary = Dict}
+ = Req,
+ S) ->
+ find_transport(get_destination(Dict, Msg), Req, S).
%%% ---------------------------------------------------------------------------
%%% # report_status/5
@@ -2389,7 +2666,7 @@ send_event(#diameter_event{service = SvcName} = E) ->
%%% # share_peer/5
%%% ---------------------------------------------------------------------------
-share_peer(up, Caps, Aliases, TPid, #state{share_peers = true,
+share_peer(up, Caps, Aliases, TPid, #state{options = [_, {_, true} | _],
service_name = Svc}) ->
diameter_peer:notify(Svc, {peer, TPid, Aliases, Caps});
@@ -2400,11 +2677,11 @@ share_peer(_, _, _, _, _) ->
%%% # share_peers/2
%%% ---------------------------------------------------------------------------
-share_peers(Pid, #state{share_peers = true,
+share_peers(Pid, #state{options = [_, {_, true} | _],
local_peers = PDict}) ->
?Dict:fold(fun(A,Ps,ok) -> sp(Pid, A, Ps), ok end, ok, PDict);
-share_peers(_, #state{share_peers = false}) ->
+share_peers(_, _) ->
ok.
sp(Pid, Alias, Peers) ->
@@ -2414,39 +2691,31 @@ sp(Pid, Alias, Peers) ->
%%% # remote_peer_up/4
%%% ---------------------------------------------------------------------------
-remote_peer_up(Pid, Aliases, Caps, #state{use_shared_peers = true,
+remote_peer_up(Pid, Aliases, Caps, #state{options = [_, _, {_, true} | _],
service = Svc,
- shared_peers = PDict}
- = S) ->
+ shared_peers = PDict}) ->
#diameter_service{applications = Apps} = Svc,
- Update = lists:filter(fun(A) ->
- lists:keymember(A, #diameter_app.alias, Apps)
- end,
- Aliases),
- S#state{shared_peers = rpu(Pid, Caps, PDict, Update)};
+ Key = #diameter_app.alias,
+ As = lists:filter(fun(A) -> lists:keymember(A, Key, Apps) end, Aliases),
+ rpu(Pid, Caps, PDict, As);
-remote_peer_up(_, _, _, #state{use_shared_peers = false} = S) ->
- S.
+remote_peer_up(_, _, _, #state{options = [_, _, {_, false} | _]}) ->
+ ok.
rpu(_, _, PDict, []) ->
PDict;
rpu(Pid, Caps, PDict, Aliases) ->
erlang:monitor(process, Pid),
T = {Pid, Caps},
- lists:foldl(fun(A,D) -> ?Dict:append(A, T, D) end,
- PDict,
- Aliases).
+ lists:foreach(fun(A) -> ?Dict:append(A, T, PDict) end, Aliases).
%%% ---------------------------------------------------------------------------
%%% # remote_peer_down/2
%%% ---------------------------------------------------------------------------
-remote_peer_down(Pid, #state{use_shared_peers = true,
- shared_peers = PDict}
- = S) ->
- S#state{shared_peers = lists:foldl(fun(A,D) -> rpd(Pid, A, D) end,
- PDict,
- ?Dict:fetch_keys(PDict))}.
+remote_peer_down(Pid, #state{options = [_, _, {_, true} | _],
+ shared_peers = PDict}) ->
+ lists:foreach(fun(A) -> rpd(Pid, A, PDict) end, ?Dict:fetch_keys(PDict)).
rpd(Pid, Alias, PDict) ->
?Dict:update(Alias, fun(Ps) -> lists:keydelete(Pid, 1, Ps) end, PDict).
@@ -2468,12 +2737,12 @@ find_transport({alias, Alias}, Msg, Opts, #state{service = Svc} = S) ->
find_transport(#diameter_app{} = App, Msg, Opts, S) ->
ft(App, Msg, Opts, S).
-ft(#diameter_app{module = Mod, dictionary = D} = App, Msg, Opts, S) ->
+ft(#diameter_app{module = Mod, dictionary = Dict} = App, Msg, Opts, S) ->
#options{filter = Filter,
extra = Xtra}
= Opts,
pick_peer(App#diameter_app{module = Mod ++ Xtra},
- get_destination(Msg, D),
+ get_destination(Dict, Msg),
Filter,
S);
ft(false = No, _, _, _) ->
@@ -2511,7 +2780,7 @@ find_transport([_,_] = RH,
%% get_destination/2
-get_destination(Msg, Dict) ->
+get_destination(Dict, Msg) ->
[str(get_avp_value(Dict, 'Destination-Realm', Msg)),
str(get_avp_value(Dict, 'Destination-Host', Msg))].
@@ -2537,6 +2806,15 @@ str(T) ->
%% question. The third form allows messages to be sent as is, without
%% a dictionary, which is needed in the case of relay agents, for one.
+%% Messages will be header/avps list as a relay and the only AVP's we
+%% look for are in the common dictionary. This is required since the
+%% relay dictionary doesn't inherit the common dictionary (which maybe
+%% it should).
+get_avp_value(?RELAY, Name, Msg) ->
+ get_avp_value(?BASE, Name, Msg);
+
+%% Message sent as a header/avps list, probably a relay case but not
+%% necessarily.
get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
try
{Code, _, VId} = Dict:avp_header(Name),
@@ -2550,6 +2828,7 @@ get_avp_value(Dict, Name, [#diameter_header{} | Avps]) ->
undefined
end;
+%% Outgoing message as a name/values list.
get_avp_value(_, Name, [_MsgName | Avps]) ->
case lists:keyfind(Name, 1, Avps) of
{_, V} ->
@@ -2558,6 +2837,11 @@ get_avp_value(_, Name, [_MsgName | Avps]) ->
undefined
end;
+%% Record might be an answer message in the common dictionary.
+get_avp_value(Dict, Name, Rec)
+ when Dict /= ?BASE, element(1, Rec) == 'diameter_base_answer-message' ->
+ get_avp_value(?BASE, Name, Rec);
+
%% Message is typically a record but not necessarily: diameter:call/4
%% can be passed an arbitrary term.
get_avp_value(Dict, Name, Rec) ->
@@ -2752,20 +3036,59 @@ transports(#state{peerT = PeerT}) ->
'Vendor-Specific-Application-Id',
'Firmware-Revision']).
+%% The config returned by diameter:service_info(SvcName, all).
-define(ALL_INFO, [capabilities,
applications,
transport,
pending,
- statistics]).
+ options]).
+
+%% The rest.
+-define(OTHER_INFO, [connections,
+ name,
+ peers,
+ statistics]).
-service_info(Items, S)
- when is_list(Items) ->
- [{complete(I), service_info(I,S)} || I <- Items];
service_info(Item, S)
when is_atom(Item) ->
- service_info(Item, S, true).
+ case tagged_info(Item, S) of
+ {_, T} -> T;
+ undefined = No -> No
+ end;
+
+service_info(Items, S) ->
+ tagged_info(Items, S).
-service_info(Item, #state{service = Svc} = S, Complete) ->
+tagged_info(Item, S)
+ when is_atom(Item) ->
+ case complete(Item) of
+ {value, I} ->
+ {I, complete_info(I,S)};
+ false ->
+ undefined
+ end;
+
+tagged_info(TPid, #state{peerT = PT, connT = CT})
+ when is_pid(TPid) ->
+ try
+ [#conn{peer = Pid}] = ets:lookup(CT, TPid),
+ [#peer{ref = Ref, type = Type, options = Opts}] = ets:lookup(PT, Pid),
+ [{ref, Ref},
+ {type, Type},
+ {options, Opts}]
+ catch
+ error:_ ->
+ []
+ end;
+
+tagged_info(Items, S)
+ when is_list(Items) ->
+ [T || I <- Items, T <- [tagged_info(I,S)], T /= undefined, T /= []];
+
+tagged_info(_, _) ->
+ undefined.
+
+complete_info(Item, #state{service = Svc} = S) ->
case Item of
name ->
S#state.service_name;
@@ -2808,85 +3131,176 @@ service_info(Item, #state{service = Svc} = S, Complete) ->
capabilities -> service_info(?CAP_INFO, S);
applications -> info_apps(S);
transport -> info_transport(S);
+ options -> info_options(S);
pending -> info_pending(S);
- statistics -> info_stats(S);
- keys -> ?ALL_INFO ++ ?CAP_INFO; %% mostly for test
+ keys -> ?ALL_INFO ++ ?CAP_INFO ++ ?OTHER_INFO;
all -> service_info(?ALL_INFO, S);
- _ when Complete -> service_info(complete(Item), S, false);
- _ -> undefined
+ statistics -> info_stats(S);
+ connections -> info_connections(S);
+ peers -> info_peers(S)
end.
+complete(I)
+ when I == keys;
+ I == all ->
+ {value, I};
complete(Pre) ->
P = atom_to_list(Pre),
- case [I || I <- [name | ?ALL_INFO] ++ ?CAP_INFO,
+ case [I || I <- ?ALL_INFO ++ ?CAP_INFO ++ ?OTHER_INFO,
lists:prefix(P, atom_to_list(I))]
of
- [I] -> I;
- _ -> Pre
+ [I] -> {value, I};
+ _ -> false
end.
+%% info_stats/1
+
info_stats(#state{peerT = PeerT}) ->
- Peers = ets:select(PeerT, [{#peer{ref = '$1', conn = '$2', _ = '_'},
- [{'is_pid', '$2'}],
- [['$1', '$2']]}]),
- diameter_stats:read(lists:append(Peers)).
-%% TODO: include peer identities in return value
-
-info_transport(#state{peerT = PeerT, connT = ConnT}) ->
- dict:fold(fun it/3,
+ MatchSpec = [{#peer{ref = '$1', conn = '$2', _ = '_'},
+ [{'is_pid', '$2'}],
+ [['$1', '$2']]}],
+ try ets:select(PeerT, MatchSpec) of
+ L ->
+ diameter_stats:read(lists:append(L))
+ catch
+ error: badarg -> [] %% service has gone down
+ end.
+
+%% info_transport/1
+%%
+%% One entry per configured transport. Statistics for each entry are
+%% the accumulated values for the ref and associated peer pids.
+
+info_transport(S) ->
+ PeerD = peer_dict(S, config_dict(S)),
+ RefsD = dict:map(fun(_, Ls) -> [P || L <- Ls, {peer, {P,_}} <- L] end,
+ PeerD),
+ Refs = lists:append(dict:fold(fun(R, Ps, A) -> [[R|Ps] | A] end,
+ [],
+ RefsD)),
+ Stats = diameter_stats:read(Refs),
+ dict:fold(fun(R, Ls, A) ->
+ Ps = dict:fetch(R, RefsD),
+ [[{ref, R} | transport(Ls)] ++ [stats([R|Ps], Stats)]
+ | A]
+ end,
[],
- ets:foldl(fun(T,A) -> it_acc(ConnT, A, T) end,
- dict:new(),
- PeerT)).
-
-it(Ref, [[{type, connect} | _] = L], Acc) ->
- [[{ref, Ref} | L] | Acc];
-it(Ref, [[{type, accept}, {options, Opts} | _] | _] = L, Acc) ->
- [[{ref, Ref},
- {type, listen},
- {options, Opts},
- {accept, [lists:nthtail(2,A) || A <- L]}]
- | Acc].
-%% Each entry has the same Opts. (TODO)
-
-it_acc(ConnT, Acc, #peer{pid = Pid,
- type = Type,
- ref = Ref,
- options = Opts,
- op_state = OS,
- started = T,
- conn = TPid}) ->
+ PeerD).
+
+%% Only a config entry for a listening transport: use it.
+transport([[{type, listen}, _] = L]) ->
+ L ++ [{accept, []}];
+
+%% Only one config or peer entry for a connecting transport: use it.
+transport([[{type, connect} | _] = L]) ->
+ L;
+
+%% Peer entries: discard config. Note that the peer entries have
+%% length at least 3.
+transport([[_,_] | L]) ->
+ transport(L);
+
+%% Possibly many peer entries for a listening transport. Note that all
+%% have the same options by construction, which is not terribly space
+%% efficient. (TODO: all entries for the same Ref should share options.)
+transport([[{type, accept}, {options, Opts} | _] | _] = Ls) ->
+ [{type, listen},
+ {options, Opts},
+ {accept, [lists:nthtail(2,L) || L <- Ls]}].
+
+peer_dict(#state{peerT = PeerT, connT = ConnT}, Dict0) ->
+ try ets:tab2list(PeerT) of
+ L ->
+ lists:foldl(fun(T,A) -> peer_acc(ConnT, A, T) end, Dict0, L)
+ catch
+ error: badarg -> Dict0 %% service has gone down
+ end.
+
+peer_acc(ConnT, Acc, #peer{pid = Pid,
+ type = Type,
+ ref = Ref,
+ options = Opts,
+ op_state = OS,
+ started = T,
+ conn = TPid}) ->
+ WS = wd_state(OS),
dict:append(Ref,
[{type, Type},
{options, Opts},
- {watchdog, {Pid, T, OS}}
- | info_conn(ConnT, TPid)],
+ {watchdog, {Pid, T, WS}}
+ | info_conn(ConnT, TPid, WS /= ?WD_DOWN)],
Acc).
-info_conn(ConnT, TPid) ->
- info_conn(ets:lookup(ConnT, TPid)).
+info_conn(ConnT, TPid, true)
+ when is_pid(TPid) ->
+ try ets:lookup(ConnT, TPid) of
+ T -> info_conn(T)
+ catch
+ error: badarg -> [] %% service has gone down
+ end;
+info_conn(_, _, _) ->
+ [].
+
+%% The point of extracting the config here is so that 'transport' info
+%% has one entry for each transport ref, the peer table only
+%% containing entries that have a living watchdog.
+
+config_dict(#state{service_name = SvcName}) ->
+ lists:foldl(fun config_acc/2,
+ dict:new(),
+ diameter_config:lookup(SvcName)).
+
+config_acc({Ref, T, Opts}, Dict)
+ when T == listen;
+ T == connect ->
+ dict:store(Ref, [[{type, T}, {options, Opts}]], Dict);
+config_acc(_, Dict) ->
+ Dict.
+
+wd_state({_,S}) ->
+ S;
+wd_state(?STATE_UP) ->
+ ?WD_OKAY;
+wd_state(?STATE_DOWN) ->
+ ?WD_DOWN.
info_conn([#conn{pid = Pid, apps = SApps, caps = Caps, started = T}]) ->
[{peer, {Pid, T}},
{apps, SApps},
- {caps, info_caps(Caps)}];
+ {caps, info_caps(Caps)}
+ | try [{port, info_port(Pid)}] catch _:_ -> [] end];
info_conn([] = No) ->
No.
+%% Extract information that the processes involved are expected to
+%% "publish" in their process dictionaries. Simple but backhanded.
+info_port(Pid) ->
+ {_, PD} = process_info(Pid, dictionary),
+ {_, T} = lists:keyfind({diameter_peer_fsm, start}, 1, PD),
+ {TPid, {_Type, TMod, _Cfg}} = T,
+ {_, TD} = process_info(TPid, dictionary),
+ {_, Data} = lists:keyfind({TMod, info}, 1, TD),
+ [{owner, TPid},
+ {module, TMod}
+ | try TMod:info(Data) catch _:_ -> [] end].
+
+%% Use the fields names from diameter_caps instead of
+%% diameter_base_CER to distinguish between the 2-tuple values
+%% compared to the single capabilities values. Note also that the
+%% returned list is tagged 'caps' rather than 'capabilities' to
+%% emphasize the difference.
info_caps(#diameter_caps{} = C) ->
lists:zip(record_info(fields, diameter_caps), tl(tuple_to_list(C))).
info_apps(#state{service = #diameter_service{applications = Apps}}) ->
lists:map(fun mk_app/1, Apps).
-mk_app(#diameter_app{alias = Alias,
- dictionary = Dict,
- module = ModX,
- id = Id}) ->
- [{alias, Alias},
- {dictionary, Dict},
- {module, ModX},
- {id, Id}].
+mk_app(#diameter_app{} = A) ->
+ lists:zip(record_info(fields, diameter_app), tl(tuple_to_list(A))).
+
+%% info_pending/1
+%%
+%% One entry for each outgoing request whose answer is outstanding.
info_pending(#state{} = S) ->
MatchSpec = [{{'$1',
@@ -2900,4 +3314,67 @@ info_pending(#state{} = S) ->
{{transport, '$2'}},
{{from, '$3'}}]}}]}],
- ets:select(?REQUEST_TABLE, MatchSpec).
+ try
+ ets:select(?REQUEST_TABLE, MatchSpec)
+ catch
+ error: badarg -> [] %% service has gone down
+ end.
+
+%% info_connections/1
+%%
+%% One entry per transport connection. Statistics for each entry are
+%% for the peer pid only.
+
+info_connections(S) ->
+ ConnL = conn_list(S),
+ Stats = diameter_stats:read([P || L <- ConnL, {peer, {P,_}} <- L]),
+ [L ++ [stats([P], Stats)] || L <- ConnL, {peer, {P,_}} <- L].
+
+conn_list(S) ->
+ lists:append(dict:fold(fun conn_acc/3, [], peer_dict(S, dict:new()))).
+
+conn_acc(Ref, Peers, Acc) ->
+ [[[{ref, Ref} | L] || L <- Peers, lists:keymember(peer, 1, L)]
+ | Acc].
+
+stats(Refs, Stats) ->
+ {statistics, dict:to_list(lists:foldl(fun(R,D) ->
+ stats_acc(R, D, Stats)
+ end,
+ dict:new(),
+ Refs))}.
+
+stats_acc(Ref, Dict, Stats) ->
+ lists:foldl(fun({C,N}, D) -> dict:update_counter(C, N, D) end,
+ Dict,
+ proplists:get_value(Ref, Stats, [])).
+
+%% info_peers/1
+%%
+%% One entry per peer Origin-Host. Statistics for each entry are
+%% accumulated values for all peer pids.
+
+info_peers(S) ->
+ {PeerD, RefD} = lists:foldl(fun peer_acc/2,
+ {dict:new(), dict:new()},
+ conn_list(S)),
+ Refs = lists:append(dict:fold(fun(_, Rs, A) -> [Rs|A] end,
+ [],
+ RefD)),
+ Stats = diameter_stats:read(Refs),
+ dict:fold(fun(OH, Cs, A) ->
+ Rs = dict:fetch(OH, RefD),
+ [{OH, [{connections, Cs}, stats(Rs, Stats)]} | A]
+ end,
+ [],
+ PeerD).
+
+peer_acc(Peer, {PeerD, RefD}) ->
+ [{TPid, _}, [{origin_host, {_, OH}} | _]]
+ = [proplists:get_value(K, Peer) || K <- [peer, caps]],
+ {dict:append(OH, Peer, PeerD), dict:append(OH, TPid, RefD)}.
+
+%% info_options/1
+
+info_options(S) ->
+ S#state.options.
diff --git a/lib/diameter/src/app/diameter_service_sup.erl b/lib/diameter/src/base/diameter_service_sup.erl
index 153fff902f..153fff902f 100644
--- a/lib/diameter/src/app/diameter_service_sup.erl
+++ b/lib/diameter/src/base/diameter_service_sup.erl
diff --git a/lib/diameter/src/app/diameter_session.erl b/lib/diameter/src/base/diameter_session.erl
index bb91e97f39..3b236f109a 100644
--- a/lib/diameter/src/app/diameter_session.erl
+++ b/lib/diameter/src/base/diameter_session.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -20,19 +20,18 @@
-module(diameter_session).
-export([sequence/0,
+ sequence/1,
session_id/1,
origin_state_id/0]).
%% towards diameter_sup
-export([init/0]).
--include("diameter_types.hrl").
-
-define(INT64, 16#FFFFFFFFFFFFFFFF).
-define(INT32, 16#FFFFFFFF).
%% ---------------------------------------------------------------------------
-%% # sequence/0
+%% # sequence/0-1
%%
%% Output: 32-bit
%% ---------------------------------------------------------------------------
@@ -73,12 +72,21 @@
%% consumed (see Section 6.2) SHOULD be silently discarded.
-spec sequence()
- -> 'Unsigned32'().
+ -> diameter:'Unsigned32'().
sequence() ->
Instr = {_Pos = 2, _Incr = 1, _Threshold = ?INT32, _SetVal = 0},
ets:update_counter(diameter_sequence, sequence, Instr).
+-spec sequence(diameter:sequence())
+ -> diameter:'Unsigned32'().
+
+sequence({_,32}) ->
+ sequence();
+
+sequence({H,N}) ->
+ (H bsl N) bor (sequence() band (1 bsl N - 1)).
+
%% ---------------------------------------------------------------------------
%% # origin_state_id/0
%% ---------------------------------------------------------------------------
@@ -97,7 +105,7 @@ sequence() ->
%% counter retained in non-volatile memory across restarts.
-spec origin_state_id()
- -> 'Unsigned32'().
+ -> diameter:'Unsigned32'().
origin_state_id() ->
ets:lookup_element(diameter_sequence, origin_state_id, 2).
@@ -130,8 +138,8 @@ origin_state_id() ->
%% <optional value> is implementation specific but may include a modem's
%% device Id, a layer 2 address, timestamp, etc.
--spec session_id('DiameterIdentity'())
- -> 'OctetString'().
+-spec session_id(diameter:'DiameterIdentity'())
+ -> diameter:'OctetString'().
%% Note that Session-Id has type UTF8String and that any OctetString
%% is a UTF8String.
diff --git a/lib/diameter/src/base/diameter_stats.erl b/lib/diameter/src/base/diameter_stats.erl
new file mode 100644
index 0000000000..70727d068e
--- /dev/null
+++ b/lib/diameter/src/base/diameter_stats.erl
@@ -0,0 +1,287 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% Statistics collector.
+%%
+
+-module(diameter_stats).
+
+-behaviour(gen_server).
+
+-export([reg/2, reg/1,
+ incr/3, incr/1,
+ read/1,
+ flush/1]).
+
+%% supervisor callback
+-export([start_link/0]).
+
+%% gen_server callbacks
+-export([init/1,
+ terminate/2,
+ handle_call/3,
+ handle_cast/2,
+ handle_info/2,
+ code_change/3]).
+
+%% debug
+-export([state/0,
+ uptime/0]).
+
+-include("diameter_internal.hrl").
+
+%% ets table containing 2-tuple stats. reg(Pid, Ref) inserts a {Pid,
+%% Ref}, incr(Counter, X, N) updates the counter keyed at {Counter,
+%% X}, and Pid death causes counters keyed on {Counter, Pid} to be
+%% deleted and added to those keyed on {Counter, Ref}.
+-define(TABLE, ?MODULE).
+
+%% Name of registered server.
+-define(SERVER, ?MODULE).
+
+%% Server state.
+-record(state, {id = now()}).
+
+-type counter() :: any().
+-type ref() :: any().
+
+%% ---------------------------------------------------------------------------
+%% # reg(Pid, Ref)
+%%
+%% Register a process as a contributor of statistics associated with a
+%% specified term. Statistics can be contributed by specifying either
+%% Pid or Ref as the second argument to incr/3. Statistics contributed
+%% by Pid are folded into the corresponding entry for Ref when the
+%% process dies.
+%% ---------------------------------------------------------------------------
+
+-spec reg(pid(), ref())
+ -> boolean().
+
+reg(Pid, Ref)
+ when is_pid(Pid) ->
+ call({reg, Pid, Ref}).
+
+-spec reg(ref())
+ -> true.
+
+reg(Ref) ->
+ reg(self(), Ref).
+
+%% ---------------------------------------------------------------------------
+%% # incr(Counter, Ref, N)
+%%
+%% Increment a counter for the specified contributor.
+%%
+%% Ref will typically be an argument passed to reg/2 but there's
+%% nothing that requires this. Only registered pids can contribute
+%% counters however, otherwise incr/3 is a no-op.
+%% ---------------------------------------------------------------------------
+
+-spec incr(counter(), ref(), integer())
+ -> integer() | false.
+
+incr(Ctr, Ref, N)
+ when is_integer(N) ->
+ update_counter({Ctr, Ref}, N).
+
+incr(Ctr) ->
+ incr(Ctr, self(), 1).
+
+%% ---------------------------------------------------------------------------
+%% # read(Refs)
+%%
+%% Retrieve counters for the specified contributors.
+%% ---------------------------------------------------------------------------
+
+-spec read([ref()])
+ -> [{ref(), [{counter(), integer()}]}].
+
+read(Refs) ->
+ read(Refs, false).
+
+read(Refs, B) ->
+ MatchSpec = [{{{'_', '$1'}, '_'},
+ [?ORCOND([{'=:=', '$1', {const, R}}
+ || R <- Refs])],
+ ['$_']}],
+ L = ets:select(?TABLE, MatchSpec),
+ B andalso delete(L),
+ lists:foldl(fun({{C,R}, N}, D) -> orddict:append(R, {C,N}, D) end,
+ orddict:new(),
+ L).
+
+%% ---------------------------------------------------------------------------
+%% # flush(Refs)
+%%
+%% Retrieve and delete statistics for the specified contributors.
+%% ---------------------------------------------------------------------------
+
+-spec flush([ref()])
+ -> [{ref(), {counter(), integer()}}].
+
+flush(Refs) ->
+ try
+ call({flush, Refs})
+ catch
+ exit: _ ->
+ []
+ end.
+
+%% ===========================================================================
+
+start_link() ->
+ ServerName = {local, ?SERVER},
+ Module = ?MODULE,
+ Args = [],
+ Options = [{spawn_opt, diameter_lib:spawn_opts(server, [])}],
+ gen_server:start_link(ServerName, Module, Args, Options).
+
+state() ->
+ call(state).
+
+uptime() ->
+ call(uptime).
+
+%% ----------------------------------------------------------
+%% # init/1
+%% ----------------------------------------------------------
+
+init([]) ->
+ ets:new(?TABLE, [named_table, ordered_set, public]),
+ {ok, #state{}}.
+
+%% ----------------------------------------------------------
+%% # handle_call/3
+%% ----------------------------------------------------------
+
+handle_call(state, _, State) ->
+ {reply, State, State};
+
+handle_call(uptime, _, #state{id = Time} = State) ->
+ {reply, diameter_lib:now_diff(Time), State};
+
+handle_call({incr, T}, _, State) ->
+ {reply, update_counter(T), State};
+
+handle_call({reg, Pid, Ref}, _From, State) ->
+ B = ets:insert_new(?TABLE, {Pid, Ref}),
+ B andalso erlang:monitor(process, Pid),
+ {reply, B, State};
+
+handle_call({flush, Refs}, _From, State) ->
+ {reply, read(Refs, true), State};
+
+handle_call(Req, From, State) ->
+ ?UNEXPECTED([Req, From]),
+ {reply, nok, State}.
+
+%% ----------------------------------------------------------
+%% # handle_cast/2
+%% ----------------------------------------------------------
+
+handle_cast(Msg, State) ->
+ ?UNEXPECTED([Msg]),
+ {noreply, State}.
+
+%% ----------------------------------------------------------
+%% # handle_info/2
+%% ----------------------------------------------------------
+
+handle_info({'DOWN', _MRef, process, Pid, _}, State) ->
+ down(Pid),
+ {noreply, State};
+
+handle_info(Info, State) ->
+ ?UNEXPECTED([Info]),
+ {noreply, State}.
+
+%% ----------------------------------------------------------
+%% # terminate/2
+%% ----------------------------------------------------------
+
+terminate(_Reason, _State) ->
+ ok.
+
+%% ----------------------------------------------------------
+%% # code_change/3
+%% ----------------------------------------------------------
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% ===========================================================================
+
+%% down/1
+
+down(Pid) ->
+ down(lookup(Pid), ets:match_object(?TABLE, {{'_', Pid}, '_'})).
+
+down([{_, Ref} = T], L) ->
+ fold(Ref, L),
+ delete([T|L]);
+down([], L) -> %% flushed
+ delete(L).
+
+%% Fold pid-based entries into ref-based ones.
+fold(Ref, L) ->
+ lists:foreach(fun({{K, _}, V}) -> update_counter({{K, Ref}, V}) end, L).
+
+%% update_counter/2
+%%
+%% From an arbitrary process. Call to the server process to insert a
+%% new element if the counter doesn't exists so that two processes
+%% don't insert simultaneously.
+
+update_counter(Key, N) ->
+ try
+ ets:update_counter(?TABLE, Key, N)
+ catch
+ error: badarg ->
+ call({incr, {Key, N}})
+ end.
+
+%% update_counter/1
+%%
+%% From the server process, when update_counter/2 failed due to a
+%% non-existent entry.
+
+update_counter({{_Ctr, Ref} = Key, N} = T) ->
+ try
+ ets:update_counter(?TABLE, Key, N)
+ catch
+ error: badarg ->
+ (not is_pid(Ref) orelse ets:member(?TABLE, Ref))
+ andalso begin insert(T), N end
+ end.
+
+insert(T) ->
+ ets:insert(?TABLE, T).
+
+lookup(Key) ->
+ ets:lookup(?TABLE, Key).
+
+delete(Objs) ->
+ lists:foreach(fun({K,_}) -> ets:delete(?TABLE, K) end, Objs).
+
+%% call/1
+
+call(Request) ->
+ gen_server:call(?SERVER, Request, infinity).
diff --git a/lib/diameter/src/app/diameter_sup.erl b/lib/diameter/src/base/diameter_sup.erl
index e5afd23dcd..e5afd23dcd 100644
--- a/lib/diameter/src/app/diameter_sup.erl
+++ b/lib/diameter/src/base/diameter_sup.erl
diff --git a/lib/diameter/src/app/diameter_sync.erl b/lib/diameter/src/base/diameter_sync.erl
index ce2db4b3a2..ce2db4b3a2 100644
--- a/lib/diameter/src/app/diameter_sync.erl
+++ b/lib/diameter/src/base/diameter_sync.erl
diff --git a/lib/diameter/src/app/diameter_types.erl b/lib/diameter/src/base/diameter_types.erl
index 6b1b1b8d39..9ae289034c 100644
--- a/lib/diameter/src/app/diameter_types.erl
+++ b/lib/diameter/src/base/diameter_types.erl
@@ -42,8 +42,23 @@
'IPFilterRule'/2,
'QoSFilterRule'/2]).
+%% Functions taking the AVP name in question as second parameter.
+-export(['OctetString'/3,
+ 'Integer32'/3,
+ 'Integer64'/3,
+ 'Unsigned32'/3,
+ 'Unsigned64'/3,
+ 'Float32'/3,
+ 'Float64'/3,
+ 'Address'/3,
+ 'Time'/3,
+ 'UTF8String'/3,
+ 'DiameterIdentity'/3,
+ 'DiameterURI'/3,
+ 'IPFilterRule'/3,
+ 'QoSFilterRule'/3]).
+
-include_lib("diameter/include/diameter.hrl").
--include("diameter_internal.hrl").
-define(UINT(N,X), ((0 =< X) andalso (X < 1 bsl N))).
-define(SINT(N,X), ((-1*(1 bsl (N-1)) < X) andalso (X < 1 bsl (N-1)))).
@@ -433,6 +448,50 @@ uenc([C | Rest], Acc) ->
'Time'(encode, zero) ->
<<0:32>>.
+%% -------------------------------------------------------------------------
+
+'OctetString'(M, _, Data) ->
+ 'OctetString'(M, Data).
+
+'Integer32'(M, _, Data) ->
+ 'Integer32'(M, Data).
+
+'Integer64'(M, _, Data) ->
+ 'Integer64'(M, Data).
+
+'Unsigned32'(M, _, Data) ->
+ 'Unsigned32'(M, Data).
+
+'Unsigned64'(M, _, Data) ->
+ 'Unsigned64'(M, Data).
+
+'Float32'(M, _, Data) ->
+ 'Float32'(M, Data).
+
+'Float64'(M, _, Data) ->
+ 'Float64'(M, Data).
+
+'Address'(M, _, Data) ->
+ 'Address'(M, Data).
+
+'Time'(M, _, Data) ->
+ 'Time'(M, Data).
+
+'UTF8String'(M, _, Data) ->
+ 'UTF8String'(M, Data).
+
+'DiameterIdentity'(M, _, Data) ->
+ 'DiameterIdentity'(M, Data).
+
+'DiameterURI'(M, _, Data) ->
+ 'DiameterURI'(M, Data).
+
+'IPFilterRule'(M, _, Data) ->
+ 'IPFilterRule'(M, Data).
+
+'QoSFilterRule'(M, _, Data) ->
+ 'QoSFilterRule'(M, Data).
+
%% ===========================================================================
%% ===========================================================================
diff --git a/lib/diameter/src/app/diameter_watchdog.erl b/lib/diameter/src/base/diameter_watchdog.erl
index b7c1491f4b..243ad0a986 100644
--- a/lib/diameter/src/app/diameter_watchdog.erl
+++ b/lib/diameter/src/base/diameter_watchdog.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -43,26 +43,46 @@
-include("diameter_internal.hrl").
-define(DEFAULT_TW_INIT, 30000). %% RFC 3539 ch 3.4.1
+-define(NOMASK, {0,32}). %% default sequence mask
-record(watchdog,
{%% PCB - Peer Control Block; see RFC 3539, Appendix A
status = initial :: initial | okay | suspect | down | reopen,
- pending = false :: boolean(),
+ pending = false :: boolean(), %% DWA
tw :: 6000..16#FFFFFFFF | {module(), atom(), list()},
%% {M,F,A} -> integer() >= 0
num_dwa = 0 :: -1 | non_neg_integer(),
%% number of DWAs received during reopen
%% end PCB
- parent = self() :: pid(),
- transport :: pid(),
+ parent = self() :: pid(), %% service process
+ transport :: pid() | undefined, %% peer_fsm process
tref :: reference(), %% reference for current watchdog timer
- message_data}). %% term passed into diameter_service with message
+ message_data, %% term passed into diameter_service with message
+ sequence :: diameter:sequence(), %% mask
+ restrict :: {diameter:restriction(), boolean()},
+ shutdown = false :: boolean()}).
%% start/2
+%%
+%% Start a monitor before the watchdog is allowed to proceed to ensure
+%% that a failed capabilities exchange produces the desired exit
+%% reason.
+
+-spec start(Type, {RecvData, [Opt], SvcName, #diameter_service{}})
+ -> {reference(), pid()}
+ when Type :: {connect|accept, diameter:transport_ref()},
+ RecvData :: term(),
+ Opt :: diameter:transport_opt(),
+ SvcName :: diameter:service_name().
start({_,_} = Type, T) ->
- {ok, Pid} = diameter_watchdog_sup:start_child({Type, self(), T}),
- Pid.
+ Ref = make_ref(),
+ {ok, Pid} = diameter_watchdog_sup:start_child({Ref, {Type, self(), T}}),
+ try
+ {erlang:monitor(process, Pid), Pid}
+ after
+ Pid ! Ref
+ end.
start_link(T) ->
{ok, _} = proc_lib:start_link(?MODULE,
@@ -80,19 +100,45 @@ init(T) ->
proc_lib:init_ack({ok, self()}),
gen_server:enter_loop(?MODULE, [], i(T)).
-i({T, Pid, {ConnT, Opts, SvcName, #diameter_service{applications = Apps,
- capabilities = Caps}
- = Svc}}) ->
- {M,S,U} = now(),
- random:seed(M,S,U),
+i({Ref, {_, Pid, _} = T}) ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ Ref ->
+ make_state(T);
+ {'DOWN', MRef, process, _, _} = D ->
+ exit({shutdown, D})
+ end;
+
+i({_, Pid, _} = T) -> %% from old code
+ erlang:monitor(process, Pid),
+ make_state(T).
+
+make_state({T, Pid, {RecvData,
+ Opts,
+ SvcName,
+ #diameter_service{applications = Apps,
+ capabilities = Caps}
+ = Svc}}) ->
+ random:seed(now()),
putr(restart, {T, Opts, Svc}), %% save seeing it in trace
putr(dwr, dwr(Caps)), %%
- #watchdog{parent = monitor(Pid),
- transport = monitor(diameter_peer_fsm:start(T, Opts, Svc)),
+ {_,_} = Mask = call(Pid, sequence),
+ Restrict = call(Pid, restriction),
+ Nodes = restrict_nodes(Restrict),
+ #watchdog{parent = Pid,
+ transport = monitor(diameter_peer_fsm:start(T,
+ Opts,
+ {Mask, Nodes, Svc})),
tw = proplists:get_value(watchdog_timer,
Opts,
?DEFAULT_TW_INIT),
- message_data = {ConnT, SvcName, Apps}}.
+ message_data = {RecvData, SvcName, Apps, Mask},
+ sequence = Mask,
+ restrict = {Restrict, lists:member(node(), Nodes)}}.
+
+%% Retrieve the sequence mask from the parent from the parent, rather
+%% than having it passed into init/1, for upgrade reasons: the call to
+%% diameter_service:receive_message/3 passes back the mask.
%% handle_call/3
@@ -106,17 +152,46 @@ handle_cast(_, State) ->
%% handle_info/2
-handle_info(T, State) ->
+handle_info(T, #watchdog{} = State) ->
case transition(T, State) of
ok ->
{noreply, State};
- #watchdog{status = X} = S ->
- ?LOGC(X =/= State#watchdog.status, transition, X),
+ #watchdog{} = S ->
+ event(State, S),
{noreply, S};
stop ->
?LOG(stop, T),
+ event(State, State#watchdog{status = down}),
{stop, {shutdown, T}, State}
- end.
+ end;
+
+handle_info(T, S) ->
+ handle_info(T, upgrade(S)).
+
+upgrade(S) ->
+ #watchdog{} = list_to_tuple(tuple_to_list(S)
+ ++ [?NOMASK, {nodes, true}, false]).
+
+event(#watchdog{status = T}, #watchdog{status = T}) ->
+ ok;
+
+event(#watchdog{transport = undefined}, #watchdog{transport = undefined}) ->
+ ok;
+
+event(#watchdog{status = From, transport = F, parent = Pid},
+ #watchdog{status = To, transport = T}) ->
+ E = {tpid(F,T), From, To},
+ notify(Pid, E),
+ ?LOG(transition, {self(), E}).
+
+tpid(_, Pid)
+ when is_pid(Pid) ->
+ Pid;
+tpid(Pid, _) ->
+ Pid.
+
+notify(Pid, E) ->
+ Pid ! {watchdog, self(), E}.
%% terminate/2
@@ -152,9 +227,10 @@ transition({shutdown, Pid}, #watchdog{parent = Pid,
down = S, %% sanity check
stop;
transition({shutdown = T, Pid}, #watchdog{parent = Pid,
- transport = TPid}) ->
+ transport = TPid}
+ = S) ->
TPid ! {T, self()},
- ok;
+ S#watchdog{shutdown = true};
%% Parent process has died,
transition({'DOWN', _, process, Pid, _Reason},
@@ -179,18 +255,19 @@ transition({close, TPid, _Reason}, #watchdog{transport = TPid}) ->
%% state okay as the result of the Peer State Machine reaching the
%% Open state.
%%
-%% If we're an acceptor then we may be resuming a connection that went
-%% down in another acceptor process, in which case this is the
-%% transition below, from down into reopen. That is, it's not until
-%% we know the identity of the peer (ie. now) that we know that we're
-%% in state down rather than initial.
+%% If we're accepting then we may be resuming a connection that went
+%% down in another watchdog process, in which case this is the
+%% transition below, from down to reopen. That is, it's not until we
+%% know the identity of the peer (ie. now) that we know that we're in
+%% state down rather than initial.
transition({open, TPid, Hosts, T} = Open,
#watchdog{transport = TPid,
status = initial,
- parent = Pid}
+ parent = Pid,
+ restrict = {_, R}}
= S) ->
- case okay(getr(restart), Hosts) of
+ case okay(getr(restart), Hosts, R) of
okay ->
open(Pid, {TPid, T}),
set_watchdog(S#watchdog{status = okay});
@@ -205,12 +282,15 @@ transition({open, TPid, Hosts, T} = Open,
transition({open = P, TPid, _Hosts, T},
#watchdog{transport = TPid,
+ parent = Pid,
status = down}
= S) ->
%% Store the info we need to notify the parent to reopen the
%% connection after the requisite DWA's are received, at which
- %% time we eraser(open).
+ %% time we eraser(open). The reopen message is a later addition,
+ %% to communicate the new capabilities as soon as they're known.
putr(P, {TPid, T}),
+ Pid ! {reopen, self(), {TPid, T}},
set_watchdog(send_watchdog(S#watchdog{status = reopen,
num_dwa = 0}));
@@ -224,11 +304,14 @@ transition({open = P, TPid, _Hosts, T},
transition({'DOWN', _, process, TPid, _},
#watchdog{transport = TPid,
- status = initial}) ->
+ status = S,
+ shutdown = D})
+ when S == initial;
+ D ->
stop;
-transition({'DOWN', _, process, Pid, _},
- #watchdog{transport = Pid}
+transition({'DOWN', _, process, TPid, _},
+ #watchdog{transport = TPid}
= S) ->
failover(S),
close(S),
@@ -259,6 +342,15 @@ transition({state, Pid}, #watchdog{status = S}) ->
%% ===========================================================================
+%% Only call "upwards", to the parent service.
+call(Pid, Req) ->
+ try
+ gen_server:call(Pid, Req, infinity)
+ catch
+ exit: Reason ->
+ exit({shutdown, {Req, Reason}})
+ end.
+
monitor(Pid) ->
erlang:monitor(process, Pid),
Pid.
@@ -272,26 +364,36 @@ getr(Key) ->
eraser(Key) ->
erase({?MODULE, Key}).
-%% encode/1
+%% encode/2
-encode(Msg) ->
- #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Msg),
+encode(Msg, Mask) ->
+ Seq = diameter_session:sequence(Mask),
+ Hdr = #diameter_header{version = ?DIAMETER_VERSION,
+ end_to_end_id = Seq,
+ hop_by_hop_id = Seq},
+ Pkt = #diameter_packet{header = Hdr,
+ msg = Msg},
+ #diameter_packet{bin = Bin} = diameter_codec:encode(?BASE, Pkt),
Bin.
-%% okay/2
+%% okay/3
-okay({{accept, Ref}, _, _}, Hosts) ->
+okay({{accept, Ref}, _, _}, Hosts, Restrict) ->
T = {?MODULE, connection, Ref, Hosts},
diameter_reg:add(T),
- okay(diameter_reg:match(T));
+ if Restrict ->
+ okay(diameter_reg:match(T));
+ true ->
+ okay
+ end;
%% Register before matching so that at least one of two registering
-%% processes will match the other. (Which can't happen as long as
-%% diameter_peer_fsm guarantees at most one open connection to the same
-%% peer.)
+%% processes will match the other.
-okay({{connect, _}, _, _}, _) ->
+okay({{connect, _}, _, _}, _, _) ->
okay.
+%% okay/2
+
%% The peer hasn't been connected recently ...
okay([{_,P}]) ->
P = self(), %% assert
@@ -347,9 +449,10 @@ close(#watchdog{parent = Pid}) ->
%% send_watchdog/1
send_watchdog(#watchdog{pending = false,
- transport = TPid}
+ transport = TPid,
+ sequence = Mask}
= S) ->
- TPid ! {send, encode(getr(dwr))},
+ TPid ! {send, encode(getr(dwr), Mask)},
?LOG(send, 'DWR'),
S#watchdog{pending = true}.
@@ -361,7 +464,7 @@ recv(Name, Pkt, S) ->
rcv(Name, Pkt, S),
NS
catch
- throw: {?MODULE, throwaway, #watchdog{} = NS} ->
+ {?MODULE, throwaway, #watchdog{} = NS} ->
NS
end.
@@ -384,6 +487,14 @@ throwaway(S) ->
throw({?MODULE, throwaway, S}).
%% rcv/2
+%%
+%% The lack of Hop-by-Hop and End-to-End Identifiers checks in a
+%% received DWA is intentional. The purpose of the message is to
+%% demonstrate life but a peer that consistently bungles it by sending
+%% the wrong identifiers causes the connection to toggle between OPEN
+%% and SUSPECT, with failover and failback as result, despite there
+%% being no real problem with connectivity. Thus, relax and accept any
+%% incoming DWA as being in response to an outgoing DWR.
%% INITIAL Receive DWA Pending = FALSE
%% Throwaway() INITIAL
@@ -502,7 +613,7 @@ timeout(#watchdog{status = T,
= S)
when T == suspect;
T == reopen, P, N < 0 ->
- exit(TPid, shutdown),
+ exit(TPid, {shutdown, watchdog_timeout}),
close(S),
S#watchdog{status = down};
@@ -547,19 +658,40 @@ restart(#watchdog{transport = undefined} = S) ->
restart(S) ->
S.
+%% restart/2
+%%
%% Only restart the transport in the connecting case. For an accepting
-%% transport, we've registered the peer connection when leaving state
-%% initial and this is used by a new accepting process to realize that
-%% it's actually in state down rather then initial when receiving
-%% notification of an open connection.
-
-restart({{connect, _} = T, Opts, Svc}, #watchdog{parent = Pid} = S) ->
+%% transport, there's no guarantee that an accepted connection in a
+%% restarted transport if from the peer we've lost contact with so
+%% have to be prepared for another watchdog to handle it. This is what
+%% the diameter_reg registration in this module is for: the peer
+%% connection is registered when leaving state initial and this is
+%% used by a new accepting watchdog to realize that it's actually in
+%% state down rather then initial when receiving notification of an
+%% open connection.
+
+restart({{connect, _} = T, Opts, Svc}, #watchdog{parent = Pid,
+ sequence = Mask,
+ restrict = {R,_}}
+ = S) ->
Pid ! {reconnect, self()},
- S#watchdog{transport = monitor(diameter_peer_fsm:start(T, Opts, Svc))};
+ Nodes = restrict_nodes(R),
+ S#watchdog{transport = monitor(diameter_peer_fsm:start(T,
+ Opts,
+ {Mask, Nodes, Svc})),
+ restrict = {R, lists:member(node(), Nodes)}};
+
+%% No restriction on the number of connections to the same peer: just
+%% die. Note that a state machine never enters state REOPEN in this
+%% case.
+restart({{accept, _}, _, _}, #watchdog{restrict = {_, false}}) ->
+ stop;
+
+%% Otherwise hang around until told to die.
restart({{accept, _}, _, _}, S) ->
S.
-%% Don't currently use Opts/Svc in the accept case but having them in
-%% the process dictionary is helpful if the process dies unexpectedly.
+
+%% Don't currently use Opts/Svc in the accept case.
%% dwr/1
@@ -569,3 +701,22 @@ dwr(#diameter_caps{origin_host = OH,
['DWR', {'Origin-Host', OH},
{'Origin-Realm', OR},
{'Origin-State-Id', OSI}].
+
+%% restrict_nodes/1
+
+restrict_nodes(false) ->
+ [];
+
+restrict_nodes(nodes) ->
+ [node() | nodes()];
+
+restrict_nodes(node) ->
+ [node()];
+
+restrict_nodes(Nodes)
+ when [] == Nodes;
+ is_atom(hd(Nodes)) ->
+ Nodes;
+
+restrict_nodes(F) ->
+ diameter_lib:eval(F).
diff --git a/lib/diameter/src/app/diameter_watchdog_sup.erl b/lib/diameter/src/base/diameter_watchdog_sup.erl
index fc837fe4ef..fc837fe4ef 100644
--- a/lib/diameter/src/app/diameter_watchdog_sup.erl
+++ b/lib/diameter/src/base/diameter_watchdog_sup.erl
diff --git a/lib/diameter/src/compiler/Makefile b/lib/diameter/src/compiler/Makefile
deleted file mode 100644
index 779013bfbc..0000000000
--- a/lib/diameter/src/compiler/Makefile
+++ /dev/null
@@ -1,131 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-#
-#
-
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-else
-include $(DIAMETER_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(DIAMETER_TOP)/make/$(TARGET)/rules.mk
-endif
-
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../../vsn.mk
-VSN=$(DIAMETER_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-
-RELSYSDIR = $(RELEASE_PATH)/lib/diameter-$(VSN)
-
-INCDIR = ../../include
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
-include modules.mk
-
-ERL_FILES = \
- $(MODULES:%=%.erl)
-
-TARGET_FILES = \
- $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-
-ifeq ($(TYPE),debug)
-ERL_COMPILE_FLAGS += -Ddebug
-endif
-
-include ../app/diameter.mk
-
-ERL_COMPILE_FLAGS += \
- $(DIAMETER_ERL_COMPILE_FLAGS) \
- -I$(INCDIR)
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug:
- @${MAKE} TYPE=debug opt
-
-opt: $(TARGET_FILES)
-
-clean:
- rm -f $(TARGET_FILES)
- rm -f errs core *~
- rm -f depend.mk
-
-docs:
-
-info:
- @echo ""
- @echo "ERL_FILES = $(ERL_FILES)"
- @echo "HRL_FILES = $(HRL_FILES)"
- @echo ""
- @echo "TARGET_FILES = $(TARGET_FILES)"
- @echo ""
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/otp_release_targets.mk
-else
-include $(DIAMETER_TOP)/make/release_targets.mk
-endif
-
-release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
- $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/src/compiler
- $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/compiler
-
-release_docs_spec:
-
-force:
-
-# ----------------------------------------------------
-# Dependencies
-# ----------------------------------------------------
-
-depend: depend.mk
-
-# Generate dependencies makefile.
-depend.mk: ../app/depend.sed $(ERL_FILES) Makefile
- for f in $(MODULES); do \
- sed -f $< $$f.erl | sed "s@/@/$$f@"; \
- done \
- > $@
-
--include depend.mk
-
-.PHONY: clean debug depend docs force info opt release_docs_spec release_spec
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl
index a33b07a3d3..1e31c40afe 100644
--- a/lib/diameter/src/compiler/diameter_codegen.erl
+++ b/lib/diameter/src/compiler/diameter_codegen.erl
@@ -20,17 +20,18 @@
-module(diameter_codegen).
%%
-%% This module generates .erl and .hrl files for encode/decode
-%% modules from the orddict parsed from a .dia (aka spec) file by
-%% dis_spec_util. The generated code is very simple (one-liners), the
-%% generated functions being called by code included from dis_gen.hrl
-%% in order to encode/decode messages and AVPs. The orddict itself is
-%% returned by dict/0 in the generated module and dis_spec_util calls
-%% this function when importing spec files. (That is, beam has to be
-%% compiled from an imported spec file before it can be imported.)
+%% This module generates erl/hrl files for encode/decode modules
+%% from the orddict parsed from a dictionary file (.dia) by
+%% diameter_dict_util. The generated code is simple (one-liners),
+%% the generated functions being called by code included iin the
+%% generated modules from diameter_gen.hrl. The orddict itself is
+%% returned by dict/0 in the generated module and diameter_dict_util
+%% calls this function when importing dictionaries as a consequence
+%% of @inherits sections. That is, @inherits introduces a dependency
+%% on the beam file of another dictionary.
%%
--export([from_spec/4]).
+-export([from_dict/4]).
%% Internal exports (for test).
-export([file/1,
@@ -38,17 +39,23 @@
file/3]).
-include("diameter_forms.hrl").
+-include("diameter_vsn.hrl").
-%% Generated functions that could have no generated clauses will have
-%% a trailing ?UNEXPECTED clause that should never execute.
--define(UNEXPECTED(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)],
- [],
- [?APPLY(erlang,
- error,
- [?TERM({unexpected, getr(module)})])]}).
+-define(S, atom_to_list).
+-define(A, list_to_atom).
+-define(Atom(T), ?ATOM(?A(T))).
-from_spec(File, Spec, Opts, Mode) ->
+%% ===========================================================================
+
+-spec from_dict(File, Spec, Opts, Mode)
+ -> ok
+ when File :: string(),
+ Spec :: orddict:orddict(),
+ Opts :: list(),
+ Mode :: spec | erl | hrl.
+
+from_dict(File, Spec, Opts, Mode) ->
Outdir = proplists:get_value(outdir, Opts, "."),
putr(verbose, lists:member(verbose, Opts)),
putr(debug, lists:member(debug, Opts)),
@@ -73,7 +80,7 @@ getr(Key) ->
%% ===========================================================================
%% ===========================================================================
-%% Generate from parsed spec in a file.
+%% Generate from parsed dictionary in a file.
file(F) ->
file(F, spec).
@@ -83,55 +90,46 @@ file(F, Mode) ->
file(F, Outdir, Mode) ->
{ok, [Spec]} = file:consult(F),
- from_spec(F, Spec, Outdir, Mode).
+ from_dict(F, Spec, Outdir, Mode).
%% ===========================================================================
%% ===========================================================================
-choose(true, X, _) -> X;
-choose(false, _, X) -> X.
-
get_value(Key, Plist) ->
proplists:get_value(Key, Plist, []).
-write(Path, [C|_] = Spec)
- when is_integer(C) ->
- w(Path, Spec, "~s");
-write(Path, Spec) ->
- w(Path, Spec, "~p.").
+write(Path, Str) ->
+ w(Path, Str, "~s").
-w(Path, Spec, Fmt) ->
+write_term(Path, T) ->
+ w(Path, T, "~p.").
+
+w(Path, T, Fmt) ->
{ok, Fd} = file:open(Path, [write]),
- io:fwrite(Fd, Fmt ++ "~n", [Spec]),
+ io:fwrite(Fd, Fmt ++ "~n", [T]),
file:close(Fd).
codegen(File, Spec, Outdir, Mode) ->
Mod = mod(File, orddict:find(name, Spec)),
Path = filename:join(Outdir, Mod), %% minus extension
- gen(Mode, Spec, Mod, Path),
+ gen(Mode, Spec, ?A(Mod), Path),
ok.
mod(File, error) ->
filename:rootname(filename:basename(File));
mod(_, {ok, Mod}) ->
- atom_to_list(Mod).
+ Mod.
gen(spec, Spec, _Mod, Path) ->
- write(Path ++ ".spec", Spec);
+ write_term(Path ++ ".spec", [?VERSION | Spec]);
gen(hrl, Spec, Mod, Path) ->
gen_hrl(Path ++ ".hrl", Mod, Spec);
-gen(erl = Mode, Spec, Mod, Path)
- when is_list(Mod) ->
- gen(Mode, Spec, list_to_atom(Mod), Path);
-
gen(erl, Spec, Mod, Path) ->
- putr(module, Mod), %% used by ?UNEXPECTED.
-
Forms = [{?attribute, module, Mod},
{?attribute, compile, [{parse_transform, diameter_exprecs}]},
- {?attribute, compile, [nowarn_unused_function]},
+ {?attribute, compile, [{parse_transform, diameter_nowarn}]},
{?attribute, export, [{name, 0},
{id, 0},
{vendor_id, 0},
@@ -175,7 +173,7 @@ gen(erl, Spec, Mod, Path) ->
gen_erl(Path, insert_hrl_forms(Spec, Forms)).
gen_erl(Path, Forms) ->
- getr(debug) andalso write(Path ++ ".forms", Forms),
+ getr(debug) andalso write_term(Path ++ ".forms", Forms),
write(Path ++ ".erl",
header() ++ erl_prettypr:format(erl_syntax:form_list(Forms))).
@@ -224,16 +222,16 @@ a_record(Prefix, ProjF, L) ->
lists:map(fun(T) -> a_record(ProjF(T), Prefix) end, L).
a_record({Nm, Avps}, Prefix) ->
- Name = list_to_atom(Prefix ++ atom_to_list(Nm)),
+ Name = list_to_atom(Prefix ++ Nm),
Fields = lists:map(fun field/1, Avps),
{?attribute, record, {Name, Fields}}.
field(Avp) ->
{Name, Arity} = avp_info(Avp),
if 1 == Arity ->
- {?record_field, ?ATOM(Name)};
+ {?record_field, ?Atom(Name)};
true ->
- {?record_field, ?ATOM(Name), ?NIL}
+ {?record_field, ?Atom(Name), ?NIL}
end.
%%% ------------------------------------------------------------------------
@@ -256,25 +254,33 @@ c_id({ok, Id}) ->
{?clause, [], [], [?INTEGER(Id)]};
c_id(error) ->
- ?UNEXPECTED(0).
+ ?BADARG(0).
%%% ------------------------------------------------------------------------
%%% # vendor_id/0
%%% ------------------------------------------------------------------------
f_vendor_id(Spec) ->
- {Id, _} = orddict:fetch(vendor, Spec),
{?function, vendor_id, 0,
- [{?clause, [], [], [?INTEGER(Id)]}]}.
+ [{?clause, [], [], [b_vendor_id(orddict:find(vendor, Spec))]}]}.
+
+b_vendor_id({ok, {Id, _}}) ->
+ ?INTEGER(Id);
+b_vendor_id(error) ->
+ ?APPLY(erlang, error, [?TERM(undefined)]).
%%% ------------------------------------------------------------------------
%%% # vendor_name/0
%%% ------------------------------------------------------------------------
f_vendor_name(Spec) ->
- {_, Name} = orddict:fetch(vendor, Spec),
{?function, vendor_name, 0,
- [{?clause, [], [], [?ATOM(Name)]}]}.
+ [{?clause, [], [], [b_vendor_name(orddict:find(vendor, Spec))]}]}.
+
+b_vendor_name({ok, {_, Name}}) ->
+ ?Atom(Name);
+b_vendor_name(error) ->
+ ?APPLY(erlang, error, [?TERM(undefined)]).
%%% ------------------------------------------------------------------------
%%% # msg_name/1
@@ -287,22 +293,18 @@ f_msg_name(Spec) ->
%% DIAMETER_COMMAND_UNSUPPORTED should be replied.
msg_name(Spec) ->
- lists:flatmap(fun c_msg_name/1,
- proplists:get_value(command_codes, Spec, []))
+ lists:flatmap(fun c_msg_name/1, proplists:get_value(command_codes,
+ Spec,
+ []))
++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('')]}].
c_msg_name({Code, Req, Ans}) ->
[{?clause, [?INTEGER(Code), ?ATOM(true)],
[],
- [?ATOM(mname(Req))]},
+ [?Atom(Req)]},
{?clause, [?INTEGER(Code), ?ATOM(false)],
[],
- [?ATOM(mname(Ans))]}].
-
-mname({N, _Abbr}) ->
- N;
-mname(N) ->
- N.
+ [?Atom(Ans)]}].
%%% ------------------------------------------------------------------------
%%% # msg2rec/1
@@ -313,30 +315,11 @@ f_msg2rec(Spec) ->
msg2rec(Spec) ->
Pre = prefix(Spec),
- Dict = dict:from_list(lists:flatmap(fun msgs/1,
- get_value(command_codes, Spec))),
- lists:flatmap(fun(T) -> msg2rec(T, Dict, Pre) end,
- get_value(messages, Spec))
- ++ [?UNEXPECTED(1)].
-
-msgs({_Code, Req, Ans}) ->
- [{mname(Req), Req}, {mname(Ans), Ans}].
-
-msg2rec({N,_,_,_,_}, Dict, Pre) ->
- c_msg2rec(fetch_names(N, Dict), Pre).
-
-fetch_names(Name, Dict) ->
- case dict:find(Name, Dict) of
- {ok, N} ->
- N;
- error ->
- Name
- end.
+ lists:map(fun(T) -> c_msg2rec(T, Pre) end, get_value(messages, Spec))
+ ++ [?BADARG(1)].
-c_msg2rec({N,A}, Pre) ->
- [c_name2rec(N, N, Pre), c_name2rec(A, N, Pre)];
-c_msg2rec(N, Pre) ->
- [c_name2rec(N, N, Pre)].
+c_msg2rec({N,_,_,_,_}, Pre) ->
+ c_name2rec(N, Pre).
%%% ------------------------------------------------------------------------
%%% # rec2msg/1
@@ -348,10 +331,10 @@ f_rec2msg(Spec) ->
rec2msg(Spec) ->
Pre = prefix(Spec),
lists:map(fun(T) -> c_rec2msg(T, Pre) end, get_value(messages, Spec))
- ++ [?UNEXPECTED(1)].
+ ++ [?BADARG(1)].
c_rec2msg({N,_,_,_,_}, Pre) ->
- {?clause, [?ATOM(rec_name(N, Pre))], [], [?ATOM(N)]}.
+ {?clause, [?Atom(rec_name(N, Pre))], [], [?Atom(N)]}.
%%% ------------------------------------------------------------------------
%%% # name2rec/1
@@ -364,11 +347,11 @@ name2rec(Spec) ->
Pre = prefix(Spec),
Groups = get_value(grouped, Spec)
++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)),
- lists:map(fun({N,_,_,_}) -> c_name2rec(N, N, Pre) end, Groups)
+ lists:map(fun({N,_,_,_}) -> c_name2rec(N, Pre) end, Groups)
++ [{?clause, [?VAR('T')], [], [?CALL(msg2rec, [?VAR('T')])]}].
-c_name2rec(Name, Rname, Pre) ->
- {?clause, [?ATOM(Name)], [], [?ATOM(rec_name(Rname, Pre))]}.
+c_name2rec(Name, Pre) ->
+ {?clause, [?Atom(Name)], [], [?Atom(rec_name(Name, Pre))]}.
avps({_Mod, Avps}) ->
Avps.
@@ -390,32 +373,47 @@ f_avp_name(Spec) ->
%% allocated by IANA (see Section 11.1).
avp_name(Spec) ->
- Avps = get_value(avp_types, Spec)
- ++ lists:flatmap(fun avps/1, get_value(import_avps, Spec)),
- {Vid, _} = orddict:fetch(vendor, Spec),
- Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end,
- get_value(avp_vendor_id, Spec)),
+ Avps = get_value(avp_types, Spec),
+ Imported = get_value(import_avps, Spec),
+ Vid = orddict:find(vendor, Spec),
+ Vs = vendor_id_map(Spec),
- lists:map(fun(T) -> c_avp_name(T, Vid, Vs) end, Avps)
+ lists:map(fun(T) -> c_avp_name(T, Vs, Vid) end, Avps)
+ ++ lists:flatmap(fun(T) -> c_imported_avp_name(T, Vs) end, Imported)
++ [{?clause, [?VAR('_'), ?VAR('_')], [], [?ATOM('AVP')]}].
-c_avp_name({Name, Code, Type, Flags, _Encr}, Vid, Vs) ->
- c_avp_name({Name, Type},
- Code,
- lists:member('V', Flags),
- Vid,
- proplists:get_value(Name, Vs)).
+c_avp_name({Name, Code, Type, Flags}, Vs, Vid) ->
+ c_avp_name_(?TERM({?A(Name), ?A(Type)}),
+ ?INTEGER(Code),
+ vid(Name, Flags, Vs, Vid)).
-c_avp_name(T, Code, false, _, undefined = U) ->
- {?clause, [?INTEGER(Code), ?ATOM(U)],
+%% Note that an imported AVP's vendor id is determined by
+%% avp_vendor_id in the inheriting module and vendor in the inherited
+%% module. In particular, avp_vendor_id in the inherited module is
+%% ignored so can't just call Mod:avp_header/1 to retrieve the vendor
+%% id. A vendor id specified in @grouped is equivalent to one
+%% specified as avp_vendor_id.
+
+c_imported_avp_name({Mod, Avps}, Vs) ->
+ lists:map(fun(A) -> c_avp_name(A, Vs, {module, Mod}) end, Avps).
+
+c_avp_name_(T, Code, undefined = U) ->
+ {?clause, [Code, ?ATOM(U)],
[],
- [?TERM(T)]};
+ [T]};
-c_avp_name(T, Code, true, Vid, V)
- when is_integer(Vid) ->
- {?clause, [?INTEGER(Code), ?INTEGER(choose(V == undefined, Vid, V))],
+c_avp_name_(T, Code, Vid) ->
+ {?clause, [Code, ?INTEGER(Vid)],
[],
- [?TERM(T)]}.
+ [T]}.
+
+vendor_id_map(Spec) ->
+ lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end,
+ get_value(avp_vendor_id, Spec))
+ ++ lists:flatmap(fun({_,_,[],_}) -> [];
+ ({N,_,[V],_}) -> [{N,V}]
+ end,
+ get_value(grouped, Spec)).
%%% ------------------------------------------------------------------------
%%% # avp_arity/2
@@ -445,60 +443,75 @@ c_avp_arity(Name, Avps) ->
c_arity(Name, Avp) ->
{AvpName, Arity} = avp_info(Avp),
- {?clause, [?ATOM(Name), ?ATOM(AvpName)], [], [?TERM(Arity)]}.
+ {?clause, [?Atom(Name), ?Atom(AvpName)], [], [?TERM(Arity)]}.
%%% ------------------------------------------------------------------------
%%% # avp/3
%%% ------------------------------------------------------------------------
f_avp(Spec) ->
- {?function, avp, 3, avp(Spec) ++ [?UNEXPECTED(3)]}.
+ {?function, avp, 3, avp(Spec) ++ [?BADARG(3)]}.
avp(Spec) ->
- Native = get_value(avp_types, Spec),
- Custom = get_value(custom_types, Spec),
- Imported = get_value(import_avps, Spec),
- Enums = get_value(enums, Spec),
- avp([{N,T} || {N,_,T,_,_} <- Native], Imported, Custom, Enums).
+ Native = get_value(avp_types, Spec),
+ CustomMods = get_value(custom_types, Spec),
+ TypeMods = get_value(codecs, Spec),
+ Imported = get_value(import_avps, Spec),
+ Enums = get_value(enum, Spec),
-avp(Native, Imported, Custom, Enums) ->
- Dict = orddict:from_list(Native),
+ Custom = lists:map(fun({M,As}) -> {M, custom_types, As} end,
+ CustomMods)
+ ++ lists:map(fun({M,As}) -> {M, codecs, As} end,
+ TypeMods),
+ avp(types(Native), Imported, Custom, Enums).
+
+types(Avps) ->
+ lists:map(fun({N,_,T,_}) -> {N,T} end, Avps).
- report(native, Dict),
+avp(Native, Imported, Custom, Enums) ->
+ report(native, Native),
report(imported, Imported),
report(custom, Custom),
- CustomNames = lists:flatmap(fun({_,Ns}) -> Ns end, Custom),
+ TypeDict = lists:foldl(fun({N,_,T,_}, D) -> orddict:store(N,T,D) end,
+ orddict:from_list(Native),
+ lists:flatmap(fun avps/1, Imported)),
+
+ CustomNames = lists:flatmap(fun({_,_,Ns}) -> Ns end, Custom),
lists:map(fun c_base_avp/1,
- lists:filter(fun({N,_}) ->
- false == lists:member(N, CustomNames)
- end,
+ lists:filter(fun({N,_}) -> not_in(CustomNames, N) end,
Native))
- ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums) end, Imported)
- ++ lists:flatmap(fun(C) -> cs_custom_avp(C, Dict) end, Custom).
+ ++ lists:flatmap(fun(I) -> cs_imported_avp(I, Enums, CustomNames) end,
+ Imported)
+ ++ lists:flatmap(fun(C) -> cs_custom_avp(C, TypeDict) end, Custom).
+
+not_in(List, X) ->
+ not lists:member(X, List).
c_base_avp({AvpName, T}) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
- [base_avp(AvpName, T)]}.
+ [b_base_avp(AvpName, T)]}.
-base_avp(AvpName, 'Enumerated') ->
- ?CALL(enumerated_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]);
+b_base_avp(AvpName, "Enumerated") ->
+ ?CALL(enumerated_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]);
-base_avp(AvpName, 'Grouped') ->
- ?CALL(grouped_avp, [?VAR('T'), ?ATOM(AvpName), ?VAR('Data')]);
+b_base_avp(AvpName, "Grouped") ->
+ ?CALL(grouped_avp, [?VAR('T'), ?Atom(AvpName), ?VAR('Data')]);
-base_avp(_, Type) ->
- ?APPLY(diameter_types, Type, [?VAR('T'), ?VAR('Data')]).
+b_base_avp(_, Type) ->
+ ?APPLY(diameter_types, ?A(Type), [?VAR('T'), ?VAR('Data')]).
-cs_imported_avp({Mod, Avps}, Enums) ->
- lists:map(fun(A) -> imported_avp(Mod, A, Enums) end, Avps).
+cs_imported_avp({Mod, Avps}, Enums, CustomNames) ->
+ lists:map(fun(A) -> imported_avp(Mod, A, Enums) end,
+ lists:filter(fun({N,_,_,_}) -> not_in(CustomNames, N) end,
+ Avps)).
-imported_avp(_Mod, {AvpName, _, 'Grouped' = T, _, _}, _) ->
+imported_avp(_Mod, {AvpName, _, "Grouped" = T, _}, _) ->
c_base_avp({AvpName, T});
-imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) ->
+imported_avp(Mod, {AvpName, _, "Enumerated" = T, _}, Enums) ->
case lists:keymember(AvpName, 1, Enums) of
true ->
c_base_avp({AvpName, T});
@@ -506,34 +519,40 @@ imported_avp(Mod, {AvpName, _, 'Enumerated' = T, _, _}, Enums) ->
c_imported_avp(Mod, AvpName)
end;
-imported_avp(Mod, {AvpName, _, _, _, _}, _) ->
+imported_avp(Mod, {AvpName, _, _, _}, _) ->
c_imported_avp(Mod, AvpName).
c_imported_avp(Mod, AvpName) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
[?APPLY(Mod, avp, [?VAR('T'),
?VAR('Data'),
- ?ATOM(AvpName)])]}.
+ ?Atom(AvpName)])]}.
-cs_custom_avp({Mod, Avps}, Dict) ->
- lists:map(fun(N) -> c_custom_avp(Mod, N, orddict:fetch(N, Dict)) end,
+cs_custom_avp({Mod, Key, Avps}, Dict) ->
+ lists:map(fun(N) -> c_custom_avp(Mod, Key, N, orddict:fetch(N, Dict)) end,
Avps).
-c_custom_avp(Mod, AvpName, Type) ->
- {?clause, [?VAR('T'), ?VAR('Data'), ?ATOM(AvpName)],
+c_custom_avp(Mod, Key, AvpName, Type) ->
+ {F,A} = custom(Key, AvpName, Type),
+ {?clause, [?VAR('T'), ?VAR('Data'), ?Atom(AvpName)],
[],
- [?APPLY(Mod, AvpName, [?VAR('T'), ?ATOM(Type), ?VAR('Data')])]}.
+ [?APPLY(?A(Mod), ?A(F), [?VAR('T'), ?Atom(A), ?VAR('Data')])]}.
+
+custom(custom_types, AvpName, Type) ->
+ {AvpName, Type};
+custom(codecs, AvpName, Type) ->
+ {Type, AvpName}.
%%% ------------------------------------------------------------------------
%%% # enumerated_avp/3
%%% ------------------------------------------------------------------------
f_enumerated_avp(Spec) ->
- {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?UNEXPECTED(3)]}.
+ {?function, enumerated_avp, 3, enumerated_avp(Spec) ++ [?BADARG(3)]}.
enumerated_avp(Spec) ->
- Enums = get_value(enums, Spec),
+ Enums = get_value(enum, Spec),
lists:flatmap(fun cs_enumerated_avp/1, Enums)
++ lists:flatmap(fun({M,Es}) -> enumerated_avp(M, Es, Enums) end,
get_value(import_enums, Spec)).
@@ -554,11 +573,11 @@ cs_enumerated_avp(false, _, _) ->
cs_enumerated_avp({AvpName, Values}) ->
lists:flatmap(fun(V) -> c_enumerated_avp(AvpName, V) end, Values).
-c_enumerated_avp(AvpName, {I,_}) ->
- [{?clause, [?ATOM(decode), ?ATOM(AvpName), ?TERM(<<I:32/integer>>)],
+c_enumerated_avp(AvpName, {_,I}) ->
+ [{?clause, [?ATOM(decode), ?Atom(AvpName), ?TERM(<<I:32/integer>>)],
[],
[?TERM(I)]},
- {?clause, [?ATOM(encode), ?ATOM(AvpName), ?INTEGER(I)],
+ {?clause, [?ATOM(encode), ?Atom(AvpName), ?INTEGER(I)],
[],
[?TERM(<<I:32/integer>>)]}].
@@ -567,7 +586,7 @@ c_enumerated_avp(AvpName, {I,_}) ->
%%% ------------------------------------------------------------------------
f_msg_header(Spec) ->
- {?function, msg_header, 1, msg_header(Spec) ++ [?UNEXPECTED(1)]}.
+ {?function, msg_header, 1, msg_header(Spec) ++ [?BADARG(1)]}.
msg_header(Spec) ->
msg_header(get_value(messages, Spec), Spec).
@@ -582,7 +601,7 @@ msg_header(Msgs, Spec) ->
%% Note that any application id in the message header spec is ignored.
c_msg_header(Name, Code, Flags, ApplId) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
[?TERM({Code, encode_msg_flags(Flags), ApplId})]}.
@@ -598,50 +617,61 @@ emf('ERR', N) -> N bor 2#00100000.
%%% ------------------------------------------------------------------------
f_avp_header(Spec) ->
- {?function, avp_header, 1, avp_header(Spec) ++ [?UNEXPECTED(1)]}.
+ {?function, avp_header, 1, avp_header(Spec) ++ [?BADARG(1)]}.
avp_header(Spec) ->
Native = get_value(avp_types, Spec),
Imported = get_value(import_avps, Spec),
- {Vid, _} = orddict:fetch(vendor, Spec),
- Vs = lists:flatmap(fun({V,Ns}) -> [{N,V} || N <- Ns] end,
- get_value(avp_vendor_id, Spec)),
+ Vid = orddict:find(vendor, Spec),
+ Vs = vendor_id_map(Spec),
- lists:flatmap(fun(A) -> c_avp_header({Vid, Vs}, A) end,
+ lists:flatmap(fun(A) -> c_avp_header(A, Vs, Vid) end,
Native ++ Imported).
-c_avp_header({Vid, Vs}, {Name, Code, _Type, Flags, _Encr}) ->
- [{?clause, [?ATOM(Name)],
+c_avp_header({Name, Code, _Type, Flags}, Vs, Vid) ->
+ [{?clause, [?Atom(Name)],
[],
[?TERM({Code, encode_avp_flags(Flags), vid(Name, Flags, Vs, Vid)})]}];
-c_avp_header({_, Vs}, {Mod, Avps}) ->
- lists:map(fun(A) -> c_avp_header(Vs, Mod, A) end, Avps).
+c_avp_header({Mod, Avps}, Vs, _Vid) ->
+ lists:map(fun(A) -> c_imported_avp_header(A, Mod, Vs) end, Avps).
-c_avp_header(Vs, Mod, {Name, _, _, Flags, _}) ->
- Apply = ?APPLY(Mod, avp_header, [?ATOM(Name)]),
- {?clause, [?ATOM(Name)],
+%% Note that avp_vendor_id in the inherited dictionary is ignored. The
+%% value must be changed in the inheriting dictionary. This is
+%% consistent with the semantics of avp_name/2.
+
+c_imported_avp_header({Name, _Code, _Type, _Flags}, Mod, Vs) ->
+ Apply = ?APPLY(Mod, avp_header, [?Atom(Name)]),
+ {?clause, [?Atom(Name)],
[],
[case proplists:get_value(Name, Vs) of
undefined ->
Apply;
Vid ->
- true = lists:member('V', Flags), %% sanity check
?CALL(setelement, [?INTEGER(3), Apply, ?INTEGER(Vid)])
end]}.
encode_avp_flags(Fs) ->
lists:foldl(fun eaf/2, 0, Fs).
-eaf('V', F) -> 2#10000000 bor F;
-eaf('M', F) -> 2#01000000 bor F;
-eaf('P', F) -> 2#00100000 bor F.
+eaf($V, F) -> 2#10000000 bor F;
+eaf($M, F) -> 2#01000000 bor F;
+eaf($P, F) -> 2#00100000 bor F.
vid(Name, Flags, Vs, Vid) ->
- v(lists:member('V', Flags), Name, Vs, Vid).
+ v(lists:member($V, Flags), Name, Vs, Vid).
+
+v(true = T, Name, Vs, {module, Mod}) ->
+ v(T, Name, Vs, {ok, {Mod:vendor_id(), Mod:vendor_name()}});
v(true, Name, Vs, Vid) ->
- proplists:get_value(Name, Vs, Vid);
+ case proplists:get_value(Name, Vs) of
+ undefined ->
+ {ok, {Id, _}} = Vid,
+ Id;
+ Id ->
+ Id
+ end;
v(false, _, _, _) ->
undefined.
@@ -656,19 +686,19 @@ empty_value(Spec) ->
Imported = lists:flatmap(fun avps/1, get_value(import_enums, Spec)),
Groups = get_value(grouped, Spec)
++ lists:flatmap(fun avps/1, get_value(import_groups, Spec)),
- Enums = [T || {N,_} = T <- get_value(enums, Spec),
+ Enums = [T || {N,_} = T <- get_value(enum, Spec),
not lists:keymember(N, 1, Imported)]
++ Imported,
lists:map(fun c_empty_value/1, Groups ++ Enums)
++ [{?clause, [?VAR('Name')], [], [?CALL(empty, [?VAR('Name')])]}].
c_empty_value({Name, _, _, _}) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
- [?CALL(empty_group, [?ATOM(Name)])]};
+ [?CALL(empty_group, [?Atom(Name)])]};
c_empty_value({Name, _}) ->
- {?clause, [?ATOM(Name)],
+ {?clause, [?Atom(Name)],
[],
[?TERM(<<0:32/integer>>)]}.
@@ -678,7 +708,7 @@ c_empty_value({Name, _}) ->
f_dict(Spec) ->
{?function, dict, 0,
- [{?clause, [], [], [?TERM(Spec)]}]}.
+ [{?clause, [], [], [?TERM([?VERSION | Spec])]}]}.
%%% ------------------------------------------------------------------------
%%% # gen_hrl/3
@@ -706,10 +736,10 @@ gen_hrl(Path, Mod, Spec) ->
write("ENUM Macros",
Fd,
- m_enums(PREFIX, false, get_value(enums, Spec))),
- write("RESULT CODE Macros",
+ m_enums(PREFIX, false, get_value(enum, Spec))),
+ write("DEFINE Macros",
Fd,
- m_enums(PREFIX, false, get_value(result_codes, Spec))),
+ m_enums(PREFIX, false, get_value(define, Spec))),
lists:foreach(fun({M,Es}) ->
write("ENUM Macros from " ++ atom_to_list(M),
@@ -751,8 +781,8 @@ m_enums(Prefix, Wrap, Enums) ->
m_enum(Prefix, B, {Name, Values}) ->
P = Prefix ++ to_upper(Name) ++ "_",
- lists:map(fun({I,A}) ->
- N = ["'", P, to_upper(z(atom_to_list(A))), "'"],
+ lists:map(fun({A,I}) ->
+ N = ["'", P, to_upper(z(A)), "'"],
wrap(B,
N,
["-define(", N, ", ", integer_to_list(I), ").\n"])
@@ -794,34 +824,34 @@ header() ->
"%%\n\n").
hrl_header(Name) ->
- header() ++ "-hrl_name('" ++ Name ++ ".hrl').\n".
+ header() ++ "-hrl_name('" ++ ?S(Name) ++ ".hrl').\n".
%% avp_info/1
avp_info(Entry) -> %% {Name, Arity}
case Entry of
- {'<',A,'>'} -> {A, 1};
- {A} -> {A, 1};
- [A] -> {A, {0,1}};
+ {{A}} -> {A, 1};
+ {A} -> {A, 1};
+ [A] -> {A, {0,1}};
{Q,T} ->
{A,_} = avp_info(T),
- {A, arity(Q)}
+ {A, arity(T,Q)}
end.
%% Normalize arity to 1 or {N,X} where N is an integer. A record field
%% for an AVP is list-valued iff the normalized arity is not 1.
-arity('*' = Inf) -> {0, Inf};
-arity({'*', N}) -> {0, N};
-arity({1,1}) -> 1;
-arity(T) -> T.
+arity({{_}}, '*' = Inf) -> {0, Inf};
+arity([_], '*' = Inf) -> {0, Inf};
+arity({_}, '*' = Inf) -> {1, Inf};
+arity(_, {_,_} = Q) -> Q.
prefix(Spec) ->
case orddict:find(prefix, Spec) of
{ok, P} ->
- atom_to_list(P) ++ "_";
+ P ++ "_";
error ->
""
end.
rec_name(Name, Prefix) ->
- list_to_atom(Prefix ++ atom_to_list(Name)).
+ Prefix ++ Name.
diff --git a/lib/diameter/src/compiler/diameter_dict_parser.yrl b/lib/diameter/src/compiler/diameter_dict_parser.yrl
new file mode 100644
index 0000000000..6fd4cedd23
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_parser.yrl
@@ -0,0 +1,324 @@
+%% -*- erlang -*-
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% A grammar for dictionary specification.
+%%
+
+Nonterminals
+ application_id avp avp_code avp_def avp_defs avp_flags avp_header
+ avp_header_tok avp_name avp_names avp_ref avp_spec avp_type
+ avp_vendor avps bit bits command_def command_id diameter_name
+ dictionary enum_def enum_defs group_def group_defs header header_tok
+ ident idents message_defs module qual section sections.
+
+Terminals
+ avp_types avp_vendor_id codecs custom_types define enum grouped
+ id inherits messages name prefix vendor
+ number word
+ '{' '}' '<' '>' '[' ']' '*' '::=' ':' ',' '-'
+ code
+ 'answer-message'
+ 'AVP' 'AVP-Header'
+ 'Diameter' 'Diameter-Header' 'Header'
+ 'REQ' 'PXY' 'ERR'.
+
+Rootsymbol dictionary.
+
+Endsymbol '$end'.
+
+%% ===========================================================================
+
+dictionary -> sections : '$1'.
+
+sections -> '$empty' : [].
+sections -> section sections : ['$1' | '$2'].
+
+section -> name ident : ['$1', '$2'].
+section -> prefix ident : ['$1', '$2'].
+section -> id number : ['$1', '$2'].
+section -> vendor number ident : ['$1', '$2', '$3'].
+section -> inherits module avp_names : ['$1', '$2' | '$3'].
+section -> avp_types avp_defs : ['$1' | '$2'].
+section -> avp_vendor_id number avp_names : ['$1', '$2' | '$3'].
+section -> custom_types module avp_names : ['$1', '$2' | '$3'].
+section -> codecs module avp_names : ['$1', '$2' | '$3'].
+section -> messages message_defs : ['$1' | '$2'].
+section -> grouped group_defs : ['$1' | '$2'].
+section -> enum ident enum_defs : ['$1', '$2' | '$3'].
+section -> define ident enum_defs : ['$1', '$2' | '$3'].
+
+%% =====================================
+
+module -> ident : '$1'.
+
+avp_names -> idents : '$1'. %% Note: not 'AVP'
+
+avp_defs -> '$empty' : [].
+avp_defs -> avp_def avp_defs : ['$1' | '$2'].
+
+avp_def -> ident number avp_type avp_flags : ['$1', '$2', '$3', '$4'].
+
+avp_type -> ident : '$1'.
+
+idents -> '$empty' : [].
+idents -> ident idents : ['$1' | '$2'].
+
+avp_flags -> '-' :
+ {_, Lineno} = '$1',
+ {word, Lineno, ""}.
+avp_flags -> ident :
+ '$1'.
+%% Could support lowercase here if there's a use for distinguishing
+%% between Must and Should in the future in deciding whether or not
+%% to set a flag.
+
+ident -> word : '$1'.
+
+%% Don't bother mapping reserved words to make these usable in this
+%% context. That an AVP can't be named Diameter-Header is probably no
+%% great loss, and that it can't be named AVP may even save someone
+%% from themselves. (Temporarily at least.)
+
+group_defs -> '$empty' : [].
+group_defs -> group_def group_defs : ['$1' | '$2'].
+
+message_defs -> '$empty' : [].
+message_defs -> command_def message_defs : ['$1' | '$2'].
+
+enum_defs -> '$empty' : [].
+enum_defs -> enum_def enum_defs : ['$1' | '$2'].
+
+enum_def -> ident number : ['$1', '$2'].
+
+%% =====================================
+%% 3.2. Command Code ABNF specification
+%%
+%% Every Command Code defined MUST include a corresponding ABNF
+%% specification, which is used to define the AVPs that MUST or MAY be
+%% present when sending the message. The following format is used in
+%% the definition:
+
+%% command-def = <command-name> "::=" diameter-message
+%%
+%% command-name = diameter-name
+%%
+%% diameter-name = ALPHA *(ALPHA / DIGIT / "-")
+%%
+%% diameter-message = header [ *fixed] [ *required] [ *optional]
+
+%% answer-message is a special case.
+command_def -> 'answer-message' '::=' '<' header_tok ':' code
+ ',' 'ERR' '[' 'PXY' ']' '>'
+ avps
+ : ['$1', false | '$13'].
+
+command_def -> diameter_name '::=' header avps
+ : ['$1', '$3' | '$4'].
+%% Ensure the order fixed/required/optional by semantic checks rather
+%% than grammatically since the latter requires more lookahead: don't
+%% know until after a leading qual which of the three it is that's
+%% being parsed.
+
+diameter_name -> ident : '$1'.
+
+%% header = "<" "Diameter Header:" command-id
+%% [r-bit] [p-bit] [e-bit] [application-id] ">"
+%%
+%% command-id = 1*DIGIT
+%% ; The Command Code assigned to the command
+%%
+%% r-bit = ", REQ"
+%% ; If present, the 'R' bit in the Command
+%% ; Flags is set, indicating that the message
+%% ; is a request, as opposed to an answer.
+%%
+%% p-bit = ", PXY"
+%% ; If present, the 'P' bit in the Command
+%% ; Flags is set, indicating that the message
+%% ; is proxiable.
+%%
+%% e-bit = ", ERR"
+%% ; If present, the 'E' bit in the Command
+%% ; Flags is set, indicating that the answer
+%% ; message contains a Result-Code AVP in
+%% ; the "protocol error" class.
+%%
+%% application-id = 1*DIGIT
+
+header -> '<' header_tok ':' command_id bits application_id '>'
+ : ['$4', '$5', '$6'].
+
+command_id -> number : '$1'.
+
+%% Accept both the form of the base definition and the typo (fixed in
+%% 3588bis) of the grammar.
+header_tok -> 'Diameter' 'Header'.
+header_tok -> 'Diameter-Header'.
+
+bits -> '$empty' : [].
+bits -> ',' bit bits : ['$2' | '$3'].
+
+%% ERR only makes sense for answer-message so don't allow it here
+%% (despite 3588).
+bit -> 'REQ' : '$1'.
+bit -> 'PXY' : '$1'.
+
+application_id -> '$empty' : false.
+application_id -> number : '$1'.
+
+%% fixed = [qual] "<" avp-spec ">"
+%% ; Defines the fixed position of an AVP
+%%
+%% required = [qual] "{" avp-spec "}"
+%% ; The AVP MUST be present and can appear
+%% ; anywhere in the message.
+%%
+%% optional = [qual] "[" avp-name "]"
+%% ; The avp-name in the 'optional' rule cannot
+%% ; evaluate to any AVP Name which is included
+%% ; in a fixed or required rule. The AVP can
+%% ; appear anywhere in the message.
+%% ;
+%% ; NOTE: "[" and "]" have a slightly different
+%% ; meaning than in ABNF (RFC 5234]). These braces
+%% ; cannot be used to express optional fixed rules
+%% ; (such as an optional ICV at the end). To do this,
+%% ; the convention is '0*1fixed'.
+
+avps -> '$empty' : [].
+avps -> avp avps : ['$1' | '$2'].
+
+avp -> avp_ref : [false | '$1'].
+avp -> qual avp_ref : ['$1' | '$2'].
+
+avp_ref -> '<' avp_spec '>' : [$<, '$2'].
+avp_ref -> '{' avp_name '}' : [${, '$2'].
+avp_ref -> '[' avp_name ']' : [$[, '$2'].
+%% Note that required can be an avp_name, not just avp_spec. 'AVP'
+%% is specified as required by Failed-AVP for example.
+
+%% qual = [min] "*" [max]
+%% ; See ABNF conventions, RFC 5234 Section 4.
+%% ; The absence of any qualifiers depends on
+%% ; whether it precedes a fixed, required, or
+%% ; optional rule. If a fixed or required rule has
+%% ; no qualifier, then exactly one such AVP MUST
+%% ; be present. If an optional rule has no
+%% ; qualifier, then 0 or 1 such AVP may be
+%% ; present. If an optional rule has a qualifier,
+%% ; then the value of min MUST be 0 if present.
+%%
+%% min = 1*DIGIT
+%% ; The minimum number of times the element may
+%% ; be present. If absent, the default value is zero
+%% ; for fixed and optional rules and one for required
+%% ; rules. The value MUST be at least one for for
+%% ; required rules.
+%%
+%% max = 1*DIGIT
+%% ; The maximum number of times the element may
+%% ; be present. If absent, the default value is
+%% ; infinity. A value of zero implies the AVP MUST
+%% ; NOT be present.
+
+qual -> number '*' number : {'$1', '$3'}.
+qual -> number '*' : {'$1', true}.
+qual -> '*' number : {true, '$2'}.
+qual -> '*' : true.
+
+%% avp-spec = diameter-name
+%% ; The avp-spec has to be an AVP Name, defined
+%% ; in the base or extended Diameter
+%% ; specifications.
+
+avp_spec -> diameter_name : '$1'.
+
+%% avp-name = avp-spec / "AVP"
+%% ; The string "AVP" stands for *any* arbitrary AVP
+%% ; Name, not otherwise listed in that command code
+%% ; definition. Addition this AVP is recommended for
+%% ; all command ABNFs to allow for extensibility.
+
+avp_name -> 'AVP' : '$1'.
+avp_name -> avp_spec : '$1'.
+
+%% The following is a definition of a fictitious command code:
+%%
+%% Example-Request ::= < Diameter Header: 9999999, REQ, PXY >
+%% { User-Name }
+%% * { Origin-Host }
+%% * [ AVP ]
+
+%% =====================================
+%% 4.4. Grouped AVP Values
+%%
+%% The Diameter protocol allows AVP values of type 'Grouped'. This
+%% implies that the Data field is actually a sequence of AVPs. It is
+%% possible to include an AVP with a Grouped type within a Grouped type,
+%% that is, to nest them. AVPs within an AVP of type Grouped have the
+%% same padding requirements as non-Grouped AVPs, as defined in Section
+%% 4.
+%%
+%% The AVP Code numbering space of all AVPs included in a Grouped AVP is
+%% the same as for non-grouped AVPs. Receivers of a Grouped AVP that
+%% does not have the 'M' (mandatory) bit set and one or more of the
+%% encapsulated AVPs within the group has the 'M' (mandatory) bit set
+%% MAY simply be ignored if the Grouped AVP itself is unrecognized. The
+%% rule applies even if the encapsulated AVP with its 'M' (mandatory)
+%% bit set is further encapsulated within other sub-groups; i.e. other
+%% Grouped AVPs embedded within the Grouped AVP.
+%%
+%% Every Grouped AVP defined MUST include a corresponding grammar, using
+%% ABNF [RFC5234] (with modifications), as defined below.
+
+%% grouped-avp-def = <name> "::=" avp
+%%
+%% name-fmt = ALPHA *(ALPHA / DIGIT / "-")
+%%
+%% name = name-fmt
+%% ; The name has to be the name of an AVP,
+%% ; defined in the base or extended Diameter
+%% ; specifications.
+%%
+%% avp = header [ *fixed] [ *required] [ *optional]
+
+group_def -> ident '::=' avp_header avps : ['$1', '$3' | '$4'].
+
+%% header = "<" "AVP-Header:" avpcode [vendor] ">"
+%%
+%% avpcode = 1*DIGIT
+%% ; The AVP Code assigned to the Grouped AVP
+%%
+%% vendor = 1*DIGIT
+%% ; The Vendor-ID assigned to the Grouped AVP.
+%% ; If absent, the default value of zero is
+%% ; used.
+
+avp_header -> '<' avp_header_tok ':' avp_code avp_vendor '>'
+ : ['$4', '$5'].
+
+avp_header_tok -> 'AVP-Header'.
+avp_header_tok -> 'AVP' 'Header'.
+
+avp_code -> number : '$1'.
+
+avp_vendor -> '$empty' : false.
+avp_vendor -> number : '$1'.
diff --git a/lib/diameter/src/compiler/diameter_dict_scanner.erl b/lib/diameter/src/compiler/diameter_dict_scanner.erl
new file mode 100644
index 0000000000..45189376fb
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_scanner.erl
@@ -0,0 +1,276 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(diameter_dict_scanner).
+
+%%
+%% A scanner for dictionary files of the form expected by yecc.
+%%
+
+-export([scan/1,
+ format_error/1]).
+
+-export([is_name/1]).
+
+%% -----------------------------------------------------------
+%% # scan/1
+%% -----------------------------------------------------------
+
+-spec scan(string() | binary())
+ -> {ok, [Token]}
+ | {error, {string(), string(), Lineno}}
+ when Token :: {word, Lineno, string()}
+ | {number, Lineno, non_neg_integer()}
+ | {Symbol, Lineno},
+ Lineno :: pos_integer(),
+ Symbol :: '{' | '}' | '<' | '>' | '[' | ']'
+ | '*' | '::=' | ':' | ',' | '-'
+ | avp_types
+ | avp_vendor_id
+ | codecs
+ | custom_types
+ | define
+ | grouped
+ | id
+ | inherits
+ | messages
+ | name
+ | prefix
+ | vendor
+ | '$end'
+ | code
+ | 'answer-message'
+ | 'AVP'
+ | 'AVP-Header'
+ | 'Diameter'
+ | 'Diameter-Header'
+ | 'Header'
+ | 'REQ'
+ | 'PXY'
+ | 'ERR'.
+
+scan(B)
+ when is_binary(B) ->
+ scan(binary_to_list(B));
+scan(S) ->
+ scan(S, {1, []}).
+
+scan(S, {Lineno, Acc}) ->
+ case split(S) of
+ '$end' = E ->
+ {ok, lists:reverse([{E, Lineno} | Acc])};
+ {Tok, Rest} ->
+ scan(Rest, acc(Tok, Lineno, Acc));
+ Reason when is_list(Reason) ->
+ {error, {Reason, S, Lineno}}
+ end.
+
+%% format_error/1
+
+format_error({Reason, Input, Lineno}) ->
+ io_lib:format("~s at line ~p: ~s",
+ [Reason, Lineno, head(Input, [], 20, true)]).
+
+%% is_name/1
+
+is_name([H|T]) ->
+ is_alphanum(H) andalso lists:all(fun is_name_ch/1, T).
+
+%% ===========================================================================
+
+head(Str, Acc, N, _)
+ when [] == Str;
+ 0 == N;
+ $\r == hd(Str);
+ $\n == hd(Str) ->
+ lists:reverse(Acc);
+head([C|Rest], Acc, N, true = T) %% skip leading whitespace
+ when C == $\s;
+ C == $\t;
+ C == $\f;
+ C == $\v ->
+ head(Rest, Acc, N, T);
+head([C|Rest], Acc, N, _) ->
+ head(Rest, [C|Acc], N-1, false).
+
+acc(endline, Lineno, Acc) ->
+ {Lineno + 1, Acc};
+acc(T, Lineno, Acc) ->
+ {Lineno, [tok(T, Lineno) | Acc]}.
+
+tok({Cat, Sym}, Lineno) ->
+ {Cat, Lineno, Sym};
+tok(Sym, Lineno) ->
+ {Sym, Lineno}.
+
+%% # split/1
+%%
+%% Output: {Token, Rest} | atom()
+
+%% Finito.
+split("") ->
+ '$end';
+
+%% Skip comments. This precludes using semicolon for any other purpose.
+split([$;|T]) ->
+ split(lists:dropwhile(fun(C) -> not is_eol_ch(C) end, T));
+
+%% Beginning of a section.
+split([$@|T]) ->
+ {Name, Rest} = lists:splitwith(fun is_name_ch/1, T),
+ case section(Name) of
+ false ->
+ "Unknown section";
+ 'end' ->
+ '$end';
+ A ->
+ {A, Rest}
+ end;
+
+split("::=" ++ T) ->
+ {'::=', T};
+
+split([H|T])
+ when H == ${; H == $};
+ H == $<; H == $>;
+ H == $[; H == $];
+ H == $*; H == $:; H == $,; H == $- ->
+ {list_to_atom([H]), T};
+
+%% RFC 3588 requires various names to begin with a letter but 3GPP (for
+%% one) abuses this. (eg 3GPP-Charging-Id in TS32.299.)
+split([H|_] = L) when $0 =< H, H =< $9 ->
+ {P, Rest} = splitwith(fun is_name_ch/1, L),
+ Tok = try
+ {number, read_int(P)}
+ catch
+ error:_ ->
+ word(P)
+ end,
+ {Tok, Rest};
+
+split([H|_] = L) when $a =< H, H =< $z;
+ $A =< H, H =< $Z ->
+ {P, Rest} = splitwith(fun is_name_ch/1, L),
+ {word(P), Rest};
+
+split([$'|T]) ->
+ case lists:splitwith(fun(C) -> not lists:member(C, "'\r\n") end, T) of
+ {[_|_] = A, [$'|Rest]} ->
+ {{word, A}, Rest};
+ {[], [$'|_]} ->
+ "Empty string";
+ _ -> %% not terminated on same line
+ "Unterminated string"
+ end;
+
+%% Line ending of various forms.
+split([$\r,$\n|T]) ->
+ {endline, T};
+split([C|T])
+ when C == $\r;
+ C == $\n ->
+ {endline, T};
+
+%% Ignore whitespace.
+split([C|T])
+ when C == $\s;
+ C == $\t;
+ C == $\f;
+ C == $\v ->
+ split(T);
+
+split(_) ->
+ "Unexpected character".
+
+%% word/1
+
+%% Reserved words significant in parsing ...
+word(S)
+ when S == "answer-message";
+ S == "code";
+ S == "AVP";
+ S == "AVP-Header";
+ S == "Diameter";
+ S == "Diameter-Header";
+ S == "Header";
+ S == "REQ";
+ S == "PXY";
+ S == "ERR" ->
+ list_to_atom(S);
+
+%% ... or not.
+word(S) ->
+ {word, S}.
+
+%% section/1
+
+section(N)
+ when N == "avp_types";
+ N == "avp_vendor_id";
+ N == "codecs";
+ N == "custom_types";
+ N == "define";
+ N == "end";
+ N == "enum";
+ N == "grouped";
+ N == "id";
+ N == "inherits";
+ N == "messages";
+ N == "name";
+ N == "prefix";
+ N == "vendor" ->
+ list_to_atom(N);
+section(_) ->
+ false.
+
+%% read_int/1
+
+read_int([$0,X|S])
+ when X == $X;
+ X == $x ->
+ {ok, [N], []} = io_lib:fread("~16u", S),
+ N;
+
+read_int(S) ->
+ list_to_integer(S).
+
+%% splitwith/3
+
+splitwith(Fun, [H|T]) ->
+ {SH, ST} = lists:splitwith(Fun, T),
+ {[H|SH], ST}.
+
+is_eol_ch(C) ->
+ C == $\n orelse C == $\r.
+
+is_name_ch(C) ->
+ is_alphanum(C) orelse C == $- orelse C == $_.
+
+is_alphanum(C) ->
+ is_lower(C) orelse is_upper(C) orelse is_digit(C).
+
+is_lower(C) ->
+ $a =< C andalso C =< $z.
+
+is_upper(C) ->
+ $A =< C andalso C =< $Z.
+
+is_digit(C) ->
+ $0 =< C andalso C =< $9.
diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl
new file mode 100644
index 0000000000..36a6efa294
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_dict_util.erl
@@ -0,0 +1,1358 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% This module turns a dictionary file into the orddict that
+%% diameter_codegen.erl in turn morphs into .erl and .hrl files for
+%% encode and decode of Diameter messages and AVPs.
+%%
+
+-module(diameter_dict_util).
+
+-export([parse/2,
+ format_error/1,
+ format/1]).
+
+-include("diameter_vsn.hrl").
+
+-define(RETURN(T), throw({T, ?MODULE, ?LINE})).
+-define(RETURN(T, Args), ?RETURN({T, Args})).
+
+-define(A, list_to_atom).
+-define(L, atom_to_list).
+-define(I, integer_to_list).
+-define(F, io_lib:format).
+
+%% ===========================================================================
+%% parse/2
+%% ===========================================================================
+
+-spec parse(File, Opts)
+ -> {ok, orddict:orddict()}
+ | {error, term()}
+ when File :: {path, string()}
+ | iolist()
+ | binary(),
+ Opts :: list().
+
+parse(File, Opts) ->
+ putr(verbose, lists:member(verbose, Opts)),
+ try
+ {ok, do_parse(File, Opts)}
+ catch
+ {Reason, ?MODULE, _Line} ->
+ {error, Reason}
+ after
+ eraser(verbose)
+ end.
+
+%% ===========================================================================
+%% format_error/1
+%% ===========================================================================
+
+format_error({read, Reason}) ->
+ file:format_error(Reason);
+format_error({scan, Reason}) ->
+ diameter_dict_scanner:format_error(Reason);
+format_error({parse, {Line, _Mod, Reason}}) ->
+ lists:flatten(["Line ", ?I(Line), ", ", Reason]);
+
+format_error(T) ->
+ {Fmt, As} = fmt(T),
+ lists:flatten(io_lib:format(Fmt, As)).
+
+fmt({avp_code_already_defined = E, [Code, false, Name, Line, L]}) ->
+ {fmt(E), [Code, "", Name, Line, L]};
+fmt({avp_code_already_defined = E, [Code, Vid, Name, Line, L]}) ->
+ {fmt(E), [Code, ?F("/~p", [Vid]), Name, Line, L]};
+
+fmt({uint32_out_of_range = E, [id | T]}) ->
+ {fmt(E), ["@id", "application identifier" | T]};
+fmt({uint32_out_of_range = E, [K | T]})
+ when K == vendor;
+ K == avp_vendor_id ->
+ {fmt(E), [?F("@~p", [K]), "vendor id" | T]};
+fmt({uint32_out_of_range = E, [K, Name | T]})
+ when K == enum;
+ K == define ->
+ {fmt(E), [?F("@~p ~s", [K, Name]), "value" | T]};
+fmt({uint32_out_of_range = E, [avp_types, Name | T]}) ->
+ {fmt(E), ["AVP " ++ Name, "AVP code" | T]};
+fmt({uint32_out_of_range = E, [grouped, Name | T]}) ->
+ {fmt(E), ["Grouped AVP " ++ Name | T]};
+fmt({uint32_out_of_range = E, [messages, Name | T]}) ->
+ {fmt(E), ["Message " ++ Name, "command code" | T]};
+
+fmt({Reason, As}) ->
+ {fmt(Reason), As};
+
+fmt(avp_code_already_defined) ->
+ "AVP ~p~s (~s) at line ~p already defined at line ~p";
+
+fmt(uint32_out_of_range) ->
+ "~s specifies ~s ~p at line ~p that is out of range for a value of "
+ "Diameter type Unsigned32";
+
+fmt(imported_avp_already_defined) ->
+ "AVP ~s imported by @inherits ~p at line ~p defined at line ~p";
+fmt(duplicate_import) ->
+ "AVP ~s is imported by more than one @inherits, both at line ~p "
+ "and at line ~p";
+
+fmt(duplicate_section) ->
+ "Section @~s at line ~p already declared at line ~p";
+
+fmt(already_declared) ->
+ "Section @~p ~s at line ~p already declared at line ~p";
+
+fmt(inherited_avp_already_defined) ->
+ "AVP ~s inherited at line ~p defined in @avp_types at line ~p";
+fmt(avp_already_defined) ->
+ "AVP ~s at line ~p already in @~p at line ~p";
+fmt(key_already_defined) ->
+ "Value for ~s:~s in @~p at line ~p already provided at line ~p";
+
+fmt(messages_without_id) ->
+ "@messages at line ~p but @id not declared";
+
+fmt(avp_name_already_defined) ->
+ "AVP ~s at line ~p already defined at line ~p";
+fmt(avp_has_unknown_type) ->
+ "AVP ~s at line ~p defined with unknown type ~s";
+fmt(avp_has_invalid_flag) ->
+ "AVP ~s at line ~p specifies invalid flag ~c";
+fmt(avp_has_duplicate_flag) ->
+ "AVP ~s at line ~p specifies duplicate flag ~c";
+fmt(avp_has_vendor_id) ->
+ "AVP ~s at line ~p does not specify V flag "
+ "but is assigned vendor id ~p at line ~p";
+fmt(avp_has_no_vendor) ->
+ "AVP ~s at line ~p specifies V flag "
+ "but neither @vendor_avp_id nor @vendor supplies a value";
+
+fmt(group_already_defined) ->
+ "Group ~s at line ~p already defined at line ~p";
+fmt(grouped_avp_code_mismatch) ->
+ "AVP ~s at line ~p has with code ~p "
+ "but @avp_types specifies ~p at line ~p";
+fmt(grouped_avp_has_wrong_type) ->
+ "Grouped AVP ~s at line ~p defined with type ~s at line ~p";
+fmt(grouped_avp_not_defined) ->
+ "Grouped AVP ~s on line ~p not defined in @avp_types";
+fmt(grouped_vendor_id_without_flag) ->
+ "Grouped AVP ~s at line ~p has vendor id "
+ "but definition at line ~p does not specify V flag";
+fmt(grouped_vendor_id_mismatch) ->
+ "Grouped AVP ~s at line ~p has vendor id ~p "
+ "but ~p specified at line ~p";
+
+fmt(message_name_already_defined) ->
+ "Message ~s at line ~p already defined at line ~p";
+fmt(message_code_already_defined) ->
+ "~s message with code ~p at line ~p already defined at line ~p";
+fmt(message_has_duplicate_flag) ->
+ "Message ~s has duplicate flag ~s at line ~p";
+fmt(message_application_id_mismatch) ->
+ "Message ~s has application id ~p at line ~p "
+ "but @id specifies ~p at line ~p";
+
+fmt(invalid_avp_order) ->
+ "AVP reference ~c~s~c at line ~p breaks fixed/required/optional order";
+fmt(required_avp_has_zero_max_arity) ->
+ "Required AVP has maximum arity 0 at line ~p";
+fmt(required_avp_has_zero_min_arity) ->
+ "Required AVP has minimum arity 0 at line ~p";
+fmt(optional_avp_has_nonzero_min_arity) ->
+ "Optional AVP has non-zero minimum arity at line ~p";
+fmt(qualifier_has_min_greater_than_max) ->
+ "Qualifier ~p*~p at line ~p has Min > Max";
+fmt(avp_already_referenced) ->
+ "AVP ~s at line ~p already referenced at line ~p";
+
+fmt(message_missing) ->
+ "~s message at line ~p but no ~s message is defined";
+
+fmt(requested_avp_not_found) ->
+ "@inherit ~s at line ~p requests AVP ~s at line ~p "
+ "but module does not define that AVP";
+
+fmt(enumerated_avp_has_wrong_local_type) ->
+ "Enumerated AVP ~s in @enum at line ~p defined with type ~s at line ~p";
+fmt(enumerated_avp_has_wrong_inherited_type) ->
+ "Enumerated AVP ~s in @enum at line ~p "
+ "inherited with type ~s from module ~s at line ~p";
+fmt(enumerated_avp_not_defined) ->
+ "Enumerated AVP ~s in @enum at line ~p neither defined nor inherited";
+
+fmt(avp_not_defined) ->
+ "AVP ~s referenced at line ~p neither defined nor inherited";
+
+fmt(recompile) ->
+ "Module ~p appears to have been compiler with an incompatible "
+ "version of the dictionary compiler and must be recompiled";
+fmt(not_loaded) ->
+ "Module ~p is not on the code path or could not be loaded";
+fmt(no_dict) ->
+ "Module ~p does not appear to be a diameter dictionary".
+
+%% ===========================================================================
+%% format/1
+%%
+%% Turn dict/0 output back into a dictionary file (with line ending = $\n).
+
+-spec format(Dict)
+ -> iolist()
+ when Dict :: orddict:orddict().
+
+-define(KEYS, [id, name, prefix, vendor,
+ inherits, codecs, custom_types,
+ avp_types,
+ messages,
+ grouped,
+ enum, define]).
+
+format(Dict) ->
+ Io = orddict:fold(fun io/3, [], Dict),
+ [S || {_,S} <- lists:sort(fun keysort/2, Io)].
+
+keysort({A,_}, {B,_}) ->
+ [HA, HB] = [H || K <- [A,B],
+ H <- [lists:takewhile(fun(X) -> X /= K end, ?KEYS)]],
+ HA < HB.
+
+%% ===========================================================================
+
+-define(INDENT, " ").
+-define(SP, " ").
+-define(NL, $\n).
+
+%% io/3
+
+io(K, _, Acc)
+ when K == command_codes;
+ K == import_avps;
+ K == import_groups;
+ K == import_enums ->
+ Acc;
+
+io(Key, Body, Acc) ->
+ [{Key, io(Key, Body)} | Acc].
+
+%% io/2
+
+io(K, Id)
+ when K == id;
+ K == name;
+ K == prefix ->
+ [?NL, section(K), ?SP, tok(Id)];
+
+io(vendor = K, {Id, Name}) ->
+ [?NL, section(K) | [[?SP, tok(X)] || X <- [Id, Name]]];
+
+io(avp_types = K, Body) ->
+ [?NL, ?NL, section(K), ?NL, [body(K,A) || A <- Body]];
+
+io(K, Body)
+ when K == messages;
+ K == grouped ->
+ [?NL, ?NL, section(K), [body(K,A) || A <- Body]];
+
+io(K, Body)
+ when K == avp_vendor_id;
+ K == inherits;
+ K == custom_types;
+ K == codecs;
+ K == enum;
+ K == define ->
+ [[?NL, pairs(K, T)] || T <- Body].
+
+pairs(K, {Id, Avps}) ->
+ [?NL, section(K), ?SP, tok(Id), ?NL, [[?NL, body(K, A)] || A <- Avps]].
+
+body(K, AvpName)
+ when K == avp_vendor_id;
+ K == inherits;
+ K == custom_types;
+ K == codecs ->
+ [?INDENT, word(AvpName)];
+
+body(K, {Name, N})
+ when K == enum;
+ K == define ->
+ [?INDENT, word(Name), ?SP, ?I(N)];
+
+body(avp_types = K, {Name, Code, Type, ""}) ->
+ body(K, {Name, Code, Type, "-"});
+body(avp_types, {Name, Code, Type, Flags}) ->
+ [?NL, ?INDENT, word(Name),
+ [[?SP, ?SP, S] || S <- [?I(Code), Type, Flags]]];
+
+body(messages, {"answer-message", _, _, [], Avps}) ->
+ [?NL, ?NL, ?INDENT,
+ "answer-message ::= < Diameter Header: code, ERR [PXY] >",
+ f_avps(Avps)];
+body(messages, {Name, Code, Flags, ApplId, Avps}) ->
+ [?NL, ?NL, ?INDENT, word(Name), " ::= ", header(Code, Flags, ApplId),
+ f_avps(Avps)];
+
+body(grouped, {Name, Code, Vid, Avps}) ->
+ [?NL, ?NL, ?INDENT, word(Name), " ::= ", avp_header(Code, Vid),
+ f_avps(Avps)].
+
+header(Code, Flags, ApplId) ->
+ ["< Diameter Header: ",
+ ?I(Code),
+ [[", ", ?L(F)] || F <- Flags],
+ [[" ", ?I(N)] || N <- ApplId],
+ " >"].
+
+avp_header(Code, Vid) ->
+ ["< AVP Header: ",
+ ?I(Code),
+ [[" ", ?I(V)] || V <- Vid],
+ " >"].
+
+f_avps(L) ->
+ [[?NL, ?INDENT, ?INDENT, f_avp(A)] || A <- L].
+
+f_avp({Q, A}) ->
+ [D | _] = Avp = f_delim(A),
+ f_avp(f_qual(D, Q), Avp);
+f_avp(A) ->
+ f_avp("", f_delim(A)).
+
+f_delim({{A}}) ->
+ [$<, word(A), $>];
+f_delim({A}) ->
+ [${, word(A), $}];
+f_delim([A]) ->
+ [$[, word(A), $]].
+
+f_avp(Q, [L, Avp, R]) ->
+ Len = length(lists:flatten([Q])),
+ [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), L, " ", Avp, " ", R].
+
+f_qual(${, '*') ->
+ "1*"; %% Equivalent to "*" but the more common/obvious rendition
+f_qual(_, '*') ->
+ "*";
+f_qual(_, {'*', N}) ->
+ [$*, ?I(N)];
+f_qual(_, {N, '*'}) ->
+ [?I(N), $*];
+f_qual(_, {M,N}) ->
+ [?I(M), $*, ?I(N)].
+
+section(Key) ->
+ ["@", ?L(Key)].
+
+tok(N)
+ when is_integer(N) ->
+ ?I(N);
+tok(N) ->
+ word(N).
+
+word(Str) ->
+ word(diameter_dict_scanner:is_name(Str), Str).
+
+word(true, Str) ->
+ Str;
+word(false, Str) ->
+ [$', Str, $'].
+
+%% ===========================================================================
+
+do_parse(File, Opts) ->
+ Bin = do([fun read/1, File], read),
+ Toks = do([fun diameter_dict_scanner:scan/1, Bin], scan),
+ Tree = do([fun diameter_dict_parser:parse/1, Toks], parse),
+ make_dict(Tree, Opts).
+
+do([F|A], E) ->
+ case apply(F,A) of
+ {ok, T} ->
+ T;
+ {error, Reason} ->
+ ?RETURN({E, Reason})
+ end.
+
+read({path, Path}) ->
+ file:read_file(Path);
+read(File) ->
+ {ok, iolist_to_binary([File])}.
+
+make_dict(Parse, Opts) ->
+ make_orddict(pass4(pass3(pass2(pass1(reset(make_dict(Parse),
+ Opts))),
+ Opts))).
+
+%% make_orddict/1
+
+make_orddict(Dict) ->
+ dict:fold(fun mo/3,
+ orddict:from_list([{K,[]} || K <- [avp_types,
+ messages,
+ grouped,
+ inherits,
+ custom_types,
+ codecs,
+ avp_vendor_id,
+ enum,
+ define]]),
+ Dict).
+
+mo(K, Sects, Dict)
+ when is_atom(K) ->
+ orddict:store(K, make(K, Sects), Dict);
+
+mo(_, _, Dict) ->
+ Dict.
+
+make(K, [[_Line, {_, _, X}]])
+ when K == id;
+ K == name;
+ K == prefix ->
+ X;
+
+make(vendor, [[_Line, {_, _, Id}, {_, _, Name}]]) ->
+ {Id, Name};
+
+make(K, T)
+ when K == command_codes;
+ K == import_avps;
+ K == import_groups;
+ K == import_enums ->
+ T;
+
+make(K, Sects) ->
+ post(K, foldl(fun([_L|B], A) -> make(K,B,A) end,
+ [],
+ Sects)).
+
+post(avp_types, L) ->
+ lists:sort(L);
+
+post(K, L)
+ when K == grouped;
+ K == messages;
+ K == enum;
+ K == define ->
+ lists:reverse(L);
+
+post(_, L) ->
+ L.
+
+make(K, [{_,_,Name} | Body], Acc)
+ when K == enum;
+ K == define;
+ K == avp_vendor_id;
+ K == custom_types;
+ K == inherits;
+ K == codecs ->
+ [{Name, mk(K, Body)} | Acc];
+
+make(K, Body, Acc) ->
+ foldl(fun(T,A) -> [mk(K, T) | A] end, Acc, Body).
+
+mk(avp_types, [{_,_,Name}, {_,_,Code}, {_,_,Type}, {_,_,Flags}]) ->
+ {Name, Code, type(Type), Flags};
+
+mk(messages, [{'answer-message' = A, _}, false | Avps]) ->
+ {?L(A), -1, ['ERR', 'PXY'], [], make_body(Avps)};
+
+mk(messages, [{_,_,Name}, [{_,_,Code}, Flags, ApplId] | Avps]) ->
+ {Name,
+ Code,
+ lists:map(fun({F,_}) -> F end, Flags),
+ opt(ApplId),
+ make_body(Avps)};
+
+mk(grouped, [{_,_,Name}, [{_,_,Code}, Vid] | Avps]) ->
+ {Name, Code, opt(Vid), make_body(Avps)};
+
+mk(K, Body)
+ when K == enum;
+ K == define ->
+ lists:map(fun([{_,_,Name}, {_,_,Value}]) -> {Name, Value} end, Body);
+
+mk(K, Avps)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == inherits;
+ K == codecs ->
+ lists:map(fun({_,_,N}) -> N end, Avps).
+
+opt(false) ->
+ [];
+opt({_,_,X}) ->
+ [X].
+
+make_body(Avps) ->
+ lists:map(fun avp/1, Avps).
+
+avp([false, D, Avp]) ->
+ avp(D, Avp);
+avp([Q, D, Avp]) ->
+ case {qual(D, Q), avp(D, Avp)} of
+ {{0,1}, A} when D == $[ ->
+ A;
+ {{1,1}, A} ->
+ A;
+ T ->
+ T
+ end.
+%% Could just store the qualifier as a pair in all cases but the more
+%% compact form is easier to parse visually so live with a bit of
+%% mapping. Ditto the use of '*'.
+
+avp(D, {'AVP', _}) ->
+ delim(D, "AVP");
+avp(D, {_, _, Name}) ->
+ delim(D, Name).
+
+delim($<, N) ->
+ {{N}};
+delim(${, N) ->
+ {N};
+delim($[, N) ->
+ [N].
+
+%% There's a difference between max = 0 and not specifying an AVP:
+%% reception of an AVP with max = 0 will always be an error, otherwise
+%% it depends on the existence of 'AVP' and the M flag.
+
+qual(${, {{_,L,0}, _}) ->
+ ?RETURN(required_avp_has_zero_min_arity, [L]);
+qual(${, {_, {_,L,0}}) ->
+ ?RETURN(required_avp_has_zero_max_arity, [L]);
+
+qual($[, {{_,L,N}, _})
+ when 0 < N ->
+ ?RETURN(optional_avp_has_nonzero_min_arity, [L]);
+
+qual(_, {{_,L,Min}, {_,_,Max}})
+ when Min > Max ->
+ ?RETURN(qualifier_has_min_greater_than_max, [Min, Max, L]);
+
+qual(_, true) ->
+ '*';
+
+qual(${, {true, {_,_,N}}) ->
+ {1, N};
+qual(_, {true, {_,_,N}}) ->
+ {0, N};
+
+qual(D, {{_,_,N}, true})
+ when D == ${, N == 1;
+ D /= ${, N == 0 ->
+ '*';
+qual(_, {{_,_,N}, true}) ->
+ {N, '*'};
+
+qual(_, {{_,_,Min}, {_,_,Max}}) ->
+ {Min, Max}.
+
+%% Optional reports when running verbosely.
+report(What, [F | A])
+ when is_function(F) ->
+ report(What, apply(F, A));
+report(What, Data) ->
+ report(getr(verbose), What, Data).
+
+report(true, Tag, Data) ->
+ io:format("##~n## ~p ~p~n", [Tag, Data]);
+report(false, _, _) ->
+ ok.
+
+%% ------------------------------------------------------------------------
+%% make_dict/1
+%%
+%% Turn a parsed dictionary into an dict.
+
+make_dict(Parse) ->
+ foldl(fun(T,A) ->
+ report(section, T),
+ section(T,A)
+ end,
+ dict:new(),
+ Parse).
+
+section([{T, L} | Rest], Dict)
+ when T == name;
+ T == prefix;
+ T == id;
+ T == vendor ->
+ case find(T, Dict) of
+ [] ->
+ dict:store(T, [[L | Rest]], Dict);
+ [[Line | _]] ->
+ ?RETURN(duplicate_section, [T, L, Line])
+ end;
+
+section([{T, L} | Rest], Dict)
+ when T == avp_types;
+ T == messages;
+ T == grouped;
+ T == inherits;
+ T == custom_types;
+ T == codecs;
+ T == avp_vendor_id;
+ T == enum;
+ T == define ->
+ dict:append(T, [L | Rest], Dict).
+
+%% ===========================================================================
+%% reset/2
+%%
+%% Reset sections from options.
+
+reset(Dict, Opts) ->
+ foldl([fun reset/3, Opts], Dict, [name, prefix, inherits]).
+
+reset(K, Dict, Opts) ->
+ foldl(fun opt/2, Dict, [T || {A,_} = T <- Opts, A == K]).
+
+opt({inherits = Key, "-"}, Dict) ->
+ dict:erase(Key, Dict);
+
+opt({inherits = Key, Mod}, Dict) ->
+ case lists:splitwith(fun(C) -> C /= $/ end, Mod) of
+ {Mod, ""} ->
+ dict:append(Key, [0, {word, 0, Mod}], Dict);
+ {From, [$/|To]} ->
+ dict:store(Key,
+ [reinherit(From, To, M) || M <- find(Key, Dict)],
+ Dict)
+ end;
+
+opt({Key, Val}, Dict) ->
+ dict:store(Key, [[0, {word, 0, Val}]], Dict);
+
+opt(_, Dict) ->
+ Dict.
+
+reinherit(From, To, [L, {word, _, From} = T | Avps]) ->
+ [L, setelement(3, T, To) | Avps];
+reinherit(_, _, T) ->
+ T.
+
+%% ===========================================================================
+%% pass1/1
+%%
+%% Explode sections into additional dictionary entries plus semantic
+%% checks.
+
+pass1(Dict) ->
+ true = no_messages_without_id(Dict),
+
+ foldl(fun(K,D) -> foldl([fun p1/3, K], D, find(K,D)) end,
+ Dict,
+ [id,
+ vendor,
+ avp_types, %% must precede inherits, grouped, enum
+ avp_vendor_id,
+ custom_types,
+ codecs,
+ inherits,
+ grouped,
+ messages,
+ enum,
+ define]).
+
+%% Multiple sections are allowed as long as their bodies don't
+%% overlap. (Except enum/define.)
+
+p1([_Line, N], Dict, id = K) ->
+ true = is_uint32(N, [K]),
+ Dict;
+
+p1([_Line, Id, _Name], Dict, vendor = K) ->
+ true = is_uint32(Id, [K]),
+ Dict;
+
+p1([_Line, X | Body], Dict, K)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == codecs;
+ K == inherits ->
+ foldl([fun explode/4, X, K], Dict, Body);
+
+p1([_Line, X | Body], Dict, K)
+ when K == define;
+ K == enum ->
+ {_, L, Name} = X,
+ foldl([fun explode2/4, X, K],
+ store_new({K, Name},
+ [L, Body],
+ Dict,
+ [K, Name, L],
+ already_declared),
+ Body);
+
+p1([_Line | Body], Dict, K)
+ when K == avp_types;
+ K == grouped;
+ K == messages ->
+ foldl([fun explode/3, K], Dict, Body).
+
+no_messages_without_id(Dict) ->
+ case find(messages, Dict) of
+ [] ->
+ true;
+ [[Line | _] | _] ->
+ [] /= find(id, Dict) orelse ?RETURN(messages_without_id, [Line])
+ end.
+
+%% Note that the AVP's in avp_vendor_id, custom_types, codecs and
+%% enum can all be inherited, as can the AVP content of messages and
+%% grouped AVP's. Check that the referenced AVP's exist after
+%% importing definitions.
+
+%% explode/4
+%%
+%% {avp_vendor_id, AvpName} -> [Lineno, Id::integer()]
+%% {custom_types|codecs|inherits, AvpName} -> [Lineno, Mod::string()]
+
+explode({_, Line, AvpName}, Dict, {_, _, X} = T, K) ->
+ true = K /= avp_vendor_id orelse is_uint32(T, [K]),
+ true = K /= inherits orelse avp_not_local(AvpName, Line, Dict),
+
+ store_new({key(K), AvpName},
+ [Line, X],
+ Dict,
+ [AvpName, Line, K],
+ avp_already_defined).
+
+%% explode2/4
+
+%% {define, {Name, Key}} -> [Lineno, Value::integer(), enum|define]
+
+explode2([{_, Line, Key}, {_, _, Value} = T], Dict, {_, _, Name}, K) ->
+ true = is_uint32(T, [K, Name]),
+
+ store_new({key(K), {Name, Key}},
+ [Line, Value, K],
+ Dict,
+ [Name, Key, K, Line],
+ key_already_defined).
+
+%% key/1
+%%
+%% Conflate keys that are equivalent as far as uniqueness of
+%% definition goes.
+
+key(K)
+ when K == enum;
+ K == define ->
+ define;
+key(K)
+ when K == custom_types;
+ K == codecs ->
+ custom;
+key(K) ->
+ K.
+
+%% explode/3
+
+%% {avp_types, AvpName} -> [Line | Toks]
+%% {avp_types, {Code, IsReq}} -> [Line, AvpName]
+%%
+%% where AvpName = string()
+%% Code = integer()
+%% IsReq = boolean()
+
+explode([{_, Line, Name} | Toks], Dict0, avp_types = K) ->
+ %% Each AVP can be defined only once.
+ Dict = store_new({K, Name},
+ [Line | Toks],
+ Dict0,
+ [Name, Line],
+ avp_name_already_defined),
+
+ [{number, _, _Code} = C, {word, _, Type}, {word, _, _Flags}] = Toks,
+
+ true = avp_type_known(Type, Name, Line),
+ true = is_uint32(C, [K, Name]),
+
+ Dict;
+
+%% {grouped, Name} -> [Line, HeaderTok | AvpToks]
+%% {grouped, {Name, AvpName}} -> [Line, Qual, Delim]
+%%
+%% where Name = string()
+%% AvpName = string()
+%% Qual = {Q, Q} | boolean()
+%% Q = true | NumberTok
+%% Delim = $< | ${ | $[
+
+explode([{_, Line, Name}, Header | Avps], Dict0, grouped = K) ->
+ Dict = store_new({K, Name},
+ [Line, Header | Avps],
+ Dict0,
+ [Name, Line],
+ group_already_defined),
+
+ [{_,_, Code} = C, Vid] = Header,
+ {DefLine, {_, _, Flags}} = grouped_flags(Name, Code, Dict0, Line),
+ V = lists:member($V, Flags),
+
+ true = is_uint32(C, [K, Name, "AVP code"]),
+ true = is_uint32(Vid, [K, Name, "vendor id"]),
+ false = vendor_id_mismatch(Vid, V, Name, Dict0, Line, DefLine),
+
+ explode_avps(Avps, Dict, K, Name);
+
+%% {messages, Name} -> [Line, HeaderTok | AvpToks]
+%% {messages, {Code, IsReq}} -> [Line, NameTok]
+%% {messages, Code} -> [[Line, NameTok, IsReq]]
+%% {messages, {Name, Flag}} -> [Line]
+%% {messages, {Name, AvpName}} -> [Line, Qual, Delim]
+%%
+%% where Name = string()
+%% Code = integer()
+%% IsReq = boolean()
+%% Flag = 'REQ' | 'PXY'
+%% AvpName = string()
+%% Qual = true | {Q,Q}
+%% Q = true | NumberTok
+%% Delim = $< | ${ | ${
+
+explode([{'answer-message' = A, Line}, false = H | Avps],
+ Dict0,
+ messages = K) ->
+ Name = ?L(A),
+ Dict1 = store_new({K, Name},
+ [Line, H, Avps],
+ Dict0,
+ [Name, Line],
+ message_name_already_defined),
+
+ explode_avps(Avps, Dict1, K, Name);
+
+explode([{_, Line, MsgName} = M, Header | Avps],
+ Dict0,
+ messages = K) ->
+ %% There can be at most one message with a given name.
+ Dict1 = store_new({K, MsgName},
+ [Line, Header | Avps],
+ Dict0,
+ [MsgName, Line],
+ message_name_already_defined),
+
+ [{_, _, Code} = C, Bits, ApplId] = Header,
+
+ %% Don't check any application id since it's required to be
+ %% the same as @id.
+ true = is_uint32(C, [K, MsgName]),
+
+ %% An application id specified as part of the message definition
+ %% has to agree with @id. The former is parsed just because RFC
+ %% 3588 specifies it.
+ false = application_id_mismatch(ApplId, Dict1, MsgName),
+
+ IsReq = lists:keymember('REQ', 1, Bits),
+
+ %% For each command code, there can be at most one request and
+ %% one answer.
+ Dict2 = store_new({K, {Code, IsReq}},
+ [Line, M],
+ Dict1,
+ [choose(IsReq, "Request", "Answer"), Code, Line],
+ message_code_already_defined),
+
+ %% For each message, each flag can occur at most once.
+ Dict3 = foldl(fun({F,L},D) ->
+ store_new({K, {MsgName, F}},
+ [L],
+ D,
+ [MsgName, ?L(F)],
+ message_has_duplicate_flag)
+ end,
+ Dict2,
+ Bits),
+
+ dict:append({K, Code},
+ [Line, M, IsReq],
+ explode_avps(Avps, Dict3, K, MsgName)).
+
+%% explode_avps/4
+%%
+%% Ensure required AVP order and sane qualifiers. Can't check for AVP
+%% names until after they've been imported.
+%%
+%% RFC 3588 allows a trailing fixed while 3588bis doesn't. Parse the
+%% former.
+
+explode_avps(Avps, Dict, Key, Name) ->
+ xa("<{[<", Avps, Dict, Key, Name).
+
+xa(_, [], Dict, _, _) ->
+ Dict;
+
+xa(Ds, [[Qual, D, {'AVP', Line}] | Avps], Dict, Key, Name) ->
+ xa(Ds, [[Qual, D, {word, Line, "AVP"}] | Avps], Dict, Key, Name);
+
+xa([], [[_Qual, D, {_, Line, Name}] | _], _, _, _) ->
+ ?RETURN(invalid_avp_order, [D, Name, close(D), Line]);
+
+xa([D|_] = Ds, [[Qual, D, {_, Line, AvpName}] | Avps], Dict, Key, Name) ->
+ xa(Ds,
+ Avps,
+ store_new({Key, {Name, AvpName}},
+ [Line, Qual, D],
+ Dict,
+ [Name, Line],
+ avp_already_referenced),
+ Key,
+ Name);
+
+xa([_|Ds], Avps, Dict, Key, Name) ->
+ xa(Ds, Avps, Dict, Key, Name).
+
+close($<) -> $>;
+close(${) -> $};
+close($[) -> $].
+
+%% is_uint32/2
+
+is_uint32(false, _) ->
+ true;
+is_uint32({Line, _, N}, Args) ->
+ N < 1 bsl 32 orelse ?RETURN(uint32_out_of_range, Args ++ [N, Line]).
+%% Can't call diameter_types here since it may not exist yet.
+
+%% application_id_mismatch/3
+
+application_id_mismatch({number, Line, Id}, Dict, MsgName) ->
+ [[_, {_, L, I}]] = dict:fetch(id, Dict),
+
+ I /= Id andalso ?RETURN(message_application_id_mismatch,
+ [MsgName, Id, Line, I, L]);
+
+application_id_mismatch(false = No, _, _) ->
+ No.
+
+%% avp_not_local/3
+
+avp_not_local(Name, Line, Dict) ->
+ A = find({avp_types, Name}, Dict),
+
+ [] == A orelse ?RETURN(inherited_avp_already_defined,
+ [Name, Line, hd(A)]).
+
+%% avp_type_known/3
+
+avp_type_known(Type, Name, Line) ->
+ false /= type(Type)
+ orelse ?RETURN(avp_has_unknown_type, [Name, Line, Type]).
+
+%% vendor_id_mismatch/6
+%%
+%% Require a vendor id specified on a group to match any specified
+%% in @avp_vendor_id. Note that both locations for the value are
+%% equivalent, both in the value being attributed to a locally
+%% defined AVP and ignored when imported from another dictionary.
+
+vendor_id_mismatch({_,_,_}, false, Name, _, Line, DefLine) ->
+ ?RETURN(grouped_vendor_id_without_flag, [Name, Line, DefLine]);
+
+vendor_id_mismatch({_, _, I}, true, Name, Dict, Line, _) ->
+ case vendor_id(Name, Dict) of
+ {avp_vendor_id, L, N} ->
+ I /= N andalso
+ ?RETURN(grouped_vendor_id_mismatch, [Name, Line, I, N, L]);
+ _ ->
+ false
+ end;
+
+vendor_id_mismatch(_, _, _, _, _, _) ->
+ false.
+
+%% grouped_flags/4
+
+grouped_flags(Name, Code, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [L, {_, _, Code}, {_, _, "Grouped"}, Flags] ->
+ {L, Flags};
+ [_, {_, L, C}, {_, _, "Grouped"}, _Flags] ->
+ ?RETURN(grouped_avp_code_mismatch, [Name, Line, Code, C, L]);
+ [_, _Code, {_, L, T}, _] ->
+ ?RETURN(grouped_avp_has_wrong_type, [Name, Line, T, L]);
+ [] ->
+ ?RETURN(grouped_avp_not_defined, [Name, Line])
+ end.
+
+%% vendor_id/2
+
+%% Look for a vendor id in @avp_vendor_id, then @vendor.
+vendor_id(Name, Dict) ->
+ case find({avp_vendor_id, Name}, Dict) of
+ [Line, Id] when is_integer(Id) ->
+ {avp_vendor_id, Line, Id};
+ [] ->
+ vendor(Dict)
+ end.
+
+vendor(Dict) ->
+ case find(vendor, Dict) of
+ [[_Line, {_, _, Id}, {_, _, _}]] ->
+ {vendor, Id};
+ [] ->
+ false
+ end.
+
+%% find/2
+
+find(Key, Dict) ->
+ case dict:find(Key, Dict) of
+ {ok, L} when is_list(L) ->
+ L;
+ error ->
+ []
+ end.
+
+%% store_new/5
+
+store_new(Key, Value, Dict, Args, Err) ->
+ case dict:find(Key, Dict) of
+ {ok, [L | _]} ->
+ ?RETURN(Err, Args ++ [L]);
+ error ->
+ dict:store(Key, Value, Dict)
+ end.
+
+%% type/1
+
+type("DiamIdent") ->
+ "DiameterIdentity";
+type("DiamURI") ->
+ "DiameterURI";
+type(T)
+ when T == "OctetString";
+ T == "Integer32";
+ T == "Integer64";
+ T == "Unsigned32";
+ T == "Unsigned64";
+ T == "Float32";
+ T == "Float64";
+ T == "Grouped";
+ T == "Enumerated";
+ T == "Address";
+ T == "Time";
+ T == "UTF8String";
+ T == "DiameterIdentity";
+ T == "DiameterURI";
+ T == "IPFilterRule";
+ T == "QoSFilterRule" ->
+ T;
+type(_) ->
+ false.
+
+%% ===========================================================================
+%% pass2/1
+%%
+%% More explosion, but that requires the previous pass to write its
+%% entries.
+
+pass2(Dict) ->
+ foldl(fun(K,D) -> foldl([fun p2/3, K], D, find(K,D)) end,
+ Dict,
+ [avp_types]).
+
+p2([_Line | Body], Dict, avp_types) ->
+ foldl(fun explode_avps/2, Dict, Body);
+
+p2([], Dict, _) ->
+ Dict.
+
+explode_avps([{_, Line, Name} | Toks], Dict) ->
+ [{number, _, Code}, {word, _, _Type}, {word, _, Flags}] = Toks,
+
+ true = avp_flags_valid(Flags, Name, Line),
+
+ Vid = avp_vendor_id(Flags, Name, Line, Dict),
+
+ %% An AVP is uniquely defined by its AVP code and vendor id (if any).
+ %% Ensure there are no duplicate.
+ store_new({avp_types, {Code, Vid}},
+ [Line, Name],
+ Dict,
+ [Code, Vid, Name, Line],
+ avp_code_already_defined).
+
+%% avp_flags_valid/3
+
+avp_flags_valid(Flags, Name, Line) ->
+ Bad = lists:filter(fun(C) -> not lists:member(C, "MVP") end, Flags),
+ [] == Bad
+ orelse ?RETURN(avp_has_invalid_flag, [Name, Line, hd(Bad)]),
+
+ Dup = Flags -- "MVP",
+ [] == Dup
+ orelse ?RETURN(avp_has_duplicate_flag, [Name, Line, hd(Dup)]).
+
+%% avp_vendor_id/4
+
+avp_vendor_id(Flags, Name, Line, Dict) ->
+ V = lists:member($V, Flags),
+
+ case vendor_id(Name, Dict) of
+ {avp_vendor_id, _, I} when V ->
+ I;
+ {avp_vendor_id, L, I} ->
+ ?RETURN(avp_has_vendor_id, [Name, Line, I, L]);
+ {vendor, I} when V ->
+ I;
+ false when V ->
+ ?RETURN(avp_has_no_vendor, [Name, Line]);
+ _ ->
+ false
+ end.
+
+%% ===========================================================================
+%% pass3/2
+%%
+%% Import AVPs.
+
+pass3(Dict, Opts) ->
+ import_enums(import_groups(import_avps(insert_codes(Dict), Opts))).
+
+%% insert_codes/1
+%%
+%% command_codes -> [{Code, ReqNameTok, AnsNameTok}]
+
+insert_codes(Dict) ->
+ dict:store(command_codes,
+ dict:fold(fun make_code/3, [], Dict),
+ Dict).
+
+make_code({messages, Code}, Names, Acc)
+ when is_integer(Code) ->
+ [mk_code(Code, Names) | Acc];
+make_code(_, _, Acc) ->
+ Acc.
+
+mk_code(Code, [[_, _, false] = Ans, [_, _, true] = Req]) ->
+ mk_code(Code, [Req, Ans]);
+
+mk_code(Code, [[_, {_,_,Req}, true], [_, {_,_,Ans}, false]]) ->
+ {Code, Req, Ans};
+
+mk_code(_Code, [[Line, _Name, IsReq]]) ->
+ ?RETURN(message_missing, [choose(IsReq, "Request", "Answer"),
+ Line,
+ choose(IsReq, "answer", "request")]).
+
+%% import_avps/2
+
+import_avps(Dict, Opts) ->
+ Import = inherit(Dict, Opts),
+ report(imported, Import),
+
+ %% pass4/1 tests that all referenced AVP's are either defined
+ %% or imported.
+
+ dict:store(import_avps,
+ lists:map(fun({M, _, As}) -> {M, [A || {_,A} <- As]} end,
+ lists:reverse(Import)),
+ foldl(fun explode_imports/2, Dict, Import)).
+
+explode_imports({Mod, Line, Avps}, Dict) ->
+ foldl([fun xi/4, Mod, Line], Dict, Avps).
+
+xi({L, {Name, _Code, _Type, _Flags} = A}, Dict, Mod, Line) ->
+ store_new({avp_types, Name},
+ [0, Mod, Line, L, A],
+ store_new({import, Name},
+ [Line],
+ Dict,
+ [Name, Line],
+ duplicate_import),
+ [Name, Mod, Line],
+ imported_avp_already_defined).
+
+%% import_groups/1
+%% import_enums/1
+%%
+%% For each inherited module, store the content of imported AVP's of
+%% type grouped/enumerated in a new key.
+
+import_groups(Dict) ->
+ dict:store(import_groups, import(grouped, Dict), Dict).
+
+import_enums(Dict) ->
+ dict:store(import_enums, import(enum, Dict), Dict).
+
+import(Key, Dict) ->
+ flatmap([fun import_key/2, Key], dict:fetch(import_avps, Dict)).
+
+import_key({Mod, Avps}, Key) ->
+ As = lists:flatmap(fun(T) ->
+ N = element(1,T),
+ choose(lists:keymember(N, 1, Avps), [T], [])
+ end,
+ orddict:fetch(Key, dict(Mod))),
+ if As == [] ->
+ [];
+ true ->
+ [{Mod, As}]
+ end.
+
+%% ------------------------------------------------------------------------
+%% inherit/2
+%%
+%% Return a {Mod, Line, [{Lineno, Avp}]} list, where Mod is a module
+%% name, Line points to the corresponding @inherit and each Avp is
+%% from Mod:dict(). Lineno is 0 if the import is implicit.
+
+inherit(Dict, Opts) ->
+ code:add_pathsa([D || {include, D} <- Opts]),
+ foldl(fun inherit_avps/2, [], find(inherits, Dict)).
+%% Note that the module order of the returned lists is reversed
+%% relative to @inherits.
+
+inherit_avps([Line, {_,_,M} | Names], Acc) ->
+ Mod = ?A(M),
+ report(inherit_from, Mod),
+ case find_avps(Names, avps_from_module(Mod)) of
+ {_, [{_, L, N} | _]} ->
+ ?RETURN(requested_avp_not_found, [Mod, Line, N, L]);
+ {Found, []} ->
+ [{Mod, Line, lists:sort(Found)} | Acc]
+ end.
+
+%% Import everything not defined locally ...
+find_avps([], Avps) ->
+ {[{0, A} || A <- Avps], []};
+
+%% ... or specified AVPs.
+find_avps(Names, Avps) ->
+ foldl(fun acc_avp/2, {[], Names}, Avps).
+
+acc_avp({Name, _Code, _Type, _Flags} = A, {Found, Not} = Acc) ->
+ case lists:keyfind(Name, 3, Not) of
+ {_, Line, Name} ->
+ {[{Line, A} | Found], lists:keydelete(Name, 3, Not)};
+ false ->
+ Acc
+ end.
+
+%% avps_from_module/2
+
+avps_from_module(Mod) ->
+ orddict:fetch(avp_types, dict(Mod)).
+
+dict(Mod) ->
+ try Mod:dict() of
+ [?VERSION | Dict] ->
+ Dict;
+ _ ->
+ ?RETURN(recompile, [Mod])
+ catch
+ error: _ ->
+ ?RETURN(choose(false == code:is_loaded(Mod),
+ not_loaded,
+ no_dict),
+ [Mod])
+ end.
+
+%% ===========================================================================
+%% pass4/1
+%%
+%% Sanity checks.
+
+pass4(Dict) ->
+ dict:fold(fun(K, V, _) -> p4(K, V, Dict) end, ok, Dict),
+ Dict.
+
+%% Ensure enum AVP's have type Enumerated.
+p4({enum, Name}, [Line | _], Dict)
+ when is_list(Name) ->
+ true = is_enumerated_avp(Name, Dict, Line);
+
+%% Ensure all referenced AVP's are either defined locally or imported.
+p4({K, {Name, AvpName}}, [Line | _], Dict)
+ when (K == grouped orelse K == messages),
+ is_list(Name),
+ is_list(AvpName),
+ AvpName /= "AVP" ->
+ true = avp_is_defined(AvpName, Dict, Line);
+
+%% Ditto.
+p4({K, AvpName}, [Line | _], Dict)
+ when K == avp_vendor_id;
+ K == custom_types;
+ K == codecs ->
+ true = avp_is_defined(AvpName, Dict, Line);
+
+p4(_, _, _) ->
+ ok.
+
+%% has_enumerated_type/3
+
+is_enumerated_avp(Name, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [_Line, _Code, {_, _, "Enumerated"}, _Flags] -> %% local
+ true;
+ [_Line, _Code, {_, L, T}, _] ->
+ ?RETURN(enumerated_avp_has_wrong_local_type,
+ [Name, Line, T, L]);
+ [0, _, _, _, {_Name, _Code, "Enumerated", _Flags}] -> %% inherited
+ true;
+ [0, Mod, LM, LA, {_Name, _Code, Type, _Flags}] ->
+ ?RETURN(enumerated_avp_has_wrong_inherited_type,
+ [Name, Line, Type, Mod, choose(0 == LA, LM, LA)]);
+ [] ->
+ ?RETURN(enumerated_avp_not_defined, [Name, Line])
+ end.
+
+avp_is_defined(Name, Dict, Line) ->
+ case find({avp_types, Name}, Dict) of
+ [_Line, _Code, _Type, _Flags] -> %% local
+ true;
+ [0, _, _, _, {Name, _Code, _Type, _Flags}] -> %% inherited
+ true;
+ [] ->
+ ?RETURN(avp_not_defined, [Name, Line])
+ end.
+
+%% ===========================================================================
+
+putr(Key, Value) ->
+ put({?MODULE, Key}, Value).
+
+getr(Key) ->
+ get({?MODULE, Key}).
+
+eraser(Key) ->
+ erase({?MODULE, Key}).
+
+choose(true, X, _) -> X;
+choose(false, _, X) -> X.
+
+foldl(F, Acc, List) ->
+ lists:foldl(fun(T,A) -> eval([F,T,A]) end, Acc, List).
+
+flatmap(F, List) ->
+ lists:flatmap(fun(T) -> eval([F,T]) end, List).
+
+eval([[F|X] | A]) ->
+ eval([F | A ++ X]);
+eval([F|A]) ->
+ apply(F,A).
diff --git a/lib/diameter/src/app/diameter_exprecs.erl b/lib/diameter/src/compiler/diameter_exprecs.erl
index 5e120d6f44..191f53f29d 100644
--- a/lib/diameter/src/app/diameter_exprecs.erl
+++ b/lib/diameter/src/compiler/diameter_exprecs.erl
@@ -96,41 +96,15 @@
-export([parse_transform/2]).
-%% Form tag with line number.
--define(F(T), T, ?LINE).
-%% Yes, that's right. The replacement is to the first unmatched ')'.
-
--define(attribute, ?F(attribute)).
--define(clause, ?F(clause)).
--define(function, ?F(function)).
--define(call, ?F(call)).
--define('fun', ?F('fun')).
--define(generate, ?F(generate)).
--define(lc, ?F(lc)).
--define(match, ?F(match)).
--define(remote, ?F(remote)).
--define(record, ?F(record)).
--define(record_field, ?F(record_field)).
--define(record_index, ?F(record_index)).
--define(tuple, ?F(tuple)).
-
--define(ATOM(T), {atom, ?LINE, T}).
--define(VAR(V), {var, ?LINE, V}).
-
--define(CALL(F,A), {?call, ?ATOM(F), A}).
--define(APPLY(M,F,A), {?call, {?remote, ?ATOM(M), ?ATOM(F)}, A}).
+-include("diameter_forms.hrl").
%% parse_transform/2
parse_transform(Forms, _Options) ->
Rs = [R || {attribute, _, record, R} <- Forms],
- case lists:append([E || {attribute, _, export_records, E} <- Forms]) of
- [] ->
- Forms;
- Es ->
- {H,T} = lists:splitwith(fun is_head/1, Forms),
- H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T
- end.
+ Es = lists:append([E || {attribute, _, export_records, E} <- Forms]),
+ {H,T} = lists:splitwith(fun is_head/1, Forms),
+ H ++ [a_export(Es) | f_accessors(Es, Rs)] ++ T.
is_head(T) ->
not lists:member(element(1,T), [function, eof]).
@@ -200,7 +174,7 @@ fname(Op, Rname) ->
'#info-/2'(Exports) ->
{?function, fname(info), 2,
- lists:map(fun 'info-'/1, Exports)}.
+ lists:map(fun 'info-'/1, Exports) ++ [?BADARG(2)]}.
'info-'(R) ->
{?clause, [?ATOM(R), ?VAR('Info')],
@@ -209,7 +183,7 @@ fname(Op, Rname) ->
'#new-/1'(Exports) ->
{?function, fname(new), 1,
- lists:map(fun 'new-'/1, Exports)}.
+ lists:map(fun 'new-'/1, Exports) ++ [?BADARG(1)]}.
'new-'(R) ->
{?clause, [?ATOM(R)],
@@ -218,7 +192,7 @@ fname(Op, Rname) ->
'#new-/2'(Exports) ->
{?function, fname(new), 2,
- lists:map(fun 'new--'/1, Exports)}.
+ lists:map(fun 'new--'/1, Exports) ++ [?BADARG(2)]}.
'new--'(R) ->
{?clause, [?ATOM(R), ?VAR('Vals')],
@@ -227,7 +201,7 @@ fname(Op, Rname) ->
'#get-/2'(Exports) ->
{?function, fname(get), 2,
- lists:map(fun 'get-'/1, Exports)}.
+ lists:map(fun 'get-'/1, Exports) ++ [?BADARG(2)]}.
'get-'(R) ->
{?clause, [?VAR('Attrs'),
@@ -237,7 +211,7 @@ fname(Op, Rname) ->
'#set-/2'(Exports) ->
{?function, fname(set), 2,
- lists:map(fun 'set-'/1, Exports)}.
+ lists:map(fun 'set-'/1, Exports) ++ [?BADARG(2)]}.
'set-'(R) ->
{?clause, [?VAR('Vals'), {?match, {?record, R, []}, ?VAR('Rec')}],
diff --git a/lib/diameter/src/compiler/diameter_forms.hrl b/lib/diameter/src/compiler/diameter_forms.hrl
index d93131df34..4cd86c32aa 100644
--- a/lib/diameter/src/compiler/diameter_forms.hrl
+++ b/lib/diameter/src/compiler/diameter_forms.hrl
@@ -21,6 +21,13 @@
%% Macros used when building abstract code.
%%
+%% Generated functions that could have no generated clauses will have
+%% a trailing ?BADARG clause that should never execute as called
+%% by diameter.
+-define(BADARG(N), {?clause, [?VAR('_') || _ <- lists:seq(1,N)],
+ [],
+ [?APPLY(erlang, error, [?ATOM(badarg)])]}).
+
%% Form tag with line number.
-define(F(T), T, ?LINE).
%% Yes, that's right. The replacement is to the first unmatched ')'.
diff --git a/lib/diameter/src/compiler/diameter_make.erl b/lib/diameter/src/compiler/diameter_make.erl
index 4431b88c4d..16e30c1ffb 100644
--- a/lib/diameter/src/compiler/diameter_make.erl
+++ b/lib/diameter/src/compiler/diameter_make.erl
@@ -18,103 +18,115 @@
%%
%%
-%% Driver for the encoder generator utility.
+%% Module alternative to diameterc for dictionary compilation.
+%%
+%% Eg. 1> diameter_make:codec("mydict.dia").
+%%
+%% $ erl -noinput \
+%% -boot start_clean \
+%% -eval 'ok = diameter_make:codec("mydict.dia")' \
+%% -s init stop
%%
-module(diameter_make).
--export([spec/0,
- hrl/0,
- erl/0]).
+-export([codec/1,
+ codec/2,
+ dict/1,
+ dict/2,
+ format/1,
+ reformat/1]).
--spec spec() -> no_return().
--spec hrl() -> no_return().
--spec erl() -> no_return().
+-export_type([opt/0]).
-spec() ->
- make(spec).
+-type opt() :: {include|outdir|name|prefix|inherits, string()}
+ | verbose
+ | debug.
-hrl() ->
- make(hrl).
+%% ===========================================================================
-erl() ->
- make(erl).
+%% codec/1-2
+%%
+%% Parse a dictionary file and generate a codec module.
+
+-spec codec(Path, [opt()])
+ -> ok
+ | {error, Reason}
+ when Path :: string(),
+ Reason :: string().
+
+codec(File, Opts) ->
+ case dict(File, Opts) of
+ {ok, Dict} ->
+ make(File,
+ Opts,
+ Dict,
+ [spec || _ <- [1], lists:member(debug, Opts)] ++ [erl, hrl]);
+ {error, _} = E ->
+ E
+ end.
-%% make/1
+codec(File) ->
+ codec(File, []).
-make(Mode) ->
- Args = init:get_plain_arguments(),
- Opts = try options(Args) catch throw: help -> help(Mode) end,
- Files = proplists:get_value(files, Opts, []),
- lists:foreach(fun(F) -> from_file(F, Mode, Opts) end, Files),
- halt(0).
+%% dict/2
+%%
+%% Parse a dictionary file and return the orddict that a codec module
+%% returns from dict/0.
+
+-spec dict(string(), [opt()])
+ -> {ok, orddict:orddict()}
+ | {error, string()}.
+
+dict(Path, Opts) ->
+ case diameter_dict_util:parse({path, Path}, Opts) of
+ {ok, _} = Ok ->
+ Ok;
+ {error = E, Reason} ->
+ {E, diameter_dict_util:format_error(Reason)}
+ end.
-%% from_file/3
+dict(File) ->
+ dict(File, []).
-from_file(F, Mode, Opts) ->
- try to_spec(F, Mode, Opts) of
- Spec ->
- from_spec(F, Spec, Mode, Opts)
- catch
- error: Reason ->
- io:format("==> ~p parse failure:~n~p~n",
- [F, {Reason, erlang:get_stacktrace()}]),
- halt(1)
- end.
+%% format/1
+%%
+%% Turn an orddict returned by dict/1-2 back into a dictionary file
+%% in the form of an iolist().
-%% to_spec/2
+-spec format(orddict:orddict())
+ -> iolist().
-%% Try to read the input as an already parsed file or else parse it.
-to_spec(F, spec, Opts) ->
- diameter_spec_util:parse(F, Opts);
-to_spec(F, _, _) ->
- {ok, [Spec]} = file:consult(F),
- Spec.
+format(Dict) ->
+ diameter_dict_util:format(Dict).
-%% from_spec/4
+%% reformat/1
+%%
+%% Parse a dictionary file and return its formatted equivalent.
+
+-spec reformat(File)
+ -> {ok, iolist()}
+ | {error, Reason}
+ when File :: string(),
+ Reason :: string().
+
+reformat(File) ->
+ case dict(File) of
+ {ok, Dict} ->
+ {ok, format(Dict)};
+ {error, _} = No ->
+ No
+ end.
-from_spec(File, Spec, Mode, Opts) ->
+%% ===========================================================================
+
+make(_, _, _, []) ->
+ ok;
+make(File, Opts, Dict, [Mode | Rest]) ->
try
- diameter_codegen:from_spec(File, Spec, Opts, Mode)
+ ok = diameter_codegen:from_dict(File, Dict, Opts, Mode),
+ make(File, Opts, Dict, Rest)
catch
error: Reason ->
- io:format("==> ~p codegen failure:~n~p~n~p~n",
- [Mode, File, {Reason, erlang:get_stacktrace()}]),
- halt(1)
+ erlang:error({Reason, Mode, erlang:get_stacktrace()})
end.
-
-%% options/1
-
-options(["-v" | Rest]) ->
- [verbose | options(Rest)];
-
-options(["-o", Outdir | Rest]) ->
- [{outdir, Outdir} | options(Rest)];
-
-options(["-i", Incdir | Rest]) ->
- [{include, Incdir} | options(Rest)];
-
-options(["-h" | _]) ->
- throw(help);
-
-options(["--" | Fs]) ->
- [{files, Fs}];
-
-options(["-" ++ _ = Opt | _]) ->
- io:fwrite("==> unknown option: ~s~n", [Opt]),
- throw(help);
-
-options(Fs) -> %% trailing arguments
- options(["--" | Fs]).
-
-%% help/1
-
-help(M) ->
- io:fwrite("Usage: ~p ~p [Options] [--] File ...~n"
- "Options:~n"
- " -v verbose output~n"
- " -h shows this help message~n"
- " -o OutDir where to put the output files~n"
- " -i IncludeDir where to search for beams to import~n",
- [?MODULE, M]),
- halt(1).
diff --git a/lib/diameter/src/compiler/diameter_nowarn.erl b/lib/diameter/src/compiler/diameter_nowarn.erl
new file mode 100644
index 0000000000..6c17af6563
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_nowarn.erl
@@ -0,0 +1,41 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%%
+%% A parse transform to work around dialyzer currently not
+%% understanding nowarn_unused_function except on individual
+%% functions. The include of diameter_gen.hrl by generated dictionary
+%% modules contains code that may not be called depending on the
+%% dictionary. (The relay dictionary for example.)
+%%
+%% Even called functions may contain cases that aren't used for a
+%% particular dictionary. This also causes dialyzer to complain but
+%% there's no way to silence it in this case.
+%%
+
+-module(diameter_nowarn).
+
+-export([parse_transform/2]).
+
+parse_transform(Forms, _Options) ->
+ [{attribute, ?LINE, compile, {nowarn_unused_function, {F,A}}}
+ || {function, _, F, A, _} <- Forms]
+ ++ Forms.
+%% Note that dialyzer also doesn't understand {nowarn_unused_function, FAs}
+%% with FAs a list of tuples.
diff --git a/lib/diameter/src/compiler/diameter_spec_scan.erl b/lib/diameter/src/compiler/diameter_spec_scan.erl
deleted file mode 100644
index bc0448882a..0000000000
--- a/lib/diameter/src/compiler/diameter_spec_scan.erl
+++ /dev/null
@@ -1,157 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
--module(diameter_spec_scan).
-
-%%
-%% Functions used by the spec file parser in diameter_spec_util.
-%%
-
--export([split/1,
- split/2,
- parse/1]).
-
-%%% -----------------------------------------------------------
-%%% # parse/1
-%%%
-%%% Output: list of Token
-%%%
-%%% Token = '{' | '}' | '<' | '>' | '[' | ']'
-%%% | '*' | '::=' | ':' | ',' | '-'
-%%% | {name, string()}
-%%% | {tag, atom()}
-%%% | {number, integer() >= 0}
-%%%
-%%% Tokenize a string. Fails if the string does not parse.
-%%% -----------------------------------------------------------
-
-parse(S) ->
- parse(S, []).
-
-%% parse/2
-
-parse(S, Acc) ->
- acc(split(S), Acc).
-
-acc({T, Rest}, Acc) ->
- parse(Rest, [T | Acc]);
-acc("", Acc) ->
- lists:reverse(Acc).
-
-%%% -----------------------------------------------------------
-%%% # split/2
-%%%
-%%% Output: {list() of Token, Rest}
-%%%
-%%% Extract a specified number of tokens from a string. Returns a list
-%%% of length less than the specified number if there are less than
-%%% this number of tokens to be parsed.
-%%% -----------------------------------------------------------
-
-split(Str, N)
- when N >= 0 ->
- split(N, Str, []).
-
-split(0, Str, Acc) ->
- {lists:reverse(Acc), Str};
-
-split(N, Str, Acc) ->
- case split(Str) of
- {T, Rest} ->
- split(N-1, Rest, [T|Acc]);
- "" = Rest ->
- {lists:reverse(Acc), Rest}
- end.
-
-%%% -----------------------------------------------------------
-%%% # split/1
-%%%
-%%% Output: {Token, Rest} | ""
-%%%
-%%% Extract the next token from a string.
-%%% -----------------------------------------------------------
-
-split("" = Rest) ->
- Rest;
-
-split("::=" ++ T) ->
- {'::=', T};
-
-split([H|T])
- when H == ${; H == $};
- H == $<; H == $>;
- H == $[; H == $];
- H == $*; H == $:; H == $,; H == $- ->
- {list_to_atom([H]), T};
-
-split([H|T]) when $A =< H, H =< $Z;
- $0 =< H, H =< $9 ->
- {P, Rest} = splitwith(fun is_name_ch/1, [H], T),
- Tok = try
- {number, read_int(P)}
- catch
- error:_ ->
- {name, P}
- end,
- {Tok, Rest};
-
-split([H|T]) when $a =< H, H =< $z ->
- {P, Rest} = splitwith(fun is_name_ch/1, [H], T),
- {{tag, list_to_atom(P)}, Rest};
-
-split([H|T]) when H == $\t;
- H == $\s;
- H == $\n ->
- split(T).
-
-%% read_int/1
-
-read_int([$0,X|S])
- when X == $X;
- X == $x ->
- {ok, [N], []} = io_lib:fread("~16u", S),
- N;
-
-read_int(S) ->
- list_to_integer(S).
-
-%% splitwith/3
-
-splitwith(Fun, Acc, S) ->
- split([] /= S andalso Fun(hd(S)), Fun, Acc, S).
-
-split(true, Fun, Acc, [H|T]) ->
- splitwith(Fun, [H|Acc], T);
-split(false, _, Acc, S) ->
- {lists:reverse(Acc), S}.
-
-is_name_ch(C) ->
- is_alphanum(C) orelse C == $- orelse C == $_.
-
-is_alphanum(C) ->
- is_lower(C) orelse is_upper(C) orelse is_digit(C).
-
-is_lower(C) ->
- $a =< C andalso C =< $z.
-
-is_upper(C) ->
- $A =< C andalso C =< $Z.
-
-is_digit(C) ->
- $0 =< C andalso C =< $9.
diff --git a/lib/diameter/src/compiler/diameter_spec_util.erl b/lib/diameter/src/compiler/diameter_spec_util.erl
deleted file mode 100644
index b60886b678..0000000000
--- a/lib/diameter/src/compiler/diameter_spec_util.erl
+++ /dev/null
@@ -1,1068 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-
-%%
-%% This module turns a .dia (aka spec) file into the orddict that
-%% diameter_codegen.erl in turn morphs into .erl and .hrl files for
-%% encode and decode of Diameter messages and AVPs.
-%%
-
--module(diameter_spec_util).
-
--export([parse/2]).
-
--define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
--define(ATOM, list_to_atom).
-
-%% parse/1
-%%
-%% Output: orddict()
-
-parse(Path, Options) ->
- put({?MODULE, verbose}, lists:member(verbose, Options)),
- {ok, B} = file:read_file(Path),
- Chunks = chunk(B),
- Spec = make_spec(Chunks),
- true = groups_defined(Spec), %% sanity checks
- true = customs_defined(Spec), %%
- Full = import_enums(import_groups(import_avps(insert_codes(Spec),
- Options))),
- true = enums_defined(Full), %% sanity checks
- true = v_flags_set(Spec),
- Full.
-
-%% Optional reports when running verbosely.
-report(What, Data) ->
- report(get({?MODULE, verbose}), What, Data).
-
-report(true, Tag, Data) ->
- io:format("##~n## ~p ~p~n", [Tag, Data]);
-report(false, _, _) ->
- ok.
-
-%% chunk/1
-
-chunk(B) ->
- chunkify(normalize(binary_to_list(B))).
-
-%% normalize/1
-%%
-%% Replace CR NL by NL, multiple NL by one, tab by space, and strip
-%% comments and leading/trailing space from each line. Precludes
-%% semicolons being used for any other purpose than comments.
-
-normalize(Str) ->
- nh(Str, []).
-
-nh([], Acc) ->
- lists:reverse(Acc);
-
-%% Trim leading whitespace.
-nh(Str, Acc) ->
- nb(trim(Str), Acc).
-
-%% tab -> space
-nb([$\t|Rest], Acc) ->
- nb(Rest, [$\s|Acc]);
-
-%% CR NL -> NL
-nb([$\r,$\n|Rest], Acc) ->
- nt(Rest, Acc);
-
-%% Gobble multiple newlines before starting over again.
-nb([$\n|Rest], Acc) ->
- nt(Rest, Acc);
-
-%% Comment.
-nb([$;|Rest], Acc) ->
- nb(lists:dropwhile(fun(C) -> C /= $\n end, Rest), Acc);
-
-%% Just an ordinary character. Boring ...
-nb([C|Rest], Acc) ->
- nb(Rest, [C|Acc]);
-
-nb([] = Str, Acc) ->
- nt(Str, Acc).
-
-%% Discard a subsequent newline.
-nt(T, [$\n|_] = Acc) ->
- nh(T, trim(Acc));
-
-%% Trim whitespace from the end of the line before continuing.
-nt(T, Acc) ->
- nh(T, [$\n|trim(Acc)]).
-
-trim(S) ->
- lists:dropwhile(fun(C) -> lists:member(C, "\s\t") end, S).
-
-%% chunkify/1
-%%
-%% Split the spec file into pieces delimited by lines starting with
-%% @Tag. Returns a list of {Tag, Args, Chunk} where Chunk is the
-%% string extending to the next delimiter. Note that leading
-%% whitespace has already been stripped.
-
-chunkify(Str) ->
- %% Drop characters to the start of the first chunk.
- {_, Rest} = split_chunk([$\n|Str]),
- chunkify(Rest, []).
-
-chunkify([], Acc) ->
- lists:reverse(Acc);
-
-chunkify(Rest, Acc) ->
- {H,T} = split_chunk(Rest),
- chunkify(T, [split_tag(H) | Acc]).
-
-split_chunk(Str) ->
- split_chunk(Str, []).
-
-split_chunk([] = Rest, Acc) ->
- {lists:reverse(Acc), Rest};
-split_chunk([$@|Rest], [$\n|_] = Acc) ->
- {lists:reverse(Acc), Rest};
-split_chunk([C|Rest], Acc) ->
- split_chunk(Rest, [C|Acc]).
-
-%% Expect a tag and its arguments on a single line.
-split_tag(Str) ->
- {L, Rest} = get_until($\n, Str),
- [{tag, Tag} | Toks] = diameter_spec_scan:parse(L),
- {Tag, Toks, trim(Rest)}.
-
-get_until(EndT, L) ->
- {H, [EndT | T]} = lists:splitwith(fun(C) -> C =/= EndT end, L),
- {H,T}.
-
-%% ------------------------------------------------------------------------
-%% make_spec/1
-%%
-%% Turn chunks into spec.
-
-make_spec(Chunks) ->
- lists:foldl(fun(T,A) -> report(chunk, T), chunk(T,A) end,
- orddict:new(),
- Chunks).
-
-chunk({T, [X], []}, Dict)
- when T == name;
- T == prefix ->
- store(T, atomize(X), Dict);
-
-chunk({id = T, [{number, I}], []}, Dict) ->
- store(T, I, Dict);
-
-chunk({vendor = T, [{number, I}, N], []}, Dict) ->
- store(T, {I, atomize(N)}, Dict);
-
-%% inherits -> [{Mod, [AvpName, ...]}, ...]
-chunk({inherits = T, [_,_|_] = Args, []}, Acc) ->
- Mods = [atomize(A) || A <- Args],
- append_list(T, [{M,[]} || M <- Mods], Acc);
-chunk({inherits = T, [Mod], Body}, Acc) ->
- append(T, {atomize(Mod), parse_avp_names(Body)}, Acc);
-
-%% avp_types -> [{AvpName, Code, Type, Flags, Encr}, ...]
-chunk({avp_types = T, [], Body}, Acc) ->
- store(T, parse_avp_types(Body), Acc);
-
-%% custom_types -> [{Mod, [AvpName, ...]}, ...]
-chunk({custom_types = T, [Mod], Body}, Dict) ->
- [_|_] = Avps = parse_avp_names(Body),
- append(T, {atomize(Mod), Avps}, Dict);
-
-%% messages -> [{MsgName, Code, Type, Appl, Avps}, ...]
-chunk({messages = T, [], Body}, Acc) ->
- store(T, parse_messages(Body), Acc);
-
-%% grouped -> [{AvpName, Code, Vendor, Avps}, ...]
-chunk({grouped = T, [], Body}, Acc) ->
- store(T, parse_groups(Body), Acc);
-
-%% avp_vendor_id -> [{Id, [AvpName, ...]}, ...]
-chunk({avp_vendor_id = T, [{number, I}], Body}, Dict) ->
- [_|_] = Names = parse_avp_names(Body),
- append(T, {I, Names}, Dict);
-
-%% enums -> [{AvpName, [{Value, Name}, ...]}, ...]
-chunk({enum, [N], Str}, Dict) ->
- append(enums, {atomize(N), parse_enums(Str)}, Dict);
-
-%% result_codes -> [{ResultName, [{Value, Name}, ...]}, ...]
-chunk({result_code, [N], Str}, Dict) ->
- append(result_codes, {atomize(N), parse_enums(Str)}, Dict);
-
-%% commands -> [{Name, Abbrev}, ...]
-chunk({commands = T, [], Body}, Dict) ->
- store(T, parse_commands(Body), Dict);
-
-chunk(T, _) ->
- ?ERROR({unknown_tag, T}).
-
-store(Key, Value, Dict) ->
- error == orddict:find(Key, Dict) orelse ?ERROR({duplicate, Key}),
- orddict:store(Key, Value, Dict).
-append(Key, Value, Dict) ->
- orddict:append(Key, Value, Dict).
-append_list(Key, Values, Dict) ->
- orddict:append_list(Key, Values, Dict).
-
-atomize({tag, T}) ->
- T;
-atomize({name, T}) ->
- ?ATOM(T).
-
-get_value(Keys, Spec)
- when is_list(Keys) ->
- [get_value(K, Spec) || K <- Keys];
-get_value(Key, Spec) ->
- proplists:get_value(Key, Spec, []).
-
-%% ------------------------------------------------------------------------
-%% enums_defined/1
-%% groups_defined/1
-%% customs_defined/1
-%%
-%% Ensure that every local enum/grouped/custom is defined as an avp
-%% with an appropriate type.
-
-enums_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- Import = get_value(import_enums, Spec),
- lists:all(fun({N,_}) ->
- true = enum_defined(N, Avps, Import)
- end,
- get_value(enums, Spec)).
-
-enum_defined(Name, Avps, Import) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, 'Enumerated', _, _} ->
- true;
- {Name, _, T, _, _} ->
- ?ERROR({avp_has_wrong_type, Name, 'Enumerated', T});
- false ->
- lists:any(fun({_,Is}) -> lists:keymember(Name, 1, Is) end, Import)
- orelse ?ERROR({avp_not_defined, Name, 'Enumerated'})
- end.
-%% Note that an AVP is imported only if referenced by a message or
-%% grouped AVP, so the final branch will fail if an enum definition is
-%% extended without this being the case.
-
-groups_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- lists:all(fun({N,_,_,_}) -> true = group_defined(N, Avps) end,
- get_value(grouped, Spec)).
-
-group_defined(Name, Avps) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, 'Grouped', _, _} ->
- true;
- {Name, _, T, _, _} ->
- ?ERROR({avp_has_wrong_type, Name, 'Grouped', T});
- false ->
- ?ERROR({avp_not_defined, Name, 'Grouped'})
- end.
-
-customs_defined(Spec) ->
- Avps = get_value(avp_types, Spec),
- lists:all(fun(A) -> true = custom_defined(A, Avps) end,
- lists:flatmap(fun last/1, get_value(custom_types, Spec))).
-
-custom_defined(Name, Avps) ->
- case lists:keyfind(Name, 1, Avps) of
- {Name, _, T, _, _} when T == 'Grouped';
- T == 'Enumerated' ->
- ?ERROR({avp_has_invalid_custom_type, Name, T});
- {Name, _, _, _, _} ->
- true;
- false ->
- ?ERROR({avp_not_defined, Name})
- end.
-
-last({_,Xs}) -> Xs.
-
-%% ------------------------------------------------------------------------
-%% v_flags_set/1
-
-v_flags_set(Spec) ->
- Avps = get_value(avp_types, Spec)
- ++ lists:flatmap(fun last/1, get_value(import_avps, Spec)),
- Vs = lists:flatmap(fun last/1, get_value(avp_vendor_id, Spec)),
-
- lists:all(fun(N) -> vset(N, Avps) end, Vs).
-
-vset(Name, Avps) ->
- A = lists:keyfind(Name, 1, Avps),
- false == A andalso ?ERROR({avp_not_defined, Name}),
- {Name, _Code, _Type, Flags, _Encr} = A,
- lists:member('V', Flags) orelse ?ERROR({v_flag_not_set, A}).
-
-%% ------------------------------------------------------------------------
-%% insert_codes/1
-
-insert_codes(Spec) ->
- [Msgs, Cmds] = get_value([messages, commands], Spec),
-
- %% Code -> [{Name, Flags}, ...]
- Dict = lists:foldl(fun({N,C,Fs,_,_}, D) -> dict:append(C,{N,Fs},D) end,
- dict:new(),
- Msgs),
-
- %% list() of {Code, {ReqName, ReqAbbr}, {AnsName, AnsAbbr}}
- %% If the name and abbreviation are the same then the 2-tuples
- %% are replaced by the common atom()-valued name.
- Codes = dict:fold(fun(C,Ns,A) -> [make_code(C, Ns, Cmds) | A] end,
- [],
- dict:erase(-1, Dict)), %% answer-message
-
- orddict:store(command_codes, Codes, Spec).
-
-make_code(Code, [_,_] = Ns, Cmds) ->
- {Req, Ans} = make_names(Ns, lists:map(fun({_,Fs}) ->
- lists:member('REQ', Fs)
- end,
- Ns)),
- {Code, abbrev(Req, Cmds), abbrev(Ans, Cmds)};
-
-make_code(Code, Cs, _) ->
- ?ERROR({missing_request_or_answer, Code, Cs}).
-
-%% 3.3. Diameter Command Naming Conventions
-%%
-%% Diameter command names typically includes one or more English words
-%% followed by the verb Request or Answer. Each English word is
-%% delimited by a hyphen. A three-letter acronym for both the request
-%% and answer is also normally provided.
-
-make_names([{Rname,_},{Aname,_}], [true, false]) ->
- {Rname, Aname};
-make_names([{Aname,_},{Rname,_}], [false, true]) ->
- {Rname, Aname};
-make_names([_,_] = Names, _) ->
- ?ERROR({inconsistent_command_flags, Names}).
-
-abbrev(Name, Cmds) ->
- case abbr(Name, get_value(Name, Cmds)) of
- Name ->
- Name;
- Abbr ->
- {Name, Abbr}
- end.
-
-%% No explicit abbreviation: construct.
-abbr(Name, []) ->
- ?ATOM(abbr(string:tokens(atom_to_list(Name), "-")));
-
-%% Abbreviation was specified.
-abbr(_Name, Abbr) ->
- Abbr.
-
-%% No hyphens: already abbreviated.
-abbr([Abbr]) ->
- Abbr;
-
-%% XX-Request/Answer ==> XXR/XXA
-abbr([[_,_] = P, T])
- when T == "Request";
- T == "Answer" ->
- P ++ [hd(T)];
-
-%% XXX-...-YYY-Request/Answer ==> X...YR/X...YA
-abbr([_,_|_] = L) ->
- lists:map(fun erlang:hd/1, L).
-
-%% ------------------------------------------------------------------------
-%% import_avps/2
-
-import_avps(Spec, Options) ->
- Msgs = get_value(messages, Spec),
- Groups = get_value(grouped, Spec),
-
- %% Messages and groups require AVP's referenced by them.
- NeededAvps
- = ordsets:from_list(lists:flatmap(fun({_,_,_,_,As}) ->
- [avp_name(A) || A <- As]
- end,
- Msgs)
- ++ lists:flatmap(fun({_,_,_,As}) ->
- [avp_name(A) || A <- As]
- end,
- Groups)),
- MissingAvps = missing_avps(NeededAvps, Spec),
-
- report(needed, NeededAvps),
- report(missing, MissingAvps),
-
- Import = inherit(get_value(inherits, Spec), Options),
-
- report(imported, Import),
-
- ImportedAvps = lists:map(fun({N,_,_,_,_}) -> N end,
- lists:flatmap(fun last/1, Import)),
-
- Unknown = MissingAvps -- ImportedAvps,
-
- [] == Unknown orelse ?ERROR({undefined_avps, Unknown}),
-
- orddict:store(import_avps, Import, orddict:erase(inherits, Spec)).
-
-%% missing_avps/2
-%%
-%% Given a list of AVP names and parsed spec, return the list of
-%% AVP's that aren't defined in this spec.
-
-missing_avps(NeededNames, Spec) ->
- Avps = get_value(avp_types, Spec),
- Groups = lists:map(fun({N,_,_,As}) ->
- {N, [avp_name(A) || A <- As]}
- end,
- get_value(grouped, Spec)),
- Names = ordsets:from_list(['AVP' | lists:map(fun({N,_,_,_,_}) -> N end,
- Avps)]),
- missing_avps(NeededNames, [], {Names, Groups}).
-
-avp_name({'<',A,'>'}) -> A;
-avp_name({A}) -> A;
-avp_name([A]) -> A;
-avp_name({_, A}) -> avp_name(A).
-
-missing_avps(NeededNames, MissingNames, {Names, _} = T) ->
- missing(ordsets:filter(fun(N) -> lists:member(N, NeededNames) end, Names),
- ordsets:union(NeededNames, MissingNames),
- T).
-
-%% Nothing found locally.
-missing([], MissingNames, _) ->
- MissingNames;
-
-%% Or not. Keep looking for for the AVP's needed by the found AVP's of
-%% type Grouped.
-missing(FoundNames, MissingNames, {_, Groups} = T) ->
- NeededNames = lists:flatmap(fun({N,As}) ->
- choose(lists:member(N, FoundNames), As, [])
- end,
- Groups),
- missing_avps(ordsets:from_list(NeededNames),
- ordsets:subtract(MissingNames, FoundNames),
- T).
-
-%% inherit/2
-
-inherit(Inherits, Options) ->
- Dirs = [D || {include, D} <- Options] ++ ["."],
- lists:foldl(fun(T,A) -> find_avps(T, A, Dirs) end, [], Inherits).
-
-find_avps({Mod, AvpNames}, Acc, Path) ->
- report(inherit_from, Mod),
- Avps = avps_from_beam(find_beam(Mod, Path), Mod), %% could be empty
- [{Mod, lists:sort(find_avps(AvpNames, Avps))} | Acc].
-
-find_avps([], Avps) ->
- Avps;
-find_avps(Names, Avps) ->
- lists:filter(fun({N,_,_,_,_}) -> lists:member(N, Names) end, Avps).
-
-%% find_beam/2
-
-find_beam(Mod, Dirs)
- when is_atom(Mod) ->
- find_beam(atom_to_list(Mod), Dirs);
-find_beam(Mod, Dirs) ->
- Beam = Mod ++ code:objfile_extension(),
- case try_path(Dirs, Beam) of
- {value, Path} ->
- Path;
- false ->
- ?ERROR({beam_not_on_path, Beam, Dirs})
- end.
-
-try_path([D|Ds], Fname) ->
- Path = filename:join(D, Fname),
- case file:read_file_info(Path) of
- {ok, _} ->
- {value, Path};
- _ ->
- try_path(Ds, Fname)
- end;
-try_path([], _) ->
- false.
-
-%% avps_from_beam/2
-
-avps_from_beam(Path, Mod) ->
- report(beam, Path),
- ok = load_module(code:is_loaded(Mod), Mod, Path),
- orddict:fetch(avp_types, Mod:dict()).
-
-load_module(false, Mod, Path) ->
- R = filename:rootname(Path, code:objfile_extension()),
- {module, Mod} = code:load_abs(R),
- ok;
-load_module({file, _}, _, _) ->
- ok.
-
-choose(true, X, _) -> X;
-choose(false, _, X) -> X.
-
-%% ------------------------------------------------------------------------
-%% import_groups/1
-%% import_enums/1
-%%
-%% For each inherited module, store the content of imported AVP's of
-%% type grouped/enumerated in a new key.
-
-import_groups(Spec) ->
- orddict:store(import_groups, import(grouped, Spec), Spec).
-
-import_enums(Spec) ->
- orddict:store(import_enums, import(enums, Spec), Spec).
-
-import(Key, Spec) ->
- lists:flatmap(fun(T) -> import_key(Key, T) end,
- get_value(import_avps, Spec)).
-
-import_key(Key, {Mod, Avps}) ->
- Imports = lists:flatmap(fun(T) ->
- choose(lists:keymember(element(1,T),
- 1,
- Avps),
- [T],
- [])
- end,
- get_value(Key, Mod:dict())),
- if Imports == [] ->
- [];
- true ->
- [{Mod, Imports}]
- end.
-
-%% ------------------------------------------------------------------------
-%% parse_enums/1
-%%
-%% Enums are specified either as the integer value followed by the
-%% name or vice-versa. In the former case the name of the enum is
-%% taken to be the string up to the end of line, which may contain
-%% whitespace. In the latter case the integer may be parenthesized,
-%% specified in hex and followed by an inline comment. This is
-%% historical and will likely be changed to require a precise input
-%% format.
-%%
-%% Output: list() of {integer(), atom()}
-
-parse_enums(Str) ->
- lists:flatmap(fun(L) -> parse_enum(trim(L)) end, string:tokens(Str, "\n")).
-
-parse_enum([]) ->
- [];
-
-parse_enum(Str) ->
- REs = [{"^(0[xX][0-9A-Fa-f]+|[0-9]+)\s+(.*?)\s*$", 1, 2},
- {"^(.+?)\s+(0[xX][0-9A-Fa-f]+|[0-9]+)(\s+.*)?$", 2, 1},
- {"^(.+?)\s+\\((0[xX][0-9A-Fa-f]+|[0-9]+)\\)(\s+.*)?$", 2, 1}],
- parse_enum(Str, REs).
-
-parse_enum(Str, REs) ->
- try lists:foreach(fun(R) -> enum(Str, R) end, REs) of
- ok ->
- ?ERROR({bad_enum, Str})
- catch
- throw: {enum, T} ->
- [T]
- end.
-
-enum(Str, {Re, I, N}) ->
- case re:run(Str, Re, [{capture, all_but_first, list}]) of
- {match, Vs} ->
- T = list_to_tuple(Vs),
- throw({enum, {to_int(element(I,T)), ?ATOM(element(N,T))}});
- nomatch ->
- ok
- end.
-
-to_int([$0,X|Hex])
- when X == $x;
- X == $X ->
- {ok, [I], _} = io_lib:fread("~#", "16#" ++ Hex),
- I;
-to_int(I) ->
- list_to_integer(I).
-
-%% ------------------------------------------------------------------------
-%% parse_messages/1
-%%
-%% Parse according to the ABNF for message specifications in 3.2 of
-%% RFC 3588 (shown below). We require all message and AVP names to
-%% start with a digit or uppercase character, except for the base
-%% answer-message, which is treated as a special case. Allowing names
-%% that start with a digit is more than the RFC specifies but the name
-%% doesn't affect what's sent over the wire. (Certains 3GPP standards
-%% use names starting with a digit. eg 3GPP-Charging-Id in TS32.299.)
-
-%%
-%% Sadly, not even the RFC follows this grammar. In particular, except
-%% in the example in 3.2, it wraps each command-name in angle brackets
-%% ('<' '>') which makes parsing a sequence of specifications require
-%% lookahead: after 'optional' avps have been parsed, it's not clear
-%% whether a '<' is a 'fixed' or whether it's the start of a
-%% subsequent message until we see whether or not '::=' follows the
-%% closing '>'. Require the grammar as specified.
-%%
-%% Output: list of {Name, Code, Flags, ApplId, Avps}
-%%
-%% Name = atom()
-%% Code = integer()
-%% Flags = integer()
-%% ApplId = [] | [integer()]
-%% Avps = see parse_avps/1
-
-parse_messages(Str) ->
- p_cmd(trim(Str), []).
-
-%% command-def = command-name "::=" diameter-message
-%%
-%% command-name = diameter-name
-%%
-%% diameter-name = ALPHA *(ALPHA / DIGIT / "-")
-%%
-%% diameter-message = header [ *fixed] [ *required] [ *optional]
-%% [ *fixed]
-%%
-%% header = "<" Diameter-Header:" command-id
-%% [r-bit] [p-bit] [e-bit] [application-id]">"
-%%
-%% The header spec (and example that follows it) is slightly mangled
-%% and, given the examples in the RFC should as follows:
-%%
-%% header = "<" "Diameter Header:" command-id
-%% [r-bit] [p-bit] [e-bit] [application-id]">"
-%%
-%% This is what's required/parsed below, modulo whitespace. This is
-%% also what's specified in the current draft standard at
-%% http://ftp.ietf.org/drafts/wg/dime.
-%%
-%% Note that the grammar specifies the order fixed, required,
-%% optional. In practise there seems to be little difference between
-%% the latter two since qualifiers can be used to change the
-%% semantics. For example 1*[XXX] and *1{YYY} specify 1 or more of the
-%% optional avp XXX and 0 or 1 of the required avp YYY, making the
-%% iotional avp required and the required avp optional. The current
-%% draft addresses this somewhat by requiring that min for a qualifier
-%% on an optional avp must be 0 if present. It doesn't say anything
-%% about required avps however, so specifying a min of 0 would still
-%% be possible. The draft also does away with the trailing *fixed.
-%%
-%% What will be parsed here will treat required and optional
-%% interchangeably. That is. only require that required/optional
-%% follow and preceed fixed, not that optional avps must follow
-%% required ones. We already have several specs for which this parsing
-%% is necessary and there seems to be no harm in accepting it.
-
-p_cmd("", Acc) ->
- lists:reverse(Acc);
-
-p_cmd(Str, Acc) ->
- {Next, Rest} = split_def(Str),
- report(command, Next),
- p_cmd(Rest, [p_cmd(Next) | Acc]).
-
-p_cmd("answer-message" ++ Str) ->
- p_header([{name, 'answer-message'} | diameter_spec_scan:parse(Str)]);
-
-p_cmd(Str) ->
- p_header(diameter_spec_scan:parse(Str)).
-
-%% p_header/1
-
-p_header(['<', {name, _} = N, '>' | Toks]) ->
- p_header([N | Toks]);
-
-p_header([{name, 'answer-message' = N}, '::=',
- '<', {name, "Diameter"}, {name, "Header"}, ':', {tag, code},
- ',', {name, "ERR"}, '[', {name, "PXY"}, ']', '>'
- | Toks]) ->
- {N, -1, ['ERR', 'PXY'], [], parse_avps(Toks)};
-
-p_header([{name, Name}, '::=',
- '<', {name, "Diameter"}, {name, "Header"}, ':', {number, Code}
- | Toks]) ->
- {Flags, Rest} = p_flags(Toks),
- {ApplId, [C|_] = R} = p_appl(Rest),
- '>' == C orelse ?ERROR({invalid_flag, {Name, Code, Flags, ApplId}, R}),
- {?ATOM(Name), Code, Flags, ApplId, parse_avps(tl(R))};
-
-p_header(Toks) ->
- ?ERROR({invalid_header, Toks}).
-
-%% application-id = 1*DIGIT
-%%
-%% command-id = 1*DIGIT
-%% ; The Command Code assigned to the command
-%%
-%% r-bit = ", REQ"
-%% ; If present, the 'R' bit in the Command
-%% ; Flags is set, indicating that the message
-%% ; is a request, as opposed to an answer.
-%%
-%% p-bit = ", PXY"
-%% ; If present, the 'P' bit in the Command
-%% ; Flags is set, indicating that the message
-%% ; is proxiable.
-%%
-%% e-bit = ", ERR"
-%% ; If present, the 'E' bit in the Command
-%% ; Flags is set, indicating that the answer
-%% ; message contains a Result-Code AVP in
-%% ; the "protocol error" class.
-
-p_flags(Toks) ->
- lists:foldl(fun p_flags/2, {[], Toks}, ["REQ", "PXY", "ERR"]).
-
-p_flags(N, {Acc, [',', {name, N} | Toks]}) ->
- {[?ATOM(N) | Acc], Toks};
-
-p_flags(_, T) ->
- T.
-
-%% The RFC doesn't specify ',' before application-id but this seems a
-%% bit inconsistent. Accept a comma if it exists.
-p_appl([',', {number, I} | Toks]) ->
- {[I], Toks};
-p_appl([{number, I} | Toks]) ->
- {[I], Toks};
-p_appl(Toks) ->
- {[], Toks}.
-
-%% parse_avps/1
-%%
-%% Output: list() of Avp | {Qual, Avp}
-%%
-%% Qual = '*' | {Min, '*'} | {'*', Max} | {Min, Max}
-%% Avp = {'<', Name, '>'} | {Name} | [Name]
-%%
-%% Min, Max = integer() >= 0
-
-parse_avps(Toks) ->
- p_avps(Toks, ['<', '|', '<'], []).
-%% The list corresponds to the delimiters expected at the front, middle
-%% and back of the avp specification, '|' representing '{' and '['.
-
-%% fixed = [qual] "<" avp-spec ">"
-%% ; Defines the fixed position of an AVP
-%%
-%% required = [qual] "{" avp-spec "}"
-%% ; The AVP MUST be present and can appear
-%% ; anywhere in the message.
-%%
-%% optional = [qual] "[" avp-name "]"
-%% ; The avp-name in the 'optional' rule cannot
-%% ; evaluate to any AVP Name which is included
-%% ; in a fixed or required rule. The AVP can
-%% ; appear anywhere in the message.
-%%
-%% qual = [min] "*" [max]
-%% ; See ABNF conventions, RFC 2234 Section 6.6.
-%% ; The absence of any qualifiers depends on whether
-%% ; it precedes a fixed, required, or optional
-%% ; rule. If a fixed or required rule has no
-%% ; qualifier, then exactly one such AVP MUST
-%% ; be present. If an optional rule has no
-%% ; qualifier, then 0 or 1 such AVP may be
-%% ; present.
-%% ;
-%% ; NOTE: "[" and "]" have a different meaning
-%% ; than in ABNF (see the optional rule, above).
-%% ; These braces cannot be used to express
-%% ; optional fixed rules (such as an optional
-%% ; ICV at the end). To do this, the convention
-%% ; is '0*1fixed'.
-%%
-%% min = 1*DIGIT
-%% ; The minimum number of times the element may
-%% ; be present. The default value is zero.
-%%
-%% max = 1*DIGIT
-%% ; The maximum number of times the element may
-%% ; be present. The default value is infinity. A
-%% ; value of zero implies the AVP MUST NOT be
-%% ; present.
-%%
-%% avp-spec = diameter-name
-%% ; The avp-spec has to be an AVP Name, defined
-%% ; in the base or extended Diameter
-%% ; specifications.
-%%
-%% avp-name = avp-spec / "AVP"
-%% ; The string "AVP" stands for *any* arbitrary
-%% ; AVP Name, which does not conflict with the
-%% ; required or fixed position AVPs defined in
-%% ; the command code definition.
-%%
-
-p_avps([], _, Acc) ->
- lists:reverse(Acc);
-
-p_avps(Toks, Delim, Acc) ->
- {Qual, Rest} = p_qual(Toks),
- {Avp, R, D} = p_avp(Rest, Delim),
- T = if Qual == false ->
- Avp;
- true ->
- {Qual, Avp}
- end,
- p_avps(R, D, [T | Acc]).
-
-p_qual([{number, Min}, '*', {number, Max} | Toks]) ->
- {{Min, Max}, Toks};
-p_qual([{number, Min}, '*' = Max | Toks]) ->
- {{Min, Max}, Toks};
-p_qual(['*' = Min, {number, Max} | Toks]) ->
- {{Min, Max}, Toks};
-p_qual(['*' = Q | Toks]) ->
- {Q, Toks};
-p_qual(Toks) ->
- {false, Toks}.
-
-p_avp([B, {name, Name}, E | Toks], [_|_] = Delim) ->
- {avp(B, ?ATOM(Name), E),
- Toks,
- delim(choose(B == '<', B, '|'), Delim)};
-p_avp(Toks, Delim) ->
- ?ERROR({invalid_avp, Toks, Delim}).
-
-avp('<' = B, Name, '>' = E) ->
- {B, Name, E};
-avp('{', Name, '}') ->
- {Name};
-avp('[', Name, ']') ->
- [Name];
-avp(B, Name, E) ->
- ?ERROR({invalid_avp, B, Name, E}).
-
-delim(B, D) ->
- if B == hd(D) -> D; true -> tl(D) end.
-
-%% split_def/1
-%%
-%% Strip one command definition off head of a string.
-
-split_def(Str) ->
- sdh(Str, []).
-
-%% Look for the "::=" starting off the definition.
-sdh("", _) ->
- ?ERROR({missing, '::='});
-sdh("::=" ++ Rest, Acc) ->
- sdb(Rest, [$=,$:,$:|Acc]);
-sdh([C|Rest], Acc) ->
- sdh(Rest, [C|Acc]).
-
-%% Look for the "::=" starting off the following definition.
-sdb("::=" ++ _ = Rest, Acc) ->
- sdt(trim(Acc), Rest);
-sdb("" = Rest, Acc) ->
- sd(Acc, Rest);
-sdb([C|Rest], Acc) ->
- sdb(Rest, [C|Acc]).
-
-%% Put name characters of the subsequent specification back into Rest.
-sdt([C|Acc], Rest)
- when C /= $\n, C /= $\s ->
- sdt(Acc, [C|Rest]);
-
-sdt(Acc, Rest) ->
- sd(Acc, Rest).
-
-sd(Acc, Rest) ->
- {trim(lists:reverse(Acc)), Rest}.
-%% Note that Rest is already trimmed of leading space.
-
-%% ------------------------------------------------------------------------
-%% parse_groups/1
-%%
-%% Parse according to the ABNF for message specifications in 4.4 of
-%% RFC 3588 (shown below). Again, allow names starting with a digit
-%% and also require "AVP Header" without "-" since this is what
-%% the RFC uses in all examples.
-%%
-%% Output: list of {Name, Code, Vendor, Avps}
-%%
-%% Name = atom()
-%% Code = integer()
-%% Vendor = [] | [integer()]
-%% Avps = see parse_avps/1
-
-parse_groups(Str) ->
- p_group(trim(Str), []).
-
-%% grouped-avp-def = name "::=" avp
-%%
-%% name-fmt = ALPHA *(ALPHA / DIGIT / "-")
-%%
-%% name = name-fmt
-%% ; The name has to be the name of an AVP,
-%% ; defined in the base or extended Diameter
-%% ; specifications.
-%%
-%% avp = header [ *fixed] [ *required] [ *optional]
-%% [ *fixed]
-%%
-%% header = "<" "AVP-Header:" avpcode [vendor] ">"
-%%
-%% avpcode = 1*DIGIT
-%% ; The AVP Code assigned to the Grouped AVP
-%%
-%% vendor = 1*DIGIT
-%% ; The Vendor-ID assigned to the Grouped AVP.
-%% ; If absent, the default value of zero is
-%% ; used.
-
-p_group("", Acc) ->
- lists:reverse(Acc);
-
-p_group(Str, Acc) ->
- {Next, Rest} = split_def(Str),
- report(group, Next),
- p_group(Rest, [p_group(diameter_spec_scan:parse(Next)) | Acc]).
-
-p_group([{name, Name}, '::=', '<', {name, "AVP"}, {name, "Header"},
- ':', {number, Code}
- | Toks]) ->
- {Id, [C|_] = R} = p_vendor(Toks),
- C == '>' orelse ?ERROR({invalid_group_header, R}),
- {?ATOM(Name), Code, Id, parse_avps(tl(R))};
-
-p_group(Toks) ->
- ?ERROR({invalid_group, Toks}).
-
-p_vendor([{number, I} | Toks]) ->
- {[I], Toks};
-p_vendor(Toks) ->
- {[], Toks}.
-
-%% ------------------------------------------------------------------------
-%% parse_avp_names/1
-
-parse_avp_names(Str) ->
- [p_name(N) || N <- diameter_spec_scan:parse(Str)].
-
-p_name({name, N}) ->
- ?ATOM(N);
-p_name(T) ->
- ?ERROR({invalid_avp_name, T}).
-
-%% ------------------------------------------------------------------------
-%% parse_avp_types/1
-%%
-%% Output: list() of {Name, Code, Type, Flags, Encr}
-
-parse_avp_types(Str) ->
- p_avp_types(Str, []).
-
-p_avp_types(Str, Acc) ->
- p_type(diameter_spec_scan:split(Str, 3), Acc).
-
-p_type({[],[]}, Acc) ->
- lists:reverse(Acc);
-
-p_type({[{name, Name}, {number, Code}, {name, Type}], Str}, Acc) ->
- {Flags, Encr, Rest} = try
- p_avp_flags(trim(Str), [])
- catch
- throw: {?MODULE, Reason} ->
- ?ERROR({invalid_avp_type, Reason})
- end,
- p_avp_types(Rest, [{?ATOM(Name), Code, ?ATOM(type(Type)), Flags, Encr}
- | Acc]);
-
-p_type(T, _) ->
- ?ERROR({invalid_avp_type, T}).
-
-p_avp_flags([C|Str], Acc)
- when C == $M;
- C == $P;
- C == $V ->
- p_avp_flags(Str, [?ATOM([C]) | Acc]);
-%% Could support lowercase here if there's a use for distinguishing
-%% between Must and Should in the future in deciding whether or not
-%% to set a flag.
-
-p_avp_flags([$-|Str], Acc) ->
- %% Require encr on same line as flags if specified.
- {H,T} = lists:splitwith(fun(C) -> C /= $\n end, Str),
-
- {[{name, [$X|X]} | Toks], Rest} = diameter_spec_scan:split([$X|H], 2),
-
- "" == X orelse throw({?MODULE, {invalid_avp_flag, Str}}),
-
- Encr = case Toks of
- [] ->
- "-";
- [{_, E}] ->
- (E == "Y" orelse E == "N")
- orelse throw({?MODULE, {invalid_encr, E}}),
- E
- end,
-
- Flags = ordsets:from_list(lists:reverse(Acc)),
-
- {Flags, ?ATOM(Encr), Rest ++ T};
-
-p_avp_flags(Str, Acc) ->
- p_avp_flags([$-|Str], Acc).
-
-type("DiamIdent") -> "DiameterIdentity"; %% RFC 3588
-type("DiamURI") -> "DiameterURI"; %% RFC 3588
-type("IPFltrRule") -> "IPFilterRule"; %% RFC 4005
-type("QoSFltrRule") -> "QoSFilterRule"; %% RFC 4005
-type(N)
- when N == "OctetString";
- N == "Integer32";
- N == "Integer64";
- N == "Unsigned32";
- N == "Unsigned64";
- N == "Float32";
- N == "Float64";
- N == "Grouped";
- N == "Enumerated";
- N == "Address";
- N == "Time";
- N == "UTF8String";
- N == "DiameterIdentity";
- N == "DiameterURI";
- N == "IPFilterRule";
- N == "QoSFilterRule" ->
- N;
-type(N) ->
- ?ERROR({invalid_avp_type, N}).
-
-%% ------------------------------------------------------------------------
-%% parse_commands/1
-
-parse_commands(Str) ->
- p_abbr(diameter_spec_scan:parse(Str), []).
-
- p_abbr([{name, Name}, {name, Abbrev} | Toks], Acc)
- when length(Abbrev) < length(Name) ->
- p_abbr(Toks, [{?ATOM(Name), ?ATOM(Abbrev)} | Acc]);
-
-p_abbr([], Acc) ->
- lists:reverse(Acc);
-
-p_abbr(T, _) ->
- ?ERROR({invalid_command, T}).
diff --git a/lib/diameter/src/compiler/diameter_vsn.hrl b/lib/diameter/src/compiler/diameter_vsn.hrl
new file mode 100644
index 0000000000..024d047adc
--- /dev/null
+++ b/lib/diameter/src/compiler/diameter_vsn.hrl
@@ -0,0 +1,22 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+%% The version of the format of the return value of dict/0 in
+%% generated dictionary modules.
+-define(VERSION, 1).
diff --git a/lib/diameter/src/compiler/modules.mk b/lib/diameter/src/compiler/modules.mk
deleted file mode 100644
index 17a311dacf..0000000000
--- a/lib/diameter/src/compiler/modules.mk
+++ /dev/null
@@ -1,27 +0,0 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
-
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-
-MODULES = \
- diameter_codegen \
- diameter_spec_scan \
- diameter_spec_util
-
-HRL_FILES = \
- diameter_forms.hrl
-
diff --git a/lib/diameter/src/app/depend.sed b/lib/diameter/src/depend.sed
index 9df0133960..8f999f646f 100644
--- a/lib/diameter/src/app/depend.sed
+++ b/lib/diameter/src/depend.sed
@@ -18,14 +18,34 @@
#
#
-# Extract include dependencies from .erl files. The output is massaged
-# further in Makefile.
+# Extract include dependencies from .erl files. First line of input
+# is the path to the module in question (minus the .erl extension),
+# the rest is the contents of the module.
#
+1{
+ s@^[^/]*/@@
+ h
+ d
+}
+
+# Only interested in includes of diameter hrls.
/^-include/!d
/"diameter/!d
-s@^-include_lib("[^/]*@$(DIAMETER_TOP)@
+# Extract the name of the included files in one of two forms:
+#
+# $(INCDIR)/diameter.hrl
+# diameter_internal.hrl
+
+s@^-include_lib(".*/@$(INCDIR)/@
s@^-include("@@
s@".*@@
-s@^@$(EBIN)/.$(EMULATOR): @
+
+# Retrieve the path to our module from the hold space, morph it
+# into a beam path and turn it into a dependency like this:
+#
+# $(EBIN)/diameter_service.$(EMULATOR): $(INCDIR)/diameter.hrl
+
+G
+s@^\(.*\)\n\(.*\)@$(EBIN)/\2.$(EMULATOR): \1@
diff --git a/lib/diameter/src/dict/base_accounting.dia b/lib/diameter/src/dict/base_accounting.dia
new file mode 100644
index 0000000000..ced324078c
--- /dev/null
+++ b/lib/diameter/src/dict/base_accounting.dia
@@ -0,0 +1,69 @@
+;;
+;; %CopyrightBegin%
+;;
+;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
+;;
+;; The contents of this file are subject to the Erlang Public License,
+;; Version 1.1, (the "License"); you may not use this file except in
+;; compliance with the License. You should have received a copy of the
+;; Erlang Public License along with this software. If not, it can be
+;; retrieved online at http://www.erlang.org/.
+;;
+;; Software distributed under the License is distributed on an "AS IS"
+;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+;; the License for the specific language governing rights and limitations
+;; under the License.
+;;
+;; %CopyrightEnd%
+;;
+
+@id 3
+@name diameter_gen_base_accounting
+@prefix diameter_base_accounting
+@vendor 0 IETF
+
+@inherits diameter_gen_base_rfc3588
+
+@messages
+
+ ACR ::= < Diameter Header: 271, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+
+ ACA ::= < Diameter Header: 271, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Error-Reporting-Host ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ AVP ]
diff --git a/lib/diameter/src/dict/base_rfc3588.dia b/lib/diameter/src/dict/base_rfc3588.dia
new file mode 100644
index 0000000000..acd7fffd00
--- /dev/null
+++ b/lib/diameter/src/dict/base_rfc3588.dia
@@ -0,0 +1,461 @@
+;;
+;; %CopyrightBegin%
+;;
+;; Copyright Ericsson AB 2010-2011. All Rights Reserved.
+;;
+;; The contents of this file are subject to the Erlang Public License,
+;; Version 1.1, (the "License"); you may not use this file except in
+;; compliance with the License. You should have received a copy of the
+;; Erlang Public License along with this software. If not, it can be
+;; retrieved online at http://www.erlang.org/.
+;;
+;; Software distributed under the License is distributed on an "AS IS"
+;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+;; the License for the specific language governing rights and limitations
+;; under the License.
+;;
+;; %CopyrightEnd%
+;;
+
+@id 0
+@name diameter_gen_base_rfc3588
+@prefix diameter_base
+@vendor 0 IETF
+
+@avp_types
+
+ Acct-Interim-Interval 85 Unsigned32 M
+ Accounting-Realtime-Required 483 Enumerated M
+ Acct-Multi-Session-Id 50 UTF8String M
+ Accounting-Record-Number 485 Unsigned32 M
+ Accounting-Record-Type 480 Enumerated M
+ Acct-Session-Id 44 OctetString M
+ Accounting-Sub-Session-Id 287 Unsigned64 M
+ Acct-Application-Id 259 Unsigned32 M
+ Auth-Application-Id 258 Unsigned32 M
+ Auth-Request-Type 274 Enumerated M
+ Authorization-Lifetime 291 Unsigned32 M
+ Auth-Grace-Period 276 Unsigned32 M
+ Auth-Session-State 277 Enumerated M
+ Re-Auth-Request-Type 285 Enumerated M
+ Class 25 OctetString M
+ Destination-Host 293 DiamIdent M
+ Destination-Realm 283 DiamIdent M
+ Disconnect-Cause 273 Enumerated M
+ E2E-Sequence 300 Grouped M
+ Error-Message 281 UTF8String -
+ Error-Reporting-Host 294 DiamIdent -
+ Event-Timestamp 55 Time M
+ Experimental-Result 297 Grouped M
+ Experimental-Result-Code 298 Unsigned32 M
+ Failed-AVP 279 Grouped M
+ Firmware-Revision 267 Unsigned32 -
+ Host-IP-Address 257 Address M
+ Inband-Security-Id 299 Unsigned32 M
+ Multi-Round-Time-Out 272 Unsigned32 M
+ Origin-Host 264 DiamIdent M
+ Origin-Realm 296 DiamIdent M
+ Origin-State-Id 278 Unsigned32 M
+ Product-Name 269 UTF8String -
+ Proxy-Host 280 DiamIdent M
+ Proxy-Info 284 Grouped M
+ Proxy-State 33 OctetString M
+ Redirect-Host 292 DiamURI M
+ Redirect-Host-Usage 261 Enumerated M
+ Redirect-Max-Cache-Time 262 Unsigned32 M
+ Result-Code 268 Unsigned32 M
+ Route-Record 282 DiamIdent M
+ Session-Id 263 UTF8String M
+ Session-Timeout 27 Unsigned32 M
+ Session-Binding 270 Unsigned32 M
+ Session-Server-Failover 271 Enumerated M
+ Supported-Vendor-Id 265 Unsigned32 M
+ Termination-Cause 295 Enumerated M
+ User-Name 1 UTF8String M
+ Vendor-Id 266 Unsigned32 M
+ Vendor-Specific-Application-Id 260 Grouped M
+
+@messages
+
+ CER ::= < Diameter Header: 257, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ 1* { Host-IP-Address }
+ { Vendor-Id }
+ { Product-Name }
+ [ Origin-State-Id ]
+ * [ Supported-Vendor-Id ]
+ * [ Auth-Application-Id ]
+ * [ Inband-Security-Id ]
+ * [ Acct-Application-Id ]
+ * [ Vendor-Specific-Application-Id ]
+ [ Firmware-Revision ]
+ * [ AVP ]
+
+ CEA ::= < Diameter Header: 257 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ 1* { Host-IP-Address }
+ { Vendor-Id }
+ { Product-Name }
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ * [ Failed-AVP ]
+ * [ Supported-Vendor-Id ]
+ * [ Auth-Application-Id ]
+ * [ Inband-Security-Id ]
+ * [ Acct-Application-Id ]
+ * [ Vendor-Specific-Application-Id ]
+ [ Firmware-Revision ]
+ * [ AVP ]
+
+ DPR ::= < Diameter Header: 282, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ { Disconnect-Cause }
+
+ DPA ::= < Diameter Header: 282 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ Error-Message ]
+ * [ Failed-AVP ]
+
+ DWR ::= < Diameter Header: 280, REQ >
+ { Origin-Host }
+ { Origin-Realm }
+ [ Origin-State-Id ]
+
+ DWA ::= < Diameter Header: 280 >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ Error-Message ]
+ * [ Failed-AVP ]
+ [ Origin-State-Id ]
+
+ answer-message ::= < Diameter Header: code, ERR [PXY] >
+ 0*1 < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Result-Code }
+ [ Origin-State-Id ]
+ [ Error-Reporting-Host ]
+ [ Proxy-Info ]
+ * [ AVP ]
+
+ RAR ::= < Diameter Header: 258, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Destination-Host }
+ { Auth-Application-Id }
+ { Re-Auth-Request-Type }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+
+ RAA ::= < Diameter Header: 258, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ * [ Failed-AVP ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+
+ STR ::= < Diameter Header: 275, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Auth-Application-Id }
+ { Termination-Cause }
+ [ User-Name ]
+ [ Destination-Host ]
+ * [ Class ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+
+ STA ::= < Diameter Header: 275, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ * [ Class ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ * [ Failed-AVP ]
+ [ Origin-State-Id ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+
+ ASR ::= < Diameter Header: 274, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Destination-Host }
+ { Auth-Application-Id }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+
+ ASA ::= < Diameter Header: 274, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ [ User-Name ]
+ [ Origin-State-Id ]
+ [ Error-Message ]
+ [ Error-Reporting-Host ]
+ * [ Failed-AVP ]
+ * [ Redirect-Host ]
+ [ Redirect-Host-Usage ]
+ [ Redirect-Max-Cache-Time ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+
+ ACR ::= < Diameter Header: 271, REQ, PXY >
+ < Session-Id >
+ { Origin-Host }
+ { Origin-Realm }
+ { Destination-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ Route-Record ]
+ * [ AVP ]
+
+ ACA ::= < Diameter Header: 271, PXY >
+ < Session-Id >
+ { Result-Code }
+ { Origin-Host }
+ { Origin-Realm }
+ { Accounting-Record-Type }
+ { Accounting-Record-Number }
+ [ Acct-Application-Id ]
+ [ Vendor-Specific-Application-Id ]
+ [ User-Name ]
+ [ Accounting-Sub-Session-Id ]
+ [ Acct-Session-Id ]
+ [ Acct-Multi-Session-Id ]
+ [ Error-Reporting-Host ]
+ [ Acct-Interim-Interval ]
+ [ Accounting-Realtime-Required ]
+ [ Origin-State-Id ]
+ [ Event-Timestamp ]
+ * [ Proxy-Info ]
+ * [ AVP ]
+
+@enum Disconnect-Cause
+
+ REBOOTING 0
+ BUSY 1
+ DO_NOT_WANT_TO_TALK_TO_YOU 2
+
+@enum Redirect-Host-Usage
+
+ DONT_CACHE 0
+ ALL_SESSION 1
+ ALL_REALM 2
+ REALM_AND_APPLICATION 3
+ ALL_APPLICATION 4
+ ALL_HOST 5
+ ALL_USER 6
+
+@enum Auth-Request-Type
+
+ AUTHENTICATE_ONLY 1
+ AUTHORIZE_ONLY 2
+ AUTHORIZE_AUTHENTICATE 3
+
+@enum Auth-Session-State
+
+ STATE_MAINTAINED 0
+ NO_STATE_MAINTAINED 1
+
+@enum Re-Auth-Request-Type
+
+ AUTHORIZE_ONLY 0
+ AUTHORIZE_AUTHENTICATE 1
+
+@enum Termination-Cause
+
+ LOGOUT 1
+ SERVICE_NOT_PROVIDED 2
+ BAD_ANSWER 3
+ ADMINISTRATIVE 4
+ LINK_BROKEN 5
+ AUTH_EXPIRED 6
+ USER_MOVED 7
+ SESSION_TIMEOUT 8
+
+@enum Session-Server-Failover
+
+ REFUSE_SERVICE 0
+ TRY_AGAIN 1
+ ALLOW_SERVICE 2
+ TRY_AGAIN_ALLOW_SERVICE 3
+
+@enum Accounting-Record-Type
+
+ EVENT_RECORD 1
+ START_RECORD 2
+ INTERIM_RECORD 3
+ STOP_RECORD 4
+
+@enum Accounting-Realtime-Required
+
+ DELIVER_AND_GRANT 1
+ GRANT_AND_STORE 2
+ GRANT_AND_LOSE 3
+
+@define Result-Code
+
+ ;; 7.1.1. Informational
+ MULTI_ROUND_AUTH 1001
+
+ ;; 7.1.2. Success
+ SUCCESS 2001
+ LIMITED_SUCCESS 2002
+
+ ;; 7.1.3. Protocol Errors
+ COMMAND_UNSUPPORTED 3001
+ UNABLE_TO_DELIVER 3002
+ REALM_NOT_SERVED 3003
+ TOO_BUSY 3004
+ LOOP_DETECTED 3005
+ REDIRECT_INDICATION 3006
+ APPLICATION_UNSUPPORTED 3007
+ INVALID_HDR_BITS 3008
+ INVALID_AVP_BITS 3009
+ UNKNOWN_PEER 3010
+
+ ;; 7.1.4. Transient Failures
+ AUTHENTICATION_REJECTED 4001
+ OUT_OF_SPACE 4002
+ ELECTION_LOST 4003
+
+ ;; 7.1.5. Permanent Failures
+ AVP_UNSUPPORTED 5001
+ UNKNOWN_SESSION_ID 5002
+ AUTHORIZATION_REJECTED 5003
+ INVALID_AVP_VALUE 5004
+ MISSING_AVP 5005
+ RESOURCES_EXCEEDED 5006
+ CONTRADICTING_AVPS 5007
+ AVP_NOT_ALLOWED 5008
+ AVP_OCCURS_TOO_MANY_TIMES 5009
+ NO_COMMON_APPLICATION 5010
+ UNSUPPORTED_VERSION 5011
+ UNABLE_TO_COMPLY 5012
+ INVALID_BIT_IN_HEADER 5013
+ INVALID_AVP_LENGTH 5014
+ INVALID_MESSAGE_LENGTH 5015
+ INVALID_AVP_BIT_COMBO 5016
+ NO_COMMON_SECURITY 5017
+
+ ;; With a prefix for backwards compatibility.
+ DIAMETER_MULTI_ROUND_AUTH 1001
+ DIAMETER_SUCCESS 2001
+ DIAMETER_LIMITED_SUCCESS 2002
+ DIAMETER_COMMAND_UNSUPPORTED 3001
+ DIAMETER_UNABLE_TO_DELIVER 3002
+ DIAMETER_REALM_NOT_SERVED 3003
+ DIAMETER_TOO_BUSY 3004
+ DIAMETER_LOOP_DETECTED 3005
+ DIAMETER_REDIRECT_INDICATION 3006
+ DIAMETER_APPLICATION_UNSUPPORTED 3007
+ DIAMETER_INVALID_HDR_BITS 3008
+ DIAMETER_INVALID_AVP_BITS 3009
+ DIAMETER_UNKNOWN_PEER 3010
+ DIAMETER_AUTHENTICATION_REJECTED 4001
+ DIAMETER_OUT_OF_SPACE 4002
+ DIAMETER_ELECTION_LOST 4003
+ DIAMETER_AVP_UNSUPPORTED 5001
+ DIAMETER_UNKNOWN_SESSION_ID 5002
+ DIAMETER_AUTHORIZATION_REJECTED 5003
+ DIAMETER_INVALID_AVP_VALUE 5004
+ DIAMETER_MISSING_AVP 5005
+ DIAMETER_RESOURCES_EXCEEDED 5006
+ DIAMETER_CONTRADICTING_AVPS 5007
+ DIAMETER_AVP_NOT_ALLOWED 5008
+ DIAMETER_AVP_OCCURS_TOO_MANY_TIMES 5009
+ DIAMETER_NO_COMMON_APPLICATION 5010
+ DIAMETER_UNSUPPORTED_VERSION 5011
+ DIAMETER_UNABLE_TO_COMPLY 5012
+ DIAMETER_INVALID_BIT_IN_HEADER 5013
+ DIAMETER_INVALID_AVP_LENGTH 5014
+ DIAMETER_INVALID_MESSAGE_LENGTH 5015
+ DIAMETER_INVALID_AVP_BIT_COMBO 5016
+ DIAMETER_NO_COMMON_SECURITY 5017
+
+@grouped
+
+ Proxy-Info ::= < AVP Header: 284 >
+ { Proxy-Host }
+ { Proxy-State }
+ * [ AVP ]
+
+ Failed-AVP ::= < AVP Header: 279 >
+ 1* {AVP}
+
+ Experimental-Result ::= < AVP Header: 297 >
+ { Vendor-Id }
+ { Experimental-Result-Code }
+
+ Vendor-Specific-Application-Id ::= < AVP Header: 260 >
+ 1* { Vendor-Id }
+ [ Auth-Application-Id ]
+ [ Acct-Application-Id ]
+
+;; The E2E-Sequence AVP is defined in RFC 3588 as Grouped, but
+;; there is no definition of the group - only an informal text stating
+;; that there should be a nonce (an OctetString) and a counter
+;; (integer)
+;;
+ E2E-Sequence ::= <AVP Header: 300 >
+ 2* { AVP }
+
+;; Backwards compatibility.
+@define Termination-Cause
+
+ DIAMETER_LOGOUT 1
+ DIAMETER_SERVICE_NOT_PROVIDED 2
+ DIAMETER_BAD_ANSWER 3
+ DIAMETER_ADMINISTRATIVE 4
+ DIAMETER_LINK_BROKEN 5
+ DIAMETER_AUTH_EXPIRED 6
+ DIAMETER_USER_MOVED 7
+ DIAMETER_SESSION_TIMEOUT 8
diff --git a/lib/diameter/src/app/diameter_gen_relay.dia b/lib/diameter/src/dict/relay.dia
index d86446e368..294014b093 100644
--- a/lib/diameter/src/app/diameter_gen_relay.dia
+++ b/lib/diameter/src/dict/relay.dia
@@ -18,7 +18,6 @@
;;
@id 0xFFFFFFFF
+@name diameter_gen_relay
@prefix diameter_relay
@vendor 0 IETF
-
-@inherits diameter_gen_base_rfc3588
diff --git a/lib/diameter/src/gen/.gitignore b/lib/diameter/src/gen/.gitignore
new file mode 100644
index 0000000000..3f32313f56
--- /dev/null
+++ b/lib/diameter/src/gen/.gitignore
@@ -0,0 +1,2 @@
+/diameter_dict_parser.erl
+/diameter_gen*rl
diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk
new file mode 100644
index 0000000000..01f9284881
--- /dev/null
+++ b/lib/diameter/src/modules.mk
@@ -0,0 +1,105 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2010-2012. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+# Runtime dictionary files in ./dict. Modules will be generated from
+# these are included in the app file.
+DICTS = \
+ base_rfc3588 \
+ base_accounting \
+ relay
+
+# The yecc grammar for the dictionary parser.
+DICT_YRL = \
+ diameter_dict_parser
+
+# Handwritten (runtime) modules included in the app file.
+RT_MODULES = \
+ base/diameter \
+ base/diameter_app \
+ base/diameter_callback \
+ base/diameter_capx \
+ base/diameter_config \
+ base/diameter_codec \
+ base/diameter_dict \
+ base/diameter_lib \
+ base/diameter_misc_sup \
+ base/diameter_peer \
+ base/diameter_peer_fsm \
+ base/diameter_peer_fsm_sup \
+ base/diameter_reg \
+ base/diameter_service \
+ base/diameter_service_sup \
+ base/diameter_session \
+ base/diameter_stats \
+ base/diameter_sup \
+ base/diameter_sync \
+ base/diameter_types \
+ base/diameter_watchdog \
+ base/diameter_watchdog_sup \
+ transport/diameter_etcp \
+ transport/diameter_etcp_sup \
+ transport/diameter_tcp \
+ transport/diameter_tcp_sup \
+ transport/diameter_sctp \
+ transport/diameter_sctp_sup \
+ transport/diameter_transport \
+ transport/diameter_transport_sup
+
+# Handwritten (compile time) modules not included in the app file.
+CT_MODULES = \
+ base/diameter_dbg \
+ base/diameter_info \
+ compiler/diameter_codegen \
+ compiler/diameter_exprecs \
+ compiler/diameter_nowarn \
+ compiler/diameter_dict_scanner \
+ compiler/diameter_dict_util \
+ compiler/diameter_make
+
+# Released hrl files in ../include intended for public consumption.
+EXTERNAL_HRLS = \
+ diameter.hrl \
+ diameter_gen.hrl
+
+# Released hrl files intended for private use.
+INTERNAL_HRLS = \
+ base/diameter_internal.hrl \
+ compiler/diameter_forms.hrl \
+ compiler/diameter_vsn.hrl
+
+# Released files relative to ../bin.
+BINS = \
+ diameterc
+
+# Released files relative to ../examples.
+EXAMPLES = \
+ code/GNUmakefile \
+ code/peer.erl \
+ code/client.erl \
+ code/client_cb.erl \
+ code/server.erl \
+ code/server_cb.erl \
+ code/relay.erl \
+ code/relay_cb.erl \
+ dict/rfc4004_mip.dia \
+ dict/rfc4005_nas.dia \
+ dict/rfc4006_cc.dia \
+ dict/rfc4072_eap.dia \
+ dict/rfc4590_digest.dia \
+ dict/rfc4740_sip.dia
diff --git a/lib/diameter/src/subdirs.mk b/lib/diameter/src/subdirs.mk
deleted file mode 100644
index 3e12d935bc..0000000000
--- a/lib/diameter/src/subdirs.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
-
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-
-SUB_DIRS = compiler app transport
-SUB_DIRECTORIES = $(SUB_DIRS) \ No newline at end of file
diff --git a/lib/diameter/src/transport/.gitignore b/lib/diameter/src/transport/.gitignore
deleted file mode 100644
index d9f072e262..0000000000
--- a/lib/diameter/src/transport/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-
-/depend.mk
-
diff --git a/lib/diameter/src/transport/Makefile b/lib/diameter/src/transport/Makefile
deleted file mode 100644
index 4b53100fd2..0000000000
--- a/lib/diameter/src/transport/Makefile
+++ /dev/null
@@ -1,141 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-#
-#
-
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-else
-include $(DIAMETER_TOP)/make/target.mk
-EBIN = ../../ebin
-include $(DIAMETER_TOP)/make/$(TARGET)/rules.mk
-endif
-
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-
-include ../../vsn.mk
-VSN=$(DIAMETER_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-
-RELSYSDIR = $(RELEASE_PATH)/lib/diameter-$(VSN)
-
-INCDIR = ../../include
-
-# ----------------------------------------------------
-# Target Specs
-# ----------------------------------------------------
-
-include modules.mk
-
-ERL_FILES = \
- $(MODULES:%=%.erl)
-
-TARGET_FILES = \
- $(MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-
-ifeq ($(TYPE),debug)
-ERL_COMPILE_FLAGS += -Ddebug
-endif
-
-include ../app/diameter.mk
-
-ERL_COMPILE_FLAGS += \
- $(DIAMETER_ERL_COMPILE_FLAGS) \
- -I$(INCDIR)
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug:
- @${MAKE} TYPE=debug opt
-
-opt: $(TARGET_FILES)
-
-clean:
- rm -f $(TARGET_FILES)
- rm -f errs core *~
- rm -f depend.mk
-
-docs:
-
-info:
- @echo ""
- @echo "ERL_FILES = $(ERL_FILES)"
- @echo "HRL_FILES = $(HRL_FILES)"
- @echo ""
- @echo "TARGET_FILES = $(TARGET_FILES)"
- @echo ""
-
-# ----------------------------------------------------
-# Special Build Targets
-# ----------------------------------------------------
-
-# Invoked from ../app to add modules to the app file.
-$(APP_TARGET): force
- M=`echo $(MODULES) | sed -e 's/^ *//' -e 's/ *$$//' -e 'y/ /,/'`; \
- echo "/%TRANSPORT_MODULES%/s//$$M/;w;q" | tr ';' '\n' \
- | ed -s $@
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-ifneq ($(ERL_TOP),)
-include $(ERL_TOP)/make/otp_release_targets.mk
-else
-include $(DIAMETER_TOP)/make/release_targets.mk
-endif
-
-release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
- $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
- $(INSTALL_DIR) $(RELSYSDIR)/src/transport
- $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)/src/transport
-
-release_docs_spec:
-
-force:
-
-# ----------------------------------------------------
-# Dependencies
-# ----------------------------------------------------
-
-depend: depend.mk
-
-# Generate dependencies makefile.
-depend.mk: ../app/depend.sed $(ERL_FILES) Makefile
- for f in $(MODULES); do \
- sed -f $< $$f.erl | sed "s@/@/$$f@"; \
- done \
- > $@
-
--include depend.mk
-
-.PHONY: clean debug depend docs force info opt release_docs_spec release_spec
diff --git a/lib/diameter/src/transport/diameter_etcp.erl b/lib/diameter/src/transport/diameter_etcp.erl
index d925d62545..cd62cf34fa 100644
--- a/lib/diameter/src/transport/diameter_etcp.erl
+++ b/lib/diameter/src/transport/diameter_etcp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -36,7 +36,9 @@
send/2,
close/1,
setopts/2,
- port/1]).
+ sockname/1,
+ peername/1,
+ getstat/1]).
%% child start
-export([start_link/1]).
@@ -113,10 +115,20 @@ close(Pid) ->
setopts(_, _) ->
ok.
-%% port/1
+%% sockname/1
-port(_) ->
- 3868. %% We have no local port: fake it.
+sockname(_) ->
+ {error, ?MODULE}.
+
+%% peername/1
+
+peername(_) ->
+ {error, ?MODULE}.
+
+%% getstat/1
+
+getstat(_) ->
+ {error, ?MODULE}.
%% start_link/1
diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl
index 46473e7bf1..79b8b851fb 100644
--- a/lib/diameter/src/transport/diameter_sctp.erl
+++ b/lib/diameter/src/transport/diameter_sctp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -37,9 +37,18 @@
code_change/3,
terminate/2]).
+-export([info/1]). %% service_info callback
+
+-export([ports/0,
+ ports/1]).
+
-include_lib("kernel/include/inet_sctp.hrl").
-include_lib("diameter/include/diameter.hrl").
+%% Keys into process dictionary.
+-define(INFO_KEY, info).
+-define(REF_KEY, ref).
+
-define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
%% The default port for a listener.
@@ -59,6 +68,7 @@
-record(transport,
{parent :: pid(),
mode :: {accept, pid()}
+ | accept
| {connect, {list(inet:ip_address()), uint(), list()}}
%% {RAs, RP, Errors}
| connect,
@@ -118,8 +128,8 @@ s({accept, Ref} = A, Addrs, Opts) ->
%% gen_sctp in order to be able to accept a new association only
%% *after* an accepting transport has been spawned.
-s({connect = C, _}, Addrs, Opts) ->
- diameter_sctp_sup:start_child({C, self(), Opts, Addrs}).
+s({connect = C, Ref}, Addrs, Opts) ->
+ diameter_sctp_sup:start_child({C, self(), Opts, Addrs, Ref}).
%% start_link/1
@@ -131,6 +141,24 @@ start_link(T) ->
diameter_lib:spawn_opts(server, [])).
%% ---------------------------------------------------------------------------
+%% # info/1
+%% ---------------------------------------------------------------------------
+
+info({gen_sctp, Sock}) ->
+ lists:flatmap(fun(K) -> info(K, Sock) end,
+ [{socket, sockname},
+ {peer, peername},
+ {statistics, getstat}]).
+
+info({K,F}, Sock) ->
+ case inet:F(Sock) of
+ {ok, V} ->
+ [{K,V}];
+ _ ->
+ []
+ end.
+
+%% ---------------------------------------------------------------------------
%% # init/1
%% ---------------------------------------------------------------------------
@@ -149,28 +177,36 @@ i({listen, Ref, {Opts, Addrs}}) ->
socket = Sock});
%% A connecting transport.
-i({connect, Pid, Opts, Addrs}) ->
+i({connect, Pid, Opts, Addrs, Ref}) ->
{[As, Ps], Rest} = proplists:split(Opts, [raddr, rport]),
RAs = [diameter_lib:ipaddr(A) || {raddr, A} <- As],
[RP] = [P || {rport, P} <- Ps] ++ [P || P <- [?DEFAULT_PORT], [] == Ps],
{LAs, Sock} = open(Addrs, Rest, 0),
+ putr(?REF_KEY, Ref),
proc_lib:init_ack({ok, self(), LAs}),
erlang:monitor(process, Pid),
#transport{parent = Pid,
mode = {connect, connect(Sock, RAs, RP, [])},
socket = Sock};
+i({connect, _, _, _} = T) -> %% from old code
+ x(T);
%% An accepting transport spawned by diameter.
-i({accept, Pid, LPid, Sock}) ->
+i({accept, Pid, LPid, Sock, Ref})
+ when is_pid(Pid) ->
+ putr(?REF_KEY, Ref),
proc_lib:init_ack({ok, self()}),
erlang:monitor(process, Pid),
erlang:monitor(process, LPid),
#transport{parent = Pid,
mode = {accept, LPid},
socket = Sock};
+i({accept, _, _, _} = T) -> %% from old code
+ x(T);
%% An accepting transport spawned at association establishment.
i({accept, Ref, LPid, Sock, Id}) ->
+ putr(?REF_KEY, Ref),
proc_lib:init_ack({ok, self()}),
MRef = erlang:monitor(process, LPid),
%% Wait for a signal that the transport has been started before
@@ -250,13 +286,33 @@ gen_opts(Opts) ->
[binary, {active, once} | Opts].
%% ---------------------------------------------------------------------------
+%% # ports/0-1
+%% ---------------------------------------------------------------------------
+
+ports() ->
+ Ts = diameter_reg:match({?MODULE, '_', '_'}),
+ [{type(T), N, Pid} || {{?MODULE, T, {_, {_, S}}}, Pid} <- Ts,
+ {ok, N} <- [inet:port(S)]].
+
+ports(Ref) ->
+ Ts = diameter_reg:match({?MODULE, '_', {Ref, '_'}}),
+ [{type(T), N, Pid} || {{?MODULE, T, {R, {_, S}}}, Pid} <- Ts,
+ R == Ref,
+ {ok, N} <- [inet:port(S)]].
+
+type(listener) ->
+ listen;
+type(T) ->
+ T.
+
+%% ---------------------------------------------------------------------------
%% # handle_call/3
%% ---------------------------------------------------------------------------
handle_call({{accept, Ref}, Pid}, _, #listener{ref = Ref,
count = N}
= S) ->
- {TPid, NewS} = accept(Pid, S),
+ {TPid, NewS} = accept(Ref, Pid, S),
{reply, {ok, TPid}, NewS#listener{count = N+1}};
handle_call(_, _, State) ->
@@ -294,6 +350,11 @@ terminate(_, #transport{assoc_id = undefined}) ->
ok;
terminate(_, #transport{socket = Sock,
+ mode = accept,
+ assoc_id = Id}) ->
+ close(Sock, Id);
+
+terminate(_, #transport{socket = Sock,
mode = {accept, _},
assoc_id = Id}) ->
close(Sock, Id);
@@ -306,6 +367,12 @@ terminate(_, #listener{socket = Sock}) ->
%% ---------------------------------------------------------------------------
+putr(Key, Val) ->
+ put({?MODULE, Key}, Val).
+
+getr(Key) ->
+ get({?MODULE, Key}).
+
%% start_timer/1
start_timer(#listener{count = 0} = S) ->
@@ -319,13 +386,16 @@ start_timer(S) ->
%% Incoming message from SCTP.
l({sctp, Sock, _RA, _RP, Data} = Msg, #listener{socket = Sock} = S) ->
- setopts(Sock),
- case find(Data, S) of
+ Id = assoc_id(Data),
+
+ try find(Id, Data, S) of
{TPid, NewS} ->
- TPid ! Msg,
+ TPid ! {peeloff, peeloff(Sock, Id, TPid), Msg},
NewS;
false ->
S
+ after
+ setopts(Sock)
end;
%% Transport is asking message to be sent. See send/3 for why the send
@@ -393,15 +463,19 @@ t(T,S) ->
%% transition/2
-%% Incoming message.
-transition({sctp, Sock, _RA, _RP, Data}, #transport{socket = Sock,
- mode = {accept, _}}
- = S) ->
- recv(Data, S);
+%% Listening process is transfering ownership of an association.
+transition({peeloff, Sock, {sctp, LSock, _RA, _RP, _Data} = Msg},
+ #transport{mode = {accept, _},
+ socket = LSock}
+ = S) ->
+ transition(Msg, S#transport{socket = Sock});
-transition({sctp, Sock, _RA, _RP, Data}, #transport{socket = Sock} = S) ->
+%% Incoming message.
+transition({sctp, _Sock, _RA, _RP, Data}, #transport{socket = Sock} = S) ->
setopts(Sock),
recv(Data, S);
+%% Don't match on Sock since in R15B01 it can be the listening socket
+%% in the (peeled-off) accept case, which is likely a bug.
%% Outgoing message.
transition({diameter, {send, Msg}}, S) ->
@@ -411,27 +485,46 @@ transition({diameter, {send, Msg}}, S) ->
transition({diameter, {close, Pid}}, #transport{parent = Pid}) ->
stop;
-%% Listener process has died.
-transition({'DOWN', _, process, Pid, _}, #transport{mode = {accept, Pid}}) ->
+%% TLS over SCTP is described in RFC 3436 but has limitations as
+%% described in RFC 6083. The latter describes DTLS over SCTP, which
+%% addresses these limitations, DTLS itself being described in RFC
+%% 4347. TLS is primarily used over TCP, which the current RFC 3588
+%% draft acknowledges by equating TLS with TLS/TCP and DTLS/SCTP.
+transition({diameter, {tls, _Ref, _Type, _Bool}}, _) ->
stop;
%% Parent process has died.
transition({'DOWN', _, process, Pid, _}, #transport{parent = Pid}) ->
- stop.
+ stop;
+
+%% Listener process has died.
+transition({'DOWN', _, process, Pid, _}, #transport{mode = {accept, Pid}}) ->
+ stop;
+
+%% Ditto but we have ownership of the association. It might be that
+%% we'll go down anyway though.
+transition({'DOWN', _, process, _Pid, _}, #transport{mode = accept}) ->
+ ok;
+
+%% Request for the local port number.
+transition({resolve_port, Pid}, #transport{socket = Sock})
+ when is_pid(Pid) ->
+ Pid ! inet:port(Sock),
+ ok.
%% Crash on anything unexpected.
-%% accept/2
+%% accept/3
%%
%% Start a new transport process or use one that's already been
%% started as a consequence of association establishment.
%% No pending associations: spawn a new transport.
-accept(Pid, #listener{socket = Sock,
- tmap = T,
- pending = {0,_} = Q}
- = S) ->
- Arg = {accept, Pid, self(), Sock},
+accept(Ref, Pid, #listener{socket = Sock,
+ tmap = T,
+ pending = {0,_} = Q}
+ = S) ->
+ Arg = {accept, Pid, self(), Sock, Ref},
{ok, TPid} = diameter_sctp_sup:start_child(Arg),
MRef = erlang:monitor(process, TPid),
ets:insert(T, [{MRef, TPid}, {TPid, MRef}]),
@@ -442,12 +535,12 @@ accept(Pid, #listener{socket = Sock,
%% Accepting transport has died. This can happen if a new transport is
%% started before the DOWN has arrived.
-accept(Pid, #listener{pending = [TPid | {0,_} = Q]} = S) ->
+accept(Ref, Pid, #listener{pending = [TPid | {0,_} = Q]} = S) ->
false = is_process_alive(TPid), %% assert
- accept(Pid, S#listener{pending = Q});
+ accept(Ref, Pid, S#listener{pending = Q});
%% Pending associations: attach to the first in the queue.
-accept(Pid, #listener{ref = Ref, pending = {N,Q}} = S) ->
+accept(_, Pid, #listener{ref = Ref, pending = {N,Q}} = S) ->
TPid = ets:first(Q),
TPid ! {Ref, Pid},
ets:delete(Q, TPid),
@@ -470,14 +563,6 @@ send(Bin, #transport{streams = {_, OS},
%% send/3
-%% Messages have to be sent from the controlling process, which is
-%% probably a bug. Sending from here causes an inet_reply, Sock,
-%% Status} message to be sent to the controlling process while
-%% gen_sctp:send/4 here hangs.
-send(StreamId, Bin, #transport{assoc_id = AId,
- mode = {accept, LPid}}) ->
- LPid ! {send, AId, StreamId, Bin};
-
send(StreamId, Bin, #transport{socket = Sock,
assoc_id = AId}) ->
send(Sock, AId, StreamId, Bin).
@@ -495,17 +580,22 @@ send(Sock, AssocId, Stream, Bin) ->
%% recv/2
%% Association established ...
-recv({[], #sctp_assoc_change{state = comm_up,
- outbound_streams = OS,
- inbound_streams = IS,
- assoc_id = Id}},
- #transport{assoc_id = undefined}
+recv({_, #sctp_assoc_change{state = comm_up,
+ outbound_streams = OS,
+ inbound_streams = IS,
+ assoc_id = Id}},
+ #transport{assoc_id = undefined,
+ mode = {T, _},
+ socket = Sock}
= S) ->
+ Ref = getr(?REF_KEY),
+ is_reference(Ref) %% started in new code
+ andalso publish(T, Ref, Id, Sock),
up(S#transport{assoc_id = Id,
streams = {IS, OS}});
%% ... or not: try the next address.
-recv({[], #sctp_assoc_change{} = E},
+recv({_, #sctp_assoc_change{} = E},
#transport{assoc_id = undefined,
socket = Sock,
mode = {connect = C, {[RA|RAs], RP, Es}}}
@@ -513,7 +603,7 @@ recv({[], #sctp_assoc_change{} = E},
S#transport{mode = {C, connect(Sock, RAs, RP, [{RA,E} | Es])}};
%% Lost association after establishment.
-recv({[], #sctp_assoc_change{}}, _) ->
+recv({_, #sctp_assoc_change{}}, _) ->
stop;
%% Inbound Diameter message.
@@ -523,7 +613,7 @@ recv({[#sctp_sndrcvinfo{stream = Id}], Bin}, #transport{parent = Pid})
bin = Bin}),
ok;
-recv({[], #sctp_shutdown_event{assoc_id = Id}},
+recv({_, #sctp_shutdown_event{assoc_id = Id}},
#transport{assoc_id = Id}) ->
stop;
@@ -536,12 +626,16 @@ recv({[], #sctp_shutdown_event{assoc_id = Id}},
%% disabled by default so don't handle it. We could simply disable
%% events we don't react to but don't.
-recv({[], #sctp_paddr_change{}}, _) ->
+recv({_, #sctp_paddr_change{}}, _) ->
ok;
-recv({[], #sctp_pdapi_event{}}, _) ->
+recv({_, #sctp_pdapi_event{}}, _) ->
ok.
+publish(T, Ref, Id, Sock) ->
+ true = diameter_reg:add_new({?MODULE, T, {Ref, {Id, Sock}}}),
+ putr(?INFO_KEY, {gen_sctp, Sock}). %% for info/1
+
%% up/1
up(#transport{parent = Pid,
@@ -551,21 +645,15 @@ up(#transport{parent = Pid,
S#transport{mode = C};
up(#transport{parent = Pid,
- mode = {accept, _}}
+ mode = {accept = A, _}}
= S) ->
diameter_peer:up(Pid),
- S.
+ S#transport{mode = A}.
-%% find/2
+%% find/3
-find({[#sctp_sndrcvinfo{assoc_id = Id}], _}
- = Data,
- #listener{tmap = T}
- = S) ->
- f(ets:lookup(T, Id), Data, S);
-
-find({_, Rec} = Data, #listener{tmap = T} = S) ->
- f(ets:lookup(T, assoc_id(Rec)), Data, S).
+find(Id, Data, #listener{tmap = T} = S) ->
+ f(ets:lookup(T, Id), Data, S).
%% New association and a transport waiting for one: use it.
f([],
@@ -606,17 +694,29 @@ f([], _, _) ->
%% assoc_id/1
-assoc_id(#sctp_shutdown_event{assoc_id = Id}) ->
+assoc_id({[#sctp_sndrcvinfo{assoc_id = Id}], _}) ->
+ Id;
+assoc_id({_, Rec}) ->
+ id(Rec).
+
+id(#sctp_shutdown_event{assoc_id = Id}) ->
Id;
-assoc_id(#sctp_assoc_change{assoc_id = Id}) ->
+id(#sctp_assoc_change{assoc_id = Id}) ->
Id;
-assoc_id(#sctp_sndrcvinfo{assoc_id = Id}) ->
+id(#sctp_sndrcvinfo{assoc_id = Id}) ->
Id;
-assoc_id(#sctp_paddr_change{assoc_id = Id}) ->
+id(#sctp_paddr_change{assoc_id = Id}) ->
Id;
-assoc_id(#sctp_adaptation_event{assoc_id = Id}) ->
+id(#sctp_adaptation_event{assoc_id = Id}) ->
Id.
+%% peeloff/3
+
+peeloff(LSock, Id, TPid) ->
+ {ok, Sock} = gen_sctp:peeloff(LSock, Id),
+ ok = gen_sctp:controlling_process(Sock, TPid),
+ Sock.
+
%% connect/4
connect(_, [], _, Reasons) ->
diff --git a/lib/diameter/src/transport/diameter_tcp.erl b/lib/diameter/src/transport/diameter_tcp.erl
index 653c114471..f3fbbee609 100644
--- a/lib/diameter/src/transport/diameter_tcp.erl
+++ b/lib/diameter/src/transport/diameter_tcp.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2010-2011. All Rights Reserved.
+%% Copyright Ericsson AB 2010-2012. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -37,14 +37,26 @@
code_change/3,
terminate/2]).
+-export([info/1]). %% service_info callback
+
+-export([ports/0,
+ ports/1]).
+
-include_lib("diameter/include/diameter.hrl").
+%% Keys into process dictionary.
+-define(INFO_KEY, info).
+-define(REF_KEY, ref).
+
-define(ERROR(T), erlang:error({T, ?MODULE, ?LINE})).
-define(DEFAULT_PORT, 3868). %% RFC 3588, ch 2.1
-define(LISTENER_TIMEOUT, 30000).
-define(FRAGMENT_TIMEOUT, 1000).
+%% cb_info passed to ssl.
+-define(TCP_CB(Mod), {Mod, tcp, tcp_closed, tcp_error}).
+
%% The same gen_server implementation supports three different kinds
%% of processes: an actual transport process, one that will club it to
%% death should the parent die before a connection is established, and
@@ -68,11 +80,11 @@
%% Accepting/connecting transport process state.
-record(transport,
- {socket :: inet:socket(), %% accept or connect socket
+ {socket :: inet:socket() | ssl:sslsock(), %% accept or connect socket
parent :: pid(), %% of process that started us
module :: module(), %% gen_tcp-like module
- frag = <<>> :: binary() | {tref(), frag()}}). %% message fragment
-
+ frag = <<>> :: binary() | {tref(), frag()}, %% message fragment
+ ssl :: boolean() | [term()]}). %% ssl options
%% The usual transport using gen_tcp can be replaced by anything
%% sufficiently gen_tcp-like by passing a 'module' option as the first
%% (for simplicity) transport option. The transport_module diameter_etcp
@@ -105,6 +117,33 @@ start_link(T) ->
diameter_lib:spawn_opts(server, [])).
%% ---------------------------------------------------------------------------
+%% # info/1
+%% ---------------------------------------------------------------------------
+
+info({Mod, Sock}) ->
+ lists:flatmap(fun(K) -> info(Mod, K, Sock) end,
+ [{socket, fun sockname/2},
+ {peer, fun peername/2},
+ {statistics, fun getstat/2}
+ | ssl_info(Mod, Sock)]).
+
+info(Mod, {K,F}, Sock) ->
+ case F(Mod, Sock) of
+ {ok, V} ->
+ [{K,V}];
+ _ ->
+ []
+ end.
+
+ssl_info(ssl = M, Sock) ->
+ [{M, ssl_info(Sock)}];
+ssl_info(_, _) ->
+ [].
+
+ssl_info(Sock) ->
+ [{peercert, C} || {ok, C} <- [ssl:peercert(Sock)]].
+
+%% ---------------------------------------------------------------------------
%% # init/1
%% ---------------------------------------------------------------------------
@@ -122,12 +161,18 @@ i({T, Ref, Mod, Pid, Opts, Addrs})
%% that does nothing but kill us with the parent until call
%% returns.
{ok, MPid} = diameter_tcp_sup:start_child(#monitor{parent = Pid}),
- Sock = i(T, Ref, Mod, Pid, Opts, Addrs),
+ {SslOpts, Rest} = ssl(Opts),
+ Sock = i(T, Ref, Mod, Pid, SslOpts, Rest, Addrs),
MPid ! {stop, self()}, %% tell the monitor to die
- setopts(Mod, Sock),
+ M = if SslOpts -> ssl; true -> Mod end,
+ setopts(M, Sock),
+ putr(?REF_KEY, Ref),
#transport{parent = Pid,
- module = Mod,
- socket = Sock};
+ module = M,
+ socket = Sock,
+ ssl = SslOpts};
+%% Put the reference in the process dictionary since we now use it
+%% advertise the ssl socket after TLS upgrade.
%% A monitor process to kill the transport if the parent dies.
i(#monitor{parent = Pid, transport = TPid} = S) ->
@@ -146,30 +191,58 @@ i({listen, LRef, APid, {Mod, Opts, Addrs}}) ->
LAddr = get_addr(LA, Addrs),
LPort = get_port(LP),
{ok, LSock} = Mod:listen(LPort, gen_opts(LAddr, Rest)),
+ true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}),
proc_lib:init_ack({ok, self(), {LAddr, LSock}}),
erlang:monitor(process, APid),
- true = diameter_reg:add_new({?MODULE, listener, {LRef, {LAddr, LSock}}}),
start_timer(#listener{socket = LSock}).
-%% i/6
+ssl(Opts) ->
+ {[SslOpts], Rest} = proplists:split(Opts, [ssl_options]),
+ {ssl_opts(SslOpts), Rest}.
+
+ssl_opts([]) ->
+ false;
+ssl_opts([{ssl_options, true}]) ->
+ true;
+ssl_opts([{ssl_options, Opts}])
+ when is_list(Opts) ->
+ Opts;
+ssl_opts(L) ->
+ ?ERROR({ssl_options, L}).
+
+%% i/7
+
+%% Establish a TLS connection before capabilities exchange ...
+i(Type, Ref, Mod, Pid, true, Opts, Addrs) ->
+ i(Type, Ref, ssl, Pid, [{cb_info, ?TCP_CB(Mod)} | Opts], Addrs);
-i(accept, Ref, Mod, Pid, Opts, Addrs) ->
+%% ... or not.
+i(Type, Ref, Mod, Pid, _, Opts, Addrs) ->
+ i(Type, Ref, Mod, Pid, Opts, Addrs).
+
+i(accept = T, Ref, Mod, Pid, Opts, Addrs) ->
{LAddr, LSock} = listener(Ref, {Mod, Opts, Addrs}),
proc_lib:init_ack({ok, self(), [LAddr]}),
Sock = ok(accept(Mod, LSock)),
+ publish(Mod, T, Ref, Sock),
diameter_peer:up(Pid),
Sock;
-i(connect, _, Mod, Pid, Opts, Addrs) ->
+i(connect = T, Ref, Mod, Pid, Opts, Addrs) ->
{[LA, RA, RP], Rest} = proplists:split(Opts, [ip, raddr, rport]),
LAddr = get_addr(LA, Addrs),
RAddr = get_addr(RA, []),
RPort = get_port(RP),
proc_lib:init_ack({ok, self(), [LAddr]}),
Sock = ok(connect(Mod, RAddr, RPort, gen_opts(LAddr, Rest))),
+ publish(Mod, T, Ref, Sock),
diameter_peer:up(Pid, {RAddr, RPort}),
Sock.
+publish(Mod, T, Ref, Sock) ->
+ true = diameter_reg:add_new({?MODULE, T, {Ref, Sock}}),
+ putr(?INFO_KEY, {Mod, Sock}). %% for info/1
+
ok({ok, T}) ->
T;
ok(No) ->
@@ -227,6 +300,43 @@ gen_opts(LAddr, Opts) ->
| Opts].
%% ---------------------------------------------------------------------------
+%% # ports/1
+%% ---------------------------------------------------------------------------
+
+ports() ->
+ Ts = diameter_reg:match({?MODULE, '_', '_'}),
+ [{type(T), resolve(T,S), Pid} || {{?MODULE, T, {_,S}}, Pid} <- Ts].
+
+ports(Ref) ->
+ Ts = diameter_reg:match({?MODULE, '_', {Ref, '_'}}),
+ [{type(T), resolve(T,S), Pid} || {{?MODULE, T, {R,S}}, Pid} <- Ts,
+ R == Ref].
+
+type(listener) ->
+ listen;
+type(T) ->
+ T.
+
+sock(listener, {_LAddr, Sock}) ->
+ Sock;
+sock(_, Sock) ->
+ Sock.
+
+resolve(Type, S) ->
+ Sock = sock(Type, S),
+ try
+ ok(portnr(Sock))
+ catch
+ _:_ -> Sock
+ end.
+
+portnr(Sock)
+ when is_port(Sock) ->
+ portnr(gen_tcp, Sock);
+portnr(Sock) ->
+ portnr(ssl, Sock).
+
+%% ---------------------------------------------------------------------------
%% # handle_call/3
%% ---------------------------------------------------------------------------
@@ -258,6 +368,8 @@ handle_info(T, #monitor{} = S) ->
%% # code_change/3
%% ---------------------------------------------------------------------------
+code_change(_, {transport, _, _, _, _} = S, _) ->
+ {ok, #transport{} = list_to_tuple(tuple_to_list(S) ++ [false])};
code_change(_, State, _) ->
{ok, State}.
@@ -270,6 +382,12 @@ terminate(_, _) ->
%% ---------------------------------------------------------------------------
+putr(Key, Val) ->
+ put({?MODULE, Key}, Val).
+
+getr(Key) ->
+ get({?MODULE, Key}).
+
%% start_timer/1
start_timer(#listener{count = 0} = S) ->
@@ -332,17 +450,56 @@ t(T,S) ->
%% transition/2
+%% Initial incoming message when we might need to upgrade to TLS:
+%% don't request another message until we know.
+transition({tcp, Sock, Bin}, #transport{socket = Sock,
+ parent = Pid,
+ frag = Head,
+ module = M,
+ ssl = Opts}
+ = S)
+ when is_list(Opts) ->
+ case recv1(Head, Bin) of
+ {Msg, B} when is_binary(Msg) ->
+ diameter_peer:recv(Pid, Msg),
+ S#transport{frag = B};
+ Frag ->
+ setopts(M, Sock),
+ S#transport{frag = Frag}
+ end;
+
%% Incoming message.
-transition({tcp, Sock, Data}, #transport{socket = Sock,
- module = M}
- = S) ->
+transition({P, Sock, Bin}, #transport{socket = Sock,
+ module = M,
+ ssl = B}
+ = S)
+ when P == tcp, not B;
+ P == ssl, B ->
setopts(M, Sock),
- recv(Data, S);
+ recv(Bin, S);
+
+%% Capabilties exchange has decided on whether or not to run over TLS.
+transition({diameter, {tls, Ref, Type, B}}, #transport{parent = Pid}
+ = S) ->
+ #transport{socket = Sock,
+ module = M}
+ = NS
+ = tls_handshake(Type, B, S),
+ Pid ! {diameter, {tls, Ref}},
+ setopts(M, Sock),
+ NS#transport{ssl = B};
-transition({tcp_closed, Sock}, #transport{socket = Sock}) ->
+transition({C, Sock}, #transport{socket = Sock,
+ ssl = B})
+ when C == tcp_closed, not B;
+ C == ssl_closed, B ->
stop;
-transition({tcp_error, Sock, _Reason} = T, #transport{socket = Sock} = S) ->
+transition({E, Sock, _Reason} = T, #transport{socket = Sock,
+ ssl = B}
+ = S)
+ when E == tcp_error, not B;
+ E == ssl_error, B ->
?ERROR({T,S});
%% Outgoing message.
@@ -367,10 +524,10 @@ transition({timeout, TRef, flush}, S) ->
flush(TRef, S);
%% Request for the local port number.
-transition({resolve_port, RPid}, #transport{socket = Sock,
- module = M})
- when is_pid(RPid) ->
- RPid ! lport(M, Sock),
+transition({resolve_port, Pid}, #transport{socket = Sock,
+ module = M})
+ when is_pid(Pid) ->
+ Pid ! portnr(M, Sock),
ok;
%% Parent process has died.
@@ -379,80 +536,122 @@ transition({'DOWN', _, process, Pid, _}, #transport{parent = Pid}) ->
%% Crash on anything unexpected.
+%% tls_handshake/3
+%%
+%% In the case that no tls message is received (eg. the service hasn't
+%% been configured to advertise TLS support) we will simply never ask
+%% for another TCP message, which will force the watchdog to
+%% eventually take us down.
+
+%% TLS has already been established with the connection.
+tls_handshake(_, _, #transport{ssl = true} = S) ->
+ S;
+
+%% Capabilities exchange negotiated TLS but transport was not
+%% configured with an options list.
+tls_handshake(_, true, #transport{ssl = false}) ->
+ ?ERROR(no_ssl_options);
+
+%% Capabilities exchange negotiated TLS: upgrade the connection.
+tls_handshake(Type, true, #transport{socket = Sock,
+ module = M,
+ ssl = Opts}
+ = S) ->
+ {ok, SSock} = tls(Type, Sock, [{cb_info, ?TCP_CB(M)} | Opts]),
+ Ref = getr(?REF_KEY),
+ is_reference(Ref) %% started in new code
+ andalso
+ (true = diameter_reg:add_new({?MODULE, Type, {Ref, SSock}})),
+ S#transport{socket = SSock,
+ module = ssl};
+
+%% Capabilities exchange has not negotiated TLS.
+tls_handshake(_, false, S) ->
+ S.
+
+tls(connect, Sock, Opts) ->
+ ssl:connect(Sock, Opts);
+tls(accept, Sock, Opts) ->
+ ssl:ssl_accept(Sock, Opts).
+
%% recv/2
%%
%% Reassemble fragmented messages and extract multple message sent
%% using Nagle.
recv(Bin, #transport{parent = Pid, frag = Head} = S) ->
- S#transport{frag = recv(Pid, Head, Bin)}.
+ case recv1(Head, Bin) of
+ {Msg, B} when is_binary(Msg) ->
+ diameter_peer:recv(Pid, Msg),
+ recv(B, S#transport{frag = <<>>});
+ Frag ->
+ S#transport{frag = Frag}
+ end.
-%% recv/3
+%% recv1/2
%% No previous fragment.
-recv(Pid, <<>>, Bin) ->
- rcv(Pid, Bin);
+recv1(<<>>, Bin) ->
+ rcv(Bin);
-recv(Pid, {TRef, Head}, Bin) ->
+recv1({TRef, Head}, Bin) ->
erlang:cancel_timer(TRef),
- rcv(Pid, Head, Bin).
+ rcv(Head, Bin).
-%% rcv/3
+%% rcv/2
%% Not even the first four bytes of the header.
-rcv(Pid, Head, Bin)
+rcv(Head, Bin)
when is_binary(Head) ->
- rcv(Pid, <<Head/binary, Bin/binary>>);
+ rcv(<<Head/binary, Bin/binary>>);
%% Or enough to know how many bytes to extract.
-rcv(Pid, {Len, N, Head, Acc}, Bin) ->
- rcv(Pid, Len, N + size(Bin), Head, [Bin | Acc]).
+rcv({Len, N, Head, Acc}, Bin) ->
+ rcv(Len, N + size(Bin), Head, [Bin | Acc]).
-%% rcv/5
+%% rcv/4
%% Extract a message for which we have all bytes.
-rcv(Pid, Len, N, Head, Acc)
+rcv(Len, N, Head, Acc)
when Len =< N ->
- rcv(Pid, rcv1(Pid, Len, bin(Head, Acc)));
+ rcv1(Len, bin(Head, Acc));
%% Wait for more packets.
-rcv(_, Len, N, Head, Acc) ->
+rcv(Len, N, Head, Acc) ->
{start_timer(), {Len, N, Head, Acc}}.
%% rcv/2
%% Nothing left.
-rcv(_, <<>> = Bin) ->
+rcv(<<>> = Bin) ->
Bin;
%% Well, this isn't good. Chances are things will go south from here
%% but if we're lucky then the bytes we have extend to an intended
%% message boundary and we can recover by simply discarding them,
%% which is the result of receiving them.
-rcv(Pid, <<_:1/binary, Len:24, _/binary>> = Bin)
+rcv(<<_:1/binary, Len:24, _/binary>> = Bin)
when Len < 20 ->
- diameter_peer:recv(Pid, Bin),
- <<>>;
+ {Bin, <<>>};
%% Enough bytes to extract a message.
-rcv(Pid, <<_:1/binary, Len:24, _/binary>> = Bin)
+rcv(<<_:1/binary, Len:24, _/binary>> = Bin)
when Len =< size(Bin) ->
- rcv(Pid, rcv1(Pid, Len, Bin));
+ rcv1(Len, Bin);
%% Or not: wait for more packets.
-rcv(_, <<_:1/binary, Len:24, _/binary>> = Head) ->
+rcv(<<_:1/binary, Len:24, _/binary>> = Head) ->
{start_timer(), {Len, size(Head), Head, []}};
%% Not even 4 bytes yet.
-rcv(_, Head) ->
+rcv(Head) ->
{start_timer(), Head}.
-%% rcv1/3
+%% rcv1/2
-rcv1(Pid, Len, Bin) ->
+rcv1(Len, Bin) ->
<<Msg:Len/binary, Rest/binary>> = Bin,
- diameter_peer:recv(Pid, Msg),
- Rest.
+ {Msg, Rest}.
%% bin/[12]
@@ -489,15 +688,18 @@ flush(_, S) ->
%% accept/2
-accept(gen_tcp, LSock) ->
- gen_tcp:accept(LSock);
+accept(ssl, LSock) ->
+ case ssl:transport_accept(LSock) of
+ {ok, Sock} ->
+ {ssl:ssl_accept(Sock), Sock};
+ {error, _} = No ->
+ No
+ end;
accept(Mod, LSock) ->
Mod:accept(LSock).
%% connect/4
-connect(gen_tcp, Host, Port, Opts) ->
- gen_tcp:connect(Host, Port, Opts);
connect(Mod, Host, Port, Opts) ->
Mod:connect(Host, Port, Opts).
@@ -505,6 +707,8 @@ connect(Mod, Host, Port, Opts) ->
send(gen_tcp, Sock, Bin) ->
gen_tcp:send(Sock, Bin);
+send(ssl, Sock, Bin) ->
+ ssl:send(Sock, Bin);
send(M, Sock, Bin) ->
M:send(Sock, Bin).
@@ -512,6 +716,8 @@ send(M, Sock, Bin) ->
setopts(gen_tcp, Sock, Opts) ->
inet:setopts(Sock, Opts);
+setopts(ssl, Sock, Opts) ->
+ ssl:setopts(Sock, Opts);
setopts(M, Sock, Opts) ->
M:setopts(Sock, Opts).
@@ -523,9 +729,36 @@ setopts(M, Sock) ->
X -> x({setopts, M, Sock, X}) %% possibly on peer disconnect
end.
-%% lport/2
+%% portnr/2
-lport(gen_tcp, Sock) ->
+portnr(gen_tcp, Sock) ->
inet:port(Sock);
-lport(M, Sock) ->
- M:port(Sock).
+portnr(M, Sock) ->
+ case M:sockname(Sock) of
+ {ok, {_Addr, PortNr}} ->
+ {ok, PortNr};
+ {error, _} = No ->
+ No
+ end.
+
+%% sockname/2
+
+sockname(gen_tcp, Sock) ->
+ inet:sockname(Sock);
+sockname(M, Sock) ->
+ M:sockname(Sock).
+
+%% peername/2
+
+peername(gen_tcp, Sock) ->
+ inet:peername(Sock);
+peername(M, Sock) ->
+ M:peername(Sock).
+
+%% getstat/2
+
+getstat(gen_tcp, Sock) ->
+ inet:getstat(Sock);
+getstat(M, Sock) ->
+ M:getstat(Sock).
+%% Note that ssl:getstat/1 doesn't yet exist in R15B01.
diff --git a/lib/diameter/src/transport/diameter_transport.erl b/lib/diameter/src/transport/diameter_transport.erl
new file mode 100644
index 0000000000..ff4b6bbc6d
--- /dev/null
+++ b/lib/diameter/src/transport/diameter_transport.erl
@@ -0,0 +1,55 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2012. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(diameter_transport).
+
+%%
+%% This module implements a transport start function that
+%% evaluates its config argument.
+%%
+
+%% Transport start functions
+-export([start/3,
+ select/3,
+ eval/3]).
+
+%% start/3
+
+%% Call a start function in this module ...
+start(T, Svc, {F,A}) ->
+ start(T, Svc, {?MODULE, F, [A]});
+
+%% ... or some other.
+start(T, Svc, F) ->
+ diameter_lib:eval([F, T, Svc]).
+
+%% select/3
+%%
+%% A start function that whose config argument is expected to return a
+%% new start function.
+
+select(T, Svc, F) ->
+ start(T, Svc, diameter_lib:eval([F, T, Svc])).
+
+%% eval/3
+%%
+%% A start function that simply evaluates its config argument.
+
+eval(_, _, F) ->
+ diameter_lib:eval(F).
diff --git a/lib/diameter/src/transport/modules.mk b/lib/diameter/src/transport/modules.mk
deleted file mode 100644
index a0dc3cf2c0..0000000000
--- a/lib/diameter/src/transport/modules.mk
+++ /dev/null
@@ -1,29 +0,0 @@
-#-*-makefile-*- ; force emacs to enter makefile-mode
-
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2010-2011. All Rights Reserved.
-#
-# The contents of this file are subject to the Erlang Public License,
-# Version 1.1, (the "License"); you may not use this file except in
-# compliance with the License. You should have received a copy of the
-# Erlang Public License along with this software. If not, it can be
-# retrieved online at http://www.erlang.org/.
-#
-# Software distributed under the License is distributed on an "AS IS"
-# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-# the License for the specific language governing rights and limitations
-# under the License.
-#
-# %CopyrightEnd%
-
-MODULES = \
- diameter_etcp \
- diameter_etcp_sup \
- diameter_tcp \
- diameter_tcp_sup \
- diameter_sctp \
- diameter_sctp_sup \
- diameter_transport_sup
-
-HRL_FILES =